1、Spring 概述
1.1、Spring 简介
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 SpringMVC和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE 企业应⽤开源框架。我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。Spring 官方网站:Spring | Home
1.2、Spring 的优势
- 方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更专注于上层的应⽤。
- AOP编程的支持
通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过AOP轻松应付。
- 声明式事务的支持
@Transactional注解,可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
- 方便程序的测试
可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的事情。
- 方便集成各种优秀框架
Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接⽀持。
- 降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤难度⼤为降低。
- 源码是经典的学习示例
Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。
1.3、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)/Aspects Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应⽤系统中开发切⾯的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。
- 数据访问与集成(Data Access/Integration),Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。
- Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。
- Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。
1.4、Spring 的框架版本
详见官网地址:Spring Framework
2、核心思想
2.1、IOC
2.1.1、什么是IOC
IoC Inversion of Control (控制反转/反转控制),需要注意它是⼀个技术思想,不是⼀个技术实现。
- 描述的事情:Java开发领域对象的创建,管理的问题。
- 传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
- IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
- 控制:指的是对象创建(实例化、管理)的权利
- 反转:控制权交给外部环境了(spring框架、IoC容器)
2.1.2、IOC解决了什么
IoC解决对象之间的耦合问题。
2.1.3、IOC和DI的区别
DI(Dependancy Injection(依赖注⼊)),IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了。
2.2、AOP
2.2.1、什么是AOP
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程。AOP是OOP的延续,从OOP说起;OOP三⼤特征:封装、继承和多态,oop是⼀种垂直继承体系。
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了。
横切逻辑代码:
横切逻辑代码存在什么问题:
- 横切代码重复问题
- 横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析。
2.2.2、AOP解决了什么
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复!
2.2.3、为什么叫面向切面编程
- 「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
- 「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯
3、Spring IOC 应用
3.1、IOC 基础
beans.xml(定义需要实例化对象的类的全限定类名以及类之间的依赖关系描述):
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
<!--id标识对象,class是类的全限定类名-->
<bean id="accountDao" class="com.blnp.net.edu.dao.impl.JdbcTemplateDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService" class="com.blnp.net.edu.service.impl.TransferServiceImpl">
<!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
<!--配置新增的三个Bean-->
<bean id="connectionUtils" class="com.blnp.net.edu.utils.ConnectionUtils"></bean>
<!--事务管理器-->
<bean id="transactionManager" class="com.blnp.net.edu.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<!--代理对象工厂-->
<bean id="proxyFactory" class="com.blnp.net.edu.factory.ProxyFactory">
<property name="TransactionManager" ref="transactionManager"/>
</bean>
</beans>
MyBeanFactory(通过反射技术来实例化对象并维护对象之间的依赖关系):
public class MyBeanFactory {
/**
* 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
* 任务二:对外提供获取实例对象的接口(根据id获取)
*/
private static Map<String,Object> map = new HashMap<>(); // 存储对象
static {
// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
// 加载xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 解析xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> beanList = rootElement.selectNodes("//bean");
for (int i = 0; i < beanList.size(); i++) {
Element element = beanList.get(i);
// 处理每个bean元素,获取到该元素的id 和 class 属性
String id = element.attributeValue("id"); // accountDao
String clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
// 通过反射技术实例化对象
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance(); // 实例化之后的对象
// 存储到map中待用
map.put(id,o);
}
// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
// 有property子元素的bean就有传值需求
List<Element> propertyList = rootElement.selectNodes("//property");
// 解析property,获取父元素
for (int i = 0; i < propertyList.size(); i++) {
Element element = propertyList.get(i); //<property name="AccountDao" ref="accountDao"></property>
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
// 找到当前需要被处理依赖关系的bean
Element parent = element.getParent();
// 调用父元素对象的反射功能
String parentId = parent.attributeValue("id");
Object parentObject = map.get(parentId);
// 遍历父对象中的所有方法,找到"set" + name
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if(method.getName().equalsIgnoreCase("set" + name)) { // 该方法就是 setAccountDao(AccountDao accountDao)
method.invoke(parentObject,map.get(ref));
}
}
// 把处理之后的parentObject重新放到map中
map.put(parentId,parentObject);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
// 任务二:对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id) {
return map.get(id);
}
}
上述的两个类就是模拟实现Spring IOC的相关功能。
3.1.1、BeanFactory 与 ApplicationContext 区别
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等。而启动 IOC 容器的方式主要有以下:
- Java环境下启动IoC容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
- Web环境下启动IoC容器
1.从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>
<listenerclass>
org.springframework.web.context.ContextLoaderListener
</listenerclass>
</listener>
</web-app>
2.从配置类启动容器
<!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
</paramvalue>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>
contextConfigLocation
</param-name>
<param-value>
com.blnp.net.edu.SpringConfig
</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>
org.springframework.web.context.ContextLoaderListener
</listenerclass>
</listener>
</web-app>
3.1.2、纯XML模式
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">
2、实例化bean的方式
方式一:使用无参构造函数
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<!--配置service对象-->
<bean id="userService" class="com.blnp.net.service.impl.TransferServiceImpl">
</bean>
方式二:使用静态方法创建
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程 中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,即是此种情况。
例如,我们在做Jdbc操作时,会⽤到java.sql.Connection接⼝的实现类,如果是mysql数据库,那么⽤的就 是JDBC4Connection,但是我们不会去写 JDBC4Connection connection = new JDBC4Connection();因为我们要注册驱动,还要提供URL和凭证信息,⽤DriverManager.getConnection ⽅法来获取连接。
那么在实际开发中,尤其早期的项⽬没有使⽤Spring框架来管理对象的创建,但是在设计时使⽤了⼯⼚模式 解耦,那么当接⼊spring之后,⼯⼚类创建对象就具有和上述例⼦相同特征,即可采⽤此种⽅式配置。
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.blnp.net.factory.BeanFactory" factory-method="getTransferService"></bean>
方式三:使用实例化方法创建
和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀ 个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。
在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可 采⽤下⾯的配置⽅式:
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory" class="com.blnp.net.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factorymethod="getTransferService"></bean>
3、Bean的生命周期
作用范围的改变:
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:
在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置⽅式参考下⾯的代码:
<!--配置service对象-->
<bean id="transferService" class="com.blnp.net.service.impl.TransferServiceImpl" scope="singleton">
</bean>
不同作用范围的生命周期:
- 单例模式:singleton
- 对象出⽣:当创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象⼀直活着。
- 对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
- 多例模式:prototype
- 对象出⽣:当使⽤对象时,创建新的对象实例。
- 对象活着:只要对象在使⽤中,就⼀直活着。
- 对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
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时起作⽤。
5、DI依赖注入的XML配置
依赖注入的分类:
- 按照注⼊的⽅式分类
- 构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
- set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
- 按照注⼊的数据类型分类
- 基本类型和String:注⼊的数据类型是基本类型或者是字符串类型的数据。
- 其他Bean类型:注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
- 复杂类型(集合类型):注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
- 依赖注⼊的配置实现之构造函数注⼊:就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
Java类:
public class JdbcAccountDaoImpl implements AccountDao {
private ConnectionUtils connectionUtils;
private String name;
private Integer age;
private float money;
public void setConnectionUtils(ConnectionUtils connectionUtils,String name,Integer age,float money) {
this.connectionUtils = connectionUtils;
this.name = name;
this.age = age;
this.money = money;
}
public void init() {
System.out.println("初始化方法.....");
}
public void destory() {
System.out.println("销毁方法......");
}
}
xml声明配置:
<bean id="accountDao" class="com.blnp.net.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<constructor-arg name="ConnectionUtils" ref="connectionUtils"/>
<constructor-arg name="name" value="王二麻子"/>
<constructor-arg name="age" value="26"/>
<constructor-arg name="money" value="32.5"/>
</bean>
在使⽤构造函数注⼊时,涉及的标签是constructor-arg ,该标签有如下属性:
- name:⽤于给构造函数中指定名称的参数赋值。
- index:⽤于给构造函数中指定索引位置的参数赋值。
- value:⽤于指定基本类型或者String类型的数据。
- ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。
依赖注⼊的配置实现之set⽅法注⼊:
顾名思义,就是利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。
<bean id="accountDao" class="com.blnp.net.edu.dao.impl.JdbcTemplateDaoImpl" scope="singleton" init-method="init" destroy-method="destory">
<property name="ConnectionUtils" ref="connectionUtils"/>
<property name="name" value="王二麻子"/>
<property name="age" value="26"/>
<property name="money" value="32.5"/>
</bean>
在使⽤set⽅法注⼊时,需要使⽤property 标签,该标签属性如下:
- name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)
- value:指定注⼊的数据。它⽀持基本类型和String类型。
- ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。
复杂数据类型注⼊:
⾸先,解释⼀下复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。接下来就是注⼊的⽅式的选择,只能在构造函数和set⽅法中选择,我们的示例选⽤set⽅法注⼊。
在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的value 标签内部可以直接写值,也可以使⽤bean 标签配置⼀个对象,或者⽤ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时entry 标签中也可以使⽤ref 标签,但是不能使⽤bean 标签。⽽property 标签中不能使⽤ref 或者bean 标签引⽤对象。
3.1.3、XML与注解的组合模式
注意事项:
- 引⼊注解功能,不需要引⼊额外的jar
- xml+注解结合模式,xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
- 哪些bean的定义写在xml中,哪些bean的定义使⽤注解
xml中标签与注解的对应(IoC):
XML形式 | 对应注解形式 |
---|---|
标签 | @Component("accountDao"),注解加在类上。bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写; 另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已。 |
scope | @Scope("prototype"),默认单例,注解加在类上 |
init-method | @PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
destory-method | @PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
DI 依赖注⼊的注解实现⽅式:
1、@Autowired:@Autowired为Spring提供的注解,需要导⼊包(org.springframework.beans.factory.annotation.Autowired)@Autowired采取的策略为按照类型注⼊。
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
如上代码所示,这样装配会去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。
2、@Qualifier:告诉Spring具体去装配哪个对象
public class TransferServiceImpl {
@Autowired
@Qualifier(name = "jdbcAccountDaoImpl")
private AccountDao accountDao;
}
这个时候我们就可以通过类型和名称定位到我们想注⼊的对象。
3、@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>
3.1.3、纯注解模式
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动。
对应注解:
- @Configuration 注解,表名当前类是⼀个配置类
- @ComponentScan 注解,替代 context:component-scan
- @PropertySource,引⼊外部属性配置⽂件
- @Import 引⼊其他配置类
- @Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
- @Bean 将⽅法返回对象加⼊ SpringIOC 容器
3.2、Spring IOC 高级特性
3.2.1、延迟加载(lazy-Init)
Bean的延迟加载(即延迟创建)。ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。比如:
<bean id="testBean" class="cn.blnp.net.LazyBean" />
<!-- 该bean默认的设置为: -->
<bean id="testBean" calss="cn.blnp.net.LazyBean" lazy-init="false" />
lazy-init="false",⽴即加载,表示在 spring 启动时,⽴刻进⾏实例化。如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将 bean 设置为延迟实例化。
<bean id="testBean" calss="cn.blnp.net.LazyBean" lazy-init="true" />
设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。
如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 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 ⽅法实例化的。
应用场景:
- 开启延迟加载⼀定程度提⾼容器启动和运转性能
- 对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源
3.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;
}
}
应用Bean:
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 +
'}';
}
}
FactoryBean应用:
public class CompanyFactoryBean implements FactoryBean<Company> {
private String 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;
}
public void setCompanyInfo(String companyInfo) {
this.companyInfo = companyInfo;
}
@Override
public boolean isSingleton() {
return true;
}
}
XML配置:
<bean id="companyBean" class="com.blnp.net.edu.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
3.2.3、后置处理器
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
⼯⼚初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情;在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情
注意:对象不⼀定是springbean,⽽springbean⼀定是个对象!
1、BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean。
该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法。
定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
2、BeanFactoryPostProcessor
BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer。
此接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法:
其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改,以下是BeanDefinition中的⽅法:
⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。
BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition
注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象。
4、Spring IOC 源码剖析
4.1、容器初始化主题流程
4.1.1、容器体系
IoC容器是Spring的核⼼模块,是抽象了对象管理、依赖关系管理的框架解决⽅案。Spring 提供了很多的容器,其中 BeanFactory 是顶层容器(根容器),不能被实例化,它定义了所有 IoC 容器 必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,⽐如我们常⽤到的ApplicationContext,其下更具体的实现如 ClassPathXmlApplicationContext 包含了解析 xml 等⼀系列的内容,AnnotationConfigApplicationContext 则是包含了注解解析等⼀系列的内容。Spring IoC 容器继承体系⾮常聪明,需要使⽤哪个层次⽤哪个层次即可,不必使⽤功能⼤⽽全的。
通过其接⼝设计,我们可以看到我们⼀贯使⽤的 ApplicationContext 除了继承BeanFactory的⼦接⼝,还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。
4.1.2、Bean 的生命周期
创建一个类 MyBean ,让其实现⼏个特殊的接⼝,并分别在接⼝实现的构造器、接⼝⽅法中断点,观察线程调⽤栈,分析出 Bean 对象创建和管理关键点的触发时机。
常规Bean:
package com.blnp.net.mp.generator.project.pojo;
import org.springframework.beans.factory.InitializingBean;
/**
* <p></p>
*
* @author lyb 2045165565@qq.com
* @version 1.0
* @since 2024/8/30 10:25
*/
public class MyBean implements InitializingBean {
public MyBean() {
System.out.println("MyBean 构造器……");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("MyBean afterPropertiesSet …………");
}
}
Bean后置处理器:
package com.blnp.net.mp.generator.project.pojo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* <p></p>
*
* @author lyb 2045165565@qq.com
* @version 1.0
* @since 2024/8/30 10:26
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("MyBeanPostProcessor 实现类构造函数…………");
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if("myBean".equals(beanName)) {
System.out.println("MyBeanPostProcessor 实现类 postProcessBeforeInitialization 被调用中......");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if("myBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类 postProcessAfterInitialization 被调用中......");
}
return bean;
}
}
BeanFactory后置处理实现:
package com.blnp.net.mp.generator.project.pojo;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* <p></p>
*
* @author lyb 2045165565@qq.com
* @version 1.0
* @since 2024/8/30 10:30
*/
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
System.out.println("BeanFactoryPostProcessor的实现类构造函数...");
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactoryPostProcessor的实现方法调用中......");
}
}
断点调试入口:
@Test
public void testIoc() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
MyBean myBean = applicationContext.getBean(MyBean.class);
System.out.println(myBean);
}
XML配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--测试验证Bean-->
<bean id="myBean" class="com.blnp.net.mp.generator.project.pojo.MyBean" />
<!--Bean后置处理器-->
<bean id="myBeanPostProcessor" class="com.blnp.net.mp.generator.project.pojo.MyBeanPostProcessor" />
<!--BeanFactory 后置处理器-->
<bean id="myBeanFactoryPostProcessor" class="com.blnp.net.mp.generator.project.pojo.MyBeanFactoryPostProcessor" />
</beans>
1、分析Bean的创建时机
根据断点调试,我们发现,在未设置延迟加载的前提下,Bean 的创建是在容器初始化过程中完成的。
2、分析构造函数调用
通过如上观察,我们发现构造函数的调⽤时机在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory)处;
3、分析初始化方法调用情况
通过如上观察,我们发现 InitializingBean中afterPropertiesSet ⽅法的调⽤时机和构造函数一样。
4、分析BeanFactoryPostProcessor 初始化和调⽤情况
调试方法同上面一致。分别在构造函数、postProcessBeanFactory ⽅法处打断点,观察调⽤栈,发现:
- BeanFactoryPostProcessor:初始化在AbstractApplicationContext类refresh⽅法的invokeBeanFactoryPostProcessors(beanFactory);
- postProcessBeanFactory: 调⽤在AbstractApplicationContext类refresh⽅法的invokeBeanFactoryPostProcessors(beanFactory);
5、分析 BeanPostProcessor 初始化和调⽤情况
分别在构造函数、postProcessBeanFactory ⽅法处打断点,观察调⽤栈,发现:
- BeanPostProcessor: 初始化在AbstractApplicationContext类refresh⽅法的registerBeanPostProcessors(beanFactory);
- postProcessBeforeInitialization :调⽤在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory);
- postProcessAfterInitialization: 调⽤在AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory);
4.1.3、IOC 初始化的主体流程
由上分析可知,Spring IoC 容器初始化的关键环节就在 AbstractApplicationContext#refresh() ⽅法中,我们查看 refresh ⽅法来俯瞰容器创建的主体流程:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
// 第⼀步:刷新前的预处理
prepareRefresh();
/*
第⼆步:
获取BeanFactory;默认实现是DefaultListableBeanFactory
加载BeanDefition 并注册到 BeanDefitionRegistry
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 第三步:BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
prepareBeanFactory(beanFactory);
try {
// 第四步:BeanFactory准备⼯作完成后进⾏的后置处理⼯作
postProcessBeanFactory(beanFactory);
// 第五步:实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 第六步:注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执⾏
registerBeanPostProcessors(beanFactory);
// 第七步:初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
initMessageSource();
// 第⼋步:初始化事件派发器
initApplicationEventMulticaster();
// 第九步:⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑
onRefresh();
// 第⼗步:注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
registerListeners();
/*
第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
第⼗⼆步:
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 (ContextRefreshedEvent)
*/
finishRefresh();
}
//……
}
}
4.2、BeanFactory 创建流程
4.2.1、BeanFactory 子流程获取
4.2.2、BeanDefinition 加载解析
1、关键步骤
- Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂件,并将其封装成Resource对象。
- BeanDefinition载⼊ :把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
- 注册BeanDefinition到 IoC 容器
2、过程分析
Step1:
⼦流程⼊⼝在 AbstractRefreshableApplicationContext#refreshBeanFactory ⽅法中。
Step2:
依次调⽤多个类的 loadBeanDefinitions ⽅法 —> AbstractXmlApplicationContext —>AbstractBeanDefinitionReader —> XmlBeanDefinitionReader ⼀直执⾏到XmlBeanDefinitionReader 的 doLoadBeanDefinitions ⽅法。
Step3:
我们重点观察XmlBeanDefinitionReader 类的 registerBeanDefinitions ⽅法,期间产⽣了多次重载调⽤,我们定位到最后⼀个。
我们关注两个地⽅:⼀个createRederContext⽅法,⼀个是DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions⽅法,先进⼊createRederContext ⽅法看看。
可以看到,此处 Spring ⾸先完成了 NamespaceHandlerResolver 的初始化。我们再进⼊ registerBeanDefinitions ⽅法中追踪,调⽤了DefaultBeanDefinitionDocumentReader#registerBeanDefinitions ⽅法。
进⼊ doRegisterBeanDefinitions ⽅法:
进⼊ parseBeanDefinitions ⽅法:
进⼊ parseDefaultElement ⽅法:
进⼊ processBeanDefinition ⽅法:
⾄此,注册流程结束,我们发现,所谓的注册就是把封装的 XML 中定义的 Bean信息封装为BeanDefinition 对象之后放⼊⼀个Map中,BeanFactory 是以 Map 的结构组织这些 BeanDefinition的。
可以在DefaultListableBeanFactory中看到此Map的定义。
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new
ConcurrentHashMap<>(256);
3、时序图
4.3、Bean创建流程
- 通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory) 处
- 进⼊finishBeanFactoryInitialization
- 继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,我们找到下⾯部分的代码,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例
- 继续跟踪下去,我们进⼊到了AbstractBeanFactory类的doGetBean⽅法,这个⽅法中的代码很多,我们直接找到核⼼部分
- 接着进⼊到AbstractAutowireCapableBeanFactory类的⽅法,找到以下代码部分
- 进⼊doCreateBean⽅法看看,该⽅法我们关注两块重点区域
- 创建Bean实例,此时尚未设置属性
- 给Bean填充属性,调⽤初始化⽅法,应⽤BeanPostProcessor后置处理器
4.4、lazy-init 延迟加载机制原理
lazy-init 延迟加载机制分析
普通 Bean 的初始化是在容器启动初始化阶段执⾏的,⽽被lazy-init=true修饰的 bean 则是在从容器⾥第⼀次进⾏context.getBean() 时进⾏触发。Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap⾥供下⾯的初始化时⽤,然后对每个BeanDefinition 进⾏处理,如果是懒加载的则在容器初始化阶段不处理,其他的则在容器初始化阶段进⾏初始化并依赖注⼊。
public void preInstantiateSingletons() throws BeansException {
// 所有beanDefinition集合
List < String > beanNames = new ArrayList < String > (this.beanDefinitionNames);
// 触发所有⾮懒加载单例bean的初始化
for(String beanName: beanNames) {
// 获取bean 定义
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 判断是否是懒加载单例bean,如果是单例的并且不是懒加载的则在容器创建时初始化
if(!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 判断是否是 FactoryBean
if(isFactoryBean(beanName)) {
final FactoryBean < ? > factory = (FactoryBean < ? > )
getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if(System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction < Boolean > () {
@Override
public Boolean run() {
return ((SmartFactoryBean < ? > ) factory).isEagerInit();
}
}, getAccessControlContext());
}
}
else {
/*
如果是普通bean则进⾏初始化并依赖注⼊,此 getBean(beanName)接下来触发的逻辑
和
懒加载时 context.getBean("beanName") 所触发的逻辑是⼀样的
*/
getBean(beanName);
}
}
}
}
4.5、Spring IOC 循环依赖问题
4.5.1、什么是循环依赖
循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B依赖于C,C⼜依赖于A。
注意,这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。Spring中循环依赖场景有:
- 构造器的循环依赖(构造器注⼊)
- Field 属性的循环依赖(set注⼊)
其中,构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常,在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。
4.5.2、处理机制
- 单例 bean 构造器参数循环依赖(⽆法解决)
- prototype 原型 bean循环依赖(⽆法解决)
对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理(AbstractBeanFactory.doGetBean()⽅法:)。
if(isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set < ? > ) curVal).contains(beanName))));
}
在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记
try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
总结:Spring 不⽀持原型 bean 的循环依赖。
- 单例bean通过setXxx或者@Autowired进⾏循环依赖
Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前。
Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if(earlySingletonExposure) {
if(logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
//将初始化后的对象提前已ObjectFactory对象注⼊到容器中
addSingletonFactory(beanName, new ObjectFactory < Object > () {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
- ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring容器中。
- Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
- ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例
- ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
- 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
5、Spring AOP 应用
5.1、AOP 相关术语
5.1.1、业务主线
上图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,在运⾏期对需要使⽤的业务逻辑⽅法进⾏增强。
5.1.2、AOP 术语
名词 | 解释 |
---|---|
Joinpoint(连接点) | 它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。 |
Pointcut(切⼊点) | 它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。 |
Advice(通知/增强) | 它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。 |
Target(⽬标对象) | 它指的是代理的⽬标对象。即被代理对象。 |
Proxy(代理) | 它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。 |
Weaving(织⼊) | 它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。 |
Aspect(切⾯) | 它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中TrasnactionManager 就是⼀个切⾯。 |
- 连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点
- 切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法
- Advice增强:
- 第⼀个层次:指的是横切逻辑
- 第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)
- Aspect切⾯:切⾯概念是对上述概念的⼀个综合
Aspect切⾯= 切⼊点+增强= 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑。众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码。
5.2、AOP 的代理选择
Spring 实现AOP思想使⽤的是动态代理技术;默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
5.3、AOP 的配置方式
在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式。
- 第⼀类:使⽤XML配置
- 第⼆类:使⽤XML+注解组合配置
- 第三类:使⽤纯注解配置
5.4、AOP 的实现
5.4.1、XML 模式
Spring是模块化开发的框架,使⽤aop就引⼊aop的jar;坐标如下:
jar 坐标:
<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>
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.blnp.net.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切⾯-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public *com.blnp.net.service.impl.TransferServiceImpl.updateAccountByCardNo(com.blnp.net.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>
关于切⼊点表达式:
上述配置实现了对 TransferServiceImpl 的 updateAccountByCardNo ⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?
- 概念及作⽤:切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。
- 关于AspectJ:AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。
- 切⼊点表达式使⽤示例:
##格式:全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
##全匹配方式:
public void com.blnp.net.service.impl.TransferServiceImpl.updateAccountByCardNo(com.blnp.net.pojo.Account)
##访问修饰符可以省略:
void com.blnp.net.service.impl.TransferServiceImpl.updateAccountByCardNo(com.blnp.net.pojo.Account)
##返回值可以使⽤*,表示任意返回值
*com.blnp.net.service.impl.TransferServiceImpl.updateAccountByCardNo(com.blnp.net.pojo.Account)
##包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
*....TransferServiceImpl.updateAccountByCardNo(com.blnp.net.pojo.Account)
##包名可以使⽤..表示当前包及其⼦包
*..TransferServiceImpl.updateAccountByCardNo(com.blnp.net.pojo.Account)
##类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.blnp.net.pojo.Account)
#参数列表,可以使⽤具体类型
##基本类型直接写类型名称 : int
##引⽤类型必须写全限定类名:java.lang.String
##参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
##参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
##全通配⽅式:
* *..*.*(..)
改变代理⽅式的配置:
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:aspectjautoproxy>
1、通知类型
1.1、前置通知
配置方式:
<!--
作⽤:
⽤于配置前置通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定前置通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:before method="printLog" pointcut-ref="pointcut1"></aop:before>
执行时机:
前置通知永远都会在切⼊点⽅法(业务核⼼⽅法)执⾏之前执⾏。
细节:
前置通知可以获取切⼊点⽅法的参数,并对其进⾏增强。
1.2、正常执行通知
配置方式:
<!--
作⽤:
⽤于配置正常执⾏时通知
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定后置通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-returning method="afterReturningPrintLog" pointcutref="pt1"></aop:after-returning>
1.3、异常通知
配置方式:
<!--
作⽤:
⽤于配置异常通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定异常通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
执行时机:
异常通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏产⽣异常之后,异常通知执⾏。如果切⼊点⽅法执⾏没有产⽣异常,则异常通知不会执⾏。
细节:
异常通知不仅可以获取切⼊点⽅法执⾏的参数,也可以获取切⼊点⽅法执⾏产⽣的异常信息。
1.4、最终通知
配置方式:
<!--
作⽤:
⽤于指定最终通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:⽤于指定最终通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
执行时机:
最终通知的执⾏时机是在切⼊点⽅法(业务核⼼⽅法)执⾏完成之后,切⼊点⽅法返回之前执⾏。换句话说,⽆论切⼊点⽅法执⾏是否产⽣异常,它都会在返回之前执⾏。
细节:
最终通知执⾏时,可以获取到通知⽅法的参数。同时它可以做⼀些清理操作。
1.5、环绕通知
配置方式:
<!--
作⽤:
⽤于配置环绕通知。
出现位置:
它只能出现在aop:aspect标签的内部
属性:
method:⽤于指定环绕通知的⽅法名称
pointcut:⽤于指定切⼊点表达式
pointcut-ref:⽤于指定切⼊点表达式的引⽤
-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
特别说明:
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,实现⼿动触发切⼊点⽅法的调⽤。
5.4.2、XML + 注解模式
XML 中开启 Spring 对注解 AOP 的⽀持:
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
示例:
package com.blnp.net.mp.generator.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* <p></p>
*
* @author lyb 2045165565@qq.com
* @version 1.0
* @since 2024/8/30 16:21
*/
@Component
@Aspect
public class LogUtil {
/**
* 用途:切入点
* <p>
* 第一步:编写一个方法<br/>
* 第二步:在方法使用@Pointcut注解<br/>
* 第三步:给注解的value属性提供切入点表达式<br/><br/>
* 细节:<br/>
* 1.在引用切入点表达式时,必须是方法名+(),例如"pointcut()"。<br/>
* 2.在当前切面中使用,可以直接写方法名。在其他切面中使用必须是全限定方法名。
* </p>
*
* @param
* @return void
* @author liaoyibin
* @params []
* @since 16:24 2024/8/30
**/
@Pointcut("execution(* com.blnp.net.mp.*.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");
}
/**
* 用途:环绕通知
* @author liaoyibin
* @since 16:31 2024/8/30
* @params [pjp]
* @param pjp
* @return java.lang.Object
**/
@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;
}
}
5.4.3、注解模式
在使⽤注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下⾯这⾏配置:
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
在配置类中使⽤如下注解进⾏替换上述配置:
@Configuration
@ComponentScan("com.blnp.net")
//开启spring对注解AOP的⽀持
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
5.5、声明式事务的支持
- 编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
- 声明式事务:通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务
5.5.1、关于事务
1、事务的概念
事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数据的准确与安全。例如:A——B转帐,对应于如下两条sql语句:
/*转出账户减钱*/
update account set money=money-100 where name=‘a’;
/**转⼊账户加钱*/
update account set money=money+100 where name=‘b’;
这两条语句的执⾏,要么全部成功,要么全部不成功。
2、事务的四大特性
- 原⼦性(Atomicity): 原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败
- ⼀致性(Consistency): 事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。例如转账前A有1000,B有1000。转账后A+B也得是2000。⼀致性是从数据的⻆度来说的,(1000,1000) (900,1100),不应该出现(900,1000)
- 隔离性(Isolation): 事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。⽐如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
- 持久性(Durability):持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响。
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连接会话的,并不是永久改变的)。
4、事务的传播行为
事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为。
事务 | 描述 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加⼊到这个事务中。这是最常⻅的选择。 |
PROPAGATION_SUPPORTS | ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。 |
PROPAGATION_MANDATORY | 使⽤当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。 |
5.5.2、Spring 中事务的API
- mybatis: sqlSession.commit();
- hibernate: session.commit();
- PlatformTransactionManager
PlatformTransactionManager ,此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层⽀持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应⽤。在Spring框架中,也为我们内置了⼀些具体策略,例如:DataSourceTransactionManager , HibernateTransactionManager 等。
5.5.3、声明式事务配置
1、纯XML模式
<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.blnp.net.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
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)
3、纯注解
Spring基于注解驱动开发的事务控制配置,只需要把 xml 配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的 <tx:annotation-driven transactiontransactionmanager="
transactionManager"/> 配置。在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可。
@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}
6、Spring AOP 源码剖析
6.1、代理对象创建
6.1.1、AOP 用例准备
Bean定义:
package com.blnp.net.mp.generator.project.pojo;
import org.springframework.stereotype.Component;
/**
* <p></p>
*
* @author lyb 2045165565@qq.com
* @version 1.0
* @since 2024/8/30 17:01
*/
@Component
public class MyAopBean {
public void teachStudy() {
System.out.println("执行某业务操作中…………");
}
}
Aspect定义:
package com.blnp.net.mp.generator.project.pojo;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* <p></p>
*
* @author lyb 2045165565@qq.com
* @version 1.0
* @since 2024/8/30 17:03
*/
@Component
@Aspect
public class MyAspect {
@Pointcut("execution(* com.blnp.net.mp.generator.project.pojo.MyAopBean.teachStudy(..))")
public void pointcut(){
}
@Before("pointcut()")
public void before() {
System.out.println("before method ......");
}
}
Maven配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>generator-code</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis-plus的springboot持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--简化代码的具包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--SQL 分析打印-->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置Bean:
@Configuration
@MapperScan("com.blnp.net")
@EnableAspectJAutoProxy
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
@Bean
public ISqlInjector iSqlInjector(){
return new MySqlInjector();
}
}
测试用例:
package com.blnp.net.mp;
import com.blnp.net.mp.generator.project.entity.TbProject;
import com.blnp.net.mp.generator.project.mapper.TbProjectMapper;
import com.blnp.net.mp.generator.project.pojo.MyAopBean;
import com.blnp.net.mp.generator.project.pojo.MyBean;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.annotation.Resource;
import java.util.List;
@SpringBootTest
class GeneratorCodeApplicationTests {
@Resource
TbProjectMapper projectMapper;
@Autowired
private ApplicationContext context;
@Test
public void testAop() {
MyAopBean bean = context.getBean(MyAopBean.class);
bean.teachStudy();
}
}
6.1.2、时机点分析
可以看到在 getBean 之前,MyAopBean对象已经产⽣(即在第⼀⾏初始化代码中完成),⽽且该对象是⼀个代理对象(Cglib代理对象),我们断定,容器初始化过程中⽬标Ban已经完成了代理,返回了代理对象。也就是 value 值的内容。
6.1.3、代理对象创建流程
即方法:AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object,org.springframework.beans.factory.support.RootBeanDefinition)
/**
*
* 初始化Bean
*包括Bean后置处理器初始化
*Bean的⼀些初始化⽅法的执⾏init-method
*Bean的实现的声明周期相关接⼝的属性注⼊
*/
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
// 执⾏所有的AwareMethods
if(System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction < Object > )() - > {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if(mbd == null || !mbd.isSynthetic()) {
// 执⾏所有的BeanPostProcessor#postProcessBeforeInitialization 初始化之前的处理器⽅法
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 这⾥就开始执⾏afterPropertiesSet(实现了InitializingBean接⼝)⽅法和initMethod
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex);
}
if(mbd == null || !mbd.isSynthetic()) {
// 整个Bean初始化完成,执⾏后置处理器⽅法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
// 循环执⾏后置处理器
for(BeanPostProcessor processor: getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if(current == null) {
return result;
}
result = current;
}
return result;
}
创建代理对象的后置处理器AbstractAutoProxyCreator#postProcessAfterInitialization
/**
* Create a proxy with the configured interceptors if the bean is
* identified as one to proxy by the subclass.
* @see #getAdvicesAndAdvisorsForBean
*/
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if(bean != null) {
// 检查下该类是否已经暴露过了(可能已经创建了,⽐如A依赖B时,创建A时候,就会先去创建B。
// 当真正需要创建B时,就没必要再代理⼀次已经代理过的对象),避免重复创建
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if(this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
AbstractAutoProxyCreator#wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// targetSourcedBeans包含,说明前⾯创建过
if(StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if(Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if(isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
// 得到所有候选Advisor,对Advisors和bean的⽅法双层遍历匹配,最终得到⼀个List < Advisor > ,即specificInterceptors
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if(specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 重点,创建代理对象
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
AbstractAutoProxyCreator#createProxy
/**
* Create an AOP proxy for the given bean.
* 为指定 bean 创建代理对象
*/
protected Object createProxy(Class < ? > beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
if(this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
// 创建代理的⼯作交给ProxyFactory
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
// 根据⼀些情况判断是否要设置proxyTargetClass=true
if(!proxyFactory.isProxyTargetClass()) {
if(shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
// 把指定和通⽤拦截对象合并, 并都适配成Advisor
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
// 设置参数
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if(advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
// 上⾯准备做完就开始创建代理
return proxyFactory.getProxy(getProxyClassLoader());
}
跟进到ProxyFactory中:
public class ProxyFactory extends ProxyCreatorSupport {
public Object getProxy(ClassLoader classLoader) {
// ⽤ProxyFactory创建AopProxy, 然后⽤AopProxy创建Proxy, 所以这⾥重要的是看获取的AopProxy
// 对象是什么,
// 然后进去看怎么创建动态代理, 提供了两种:jdk proxy, cglib
return createAopProxy().getProxy(classLoader);
}
}
public class ProxyCreatorSupport extends AdvisedSupport {
private AopProxyFactory aopProxyFactory;
public ProxyCreatorSupport() {
this.aopProxyFactory = new DefaultAopProxyFactory();
}
protected final synchronized AopProxy createAopProxy() {
if(!this.active) {
activate();
}
//先获取创建AopProxy的⼯⼚, 再由此创建AopProxy
return getAopProxyFactory().createAopProxy(this);
}
public AopProxyFactory getAopProxyFactory() {
return this.aopProxyFactory;
}
}
流程就是⽤AopProxyFactory创建AopProxy, 再⽤AopProxy创建代理对象,这⾥的AopProxyFactory默认是DefaultAopProxyFactory,看他的createAopProxy⽅法:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws
AopConfigException {
if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class < ? > targetClass = config.getTargetClass();
if(targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target
class: " + "Either an interface or a target is required for proxy
creation.
");
}
if(targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class < ? > [] interfaces = config.getProxiedInterfaces();
return (interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.equals(interfaces[0])));
}
}
这⾥决定创建代理对象是⽤JDK Proxy,还是⽤ Cglib 了,最简单的从使⽤⽅⾯使⽤来说:设置 proxyTargetClass=true 强制使⽤Cglib 代理,什么参数都不设并且对象类实现了接⼝则默认⽤JDK 代理,如果没有实现接⼝则也必须⽤Cglib。
@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
if(logger.isTraceEnabled()) {
logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
}
try {
Class < ? > rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for
creating a CGLIB proxy ");
Class < ? > proxySuperClass = rootClass;
if(ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class < ? > [] additionalInterfaces = rootClass.getInterfaces();
for(Class < ? > additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass, classLoader);
// 配置 Cglib 增强
Enhancer enhancer = createEnhancer();
if(classLoader != null) {
enhancer.setClassLoader(classLoader);
if(classLoader instanceof SmartClassLoader && ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader)); Callback[] callbacks = getCallbacks(rootClass); Class < ? > [] types = new Class < ? > [callbacks.length];
for(int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// fixedInterceptorMap only populated at this point, after getCallbacks
call above enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); enhancer.setCallbackTypes(types);
// ⽣成代理类,并且创建⼀个代理类的实例
return createProxyClassAndInstance(enhancer, callbacks);
}
catch (CodeGenerationException | IllegalArgumentException ex) {
throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a
non - visible class ",
ex);
}
catch (Throwable ex) {
// TargetSource.getTarget() failed
throw new AopConfigException("Unexpected AOP exception", ex);
}
}
6.2、Spring 声明式事务控制
6.2.1、事务注解
@EnableTransactionManagement
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
}
@EnableTransactionManagement 注解使⽤ @Import 标签引⼊了TransactionManagementConfigurationSelector类,这个类⼜向容器中导⼊了两个重要的组件。
6.2.2、加载事务控制组件
AutoProxyRegistrar:
AutoProxyRegistrar 类的 registerBeanDefinitions ⽅法中⼜注册了⼀个组件。
进⼊ AopConfigUtils.registerAutoProxyCreatorIfNecessary ⽅法:
发现最终,注册了⼀个叫做 InfrastructureAdvisorAutoProxyCreator 的 Bean,⽽这个类是AbstractAutoProxyCreator 的⼦类,实现了 SmartInstantiationAwareBeanPostProcessor 接⼝。
public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
它实现了SmartInstantiationAwareBeanPostProcessor,说明这是⼀个后置处理器,⽽且跟spring AOP 开启@EnableAspectJAutoProxy 时注册的 AnnotationAwareAspectJProxyCreator实现的是同⼀个接⼝,所以说,声明式事务是 springAOP 思想的⼀种应⽤。
ProxyTransactionManagementConfiguration 组件:
package org.springframework.transaction.annotation;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
import
org.springframework.transaction.config.TransactionManagementConfigUtils;
import
org.springframework.transaction.interceptor.BeanFactoryTransactionAttribut
eSourceAdvisor;
import
org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* {@code @Configuration} class that registers the Spring infrastructure
beans
* necessary to enable proxy-based annotation-driven transaction
management.
*
* @author Chris Beams
* @since 3.1
* @see EnableTransactionManagement
* @see TransactionManagementConfigurationSelector
*/
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
// 事务增强器
BeanFactoryTransactionAttributeSourceAdvisor advisor = new
BeanFactoryTransactionAttributeSourceAdvisor();
// 向事务增强器中注⼊ 属性解析器 transactionAttributeSource
advisor.setTransactionAttributeSource(transactionAttributeSource());
// 向事务增强器中注⼊ 事务拦截器 transactionInterceptor
advisor.setAdvice(transactionInterceptor());
if(this.enableTx != null) {
advisor.setOrder(this.enableTx. < Integer > getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// 属性解析器 transactionAttributeSource
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
// 事务拦截器 transactionInterceptor
public TransactionInterceptor transactionInterceptor() {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if(this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
ProxyTransactionManagementConfiguration是⼀个容器配置类,注册了⼀个组件 transactionAdvisor,称为事务增强器,然后在这个事务增强器中⼜注⼊了两个属性:
- transactionAttributeSource,即属性解析器
- transactionInterceptor 事务拦截器
AnnotationTransactionAttributeSource:
属性解析器有⼀个成员变量是annotationParsers,是⼀个集合,可以添加多种注解解析器(TransactionAnnotationParser),我们关注 Spring 的注解解析器,部分源码如下:
属性解析器的作⽤之⼀就是⽤来解析@Transaction注解。TransactionInterceptor 事务拦截器,部分源码如下:
这些组件是如何关联的?
事务拦截器实现了MethodInterceptor接⼝,追溯⼀下上⾯提到的InfrastructureAdvisorAutoProxyCreator后置处理器,它会在代理对象执⾏⽬标⽅法的时候获取其拦截器链,⽽拦截器链就是这个TransactionInterceptor,这就把这两个组件联系起来;
构造⽅法传⼊PlatformTransactionManager(事务管理器)、TransactionAttributeSource(属性解析器),但是追溯⼀下上⾯贴的ProxyTransactionManagementConfiguration的源码,在注册事务拦截器的时候并没有调⽤这个带参构造⽅法,⽽是调⽤的⽆参构造⽅法,然后再调⽤set⽅法注⼊这两个属性,效果⼀样。
invokeWithinTransaction ⽅法源码: