20201128 IoC容器设计实现及Spring源码分析

环境信息

  • Spring 版本:5.1.12.RELEASE

第一部分 Spring 概述

Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring
MVC 和业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,已
经成为使用最多的 Java EE 企业应用开源框架。

img

第二部分 核心思想

IoC

  • IoC:Inversion of Control (控制反转/反转控制),注意它是⼀个 技术思想,不是一个技术实现

    • 使用 IoC 让我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不用考虑对象的创建、管理等⼀系列事情)
  • IoC 解决对象之间的耦合问题,对象之间不再相互依赖,而是依赖于 IoC 容器

img

IoC 和 DI 的区别

  • DI: Dependancy Injection(依赖注入)
  • IoC 和 DI 描述的是同一件事情(对象实例化及依赖关系维护),只不过角度不同罢了
  • IoC 是站在对象的角度,对象实例化及其管理的权利交给了(反转)给了容器
  • DI 是站在容器的角度,容器会把对象依赖的其他对象注入(送进去),比如 A 对象实例化过程中因为声明了一个 B 类型的属性,那么就需要容器把 B 对象注入给 A

AOP

  • AOP:Aspect oriented Programming 面向切面编程/面向方面编程

  • AOP 是 OOP 的延续、补充

  • OOP 三大特征:封装、继承和多态

  • OOP 是一种垂直继承体系

    img

  • AOP 在解决什么问题

    • 在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复
  • 横切逻辑

    img

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

    img

第三部分 手写实现 IoC 和 AOP

第四部分 Spring IOC 应用

img

第五部分 Spring IOC 源码深度剖析

Spring IoC容器初始化主体流程

见参考资料

BeanFactory创建流程

见参考资料

Bean创建流程

见参考资料

读取 @Bean 的过程

读取 @Bean 注解的方法,加载为 BeanDefinitionorg.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.ConfigurationClassBeanDefinition) :

  • org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
    • org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod

lazy-init 延迟加载机制原理

  • org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
    • 加载所有 Bean 时,判断如果配置了懒加载的,直接跳过

Spring IoC 循环依赖问题

img

相关代码

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 为解决循环依赖,使用了三级缓存:

  1. DefaultSingletonBeanRegistry#singletonObjects
    • 赋值方法:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton
  2. DefaultSingletonBeanRegistry#earlySingletonObjects
    • 赋值方法:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
  3. DefaultSingletonBeanRegistry#singletonFactories
    • 赋值方法:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory

从三级缓存中获取值的方法为:

  • org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

Spring 加载两个互为依赖的 Bean 整体流程如 UML 图,图中说明如下:

  • 整体流程为:
    1. doGetBean 方法获取 lagouBean
    2. getSingleton 方法从三级缓存中获取 lagouBean,未获取到
    3. doCreateBean 方法、createBeanInstance 方法创建 lagouBean 实例
    4. addSingletonFactory 方法将 lagouBean 加入第三级缓存 singletonFactories
    5. populateBean 方法为 lagouBean 属性赋值时通过 doGetBean 方法触发 itBean 的创建
      1. doGetBean 方法获取 itBean
      2. getSingleton 方法从三级缓存中获取 itBean ,未获取到
      3. doCreateBean 方法、createBeanInstance 方法创建 itBean 实例
      4. addSingletonFactory 方法将 itBean 加入第三级缓存 singletonFactories
      5. populateBean 方法为 itBean 属性赋值时通过 doGetBean 方法从缓存中获取 lagouBean
        1. getSingleton 方法从三级缓存中获取 lagouBean,获取到,存入二级缓存 earlySingletonObjects,并从三级缓存 singletonFactories 中删除 lagouBean
      6. itBean 创建完成,addSingleton 将 itBean 加入一级缓存,并从二、三级缓存中删除(此时,itBean 在三级缓存,lagouBean 在二级缓存)
    6. lagouBean 创建完成,addSingleton 将 lagouBean 加入一级缓存,并从二、三级缓存中删除

第六部分 Spring AOP 应用

参考资料:

名词 解释
Joinpoint
连接点
它指的是那些可以用于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在 Spring 框架 AOP 思想的技术实现中,也只支持方法类型的连接点。
Pointcut
切入点
它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层 transfer 方法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice
通知 / 增强
它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不⼀样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有: 前置通知、后置通知、异常通知、最终通知、环绕通知。
Target
目标对象
它指的是代理的目标对象。即被代理对象。
Proxy
代理
它指的是一个类被 AOP 织入增强后,产生的代理类。即代理对象。
Weaving
织入
它指的是把增强应用到目标对象来创建新的代理对象的过程。Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Aspect
切面
它指定是增强的代码所关注的方面,把这些相关的增强代码定义到⼀个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。我们前面 的案例中 TrasnactionManager 就是⼀个切面。
  • 连接点:方法开始时、结束时、正常运行完毕时、方法异常时等这些特殊的时机点,我们称之为连接点,项目中每个方法都有连接点,连接点是一种候选点

  • 切入点:指定 AOP 思想想要影响的具体方法是哪些,描述感兴趣的方法

  • Advic e增强:

    • 第⼀个层次:指的是横切逻辑
    • 第二个层次:方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点,描述的是具体的特殊时机)
  • 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 模式

  1. 把通知 Bean 、切面 Bean 交给 Spring 管理
  2. 使用 aop:config 开始 aop 的配置
  3. 使用 aop:aspect 配置切面
  4. 使用对应的标签配置通知的类型
<?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)的结合体,规定了应该通知哪些方法以及如何通知。
      • DefaultPointcutAdvisor
      • NameMatchMethodPointcutAdvisor
  • 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.EnableAspectJAutoProxy
    • org.springframework.context.annotation.AspectJAutoProxyRegistrar#registerBeanDefinitions
      • org.springframework.aop.config.AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)
      • AnnotationAwareAspectJAutoProxyCreator(BeanPostProcessor)
  • 加载 Bean 时,触发后置处理器进行处理,包装为 代理类
  • org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
    • org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

Spring 声明式事务控制

  • 注解引入 BeanPostProcessor - InfrastructureAdvisorAutoProxyCreator

  • org.springframework.transaction.annotation.EnableTransactionManagement

    • org.springframework.context.annotation.AutoProxyRegistrar#registerBeanDefinitions
    • org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
  • 加载 Bean 时,触发后置处理器进行处理,包装为 代理类

  • org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization

    • org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

参考资料

原文地址:https://www.cnblogs.com/huangwenjie/p/14053639.html