Spring
Spring概述
Spring是分层的 full-stack(全栈)轻量级开源框架,以 IoC和 AOP为内核,提供了展现层 Spring MVC和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE企业应⽤开源框架。
Spring官⽅⽹址
Spring优势
- ⽅便解耦,简化开发
- 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更专注于上层的应⽤。
- AOP编程的⽀持
- 通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过AOP轻松应付。
- 声明式事务的⽀持
- 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼开发效率和质量。
- ⽅便集成各种优秀框架
- Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接⽀持。
Spring的核⼼结构
Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块和Test模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决⽅案的零侵⼊的轻量级框架。
架构图
- pring核⼼容器(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对象实现。
核心思想
IOC
什么是IOC
IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现
描述的事情:Java开发领域对象的创建,管理的问题
传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可
我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列事情)
为什么叫做控制反转?
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
IoC解决对象之间的耦合问题
IOC和DI的区别
DI:Dependancy Injection(依赖注⼊)
IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
AOP
什么是AOP
AOP: Aspect oriented Programming⾯向切⾯编程/⾯向⽅⾯编程AOP是OOP的延续,从OOP说起
OOP三⼤特征:封装、继承和多态
oop是⼀种垂直继承体系
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了
上述的代码逻辑叫做横切逻辑代码
横切逻辑代码存在什么问题:
- 横切代码重复问题
- 横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
为什么叫做⾯向切⾯编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯
Spring IOC应⽤
Spring IoC基础
BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。
通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等
启动 IoC容器的⽅式
- Java环境下启动IoC容器
- ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
- AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
- 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>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<!--配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
纯xml模式
- 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">
- 实例化Bean的三种⽅式
- ⽅式⼀:使⽤⽆参构造函数
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败
<! 配置service对象 >
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
</bean>
- ⽅式⼆:使⽤静态⽅法创建
在实际开发中,我们使⽤的对象有些时候并不是直接通过构造函数就可以创建出来的,它可能在创建的过程中会做很多额外的操作。此时会提供⼀个创建对象的⽅法,恰好这个⽅法是static修饰的⽅法,
<!--使⽤静态⽅法创建对象的配置⽅式-->
<bean id="userService" class="com.lagou.factory.BeanFactory"
factory-method="getTransferService"></bean>
- ⽅式三:使⽤实例化⽅法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。在早期开发的项⽬中,⼯⼚类中的⽅法有可能是静态的,也有可能是⾮静态⽅法,当是⾮静态⽅法时,即可采⽤下⾯的配置⽅式:
<!--使⽤实例⽅法创建对象的配置⽅式-->
<bean id="beanFactory"
class="com.lagou.factory.instancemethod.BeanFactory"></bean>
<bean id="transferService" factory-bean="beanFactory" factory-
method="getTransferService"></bean>
bean的生命周期
- 作用范围的改变
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:
在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置⽅式参考下⾯的代码:
<!--配置service对象-->
<bean id="transferService"
class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
</bean>
- 不同作⽤范围的⽣命周期
单例模式:singleton
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
多例模式:prototype
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
- 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时起作⽤。
- DI依赖注⼊的xml配置
- 依赖注⼊分类
- 按照注⼊的⽅式分类
- 构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
- set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
- 按照注⼊的数据类型分类
- 基本类型和String
注⼊的数据类型是基本类型或者是字符串类型的数据。 - 其他Bean类型
注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
- 复杂类型(集合类型)
注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
- 依赖注⼊的配置实现之构造函数注⼊顾名思义,就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。
xml与注解相结合模式
第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池
⾃⼰开发的bean定义使⽤注解
xml形式 |
对应的注解形式 |
标签 |
@Component("accountDao"),注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名**@Controller、@Service、@Repository**分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已 |
标签的scope属性 |
@Scope("prototype"),默认单例,注解加在类上 |
标签的init-method属性 |
@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法 |
标签的destory-method属性 |
@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法 |
DI依赖注⼊的注解实现⽅式
@Autowired(推荐使⽤)
@Autowired为Spring提供的注解,需要导⼊包
public class TransferServiceImpl {
@Autowired
private AccountDao accountDao;
}
如上代码所示,这样装配回去spring容器中找到类型为AccountDao的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。
@Qualifier告诉Spring具体去装配哪个对象。
public class TransferServiceImpl {
@Autowired
@Qualifier(name="jdbcAccountDaoImpl")
private AccountDao accountDao;
}
这个时候我们就可以通过类型和名称定位到我们想注⼊的对象。
@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>
纯注解模式
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动对应注解
- @Configuration注解,表名当前类是⼀个配置类
- @ComponentScan注解,替代 context:component-scan
- @PropertySource,引⼊外部属性配置⽂件!
- @Import引⼊其他配置类
- @Value对变量赋值,可以直接赋值,也可以使⽤ ${}读取资源配置⽂件中的信息
- @Bean将⽅法返回对象加⼊ SpringIOC容器
Spring IOC⾼级特性
lazy-Init延迟加载
Bean的延迟加载(延迟创建)
ApplicationContext容器的默认⾏为是在启动服务器时将所有 singleton bean提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext实例会创建并配置所有的singleton
bean。
<bean id="testBean" class="cn.lagou.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />
lazy-init="false",⽴即加载,表示在spring启动时,⽴刻进⾏实例化。
如果不想让⼀个singleton bean在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。
<bean id="testBean" calss="cn.lagou.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就占⽤资源
FactoryBean和BeanFactory
BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;此处我们重点分析FactoryBean
Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。
Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤
public interface FactoryBean<T> {
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton(){
return true;
}
}
xml配置
<bean id="companyBean" class="com.lagou.edu.factory.CompanyFactoryBean">
<property name="companyInfo" value="拉勾,中关村,500"/>
</bean>
后置处理器
Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor和
BeanFactoryPostProcessor,两者在使⽤上是有所区别的。
⼯⼚初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情
在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情
注意:对象不⼀定是springbean,⽽springbean⼀定是个对象
SpringBean的⽣命周期
- BeanPostProcessor
BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.
该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法
定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。
注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。
- 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对象
Spring IOC源码深度剖析
Spring源码构建
- 下载源码(github)
- 安装gradle 5.6.3(类似于maven)Idea 2019.1 Jdk 11.0.5
- 导⼊(耗费⼀定时间)
- 编译⼯程(顺序:core-oxm-context-beans-aspects-aop)
- ⼯程—>tasks—>compileTestJava
参考地址:https://blog.csdn.net/dadachenchen/article/details/106435025
Spring IoC容器初始化主体流程
Spring IoC的容器体系
IoC容器是Spring的核⼼模块,是抽象了对象管理、依赖关系管理的框架解决⽅案。Spring提供了很多的容器,其中 BeanFactory是顶层容器(根容器),不能被实例化,它定义了所有 IoC容器必须遵从的⼀套原则,具体的容器实现可以增加额外的功能,⽐如我们常⽤到的ApplicationContext,其下更具体的实现如 ClassPathXmlApplicationContext包含了解析 xml等⼀系列的内容,
AnnotationConfigApplicationContext则是包含了注解解析等⼀系列的内容。Spring IoC容器继承体系⾮常聪明,需要使⽤哪个层次⽤哪个层次即可,不必使⽤功能⼤⽽全的。
BeanFactory顶级接⼝⽅法栈如下
BeanFactory容器继承体系
通过其接⼝设计,我们可以看到我们⼀贯使⽤的 ApplicationContext除了继承BeanFactory的⼦接⼝,还继承了ResourceLoader、MessageSource等接⼝,因此其提供的功能也就更丰富了。
下⾯我们以 ClasspathXmlApplicationContext为例,深⼊源码说明 IoC容器的初始化流程。
Bean⽣命周期关键时机点
**思路:**创建⼀个类 LagouBean,让其实现⼏个特殊的接⼝,并分别在接⼝实现的构造器、接⼝⽅法中断点,观察线程调⽤栈,分析出 Bean对象创建和管理关键点的触发时机。
LagouBean类
BeanPostProcessor接⼝实现类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
public class MyBeanPostProcessor implements BeanPostProcessor {
public MyBeanPostProcessor() {
System.out.println("BeanPostProcessor 实现类构造函数...");
}
public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {
if("lagouBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类postProcessBeforeInitialization ⽅法被调⽤中");
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {
if("lagouBean".equals(beanName)) {
System.out.println("BeanPostProcessor 实现类postProcessAfterInitialization ⽅法被调⽤中");
}
return bean;
}
}
BeanFactoryPostProcessor接⼝实现类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public MyBeanFactoryPostProcessor() {
System.out.println("BeanFactoryPostProcessor的实现类构造函数...");
}
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory) throws BeansException {
System.out.println("BeanFactoryPostProcessor的实现⽅法调⽤中 ");
}
}
applicationContext.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"/>
<bean id="lagouBean" class="com.lagou.LagouBean"/>
<bean id="myBeanFactoryPostProcessor"class="com.lagou.MyBeanFactoryPostProcessor"/>
<bean id="myBeanPostProcessor" class="com.lagou.MyBeanPostProcessor"/></beans>
IoC容器源码分析⽤例
/*
* Ioc 容器源码分析基础案例
*/
@Test
public void testIoC() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
System.out.println(lagouBean);
}
通过如上观察,我们发现 InitializingBean中afterPropertiesSet⽅法的调⽤时机也是在 AbstractApplicationContext类refresh⽅法的finishBeanFactoryInitialization(beanFactory);
- 分析BeanFactoryPostProcessor初始化和调⽤情况
分别在构造函数、postProcessBeanFactory⽅法处打断点,观察调⽤栈,发现
BeanFactoryPostProcessor初始化在AbstractApplicationContext类refresh⽅法的 invokeBeanFactoryPostProcessors(beanFactory);
postProcessBeanFactory调⽤在AbstractApplicationContext类refresh⽅法的 invokeBeanFactoryPostProcessors(beanFactory);
- 分析 BeanPostProcessor初始化和调⽤情况
分别在构造函数、postProcessBeanFactory⽅法处打断点,观察调⽤栈,发现
BeanPostProcessor初始化在AbstractApplicationContext类refresh⽅法的 registerBeanPostProcessors(beanFactory);
postProcessBeforeInitialization调⽤在AbstractApplicationContext类refresh⽅法的 finishBeanFactoryInitialization(beanFactory);
postProcessAfterInitialization调⽤在AbstractApplicationContext类refresh⽅法的 finishBeanFactoryInitialization(beanFactory);
- 总结
根据上⾯的调试分析,我们发现 Bean对象创建的⼏个关键时机点代码层级的调⽤都在
AbstractApplicationContext类的 refresh⽅法中,可⻅这个⽅法对于Spring IoC容器初始化来说相当关键,汇总如下:
关键点 |
触发代码 |
构造器 |
refresh#finishBeanFactoryInitialization(beanFactory)(beanFactory) |
BeanFactoryPostProcessor初始化 |
refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanFactoryPostProcessor⽅法调⽤ |
refresh#invokeBeanFactoryPostProcessors(beanFactory) |
BeanPostProcessor初始化 |
registerBeanPostProcessors(beanFactory) |
BeanPostProcessor⽅法调⽤ |
refresh#finishBeanFactoryInitialization(beanFactory) |
Spring 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接⼝的监听器
registerListeners();
/*第⼗⼀步:
初始化所有剩下的⾮懒加载的单例bean
初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
填充属性
初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
*/
finishBeanFactoryInitialization(beanFactory);
/*
完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事
件 (ContextRefreshedEvent)
*/
finishRefresh();
}
}
BeanFactory创建流程
- 获取BeanFactory⼦流程
- BeanDefinition加载解析及注册⼦流程
- 该⼦流程涉及到如下⼏个关键步骤
Resource定位:指对BeanDefinition的资源定位过程。通俗讲就是找到定义Javabean信息的XML⽂件,并将其封装成Resource对象。
BeanDefinition载⼊:把⽤户定义好的Javabean表示为IoC容器内部的数据结构,这个容器内部的数据结构就是BeanDefinition。
注册BeanDefinition到IoC容器 - 过程分析
Step 1:⼦流程⼊⼝在 AbstractRefreshableApplicationContext#refreshBeanFactory⽅法中
![img](
Step 2:依次调⽤多个类的 loadBeanDefinitions⽅法 —> AbstractXmlApplicationContext —>AbstractBeanDefinitionReader —> XmlBeanDefinitionReader ⼀直执⾏到
XmlBeanDefinitionReader的 doLoadBeanDefinitions⽅法
Step 3:我们重点观察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);
时序图
Bean创建流程
- 通过最开始的关键时机点分析,我们知道Bean创建⼦流程⼊⼝在
AbstractApplicationContext#refresh()⽅法的finishBeanFactoryInitialization(beanFactory)处
- 进⼊finishBeanFactoryInitialization
- 继续进⼊DefaultListableBeanFactory类的preInstantiateSingletons⽅法,我们找到下⾯部分的代码,看到⼯⼚Bean或者普通Bean,最终都是通过getBean的⽅法获取实例
- 继续跟踪下去,我们进⼊到了AbstractBeanFactory类的doGetBean⽅法,这个⽅法中的代码很多,我们直接找到核⼼部分
- 接着进⼊到AbstractAutowireCapableBeanFactory类的⽅法,找到以下代码部分
- 进入doCreateBean⽅法看看,该⽅法我们关注两块重点区域
- 创建Bean实例,此时尚未设置属性
- 给Bean填充属性,调⽤初始化⽅法,应⽤BeanPostProcessor后置处理器
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);
}
}
}
}
- 总结
对于被修饰为lazy-init的bean Spring容器初始化阶段不会进⾏ init并且依赖注⼊,当第⼀次进⾏getBean时候才进⾏初始化并依赖注⼊
对于⾮懒加载的bean,getBean的时候会从缓存⾥头获取,因为容器初始化阶段 Bean已经初始化完成并缓存了起来
循环依赖处理机制
单例 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容器中。
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);
}
});
}
- Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
- Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
- ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA,因为第⼀步中已经提前暴露了ClassA,因此可以获取到ClassA实例
- ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
- 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
Spring Aop 应用
名词 |
解释 |
Joinpoint(连接点) |
它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。 |
Pointcut(切⼊点 |
它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。 |
Advice(通*知/增强) |
它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么这些就是通知的类型。其分类有:前置通知后置通知异常通知最终通知环绕通知。 |
Target(⽬标对象) |
它指的是代理的⽬标对象。即被代理对象。 |
**Proxy(**代理) |
它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。 |
**Weaving(**织⼊) |
它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。 |
Aspect(切⾯) |
它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中 TrasnactionManager就是⼀个切⾯。 |
连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点
切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法
Advice增强:
第⼀个层次:指的是横切逻辑
第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)
Aspect切⾯:切⾯概念是对上述概念的⼀个综合
Aspect切⾯=切⼊点+增强
=切⼊点(锁定⽅法)+⽅位点(锁定⽅法中的特殊时机)+横切逻辑
众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码
Spring中AOP的代理选择
Spring实现AOP思想使⽤的是动态代理技术
默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。
Spring中AOP的配置⽅式
在Spring的AOP配置中,也和IoC配置⼀样,⽀持3类配置⽅式。
第⼀类:使⽤XML配置
第⼆类:使⽤XML+注解组合配置
第三类:使⽤纯注解配置
Spring中AOP实现
需求:横切逻辑代码是打印⽇志,希望把打印⽇志的逻辑织⼊到⽬标⽅法的特定位置(service层transfer⽅法)
Spring是模块化开发的框架,使⽤aop就引⼊aop的jar
- xml模式
<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.lagou.utils.LogUtil"></bean>
<!--开始aop的配置-->
<aop:config>
<!--配置切⾯-->
<aop:aspect id="logAdvice" ref="logUtil">
<!--配置前置通知-->
<aop:before method="printLog" pointcut="execution(public *
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou
.pojo.Account))"></aop:before>
</aop:aspect>
</aop:config>
- 细节
关于切⼊点表达式
上述配置实现了对 TransferServiceImpl的 updateAccountByCardNo⽅法进⾏增强,在其执⾏之前,输出了记录⽇志的语句。这⾥⾯,我们接触了⼀个⽐较陌⽣的名称:切⼊点表达式,它是做什么的呢?我们往下看。
- 概念及作⽤
切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分。
- 关于AspectJ
AspectJ是⼀个基于Java语⾔的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切⼊点表达式的部分,开始⽀持AspectJ切⼊点表达式。
- 切⼊点表达式使⽤示例
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
全匹配⽅式:
public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
访问修饰符可以省略
void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
返回值可以使⽤*,表示任意返回值
*
com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
*
TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
包名可以使⽤..表示当前包及其⼦包
*
..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account
)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.lagou.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:aspectj-
autoproxy>
- 五种通知类型
前置通知
配置⽅式: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" pointcut-ref="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:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
特别说明
环绕通知,它是有别于前⾯四种通知类型外的特殊通知。前⾯四种通知(前置,后置,异常和最终)
它们都是指定何时增强的通知类型。⽽环绕通知,它是Spring框架为我们提供的⼀种可以通过编码的
⽅式,控制增强代码何时执⾏的通知类型。它⾥⾯借助的ProceedingJoinPoint接⼝及其实现类,
实现⼿动触发切⼊点⽅法的调⽤。
- **XML+**注解模式
XML中开启 Spring对注解 AOP的⽀持
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
- 示例
@Component
@Aspect
public class LogUtil {
/**
* 我们在xml中已经使⽤了通⽤切⼊点表达式,供多个切⾯使⽤,那么在注解中如何使⽤呢?
* 第⼀步:编写⼀个⽅法
* 第⼆步:在⽅法使⽤@Pointcut注解
* 第三步:给注解的value属性提供切⼊点表达式
* 细节:
* 1.在引⽤切⼊点表达式时,必须是⽅法名+(),例如"pointcut()"。
* 2.在当前切⾯中使⽤,可以直接写⽅法名。在其他切⾯中使⽤必须是全限定⽅法名。*/
@Pointcut("execution(* com.lagou.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;
}
- 注解模式
在使⽤注解驱动开发aop时,我们要明确的就是,是注解替换掉配置⽂件中的下⾯这⾏配置:
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy/>
在配置类中使⽤如下注解进⾏替换上述配置
/*
* @author 应癫
*/
@Configuration
@ComponentScan("com.lagou")
@EnableAspectJAutoProxy //开启spring对注解AOP的⽀持
public class SpringConfiguration {
}
Spring声明式事务的⽀持
**编程式事务:**在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务
**声明式事务:**通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务
- 事务回顾
- 事务概念
事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数据的准确与安全。
例如:A——B转帐,对应于如下两条sql语句:
/*转出账户减钱*/
update account set money=money-100 where name=‘a’;
/**转⼊账户加钱*/
update account set money=money+100 where name=‘b’;
这两条语句的执⾏,要么全部成功,要么全部不成功。
- 事务的四⼤特性
- **原⼦性(Atomicity)**原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败
- ⼀致性(Consistency)事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。
例如转账前A有1000,B有1000。转账后A+B也得是2000。⼀致性是从数据的⻆度来说的,(1000,1000)(900,1100),不应该出现(900,1000) - **隔离性(Isolation)**事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。⽐如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000块钱,读到了事务1尚未提交的数据(脏读)
- **持久性(**Durability)
持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响。
- 事务的隔离级别
不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的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
事务的传播⾏为
事务往往在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类似的操作。 |
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(动态代理)来将事务控制逻辑织⼊到业务代码
Spring声明式事务配置
- 纯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.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
</aop:config>
- 基于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)
- 基于纯注解
Spring基于注解驱动开发的事务控制配置,只需要把 xml配置部分改为注解实现。只是需要⼀个注解替换掉xml配置⽂件中的 <tx:annotation-driven transaction-manager="transactionManager"/>配置。
在 Spring的配置类上添加@EnableTransactionManagement注解即可
@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}
Spring声明式事务控制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
}
@EnableTransactionManagement注解使⽤@Import标签引⼊了
TransactionManagementConfigurationSelector类,这个类⼜向容器中导⼊了两个重要的组件
加载事务控制组件
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组件
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 transactionmanagement.
*
* @author Chris Beams
* @since 3.1
* @see EnableTransactionManagement
* @see TransactionManagementConfigurationSelector
*/
@Configuration
public class ProxyTransactionManagementConfiguration extends
AbstractTransactionManagementConfiguration {
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;
}
@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,即属性解析器transactionAttributeSource和事务拦截器transactionInterceptor
属性解析器 AnnotationTransactionAttributeSource部分源码如下
属性解析器有⼀个成员变量是annotationParsers,是⼀个集合,可以添加多种注解解析器(TransactionAnnotationParser),我们关注 Spring的注解解析器,部分源码如下
属性解析器的作⽤之⼀就是⽤来解析@Transaction注解
TransactionInterceptor事务拦截器,部分源码如下
上述组件如何关联起来的?
- 事务拦截器实现了MethodInterceptor接⼝,追溯⼀下上⾯提到的InfrastructureAdvisorAutoProxyCreator后置处理器,它会在代理对象执⾏⽬标⽅法的时候获取其拦截器链,⽽拦截器链就是这个TransactionInterceptor,这就把这两个组件联系起来;构造⽅法传⼊PlatformTransactionManager(事务管理器)、TransactionAttributeSource(属性解析器),但是追溯⼀下上⾯贴的ProxyTransactionManagementConfiguration的源码,在注册事务拦截器的时候并没有调⽤这个带参构造⽅法,⽽是调⽤的⽆参构造⽅法,然后再调⽤set⽅法注⼊这两个属性,效果⼀样。
- invokeWithinTransaction⽅法,部分源码如下(关注1、2、3、4标注处)