深入了解 Spring IOC,AOP 两大核心思想

文章目录

  • 一、Spring 基础 - 控制反转(IOC)
    • 1.1. 引入
    • 1.2. 如何理解 IOC
      • Spring Bean 是什么?
      • IoC 是什么?
      • IoC 能做什么?
      • IoC 和 DI 是什么关系?
    • 1.3. IoC 配置的三种方式
      • xml 配置
      • Java 配置
      • 注解配置
    • 1.4. 依赖注入的三种方式
      • setter 方式
      • 构造函数
      • 注解注入
    • 1.5. IoC 和 DI 使用问题小结
      • 为什么推荐构造器注入方式?
      • @Autowired 和 @Resource 以及 @Inject 等注解注入有何区别?
  • 二、Spring 基础 - 面向切面编程(AOP)
    • 2.1. 引入
    • 2.2. 如何理解 AOP
      • AOP 是什么?
      • AOP 术语
      • Spring AOP 和 AspectJ 是什么关系?
    • 2.3. AOP 的配置方式
      • XML Schema 配置方式
      • AspectJ 注解方式
    • 2.4. AOP 使用问题小结
      • 切入点(pointcut)的申明规则?
      • 多种增强通知的顺序?
      • Spring AOP 还是完全用 AspectJ?
  • 三、Spring 进阶 - IOC 体系结构设计
    • 3.1 站在设计者的角度考虑设计 IOC 容器
    • 3.2 Spring IoC 的体系结构设计
      • BeanFactory 和 BeanRegistry:IOC 容器功能规范和 Bean 的注册
      • BeanDefinition:各种 Bean 对象及其相互的关系
    • 3.3 ApplicationContext:IOC 接口设计和实现
      • ApplicationContext 接口的设计
      • **ApplicationContext 接口的实现**
  • 四、Spring 进阶 - IOC 初始化流程
    • 4.1 引入
    • 4.2 如何将 Bean 从 XML 配置中解析后放到 IoC 容器中的?
      • 初始化的入口
      • 设置资源解析器和环境
      • 设置配置路径
      • 初始化的主体流程
    • 4.3 总结
  • 五、Spring 进阶 - Bean 实例化(声明周期、循环依赖等)
    • 5.1 引入
    • 5.2 BeanFactory 中 getBean 的主体思路
      • 初步的思考
      • Spring 中 getBean 的主体思路
    • 5.3 重点:Spring 如何解决循环依赖问题
      • Spring 单例模式下的属性依赖
      • Spring 为何不能解决非单例属性之外的依赖循环?
      • 那么其它循环依赖如何解决?
    • 5.4 重点:Spring 中 Bean 的生命周期
      • Spring Bean 的生命周期流程
      • Spring Bean 生命周期案例
  • 六、Spring 进阶 - AOP 切面的实现
    • 6.1 引入
    • 6.2 AOP 配置标签的解析
      • config 配置标签的解析
      • aspectj-autoproxy 配置标签的解析
    • 6.3 注解切面代理创建类(AnnotationAwareAspectJAutoProxyCreator)
      • postProcessorBeforeInstantiation
      • postProcessAfterInitialization
    • 6.4 总结
  • 七、Spring 进阶 - AOP 代理的创建
    • 7.1 引入
    • 7.2 代理的创建
  • 八、Spring 进阶 - Cglib 代理实现
    • 8.1 引入
      • 动态代理要解决什么问题?
    • 8.2 Cglib 代理的例子
      • pom 包依赖
      • 定义实体类
      • 被代理的类
      • Cglib 代理
      • 使用代理
      • 简单测试
    • 8.3 Cglib 代理的流程
    • 8.4 Spring AOP 中 Cglib 代理的实现
  • 九、Spring 进阶 - JDK 代理实现
    • 9.1 引入
      • 什么是 JDK 代理?
    • 9.2 JDK 代理的例子
      • 定义实体
      • 被代理的类和接口
      • JDK 代理类
      • 使用代理
      • 简单测试
    • 9.3 JDK 代理的流程
      • ProxyGenerator 生成代码
      • 从生成的 Proxy 代码看执行流程
    • 9.4 Spring AOP 中 JDK 代理的实现
      • Spring AOP Jdk 代理的创建
      • Spring AOP Jdk 代理的执行

本文围绕 Spring 的 IOC 和 AOP 两大核心思想展开学习,从基础到进阶,一步步使用并了解 Spring 对其实现方式。

一、Spring 基础 - 控制反转(IOC)

1.1. 引入

  1. Spring 框架管理这些 Bean 的创建工作,即用户管理 Bean 转变为框架管理 Bean,这个就叫做控制反转 - Inversion of Control(IoC)
  2. Spring 框架托管创建的 Bean 放在哪里?这便是 IoC Container;
  3. Spring 框架为了更好让用户配置 Bean,必然会引入不同方式来配置 Bean?这便是 xml 配置、Java 配置、注解配置等支持
  4. Spring 框架既然接管了 Bean 的生成,必然需要管理整个 Bean 的生命周期等;
  5. 应用程序代码从 IoC Container 中获取依赖的 Bean,注入到应用程序中,这个过程叫依赖注入(Dependency Injection,DI);所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是 IoC 是涉及思想,DI 是实现方式;
  6. 在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired、@Resource、@Qualifier…同时 Bean 之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)

​ 本章节将在此基础上进一步解读 IOC 的含义以及 IOC 的使用方式。

1.2. 如何理解 IOC

Spring Bean 是什么?

IOC Container 管理的是 Spring Bean,那么 Spring Bean 是什么呢?

​ Spring 里面的 bean 就类似是定义的一个组件,而这个组件的作用就是实现某个功能的,这里所定义的 bean 就相当于给了你一种更为简便的方法来调用这个组件去实现要完成的功能。

IoC 是什么?

IoC - Inversion of Control,即 “控制反转”,不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意味着将设计好的对象交给容器控制,而不是传统的在对象内部控制。

​ 我们来深入分析一下:

  • 谁控制谁,控制什么?

​ 传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 IoC 容器来控制对象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  • 为何是反转,哪方面反转了?

​ 有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

​ 如下图例所示:

​ 首先是传统程序设计下,都是主动去创建相关对象然后再进行组合:

在这里插入图片描述

​ 当有了 Ioc / DI 的容器后,在客户端类中不再主动去创建这些对象了,如下图:

在这里插入图片描述

IoC 能做什么?

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。

​ 传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难以测试;有了 IoC 容器后, 把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象之间是松耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

​ 其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了 “主从换位” 的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。

​ IoC 很好地体现了面向对象编程法则之一 —— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找响应的依赖对象并注入,而不是由对象主动去找。

IoC 和 DI 是什么关系?

控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是 IoC 是设计思想,DI 是实现方式。

​ DI - Dependency Injection,即依赖注入:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态地将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可以指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

1.3. IoC 配置的三种方式

xml 配置

​ 顾名思义,就是将 bean 的信息配置到 .xml 文件里,通过 Spring 加载文件为我们创建 bean。这种方式出现很多早期的 SSM 项目中,将第三方类库或者一些配置工具都以这种方式进行配置,主要原因是由于第三方类不支持 Spring 注解。

  • 优点:可以适用于任何场景,结构清晰,通俗易懂。
  • 缺点:配置繁琐,不宜维护,枯燥无味,拓展性差。

举例:

  1. 配置 xx.xml 文件
  2. 声明命名空间和配置 bean
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="io.xinghuo.spring.UserDao"/>

    <bean id="userService" class="io.xinghuo.spring.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

Java 配置

​ 将类的创建交给我们配置的 JavaConfig 类来完成,Spring 只负责维护和管理,采用纯 Java 创建方式。其本质上就是把 XML 上的配置声明移到到 Java 配置类中。

  • 优点:适用于任何场景,配置方便,因为是纯 Java 代码,拓展性高,十分灵活。
  • 缺点:由于是采用 Java 类的方式,声明不明显,如果大量配置,可读性比较差。

举例:

  1. 创建一个配置类,添加 @Configuration 注解声明为配置类
  2. 创建方法,方法上加上 @Bean,该方法用于创建实例并返回,该实例创建后会交由 Spring 管理,方法名建议与实例名相同(首字母小写)。注:实力类不需要加任何注解。
@Configuration
public class BeansConfig {
    
    @Bean("userDao")
    public UserDao userDao() {
        return new UserDao();
    }
    
    @Bean("userService")
    public UserServiceImpl userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao());
        return userService;
    }
}

注解配置

​ 通过在类上加注解的方式,来声明一个类交给 Spring 管理,Spring 会自动扫描带有 @Component、@Controller、@Service、@Repository 这四个注解的类,然后帮我们创建并管理,前提是需要先配置 Spring 的注解扫描器。

  • 优点:开发便捷,通俗易懂,方便维护。
  • 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用 XML 或 JavaConfig 的方式配置。

举例:

  1. 对类添加 @Component 相关的注解
  2. 设置 ComponentScan 的 basePackage,比如 <context:component-scan base-package="io.xinghuo.spring"/> 或者 @ComponentScan("io.xinghuo.spring") 注解 等。
@Service
public class UserServiceImpl {
    @Autowired
    private UserDao userDao;

    public List<User> findAll() {
        return userDao.getAll();
    }
}

1.4. 依赖注入的三种方式

常用的依赖注入主要有三种方式:构造方法注入(Construct 注入)、setter 注入、基于注解的注入(接口注入)。

setter 方式

  • 在 XML 配置方式中,property 都是 setter 方式注入,比如下面的 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="io.xinghuo.spring.UserDao"/>

    <bean id="userService" class="io.xinghuo.spring.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

本质上包含两步:

  1. 第一步,需要 new UserServiceImpl() 创建对象,所以需要默认构造函数
  2. 第二步,调用 setUserDao() 函数注入 userDao 的值,所以需要 setUserDao() 的函数

所以对应的 service 类是这样的:

public class UserServiceImpl {

    private UserDao userDao;

    public UserServiceImpl() {
    }

    public List<User> findAll() {
        return userDao.getAll();
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}
  • 在注解和 Java 配置方式下
public class UserServiceImpl {

    private UserDao userDao;

    public List<User> findAll() {
        return userDao.getAll();
    }

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

在 Spring3.X 刚推出的时候,推荐使用注入的就是这种,但是这种方式比较麻烦,所以在 Spring4.X 版本中推荐构造函数注入。

构造函数

  • 在 XML 配置方式中,constructor-org> 是通过构造函数参数注入,比如下面的 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userDao" class="io.xinghuo.spring.UserDao"/>

    <bean id="userService" class="io.xinghuo.spring.UserServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
</beans>

本质上是 new UserServiceImpl(userDao) 创建对象,所以对应的 service 类是这样的:

public class UserServiceImpl {

    private final UserDao userDao;

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public List<User> findAll() {
        return userDao.getAll();
    }
}
  • 在注解和 Java 配置方式下
@Service
public class UserServiceImpl {

    private final UserDao userDao;

    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    public List<User> findAll() {
        return userDao.getAll();
    }
}

在 Spring4.X 版本中推荐的注入方式就是这种。

注解注入

​ 以 @Autowired(自动注入)注解注入为例,修饰符有三个属性:Constructor、byType、byName。默认按照 byType 注入。

  • Constructor:通过构造方法进行自动注入,spring 会匹配与构造方法参数类型一致的 bean 进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的 bean,那么 spring 会优先将 bean 注入到多参数的构造方法中。
  • byName:被注入 bean 的 id 名必须与 set 方法后半截匹配,并且 id 名称的第一个单词首字母必须小写,这一点与手动 set 注入优点不同。
  • byType:查找所有的 set 方法,将符合参数类型的 bean 注入。

比如:

public class UserServiceImpl {

    @Autowired
    private UserDao userDao;

    public List<User> findAll() {
        return userDao.getAll();
    }
}

1.5. IoC 和 DI 使用问题小结

为什么推荐构造器注入方式?

​ 先看一下 Spring 在文档中解释:

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.

​ 简单翻译一下:这个构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。

下面简单解释一下:

  • 依赖不可变:其实说的就是 final 关键字。
  • 依赖不为空(省去了我们对其检查):当要实例化 UserServiceImpl 的时候,由于自己实现了有参数的构造函数,所以不会调用默认无参构造函数,那么就需要 Spring 容器传入所需要的参数,所以就两种情况:1、有该类型的参数 -> 传入,正确实例化。2、无该类型的参数 -> 报错。
  • 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在 Java 类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的都是初始化之后的状态。

所以通常是这样的:

@Service
public class UserServiceImpl {
    
    private final UserDao userDao;

    public UserServiceImpl(final UserDao userDao) {
        this.userDao = userDao;
    }
}

如果使用 setter 注入,缺点是显而易见,对于 IoC 容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现 NPE 的存在。

UserServiceImpl userService = new UserServiceImpl();
userService.findAll(); // -> NullPointerException, 潜在的隐患

循环依赖的问题:使用 field 注入可能会导致循环依赖,即 A 里面注入 B,B 里面又注入 A:

public class A {
	@Autowired
	private B b;
}

public class B {
	@Autowired
	private A a;
}

​ 如果使用构造器注入,在 spring 项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是 field 注入的话,启动的时候就不会报错,在使用那个 bean 的时候才会报错。

@Autowired 和 @Resource 以及 @Inject 等注解注入有何区别?

@Autowired

  • Autowired 注解源码
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

从 Autowired 注解源码上看,可以使用在下面这些地方:

@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.PARAMETER) #方法参数
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.ANNOTATION_TYPE) #注解

还有一个 value 属性,默认是 true。

  • 简单总结:
  1. @Autowired 是 Spring 自带的注解,通过 AutowiredAnnotationBeanPostProcessor 类实现的依赖注入
  2. @Autowired 可以作用在 CONSTRUCTOR、METHOD、FIELD、ANNOTATION_TYPE
  3. @Autowired 默认是根据类型(byType)进行自动装配的
  4. 如果有多个类型一样的 Bean 候选者,需要指定按照名称(byName)进行装配,则需要配合 @Qualifier

​ 指定名称后,如果 Spring IoC 容器中没有对应的组件 bean 抛出 NoSuchBeanDefinitionException,也可以将 @Autowired 中 required 配置为 false,如果配置为 false 之后,当没有找到相应 bean 的时候,系统不会抛出异常。

@Resource

  • Resource 注解源码
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    // 其他省略
}

从 Resource 注解源码上看,可以使用在下面这些地方:

@Target(ElementType.TYPE) #接口、类、枚举、注解
@Target(ElementType.FIELD) #字段、枚举的常量
@Target(ElementType.METHOD) #方法

name 指定注入指定名称的组件。

  • 简单总结:
  1. @Resource 是 JSR250 规范的实现,在 javax.annotation 包下
  2. @Resource 可以作用在 TYPE、FIELD、METHOD 上
  3. @Resource 是默认根据属性名称进行自动装配的,如果有多个类型一样的 Bean 候选者,则可以通过 name 进行指定注入。

@Inject

  • Inject 注解源码
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inject {
}

从 Inject 注解源码上看,可以用使用在下面这些地方:

@Target(ElementType.CONSTRUCTOR) #构造函数
@Target(ElementType.METHOD) #方法
@Target(ElementType.FIELD) #字段、枚举的常量
  • 简单总结:
  1. @Inject 是 JSR330 中的规范,需要导入 javax.inject.Inject jar 包,才能实现注入
  2. @Inject 可以作用 CONSTRUCTOR、METHOD、FIELD 上
  3. @Inject 是根据类型进行自动装配的,如果需要按名称进行装配,则需要配置 @Named

总结

  1. @Autowired 是 Spring 自带的,@Resource 是 JSR250 规范实现的,@Inject 是 JSR330 规范实现的
  2. @Autowired、@Inject 用法基本一样,不同的是 @Inject 没有 required 属性
  3. @Autowired、@Inject 是默认按照类型匹配的,@Resource 是按照名称匹配的
  4. @Autowired 如果需要按照名称匹配需要和 @Qualifier 一起使用,@Inject 和 @Named 一起使用,@Resource 则通过 name 进行指定。

二、Spring 基础 - 面向切面编程(AOP)

2.1. 引入

  1. Spring 框架通过定义切面,通过拦截切点实现了不同业务模块的解耦,这个就叫面向切面编程 - Aspect Oriented Programming(AOP)
  2. 为什么 @Aspect 注解使用的是 aspect 的 jar 包呢?这就引入了 Aspect4J 和 Spring AOP 的历史历史渊源,只有理解了 Aspect4J 和 Spring 的渊源才能理解有些注解上的兼容设计
  3. 如何支持更多拦截方式来实现解耦,以满足更多场景需求呢?这就是 @Around、@Pointcut … 等的设计
  4. 那么 Spring 框架又是如何实现 AOP 的呢?这就引入 代理技术,分静态代理和动态代理,动态代理又包含 JDK 代理和 CGLIB 代理等

​ 本章节将在此基础上进一步解读 AOP 的含义以及 AOP 的使用方式。

2.2. 如何理解 AOP

AOP 是什么?

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程

​ AOP 最早是 AOP 联盟组织提出的,指定的一套规范,Spring 将 AOP 的思想引入框架之中,通过预编译方式运行期间动态代理实现程序的统一维护的一种技术。

  • 先来看一个例子,如何给 UserServiceImpl 中所有方法添加日志:
public class UserServiceImpl {
    public List<User> findAll() {
        System.out.println("execute method: findAll");
        return Collections.singletonList(new User("xinghuo", 18));
    }

    public void addUser() {
        System.out.println("execute method: addUser");
    }
}
我们将记录日志功能解耦为日志切面,它的目标是解耦,进而引出 AOP 的理念:就是将分散在各个业务逻辑代码中相同的代码通过**横向切割**的方式抽取到一个独立的模块中!

在这里插入图片描述

​ OOP 面向对象编程,针对业务处理的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。而 AOP 则是针对业务处理过程中的切面进行提取,它所面对的是处理过程的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合的隔离效果。这两种设计思想在目标上有着本质的差异。

在这里插入图片描述

AOP 术语

首先让我们从一些重要的 AOP 概念和术语开始。这些术语不是 Spring 特有的

  • 连接点(Jointpoint):表示需要在程序中插入横切关注点的拓展点,连接点可能是类初始化、方法执行、方法调用、字段调用或处理异常等等,Spring 只支持方法执行连接点,在 AOP 中表示为 在哪里干
  • 切入点(Pointcut):选择一组相关连接点的模式,即可以认为i连接点的集合,Spring 支持 perl5 正则表达式和 AspectJ 切入点模式,Spring 默认使用 AspectJ 语法,在 AOP 中表示在哪里干的集合
  • 通知(Advice):在连接点上执行的行为,通知提供了在 AOP 中需要在切入点所选择的连接点处进行拓展现有行为的手段;包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice),在 Spring 中通过代理模式实现 AOP,并通过拦截器模式以环绕连接点的拦截器链织入通知;在 AOP 中表示为干什么
  • 方面/切面(Aspect):横切关注点的模块化,比如上边提到的日志组件。可以认为是通知、引入和切入点的组合;在 Spring 中可以使用 Schema 和 @AspectJ 方式进行组织实现;在 AOP 中表示为在哪里干和干什么集合
  • 引入(inter-type declaration):也称为内部类型声明,为已有的类添加额外新的字段或方法,Spring 允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象),在 AOP 中表示为干什么(引入什么)
  • 目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为通知对象;由于 Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在 AOP 中表示为对谁干
  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。
  • AOP 代理(AOP Proxy):AOP 框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在 Spring 中,AOP 代理可以用 JDK 动态代理或 CGLIB 代理实现,而通过拦截器模型应用切面。在 AOP 中表示为怎么实现的一种典型方式

通知类型

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。
  • 后置通知(After returing advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知。
  • 最终通知(After (finally)advice):当某连接点退出的时候执行的通知(无论是正常返回还是异常退出)。
  • 环绕通知(Around advice):包围一个连接点的通知,如方法调用。这是一个强大的通知类型。环绕通知可以在方法调用的前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

​ 环绕通知是最常用的通知类型。和 AspectJ 一样,Spring 提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只需要一个方法的返回值来更新缓存,最后使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在 JointPoint 上调用用于环绕通知的 proceed() 方法,就不会有调用的问题。

下面是术语串联图

在这里插入图片描述

Spring AOP 和 AspectJ 是什么关系?

  • 首先 AspectJ 是什么?

​ AspectJ 是一个 java 实现的 AOP 框架,它能够对 java 代码进行 AOP 编译(一般在编译期进行),让 java 代码具有 AspectJ 的 AOP 功能(当然需要特殊的编译器)。

​ 可以这样说 AspectJ 是目前实现 AOP 框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ 与 java 程序完全兼容,几乎是无缝关联,因此对于有 java 编程基础的人来说,上手和使用都非常容易。

  • 其次,为什么需要清楚 Spring AOP 和 AspectJ 的关系?

​ @Aspect 以及增强的几个注解,为什么不是 Spring 包,而是来源于 aspectJ 呢?

  • Spring AOP 和 AspectJ 是什么关系?
  1. AspectJ 是更强的 AOP 框架,是实际意义的 AOP 标准;
  2. Spring AOP 使用纯 Java 实现,它不需要专门的编译过程,它一个重要的原则就是无侵入性(non-invasiveness)
  3. Spring 开发小组喜欢 @AspectJ 注解风格更胜于 Spring XML 配置;所以在 Spring 2.0 使用了和 AspectJ 5 一样的注解,并使用 AspectJ 来做切入点解析和匹配。但是,AOP 在运行时仍旧是纯的 Spring AOP,并不依赖于 AspectJ 的编译器或织入器(weaver);
  4. Spring 2.5 对 AspectJ 的支持:在一些环境下,增加了 AspectJ 的装载时编织支持,同时提供了一个新的 bean 切入点。
  • 更多关于 AspectJ?

​ 了解 AspectJ 应用到 java 代码的过程(这个过程称为织入),对于织入这个概念,可以简单理解为 aspect(切面)应用到目标函数(类)的过程。

​ 对于这个过程,一般分为动态织入静态织入

  1. 动态织入的方式是在运行时动态将要增强的代码织入到目标类中,这样往往是通过动态代理技术完成的,如 Java JDK 的动态代理(Proxy,底层通过反射实现)或者 CGLIB 的动态代理(底层通过继承实现),Spring AOP 采用的就是基于运行时增强的代理技术。
  2. AspectJ 采用的就是静态织入的方式。AspetJ 主要采用的是编译器织入,在这个期间使用 AspectJ 的 acj 编译器(类似 javac)把 aspect 类编译为 class 字节码后,在 java 目标类编译时织入,即先编译 aspect 类再编译目标类。

2.3. AOP 的配置方式

Spring AOP 支持对 XML 模式和基于 @AspectJ 注解的两种配置方式。

XML Schema 配置方式

​ Spring 提供了使用 “aop” 命名空间来定义一个切面,看下面例子:

  • 定义目标类
public class AopServiceImpl {
    public void doMethod1() {
        System.out.println("doMethod1");
    }
    
    public String doMethod2() {
        System.out.println("doMethod2");
        return "Hello World!";
    }
    
    public String doMethod3() throws Exception {
        System.out.println("doMethod3");
        throw new Exception("some exception");
    }
}
  • 定义切面类
public class LogAspect {
    public void doBefore() {
        System.out.println("前置通知");
    }

    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知:进入方法");
        Object result = pjp.proceed();
        System.out.println("环绕通知:退出方法");
        return result;
    }
    
    public void doAfterReturning(String returnValue) {
        System.out.println("后置通知:返回值是:" + returnValue);
    }
    
    public void doAfterThrowing(Exception e) {
        System.out.println("异常通知:异常信息是:" + e.getMessage());
    }

    public void doAfter() {
        System.out.println("最终通知");
    }
}
  • XML 配置 AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://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="aopService" class="io.xinghuo.spring.aop.AopServiceImpl"/>
    <bean id="logAspect" class="io.xinghuo.spring.aop.LogAspect"/>
    
    <aop:config>
        <aop:aspect ref="logAspect">
            <aop:pointcut id="pointCutMethod" expression="execution(* io.xinghuo.spring.aop.*.*(..))"/>
            <aop:around method="doAround" pointcut-ref="pointCutMethod"/>
            <aop:before method="doBefore" pointcut-ref="pointCutMethod"/>
            <aop:after-returning method="doAfterReturning" pointcut-ref="pointCutMethod" returning="returnValue"/>
            <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointCutMethod" throwing="e"/>
            <aop:after method="doAfter" pointcut-ref="pointCutMethod"/>
        </aop:aspect>
    </aop:config>
</beans>

AspectJ 注解方式

​ 基于 XML 的声明式 AspectJ 存在一些不足,需要在 Spring 配置文件配置大量的代码信息,为了解决这个问题,SPring 使用了 @AspectJ 框架为 AOP 的实现提供了一套注解。

注解名称解释
@Aspect用来定义一个切面
@pointcut用于定义切入点表达式。在使用时还需要定义一个包含名称和任意参数类型的方法签名来表示切入点名称,这个方法签名就是一个返回值 void,且方法体为空的普通方法。
@Before用于定义前置通知,相当于 BeforeAdvice。在使用时,通常需要指定一个 value 属性值,该属性值用于指定一个切入点表达式(可以是已有切入点,也可以直接定义切入点表达式)。
@AfterReturning用于定义后置通知,相当于 AfterReturningAdvice。在使用时可以指定 pointcut / value 和 returning 属性,其中 pointcut / value 这两个属性的作用一样,都用于指定切入点表达式。
@Around用于定义环绕通知,相当于 MethodInterceptor。在使用时需要指定一个 value 属性,该属性用于指定该通知被织入的切入点。
@After-Throwing用于定义异常通知来处理程序中未处理的异常,相当于 ThrowingAdvice。在使用时可指定 pointcut / value 和 throwing 属性,其中 pointcut / value 用于指定切入点表达式,而 throwing 属性值用于指定- 一个形参名来表示 Advice 方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。
@After用于定于最终 final 通知,不管是否异常,该通知都会执行。使用时需要指定一个 value 属性,该属性用于指定该通知被织入的切入点。
@DeclareParents用于定义引介通知,相当于 IntroductionInterceptor。

2.4. AOP 使用问题小结

切入点(pointcut)的申明规则?

​ Spring AOP 用户可能会经常使用 execution 切入点指示符。执行表达式的格式如下:

execution (modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • ret-type-pattern 返回类型模式,name-pattern 名字模式和 param-pattern 参数模式是必选的,其它部分都是可选的。返回类型模式决定了方法的返回类型必须依次匹配一个连接点。你会使用的最频繁的返回类型模式是 *,它代表了匹配任意的返回类型。
  • declaring-type-pattern,一个全限定的类型名称将只会匹配返回给定类型的方法。
  • name-pattern 名字模式匹配就是方法名。你可以使用 * 通配符作为所有或者部分命名模式。
  • param-pattern 参数模式稍微有点复杂:() 匹配了一个不接受任意参数的方法,而 (…) 匹配了一个接受任意数量参数的方法(零或更多)。模式 () 匹配了一个接受一个任意类型的参数的方法。模式 (,String) 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是 String 类型。

​ 此外 Spring 支持如下三个逻辑运算符来组合切入点表达式

&&:要求连接点同时匹配两个切入点表达式
||:要求连接点匹配任意个切入点表达式
!::要求连接点不匹配指定的切入点表达式

多种增强通知的顺序?

​ 如果有多个通知想要在同一连接点运行会发生什么?Spring AOP 遵循跟 AspectJ 一样的优先规则来确定通知执行的顺序。在 “进入” 连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。在 “退出” 连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中,优先级高的那个会第二个执行)。

​ 当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行,那么除非你指定,否则执行的顺序是未知的。你可以通过指定优先级来控制执行顺序。在标准的 Spring 方法中可以在切面类中实现 org.springframework.core.Ordered 接口或者用 Order 注解做到这一点。在两个切面中,Ordered.getValue() 方法返回值(或者注解值)较低的那个有更高的优先级。

​ 当定义在相同的切面里的两个通知都需要在一个相同的连接点中执行,执行的顺序是未知的。

Spring AOP 和 AspectJ 之间的关键区别?

​ AspectJ 可以做 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案,Spring AOP 则致力于解决企业级开发中最普遍的 AOP(方法织入)。

​ 下表总结了 Spring AOP 和 AspectJ 之间的关键区别:

Spring AOPAspectJ
在纯 Java 中实现使用 Java 编程语言的拓展实现
不需要单独的编译过程除非设置 LTW,否则需要 AspectJ 编译器(ajc)
只能使用运行时织入运行时织入不可用。支持编译时、编译后和加载时织入
功能强大-仅支持方法级编织更强大-可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等 …
只能在 由 Spring 容器管理的 bean 上实现可以在所有域对象上实现
仅支持方法执行切入点支持所有切入点
代理是由目标对象创建的,并且切面应用在这些代理上在执行应用程序之前(在运行时)前,各方面之间在代码中进行织入
比 AspectJ 慢多了更好的性能
易于学习和应用相对于 Spring AOP 来说更复杂

Spring AOP 还是完全用 AspectJ?

​ 以下 Spring 官方的回答:(总结来说就是 Spring AOP 更易用,AspectJ 更强大)。

  • Spring AOP 比完全使用 AspectJ 更加简单,因为它不需要引入 AspectJ 的编译器 / 织入器到你开发和构建过程中。如果你仅仅需要在 Spring bean 上通知执行操作,那么 Spring AOP 是合适的选择。
  • 如果你需要通知 domain 对象或其它没有在 Spring 容器中管理的任意对象,那么你需要使用 AspectJ。
  • 如果你想通知除了简单的方法执行之外的连接点(如:调用连接点、字段 get 或 set 的连接点等等),也需要使用 AspectJ。

​ 当使用 AspectJ 时,你可以选择使用 AspectJ 语言(也称为 “代码风格”) 或 @AspectJ 注解风格。如果切面在你的设计中扮演一个很大的角色,并且你能在 Eclipse 等 IDE 中使用 AspectJ Development Tools(AJDT),那么首选 AspectJ 语言;因为该语言专门被设计用来编写切面,所以会更清晰、更简单。如果你没有使用 Eclipse 等 IDE,或者在你应用中只有很少的切面并没有作为一个主要的角色,你或许应该考虑使用 @AspectJ 风格 并在你的 IDE 中附加一个普通的 Java 编译器,并且在你构建脚本中增加切面织入(链接)的段落。

三、Spring 进阶 - IOC 体系结构设计

3.1 站在设计者的角度考虑设计 IOC 容器

如果让你设计一个 IoC 容器,你会怎么设计?我们初步通过这个问题,来帮助我们更好的理解 IoC 的设计。

​ 在设计时,首先需要考虑的是 IOC 容器的功能(输入和输出),承接前面的文章,我们初步画出 IOC 容器的整体功能。

在这里插入图片描述

​ 在此基础上,我们初步的去思考,如果作为一个 IOC 容器的设计者,主体上应该包含哪几个部分:

  • 加载 Bean 的配置(比如 xml 配置)
    • 比如不同类型资源的加载,解析生成统一 Bean 的定义
  • 根据 Bean 的定义加载生成 Bean 的实例,并放置在 Bean 容器中
    • 比如 Bean 的依赖注入,Bean 的嵌套,Bean 的存放(缓存)等
  • 除了基础 Bean 外,还有常规针对企业级业务的特殊 Bean
    • 比如国际化 Message,事件 Event 等生成特殊的类结构去支撑
  • 对容器中的 Bean 提供统一的管理和调用
    • 比如用工厂模式管理,提供方法根据名称/类的类型等从容器中获取 Bean

3.2 Spring IoC 的体系结构设计

下面看 Spring 设计者是如何设计 IoC 并解决这些问题的。

BeanFactory 和 BeanRegistry:IOC 容器功能规范和 Bean 的注册

Spring Bean 的创建是典型的工厂模式,这一系列的 Bean 工厂,也即 IOC 容器为开发者管理对象间的依赖关系提供了很多便利和基础服务,在 Spring 中有许多的 IOC 容器的实现供用户选择和使用,这是 IOC 容器的基础;在顶层的结构设计主要围绕着 BeanFactory 和 xxxRegistry 进行:

  • BeanFactory:工厂模式定义了 IOC 容器的基本功能规范
  • BeanRegistry:向 IOC 容器手工注册 BeanDefinition 对象的方法

​ 关系如下:

在这里插入图片描述

BeanFactory 定义了 IOC 容器基本功能规范?

BeanFactory 作为最顶层的一个接口类,它定义了 IOC 容器的基本功能规范,BeanFactory 有三个子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory。我们看下 BeanFactory 接口:

public interface BeanFactory {
    // 用于取消引用实例并将其与 FactoryBean 创建的 bean 区分开来。例如,如果命名的 bean 是 FactoryBean,则获取将返回 Factory,而不是 Factory 返回的实例。
    String FACTORY_BEAN_PREFIX = "&";
	
    // 根据 bean 的名字和 Class 类型等来得到 bean 实例
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
	
    // 返回指定 bean 的 Provider
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    // 检查工厂中是否包含给定 name 的 bean,或者外部注册的 bean
    boolean containsBean(String name);
	
    // 检查所给 name 的类型与 type 是否为单例/原型
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
	
    // 判断所给 name 的类型与 type 是否匹配
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
	
    // 获取给定 name 的 bean 的类型
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    @Nullable
    Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
    
    // 返回给定 name 的 bean 的别名
    String[] getAliases(String var1);
}

BeanFactory 为何要定义这么多层次的接口?定义了哪些接口?

​ 主要是为了区分 Spring 内部在操作过程中对象的传递和转换过程中,对对象的数据访问所做的限制

​ 有哪些接口呢?

  • ListableBeanFactory:该接口定义了访问容量中 Bean 基本信息的若干方法,如查看 Bean 的个数、获取某一类型 Bean 的配置名、查看容器中是否包含某一 Bean 等方法;

  • HierarchicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器;

    通过 HierarchicalBeanFactory 接口,Spring 的 IoC 容器可以建议父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。Spring 使用父子容器实现了很多功能,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样,展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。

  • ConfigurableBeanFactory:是一个重要的接口,增强了 IoC 容器的可定制性,它定义了设置类装载器、属性编辑器、容量初始化后置处理器等方法;

  • ConfigurableListableBeanFactory:ListableBeanFactory 和 ConfigurableBeanFactory 的融合;

  • AutowireCapableBeanFactory:定义了将容量中的 Bean 按某种规则(如按名字匹配、按类型匹配等)进行自动装配的方法;

如何将 Bean 注册到 BeanFactory 中?BeanRegistry

​ Spring 配置文件中每一个 <bean> 节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器中手工注册 BeanDefinition 对象的方法。

BeanDefinition:各种 Bean 对象及其相互的关系

Bean 对象存在依赖嵌套等关系,所以设计者设计了 BeanDefinition,它用来对 Bean 对象及关系定义;我们在理解时只需要抓住如下三个要点:

  • BeanDefinition 定义了各种 Bean 对象及其相互的关系
  • BeanDefinitionReader 这是 BeanDefinition 的解析器
  • BeanDefinitionHolder 这是 BeanDefinition 的包装类,用来存储 BeanDefinition、name 以及 aliases 等
  • BeanDefinition

​ Spring IoC 容器管理了我们定义的各种 Bean 对象及其相互的关系,Bean 对象在 Spring 实现中是以 BeanDefinition 来描述的,其继承体系如下:

在这里插入图片描述

  • BeanDefinitionReader

​ Bean 的解析过程非常复杂,功能被分的很细,因为这里需要被拓展的地方很多,必须保证有足够的灵活性,以应对可能的变化。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:

在这里插入图片描述

  • BeanDefinitionHolder

​ BeanDefinitionHolder 这是 BeanDefinition 的包装类,用来存储 BeanDefinition,name 以及 aliases 等。

在这里插入图片描述

3.3 ApplicationContext:IOC 接口设计和实现

IoC 容器的接口类是 ApplicationContext,很显然它必然继承 BeanFactory 对 Bean 规范(最基本的 IoC 容器的实现)进行定义。而 ApplicationContext 表示的是应用的上下文,除了对 Bean 的管理外,还至少应该包含:

  • 访问资源:对不同方式的 Bean 配置(即资源)进行加载。(实现 ResourcePatternResolver 接口)
  • 国际化:支持信息源,可以实现国际化。(实现 MessageSource 接口)
  • 应用事件:支持应用事件。(实现 ApplicationEventPublisher 接口)

ApplicationContext 接口的设计

在这里插入图片描述

  • HierarchicalBeanFactory 和 ListableBeanFactory:ApplicationContext 继承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基础上,还通过多个其它的接口拓展了 BeanFactory 的功能;
  • ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收的到容器事件,并对事件进行响应处理。在 ApplicationContext 抽象实现类 AbstractApplicationContext 中,我们可以发现存在一个 ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。
  • MessageSource:为应用提供 i18n 国际化消息访问的功能;
  • ResourcePatternResolver:所有 ApplicationContext 实现类都实现了类似于 PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
  • LiftCycle:该接口是 Spring2.0 加入的,该接口提供了 start() 和 stop() 两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现,ApplicationContext 会将 start/stop 的消息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。

ApplicationContext 接口的实现

​ 在考虑 ApplicationContext 接口的实现时,关键的点在于,不同 Bean 的配置方式(比如 xml,groovy,annotation 等)有着不同的资源加载方式,这便衍生了众多 ApplicationContext 的实现类。

在这里插入图片描述

第一,从类结构设计上看,围绕着是否需要 Refresh 容器衍生出两个抽象类:

  • GenericApplicationContext:是初始化的时候就创建容器,往后的每次 refresh 都不会更改
  • AbstractRefreshableApplicationContext:AbstractRefreshableApplicationContext 及子类的每次 refresh 都是先清除已有(如果不存在就创建)的容器,然后再重新创建;AbstractRefreshableApplicationContext 及子类无法做到 GeniricApplicationContext 混合搭配从不同源头获取 bean 的定义信息

第二,从加载的源来看(比如 xml,groovy,annotation 等),衍生出许多类型的 ApplicationContext,典型比如:

  • FileSystemXmlApplicationContext:从文件系统下的一个或多个 xml 配置文件中加载上下文定义,也就是说系统盘符中加载 xml 配置文件。
  • ClassPathXmlApplicationContext:从类路径下的一个或多个 xml 配置文件中加载上下文定义,适用于 xml 配置的方式。
  • AnnotationConfigApplicationContext:从一个或多个基于 java 的配置类中加载上下文定义,适用于 java 注解的方式。
  • ConfigurableApplicationContext:拓展于 ApplicationContext,它新增加了两个主要的方法:refresh() 和 close(),让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用 refresh() 即可启动应用上下文,在已启动的状态下,调用 refresh() 则清除缓存并重新装载配置信息,而调用 close() 则可关闭应用上下文。这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要关心这些方法。

第三,更进一步理解:

设计者在设计时 AnnotationConfigApplicationContext 为什么是继承 GenericApplicationContext?因为基于注解的配置,是不太会被运行时修改的,这意味着不需要进行动态 Bean 配置和刷新容器,所以只需要 GenericApplicationContext。

而基于 XML 这种配置文件,这种文件是容易修改的,需要动态性刷新 Bean 的支持,所以 XML 相关的配置必然继承 AbstractRefreshableApplicationContext;且存在多种 xml 的加载方式(位置不同设计),所以必然会设计出 AbstractXmlApplicationContext,其中包含对 XML 配置解析成 BeanDefiniation 的过程。

我们将之前的设计要点和设计结构结合起来看:

在这里插入图片描述

四、Spring 进阶 - IOC 初始化流程

4.1 引入

上一节,我们了解了 IOC 的设计要点和设计结构,下面我们可以看一下源码实现了:Spring 如何实现将资源配置(以 xml 配置为例)通过加载,解析,生成 BeanDefinitation 并注册到 IoC 容器中。

在这里插入图片描述

4.2 如何将 Bean 从 XML 配置中解析后放到 IoC 容器中的?

本节目标就是分析 Spring 如何实现将资源配置(以 xml 配置为例)通过加载,解析,生成 BeanDefinitation 并注册到 IoC 容器中。

初始化的入口

​ 对于 xml 配置的 Spring 应用,在 main() 方法中实例化 ClassPathXmlApplicationContext 即可创建一个 IoC 容器。我们从这个构造方法入手,开始探究 IoC 容器的初始化过程。

ClassPathXmlApplicationContext context =
    new ClassPathXmlApplicationContext("daos.xml");
// ClassPathXmlApplicationContext.class
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[]{configLocation}, true, (ApplicationContext)null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable 		 	         	ApplicationContext parent) throws BeansException {
    // 设置 Bean 资源加载器
    super(parent);
    // 设置配置路径
    this.setConfigLocations(configLocations);
    // 初始化容器
    if (refresh) {
        this.refresh();
    }

}

设置资源解析器和环境

​ 调用父类容器 AbstractApplicationContext 的构造方法(super(parent) 方法)为容器设置好 Bean 资源加载器。

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    // 默认构造函数初始化容器id,name,状态 以及资源解析器
    this();
    // 将父容器的 Environment 合并到当前容器
    this.setParent(parent);
}

​ 通过 AbstractApplicationContext 默认构造函数初始化容器 id,name,状态以及资源解析器。

public AbstractApplicationContext() {
    this.logger = LogFactory.getLog(this.getClass());
    this.id = ObjectUtils.identityToString(this);
    this.displayName = ObjectUtils.identityToString(this);
    this.beanFactoryPostProcessors = new ArrayList();
    this.active = new AtomicBoolean();
    this.closed = new AtomicBoolean();
    this.startupShutdownMonitor = new Object();
    this.applicationStartup = ApplicationStartup.DEFAULT;
    this.applicationListeners = new LinkedHashSet();
    this.resourcePatternResolver = this.getResourcePatternResolver();
}
// Spring 资源加载器
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

​ 通过 AbstractApplicationContext 的 setParent(parent) 方法将父容器的 Environment 合并到当前容器。

public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) {
        Environment parentEnvironment = parent.getEnvironment();
        if (parentEnvironment instanceof ConfigurableEnvironment) {
            this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
        }
    }
    
}

设置配置路径

​ 在设置容器的资源加载器之后,接下来 FileSystemXmlApplicationContext 执行 setConfigLocations 方法通过调用其父类 AbstractRefreshableConfigApplicationContext 的方法进行对 Bean 定义文件的定位。

public void setConfigLocations(@Nullable String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];

        for(int i = 0; i < locations.length; ++i) {
            // 解析配置路径
            this.configLocations[i] = this.resolvePath(locations[i]).trim();
        }
    } else {
        this.configLocations = null;
    }

}

protected String resolvePath(String path) {
    // 从上一步 Environment 中解析
    return this.getEnvironment().resolveRequiredPlaceholders(path);
}

初始化的主体流程

​ Spring IoC 容器对 Bean 定义资源的载入是从 refresh() 函数开始的,refresh() 是一个模板方法,refresh() 方法的作用是:在创建 IoC 容器前,如果已有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IoC 容器。refresh 的作用类似于对 IoC 容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
            contextRefresh.end();
        }
    }
}

​ 这里在设计上是一个非常典型的资源类加载处理型的思路:

  • 模板方法设计模式,模板方法中使用典型的钩子方法
  • 将具体的初始化加载方式插入到钩子方法之间
  • 将初始化的阶段封装,用来记录当前初始化到什么阶段;常见的设计是 xxxPhase/xxxStage;
  • 资源加载初始化有失败等处理,必然是 try/catch/finally …

在这里插入图片描述

初始化 BeanFactory 之 obtainFreshBeanFactory

​ AbstractApplicationContext 的 obtainFreshBeanFactory() 方法调用子类容器的 refreshBeanFactory() 方法,启动容器载入 Bean 定义资源文件的过程,代码如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
 	// 这里使用了委派的设计模式,父类定义了抽象的 refreshBeanFactory() 方法,具体实现调用子类容器的 refreshBeanFactory() 方法
    this.refreshBeanFactory();
    return this.getBeanFactory();
}

​ AbstractApplicationContext 类中只抽象定义了 refreshBeanFactory() 方法,容器真正调用的是其子类 AbstractRefreshableApplicationContext 实现的 refreshBeanFactory() 方法;在创建 IoC 容器前,如果已有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IoC 容器。方法源码如下:

protected final void refreshBeanFactory() throws BeansException {
    // 如果已有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IoC 容器
    if (this.hasBeanFactory()) {
        this.destroyBeans();
        this.closeBeanFactory();
    }
	
    try {
    	// 创建 DefaultListableBeanFactory,并调用 loadBeanDefinitions(beanFactory) 装载 bean 定义
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();
        beanFactory.setSerializationId(this.getId());
        this.customizeBeanFactory(beanFactory); // 对 IoC 容器进行定制化,如设置启动参数,开启注解的自动装配等
        this.loadBeanDefinitions(beanFactory); // 调用载入 Bean 定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的 loadBeanDefinitions 方法,具体的实现调用子类容器。
        this.beanFactory = beanFactory;
    } catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), ex);
    }
}

初始化 BeanFactory 之 loadBeanDefinitions

​ AbstractRefreshableApplicationContext 中只定义了抽象的 loadBeanDefinitions 方法,容器真正调用的是其子类 AbstractXmlApplicationContext 对该方法的实现,AbstractXmlApplicationContext 的主要源码如下:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 创建 XmlBeanDefinitionReader,即创建 Bean 读取器,并通过回调设置到容器中去,容器使用该读取器读取 Bean 定义资源
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    
    // 配置上下文的环境,资源加载器、解析器
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 为 Bean 读取器设置 SAX xml 解析器
    
    // 允许子类自行初始化(比如校验机制),并提供真正的加载方法
    this.initBeanDefinitionReader(beanDefinitionReader); // 当 Bean 读取器读取 Bean 定义的 Xml 资源文件时,启用 Xml 的校验机制
    this.loadBeanDefinitions(beanDefinitionReader);
}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    // 加载 XML 配置方式里的 Bean 定义的资源
    Resource[] configResources = this.getConfigResources();
    if (configResources != null) {
        reader.loadBeanDefinitions(configResources);
    }
	
    // 加载构造函数里配置的 Bean 配置文件,即 {“daos.xml"}
    String[] configLocations = this.getConfigLocations();
    if (configLocations != null) {
        reader.loadBeanDefinitions(configLocations);
    }

}

​ Xml Bean 读取器(XmlBeanDefinitionReader)调用其父类 AbstractBeanDefinitionReader 的 reader.loadBeanDefinitions() 方法读取 Bean 定义资源。

​ 由于我们使用 ClassPathXmlApplicationContext 作为例子分析,因此 getConfigResources 返回值为 null,因此程序执行 reader.loadBeanDefinitions(configLocations) 分支。

AbstractBeanDefinitionReader 读取 Bean 定义资源

​ AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源码如下:

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(location, (Set)null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    ResourceLoader resourceLoader = this.getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
    } else if (resourceLoader instanceof ResourcePatternResolver) { // 模式匹配类型的解析器,这种方式是加载多个满足匹配条件的资源
        try {
            // 获取到要加载的资源
            Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
            int count = this.loadBeanDefinitions(resources); // 委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能
            if (actualResources != null) {
                Collections.addAll(actualResources, resources);
            }

            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
            }

            return count;
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", ex);
        }
    } else {
        // 只能通过绝对路径 URL 加载单个资源
        Resource resource = resourceLoader.getResource(location);
        int count = this.loadBeanDefinitions((Resource)resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }

        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
        }

        return count;
    }
}

​ 从对 AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源码分析可以看出该方法做了以下两件事:

  • 首先,调用资源加载器的获取资源方法 resourceLoader.getResource(location),获取到要加载的资源。
  • 其次,真正执行加载的是其子类 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。

XmlBeanDefinitionReader 加载 Bean 定义资源

​ 继续看子类 XmlBeanDefinitionReader 的 loadBeanDefinitions(Resource …) 方法看到代表 bean 文件的资源定义以后的载入过程。

/**
 * 本质上是加载 Xml 配置的 Bean
 */
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        Document doc = this.doLoadDocument(inputSource, resource); // 将 Bean 定义资源转换为 Document 对象
        int count = this.registerBeanDefinitions(doc, resource);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Loaded " + count + " bean definitions from " + resource);
        }

        return count;
    } catch (BeanDefinitionStoreException ex) {
        throw ex;
    } catch (SAXParseException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
    } catch (SAXException ex) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", ex);
    } catch (ParserConfigurationException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, ex);
    } catch (IOException ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, ex);
    } catch (Throwable ex) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, ex);
    }
}
通过源码分析,载入 Bean 定义资源文件的最后一步是将 Bean 定义资源转换为 Document 对象,该过程由 documentLoader 实现。

DocumentLoader 将 Bean 定义资源转换为 Document 对象

​ DocumentLoader 将 Bean 定义资源转换成 Document 对象的源码如下:

/**
 * 使用标准的 JAXP 将载入的 Bean 定义资源转换成 Document 对象
 */
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
    // 创建文件解析器工厂
    DocumentBuilderFactory factory = this.createDocumentBuilderFactory(validationMode, namespaceAware);
    if (logger.isTraceEnabled()) {
        logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
    }
	// 创建文档解析器
    DocumentBuilder builder = this.createDocumentBuilder(factory, entityResolver, errorHandler);
    return builder.parse(inputSource); // 解析
}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(namespaceAware);
    // 设置解析 XML 的校验
    if (validationMode != 0) {
        factory.setValidating(true);
        if (validationMode == 3) {
            factory.setNamespaceAware(true);

            try {
                factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
            } catch (IllegalArgumentException ex) {
                ParserConfigurationException pcex = new ParserConfigurationException("Unable to validate using XSD: Your JAXP provider [" + factory + "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                pcex.initCause(ex);
                throw pcex;
            }
        }
    }

    return factory;
}

​ 该解析过程调用 JavaEE 标准的 JAXP 标准进行处理。

​ 至此 Spring IoC 容器根据定位的 Bean 定义资源文件,将其加载读入并转换成 Document 对象过程完成。

​ 接下来我们要继续分析 Spring IoC 容器将载入的 Bean 定义资源文件转换为 Document 对象之后,是如何将其解析为 Spring IoC 管理的 Bean 对象并将其注册到容器中的。

XmlBeanDefinitionReader 解析载入的 Bean 定义资源文件

​ XmlBeanDefinitionReader 类中的 doLoadBeanDefinitions 方法是从特定 XML 文件中实际载入 Bean 定义资源的方法,该方法在载入 Bean 定义资源之后将其转换成 Document 对象,接下来调用 registerBeanDefinitions 启动 Spring IoC 容器对 Bean 定义的解析过程,regisiterBeanDefinitions 方法源码如下:

/**
 * 按照 Spring 的 Bean 语义要求将 Bean 定义资源解析并转换为容器内部数据结构
 */
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    // 解析过程入口,这里使用了委派模式,具体的解析实现过程由实现类 DefaultBeanDefinitionDocumentReader 完成
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    return this.getRegistry().getBeanDefinitionCount() - countBefore; // 返回此次解析了多少个对象
}

/**
 * 创建 BeanDefinitionDocumentReader 对象,解析 Document 对象
 */
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return (BeanDefinitionDocumentReader)BeanUtils.instantiateClass(this.documentReaderClass);
}

public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, this.getNamespaceHandlerResolver());
}

​ Bean 定义资源的载入解析分为以下两个过程:

  • 首先,通过调用 XML 解析器将 Bean 定义资源文件转换得到 Document 对象,但是这些 Document 对象并没有按照 Spring 的 Bean 规则进行解析。这一步是载入的过程。
  • 其次,在完成通用的 XML 解析之后,按照 Spring 的 Bean 规则对 Document 对象进行解析。

​ 按照 Spring 的 Bean 规则对 Document 对象解析的过程是在接口 BeanDefinitionDocumentReader 的实现类 DefaultBeanDefinitionDocumentReader 中实现的。

DefaultBeanDefinitionDocumentReader 对 Bean 定义的 Document 对象解析

​ BeanDefinitionDocumentReader 接口通过 regisiterBeanDefinitions 方法调用其实现类 DefaultBeanDefinitionDocumentReader 对 Document 对象进行解析,解析的代码如下:

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    this.doRegisterBeanDefinitions(doc.getDocumentElement());
}
/**
 * 注册 <beans /> 配置的 Beans
 */
protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute("profile");
        if (StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                }

                return;
            }
        }
    }

    this.preProcessXml(root);
    this.parseBeanDefinitions(root, this.delegate); // 从 Document 的根元素开始进行 Bean 定义的 Document 对象
    this.postProcessXml(root);
    this.delegate = parent;
}

BeanDefinitionParserDelegate 解析 Bean 定义资源文件生成 BeanDefinition

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();

        for(int i = 0; i < nl.getLength(); ++i) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element)node;
                if (delegate.isDefaultNamespace(ele)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }

}

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    // 如果元素节点是<Import> 导入元素,进行导入解析
    if (delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if (delegate.nodeNameEquals(ele, "alias")) { // 如果元素节点是 <Alias> 别名元素,进行别名解析
        this.processAliasRegistration(ele);
    } else if (delegate.nodeNameEquals(ele, "bean")) { // 如果元素节点是 <Bean> 元素,按照 Spring 的 Bean 规则解析元素
        this.processBeanDefinition(ele, delegate);
    } else if (delegate.nodeNameEquals(ele, "beans")) { // 如果元素节点 <Beans> 元素,即它是嵌套类型的
        // 递归解析
        this.doRegisterBeanDefinitions(ele);
    }

}

​ 解析 Bean 生成 BeanDefinitionHolder 的方法

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

        try {
			// 注册最终的装饰实例
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
        } catch (BeanDefinitionStoreException ex) {
            this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex);
        }

        this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }

}

解析过后的 BeanDefinition 在 IoC 容器中的注册

​ Document 对象的解析后得到封装 BeanDefinition 的 BeanDefinitionHold 对象,然后调用 BeanDefinitionReaderUtils 的 registerBeanDefinition 方法向 IoC 容器注册解析的 Bean,BeanDefinitionReaderUtils 的源码如下:

/**
 * 通过 BeanDefinitionRegistry 将 BeanDefinitionHolder 注册到 BeanFactory
 */
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
    String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for(String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }

}

​ 当调用 BeanDefinitionReaderUtils 向 IoC 容器注册解析的 BeanDefinition 时,真正完成注册功能的是 DefaultListableBeanFactory。

DefaultListableBeanFactory 向 IoC 容器注册解析后的 BeanDefinition

​ IOC 容器本质上就是一个 beanDefintionMap,注册即将 BeanDefinition put 到 map 中。

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);


public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
    Assert.hasText(beanName, "Bean name must not be empty");
    Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    if (beanDefinition instanceof AbstractBeanDefinition) {
        try {
            ((AbstractBeanDefinition)beanDefinition).validate();
        } catch (BeanDefinitionValidationException ex) {
            throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", ex);
        }
    }

    BeanDefinition existingDefinition = (BeanDefinition)this.beanDefinitionMap.get(beanName);
   	// 如果已经注册
    if (existingDefinition != null) {
        // 检查是否可以覆盖
        if (!this.isAllowBeanDefinitionOverriding()) {
            throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
        }

        if (existingDefinition.getRole() < beanDefinition.getRole()) {
            if (this.logger.isInfoEnabled()) {
                this.logger.info("Overriding user-defined bean definition for bean '" + beanName + "' with a framework-generated bean definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
            }
        } else if (!beanDefinition.equals(existingDefinition)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Overriding bean definition for bean '" + beanName + "' with a different definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
            }
        } else if (this.logger.isTraceEnabled()) {
            this.logger.trace("Overriding bean definition for bean '" + beanName + "' with an equivalent definition: replacing [" + existingDefinition + "] with [" + beanDefinition + "]");
        }
		// 覆盖
        this.beanDefinitionMap.put(beanName, beanDefinition);
    } else {
        if (this.hasBeanCreationStarted()) {
            synchronized(this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                this.removeManualSingletonName(beanName);
            }
        } else {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            this.beanDefinitionNames.add(beanName);
            this.removeManualSingletonName(beanName);
        }
		// 重置所有已经注册过的 BeanDefinition 的缓存
        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition == null && !this.containsSingleton(beanName)) {
        if (this.isConfigurationFrozen()) {
            this.clearByTypeCache();
        }
    } else {
        this.resetBeanDefinition(beanName);
    }

}

​ 至此,Bean 定义资源文件中配置的 Bean 被解析过后,已经注册到 IoC 容器中,被容器管理起来,真正完成了 IoC 容器初始化所作的全部工作。现在 IoC 容器中已经建立了整个 Bean 的配置信息,这些 BeanDefinition 信息已经可以使用,并且可以被检索,IoC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护。这些注册的 Bean 定义信息是 IoC 容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。

4.3 总结

​ 现在通过上面的代码,对 IOC 容器初始化的基本步骤进行总结。

在这里插入图片描述

  • 初始化的入口在容器实现中的 refresh() 调用来实现
  • 对 bean 定义载入 IoC 容器使用的方法是 loadBeanDefinition,其中大致的过程如下:
    • 通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader 是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径、文件系统、URL 等方式来定位资源位置。如果是 XmlBeanFactory 作为 IoC 容器,那么需要为它指定 bean 定义的资源,也就是说 bean 定义文件时通过抽象成 Resource 来被 IoC 容器处理的。
    • 通过 BeanDefinitionReader 来完成定义信息的解析和 Bean 信息的注册,往往使用的是 XmlBeanDefinitionReader 来解析 bean 的 xml 定义文件 - 实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示 - 这个名字可以让我们想到 loadBeanDefinition,RegisterBeanDefinition 这些相关方法 - 它们都是为处理 BeanDefinition 服务的。
    • 容器解析得到 BeanDefinition 后,需要把它在 IoC 容器中注册,这由 IoC 实现 BeanDefinitionRegister 接口来实现。注册过程就是在 IoC 容器内部维护的一个 HashMap 来保存得到的 BeanDefinition 的过程,这个 HashMap 是 IoC 容器持有 bean 信息的场所,以后对 bean 的操作都是围绕这个 HashMap 来实现的。
  • 然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC 容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IoC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其它对象钩链在一起。基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring 本身提供了对声明式载入 web 应用程序用法的应用程序上下文,并将其存储在 ServletContext 中的框架实现。

五、Spring 进阶 - Bean 实例化(声明周期、循环依赖等)

5.1 引入

我们了解过了 IOC 设计要点和设计结构以及 Spring 如何实现将资源配置(以 XML 配置为例)通过加载,解析,生成 BeanDefinition 并注册到 IoC 容器中的;容器中存放的 Bean 的定义即 BeanDefinition 放到 beanDefinitionMap 中,本质上是一个 ConcurrentHashMap<String, object>;并且 BeanDefinition 接口中包含了这个类的 Class 信息以及是否是单例等。那么如何从 BeanDefinition 中实例化 Bean 对象呢?

​ 本文主要研究如何从 IOC 容器已有的 BeanDefinition 信息,实例化出 Bean 对象;这里还会包含三块重点内容:

  • BeanFactory 中 getBean 的主体思路
  • Spring 如何解决循环依赖问题
  • Spring 中 Bean 的生命周期

在这里插入图片描述

5.2 BeanFactory 中 getBean 的主体思路

第四章第二节中我们知道 BeanFactory 定义了 Bean 容器的规范,其中包含根据 bean 的名字,Class 类型和参数等来得到 bean 实例。

// 根据 bean 的名字和 Class 类型等来得到 bean 实例
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

初步的思考

​ 上文中我们已经分析了 IoC 初始化的流程,最终将 Bean 的定义即 BeanDefinition 放到 beanDefinitionMap 中,本质上是一个 ConcurrentHashMap<String, Object>;并且 BeanDefinition 接口中包含了这个类的 Class 信息以及是否是单例等;

在这里插入图片描述

​ 这样我们初步有了实现 Object getBean(String name) 这个方法是思路:

  • 从 beanDefinitionMap 通过 beanName 获得 BeanDefinition
  • 从 BeanDefinition 中获得 beanClassName
  • 通过反射初始化 beanClassName 的实例 instance
    • 构造函数从 BeanDefinition 的 getConstructorArgumentValues() 方法获取
    • 属性值从 BeanDefinition 的 getPropertyValues() 方法获取
  • 返回 beanName 的实例 instance

​ 由于 BeanDefinition 还有单例的信息,如果是无参构造函数的实例还可以放在一个缓存中,这样下次获取这个单例的实例时只需要从缓存中获取,如果获取不到再通过上述步骤获取。

Spring 中 getBean 的主体思路

​ BeanFactory 实现 getBean 方法在 AbstractBeanFactory 中,这个方法重载都是调用 doGetBean 方法进行实现的:

public Object getBean(String name) throws BeansException {
    return this.doGetBean(name, (Class)null, (Object[])null, false);
}

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    return (T)this.doGetBean(name, requiredType, (Object[])null, false);
}

public Object getBean(String name, Object... args) throws BeansException {
    return this.doGetBean(name, (Class)null, args, false);
}

public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args) throws BeansException {
    return (T)this.doGetBean(name, requiredType, args, false);
}

​ 下面来看一下 doGetBean 方法(这个方法较长,我们主要看它的整体思路和设计要点):

/**
 * 参数 typeCheckOnly:bean 实例是否包含一个类型检查
 */
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    // 解析 bean 的真正 name,如果 bean 是工厂类,name 前缀会加 %,需要去掉
    String beanName = this.transformedBeanName(name);
    Object sharedInstance = this.getSingleton(beanName);
    Object beanInstance;
    if (sharedInstance != null && args == null) {
        if (this.logger.isTraceEnabled()) {
            if (this.isSingletonCurrentlyInCreation(beanName)) {
                this.logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
            } else {
                this.logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
		// 无参单例从缓存中获取
        beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, (RootBeanDefinition)null);
    } else {
        // 如果 bean 实例还在创建中,则直接抛出异常
        if (this.isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
		// 如果 bean definition 存在于父的 bean 工厂中,委派给父 Bean 工厂获取
        BeanFactory parentBeanFactory = this.getParentBeanFactory();
        if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
            String nameToLookup = this.originalBeanName(name);
            if (parentBeanFactory instanceof AbstractBeanFactory) {
                return (T)((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
            }

            if (args != null) {
                return (T)parentBeanFactory.getBean(nameToLookup, args);
            }

            if (requiredType != null) {
                return (T)parentBeanFactory.getBean(nameToLookup, requiredType);
            }

            return (T)parentBeanFactory.getBean(nameToLookup);
        }

        if (!typeCheckOnly) {
            // 将当前 bean 实例放入 alreadyCreated 集合中,标识这个 bean 准备创建
            this.markBeanAsCreated(beanName);
        }

        StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate").tag("beanName", name);

        try {
            if (requiredType != null) {
                beanCreation.tag("beanType", requiredType::toString);
            }

            RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);
            this.checkMergedBeanDefinition(mbd, beanName, args);
            // 确保它的依赖已被初始化
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for(String dep : dependsOn) {
                    if (this.isDependent(beanName, dep)) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }

                    this.registerDependentBean(dep, beanName);

                    try {
                        // 初始化它依赖的 Bean
                        this.getBean(dep);
                    } catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                    }
                }
            }
			// 创建 Bean 实例:单例
            if (mbd.isSingleton()) {
                sharedInstance = this.getSingleton(beanName, () -> {
                    try {
                        // 真正创建 bean 的方法
                        return this.createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        this.destroySingleton(beanName);
                        throw ex;
                    }
                });
                beanInstance = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            } else if (mbd.isPrototype()) { // 创建 Bean 实例:原型
                Object prototypeInstance = null;

                try {
                    this.beforePrototypeCreation(beanName);
                    prototypeInstance = this.createBean(beanName, mbd, args);
                } finally {
                    this.afterPrototypeCreation(beanName);
                }

                beanInstance = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            } else { // 创建 Bean 实例:根据 bean 的 scope 创建
                String scopeName = mbd.getScope();
                if (!StringUtils.hasLength(scopeName)) {
                    throw new IllegalStateException("No scope name defined for bean ��" + beanName + "'");
                }

                Scope scope = (Scope)this.scopes.get(scopeName);
                if (scope == null) {
                    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
                }

                try {
                    Object scopedInstance = scope.get(beanName, () -> {
                        this.beforePrototypeCreation(beanName);

                        Object var4;
                        try {
                            var4 = this.createBean(beanName, mbd, args);
                        } finally {
                            this.afterPrototypeCreation(beanName);
                        }

                        return var4;
                    });
                    beanInstance = this.getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
                } catch (IllegalStateException ex) {
                    throw new ScopeNotActiveException(beanName, scopeName, ex);
                }
            }
        } catch (BeansException ex) {
            beanCreation.tag("exception", ex.getClass().toString());
            beanCreation.tag("message", String.valueOf(ex.getMessage()));
            this.cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        } finally {
            beanCreation.end();
        }
    }

    return (T)this.adaptBeanInstance(name, beanInstance, requiredType);
}

​ 这段代码很长,主要看部分方法即可。

  • 解析 bean 的真正 name,如果 bean 是工厂类,name 前缀会加 %,需要去掉
  • 无参单例先从缓存中尝试获取
  • 如果 bean 实例还在创建,则直接抛出异常
  • 如果 bena definition 存在于父 bean 工厂中,委派给父 Bean 工厂获取
  • 标记这个 beanName 的实例正在创建
  • 确保它的依赖也被初始化
  • 真正创建
    • 单例时
    • 原型时
    • 根据 bean 的 scope 创建

5.3 重点:Spring 如何解决循环依赖问题

首先我们需要说明:Spring 只是解决了单例模式下属性依赖的循环问题;Spring 为了解决单例的循环依赖问题,使用了三级缓存。

Spring 单例模式下的属性依赖

​ 先来看一下这三级缓存:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
  • 第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象;
  • 第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象
  • 第三次对象(singletonFactories):单例工厂的缓存;

​ 如下是获取单例中:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Spring 首先从 singletonObjects(一级缓存)中尝试获取
    Object singletonObject = this.singletonObjects.get(beanName);
    // 若是获取不到,而且对象在建立中,则尝试从 earlySingletonObjects(二级缓存)中获取
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized(this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 若是仍是获取不到,而且允许从 singletonFactories 经过 getObject 获取,则经过 singletonFactories.getObejct()(三级缓存)获取
                            singletonObject = singletonFactory.getObject();
                            // 若是获取到了则从 singletionObject 放入到 earlySingletonObjects,也就是将三级缓存提高到二级缓存中
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}

​ 补充一些方法和参数:

  • isSingletonCurrentlyInCreation():判断当前单例 bean 是否正在建立中,也就是未初始化完成;
  • allowEarlyReference:是否容许从 singletonFactories 中经过 getObject 拿到对象;

​ 分析 getSingleton() 的整个过程,Spring 首先从一级缓存 singletonObjects 中获取。若获取不到,而且对象正在建立中,就再从二级缓存 earlySingletonObjects 中获取。若仍获取不到,且容许 singletonFactories 经过 getObject() 获取,就从三级缓存 singletonFactory.getObject()(三级缓存)获取,若是获取到了则从三级缓存移到了二级缓存。

​ 从上面三级缓存的分析,咱们能够知道,Spring 解决循环依赖的诀窍就在于 singletonFactories 这个三级 cache。这个 cache 的类型是 ObjectFactory,定义如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

​ 在 bean 建立过程中,有两处比较重要的匿名内部类实现了该接口。一处是 Spring 利用其建立 bean 的时候,另外一处就是:

addSingletonFactory(beanName, new ObjectFactory<Object>() {
    @Override   public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
    }});

​ 此处就是解决循环依赖的关键,这段代码发生在 createBeanInstance 以后,也就是说单例对象此时已经被建立出来了。这个对象已经被生产出来了。虽然还不完全(尚未进行初始化的第二步和第三步),可是已经能被人认出来了(根据对象引用能够定位到堆中的对象),因此 Spring 此时将这个对象提早曝光出来,供我们使用。

​ 比如"A 对象 setter 依赖 B 对象,B 对象 setter 依赖 A 对象",A 首先完成了初始化的第一步,而且将本身提早曝光到 singletonFactories 中,此时进行初始化的第二步,发现本身依赖对象 B,此时就尝试去 get(B),发现 B 尚未被 create,因此走 create 流程,B 在初始化第一步的时候发现本身依赖了对象 A,因而尝试 get(A),尝试一级缓存 singletonObjects(确定没有,由于 A 还未初始化彻底),尝试二级缓存 earlySingletonObjects(也没有),尝试三级缓存 singletonFactories,因为 A 经过 ObjectFactory 将本身提早曝光了,因此 B 可以经过 ObjectFactory.getObject 拿到 A 对象(半成品),B 拿到 A 对象后顺利完成了初始化阶段 一、二、三,彻底初始化以后将本身放入到一级缓存 singletonObjects 中。此时返回 A 中,A 此时能拿到 B 的对象顺利完成本身的初始化阶段 二、三,最终 A 也完成了初始化,进入了一级缓存 singletonObjects 中,并且更加幸运的是,因为 B 拿到了 A 的对象引用,因此 B 如今 hold 住的 A 对象完成了初始化。

Spring 为何不能解决非单例属性之外的依赖循环?

通过以下几个问题,辅助我们进一步理解。

Spring 为什么不能解决构造器的循环依赖?

​ 构造器注入形参的循环依赖:也就是 bean B 需要在 beanA 的构造函数中完成初始化,bean A 也需要在 bean B 的构造函数中完成初始化,这种情况的结果就是两个 bean 都不能完成初始化,循环依赖难以解决。

​ Spring 解决循环依赖主要是依赖三级缓存,但是在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存种获取到依赖的 Bean,因此不能解决。

Spring 为什么不能解决 prototype 作用域循环依赖

​ 这种循环依赖同样无法解决,因为 Spring 不会缓存 ‘prototype’ 作用域的 bean,而 spring 中循环依赖的解决正是通过缓存来实现的。

Spring 为什么不能解决多例的循环依赖?

​ 多实例 Bean 是每次调用一次 getBean 都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。

那么其它循环依赖如何解决?

那么实际开发中,类似的依赖是如何解决?

  • 生产代理对象产生的循环依赖

​ 这类循环依赖问题解决方法很多,主要有:

  1. 使用 @Lazy 注解,延迟加载
  2. 使用 @DependsOn 注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序
  • 使用 @DependsOn 产生的循环依赖

​ 这类循环依赖问题要找到 @DependsOn 注解循环依赖的地方,迫使它不循环依赖就可以解决问题。

  • 多例循环依赖

​ 这里循环依赖问题可以通过把 bean 改为单例的解决。

  • 构造器循环依赖

​ 这类循环依赖问题可以通过使用 @Lazy 注解解决。

5.4 重点:Spring 中 Bean 的生命周期

Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

​ Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。

​ 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的 Bean 时,Spring 容器都会创建一个新的实例,并且不会管那些被配置成 prototype 作用域的 Bean 的生命周期。

​ 了解 Spring 生命周期的意义就在于,可以利用 Bean 在其存活期间的指定时刻完成一些相关操作。这种时刻可能有很多,但一般情况下,会在 Bean 被初始化后和被销毁前执行一些相关操作。

Spring Bean 的生命周期流程

在 Spring 中,Bean 的生命周期是一个很复杂的执行过程,我们可以利用 Spring 提供的方法定制 Bean 的创建过程。

​ Spring 容器中 Bean 的生命周期流程:

在这里插入图片描述

  • 如果 BeanFactoryPostProcessor 和 Bean 关联,则调用 postProcessBeanFactory 方法。(即首先尝试从 Bean 工厂中获取 Bean)
  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用 postProcessBeforeInstantiation 方法。
  • 根据配置情况调用 Bean 构造方法实例化 Bean。
  • 利用依赖注入完成 Bean 中所有属性值的配置注入。
  • 如果 InstantiationAwareBeanPostProcessor 和 Bean 关联,则调用 postProcessAfterInstantiation 方法和 postProcessProperties。
  • 调用 xxxAware 接口
    • 第一类 Aware 接口
      • 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
      • 如果 Bean 实现了 BeanClassLoaderAware 接口,则 Spring 调用 setBeanClassLoader() 方法传入 classLoader 的引用。
      • 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前 ApplicationContext 实例的引用。
    • 第二类 Aware 接口
      • 如果 Bean 实现了 EnvironmentAware 接口,则 Spring 调用 setEnvironment() 方法传入当前 Environment 实例的引用。
      • 如果 Bean 实现了 EmbeddedValueResolverAware 接口,则 SPring 调用 setEmbeddedValueResolver() 方法传入当前 StringValueResolver 实例的引用。
      • 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
  • 如果 Bean 实现了 InitializingBean 接口,则 Spring 调用 afterPropertiesSet() 方法。(或者执行有 @PostConstruct 注解的方法)
  • 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
  • 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessBeforeInitialzation() 。此时,Bean 已经可以被应用系统使用了。
  • 如果在 <bean> 中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 <bean> 中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
  • 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;(或者执行有 @PreDestory 注解的方法)
  • 如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

Bean 的完整生命周期经历了各种方法调用,这些方法可以划分为以下几类:

  • Bean 自身的方法:这个包括了 Bean 本身调用的方法和通过配置文件中 <bean> 的 init-method 和 destory-method 指定的方法
  • Bean 级生命周期接口方法:这个包括了 BeanNameAware、BeanFactoryAware、ApplicationContextAware;当然也包括 InitializingBean 和 DiposableBean 这些接口的方法(可以被 @PostConstruct 和 @PreDestroy 注解替代)
  • 容器级生命周期接口方法:这个包括了 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为 “后处理器”。
  • 工厂后处理器接口方法:这个包括了 AspectJWeavingEnabler,ConfigurationClassPostProcessor,CustomAutowireConfigurer 等等非常有用的工厂后置处理器接口的方法。工厂后处理器也是容量级的。在应用上下文装配配置文件之后立即调用。

Spring Bean 生命周期案例

通过一个例子来验证上面的整个流程。

​ 定义 Bean,并让它实现相关接口:

@Slf4j
@ToString
public class User implements BeanFactoryAware, BeanNameAware, ApplicationContextAware,
        InitializingBean, Destroyable {

    private String name;
    private int age;
    private BeanFactory beanFactory;
    private ApplicationContext applicationContext;
    private String beanName;

    public User() {
        log.info("execute User#new User()");
    }

    public void setName(String name) {
        log.info("execute User#setUserName({})", name);
        this.name = name;
    }

    public void setAge(int age) {
        log.info("execute User#setAge({})", age);
        this.age = age;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        log.info("execute BeanFactoryAware#setBeanFactory");
        this.beanFactory = beanFactory;
    }

    @Override
    public void setBeanName(String s) {
        log.info("execute BeanNameAware#setBeanName");
        this.beanName = s;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.info("execute InitializingBean#afterPropertiesSet");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        log.info("execute ApplicationContextAware#setApplicationContext");
        this.applicationContext = applicationContext;
    }

    @Override
    public void destroy() {
        log.info("execute Destroyable#destroy");
    }

    public void doInit() {
        log.info("execute User#doInit");
    }

    public void doDestroy() {
        log.info("execute User#doDestroy");
    }
}

​ 定义 BeanFactoryPostProcessor 的实现类:

@Slf4j
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        log.info("execute BeanFactoryPostProcessor#postProcessBeanFactory");
    }
}

​ 定义 InstantiationAwareBeanPostProcessor 的实现类:

@Slf4j
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        log.info("execute InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation for {}", beanName);
        return InstantiationAwareBeanPostProcessor.super.postProcessBeforeInstantiation(beanClass, beanName);
    }

    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        log.info("execute InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation for {}", beanName);
        return InstantiationAwareBeanPostProcessor.super.postProcessAfterInstantiation(bean, beanName);
    }

    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        log.info("execute InstantiationAwareBeanPostProcessor#postProcessProperties for {}", beanName);
        return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
    }
}

​ 定义 BeanPostProcessor 的实现类:

@Slf4j
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        log.info("execute BeanPostProcessor#postProcessBeforeInitialization for ({})", beanName);
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        log.info("execute BeanPostProcessor#postProcessAfterInitialization for ({})", beanName);
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

​ 通过 Java 配置方式初始化 Bean:

@Configuration
public class BeansConfig {

    @Bean(name = "user", initMethod = "doInit", destroyMethod = "doDestroy")
    public User user() {
        User user = new User();
        user.setName("Zhanbo");
        user.setAge(18);
        return user;
    }
}

​ 测试方法:

@Slf4j
public class Test {
    public static void main(String[] args) {
        log.info("Init application context");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("io.xinghuo.spring.instance");
        User user = (User) context.getBean("user");
        log.info(user.toString());

        log.info("Shutdown application context");
        context.registerShutdownHook();
    }
}

​ 输出(去除无关输出):

21:07:38 [main] INFO  io.xinghuo.spring.instance.Test - Init application context
...
21:07:39 [main] INFO  io.xinghuo.spring.instance.MyBeanFactoryPostProcessor - execute BeanFactoryPostProcessor#postProcessBeanFactory
...
21:07:39 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
21:07:39 [main] INFO  io.xinghuo.spring.instance.MyInstantiationAwareBeanPostProcessor - execute InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation for user
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute User#new User()
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute User#setUserName(Zhanbo)
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute User#setAge(18)
21:07:39 [main] INFO  io.xinghuo.spring.instance.MyInstantiationAwareBeanPostProcessor - execute InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation for user
21:07:39 [main] INFO  io.xinghuo.spring.instance.MyInstantiationAwareBeanPostProcessor - execute InstantiationAwareBeanPostProcessor#postProcessProperties for user
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute BeanNameAware#setBeanName
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute BeanFactoryAware#setBeanFactory
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute ApplicationContextAware#setApplicationContext
21:07:39 [main] INFO  io.xinghuo.spring.instance.MyBeanPostProcessor - execute BeanPostProcessor#postProcessBeforeInitialization for (user)
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute InitializingBean#afterPropertiesSet
21:07:39 [main] INFO  io.xinghuo.spring.instance.User - execute User#doInit
21:07:39 [main] INFO  io.xinghuo.spring.instance.MyBeanPostProcessor - execute BeanPostProcessor#postProcessAfterInitialization for (user)
21:07:39 [main] INFO  io.xinghuo.spring.instance.Test - User(name=Zhanbo, age=18)
21:07:39 [main] INFO  io.xinghuo.spring.instance.Test - Shutdown application context 
21:07:39 [SpringContextShutdownHook] INFO  io.xinghuo.spring.instance.User - execute Destroyable#destroy
21:07:39 [SpringContextShutdownHook] INFO  io.xinghuo.spring.instance.User - execute User#doDestroy

六、Spring 进阶 - AOP 切面的实现

6.1 引入

我们应该从哪里开始着手 Spring AOP 的源码呢?与我们前几节分析的 IOC 源码实现有什么关系呢?

​ 我们通过 XML 来配置 AOP,我们不难发现 AOP 是基于 IOC 的 Bean 加载来实现的;而 <aop:aspectj-autoproxy/> 便是主要的入口。

​ 然后我们就可以如下初始化的流程和 aop 对应的 handler 类

即 parseCustomElement 方法找到 parse aop:aspectj-autoproxy 的 handler(AopNamespaceHandler)

6.2 AOP 配置标签的解析

上节中,我们找到了 AopNamespaceHandler,其实就是注册 BeanDefinition 的解析器 BeanDefinitionParser,将 aop:xxx 配置标签交给指定的 parser 来处理。

public class AopNamespaceHandler extends NamespaceHandlerSupport {

    public void init() {
        // 注册解析 <aop:config> 配置
        this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        // 注册解析 <aop:aspectj-autoproxy> 配置
        this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
}

config 配置标签的解析

<aop:config/> 由 ConfigBeanDefinitionParser 这个类处理,作为 parser 类最重要的就是 parse 方法。

@Nullable
public BeanDefinition parse(Element element, 				ParserContext parserContext) 
{
    CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    parserContext.pushContainingComponent(compositeDef);
    this.configureAutoProxyCreator(parserContext, element);

    for(Element elt : DomUtils.getChildElements(element)) {
        String localName = parserContext.getDelegate().getLocalName(elt);
        if ("pointcut".equals(localName)) {
            this.parsePointcut(elt, parserContext);
        } else if ("advisor".equals(localName)) {
            this.parseAdvisor(elt, parserContext);
        } else if ("aspect".equals(localName)) {
            this.parseAspect(elt, parserContext);
        }
    }

    parserContext.popAndRegisterContainingComponent();
    return null;
}

private void parseAspect(Element aspectElement, 			ParserContext parserContext) 
{
    String aspectId = aspectElement.getAttribute("id");
    String aspectName = aspectElement.getAttribute("ref");

    try {
        this.parseState.push(new AspectEntry(aspectId, aspectName));
        List<BeanDefinition> beanDefinitions = new ArrayList();
        List<BeanReference> beanReferences = new ArrayList();
        List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, "declare-parents");

        for(int i = 0; i < declareParents.size(); ++i) {
            Element declareParentsElement = (Element)declareParents.get(i);
            beanDefinitions.add(this.parseDeclareParents(declareParentsElement, parserContext));
        }

        NodeList nodeList = aspectElement.getChildNodes();
        boolean adviceFoundAlready = false;

        for(int i = 0; i < nodeList.getLength(); ++i) {
            Node node = nodeList.item(i);
            if (this.isAdviceNode(node, parserContext)) {
                if (!adviceFoundAlready) {
                    adviceFoundAlready = true;
                    if (!StringUtils.hasText(aspectName)) {
                        parserContext.getReaderContext().error("<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.", aspectElement, this.parseState.snapshot());
                        return;
                    }

                    beanReferences.add(new RuntimeBeanReference(aspectName));
                }

                AbstractBeanDefinition advisorDefinition = this.parseAdvice(aspectName, i, aspectElement, (Element)node, parserContext, beanDefinitions, beanReferences);
                beanDefinitions.add(advisorDefinition);
            }
        }

        AspectComponentDefinition aspectComponentDefinition = this.createAspectComponentDefinition(aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
        parserContext.pushContainingComponent(aspectComponentDefinition);

        for(Element pointcutElement : DomUtils.getChildElementsByTagName(aspectElement, "pointcut")) {
            this.parsePointcut(pointcutElement, parserContext);
        }

        parserContext.popAndRegisterContainingComponent();
    } finally {
        this.parseState.pop();
    }
}

aspectj-autoproxy 配置标签的解析

<aop:aspectj-autoproxy/> 则由 AspectJAutoProxyBeanDefinitionParser 这个类处理,我们看下 parse 方法:

@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 注册 AspectJAnnotationAutoProxyCreator
    AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    // 拓展 BeanDefinition
    this.extendBeanDefinition(element, parserContext);
    return null;
}

​ AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法对应如下:

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {
    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    registerComponentIfNecessary(beanDefinition, parserContext);
}

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary 方法对应如下:

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

​ 到此,我们可以发现 AOP 的创建工作交给了 AnnotationAwareAspectJAutoProxyCreator 来完成。

6.3 注解切面代理创建类(AnnotationAwareAspectJAutoProxyCreator)

AnnotationAwareAspectJAutoProxyCreator 是如何工作的呢?这时候我们就要看 AnnotationAwareAspectJAutoProxyCreator 类结构关系了。

​ 如下是类结构关系:

在这里插入图片描述

​ 它实现了两类接口:

  • BeanFactoryAware 属于 Bean 级生命周期接口方法;
  • InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为 “后处理器”,是容器级生命周期接口方法;

结合 6.4 节中 Spring Bean 生命周期的流程

在这里插入图片描述

​ 我们就可以定位到核心的初始化方法肯定在 postProcessBeforeInstantiation 和 postProcessorAfterInitialization 中。

postProcessorBeforeInstantiation

​ 如下是上述结构中 postProcessBeforeInstantiation 的方法:

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    Object cacheKey = this.getCacheKey(beanClass, beanName);
    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        // 如果已经在缓存中,则忽略
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
		// 是否是 aop 基础类?是否跳过?
        if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }

    TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }

        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    } else {
        return null;
    }
}

判断是否是 aop 基础类

​ 是否是 aop 基础类的判断方法 isInfrastructureClass 如下:

protected boolean isInfrastructureClass(Class<?> beanClass) {
    // 该类是否实现了 Advice,Pointcut,Advisor 或者 AopInfrastructureBean 接口
    boolean retVal = Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass) || AopInfrastructureBean.class.isAssignableFrom(beanClass);
    if (retVal && this.logger.isTraceEnabled()) {
        this.logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
    }

    return retVal;
}

是否应该跳过 shouldSkip

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    for(Advisor advisor : this.findCandidateAdvisors()) {
        if (advisor instanceof AspectJPointcutAdvisor && ((AspectJPointcutAdvisor)advisor).getAspectName().equals(beanName)) {
            return true;
        }
    }

    return super.shouldSkip(beanClass, beanName);
}

切换方法转成 Advisor

​ findCandiadateAdvisors 方法如下:

protected List<Advisor> findCandidateAdvisors() {
    // 在父类中找到所有的 advisor:基于 xml 配置的 <aop:before/> 生产的
    List<Advisor> advisors = super.findCandidateAdvisors();
   	// 为 bean Factory 中 AspectJ 切面构建 advistor:通过 AspectJ 注解的方式生成 Advisor 类
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }

    return advisors;
}

​ 在当前的 bean Factory 中通过 AspectJ 注解的方式生成 Advisor 类,buildAspectJAdvisors 方法如下:

public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;
    if (aspectNames == null) {
        synchronized(this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new ArrayList();
                aspectNames = new ArrayList();
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);

                for(String beanName : beanNames) {
                    if (this.isEligibleBean(beanName)) {
                        Class<?> beanType = this.beanFactory.getType(beanName, false);
                        if (beanType != null && this.advisorFactory.isAspect(beanType)) {
                            aspectNames.add(beanName);
                            AspectMetadata amd = new AspectMetadata(beanType, beanName);
                            if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                                MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                if (this.beanFactory.isSingleton(beanName)) {
                                    this.advisorsCache.put(beanName, classAdvisors);
                                } else {
                                    this.aspectFactoryCache.put(beanName, factory);
                                }

                                advisors.addAll(classAdvisors);
                            } else {
                                if (this.beanFactory.isSingleton(beanName)) {
                                    throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect instantiation model is not singleton");
                                }

                                MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                                this.aspectFactoryCache.put(beanName, factory);
                                advisors.addAll(this.advisorFactory.getAdvisors(factory));
                            }
                        }
                    }
                }

                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }

    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    } else {
        List<Advisor> advisors = new ArrayList();

        for(String aspectName : aspectNames) {
            List<Advisor> cachedAdvisors = (List)this.advisorsCache.get(aspectName);
            if (cachedAdvisors != null) {
                advisors.addAll(cachedAdvisors);
            } else {
                MetadataAwareAspectInstanceFactory factory = (MetadataAwareAspectInstanceFactory)this.aspectFactoryCache.get(aspectName);
                advisors.addAll(this.advisorFactory.getAdvisors(factory));
            }
        }

        return advisors;
    }
}

​ 上述方法本质上的思路是:用 DCL 双重锁的单例实现方式,拿到切面类里的切面方法,将其转换称 advisor(并放入缓存中)。

​ 转换称 advisor 的方法是:this.advisorFactory.getAdvisors

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    this.validate(aspectClass);
    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
    List<Advisor> advisors = new ArrayList();

    for(Method method : this.getAdvisorMethods(aspectClass)) {
        Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }

    for(Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = this.getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    return advisors;
}

​ getAdvisor 方法如下:

@Nullable
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {
    this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
    AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    // 封装称 advisor
    return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

获取表达式的切点

​ 获取表达式的切点的方法 getPointcut 如下:

@Nullable
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    AbstractAspectJAdvisorFactory.AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    } else {
        AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
        ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
        if (this.beanFactory != null) {
            ajexp.setBeanFactory(this.beanFactory);
        }

        return ajexp;
    }
}

​ AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod 的方法如下:

private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class[]{Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};

@Nullable
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    for(Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, clazz);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }

    return null;
}

​ findAnnotation 方法如下:

    private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
        A result = AnnotationUtils.findAnnotation(method, toLookFor);
        return result != null ? new AspectJAnnotation(result) : null;
    }

​ AnnotationUtils.findAnnotation 获取注解方法如下:

    @Nullable
    public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType) {
        if (annotationType == null) {
            return null;
        } else {
            return (A)(!AnnotationFilter.PLAIN.matches(annotationType) && !AnnotationsScanner.hasPlainJavaAnnotationsOnly(method) ? (Annotation)MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none()).get(annotationType).withNonMergedAttributes().synthesize(MergedAnnotation::isPresent).orElse((Object)null) : method.getDeclaredAnnotation(annotationType));
        }
    }

封装称 Advisor

注:Advisor 是 advice 的包装器,包含了 advice 及其它信息。

​ 由 InstantiationModelAwarePointcutAdvisorImpl 构造完成

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    this.declaredPointcut = declaredPointcut;
    this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
    this.methodName = aspectJAdviceMethod.getName();
    this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
    this.aspectJAdviceMethod = aspectJAdviceMethod;
    this.aspectJAdvisorFactory = aspectJAdvisorFactory;
    this.aspectInstanceFactory = aspectInstanceFactory;
    this.declarationOrder = declarationOrder;
    this.aspectName = aspectName;
    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
        this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
        this.lazy = true;
    } else {
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        this.instantiatedAdvice = this.instantiateAdvice(this.declaredPointcut);
    }

}

​ 通过 pointcut 获取 advice

private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
    Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
    return advice != null ? advice : EMPTY_ADVICE;
}

​ 交给 aspectJAdvisorFactory 获取

@Nullable
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    // 获取切面类
    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    this.validate(candidateAspectClass);
    
    // 获取切面注解
    AbstractAspectJAdvisorFactory.AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    } else if (!this.isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");
    } else {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Found AspectJ method: " + candidateAdviceMethod);
        }
		// 切面注解转换称 Advice
        AbstractAspectJAdvice springAdvice;
        switch (aspectJAnnotation.getAnnotationType()) {
            case AtPointcut: // AtPointcut 忽略
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
                }

                return null;
            case AtAround:
                springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtBefore:
                springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfter:
                springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfterReturning:
                springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterReturning afterReturningAnnotation = (AfterReturning)aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                    springAdvice.setReturningName(afterReturningAnnotation.returning());
                }
                break;
            case AtAfterThrowing:
                springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                    springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
                }
                break;
            default:
                throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);
        }
		// 最后将其它切面信息配置到 advice
        springAdvice.setAspectName(aspectName);
        springAdvice.setDeclarationOrder(declarationOrder);
        String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
        if (argNames != null) {
            springAdvice.setArgumentNamesFromStringArray(argNames);
        }

        springAdvice.calculateArgumentBindings();
        return springAdvice;
    }
}

小结

​ 回头看,主要是处理使用了 @Aspect 注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应 Advice,并将 Advice 连同切入点匹配器和切面类等信息一并封装到 Advisor 的过程。

postProcessAfterInitialization

​ 有了 Advisor,注入到合适的位置并交给代理(cglib 和 jdk)实现了。

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return this.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }

    return bean;
}

6.4 总结

​ 通过上文的分析,我们进行小结:

  1. 由 IOC Bean 加载方法栈中找到 parseCustomElement 方法,找到 parse aop:aspectj-autoproxy 的 handler(org.springframework.aop.config.AopNamespaceHandler)
  2. AopNamespaceHandler 注册了 <aop:aspectj-autoproxy/> 的解析类是 AspectJAutoProxyBeanDefinitionParser
  3. AspectJAutoProxyBeanDefinitionParser 的 parse 方法通过 AspectJAwareAdvisorAutoProxyCreator 类去创建
  4. AspectJAwareAdvisorAutoProxyCreator 实现了两类接口,BeanFactoryAware 和 BeanPostProcessor;根据 Bean 生命周期方法找到了两个核心方法:postProcessBeforeInstantiation 和 postProcessAfterInitialization
    1. postProcessBeforeInstantiation:主要是处理使用了 @Aspect 注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应 Advice,并将 Advice 连同切入点匹配器和切面类等信息一并封装到 Advisor
    2. postProcessAfterInitialization:主要负责将 Advisor 注入到合适的位置,创建(cglib 或 jdk),为后面给代理进行增强实现做准备

七、Spring 进阶 - AOP 代理的创建

7.1 引入

前文主要介绍了 Spring AOP 原理解析的切面实现过程(加载配置,将切面类的所有切面方法根据使用的注解生成对应 Advice,并将 Advice 连同切入点匹配器和切面类等信息一并封装到 Advisor)。

​ 同时我们也总结了 Spring AOP 初始化的过程,具体如下:

  1. 由 IOC Bean 加载方法栈中找到 parseCustomElement 方法,找到 parse aop:aspectj-autoproxy 的 handler(org.springframework.aop.config.AopNamespaceHandler)
  2. AopNamespaceHandler 注册了 <aop:aspectj-autoproxy> 的解析类是 AspectJAutoProxyBeanDefinitionParser
  3. AspectJAutoProxyBeanDefinitionParser 的 parse 方法通过 AspectJAwareAdvisorAutoProxyCreator 类去创建
  4. AspectJAwareAdvisorAutoProxyCreator 实现了两类接口,BeanFactoryAware 和 BeanPostProcessor;根据 Bean 生命周期方法找到两个核心方法:postPorcessBeforeInistantiation 和 postProcessAfterInitialization
    1. postProcessBeforeInstantiation:主要是处理了使用 @Aspect 注解的切面类,然后将切面类的所有切面方法根据使用的注解生成对应 Advice,并将 Advice 连同切入点匹配器和切面类等信息一并封装到 Advisor
    2. postProcessAfterInitialization:主要负责将 Advisor 注入到合适的位置,创建代理(cglib 或 jdk),为后面给代理进行增强实现做准备

本节接着介绍 postProcessAfterInitialization 的方法,即 Spring AOP 的代理(cglib 或 jdk)的创建过程。

7.2 代理的创建

​ 创建代理的方法是 postProcessAfterInitialization:如果 bean 被子类表示为代理,则使用配置的拦截器创建一个代理:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
		// 如果不是提前暴露的代理
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return this.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }

    return bean;
}

wrapIfNecessary 方法主要用于判断是否需要创建带,如果 Bean 能够获取到 advisor 才需要创建代理。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 如果 bean 是通过 TargetSource 
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { // 如果 bean 是切面类
        return bean;
    } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        // 获取所有 advisor,如果没有获取到,那说明不要进行增强,也就不需要代理了。
        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            // 创建代理
            Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    } else {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}

获取所有的 Advisor

​ 我们看下获取所有 advisor 的方法 getAdvicesAndAdvisorsForBean

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
    return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
}

​ 通过 findEligibleAdvisors 方法获取 advisor,如果获取不到则返回 DO_NOT_PROXY(不需要创建代理),findEliglibleAdvisors 方法如下:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 如上文一样,获取所有切面类的切面方法生成 Advisor
    List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
    // 找到这些 Advisor 中能够应用于 beanClass 的 Advisor
    List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	// 如果需要,交给子类拓展
    this.extendAdvisors(eligibleAdvisors);
    // 对 Advisor 排序
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
    }

    return eligibleAdvisors;
}

​ 获取所有切面类的切面方法生成 Advisor

protected List<Advisor> findCandidateAdvisors() {
    Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
    return this.advisorRetrievalHelper.findAdvisorBeans();
}

​ 找到这些 Advisor 中能够应用于 beanClass 的 Advisor

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    } else {
        List<Advisor> eligibleAdvisors = new ArrayList();

        for(Advisor candidate : candidateAdvisors) {
            // 通过 Introduction 实现的 advice
            if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
                eligibleAdvisors.add(candidate);
            }
        }

        boolean hasIntroductions = !eligibleAdvisors.isEmpty();

        for(Advisor candidate : candidateAdvisors) {
            if (!(candidate instanceof IntroductionAdvisor) && canApply(candidate, clazz, hasIntroductions)) {
            	// 能够应用于 clazz 的 Advice
                eligibleAdvisors.add(candidate);
            }
        }

        return eligibleAdvisors;
    }
}

创建代理的入口方法

​ 获取所有 advisor 后,如果有 advisor,则说明需要增强,即需要创建代理,创建代理的方法如下:

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 = new ProxyFactory();
    proxyFactory.copyFrom(this);
    if (proxyFactory.isProxyTargetClass()) {
        if (Proxy.isProxyClass(beanClass)) {
            for(Class<?> ifc : beanClass.getInterfaces()) {
                proxyFactory.addInterface(ifc);
            }
        }
    } else if (this.shouldProxyTargetClass(beanClass, beanName)) {
        proxyFactory.setProxyTargetClass(true);
    } else {
        this.evaluateProxyInterfaces(beanClass, proxyFactory);
    }

    Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    this.customizeProxyFactory(proxyFactory);
    proxyFactory.setFrozen(this.freezeProxy);
    if (this.advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    ClassLoader classLoader = this.getProxyClassLoader();
    if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
        classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();
    }

    return proxyFactory.getProxy(classLoader);
}

​ proxyFactory.getProxy(classLoader):

public Object getProxy(@Nullable ClassLoader classLoader) {
    return this.createAopProxy().getProxy(classLoader);
}

根据条件创建代理(jdk 或 cglib)

​ DefaultAopProxyFactory.createAopProxy

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (!NativeDetector.inNativeImage() && (config.isOptimize() || config.isProxyTargetClass() || this.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.");
        } else {
            return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
        }
    } else {
        return new JdkDynamicAopProxy(config);
    }
}

​ 几个要点:

  • config.isOptimize() 是通过 optimize 设置,表示配置是自定义的,默认是 false;
  • config.isProxyTargetClass() 是通过 <aop:config proxy-target-class="true"/> 来配置的,表示优先使用 cglib 代理,默认是 false;
  • hasNoUserSuppliedProxyInterfaces(config) 表示是否目标类实现了接口

​ 由此我们可以知道:

​ Spring 默认在目标类实现接口时是通过 JDK 代理实现的,只有非接口的是通过 Cglib 代理实现的。当设置 proxy-target-class 为 true 时在目标类不是接口或者代理类优先使用 cglib 代理实现。

八、Spring 进阶 - Cglib 代理实现

8.1 引入

在前边已经介绍了 SpringAOP 的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本节主要介绍 Cglib 动态代理的案例和 Spring AOP 实现的原理。

​ 要了解动态代理是如何工作的,首先需要了解:

  • 什么是代理模式?
  • 什么是动态代理?
  • 什么是 Cglib?
  • Spring AOP 和 Cglib 是什么关系?

动态代理要解决什么问题?

什么是代理?

​ 代理模式(Proxy pattern):为另一个对象提供一个替身或占位符以控制对这个对象的访问

在这里插入图片描述

​ 举个简单的例子:

​ 我(client)如果要买(doOperation)房,可以找中介(proxy)买房,中介直接和卖方(target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(proxy)。

什么是动态代理?

动态代理就是,在程序运行期间,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。

​ 在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

什么是 Cglib?Spring AOP 和 Cglib 是什么关系?

Cglib 是一个强大的、高性能的代码生成包,它广泛被许多 AOP 框架使用,为他们提供方法的拦截。

在这里插入图片描述

  • 最底层是字节码,字节码相关的知识可以参考:Java 字节码

  • ASM 是操作字节码的工具

  • cglib 基于 ASM 字节码工具操作字节码(即动态生成代理,对方法进行增强)

  • Spring AOP 基于 cglib 进行封装,实现 cglib 方式的动态代理

8.2 Cglib 代理的例子

这里举一个使用 cglib 的简单例子。

pom 包依赖

​ 引入 cglib 的依赖包

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

定义实体类

​ User

public class user {
    private String name;
    private int age;

    public user(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "User{name=" + name + ", age=" + age + "}";
    }
}

被代理的类

​ 即目标类,对被代理的类中的方法进行增强

public class UserServiceImpl {

    public List<User> findUserList() {
        return Collections.singletonList(new User("zhangsan", 18));
    }
    
    public void addUser() {
        // TODO Add User
    }
}

Cglib 代理

​ Cglib 代理类,需要实现 MethodInterceptor 接口,并指定代理目标类 target

public class UserLogProxy implements MethodInterceptor {

    /**
     * 业务类对象,供代理方法中进行真正方法调用
     */
    private Object target;

    public Object getUserLogProxy(Object target) {
        // 给业务对象赋值
        this.target = target;
        // 创建加强类,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        // 为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
        enhancer.setSuperclass(target.getClass());
        // 设置回调,对于代理类上所有方法的调用,都会调用 Callback,而 Callback 则需要实现 intercept() 方法进行拦截
        enhancer.setCallback(this);
        // 创建动态代理类并返回
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("[before] execute method: " + method.getName());
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("[after] execute method: " + method.getName() + ", result value: " + result  );
        return result;
    }
}

使用代理

​ 启动类中指定代理目标并执行

public class Test {
    public static void main(String[] args) {
        UserServiceImpl userLogProxy = (UserServiceImpl) new UserLogProxy().getUserLogProxy(new UserServiceImpl());
        userLogProxy.findUserList();
        userLogProxy.addUser();
    }
}

简单测试

​ 启动上述类 main 函数,执行的结果如下:

[before] execute method: findUserList
[after] execute method: findUserList, result value: [User{name=zhangsan, age=18}]
[before] execute method: addUser
[after] execute method: addUser, result value: null

8.3 Cglib 代理的流程

​ 将上述测试的主要流程画出来,便于进行理解:

在这里插入图片描述

​ 更多细节:

  • 在上图中,我们可以通过在 Enhancer 中配置更多的参数来控制代理的行为,比如如果只希望增强这个类中的一个方法(而不是所有的方法),那就增强 callbackFilter 来对目标类中方法进行过滤;在 Enhancer 可以有更多的参数类配置其行为,不过我们只学习上述主要的流程就够了。
  • final 方法为什么不能被代理?很显然 final 方法没法被子类覆盖,当然能不能被代理了。
  • Mockito 为什么不能 mock 静态方法?因为 mockito 也是基于 cglib 动态代理来实现的,static 方法也不能被子类覆盖,所以显然不能 mock。但 PowerMock 可以 mock 静态方法,因为它直接在 bytecode 上工作。

8.4 Spring AOP 中 Cglib 代理的实现

Spring AOP 封装了 cglib,通过其进行动态代理的创建。

我们看一下 CglibAopProxy 的 getProxy 方法:

public Object getProxy() {
    return this.getProxy((ClassLoader)null);
}

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 (rootClass.getName().contains("$$")) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();

            for(Class<?> additionalInterface : additionalInterfaces) {
                this.advised.addInterface(additionalInterface);
            }
        }

        this.validateClassIfNecessary(proxySuperClass, classLoader);
        // 重点看这里,设置各种参数来构建 Enhancer
        Enhancer enhancer = this.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 ClassLoaderAwareGeneratorStrategy(classLoader));
        // 设置 callback 回调接口,即方法的增强点
        Callback[] callbacks = this.getCallbacks(rootClass);
        Class<?>[] types = new Class[callbacks.length];

        for(int x = 0; x < types.length; ++x) {
            types[x] = callbacks[x].getClass();
        }
		// 上节提到的 filter
        enhancer.setCallbackFilter(new ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        // 创建 proxy 和其实例
        return this.createProxyClassAndInstance(enhancer, callbacks);
    } catch (IllegalArgumentException | CodeGenerationException 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) {
        throw new AopConfigException("Unexpected AOP exception", ex);
    }
}
获取 callback 的方法如下,提几个理解的要点:
  • rootClass:即目标代理类
  • advised:包含上下文中我们获取到的 advisor 增强器的集合
  • exposeProxy:在 xml 配置文件中配置的,背景就是如果在事务 A 中使用了代理,事务 A 调用了目标类的方法 a,在方法 a 中又调用目标类的方法 b,方法 a,b 同时都是要被增强的方法,如果不配置 exposeProxy 属性,方法 b 的增强将会失效,如果配置 exposeProxy,方法 b 在方法 a 的执行中也会被增强了
  • DynamicAdvisedInterceptor:拦截器将 advised(包含上文中我们获取到的 advisor 增强器)构建配置的 AOP 的 callback(第一个 callback)
  • targetInterceptor:xml 配置的 optimize 属性使用的(第二个 callback)
  • 最后连同其它 5 个默认的 Interceptor 返回作为 cglib 的拦截器链,之后通过 CallbackFilter 的 accpet 方法返回的索引从这个集合中返回对应的拦截增强器执行增强操作
private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();
    Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);
    Callback targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = (Callback)(isStatic ? new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
    } else {
        targetInterceptor = (Callback)(isStatic ? new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
    }

    Callback targetDispatcher = (Callback)(isStatic ? new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());
    Callback[] mainCallbacks = new Callback[]{aopInterceptor, targetInterceptor, new SerializableNoOp(), targetDispatcher, this.advisedDispatcher, new EqualsInterceptor(this.advised), new HashCodeInterceptor(this.advised)};
    Callback[] callbacks;
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);

        for(int x = 0; x < methods.length; ++x) {
            Method method = methods[x];
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
            fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(method, x);
        }

        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    } else {
        callbacks = mainCallbacks;
    }

    return callbacks;
}

九、Spring 进阶 - JDK 代理实现

9.1 引入

上文我们学习了 Spring AOP Cglib 动态代理的实现,本文主要围绕 Spring AOP JDK 动态代理进行展开。

什么是 JDK 代理?

​ JDK 动态代理是由 JDK 提供的工具类 Proxy 实现的,动态代理类是在运行时生成指定接口的代理类,每个代理实例(实现需要代理的接口)都有一个关联的调用处理程序对象,此对象实现了 InvocationHandler,最终的业务逻辑是在 InvocationHandler 实现类的 invoke 方法上。

9.2 JDK 代理的例子

这里举一个使用 JDK 代理的简单例子。

定义实体

​ User

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "User{name=" + name + ", age=" + age + "}";
    }
}

被代理的类和接口

​ 接口如下:

public interface UserService {
    List<User> findUserList();
    
    void addUser();
}

​ 实现类如下:

public class UserServiceImpl implements UserService {

    public List<User> findUserList() {
        return Collections.singletonList(new User("zhangsan", 18));
    }

    public void addUser() {
        // TODO Add User
    }
}

JDK 代理类

​ 代理类如下:

public class UserLogProxy {

    private UserService target;

    public UserLogProxy(UserServiceImpl target) {
        super();
        this.target = target;
    }

    public UserService getLogProxy() {
        ClassLoader loader = target.getClass().getClassLoader();
        Class[] interfaces = new Class[]{UserService.class};
        return (UserService) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
            /**
             * @param proxy 代理对象。一般不使用该对象。
             * @param method 正在被调用的方法
             * @param args 调用方法传入的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                System.out.println("[before] execute method: " + methodName);
                Object result = null;
                try {
                    result = method.invoke(target, args);
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
                System.out.println("[after] execute method: " + methodName + ", return value: " + result);
                return result;
            }
        });
    }
}

使用代理

​ 启动类指定代理目标并执行:

public class Test {
    public static void main(String[] args) {
        UserService userService = new UserLogProxy(new UserServiceImpl()).getLogProxy();
        userService.findUserList();
        userService.addUser();
    }
}

简单测试

​ 启动上述类 main 函数,执行结果如下:

[before] execute method: findUserList
[after] execute method: findUserList, return value: [User{name=zhangsan, age=18}]
[before] execute method: addUser
[after] execute method: addUser, return value: null

9.3 JDK 代理的流程

JDK 代理自动生成的 class 是由 sun.misc.PorxyGenerator 来生成的。

ProxyGenerator 生成代码

​ 我们看下 sun.misc.ProxyGenerator 生成代码的逻辑:

public static byte[] generateProxyClass(final String name, Class<?>[] interfaces, int accessFlags) {
    ProxyGenerator gen = new ProxyGenerator(name, interfaces, accessFlags);
    final byte[] classFile = gen.generateClassFile();
	...
}

​ generateClassFile 方法如下:

private byte[] generateClassFile() {
	// 第一步:将所有方法包装成 ProxyMethod 对象
    // 将 Object 类中 hashCode、equals、toString 方法包装成 ProxyMethod 对象
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);
	// 将代理类接口方法包装成 ProxyMethod 对象
    for(Class intf : this.interfaces) {
        for(Method m : intf.getMethods()) {
            this.addProxyMethod(m, intf);
        }
    }
	// 校验返回类型
    for(List sigmethods : this.proxyMethods.values()) {
        checkReturnTypes(sigmethods);
    }
	// 第二步:为代理类组装字段,构建函数,方法,static 初始化块等
    try {
        // 添加构造函数,参数是 InvocationHandler
        this.methods.add(this.generateConstructor());
		// 代理方法
        for(List sigmethods : this.proxyMethods.values()) {
            for(ProxyMethod pm : sigmethods) {
                // 字段
                this.fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                // 上述 ProxyMethod 中的方法
                this.methods.add(pm.generateMethod());
            }
        }
		// static 初始化块
        this.methods.add(this.generateStaticInitializer());
    } catch (IOException e) {
        throw new InternalError("unexpected I/O Exception", e);
    }

    if (this.methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    } else if (this.fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    } else {
        // 第三步:写入 class 文件
        this.cp.getClass(dotToSlash(this.className));
        this.cp.getClass("java/lang/reflect/Proxy");

        for(Class intf : this.interfaces) {
            this.cp.getClass(dotToSlash(intf.getName()));
        }

        this.cp.setReadOnly();
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream(bout);

        try {
            dout.writeInt(0xCAFEBABE);
            dout.writeShort(CLASSFILE_MINOR_VERSION);
            dout.writeShort(CLASSFILE_MAJOR_VERSION);

            cp.write(dout);             

            dout.writeShort(accessFlags);
            dout.writeShort(cp.getClass(dotToSlash(className)));
            dout.writeShort(cp.getClass(superclassName));

            dout.writeShort(interfaces.length);

            for (Class<?> intf : interfaces) {
                dout.writeShort(cp.getClass(
                    dotToSlash(intf.getName())));
            }


            dout.writeShort(fields.size());
            
            for (FieldInfo f : fields) {
                f.write(dout);
            }

            dout.writeShort(methods.size());

            for (MethodInfo m : methods) {
                m.write(dout);
            }

            dout.writeShort(0);

        } catch (IOException e) {
            throw new InternalError("unexpected I/O Exception", e);
        }

        return bout.toByteArray();
    }
}

​ 一共三个步骤(把大象装进冰箱分几步):

  • 第一步:(把冰箱门打开)准备工作,将所有方法包装成 ProxyMethod 对象,包括 Object 类中 hashCode、equals、toString 方法,以及被代理的接口中的方法
  • 第二步:(把大象装进去)为代理类组装字段、构造函数、方法、static 初始化块等
  • 第三步:(把冰箱门关上)写入 class 文件

从生成的 Proxy 代码看执行流程

​ 从上述 sun.mics.ProxyGenerator 类中可以看到,这个类里面有一个配置参数 sun.mics.ProxyGenerator.saveGeneratedFiles,可以通过这个参数将生成的 Proxy 类保存在本地,比如设置为 true 执行后(System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")),生成的文件如下:

在这里插入图片描述

​ 我们看一下生成后的代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sun.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.List;
import proxy.cglib.UserService;

// 所有类和方法都是 final 类型的
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m0;
    private static Method m3;
	
    // 构造函数注入 InvocationHandler
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final List findUserList() throws  {
        try {
            return (List)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void addUser() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            // 初始化 methods,2 个 UserSerice 接口中的方法, 3个 Object 中的接口
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("proxy.cglib.UserService").getMethod("findUserList");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("proxy.cglib.UserService").getMethod("addUser");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(((Throwable)var2).getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());
        }
    }
}

​ 上述代码的主要流程是:

  • ProxyGenerator 创建 Proxy 的具体类 $Proxy0
  • 由 static 初始化块初始化接口方法:2 个 UserService 接口中的方法,3 个 Object 中的接口方法
  • 由 构造函数注入 InvocationHandler
  • 执行的时候,通过 ProxyGenerator 创建的 Proxy,调用 InvocationHandler 的 invoke 方法,执行我们自定义的 invoke 方法

9.4 Spring AOP 中 JDK 代理的实现

​ Spring AOP 扮演的是 JDK 代理的创建和调用两个角色,我们通过这两个方向来看下 Spring AOP 的代码(JdkDynamicAopProxy 类)

Spring AOP Jdk 代理的创建

​ 代理的创建比较简单,调用 getProxy 方法,然后直接调用 JDK 中 Proxy.newProxyInstance() 方法将 classloader 和被代理的接口方法传入即可。

public Object getProxy() {
    return this.getProxy(ClassUtils.getDefaultClassLoader());
}

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }

    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

Spring AOP Jdk 代理的执行

​ 执行的方法如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    Boolean retVal;
    try {
        // 执行的是 equals 方法
        if (this.equalsDefined || !AopUtils.isEqualsMethod(method)) {
            // 执行的是 hashCode 方法
            if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                Integer var19 = this.hashCode();
                return var19;
            }
			// 如果是包装类,则 dispatch to proxy config
            if (method.getDeclaringClass() == DecoratingProxy.class) {
                Class var18 = AopProxyUtils.ultimateTargetClass(this.advised);
                return var18;
            }
			// 用反射方式来执行切点
            if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                Object var17 = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
                return var17;
            }

            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }

            target = targetSource.getTarget();
            Class<?> targetClass = target != null ? target.getClass() : null;
            // 获取拦截链
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            Object retVal;
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
            }

            Class<?> returnType = method.getReturnType();
            if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
            }

            Object var12 = retVal;
            return var12;
        }

        retVal = this.equals(args[0]);
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }

    }

    return retVal;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/935609.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HNU_多传感器(专选)_作业4(构建单层感知器实现分类)

1. (论述题)&#xff08;共1题&#xff0c;100分&#xff09; 假设平面坐标系上有四个点&#xff0c;要求构建单层感知器实现分类。 (3,3),(4,3) 两个点的标签为1&#xff1b; (1,1),(0,2) 两个点的标签为-1。 思路&#xff1a;要分类的数据是2维数据&#xff0c;需要2个输入…

dolphinscheduler服务RPC框架源码解析(二)RPC核心注解@RpcService和@RpcMethod设计实现

1.工程目录 从3.2.1版本之后这个dolphinscheduler中的RPC框架工程就从原来的dolphinscheduler-remote工程重构到了dolphinscheduler-extract工程。 dolphinscheduler 父项目 dolphinscheduler-extract RPC服务项目 dolphinscheduler-extract-alert 监控告警服务RPC接口定义、…

CentOS 上如何查看 SSH 服务使用的端口号?

我们知道&#xff0c;linux操作系统中的SSH默认情况下&#xff0c;端口是使用22&#xff0c;但是有些线上服务器并不是使用的默认端口&#xff0c;那么这个时候&#xff0c;我们应该如何快速知道SSH使用的哪个端口呢&#xff1f; 1、通过配置文件查看 cat /etc/ssh/sshd_confi…

基于深度学习的猫狗识别系统【深度学习课设】

&#x1f3c6; 作者简介&#xff1a;席万里 ⚡ 个人网站&#xff1a;https://dahua.bloggo.chat/ ✍️ 一名后端开发小趴菜&#xff0c;同时略懂Vue与React前端技术&#xff0c;也了解一点微信小程序开发。 &#x1f37b; 对计算机充满兴趣&#xff0c;愿意并且希望学习更多的技…

Java常用 Date 时间格式化、Calender日历、正则表达式的用法

目录 1. SimpleDateFormat 日期格式化类 1.1 Date 类型转 String 1.2 String 类型转 Date 2. Calendar 日历类 3. 正则表达式 3.1 正则表达式的组成部分 3.2 手机号正则表达式 3.3 常用密码校验正则表达式 1. SimpleDateFormat 日期格式化类 SimpleDateFormat 是Java中…

MySQL其四,各种函数,以及模拟了炸裂函数创建用户等操作

目录 一、MySQL中的函数 1、IFNULL 2、IF 3、case &#xff08;难点&#xff09; 4、exists(难) --存在的意思 二、常见的函数 1、字符串函数 2、数学函数 3、日期函数 &#xff08;使用频率不是很高&#xff09; 4、其他函数 5、关于字符集的问题 6、mysql炸裂函数…

USB-TypeC接口设计

USB-TypeC介绍 一个全的TypeC接口一共有24个引脚,分别是A1~A12和B1~B12,并且是左右镜像对称支持正反插,TypeC向下兼容USB2.0不需要USB3.0的信号时,TypeC可以进一步简化成12pin如下图所示,因此TypeC的种类是比较多的。 USB3.2和USB4包括之后的USB的接口都是…

Dual-Write Problem 双写问题(微服务)

原文链接https://www.confluent.io/blog/dual-write-problem/ 双写问题发生于当两个外部系统必须以原子的方式更新时。 问题 说有人到银行存了一笔钱&#xff0c;触发 DepositFunds 命令&#xff0c;DepositFunds 命令被发送到Account microservice。 Account microservice需…

短信验证码burp姿势

首先声明&#xff0c;本文仅仅作为学习使用&#xff0c;因个人原因导致的后果&#xff0c;皆有个人承担&#xff0c;本人没有任何责任。 在之前的burp学习中&#xff0c;我们学习了图片验证码的突破&#xff0c;但是现实中还有很多短信验证码&#xff0c;在此我介绍几种短信验…

2024 X-GAME 上海智能新能源汽车大数据竞赛决赛顺利举行,和鲸Heywhale连续五年助推新能源汽车产业发展

11月22日&#xff0c;第七届 X-GAME 上海智能新能源汽车大数据竞赛圆满落幕&#xff01;这是和鲸作为协办方第五年为 X-GAME 新能源汽车分析赛道提供全程支持&#xff0c;赋能新能源汽车的产业发展。自 2018 年首次举办以来&#xff0c;以“数联万物&#xff0c;车载未来”为主…

Referer头部在网站反爬虫技术中的运用

网站数据的安全性和完整性至关重要。爬虫技术&#xff0c;虽然在数据收集和分析中发挥着重要作用&#xff0c;但也给网站管理员带来了挑战。为了保护网站数据不被恶意爬取&#xff0c;反爬虫技术应运而生。本文将探讨HTTP头部中的Referer字段在反爬虫技术中的应用&#xff0c;并…

【ubuntu】将Chroma配置为LINUX服务

Chroma是一个轻量级向量数据库。既然是数据库&#xff0c;那么我希望它是能够长时间运行。最直接的方式是配置为service服务。 可惜官方没有去提供配置为服务的办法&#xff0c;而鄙人对docker又不是特别感冒。所以自己研究了下chroma配置为服务的方式。 系统&#xff1a;ubu…

jenkins harbor安装

Harbor是一个企业级Docker镜像仓库‌。 文章目录 1. 什么是Docker私有仓库2. Docker有哪些私有仓库3. Harbor简介4. Harbor安装 1. 什么是Docker私有仓库 Docker私有仓库是用于存储和管理Docker镜像的私有存储库。Docker默认会有一个公共的仓库Docker Hub&#xff0c;而与Dock…

vscode 打开 setting.json

按下Ctrl Shift P&#xff08;Windows/Linux&#xff09;或Cmd Shift P&#xff08;Mac&#xff09;来打开命令面板。输入open settings&#xff0c;然后选择 Open User Settings(JSON)。打开settings.json文件 ------修改设置-----&#xff1a; 1、 html代码的行长度&am…

在conda终端运行Jupyter Notebook

文章目录 创建并激活环境安装并匹配ipykernel打开Jupyter Notebook 创建并激活环境 在Anaconda Prompt中输入conda create --name OpenAI来创建新的环境&#xff0c;其中OpenAI为新环境的名字 如果不需要创建新的环境&#xff0c;则直接激活已有环境。输入conda activate Open…

【机器学习算法】——决策树:CART

文章目录 理论sklearn 库实现完整代码 理论 CART全称叫Classification and Regression Tree&#xff0c;即分类与回归树。CART假设决策树是二叉树&#xff0c;内部结点特征的取值只有“是”和“否”&#xff0c;左分支是取值为“是”的分支&#xff0c;有分支则相反。这样的决…

Pytest-Bdd-Playwright 系列教程(15):背景(Background)

Pytest-Bdd-Playwright 系列教程&#xff08;15&#xff09;&#xff1a;背景&#xff08;Background&#xff09; 前言一、什么是背景&#xff08;Background&#xff09;二、特性文件三、测试脚本四、运行测试总结 前言 在测试的过程中&#xff0c;我们往往会遇到这样的问题&…

【精】Linux虚拟机 Docker 配置阿里云镜像加速

一、前言 1.1 拉取镜像报错 当 Docker 客户端拉取镜像时报错&#xff0c;类似如下&#xff1a; Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while 报错解释&#xff1a; 这个错误表明 Docker 客户端尝试与 …

计算机视觉与各个学科融合:探索新方向

目录 引言计算机视觉与其他学科的结合 与医学的结合与机械工程的结合与土木工程的结合与艺术与人文的结合发文的好处博雅知航的辅导服务 引言 计算机视觉作为人工智能领域的重要分支&#xff0c;正迅速发展并渗透到多个学科。通过与其他领域的结合&#xff0c;计算机视觉不仅…

vue3 使用 vue-ueditor-wrap 集成135以及秀米编辑器(亲测可用)!

1.先安装vue-ueditor-wrap富文本组件 # vue-ueditor-wrap v3 仅支持 Vue 3 npm i vue-ueditor-wrap3.x -S # or yarn add vue-ueditor-wrap3.x 2. 下载 UEditor UEditor 并不支持通过 npm 的方式来安装&#xff0c;vue-ueditor-wrap 也只是一个 Vue 组件&#xff0c;组件本身…