spring事务管理器(PlatformTransactionManager)
spring事务属性(TransactionDefinition)
spring概述
spring是一个轻量级的容器,用于管理业务相关对象的。其两大内核:Ioc(控制反转)、aop(面向切面编程)。容器功能包括依赖注入(Dependency,简称DI)和依赖查找(Dependency Lookup)。
耦合与解耦
耦合是程序间的依赖关系包括:类间的依赖、方法间的依赖
解耦是降低程序间的依赖关系,实际开发中应该做到,编译器不依赖,运行时才依赖。
解耦的思路有两种:
1.使用反射来创建对象,而避免使用new关键字
2.通过读取配置文件来获取要创建的对象全限定类名
spring容器启动
在web项目中,web.xml配置spring容器的方式有三种:
• ContextLoaderListener
• ContextLoadServlet
• ContextLoaderPlugin
-
ContextLoaderListener(监听方式)
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/classes/applicationContext-*.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
[注]还可以通过
<import resource="classpath:/spring/spring-xxx.xml"/>
的方式把其他的配置引进来 -
ContextLoadServlet(servlet方式)
在spring3.0以后不再支持,建议使用监听方式
<servlet> <servlet-name>context</servlet-name> <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
-
ContextLoaderPlugin(plugin配置方式)
适用于spring与strust等整合,在struts-config.xml里面配置一个ContextLoaderPlugIn,用于spring的初始化工作
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"> <set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml,/WEB-INF/action-servlet.xml" /> </plug-in>
Ioc控制反转
-
定义
Ioc(控制反转)将对象的创建过程交给容器,让容器管理对象的生命周期,如创建,初始化,销毁等。
目的是削减计算机程序的耦合。 -
容器实例创建
Ioc核心容器接口:ApplicationContext
a.ClassPathXmlApplicationContext 它可以加载类路径下的配置文件
b.FileSystemXmlApplicationContext 它可以加载磁盘任意路径下的配置文件(必须有访问权限)
c.AnnotationConfigApplicationContext 它是用于读取注解创建容器的ApplicationContext ac = new ClassPathXmlApplicationContext("Spring/bean.xml");
Bean创建
-
默认构造器创建
使用bean标签。
配置id 和class 属性后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建<bean id = "userService" class="com.ferao.Service.UserService"></bean>
-
普通工厂中的方法
普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring 容器)
<bean id= "instanceFactory" class="com.ferao.InstanceFactory"></bean> <bean id= "userService" factory-bean="instanceFactory" factory-method="getUserService"> </bean>
-
工厂中的静态方法
创建对象(使用某个类中的静态方法创建对象,并存入Spring容器)
<bean id= "userService" factory-bean="com.ferao.Factory.InstanceFactory" factory-method="getUserService"> </bean>
Bean作用域
-
定义
当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域(scope)。Spring支持如下5种作用域
-
作用域
作用域 内容 singleton(单例模式) 容器中,使用singleton定义的Bean将只有一个实例 protorype(原型模式) 通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例 request(请求模式) 每次HTTP请求,使用request定义的Bean都将产生一个新实例 session(会话模式) 每次HTTP Session,使用session定义的Bean都将产生一个新实例 global-session(全局会话模式) 全局的HTTP Session,每个使用session定义的Bean都将产生一个新实例 -
使用推荐
如果不指定Bean的作用域,Spring默认使用singleton作用域。
Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。
因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。 -
示例
<!-- 默认的作用域:singleton --> <bean id="p1" class="com.abc.Person" /> <!-- 指定的作用域:prototype --> <bean id="p2" class="com.abc.Person" scope="prototype" />
//加载类路径下的beans.xml文件以初始化Spring容器 ApplicationContext context = new ClassPathXmlApplicationContext(); //分两次分别取同一个Bean,比较二者是否是同一个对象 System.out.println(context.getBean("p1") == context.getBean("p1")); System.out.println(context.getBean("p2") == context.getBean("p2"));
控制台结果:
true
false
Bean生命周期
-
单例对象
单例对象的生命周期和容器相同
出生:容器创建
活着:容器存在,对象存活
死亡:容器销毁,对象消亡 -
多例对象
出生:使用对象时spring容器创建
活着:使用过程中存在
死亡:长时间不用,由java的垃圾回收机制回收
Bean属性
属性 | 内容 |
---|---|
id | bean的唯一标识符 |
class | 类的全限定名(包名+类名) |
name | 别名(alias),使用getBean(“name”),支持设置多个别名,之间用英文逗号分割 |
abstract | bean是否为抽象类,默认abstract=“false”,如果设为true,将不能被实例化 |
autowire-candidate | 默认true,如果为false,那么该bean不能作为其他bean自动装配的候选者 |
init-method | 创建一个bean之后调用该方法,初始化方法必须是一个无参方法 |
factory-bean | 指定创建bean的工厂类对象,class属性将失效 |
factory-method | 指定创建bean的工厂方法 |
scope | 作用范围,它包括singleton、prototype、request、session |
destroy-method | 销毁bean之前可以执行指定的方法。注意:必须满足scope=“singleton”,并且destroy方法参数个数不能超过1,并且参数类型只能为boolean |
depends-on | 一个bean实例化的过程需要依赖于另一个bean的初始化 |
依赖注入
-
定义
由于对象的创建过程交给容器,在当前类用到其他类的对象时,我们只需要在配置文件中说明需要引入相关类的信息即可,依赖关系的维护称之为依赖注入。
-
注入数据类型
• 基本类型和String
• 其他bean类型(在配置文件中或者注解配置过的bean)
• 复杂类型/集合类型 -
注入数据方式
• 构造函数方法
• set方法
• 注解方法
构造函数方法注入
-
描述
使用constructor-arg标签放置在bean标签的内部即可
-
标签属性
• type:指定要注入的数据的类型
• index:给构造函数中指定索引位置的参数赋值
• name:给构造函数中指定名称的参数赋值
• value:指定数据
Set方法注入
-
描述
使用property标签并放置在bean标签的内部
-
标签属性
• name: 指定注入时所调用的set方法名称
• value: 提供数据
• ref: 指定其他的bean 类型的值(容器中的bean对象)
注解方法注入
-
描述
以注解形式注入,降低程序间的耦合。是配置文件的另一种形式,有四方面需要注解化:
• 创建容器对象
• 注入数据
• 作用范围
• 生命周期 -
注解化
-
创建容器对象
@Component
(替代bean标签),放置于类上即可将当前类对象存入spring容器中。@Component内有value属性,用于指定bean的id,默认当前类名,且首字母小写
Controller、Service、Repository注解的作用和属性与Component是一模一样的,存在是为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
-
注入数据
@Autowired
(替代property标签),放置于变量、方法上,可自动按照类型注入,只要容器中有唯一的bean对象类型和要注入的变量类型匹配,就可以注入成功
@Qualifier:在按照类中注入的基础上再按照名称注入。它是给类成员注入时使用,不能单独使用,但是在给方法参数注入时可以.用于指定注入的,该注解有value属性,用于指定注入的bean的id@Resource:直接按照bean的id注入,它可以单独使用,该注解有name属性,用于指定bean的id
@Value:用于注入基本类型和String类型的数据,该注解有value属性,用于指定数据的值,它可以使用spring中spEL(也就是spring的el表达式)
-
改变作用范围
它们的作用集合在
<bean>
标签中使用scope属性实现的功能是一样的 -
和生命周期相关
它们的作用就和在
<bean>
标签中使用init-method和destroy-method的作用是一样的-
注解注入
重要细节 -Autowired注解与Resource注解的区别 相同点 Resource的作用相当于Autowired,均可在字段或属性的setter方法上 不同点 1. 提供方:Autowired是由org.springframework.beans.factory.annotation.Autowired提供,换句话说就是由spring提供 Resource是由javax.annotation.Resource提供,即j2ee提供,且jdk1.6及以上 2. 注入方式:Autowired只按照byType注入; Resource默认按byName自动注入,也提供按照byType注入 3. 属性:Autowired:按照类型装配依赖对象,默认情况下它要求依赖 对象必须存在,如果允许Null值,可以设置它requested属性为 false。如果开发者想要使用按名称装配,可以结合Qualifier注 解一起使用。 Resource:有两个重要的属性:name和type.name属性指定 byName,如果没有指定Name属性,当注解标注在字段上,即默 认取字段的名称作为bean名称寻找依赖对象。当注解标注在属性 的setter方法上,即默认取属性名作为bean名称寻找依赖对象, 需要注意的是,Resource如果没有指定name属性,并且按照默 认的名称找不到依赖对象时,Resource注解会回退到按类型装 配。但一旦指定了Name属性,就只能按照名称装配了 ========以上三个注入都只能注入其他bean类型的数据,而基本类型 和String类型无法使用上述注解实现,以外,集合类型的注入只能通过 xml来实现 改变作用范围示例 scope 作用:用于指定bean的作用范围 属性:value:指定范围的取值,常用取值:singletion prototype
注入后的使用
问题点 项目中遇到一个问题:项目启动完成前,在A类中注入B类,并调用B类的某个方法。 那么调用B类的这个方法写在哪里呢,我选择写到构造器里, 但是构造器先于Spring注入执行,那么执行构造器时,注入B类肯定为null, 于是选择了构造器注入,解决问题 执行顺序 静态变量或静态语句块–>实例变量或初始化语句块–>构造方法–>Spring注入 常见的三种使用注入方式
注解注入 @Controller public class FooController { @Autowired private FooService fooService; //简单的使用例子,下同 public List<Foo> listFoo() { return fooService.list(); } }
这种注入方式应该是笔者目前为止开发中见到的最常见的注入方式.原因很简单:
1.注入方式非常简单:加入要注入的字段,附上@Autowired,即可完成。
2.使得整体代码简洁明了,看起来美观大方。构造器注入 @Controller public class FooController { private final FooService fooService; @Autowired public FooController(FooService fooService) { this.fooService = fooService; } //使用方式上同,略 }
在Spring4.x版本中推荐的注入方式就是这种,相较于上面的注解注入方式而言,就显
得有点难看,特别是当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。对于从注解
注入转过来+有强迫症的开发人员来说,简直可以说是无法忍受。循序渐进示例
写了一个类,分别有两个构造器,一个是注入一个Bean的构造器,一个是注入两个Bean的构造器 public class ConstructorAutowiredTest { private User user; private Role role; public ConstructorAutowiredTest() { } public ConstructorAutowiredTest(User user) { this.user = user; } public ConstructorAutowiredTest(User user, Role role) { this.user = user; this.role = role; } public void test(){ System.out.println("user: "+user); System.out.println("role: "+role); } }
Spring的配置文件context.xml: <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd "> <!-- 使Spring关注Annotation --> <context:annotation-config/> <bean class="com.mytest.demo.model.Role" id="role"> <property name="name" value="testRole"/> <property name="id" value="2"/> </bean> <bean class="com.mytest.demo.model.User" id="user"> <property name="id" value="1"/> <property name="name" value="testUser"/> </bean> <bean class="com.mytest.demo.autowired.ConstructorAutowiredTest" id="test"/> </beans>
那么,该类三个构造器,Spring会使用哪个构造器初始化 ConstructorAutowiredTest这个Bean呢?写个测试便知: public class TestBeanAutowiredConstructor { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(); context.setConfigLocation("context.xml"); context.refresh(); ConstructorAutowiredTest test = (ConstructorAutowiredTest) context.getBean("test"); test.test(); } }
从这里我们可以看出来,此时三个构造器中Spring使用了无参构造器。 我们换一个方式,将无参构造器注释掉,看看Spring到底会使用哪个构造器呢? 同样执行test方法测试,控制台打印:
此时控制台报错,大致意思是Bean的实例化失败了,没有无参的构造器方法调用。 此时有个疑问,明明构造器中的参数都是Bean,为什么不能初始化, 一定要使用无参的构造器呢?是否是因为有两个构造器的原因? 此时我们再注释掉任意一个构造函数, 使测试类只有一个带参构造函数:
// public ConstructorAutowiredTest() { // } public ConstructorAutowiredTest(User user) { this.user = user; } // public ConstructorAutowiredTest(User user, Role role) { // this.user = user; // this.role = role; // } 再次运行测试类,控制台打印:
如果是注释掉第二个构造函数,则结果是两个对象都有。 从这里我们可以看出,如果只有一个构造器,且参数为IOC容器中的Bean, 将会执行自动注入。这里又有一个疑问,这也太不科学了吧, 强制用户一定只能写一个构造器? 这时我们猜想@Autowired注解是否能解决这种问题? 来试试吧。我们将构造器全部解除注释,将第三个构造器打上@Autowired注解:
public ConstructorAutowiredTest() { } public ConstructorAutowiredTest(User user) { this.user = user; } @Autowired public ConstructorAutowiredTest(User user, Role role) { this.user = user; this.role = role; }
不出所料,@Autowired注解可以解决这种问题, 此时Spring将使用有注解的构造函数进行Bean的初始化。 那么,如果有两个@Autowired注解呢?结果肯定是报错, 因为@Autowired的默认属性required是为true的, 也就是说两个required=true的构造器,Spring不知道使用哪一个。但如果是这样写的话:
public ConstructorAutowiredTest() { } @Autowired(required = false) public ConstructorAutowiredTest(User user) { this.user = user; } @Autowired(required = false) public ConstructorAutowiredTest(User user, Role role) { this.user = user; this.role = role; }
使用参数最多的那一个构造器来初始化Bean。 又如果两个有参构造器顺序调换又是怎样的呢?一个required为false一个为true, 结果又是怎样的呢?这里直接给出答案,顺序调换依然使用多参数构造器, 并且required只要有一个true就会报错。有兴趣的读者可以自己试试, 下面将深入源码分析构造器注入的过程,相信上述所有疑问都能得到解答。
setter注入 @Controller public class FooController { private FooService fooService; //使用方式上同,略 @Autowired public void setFooService(FooService fooService) { this.fooService = fooService; } }
在Spring3.x刚推出的时候,推荐使用注入的就是这种,笔者现在也基本没看到过这种
注解方式,写起来麻烦,当初推荐Spring自然也有他的道理,这里我们引用一下Spring当时的原话:
构造器注入参数太多了,显得很笨重,另外setter的方式能够让类在之后重新配置或者重新注入。构造器注入的好处 spring文档 构造器注入的方式,能够保证注入的组件不可变,并且确保需要的依赖不为空。此外, 构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。 解释 依赖不可变:其实说的就是final关键字,这里不再多解释了。 依赖不为空(省去了我们对其检查):当要实例化FooController的时候, 由于自己实现了有参数的构造函数,所以不会调用默认构造函数, 那么就需要Spring容器传入所需要的参数,所以就两种情况: 1、有该类型的参数->传入,OK 。 2:无该类型的参数->报错。所以保证不会为空 完全初始化的状态:这个可以跟上面的依赖不为空结合起来, 向构造器传参之前,要确保注入的内容不为空, 那么肯定要调用依赖组件的构造方法完成实例化。 而在Java类加载实例化的过程中,构造方法是最后一步 (之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。) 所以返回来的都是初始化之后的状态。 总结 保证依赖不可变(final关键字) 保证依赖不为空(省去了我们对其检查) 保证返回客户端(调用)的代码的时候是完全初始化的状态 避免了循环依赖 提升了代码的可复用性 构造器注入的缺点 当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。 set注入的好处 setter的方式能够让类在之后重新配置或者重新注入 set注入的缺点 写起来麻烦 field注入的缺点 对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。 而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。 还值得一提另外一点是:使用field注入可能会导致循环依赖,即A里面注入B,B里面又注入A public class A { @Autowired private B b; } public class B { @Autowired private A a; } 如果使用构造器注入,在spring项目启动的时候,就会抛出: BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference? 从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错, 在使用那个bean的时候才会报错。
-
-
AOP切面
-
定义
面向切面编程,是面向对象编程(OOP)的一种补充。当方法执行时,AOP可以劫持方法,在方法执行前或后添加额外的逻辑。AOP 框架有很多种,对应的实现方式也不同,Spring 中的 AOP 是通过动态代理实现的。
在Spring AOP中,有 4 种类型通知(advices)的支持:
• 前置通知
• 后置通知
• 异常通知
• 环绕通知 -
通知类型
类型 连接点 实现接口 前置通知 该方法执行前运行 org.springframework.aop.MethodBeforeAdvice 后置通知 通知方法会在目标方法返回或异常后调用 org.springframework.aop.AfterReturningAdvice 引入通知 在不改变原有方法的基础上却可以增加新的方法 org.springframework.aop.IntroductionInterceptor 异常通知 运行方法抛出异常后 org.springframework.aop.ThrowsAdvice 环绕通知 通知方法会将目标方法封装起来, 环绕方法执行运行,结合以上这三个通知 org.aop.alliance.intercept.MethodInterceptor -
操作术语