第⼀部分 Spring 概述
第1节 Spring 简介
- Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使用最多的Java EE 企业应用开源框架。
- Spring 官方网址:http://spring.io/
- 我们经常说的 Spring 其实指的是Spring Framework(spring 框架)
第2节 Spring 发展历程
- 1997年 IBM 提出了EJB的思想; 1998年,SUN 制定开发标准规范EJB1.0; 1999年,EJB 1.1发布; 2001年,EJB 2.0发布; 2003年,EJB 2.1发布; 2006年,EJB 3.0发布;
- Rod Johnson(spring之父)
- Expert One-to-One J2EE Design and Development(2002) 阐述了J2EE使用EJB开发设计的优点及解决方案
- Expert One-to-One J2EE Development without EJB(2004) 阐述了J2EE开发不使用EJB的解决方式(Spring雏形)
- 2017年9月份发布了Spring 的最新版本 Spring 5.0通用版(GA)
第3节 Spring 的优势
整个 Spring 优势,传达出⼀个信号,Spring 是⼀个综合性,且有很强的思想性框架,每学习⼀天,就能体会到它的⼀些优势。
- 方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。 - AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。 - 声明式事务的支持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。 - 方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。 - 方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。 - 降低 JavaEE API 的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。 - 源码是经典的 Java 学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。
第4节 Spring 的核心结构
Spring是⼀个分层非常清晰并且依赖关系、职责定位非常明确的轻量级框架,主要包括几个大模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决方案的零侵入的轻量级框架。
- Spring核心容器(Core Container) 容器是Spring框架最核心的部分,它管理着Spring应用中 bean 的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为 Spring 提供了 DI 的功能。基于bean工厂,我们还会发现有多种Spring应用上下文的实现。所有的Spring模块都构建于核心容器之上。
- 面向切面编程(AOP) Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础,与DI⼀样,AOP可以帮助应用对象解耦。
- 数据访问与集成(Data Access/Integration)
Spring 的 JDBC和 DAO模块封装了大量的样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败而引起的问题。 另外,Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进行了集成,如Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。 - Web 该模块提供了 Spring MVC 框架给 Web 应用,还提供了多种构建和其它应用交互的远程调用方案。Spring MVC框架在Web 层提升了应用的松耦合水平。
- Test 为了使得开发者能够很方便的进行测试,Spring提供了测试模块以致力于Spring应用的测试。通过该模块,Spring为使用Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。
第二部分 核心思想
注意:IOC 和 AOP 不是 Spring 提出的,在 Spring 之前就已经存在,只不过更偏向于理论化,Spring在技术层次把这两个思想做了非常好的实现(Java)
第1节 IoC
1.1 什么是 IoC?
IoC:Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现。描述的事情:Java开发领域对象的创建,管理的问题。
- 传统开发方式:比如类A依赖于类B,往往会在类A中 new ⼀个B的对象
- IoC思想下开发方式:我们不用自己去 new 对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问 IoC 容器要即可,我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不用考虑对象的创建、管理等⼀系列事情)
为什么叫做控制反转?
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境了(spring框架、IoC容器)
1.2 IoC 解决了什么问题
IoC解决对象之间的耦合问题
1.3 IoC和DI的区别
DI:Dependancy Injection(依赖注入)
怎么理解:IoC 和 DI 描述的是同⼀件事情,只不过角度不一样罢了
第2节 AOP
2.1 什么是AOP
- AOP:Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程
- AOP是OOP的延续,从OOP说起
- OOP 三大特征:封装、继承和多态
- OOP 是一种垂直继承体系
OOP编程思想可以解决大多数的代码重复问题,但是有⼀些情况是处理不了的,比如下面的在顶级父类 Animal 中的多个方法中相同位置出现了重复代码,OOP就解决不了。
横切逻辑代码
横切逻辑代码存在什么问题?
- 横切代码重复问题
- 横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不方便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄无声息的把横切逻辑代码应用到原有的业务逻辑中,达到和原来⼀样的效果,这个是比较难的。
2.2 AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
2.3 为什么叫做面向切面编程
- 「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑。
- 「面」:横切逻辑代码往往要影响的是很多个方法,每⼀个方法都如同⼀个点,多个点构成面,有⼀个面的概念在里面。
第三部分 Spring IoC 应用
第1节 Spring IoC基础
1.1 BeanFactory与ApplicationContext区别
- BeanFactory是Spring框架中IoC容器的顶层接口,它只是用来定义⼀些基础功能,定义⼀些基础规范,而ApplicationContext是它的⼀个子接口,所以ApplicationContext 是具备BeanFactory提供的全部功能的。
- 通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的高级接口,比 BeanFactory要拥有更多的功能,比如说国际化支持和资源访问(xml,java配置类)等等
1.1.1 启动 IoC 容器的方式
1.1.1.1 Java环境下启动IoC容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
1.1.1.2 Web环境下启动IoC容器
- 从xml 启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置Spring ioc容器的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--使用监听器启动Spring的IOC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- 从配置类启动容器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<paramvalue>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.cyd.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>
1.2 纯xml模式
1.2.1 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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
1.2.2 实例化Bean的三种方式
(1)方式一:使用无参构造函数
在默认情况下,它会通过反射调用无参构造函数来创建对象。如果类中没有无参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.cyd.service.impl.TransferServiceImpl"></bean>
(2)方式二:使用静态方法创建
在实际开发中,我们使用的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供⼀个创建对象的方法,恰好这个方法是static修饰的方法,即是此种情况。
例如,我们在做Jdbc操作时,会用到 java.sql.Connection 接口的实现类,如果是mysql数据库,那么用的就是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection()
,因为我们要注册驱动,还要提供URL和凭证信息,用 DriverManager.getConnection 方法来获取连接。
那么在实际开发中,尤其早期的项目没有使用Spring 框架来管理对象的创建,但是在设计时使用了工厂模式解耦,那么当接入Spring之后,工厂类创建对象就具有和上述例⼦相同特征,即可采用此种方式配置。
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>
(3)方式三:使用实例化方法创建
此种方式和上面静态方法创建其实类似,区别是用于获取对象的方法不再是static修饰的了,而是类中的⼀ 个普通方法。此种方式比静态方法创建的使用几率要高一些。
在早期开发的项⽬中,工厂类中的方法有可能是静态的,也有可能是非静态方法,当是非静态方法时,即可采用下面的配置方式:
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory" class="com.cyd.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
1.2.3 Bean的作用范围及生命周期
(1)作用范围的改变
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它支持配置的方式改变作用范围。作用范围官方提供的说明如下图:
在上图中提供的这些选项中,我们实际开发中用到最多的作用范围就是 singleton(单例模式)和 prototype(原型模式,也叫多例模式)。配置方式参考下面的代码:
<!--配置service对象-->
<bean id="transferService" class="com.cyd.service.impl.TransferServiceImpl" scope="singleton"></bean>
(2)不同作用范围的生命周期
- 单例模式:singleton
对象出生:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象生命周期与容器相同。 - 多例模式:prototype
对象出生:当使用对象时,创建新的对象实例。
对象活着:只要对象在使用中,就⼀直活着。
对象死亡:当对象长时间不使用时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,Spring框架只负责创建,不负责销毁。
1.2.4 Bean标签属性
在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使用此标签配置,Bean标签的属性如下:
- id属性: 用于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
- class属性:用于指定创建Bean对象的全限定类名。
- name属性:用于给bean提供⼀个或多个名称。多个名称用空格分隔。
- factory-bean属性:用于指定创建当前bean对象的工厂bean的唯⼀标识。当指定了此属性之后,class属性失效。
- factory-method属性:用于指定创建当前bean对象的工厂方法,如配合factory-bean属性使用,则class属性失效。如配合class属性使用,则方法必须是static的。
- scope属性:用于指定bean对象的作用范围。通常情况下就是singleton。当要用到多例模式时,可以配置为prototype。
- init-method属性:用于指定bean对象的初始化方法,此方法会在bean对象装配后调用。必须是⼀个无参方法。
- destory-method属性:用于指定bean对象的销毁方法,此方法会在bean对象销毁前执行。它只能为scope是singleton时起作用。
1.2.5 DI 依赖注入的xml配置
(1)依赖注入分类
- 按照注入的方式分类
(1)构造函数注入:顾名思义,就是利用带参构造函数实现对类成员的数据赋值。
(2)set方法注入:它是通过类成员的set⽅法实现数据的注入。(使用最多的) - 按照注入的数据类型分类
- 基本类型和String
注入的数据类型是基本类型或者是字符串类型的数据。 - 其他Bean类型
注入的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。 - 复杂类型(集合类型)
注入的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
- 基本类型和String
(2)构造函数注入
依赖注入的配置实现之构造函数注入顾名思义,就是利用构造函数实现对类成员的赋值。它的使用要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有无参构造时,则必须提供构造函数参数的注入,否则Spring 框架会报错。
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
private String name;
private int sex;
private float money;
public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
this.connectionUtils = connectionUtils;
this.name = name;
this.sex = sex;
this.money = money;
}
}
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<!--<constructor-arg index="0" ref="connectionUtils"/>
<constructor-arg index="1" value="zhangsan"/>
<constructor-arg index="2" value="1"/>
<constructor-arg index="3" value="100.5"/>-->
<!--name:按照参数名称注入,index按照参数索引位置注入-->
<constructor-arg name="connectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="zhangsan"/>
<constructor-arg name="sex" value="1"/>
<constructor-arg name="money" value="100.6"/>
</bean>
在使用构造函数注入时,涉及的标签是 constructor-arg ,该标签有如下属性:
- name: 用于给构造函数中指定名称的参数赋值。
- index: 用于给构造函数中指定索引位置的参数赋值。
- value: 用于指定基本类型或者String类型的数据。
- ref: 用于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。
(3)依赖注入的配置实现之set方法注入
顾名思义,就是利用字段的set方法实现赋值的注入方式。此种方式在实际开发中是使用最多的注入方式。
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
private String name;
private int sex;
private float money;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setName(String name) {
this.name = name;
}
public void setSex(int sex) {
this.sex = sex;
}
public void setMoney(float money) {
this.money = money;
}
}
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<!--set注入使用property标签,如果注入的是另外一个bean那么使用ref属性,如果注入的是普通值那么使用的是value属性-->
<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="zhangsan"/>
<property name="sex" value="1"/>
<property name="money" value="100.3"/>
</bean>
在使用 set 方法注入时,需要使用 property 标签,该标签属性如下:
- name: 指定注入时调用的set方法名称。(注:不包含set这三个字母,druid连接池指定属性名称)
- value: 指定注入的数据。它支持基本类型和String类型。
- ref: 指定注入的数据。它支持其他bean类型。写的是其他bean的唯⼀标识
(4)复杂数据类型注入
首先,解释⼀下复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接口(键值对)
接下来就是注⼊的方式的选择,只能在构造函数和set方法中选择,我们的示例选⽤set方法注入。
public class JdbcAccountDaoImpl implements AccountDao {
private String[] myArray;
private Map<String,String> myMap;
private Set<String> mySet;
private Properties myProperties;
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyProperties(Properties myProperties) {
this.myProperties = myProperties;
}
}
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<!--set注入注入复杂数据类型-->
<property name="myArray">
<array>
<value>array1</value>
<value>array2</value>
<value>array3</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
</set>
</property>
<property name="myProperties">
<props>
<prop key="prop1">value1</prop>
<prop key="prop2">value2</prop>
</props>
</property>
</bean>
在List结构的集合数据注入时, array
, list
, set
这三个标签通用,另外注值的 value
标签内部可以直接写值,也可以使用 bean
标签配置⼀个对象,或者用 ref
标签引用⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注入时, map
标签使用 entry
子标签实现数据注入,entry
标签可以使用key和value属性指定存入map中的数据。使用value-ref属性指定已经配置好的bean的引用。同时entry
标签中也可以使用ref
标签,但是不能使用 bean
标签。而 property
标签中不能使用 ref
或者 bean
标签引用对象。
1.3 xml与注解相结合模式
注意:
- 实际企业开发中,纯xml模式使用已经很少了
- 引入注解功能,不需要引入额外的jar
- xml+注解结合模式,xml 文件依然存在,所以,Spring IOC容器的启动仍然从加载xml开始
- 哪些bean的定义写在xml中,哪些bean的定义使用注解
第三方 jar 中的 bean 定义在xml,比如德鲁伊数据库连接池。自己开发的bean定义使用注解
1.3.1 xml中标签与注解的对应(IoC)
xml形式中标签指的是bean标签
1.3.2 DI 依赖注入的注解实现方式
1、@Autowired(推荐使用)
- @Autowired为Spring提供的注解,需要导入包 org.springframework.beans.factory.annotation.Autowired。
- @Autowired采取的策略为按照类型注入。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
如上代码所示,这样装配会去Spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产生一个问题,当⼀个类型有多个bean值的时候,会造成无法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着 @Qualifier 使用。
@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
这个时候我们就可以通过类型和名称定位到我们想注⼊的对象。
2、@Resource
- @Resource 注解由 J2EE 提供,需要导入包 javax.annotation.Resource。
- @Resource 默认按照 ByName 自动注入。
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
- 如果同时指定了 name 和 type,则从Spring上下文中找到唯⼀匹配的bean进行装配,找不到则抛出异常。
- 如果指定了 name,则从上下文中查找名称(id)匹配的 bean 进行装配,找不到则抛出异常。
- 如果指定了 type,则从上下文中找到类似匹配的唯⼀bean 进行装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定name,又没有指定type,则自动按照byName 方式进行装配。
注意:
@Resource 在 Jdk 11中已经移除,如果要使用,需要单独引⼊jar包
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
1.4 纯注解模式
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动。
对应注解
- @Configuration 注解,表名当前类是⼀个配置类
- @ComponentScan 注解,替代context:component-scan
- @PropertySource,引入外部属性配置⽂件
- @Import 引入其它配置类
- @Value 对变量赋值,可以直接赋值,也可以使用 ${} 读取资源配置文件中的信息
- @Bean 将方法返回对象加入 SpringIOC 容器
第2节 Spring IOC高级特性
2.1 lazy-Init 延迟加载
Bean的延迟加载(延迟创建)
ApplicationContext 容器的默认行为是在启动服务器时将所有 singleton bean 提前进行实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。
比如:
<bean id="testBean" class="cn.cyd.LazyBean" />
<!-- 该bean默认的设置为: -->
<bean id="testBean" calss="cn.cyd.LazyBean" lazy-init="false" />
lazy-init=“false”,立即加载,表示在spring启动时,立刻进行实例化。
如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。
<bean id="testBean" calss="cn.cyd.LazyBean" lazy-init="true" />
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是第⼀次向容器通过getBean 索取 bean 时实例化的。
如果⼀个设置了立即加载的 bean1,引用了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,而 bean2 由于被 bean1 引用,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调用时才被实例化的规则。
也可以配置所有bean的延迟加载策略,即在容器层次标签中通过在元素上使用 “default-lazy-init” 属性来控制延时初始化。如下面配置:
<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>
如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,而是调用 getBean 方法实例化的。
应用场景
(1)开启延迟加载⼀定程度提高容器启动和运转性能
(2)对于不常使用的 Bean 设置延迟加载,这样偶尔使用的时候再加载,不必要从⼀开始该 Bean 就占用资源
2.2 FactoryBean 和 BeanFactory
- BeanFactory接口是容器的顶级接口,定义了容器的⼀些基础行为,负责⽣产和管理Bean的⼀个工厂,具体使用它下面的子接口类型,比如ApplicationContext;此处我们重点分析FactoryBean
- Spring中Bean有两种,⼀种是普通Bean,⼀种是工厂Bean(FactoryBean),FactoryBean可以生成某一个类型的Bean实例(返回给我们),也就是说我们可以借助于它自定义 Bean 的创建过程。
- Bean创建的三种方式中的静态方法和实例化方法和FactoryBean作用类似,FactoryBean使用较多,尤其在Spring框架⼀些组件中会使用,还有其他框架和Spring框架整合时使用。
// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓 存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}
Company
public class Company {
private String name;
private String address;
private int scale;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getScale() {
return scale;
}
public void setScale(int scale) {
this.scale = scale;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", scale=" + scale +
'}';
}
}
CompanyFactoryBean
public class CompanyFactoryBean implements FactoryBean<Company> {
private String companyInfo; // 公司名称,地址,规模
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public Company getObject() throws Exception {
// 模拟创建复杂对象Company
Company company = new Company();
String[] strings = companyInfo.split(",");
company.setName(strings[0]);
company.setAddress(strings[1]);
company.setScale(Integer.parseInt(strings[2]));
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
xml配置
<bean id="companyBean" class="com.cyd.factory.CompanyFactoryBean">
<property name="companyInfo" value="京东,中关村,500"/>
</bean>
测试,获取 FactoryBean 产生的对象
Object companyBean = applicationContext.getBean("companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:Company{name='拉勾', address='中关村', scale=500}
测试,获取 FactoryBean,需要在id之前添加“&”
Object companyBean = applicationContext.getBean("&companyBean");
System.out.println("bean:" + companyBean);
// 结果如下
bean:com.lagou.edu.factory.CompanyFactoryBean@53f6fd09
2.3 后置处理器
Spring提供了两种后处理bean的扩展接口,分别为 BeanPostProcessor 和 BeanFactoryPostProcessor,两者在使用上是有所区别的。
- 工厂初始化(BeanFactory)—> Bean对象
- 在BeanFactory初始化之后可以使用 BeanFactoryPostProcessor 进行后置处理做⼀些事情
- 在Bean对象实例化(并不是Bean的整个生命周期完成)之后可以使用BeanPostProcessor进行后置处理做⼀些事情
- 注意:对象不⼀定是spring bean,而spring bean⼀定是个对象
Spring Bean的生命周期
2.3.1 BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
该接口提供了两个方法,分别在Bean的初始化方法前和初始化方法后执行,具体这个初始化方法指的是什么方法,类似我们在定义bean时,定义了init-method所指定的方法
定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进行处理。如果要对具体的某个bean处理,可以通过方法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发生在Spring容器的实例化和依赖注入之后。
2.3.2 BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的工厂进行处理,典型应用:PropertyPlaceholderConfigurer
@FunctionalInterface
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
此接口只提供了⼀个方法,方法参数为ConfigurableListableBeanFactory,该参数类型定义了一些方法
其中有个方法名为getBeanDefinition 的方法,我们可以根据此方法,找到我们定义 bean 的 BeanDefinition 对象。然后我们可以对定义的属性进行修改,以下是BeanDefinition中的方法
- 方法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以手动修改bean标签中所定义的属性值。
- BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition
- 注意:调用 BeanFactoryPostProcessor 方法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象
第四部分 Spring AOP 应用
AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。
第1节 AOP 相关术语
1.1 业务主线
在讲解AOP术语之前,我们先来看⼀下下面这两张图,它们就是第三部分案例需求的扩展(针对这些扩展的需求,我们只进行分析,在此基础上去进⼀步回顾AOP,不进行实现)
上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来大量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独立性很差。而下图中是采用了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。
1.2 AOP 术语
- 连接点: 方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是⼀种候选点
- 切入点: 指定AOP思想想要影响的具体方法是哪些,描述感兴趣的方法
- Advice增强:
第⼀个层次:指的是横切逻辑
第⼆个层次:方位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做方位点,描述的是具体的特殊时机) - Aspect切⾯: 切⾯概念是对上述概念的⼀个综合
Aspect切面 = 切入点 + 增强
= 切⼊点(锁定方法) + 方位点(锁定方法中的特殊时机)+ 横切逻辑
众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码
第2节 Spring中AOP的代理选择
- Spring 实现AOP思想使用的是 动态代理 技术
- 默认情况下,Spring会根据被代理对象是否实现接口来选择使用
JDK
还是CGLIB
。当被代理对象没有实现任何接口时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择JDK官方的代理技术,不过我们可以通过配置的方式,让Spring强制使用CGLIB。
第3节 Spring中AOP的配置方式
在Spring的AOP配置中,也和IoC配置⼀样,支持3类配置方式。
- 第⼀类:使用XML配置
- 第⼆类:使用XML+注解组合配置
- 第三类:使用纯注解配置
第4节 Spring中AOP实现
需求:横切逻辑代码是打印日志,希望把打印日志的逻辑织入到目标方法的特定位置(service 层 transfer 方法)
4.1 XML 模式
Spring是模块化开发的框架,使用AOP就引入AOP的jar
1、坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2、AOP 核心配置
<!--
Spring基于XML的AOP配置前期准备:
在spring的配置⽂件中加⼊aop的约束
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
Spring基于XML的AOP配置步骤:
第⼀步:把通知Bean交给Spring管理
第⼆步:使⽤aop:config开始aop的配置
第三步:使⽤aop:aspect配置切⾯
第四步:使⽤对应的标签配置通知的类型
⼊⻔案例采⽤前置通知,标签为aop:before -->
<!--把通知bean交给spring来管理-->
<bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切⾯-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public * com.cyd.service.impl.TransferServiceImpl.updateAccountByCardNo(com.cyd.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>
3、细节
(1)关于切入点表达式
上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo 方法进行增强,在其执行之前,输出了记录日志的语句。这里面,我们接触了⼀个比较陌生的名称:切入点表达式,它是做什么的呢?我们往下看。
- 概念及作用
切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作用是用于对符合语法格式的连接点进行增强。它是AspectJ表达式的⼀部分。 - 关于AspectJ
AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。 - 切入点表达式使用示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
全匹配⽅式:
public void com.cyd.service.impl.TransferServiceImpl.updateAccountByCardNo(com.cyd.pojo.Account)
访问修饰符可以省略
void com.cyd.service.impl.TransferServiceImpl.updateAccountByCardNo(com.cyd.pojo.Account)
返回值可以使⽤*,表示任意返回值
* com.cyd.service.impl.TransferServiceImpl.updateAccountByCardNo(com.cyd.pojo.Account)
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
* ....TransferServiceImpl.updateAccountByCardNo(com.cyd.pojo.Account)
包名可以使⽤..表示当前包及其⼦包
* ..TransferServiceImpl.updateAccountByCardNo(com.cyd.pojo.Account)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.cyd.pojo.Account)
参数列表,可以使⽤具体类型
基本类型直接写类型名称 : int
引⽤类型必须写全限定类名:java.lang.String
参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
全通配⽅式:
* *..*.*(..)
(2)改变代理方式的配置
在前面我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接口,则采用基于接口的动态代理。当被代理对象没有实现任何接口的时候,Spring会自动切换到基于子类的动态代理方式。
但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用cglib提供的方式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理(即cglib的方式),配置的方式有两种:
- 使用aop:config 标签配置
<aop:config proxy-target-class="true">
- 使⽤aop:aspectj-autoproxy 标签配置
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
(3)五种通知类型
- 前置通知
配置方式:aop:before 标签
<!--
作⽤:
⽤于配置前置通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定前置通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:before method="printLog" pointcut-ref="pointcut1"></aop:before>
执行时机: 前置通知永远都会在切⼊点方法(业务核心方法)执行之前执行。
细节: 前置通知可以获取切⼊点方法的参数,并对其进行增强。
- 正常执行时通知
配置方式
<!--
作⽤:
⽤于配置正常执⾏时通知
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定后置通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-returning method="afterReturningPrintLog" pointcutref="pt1"></aop:after-returning>
- 异常通知
配置方式
<!--
作⽤:
⽤于配置异常通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定异常通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
执行时机: 异常通知的执行时机是在切⼊点方法(业务核心方法)执⾏产生异常之后,异常通知执行。如果切
⼊点方法执行没有产生异常,则异常通知不会执行。
细节: 异常通知不仅可以获取切⼊点方法执行的参数,也可以获取切⼊点方法执行产生的异常信息。
- 最终通知
配置方式
<!--
作⽤:
⽤于指定最终通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定最终通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
执行时机: 最终通知的执行时机是在切⼊点方法(业务核心方法)执行完成之后,切入点方法返回之前执行。换句话说,无论切入点方法执行是否产生异常,它都会在返回之前执行。
细节: 最终通知执行时,可以获取到通知方法的参数。同时它可以做⼀些清理操作。
- 环绕通知
配置方式
<!--
作⽤:
⽤于配置环绕通知。
出现位置:
它只能出现在aop:aspect标签的内部
属性:
method:⽤于指定环绕通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
特别说明
环绕通知,它是有别于前面四种通知类型外的特殊通知。前面四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。而环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的方式,控制增强代码何时执行的通知类型。它里面借助的 ProceedingJoinPoint
接口及其实现类,实现手动触发切入点方法的调用。
4.2 XML+注解模式
(1)XML 中开启 Spring 对注解 AOP 的支持
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
(2)示例
/**
* 模拟记录⽇志
*/
@Component
@Aspect
public class LogUtil {
/**
* 我们在xml中已经使⽤了通⽤切⼊点表达式,供多个切⾯使⽤,那么在注解中如何使⽤呢?
* 第⼀步:编写⼀个⽅法
* 第⼆步:在⽅法使⽤@Pointcut注解
* 第三步:给注解的value属性提供切⼊点表达式
* 细节:
* 1.在引⽤切⼊点表达式时,必须是⽅法名+(),例如"pointcut()"。
* 2.在当前切⾯中使⽤,可以直接写⽅法名。在其他切⾯中使⽤必须是全限定⽅法名。
*/
@Pointcut("execution(* com.cyd.service.impl.*.*(..))")
public void pointcut(){}
@Before("pointcut()")
public void beforePrintLog(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置通知:beforePrintLog,参数是:"+ Arrays.toString(args));
}
@AfterReturning(value = "pointcut()",returning = "rtValue")
public void afterReturningPrintLog(Object rtValue){
System.out.println("后置通知:afterReturningPrintLog,返回值是:"+rtValue);
}
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowingPrintLog(Throwable e){
System.out.println("异常通知:afterThrowingPrintLog,异常是:"+e);
}
@After("pointcut()")
public void afterPrintLog(){
System.out.println("最终通知:afterPrintLog");
}
/**
* 环绕通知
* @param pjp
* @return
*/
@Around("pointcut()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
//定义返回值
Object rtValue = null;
try{
//前置通知
System.out.println("前置通知");
//1.获取参数
Object[] args = pjp.getArgs();
//2.执⾏切⼊点⽅法
rtValue = pjp.proceed(args);
//后置通知
System.out.println("后置通知");
}catch (Throwable t){
//异常通知
System.out.println("异常通知");
t.printStackTrace();
}finally {
//最终通知
System.out.println("最终通知");
}
return rtValue;
}
}
4.3 注解模式
在使用注解驱动开发AOP
时,我们要明确的就是,是注解替换掉配置文件中的下面这行配置:
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
在配置类中使用如下注解进行替换上述配置
@Configuration
@ComponentScan("com.cyd")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
}
第5节 Spring 声明式事务的⽀持
- 编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
- 声明式事务:通过xml或者注解配置的方式达到事务控制的目的,叫做声明式事务
5.1 事务回顾
5.1.1 事务的概念
事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从而确保了数据的准确与安全。
例如:A——B转帐,对应于如下两条sql语句:
/*转出账户减钱*/
update account set money=money-100 where name=‘a’;
/**转⼊账户加钱*/
update account set money=money+100 where name=‘b’;
这两条语句的执行,要么全部成功,要么全部不成功。
5.1.2 事务的四大特性
- 原子性(Atomicity) 原子性是指事务是⼀个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
从操作的角度来描述,事务中的各个操作要么都成功要么都失败 - 一致性(Consistency) 事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
例如转账前A有1000,B有1000。转账后A+B也得是2000。
⼀致性是从数据的角度来说的,(1000,1000) (900,1100),不应该出现(900,1000) - 隔离性(Isolation) 事务的隔离性是多个用户并发访问数据库时,数据库为每⼀个用户开启的事务,每个事务不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
比如:事务1给员工涨工资2000,但是事务1尚未被提交,员工发起事务2查询工资,发现工资涨了2000块钱,读到了事务1尚未提交的数据(脏读) - 持久性(Durability)
持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
5.1.3 事务的隔离级别
不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题
- 脏读: ⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
- 不可重复读: ⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)
场景:
员工A发起事务1,查询工资,工资为1w,此时事务1尚未关闭
财务人员发起了事务2,给员工A张了2000块钱,并且提交了事务
员工A通过事务1再次发起查询请求,发现工资为1.2w,原来读出来1w读不到了,叫做不可重复读 - 虚读(幻读): ⼀个线程中的事务读到了另外⼀个线程中已经提交的 insert 或者 delete 的数据(前后条数不⼀样)
场景:
事务1查询所有工资为1w的员工的总数,查询出来了10个人,此时事务尚未关闭
事务2财务⼈员发起,新来员工,工资1w,向表中插⼊了2条数据,并且提交了事务
事务1再次查询工资为1w的员工个数,发现有12个人,见了鬼了
数据库共定义了四种隔离级别:
- Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。(串行化) 最高
- Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。(幻读有可能发⽣) 第⼆该机制下会对要update的行进行加锁
- Read committed(读已提交):可避免脏读情况发生。不可重复读和幻读⼀定会发生。 第三
- Read uncommitted(读未提交):最低级别,以上情况均无法保证。(读未提交) 最低
注意:级别依次升高,效率依次降低
- MySQL的默认隔离级别是:REPEATABLE READ
- 查询当前使用的隔离级别: select @@tx_isolation;
- 设置MySQL事务的隔离级别: set session transaction isolation level xxx; (设置的是当前mysql连接会话的,并不是永久改变的)
5.1.4 事务的传播行为
事务往往在service层进行控制,如果出现service层方法A调用了另外⼀个service层方法B,A和B方法本身都已经被添加了事务控制,那么A调用B的时候,就需要进行事务的⼀些协商,这就叫做事务的传播行为。
A调用B,我们站在B的角度来观察来定义事务的传播行为
5.2 Spring中事务的API
- mybatis: sqlSession.commit();
- hibernate: session.commit();
PlatformTransactionManager
public interface PlatformTransactionManager {
/**
* 获取事务状态信息
*/
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
/**
* 提交事务
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* 回滚事务
*/
void rollback(TransactionStatus status) throws TransactionException;
}
作用
此接口是 Spring 的事务管理器核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应用。在Spring框架中,也为我们内置了⼀些具体策略,例如:DataSourceTransactionManager
,HibernateTransactionManager
等等。(HibernateTransactionManager
事务管理器在 spring-orm-5.1.12.RELEASE.jar
中)
- Spring JdbcTemplate(数据库操作工具)、Mybatis(mybatis-spring.jar)
——>DataSourceTransactionManager - Hibernate框架 ——>HibernateTransactionManager
DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使用AOP(动态代理)来将事务控制逻辑织入到业务代码。
5.3 Spring 声明式事务配置
5.3.1 纯xml模式
- 导入jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
- xml 配置
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--定制事务细节,传播⾏为、隔离级别等-->
<tx:attributes>
<!--⼀般性配置-->
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
<!--针对查询的覆盖性配置-->
<tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--advice-ref指向增强=横切逻辑+⽅位-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.cyd.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
5.3.2 基于XML+注解
- xml配置
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的⽀持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在接口、类或者方法上添加@Transactional注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
5.3.3 基于纯注解
Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置文件中的 <tx:annotation-driven transactionmanager="transactionManager"/>
配置。
在Spring的配置类上添加 @EnableTransactionManagement 注解即可。
@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}