20201128 IoC容器设计实现及Spring源码分析 - 拉勾教育
环境信息
- 笔记 Spring 版本:
5.1.12.RELEASE
第一部分 Spring 概述
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已经成为使用最多的 Java EE 企业应用开源框架。

第二部分 核心思想
IoC
-
IoC:Inversion of Control (控制反转/反转控制),注意它是⼀个 技术思想,不是一个技术实现
- 使用 IoC 让我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不用考虑对象的创建、管理等⼀系列事情)
-
IoC 解决对象之间的耦合问题,对象之间不再相互依赖,而是依赖于 IoC 容器

IoC 和 DI 的区别
- DI: Dependancy Injection(依赖注入)
- IoC 和 DI 描述的是同一件事情(对象实例化及依赖关系维护),只不过角度不同罢了
- IoC 是站在对象的角度,对象实例化及其管理的权利交给了(反转)给了容器
- DI 是站在容器的角度,容器会把对象依赖的其他对象注入(送进去),比如 A 对象实例化过程中因为声明了一个 B 类型的属性,那么就需要容器把 B 对象注入给 A
AOP
-
AOP:Aspect oriented Programming 面向切面编程/面向方面编程
-
AOP 是 OOP 的延续、补充
-
OOP 三大特征:封装、继承和多态
-
OOP 是一种垂直继承体系

-
AOP 在解决什么问题
- 在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
-
横切逻辑

-
AOP 独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析

第三部分 手写实现 IoC 和 AOP
略
第四部分 Spring IOC 应用

第五部分 Spring IOC 源码深度剖析
Spring IoC容器初始化主体流程
见参考资料
BeanFactory创建流程
见参考资料
Bean创建流程
见参考资料
读取 @Bean 的过程
读取 @Bean 注解的方法,加载为 BeanDefinition(org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition) :
org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitionsorg.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
lazy-init 延迟加载机制原理
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons- 加载所有 Bean 时,判断如果配置了懒加载的,直接跳过
Spring IoC 循环依赖问题

相关代码
xml 配置:
<!--循环依赖问题-->
<bean id="lagouBean" class="com.lagou.edu.LagouBean">
<property name="ItBean" ref="itBean"/>
</bean>
<bean id="itBean" class="com.lagou.edu.ItBean">
<property name="LagouBean" ref="lagouBean"/>
</bean>
Java 代码:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
LagouBean lagouBean = applicationContext.getBean(LagouBean.class);
System.out.println(lagouBean);
如上可知:
- lagouBean 和 itBean 互为依赖
- Spring 按照配置顺序加载 Bean,所以先加载 lagouBean
UML 图说明
首先,Spring 为解决循环依赖,使用了三级缓存:
- 一级:
DefaultSingletonBeanRegistry#singletonObjects- 赋值方法:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
- 赋值方法:
- 二级:
DefaultSingletonBeanRegistry#earlySingletonObjects- 赋值方法:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
- 赋值方法:
- 三级:
DefaultSingletonBeanRegistry#singletonFactories- 赋值方法:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory
- 赋值方法:
从三级缓存中获取值的方法为:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
Spring 加载两个互为依赖的 Bean 整体流程如 UML 图,图中说明如下:
- 整体流程为:
doGetBean方法获取 lagouBeangetSingleton方法从三级缓存中依次获取 lagouBean,未获取到doCreateBean方法、createBeanInstance方法创建 lagouBean 实例addSingletonFactory方法将 lagouBean 加入第三级缓存singletonFactoriespopulateBean方法为 lagouBean 属性赋值时通过doGetBean方法触发 itBean 的创建doGetBean方法获取 itBeangetSingleton方法从三级缓存中依次获取 itBean ,未获取到doCreateBean方法、createBeanInstance方法创建 itBean 实例addSingletonFactory方法将 itBean 加入第三级缓存singletonFactoriespopulateBean方法为 itBean 属性赋值时通过doGetBean方法从缓存中获取 lagouBeangetSingleton方法从三级缓存中依次获取 lagouBean,从第三级缓存中获取到,将 lagouBean 存入第二级缓存earlySingletonObjects,并从第三级缓存singletonFactories中删除 lagouBean
- itBean 创建完成,
addSingleton将 itBean 加入第一级缓存,并从第二、三级缓存中删除(此时,itBean 在第三级缓存,lagouBean 在第二级缓存)
- lagouBean 创建完成,
addSingleton将 lagouBean 加入第一级缓存,并从第二、三级缓存中删除
第六部分 Spring AOP 应用
参考资料:
-
AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。
| 名词 | 解释 |
|---|---|
| Joinpoint 连接点 |
它指的是那些可以用于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在 Spring 框架 AOP 思想的技术实现中,也只支持方法类型的连接点。 |
| Pointcut 切入点 |
它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer 方法就只是连接点,因为判断访问权限的功能并没有对其增强。 |
| Advice 通知 / 增强 |
它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不⼀样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有: 前置通知、后置通知、异常通知、最终通知、环绕通知。 |
| Target 目标对象 |
它指的是代理的目标对象。即被代理对象。 |
| Proxy 代理 |
它指的是一个类被 AOP 织入增强后,产生的代理类。即代理对象。 |
| Weaving 织入 |
它指的是把增强应用到目标对象来创建新的代理对象的过程。Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 |
| Aspect 切面 |
它指定是增强的代码所关注的方面,把这些相关的增强代码定义到⼀个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。我们前面 的案例中 TrasnactionManager 就是⼀个切面。 |
-
连接点:方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是一种候选点
-
切入点:指定 AOP 思想想要影响的具体方法是哪些,描述感兴趣的方法
-
Advice 通知/增强:
- 第一个层次:指的是横切逻辑
- 第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点,描述的是具体的特殊时机)
-
Aspect 切面:切面概念是对上述概念的一个综合
-
Aspect 切面= 切入点+增强 = 切入点(锁定方法) + 方位点(锁定方法中的特殊时机) + 横切逻辑
众多的概念,目的就是为了锁定要在哪个地方插入什么横切逻辑代码
Spring 中 AOP 的配置方式
- 第一类:使用 XML 配置
- 使用 XML + 注解组合配置
- 第三类:使用纯注解配置
Maven 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!--第三方的aop框架aspectj的jar-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
XML 模式
- 把通知 Bean 、切面 Bean 交给 Spring 管理
- 使用
aop:config开始 aop 的配置 - 使用
aop:aspect配置切面 - 使用对应的标签配置通知的类型
<?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 id="eagerBean" class="com.lagou.edu.aop.EagerBean"/>
<bean id="logAspect" class="com.lagou.edu.aop.LogAspect"/>
<!--配置 aop-->
<aop:config>
<!--配置切⾯-->
<aop:aspect id="logAdvice" ref="logAspect">
<!--配置前置通知-->
<aop:before method="beforeMethod" pointcut="execution(* com.lagou.edu.aop.EagerBean.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
XML + 注解模式
<!--开启spring对注解aop的⽀持-->
<aop:aspectj-autoproxy />
@Aspect
public class LogAspect {
@Pointcut("execution(* com.lagou.edu.aop.EagerBean.*(..))")
public void pt1(){
}
/**
* 业务逻辑开始之前执行
*/
@Before("pt1()")
public void beforeMethod(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
System.out.println(arg);
}
System.out.println("业务逻辑开始执行之前执行.......");
}
}
注解模式
@EnableAspectJAutoProxy
public class AopTest {
@Test
public void testXML() throws IOException {
// ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("aop.xml");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AopTest.class);
EagerBean eagerBean = applicationContext.getBean(EagerBean.class);
eagerBean.testEager();
}
@Bean
public EagerBean eagerBean() {
return new EagerBean();
}
@Bean
public LogAspect logAspect() {
return new LogAspect();
}
}
切入点表达式
-
切入点表达式,也称之为 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:aspectjautoproxy>
-
选择通知类型
参考资料:
-
前置通知:
before
方法调用之前执行;
如果前置通知抛出异常,拦截器链(以及目标方法)被终止,异常将传回拦截器链;
- 后置返回通知
after-returning
方法调用并且返回一个值后执行;
如果目标方法抛出异常,不会执行此通知,异常传回调用堆栈;
- 异常通知
after-throwing
在方法调用返回后、抛出异常时执行;
- 后置通知
after
方法调用正常完成后执行;
即使目标方法抛出异常,也会执行此通知;
- 环绕通知
around
允许在方法调用之前、之后执行;
如果需要,可以选择绕过目标方法;
- 引入通知
一种特殊类型的环绕通知。引入仅适用于类级别,因此不能在引入时使用切入点。
可以指定由引入通知引入的方法的实现;
Spring 声明式事务的支持
参考资料:
事务的传播行为
- 事务往往在 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 类似的操作。 |
Spring 声明式事务的API
- Spring 的事务管理器核心接口:
org.springframework.transaction.PlatformTransactionManager - Spring 本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。
- 声明式事务要做的就是使用 Aop (动态代理)来将事务控制逻辑织入到业务代码
Spring 声明式事务配置
XML 模式
Maven 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<!-- mysql数据库驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
XML 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--引入外部资源文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--第三方jar中的bean定义在xml中-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="bankService" class="com.lagou.edu.tx.BankService">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--spring声明式事务配置,声明式事务无非就是配置一个aop,只不过有些标签不一样罢了-->
<!--横切逻辑-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<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(void com.lagou.edu.tx.BankService.transfer(..))"/>
</aop:config>
</beans>
XML + 注解模式
XML 配置
<!--开启 spring 对注解事务的⽀持-->
<tx:annotation-driven transaction-manager="transactionManager" />
在接口、类或者方法上添加 @Transactional 注解
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)
public void transfer(String fromNo, String toNo, Integer money) {
this.doTransfer(fromNo, -money);
if (money == 1000) {
throw new IllegalArgumentException("测试事务异常");
}
this.doTransfer(toNo, money);
}
private void doTransfer(String cardNo, Integer money) {
jdbcTemplate.update("update account set money = money + ? where cardNo = ?", money, cardNo);
}
注解模式
// 开启spring注解事务的⽀持
@EnableTransactionManagement
Spring AOP API 分析
-
org.aopalliance.intercept.MethodInterceptor:用于实现方法调用连接点的环绕通知- 对应通知类型的包装
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor
-
org.springframework.aop.framework.ProxyFactory: 用于创建目标对象的代理,控制Spring AOP中的织入和代理创建过程 -
org.springframework.aop.Advisor: 是Spring中某个切面的表示org.springframework.aop.PointcutAdvisor: 使用切入点来控制应用于连接点的通知,它是通知(Advice)和切入点(Pointcut)的结合体,规定了应该通知哪些方法以及如何通知。DefaultPointcutAdvisorNameMatchMethodPointcutAdvisor
-
org.aopalliance.aop.Advice: 通知 / 增强org.springframework.aop.MethodBeforeAdvice: 前置通知org.springframework.aop.AfterReturningAdvice: 后置返回通知org.springframework.aop.ThrowsAdvice: 异常通知org.springframework.aop.AfterAdvice:后置通知org.aopalliance.intercept.MethodInterceptor:环绕通知org.springframework.aop.IntroductionInterceptor:引入通知
-
org.springframework.aop.Pointcut: 切入点AnnotationMatchingPointcut: 创建注解匹配切入点AspectJExpressionPointcut: 使用AspectJ切入点表达式创建切入点DynamicMethodMatcherPointcut: 动态匹配切入点JdkRegexpMethodPointcut: 用正则表达式创建切入点NameMatchMethodPointcut: 使用简单名称匹配StaticMethodMatcherPointcut: 静态匹配切入点ComposablePointcut: 组合切入点org.springframework.aop.support.Pointcuts
ControlFlowPointcut: 控制流切入点
-
org.springframework.aop.MethodMatcher: 方法匹配器
第七部分 Spring AOP 源码深度剖析
AOP 代理对象创建
- 注解引入
BeanPostProcessor-AnnotationAwareAspectJAutoProxyCreator org.springframework.context.annotation.EnableAspectJAutoProxyorg.springframework.context.annotation.AspectJAutoProxyRegistrar#registerBeanDefinitionsorg.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor)
- 加载 Bean 时,触发后置处理器进行处理,包装为 代理类
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitializationorg.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
Spring 声明式事务控制
-
注解引入
BeanPostProcessor-InfrastructureAdvisorAutoProxyCreator -
org.springframework.transaction.annotation.EnableTransactionManagementorg.springframework.context.annotation.AutoProxyRegistrar#registerBeanDefinitionsorg.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
-
加载 Bean 时,触发后置处理器进行处理,包装为 代理类
-
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitializationorg.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors
浙公网安备 33010602011771号