史上最烂 spring ioc 原理分析

盗引·上篇·spring ioc

spring ioc、di、BeanFactory 与 ApplicationContext、经典容器实现、Bean 的生命周期、Bean 的后置处理器、BeanFactory 的后置处理器、Aware 与 InitializingBean、初始化和销毁、scope 及失效、refresh()、三级缓存、@Value、@Autowired 解析等。


版本

  • jdk:8
  • spring:5.3.20
  • spring boot:2.7.0

1 spring ioc 与 di

1.1 spring ioc

  IOC (Inversion of control) 即控制反转,它不是某种技术,而是一种设计思想。其应用于面向对象编程,核心思想是控制对象的生命周期。

  在传统的面向对象编程中,对象的创建、初始化、销毁等是由该对象的使用者控制的。当某个对象会被多个对象使用时,则关于这个对象的创建、初始化、销毁等动作会频繁发生,且不易提前发现这些操作可中能会出现的异常,同时,这些操作也会过多占用程序员的精力,使其不能专注于业务代码。在 IOC 设计思想中,把对象的创建、初始化、销毁等操作交给 IOC 来完成,以节省资源开销和降低程序员对非业务代码的关注度。

  IOC 的特点:

  • 管理对象的生命周期,如创建、初始化、销毁等。
  • 维护对象间的依赖关系。
  • 解耦 “系统代码” 与业务代码。

  spring 框架中,ioc 模块是其基础模块,亦是其核心模块,贯穿整个 spring 家族。spring 通过容器来实现 ioc,所以也称作 spring ioc 容器。程序在启动时会初始化容器,容器初始化时会加载外部资源,以完成对象的创建,并通过解析编程式的类关系来完成对象间的依赖关系,且会控制对象的生命周期。spring ioc 容器的具现化实际上是一个 Map 集合,其 key 为对象名,value 为具体对象。

1.2 spring di

  DI (Dependency Injection) 即依赖注入,它也不是某种技术,而是一种设计思想。其应用于面向对象编程,核心思想是控制对象间的依赖关系。依赖注入分为两个阶段,第一个阶段是以编程的方式显示定义类之间的依赖关系(亦可理解为对象间的依赖关系),第二阶段则是注入,即根据显示定义的对象类型,给类属性设置其具体的值。

  spring 中依赖注入的实现方式:

  • 接口注入: 由于其灵活性和易用性较差,故 spring 4 开始已将其遗弃。
  • 构造器注入: 即通过类的有参构造方法来实现,方法的每一个参数则代表该类的对象所依赖的某个对象的类型。
  • Setter 方法注入: 即在对象实例化后,通过调用其 setter 方法来给其属性设置值。
  • 自动装配: 自动装配可以看做是依赖注入的升级版或简化版。
构造器注入Setter 方法注入
部分注入没有部分注入有部分注入
覆盖 setter 属性不会覆盖 setter 属性会覆盖 setter 属性
创建新实例任意修改都会创建一个新实例任意修改不会创建一个新实例
适用场景适用于很多属性适用于少量属性

  以上两种方式都可实现依赖注入,但 spring 建议使用构造器实现强制依赖注入,使用 setter 方法实现可选依赖注入。

  spring ioc 容器负责管理对象的生命周期,而在其创建对象时,会通过 di (依赖注入) 来完成对象间的依赖关系。

2 BeanFactory 与 ApplicationContext

2.1 依赖关系
  • BeanFactory:

    BeanFactory 为 spring ioc 中的最底层接口,亦是 spring ioc 的核心容器。提供了实际的控制反转、基本的依赖注入的实现,bean (对象) 的生命周期的各种功能,且维护了最终的成品 bean,以及根据名称、类型等条件获取 bean 的最基本功能。

    再简单点,可以将其理解为一个 map 集合,用来维护 bean,并提供了注册 (put) 和获取 (get) bean 的功能,可将之称之为 低级容器

  • ApplicationContext:

    ApplicationContext 为 BeanFactory 接口的派生接口,除了具有 BeanFactory 的功能外,还提供了更完整的框架功能。

    • 继承了 MessageSource,因此具有支持国际化。
    • 继承了 ResourceLoader,因此具有统一的资源文件访问方式,也可以理解为可以通过通配符匹配多个资源。
    • 继承了 ApplicationEventPublisher,因此具有事件发布和监听功能。
    • 继承了 EnvironmentCapable,因此具有处理 Environment 环境信息的功能。

    ApplicationContext 则可称之为 高级容器

2.2 加载方式
  • BeanFactory:

    BeanFactory 使用延迟加载,即只有当使用某个 bean 时(通过 getBean() 方法获取指定 bean 时)才会对该 bean 进行加载实例化。

    • 优点: 节省了程序启动时间及占用内存。
    • 缺点: 不能及时发现一些 bean 的配置及依赖注入问题。因为 bean 的加载实例化动作是在运行时发生的,而不是启动时。
  • ApplicationContext:

    ApplicationContext 使用即时加载,即在程序启动时(容器初始化时)就会完成 bean 大部分 bean 的加载实例化动作。

    • 优点: 节省了某个 bean 被使用时的加载实例化时间。可及时发现一些 bean 的配置及依赖注入问题。
    • 缺点: 当需要创建的 bean 过多时会增加了程序启动时间及占用内存。
2.3 创建方式
  • BeanFactory:

    BeanFactory 通常以编程的方式创建。

  • ApplicationContext:

    ApplicationContext 除了以编程的方式创建外,还能以声明的方式创建,如使用 ContextLoader。

2.4 注册方式

  BeanFactory 与 ApplicationContext 都支持 bean 后置处理器 (BPP) 和 bean factory 后置处理器 (BFPP) 的使用,但两者的区别是:

  • BeanFactory:

    手动注册,即需要手动将需要的后置处理器注册到容器中。

  • ApplicationContext:

    除了支持手动注册外,还支持自动注册。即在 ApplicationContext 的一些实现类中,自动帮我们注册了一些常用的后置处理器。

3 容器经典实现

3.1 容器经典实现
3.1.1 BeanFactory 实现

  BeanFactory 作为 spring ioc 最基本、最核心的接口,其有众多实现,分别代表 spring ioc 的不同功能。DefaultListableBeanFactory 是其最核心的实现。

  • BeanFactory:提供 spring ioc 容器最基础的功能,即 bean 相关的方法。如根据名称或类型获取 bean、判断其是否为单例、获取 bean 的类型、获取 bean 的提供器等。
  • ListableBeanFactory:提供枚举 bean 的相关功能。如根据类型查找 bean、根据类型查找 bean name 等。
  • AutowireCapableBeanFactory:提供创建 bean、属性填充(自动装配)、销毁 bean 的相关功能。
  • HierarchicalBeanFactory:提供父子容器相关的功能。如获取父容器、判断当前容器是否包含某个 bean 等。
  • ConfigurableBeanFactory:提供配置 bean 工厂的相关功能。如类加载器、SpEL 解析器、类型转换器、bean 依赖获取、容器启动等。
  • ConfigurableListableBeanFactory:提供获取和修改 bean 定义、预实例化单例 bean 等功能。
  • BeanDefinitionRegistry:即 bean 定义注册器,其非 BeanFactory 接口的子类。负责提供操作 bean 定义相关的功能。如注册、移除、获取 bean 定义等。
  • DefaultListableBeanFactory:BeanFactory 接口及其子接口的默认实现类,亦是 BeanFactory 最核心实现。
  • ApplicationContext:spring ioc 家族的另一大派系,其继承了 BeanFactory。
3.1.2 ApplicationContext 实现

  ApplicationContext 是 spring ioc 中另一顶级接口,其以组合的方式来继承 BeanFactory 容器的功能,同时又通过继承其它接口来拥有框架体系的功能,因此其是 spring 家族最常使用的容器。同样,其也有众多实现者,分别适用于不同的场景。AnnotationConfigApplicationContextAnnotationConfigServletWebServerApplicationContext 是其最核心的实现。

  • ApplicationContext:提供一些应用程序上下文相关的功能。如名称、获取父容器、获取 AutowireCapableBeanFactory 容器等。
  • ClassPathXmlApplicationContext:基于类路径 xml 配置文件的容器实现。
  • FileSystemXmlApplicationContext:基于磁盘路径 xml 配置文件的容器实现。
  • XmlWebApplicationContext:传统 ssm 整合时基于 xml 配置文件的容器实现。
  • AnnotationConfigWebApplicationContext:传统 ssm 整合时基于 java 配置的 web 环境的容器实现。
  • GenericApplicationContext:通用的简易容器实现,其组合了 DefaultListableBeanFactory,即持有一个DefaultListableBeanFactory 成员变量。
  • GenericWebApplicationContext:基于 web 环境的通用的简易容器实现。继承自 GenericApplicationContext,此外组合了 servlet web 相关的功能。
  • AnnotationConfigApplicationContext:基于 java 配置的非 web 环境的容器实现。
  • AnnotationConfigServletWebServerApplicationContext:spring boot 提供的基于 java 配置的 servlet web 环境的容器实现。
  • AnnotationConfigReactiveWebServerApplicationContext:spring boot 提供的基于 java 配置的 reactive web 环境的容器实现。
3.2 BeanFactory 容器核心实现

  这里写啥来着忘了…

3.2.1 DefaultListableBeanFactory

  DefaultListableBeanFactory 是 BeanFactory 最重要的实现,如控制反转、依赖注入等基础功能都是由该类实现。其类关系图如下所示:

DefaultListableBeanFactory

  DefaultListableBeanFactory 容器的核心属性 (包括其父类的核心属性):

  • beanDefinitionMap:Map<String, BeanDefinition> 类型,用来缓存 bean 定义信息。即通过 xml、java 注解等配置的 bean 类最终会被解析封装成 BeanDefinition 对象,然后存储在该集合中,留待将来创建 bean 时使用。key 为 bean name。
  • beanDefinitionNames:List< String> 类型,用来存储 bean name。即会按照 bean 定义信息注册的先后顺序存储 bean name。
  • resolvableDependencies:Map<Class<?>, Object> 类型,用来缓存依赖注入或自动装配时的默认候选者。即当某个类型的 bean 存在多个,在其被依赖注入或自动装配给其它 bean 时,spring 会因为不知道注入哪一个 bean 而报错,若将其中一个放入该集合,则 spring 会从该集合中取。key 为 bean 的类型,value 为该类型的某一个实例 bean。
  • autowireCandidateResolver:AutowireCandidateResolver 类型,自动装配候选者解析器,其主要用来处理自动装配相关。
  • parameterNameDiscoverer:ParameterNameDiscoverer 类型,参数名发现器,其主要用来获取方法的参数名。
  • beanExpressionResolver:BeanExpressionResolver 类型,bean SpEL 解析器,即主要用来解析用 spring 表达式配置的 bean。
  • typeConverter:TypeConverter 类型,类型转换器,主要用来进行必要的类型转换。
  • beanPostProcessors:List< BeanPostProcessor> 类型,用来缓存 BPP,以待将来在 bean 的生命周期过程中对 bean 进行增强处理。
  • factoryBeanObjectCache:Map<String, Object> 类型,用来缓存由那些工厂 bean(FactoryBean)创建的 bean。
  • signletonFactories:Map<String, ObjectFactory<?>> 类型,三级缓存。
  • earlySignletonObjects:Map<String, Object> 类型,二级缓存。
  • signletonObjects:Map<String, Object> 类型,一级缓存。

  DefaultListableBeanFactory 容器的核心方法 (包括其实现的核心方法):

  • DefaultListableBeanFactory#DefaultListableBeanFactory():构造方法,支持传入一个容器作为父容器。
  • BeanFactory#getBean():获取 bean。
  • BeanFactory#getBeanProvider():根据指定 bean 类型获取对象提供器(bean 提供器),通过其 getObject() 可获得对应 bean。
  • ListableBeanFactory#containsBeanDefinition():判断容器中是否包含某个 bean。
  • ListableBeanFactory#getBeanDefinitionNames():获得所有 bean name。
  • ListableBeanFactory#getBeanNamesForType():根据指定类型获取该类型对应的所有 bean name(分为指定的类类型或解析类型)。
  • ListableBeanFactory#getBeansForType():根据指定类型获取其对应的所有 bean。
  • ListableBeanFactory#getBeanNamesForAnnotation():根据指定注解查找被其标注的 bean 的 bean name。
  • ListableBeanFactory#findAnnotationOnBean():根据指定注解在指定 bean 上查找该注解。
  • ConfigurableListableBeanFactory#registerResolvableDependency():注册依赖的类型和其对应的 bean(在 spring 初始化时会用到此方法)。解决了当某个类型对应多个 bean 时不知道注入哪一个的情况。
  • ConfigurableListableBeanFactory#isAutowireCandidate():判断某个待注入的 bean 是否为指定 bean 所依赖的候选者。
  • ConfigurableListableBeanFactory#getBeanDefinition():根据指定 bean name 获取 bean definition。
  • BeanDefinitionRegistry#registerBeanDefinition():注册一个 bean definition。
  • BeanDefinitionRegistry#removeBeanDefinition():移除一个 bean definition。
  • AutowireCapableBeanFactory#createBean():创建 bean。
  • AutowireCapableBeanFactory#resolveDependency():解析依赖 解析通过 @Autowired 实现的依赖注入。

  DefaultListableBeanFactory 实现原理示例:

public class DefaultListableBeanFactorytest {
  
   	// 定义一个测试 bean one,通过 @Autowired 自动注入 bean two
   	static class OneBean {
        @Autowired
        public TwoBean twoBean;
        public OneBean() {
            System.out.println("invoke OneBean constructor");
        }
        public TwoBean getTwoBean() {
            return twoBean;
        }
    }

   	// 定义测试 bean two
    static class TwoBean {
        public TwoBean() {
            System.out.println("invoke TwoBean constructor");
        }
    }
    
  	// 通过 @Configuration 注解添加一个测试配置类
  	@Configuration
    static class TestConfig {
        @Bean
        public OneBean oneBean() {
            return new OneBean();
        }
        @Bean
        public TwoBean twoBean() {
            return new TwoBean();
        }
    }
  
  	// 通过测试方法查看 DefaultListableBeanFactory 的实现原理
    public static void main(String[] args) {

          // 定义 bean factory 即 bean 工厂 并查看容器注册的 bean
          DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
      		
      		// 通过打印结果会发现啥都没有
          for (String name : beanFactory.getBeanDefinitionNames()) {
              System.out.println(name);
          }

          // 定义 bean definition 即 bean 定义信息
          AbstractBeanDefinition testConfigBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition(TestConfig.class)
                  .setScope("singleton").getBeanDefinition();

          // 将定义的 bean definition 注册到 bean factory 中
          beanFactory.registerBeanDefinition("testConfig", testConfigBeanDefinition);

          // 手动注册 BPP 即手动向容器中添加 bean 的后置处理器
          AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);

          // 执行 BFPP(此处执行的 BPP 只作用于 bean factory 即 BFPP)
          // BFPP 的作用是扩展 bean factory(向容器中补充一些 bean)
         beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(beanFactoryPostProcessor -> {
              beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
          });

          for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values()) {
              System.out.println(beanFactoryPostProcessor);
          }

          // 添加 BPP(BPP 会在 bean factory 创建 bean 时执行)
          // BPP 的作用对 bean 生命周期的各个阶段提供扩展
       beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);

          System.out.println();
          for (BeanPostProcessor beanPostProcessor : beanFactory.getBeansOfType(BeanPostProcessor.class).values()) {
              System.out.println(beanPostProcessor);
          }

          System.out.println();
          // 打印 bean 工厂中的 bean name
          for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
              System.out.println(beanDefinitionName);
          }
          System.out.println();

          // bean factory 是懒加载的 但可以通过此方法主动初始化所有非懒加载的 bean
          beanFactory.preInstantiateSingletons();

          // 获取 oneBean 因为 BeanFactory 是懒加载 所以在 bean 被第一次使用时才会创建 bean
          // 故在调用 getBean 时才会执行 BPP 执行 BPP 时会进行依赖注入
          OneBean oneBean = beanFactory.getBean(OneBean.class);

          // BPP 中的 Autowired 和 Common 这两个 BPP 是专门用来解析 @Autowired 和 @Resource 这两个注解的
          // @Autowired 注解是先根据类型注入 若发现多个则根据属性名称注入
          // @Resource 注解可指定根据类型注入或根据名称注入 默认根据名称注入
          // 当 @Autowired 与 @Resource 注解作用于一处时 默认使用 Autowired BPP 解析
          // spring 为 BPP 初始化时设置了排序 对于作用于同一处的注解 排序在前的优先级高 优先级高的执行完后 优先级低的将不再执行

          System.out.println(oneBean.getTwoBean());
      }
}

  通过以上示例,我们可以验证 BeanFactory 容器实现的部分功能,如控制反转、依赖注入等,以及可以发现其使用延迟加载、编程式创建、手动注册等特点。

3.3 ApplicationContext 容器核心实现

  ApplicationContext 容器的特点是,其在组合了 BeanFactory 的功能的基础上,添加了更加完整的框架功能,如国际化、资源文件访问方式、事件发布监听、环境信息等。其类关系图如下:

ApplicationContext

  ApplicationContext 容器体系非常复杂,其除了以组合的方式收纳了 BeanFactory 的功能外,还通过继承接口拥有了其它框架性的功能。其虽复杂,但其核心类只有 spring 提供的 AnnotationConfigApplicationContext 实现和 spring boot 提供的 AnnotationConfigServletWebServerApplicationContext 实现。

3.3.1 ClassPathXmlApplicationContext

  ClassPathXmlApplicationContext 即基于类路径 xml 配置文件的容器实现。

<!-- 通过 xml 配置 bean -->
<!-- /Users/xgllhz/Documents/project/github/pure-desirous-wild/springcloud-demo" +
                        "/spring-demo/src/main/resources/bean/OneBean.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="oneBean" class="org.xgllhz.spring.ioc.bean_factory.ApplicationContextImplementerTest.OneBean">
        <property name="twoBean" ref="twoBean" />
    </bean>

    <bean id="twoBean" class="org.xgllhz.spring.ioc.bean_factory.ApplicationContextImplementerTest.TwoBean" />
</beans>
// ClassPathXmlApplicationContext 使用示例
public class ClassPathXmlApplicationContextTest {
  
  // 自定义 bean one 拥有 bean two 属性
  static class OneBean {
        private TwoBean twoBean;
        public OneBean() {
            System.out.println("invoke OneBean constructor");
        }
        public TwoBean getTwoBean() {
            return twoBean;
        }
        public void setTwoBean(TwoBean twoBean) {
            this.twoBean = twoBean;
        }
    }

  // 自定义 bean two
    static class TwoBean {
        public TwoBean() {
            System.out.println("invoke TwoBean constructor");
        }
    }
  
  public static void main(String[] args) throws IOException {
    // 可以通过其构造器穿入 xml 配置文件的类路径 该容器会解析 xml 配置且生成最终的 bean
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean/OneBean.xml");

        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }

        OneBean oneBean = context.getBean(OneBean.class);
        System.out.println(oneBean.getTwoBean());
  }
}
3.3.2 FileSystemXmlApplicationContext

  FileSystemXmlApplicationContext 即基于磁盘路径 xml 配置文件的容器实现。

// FileSystemXmlApplicationContext 使用示例
// 其中 OneBean、TwoBean、OneBean.xml 见 3.3.1 示例
public class FileSystemXmlApplicationContext {
  public static void main(String[] args) throws IOException {
    FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(
                "/Users/xgllhz/Documents/project/github/pure-desirous-wild/springcloud-demo" +
                        "/spring-demo/src/main/resources/OneBean.xml");

        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
        }

        OneBean oneBean = context.getBean(OneBean.class);
        System.out.println(oneBean.getTwoBean());
  }
}
3.3.3 XmlWebApplicationContext

  XmlWebApplicationContext 即传统 ssm 整合时基于 xml 配置文件的容器实现。

// 无非就是解析 xml 文件等等一系列操作...
3.3.4 AnnotationConfigWebApplicationContext

  AnnotationConfigWebApplicationContext 即传统 ssm 整合时基于 java 配置类的容器实现。

// 无非就是解析 java bean 配置注解等等一系列操作...
3.3.5 GenericApplicationContext

  GenericApplicationContext 即一个通用的简易容器实现。其主要的作用是组合了 DefaultListableBeanFactory。

3.3.6 AnnotationConfigApplicationContext

  AnnotationConfigApplicationContext 即基于 java 配置的非 web 环境的容器实现。其类关系图如下:

AnnotationConfigApplicationContext

  AnnotationConfigApplicationContext 容器的核心属性 (包括其父类的核心属性):

  • beanFactory:DefaultListableBeanFactory 类型,即 Beanfactory 容器,ApplicationContext 以组合的方式拥有了 BeanFactory 的功能。

  • scanner:ClassPathBeanDefinitionScanner 类型,即基于类路径的 bean 定义扫描器,其会将指定类路径下的 bean 定义扫描并解析成 BeanDefinition 对象,然后 通过 BeanDefinitionRegistry 注册到 BeanFactory 中。

  • reader:AnnotatedBeanDefinitionReader 类型,即基于编程式的 bean 定义读取器,其是 ClassPathBeanDefinitionScanner 的替代方案,可以用于显示的注册 bean 定义。

  • environment:ConfigurableEnvironment 类型,运行时的环境对象。

  • beanFactoryPostProcessors:List< BeanFactoryPostProcessor> 类型,缓存的 BFPP。在容器刷新时会执行这些 BFPP,用来补充一些 bean 定义。

  AnnotationConfigApplicationContext 容器的核心方法 (包括其实现的核心方法):

  • AnnotationConfigApplicationContext#AnnotationConfigApplicationContext():默认无参构造方法,会初始化 reader、scanner 等属性。
  • GenericApplicationContext#GenericApplicationContext():父类默认无参构造方法,会初始化 DefaultListableBeanFactory 属性。
  • AbstractApplicationContext#AbstractApplicationContext():父类默认无参构造方法,会初始化通配符资源解析器。
  • AnnotationConfigApplicationContext#register():利用 reader 来扫描并注册 BeanDefinition。
  • AnnotationConfigApplicationContext#scan():利用 scanner 来扫描并注册 BeanDefinition。
  • AbstractApplicationContext#refresh():刷新容器,spring ioc 中的最核心方法。其执行流程大致为:初始化配置、注册 BFPP、执行 BFPP、注册 BPP、初始化消息组件、初始化事件组件、初始化容器、初始化单例 bean、清楚缓存等。

  此外,该容器还会帮我们自动注册部分常用的 BFPP 和 BPP 等后置处理器。如:

  • ConfigurationClassPostProcessor:

    其本质是 BFPP,其作用是解析 @Configuration、@ComponentScan、@Bean、@Import、@ImportResource 等注解。

  • AutowiredAnnotationBeanPostProcessor:

    其本质是 BPP,其作用是解析 @Autowired、@Value 注解。

  • CommonAnnotationBeanPostProcessor:

    其本质是 BPP,其作用是解析 @Resource、@PostConstruct、@PreDestroy 等注解。

  BeanFactory 是被动的,需要开发者手动扫描注册 bean 定义,手动注册 BPP,手动执行 BFPP,手动初始化单例,手动刷新容器。ApplicationContext 则是主动的,它将扫描注册 bean 定义的组件作为自己的成员变量,并内置了一些常用的 BFPP 和 BPP,且会主动刷新容器,在刷新容器时会主动执行 BFPP 来外容器中补充一些 bean 定义,然后会主动初始化单例,初始化单例时会执行 BPP。由此可见,ApplicationContxt 像个组合怪,使得 spring ioc 容器更加强大和规范化。

// AnnotationConfigApplicationContext 使用示例
public class AnnotationConfigApplicationContextTest {
  
  // 用 @Configuration 添加一个测试配置类
  @Configuration
    static class TestConfig {
      // 使用 @Bean 注解注入一个 Bean1 bean 且通过方法参数方式注入 Bean2 bean
        @Bean
        public Bean1 bean1(Bean2 bean2) {
            Bean1 bean1 = new Bean1();
            bean1.setBean2(bean2);
            return bean1;
        }
      // 使用 @Bean 注入 Bean2
        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }

  // 定义 Bean1
    static class Bean1 {
        private Bean2 bean2;
        public Bean1() {
            System.out.println("invoke Bean1 constructor");
        }
        public Bean2 getBean2() {
            return bean2;
        }
        public void setBean2(Bean2 bean2) {
            this.bean2 = bean2;
        }
    }

  // 定义 bean2
    static class Bean2 {
        public Bean2() {
            System.out.println("invoke Bean2 constructor");
        }
    }
  
  public static void main(String[] args) {
    // 以构造参数传入配置类以此创建容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);

        for (String beanName : context.getBeanDefinitionNames()) {
            System.out.println(beanName);
            BeanDefinition beanDefinition = context.getBeanDefinition(beanName);
            System.out.println(beanDefinition.getBeanClassName());
        }

        Bean1 bean1 = context.getBean(Bean1.class);
        System.out.println(bean1.getBean2());
  }
}
3.3.7 AnnotationConfigServletWebServerApplicationContext

  AnnotationConfigServletWebServerApplicationContext 即基于 spring boot 的 servlet web 环境的容器实现。其类关系图如下:

AnnotationConfigServletWebServerApplicationContext

  AnnotationConfigServletWebServerApplicationContext 容器的实现可以看做是基于 AnnotationConfigApplicationContext 容器(二者并不是继承关系),于后者相比,前者继承了 ServletWebServerApplicationContext 容器,因此具有了 servlet web 相关的功能。其核心成员变量除了 AnnotationConfigApplicationContext 所拥有的之外,还有以下几种:

  • servletContext:ServletContext 类型。
  • webServer:WebServer 类型。
  • servletConfig:ServletConfig 类型。
// AnnotationConfigServletWebServerApplicationContext 使用示例
// spring boot 中的内嵌 tomcat 容器就是以以下方式实现的
// 区别在于 spring boot 使用的自动配置 而此处为了测试方便采用手动配置
public class AnnotationConfigServletWebServerApplicationContextTest {
  
  // 用 @Configuration 注解添加一个配置类
  @Configuration
    static class WebConfig {

      // 注入 ServletWebServerFactory bean(实际上注册的是 tomcat web factory bean)
      // 其会产生一个 tomcat 实例 并设置了端口号
      // 即内嵌 tomcat 容器
        @Bean
        public ServletWebServerFactory servletWebServerFactory() {
            return new TomcatServletWebServerFactory(8081);
        }

      // 注册 DispatcherServlet bean
        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }

      // 注册 DispatcherServletRegistrationBean bean
      // 该 bean 的作用是将 DispatcherServlet 实例注册到 servlet 上下文中
      // 即实现了 spring boot 与 spring web 的关联
        @Bean
        public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

      // 以实现 Controller 接口的方式实现一个测试控制器 并注册到容器中
      // 其 bean name 等于 @RequestMapping("/test") 注解的注解值
        @Bean("/test")
        public Controller test() {
            return (request, response) -> {
                response.getWriter().write("hello world");
                return null;
            };
        }
    }
  
  public static void main(String[] args) {
    // 执行主方法即可启动容器 亦可在浏览器测试 localhost:8081/test
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

4 Bean 生命周期

  spring ioc 容器最重要的作用就是管理 bean,这些管理涵盖了 bean 的整个生命周期。

4.1 Bean 生命周期

  spring ioc 容器中的一个 bean 的生命周期简单理解可分为以下几个阶段:

  • 实例化:实例化是指调用 bean 的构造方法或工厂方法创建 bean 实例对象。
  • 属性设置:属性设置是指通过 @Autowired、@Value 等手段为 bean 的属性设置值,建立关系,也可以理解为依赖注入。
  • 初始化:初始化是指调用 bean 的初始化方法。如被注解 @PostConstruct 标注的方法或实现 InitializingBean#afterPropertiesSet() 方法。
  • 使用:使用是指在程序内使用 spring ioc 容器中的 bean。服务客户。
  • 销毁:销毁是指调用 bean 的销毁方法。如被注解 @PreDestory 标注的方法或实现 DisposableBean#destory() 方法。
4.2 Bean 详细生命周期

  spring ioc 容器中的一个 bean 的详细生命周期可分为以下几个阶段:

  • 1、BPP 对 bean 实例化之前增强:若实现了 InstantiationAwareBeanPostProcessor BPP 的 postProcessBeforeInstantiation() 方法,则在 bean 实例化之前会调用其对 bean 进行实例化之前的增强。
  • 2、实例化:即实例化。
  • 3、BPP 对 bean 实例化之后增强:若实现了 InstantiationAwareBeanPostProcessor BPP 的 postProcessAfterInstantiation() 方法,则在 bean 实例化之后会调用其对 bean 进行实例化之后的增强。
  • 4、BPP 对 bean 属性设置时增强:若实现了 InstantiationAwareBeanPostProcessor BPP 的 postProcessProperties() 方法,则在 bean 属性设置时会调用其对 bean 进行属性设置时的增强。
  • 5、属性设置:即属性设置(依赖注入)。
  • 6、回调 BeanNameAware 接口:若 bean 实现了 BeanNameAware 接口,则会调用其 setBeanName() 方法,传入当前 bean 的 name 为参。
  • 7、回调 BeanFactoryAware 接口:若 bean 实现了 BeanFactoryAware 接口,则会调用其 setBeanFactory() 方法,传入当前容器持有的 BeanFactory 实例为参。
  • 8、回调ApplicationContextAware 接口:若 bean 实现了 ApplicationContextAware 接口,则会调用其 setApplicationContext() 方法,传入当前容器实例为参。
  • 9、BPP 对 bean 初始化之前增强:若实现了 BeanPostProcessor BPP 的 postProcessBeforeInitialization() 方法,则在 bean 初始化之前会调用其对 bean 进行初始化之前的增强。
  • 10、初始化:即初始化。
  • 11、BPP 对 bean 初始化之后增强:若实现了 BeanPostProcessor BPP 的 postProcessAfterInitialization() 方法,则在 bean 初始化之后会调用其对 bean 进行初始化之后的增强。
  • 12、BPP 对 bean 销毁之前增强:若实现了 DestructionAwareBeanPostProcessor BPP 的 postProcessBeforeDestruction() 方法,则在 bean 销毁之前会调用其对 bean 进行销毁之前的增强。
  • 13、销毁:即销毁。
  • 14、BPP 对 bean 销毁之后增强:若 实现了 DestructionAwareBeanPostProcessor BPP 的 postProcessBeforeDestruction() 方法,则在 bean 销毁之后会调用其对 bean 进行销毁之后的增强。
4.3 Bean 生命周期测试
// 用 @Component 注解配置一个 java bean
// 且让其实现 BeanNameAware、BeanFactoryAware、ApplicationContextAware 接口
@Component
public class ZedBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {

    public ZedBean() {
        System.out.println("2、zedBean 实例化");
    }

    @Autowired
    public void autowire(@Value("${java_home}") String javaHome) {
        System.out.println("5、zedBean 属性设置(依赖注入)");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("7、zedBean 回调 BeanNameAware 接口");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("8、zedBean 回调 BeanFactoryAware 接口");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("9、zedBean 回调 ApplicationContextAware 接口");
    }

    @PostConstruct
    public void init() {
        System.out.println("11、zedBean 初始化");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("13、zedBean 销毁");
    }
}
// 用 @Component 注解配置一个 ZedBean 的 BPP
// 通过实现各种 BPP 接口来增强 zedBean bean 生命周期的各个阶段
@Component
public class ZedPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

    private static final String ZED_BEAN_NAME = "zedBean";

    @Override   // 若返回 null 则原 bean 不变,若返回非 null 则会替换掉原来的 bean
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("1、BPP 对 zedBean 实例化之前增强");
        }
        return null;
    }

    @Override   // 若返回 false 则将终止 bean 的创建 即不会执行后续流程
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("4、BPP 对 zedBean 实例化之后增强");
        }
        return true;
    }
  
  @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("3、BPP 对 zedBean 实例化之后进行 bean 定义增强");
        }
    }

    @Override   // 若返回 null 则原 bean 不变,若返回非 null 则会替换掉原来的 bean
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("6、BPP 对 zedBean 属性设置时(依赖注入时)增强");
        }
        return null;
    }

    @Override   // 返回的对象会替换掉原来的 bean
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("10、BPP 对 zedBean 初始化之前增强");
        }
        return bean;
    }

    @Override   // 返回的对象会替换掉原来的 bean
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("12、BPP 对 zedBean 初始化之后增强");
        }
        return bean;
    }

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
      	if (ZED_BEAN_NAME.equals(beanName)) {
       	 		System.out.println("14、BPP 对 zedBean 销毁时执行");
      	}
    }
}
// 通过 AnnotationConfigApplicationContext 容器来测试
// 通过 @ComponentScan 注解来扫描上面定义的两个组件
// 前提条件是上面的两个组件和当前测试类则同一包下(或当前测试类所在包的子包下)
// 若没有在同一包下 则可以通过注解值来指定上面两个组件的所在包
@ComponentScan
public class BeanLifecycleTest {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanLifecycleTest.class);
        context.close();
    }
}
// 测试结果如下
1、BPP 对 zedBean 实例化之前增强
2、zedBean 实例化
3、BPP 对 zedBean 实例化之后进行 bean 定义增强
4、BPP 对 zedBean 实例化之后增强
5、zedBean 属性设置(依赖注入)
6、BPP 对 zedBean 属性设置时(依赖注入时)增强
7、zedBean 回调 BeanNameAware 接口
8、zedBean 回调 BeanFactoryAware 接口
9、zedBean 回调 ApplicationContextAware 接口
10、BPP 对 zedBean 初始化之前增强
11、zedBean 初始化
12、BPP 对 zedBean 初始化之后增强
13、zedBean 销毁
14、BPP 对 zedBean 销毁时执行

5 BeanFactory 后置处理器 (BFPP)

  BFPP 即 BeanFactoryPostProcessor,即 bean 工厂的后置处理器。其作用是对 bean 工厂做出一些功能扩展。常用的 BFPP 的类关系图如下:

BFPP

5.1 BFPP 简介
  • BeanFactoryPostProcessor

    BeanFactoryPostProcessor 即 bean 工厂的后置处理器,其是 spring 中 BFPP 的顶级接口,其被 @FunctionalInterface 注解标注,所以其是函数式接口。只提供一个方法,执行时机是在 BeanFactory 标准初始化之后,所有 bean 定义都被加载,但 bean 实例还未创建,作用通常是对已加载的 bean 定义信息作出修改等。

    @FunctionalInterface
    public interface BeanFactoryPostProcessor {
    	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
    }
    
  • BeanDefinitionRegistryPostProcessor

    BeanDefinitionRegistryPostProcessor 继承自 BeanFactoryProcessor 接口,其亦只提供一个方法,执行时机是 bean 定义信息被加载之前,作用是为容器补充一些 bean 定义信息。由此可以看出,BeanDefinitionRegistoryPostProcessor 的执行时机要优先于 BeanFactoryPostProcessor,即等所有配置的 bean 定义信息都被加载之后,再对 bean 定义信息作出必要的修改,然后再开始进行实例化 bean 等操作。

    public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
    }
    
  • ConfigurationClassPostProcessor

    ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,间接实现了 BeanFactoryPostProcessor 接口。其作用是解析 @Configuration、@ComponentScan、@Bean、@Import、@ImportResource 等注解。也就是该 BFPP 的作用是通过解析一些特定注解来为容器补充 bean 定义信息。

  • MapperScannerConfigurer

    MapperScannerConfigurer 是 mybatis 提供的 BFPP,其实现了 BeanDefinitionRegistryPostProcessor 接口,间接实现了 BeanFactoryPostProcessor 接口。其作用是解析 @MapperScan 注解。

5.2 关于 mapper 扫描

  关于 mybatis 或 mybatis plus 中的 mapper 扫描,有两种方式:

  • @Mapper

    即用 @Mapper 标注 mapper 接口。

  • @CompoentScan(“org.xgllhz.*.mapper”)

    即将 @ComponentScan 注解作用在启动类上,同时需要指定 mapper 接口所在的包名,如 org.xgllhz.*.mapper 包。需要注意的是,其扫描时只会扫描这个包下的接口类,会忽略非接口类。当 mapper 接口在不同的包下时,则可指定多个路径,如 @ComponentScan({“org.xgllhz. * .mapper”, “org.you. * .mapper”})。

5.3 模拟 BFPP 解析 @ComponentScan

  模拟 BFPP 解析 @ComponentScan。

  在组件目录下定义多个组件类。

// org.xgllhz.spring.ioc.five.component_test.component
@Component
public class Zed {}

// org.xgllhz.spring.ioc.five.component_test.component
@Component
public class Fizz {}

// org.xgllhz.spring.ioc.five.component_test.component.component
@Component
public class Ahri {}

  定义配置类:

// 扫描该目录
@ComponentScan("org.xgllhz.spring.ioc.five.component_test")
public class Config {}

  自定义 BFPP 解析 @ComponentScan:

public class CustomComponentPostProcessor implements BeanDefinitionRegistryPostProcessor {

    private static final String CLASSPATH_PREFIX = "classpath*:";

    private static final String CLASSPATH_SUFFIX = "/**/*.class";

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
          // 使用 spring 提供的工具类判断某个类上是否存在某个注解
          // 判断 Config 类上是否存在 ComponentScan 注解 若存在 则返回该注解
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan == null) {
                return;
            }

            String classpath;
            Resource[] resources;
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            CachingMetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
            MetadataReader metadataReader;
            AnnotationMetadata annotationMetadata;
            AnnotationBeanNameGenerator nameGenerator = new AnnotationBeanNameGenerator();

            for (String basePackage : componentScan.basePackages()) {
              // 将类路径替换成 classpath*:org/xgllhz/spring/ioc/five/component_test/**/*.class
              // . 的转义符为 \\.
                classpath = CLASSPATH_PREFIX + basePackage.replaceAll("\\.", "/") + CLASSPATH_SUFFIX;
              
              // 使用资源通配符解析器根据指定类路径解析资源
                resources = resolver.getResources(classpath);

                BeanDefinition beanDefinition;
                String beanName;
              // 遍历解析到的资源(即遍历多个 .class 文件)
                for (Resource resource : resources) {
                  // 使用元信息读取工厂从资源中读取类元数据
                    metadataReader = readerFactory.getMetadataReader(resource);
                  // 从类元数据中获取该类上的注解元数据
                    annotationMetadata = metadataReader.getAnnotationMetadata();

                  // 判断该类上的注解是否包含 @Component 注解或是否包含该注解的派生注解
                    if (annotationMetadata.hasAnnotation(Component.class.getName())
                            || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
                      // 根据该类的元数据生成 bean 定义
                        beanDefinition = BeanDefinitionBuilder
                                .genericBeanDefinition(metadataReader.getClassMetadata().getClassName())
                                .getBeanDefinition();
                      
                      // 使用 bean name 生成器生成 bean name
                        beanName = nameGenerator.generateBeanName(beanDefinition, registry);
                      // 将 bean 定义对象注册到容器中
                        registry.registerBeanDefinition(beanName, beanDefinition);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}

  测试:

public class BFPPTest {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

      // 注册 Config 配置类
        context.registerBean(Config.class);
      // 注册 自定义的 BFPP
        context.registerBean(CustomComponentPostProcessor.class);

      // 刷新容器
        context.refresh();

      // 查看容器中的 bean 定义信息 会发现通过 @Component 注解标注的 Zed、Fizz、Ahri 三个组件已被注册到容器中
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

}
5.4 模拟 BFPP 解析 @Bean

  模拟 BFPP 解析 @Bean 注解。

  定义配置类:

// 定义配置类 此处我们只模拟 BFPP 解析 @Bean 注解 所以省略了 @Configuration 注解
public class Config {

  // 定义 Zed bean 类 并持有 Fizz bean 成员变量
    static class Zed {
        private Fizz fizz;
        public Zed(Fizz fizz) {
            this.fizz = fizz;
        }
        public void setFizz(Fizz fizz) {
            this.fizz = fizz;
        }
        public Fizz getFizz() {
            return fizz;
        }
        @Override
        public String toString() {
            return "Zed{" +
                    "fizz=" + fizz +
                    '}';
        }
    }

  // 定义 Fizz bean 类
    static class Fizz {}

  // 通过 @Bean 注解注册 zed bean 并以方法入参方式注入 fizz bean 依赖
    @Bean
    public Zed zed(Fizz fizz) {
        return new Zed(fizz);
    }
  
  // 通过 @Bean 注解注册 fizz bean
    @Bean
    public Fizz fizz() {
        return new Fizz();
    }
}

  自定义 BFPP 解析 @Bean 注解:

// 因为 @Bean 注解的作用是生成 bean 所以我们实现 BeanDefinitionRegistryPostProcessor 接口
// BeanFactoryPostProcessor 接口的作用是修改 bean 定义信息
// BeanDefinitionRegistryPostProcessor 接口的作用是补充 bean 定义信息
public class CustomBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
          // 定义缓存元信息读取工厂
            CachingMetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
          // 使用工厂读取类元信息
            MetadataReader metadataReader =
                    readerFactory.getMetadataReader(new ClassPathResource("org/xgllhz/spring/ioc/five/test/Config.class"));

          // 从类元信息中根据指定注解获取被该注解标注的方法的元信息
          // 即从该类的元信息中取出被 @Bean 注解作用的方法的元信息
            Set<MethodMetadata> annotatedMethods = metadataReader.getAnnotationMetadata()
                    .getAnnotatedMethods(Bean.class.getName());

            BeanDefinitionBuilder builder;
            BeanDefinition beanDefinition;

          // 遍历所有被 @Bean 注解标注的方法的元信息
            for (MethodMetadata annotatedMethod : annotatedMethods) {
              // 通过 bean 定义构建器构建一个 bean 定义信息对象
                builder = BeanDefinitionBuilder.genericBeanDefinition();
              // 给构建器设置工厂方法 这里的工厂是指方法所在类的 bean
                builder.setFactoryMethodOnBean(annotatedMethod.getMethodName(), "config");
              // 设置自动注入模型 
              // 当被 @Bean 标注的方法存在入参时 需要指定 且默认值为 AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
              // 通过构建器构建完整的 bean 定义信息
                beanDefinition = builder.getBeanDefinition();
              // 将 bean 定义对象注册到容器中
                registry.registerBeanDefinition(annotatedMethod.getMethodName(), beanDefinition);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}

  测试:

public class BFPPTest {

    public static void main(String[] args) {
      // 创建一个空容器 这个容器中啥也没有 方便测试
        GenericApplicationContext context = new GenericApplicationContext();

      // 可以先刷新容器看下 会发现容器是空的
        /*context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }*/

      //将测试配置类注册到容器中 并设置 bean name 为 config
        context.registerBean("config", Config.class);
      // 将自定义的解析 @Bean 注解的 BFPP 注册到容器中
        context.registerBean(CustomBeanPostProcessor.class);

      // 刷新容器
        context.refresh();

      // 从容器中获取 Zed bean 会发现 zed bean 已存在
        Config.Zed zed = context.getBean(Config.Zed.class);
        System.out.println(zed);
    }
}
5.5 模拟 BFPP 解析 @MapperScan

  模拟 BFPP 解析 @MapperScan 注解。

  在 mapper 包下创建多个 mapper 接口(可包含非接口类,方便测试):

// zed mapper
public interface ZedMapper {}

// fizz mapper
public interface FizzMapper {}

// ahri 普通类
public class AhriMapper {}

  自定义 BFPP 解析 @MapperScan 注解:

public class CustomMapperScanPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        try {
            // 模拟 @MapperScan 注解所扫描的路径 即该注解所扫描的包
            String classpath = "classpath*:org/xgllhz/spring/ioc/five/mapper_test/mapper/**/*.class";

          // 资源通配符解析器
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
          // 缓存元数据读取工厂
            CachingMetadataReaderFactory readerFactory = new CachingMetadataReaderFactory();
          // 基于注解的 bean name 生成器
            AnnotationBeanNameGenerator nameGenerator = new AnnotationBeanNameGenerator();

          // 从 mapper 包下读取所有 .class 资源
            Resource[] resources = resolver.getResources(classpath);
            MetadataReader metadataReader;
            ClassMetadata classMetadata;
            BeanDefinition beanDefinition;
            BeanDefinition beanDefinitionName;
            String beanName;
          
          // 遍历所有 .class 资源
            for (Resource resource : resources) {
              // 从工厂中获取元数据读取器
                metadataReader = readerFactory.getMetadataReader(resource);
              // 从读取器中读取类元数据
                classMetadata = metadataReader.getClassMetadata();
              // 判断类是否为接口
                if (classMetadata.isInterface()) {
                  // 若是 则生成 bean 定义
                  // 对于 mapper 接口 此处生成的是 MapperFactoryBean 的 bean 定义
                  // 当获取某个 mapper bean 时 将通过 工厂 bean 的 getObject() 方法获得
                    beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
                            .addConstructorArgValue(classMetadata.getClassName())
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR)
                            .getBeanDefinition();

                  // 根据类名生成 bean 定义 该 bean 定义的作用单纯只是为了生成 bean name
                  // 因为 mybatis 模式下所有 mapper 接口对应的 bean 都为 MapperFactoryBean 
                  // 若不重新生成 bean name 则后面注册的 bean 定义会覆盖前面注册的 
                    beanDefinitionName = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName())
                            .getBeanDefinition();

                  // 根据 bean name 定义信息生成 bean name
                    beanName = nameGenerator.generateBeanName(beanDefinitionName, registry);

                  // 将 bean 定义注册到容器中
                    registry.registerBeanDefinition(beanName, beanDefinition);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
}

  测试:

@MapperScan   // 扫描 mapper
@ComponentScan   // 加该注解的作用是为了扫描当前类中的配置类 MapperConfig
public class BFPPTest {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

      // 注册 5.4 示例中的 Config 配置 在其中通过 @Bean 配置 SqlSessionFactoryBean 和 DataSource
        context.registerBean("config", Config.class);
        context.registerBean(CustomBeanPostProcessor.class);
        context.registerBean(CustomMapperScanPostProcessor.class);

        context.refresh();

      // 通过结果可发现 mapper 目录下的 Zed、Fizz 接口被扫描到了 而非接口的 Ahri 类没有被扫描
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

6 Bean 后置处理器 (BPP)

  BPP 即 BeanPostProcessor,即 bean 后置处理器,其作用是在 bean 的生命周期中对 bean 做出一些扩展。常见的 BPP 的类关系图如下:

BFPP

6.1 BPP 简介
  • BeanPostProcessor :

    BeanPostProcessor 即 bean 的后置处理器接口,其是 spring 中所有 BPP 的顶级接口,其主要作用是对 bean 的初始化作出增强。也可以理解为是在 bean 生命周期中的不同阶段对 bean 进行增强。其提供了两个方法,分别是:

    public interface BeanPostProcessor {
      
      // 初始化前增强 即在 bean 调用初始化方法之前对 bean 进行增强
      @Nullable
    	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
      
      // 初始化后增强 即在 bean 调用初始化方法之后对 bean 进行增强
    	@Nullable
    	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		return bean;
    	}
    }
    
  • MergedBeanDefinitionPostProcessor :

    MergedBeanDefinitionPostProcessor 是 spring BPP 中的另一接口,其继承自 BeanPostProcessor 接口,其主要作用是对 bean 定义最初增强。也可以理解为是在 bean 实例化之后,属性设置之前对 bean 进行增强,主要增强内容是补充一些 bean 属性所对应类的 bean 定义(如 @Autowired、@Value 注解所标注属性所对应的属性的 bean 定义)。

    public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {
      // 对指定 bean name 的 bean 定义补充指定属性类型的 bean 定义
      void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
    }
    
  • InstantiationAwareBeanPostProcessor :

    InstantiationAwareBeanPostProcessor 是 spring BPP 中另一接口,其继承自 BeanPostProcessor 接口,其主要作用是对 bean 实例化前后进行增强。

    public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
      
      // 实例化前增强 即在实例化 bean 之前增强
      @Nullable
    	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    		return null;
    	}
      
      // 实例化后增强 即在实例化 bean 之后对其增强
      default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    		return true;
    	}
      
      // 属性设置时增强 即在对 bean 进行属性设置(依赖注入)时对其进行增强
      @Nullable
    	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
    			throws BeansException {
    		return null;
    	}
    }
    
  • DestructionAwareBeanPostProcessor :

    DestructionAwareBeanPostProcessor 是 spring BPP 中的另一接口,其继承自 BeanPostProcessor 接口,其作用是对 bean 销毁时作出增强。亦可理解为是在调用 bean 的销毁方法时对 bean 增强进行增强。

    public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {
      // 销毁之前增强 即调用 bean 的销毁方法之前的增强
      void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
    }
    
  • AutowiredAnnotationBeanPostProcessor :

    AutowiredAnnotationBeanPostProcessor 是 spring BPP 的重要实现类,其主要实现了 InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor BPP 接口,其主要作用是解析 @Autowired、@Value 注解,即其主要作用是实现了 BPP 对 bean 属性设置(依赖注入)时的增强。

    public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
    		MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
        
          // 在构造函数中指定了自动注入的注解类型
          public AutowiredAnnotationBeanPostProcessor() {
            this.autowiredAnnotationTypes.add(Autowired.class);
    				this.autowiredAnnotationTypes.add(Value.class);   /*...*/
          }
          
          // 在实例化之后 属性设置前先增强 bean 的定义
          @Override
    			public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {...}
    
          // 推断候选者构造器
          // 即在属性设置(依赖注入)时 推断要注入的类型的候选者的构造器
          @Override
    			@Nullable
    			public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
    			throws BeanCreationException {...}
          
          // 属性设置时的增强 主要解析 @Autowired
    			@Override
    			public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {...}
    }
    
  • CommonAnnotationBeanPostProcessor :

    CommonAnnotationBeanPostProcessor 是 spring BPP 的另一重要实现类,其主要实现了 InstantiationAwareBeanPostProcessor、DestructionAwareBeanPostProcessor 接口,其主要作用是解析 @Resource、@PostConstruct、@PreDestroy 注解,即其主要作用是实现了 bean 初始化时和销毁时的增强。

    public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
    		implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
      
      // 通过静态代码块设置了资源注解类型 即 @Resource
      static {
        resourceAnnotationTypes.add(Resource.class);
      }
      
      // 通过构造函数设置了初始化注解类型和销毁注解类型 即 @PostConstruct 和 @PreDestroy
      public CommonAnnotationBeanPostProcessor() {
        setInitAnnotationType(PostConstruct.class);
    		setDestroyAnnotationType(PreDestroy.class);
      }
      
      // 属性设置时的增强 主要解析 @Resource
      @Override
    	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {...}
      
      // 另 其对初始化和销毁时的增强是由其继承的父类 InitDestroyAnnotationBeanPostProcessor 实现的
    }
    
  • ConfigurationPropertiesBindingPostProcessor :

    ConfigurationPropertiesBindingPostProcessor 是 sping BPP 的另一重要实现,其直接实现了 BeanPostProcessor 接口,其主要作用是解析 @ConfigurationProperties 注解,即其主要作用是实现了 bean 初始化之前的属性填充相关的增强。

    public class ConfigurationPropertiesBindingPostProcessor
    		implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean {
      
      // 其持有的配置属性绑定器 
      // 主要用来给 bean 绑定自 @ConfigurationProperties 注解所指定的配置值
      private ConfigurationPropertiesBinder binder;
      
      // 实现的 bean 初始化之前的增强 内部调用 binder.bind() 方法来绑定属性
      @Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {...}
    }
    
6.2 BPP 使用示例

  以下示例将演示 BPP 对 bean 生命周期的各个阶段的增强。

  定义相关 bean:

@Component
public class Fizz {}

@Component
public class Ahri {}

@Component
public class ZedBean {
    private Fizz fizz;
    private Ahri ahri;

    public ZedBean() { System.out.println("zedBean 实例化"); }

    @Autowired
    public void setFizz(Fizz fizz) { this.fizz = fizz; }

    @Resource
    public void setAhri(Ahri ahri) { this.ahri = ahri; }

    @PostConstruct
    public void init() { System.out.println("zedBean 初始化"); }

    @PreDestroy
    public void destroy() { System.out.println("zedBean 销毁"); }
}

  自定义 zedBean 的 BPP:

@Component
public class ZedPostProcessor implements MergedBeanDefinitionPostProcessor, InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

    private static final String ZED_BEAN_NAME = "zedBean";

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("BPP 对 zedBean 实例化之前增强");
        }
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("BPP 对 zedBean 实例化之后增强");
        }
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("BPP 对 zedBean 属性设置时(依赖注入时)增强");
        }
        return null;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("BPP 对 zedBean 初始化之前增强");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (ZED_BEAN_NAME.equals(beanName)) {
            System.out.println("BPP 对 zedBean 初始化之后增强");
        }
        return bean;
    }

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        System.out.println("BPP 对 zedBean 销毁时执行 " + beanName);
    }

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        System.out.println("BPP 对 zedBean 实例化之后进行 bean 定义增强 " + beanName);
    }
}

  测试:

@ComponentScan
public class BPPTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BPPTest.class);
        context.close();
    }
}

  测试结果:

BPP 对 zedBean 实例化之前增强
zedBean 实例化
BPP 对 zedBean 实例化之后进行 bean 定义增强 zedBean
BPP 对 zedBean 实例化之后增强
BPP 对 zedBean 属性设置时(依赖注入时)增强
zedBean 初始化
BPP 对 zedBean 初始化之前增强
BPP 对 zedBean 初始化之后增强
zedBean 销毁
BPP 对 zedBean 销毁时执行 zedBean

7 初始化和销毁

7.1 初始化

  spring 提供了多种对象的初始化手段,分别是 @PostConstruct、InitializingBean、@Bean(initMethod),这些初始化方式可同时存在,同时出现时其执行顺序如下:

  • @PostConstruct:即被该注解标注的方法将作为初始化方法,且其将被第一个执行。
  • InitializingBean:即当类实现了该接口时,其 afterPropertiesSet() 方法将为初始化方法,且其将被第二个执行。
  • @Bean(initMethod):即通过 @Bean 注解的 initMethod 参数指定的方法将被当作初始化方法,且其将被第三个执行。
7.2 销毁

  同样的,spring 也提供了对中对象的销毁手段,分别是 @PreDestroy、DisposableBean、@Bean(destroyMethod),这些销毁方式可同时存在,同时存在时其执行顺序如下:

  • @PreDestroy:即被该注解标注的方法将作为销毁方法,且其将被第一个执行。
  • DisposableBean:即当类实现了该接口时,其 destroy() 方法将作为初始化方法,且其将被第二个执行。
  • @Bean(destroyMethod):即通过 @Bean 注解的 destroyMethod 参数指定的方法将被当作初始化方法,且其将被第三个执行。
7.3 示例

  以下示例演示了上述三种初始化与销毁手段的使用方式和各自的执行顺序。

// 定义 bean 类 并实现 InitializingBean、DisposableBean 接口
public class Zed implements InitializingBean, DisposableBean {

  // 三种初始化方式实现
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct 初始化");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean 初始化");
    }

    public void initMethod() {
        System.out.println("bean(initMethod) 初始化");
    }

  // 三种销毁方式实现
    @PreDestroy
    public void preDestroy() {
        System.out.println("@PreDestroy 销毁");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean 销毁");
    }

    public void destroyMethod() {
        System.out.println("bean(destroyMethod) 销毁");
    }
}
// 定义一个配置类 以 @Bean 的方式向容器中注入一个 Zed bean
public class Config {

  // 以 @Bean 的方式向容器中注入一个 Zed bean 并通过 initMethod 与 destroyMethod 参数来指定 bean 的销毁方法
    @Bean(initMethod = "initMethod", destroyMethod = "destroyMethod")
    public Zed zed() {
        return new Zed();
    }
}
// 测试
public class InitAndDestroyTest {

    public static void main(String[] args) {
      // 创建一个空容器
        GenericApplicationContext context = new GenericApplicationContext();
      // 将配置类注册到容器中
        context.registerBean("config", Config.class);
      // 注册 ConfigurationClassPostProcessor BFPP 该 BFPP 用来解析 @Bean 注解
        context.registerBean(ConfigurationClassPostProcessor.class);
      // 注册 CommonAnnotationBeanPostProcessor BPP 该 BPP 用来解析 @PostConstruct、@PreDestroy 注解
        context.registerBean(CommonAnnotationBeanPostProcessor.class);

        context.refresh();   // 刷新容器
        context.close();   // 关闭容器

    }
}
// 测试结果
@PostConstruct 初始化
InitializingBean 初始化
bean(initMethod) 初始化
@PreDestroy 销毁
DisposableBean 销毁
bean(destroyMethod) 销毁

8 Aware 与 InitializingBean

8.1 Aware

  Aware 接口是 spring 提供的内置的与自动注入相关的接口,常用的有:

  • BeanNameAware: 其作用是注入当前 bean 的名字。
  • BeanFactoryAware: 其作用注入当前容器所持有的 bean factory 实例。
  • ApplicationContextAware: 其作用是注入当前容器 ApplicationContext 实例。
  • EmbeddedValueResolverAware: 其作用是注入 ${} 表达式解析器。(常用的解析器是 PlaceholderResolvingStringValueResolver)。
8.2 Aware 使用示例
public class OneBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {

    @Override
    public void setBeanName(String name) {
        System.out.println("当前 beanName = " + name);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("当前容器 beanFactory = " + beanFactory);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("当前容器 applicationContext = " + applicationContext);
    }
}
8.3 内置功能与扩展功能

  spring 内置功能可以理解为是不需要外部组件就可实现的功能,同理,扩展功能则可理解为需要外部组件则可实现的功能。上述的 Aware 接口,以及上上述的 InitializingBean、DisposableBean 接口等都属于内置功能,即其只需要实现相应接口就可获得其对应功能。而对于 @PostConstruct、@PreDestroy、@Autowired、@value 等注解提供的功能则可理解为扩展功能,因为其实现是基于对应 BPP 的,即其需要 BPP 来解析。spring 内置功能相对于扩展功能来说,其优点是在某些场景下扩展功能可能会失效,而内置功能不会。因此,spring 框架内部的类通常使用内置功能来完成相关操作。

  以下示例演示了扩展功能失效的场景:

// 定义一个配置类 实现 @Autowired 自动注入 @PostConstruct 初始化方法 以及通过 @Bean 注册 BFPP bean
public class OneConfig {

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        lSystem.out.println("使用 @Autowired 注入 bean");
    }

    @PostConstruct
    public void init() {
        System.out.println("使用 @PostConstruct 实现初始化方法");
    }

    @Bean
    public BeanFactoryPostProcessor onePostProcessor() {
        return beanFactory -> {
            System.out.println("注入 BFPP bean");
        };
    }
}
public class AwareTest {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();

        // 注册 OneConfig bean
        context.registerBean("oneConfig", OneConfig.class);

        // 注册 ConfigurationClassPostProcessor BFPP 其作用是解析 @Bean
        context.registerBean(ConfigurationClassPostProcessor.class);
      
      // 注册 BPP 其作用分别是解析 @Autowired 注解和 @PostConstruct 注解
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        context.registerBean(CommonAnnotationBeanPostProcessor.class);

        context.refresh();
    }
}
// 测试结果
注入 BFPP bean

  通过测试结果发现,当在 bean 配置类中通过 @Bean 注册 BFPP 时,会导致 @Autowired、@PostConstruct 注解失效。这和 spring ioc 的执行流程有关。在 spring ioc 刷新时,即执行 refresh() 方法时,其会先执行所有的 BFPP,然后注册 BPP,最后再进行 bean 的实例化、属性设置、初始化等操作。所以在上述实例中,由于容器要先注册 BFPP(即实例化 BFPP,即执行 onePostProcessor 方法),但该 BFPP 的注册又依赖于 OneConfig 实例,所以此时就先创建了 OneConfig 实例,但由于此时还为注册 BPP,所以就导致 @Autowired 等注解没有被解析。这也是 @Autowired 注解失效的原因。

  这也从侧面证明了 spring 扩展功能有可能会失效。而像 Aware、InitializingBean 等这些内置功能则不会失效,是因为在 spring ioc 容器的刷新流程中,在 bean 初始化之后一定会对这些内置接口进行回调,所以其所实现的功能不会失效。

9 scope 及失效

9.1 bean 的域

  spring ioc 中的 bean 有五种作用域,分别是 singleton、prototype、request、session、application,其含义及特点如下:

  • singleton:

    即单例域,第一次调用 getBean 时创建,容器关闭时销毁。即容器中该类型的 bean 只会存在一个,首次创建后,后续获取时获取到的都是同一个 bean。

  • prototype:

    即多例域,每次调用 getBean 时都会创建一个新的 bean,容器不负责销毁,需要使用 DefaultListableBeanFactory.destroyBean() 销毁。

  • request:

    即请求域,每次 http 请求创建时创建,请求结束时销毁。

  • session:

    即会话域,每次 http 会话建立时创建,会话结束或 session 超时时销毁。

  • application:

    即应用域,容器创建时创建,容器销毁时销毁。

9.2 域失效

  域失效是指当在一个单例域的 bean 中注入其它域的 bean 时,其它域的作用将会失效。如在一个单例的 bean Zed 中依赖注入多例的 bean Fizz,这时,注入到 Zed 中的 Fizz 实际上是单例的,而非多例。这是因为单例域 bean 的依赖注入只会执行一次,即在创建时执行,执行后将不再执行,这就导致注入到单例域 bean 中的其它域的 bean 始终时第一次依赖注入时创建的。

  可以通过以下四种方式来解决域失效问题,虽然解决方式不同,但在思想上都殊途同归,都是推迟了其它域的 bean 的获取,也可理解为懒加载思想。

  • @Lazy:

    即使用 @Lazy 注解。其原理是懒加载思想,即只有当依赖当前 bean 的 bean 实际使用当前 bean 时才创建。

  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS):

    即在使用 @Scope 指定 bean 的域时,设置 proxyMode 参数的值为 ScopedProxyMode.TARGET_CLASS。其原理是代理思想,即依赖注入时注入的是当前 bean 的代理对象,当调用当前 bean 的任意方法时其代理对象才会创建当前 bean。

  • ObjectFactory<>:

    即使用工厂 bean 包装被依赖注入的 bean。其原理是在依赖注入时注入的是工厂 bean,而非实际被依赖的 bean,在实际使用时将有工厂 bean 的 getObject 方法创建并返回依赖 bean。

  • ioc:

    即使用容器。其原理是注入容器实例,当需要某个 bean 时直接从容器中取。

10 FactoryBean

10.1 FactoryBean

  FactoryBean 是 spring 提供的工厂 bean,其作用是用来创建一个 bean,且其本身也会被当作一个 bean 注册到容器中。FactoryBean 一般被用来创建一些复杂的 bean,如 SqlSessionFactory。其创建的 bean 没有被存储在 BeanFactory 的 singletonObjects 单例池中,而是存储在 FactoryBeanRegistrySupport 的 factoryBeanObjectCache 缓存中。其创建的 bean,当调用 getBean 获取这个 bean 时,内部会使用注册到容器中的工厂 bean 的 getObject 方法,这个方法会创建并返回 bean,且可通过 getBean(“&beanName”) 方式从容器获取到工厂 bean。

  由 FactoryBean 创建的 bean,spring ioc 会认为 bean 生命周期中的 实例化、属性设置 (依赖注入)、Aware 接口回调、初始化前置增强、初始化、销毁 这些阶段都属于 FactoryBean 的责任,故 spring ioc 不会执行这些阶段的操作,其只负责执行初始化后置增强。但实际上,FactoryBean 在创建 bean 的过程中只会执行 bean 的实例化、初始化前置增强这两个阶段,其余的 属性设置 (依赖注入)、Aware 接口回调、初始化、销毁 这些阶段它也不会执行。

  所以,由 FactoryBean 创建的 bean,其属性设置、Aware 接口回调、初始化、销毁这些阶段不会被执行。且由于 spring 只执行 bean 的初始化后置增强,所以 FactoryBean 常被用来为 bean 创建代理。

  FactoryBean 拥有的功能 @Bean 注解同样拥有,且比其更丰富,故能用 @Bean 就不用 FactoryBean。

10.2 BeanFactory 与 FactoryBean

  此时,BeanFactory 与 FactoryBean 的区别就一目了然了,即 BeanFactory 是负责存储管理 bean 的 spring ioc 容器,而 FactoryBean 只是一个负责创建 bean 的工厂。

11 refresh()

11.1 refresh() 简介

  ConfigurableApplicationContext 接口中声明的 refresh() 是整个 spring ioc 容器的核心,也是 java 发烧友的必学密典之一。其最核心的实现由 AbstractApplicationContext 抽象子类实现,其基本实现了 spring ioc 容器从准备、创建、bean 的创建、装配、生命周期管理等最重要的内容。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
      
        prepareRefresh();   // 1
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();   // 2
        prepareBeanFactory(beanFactory);   // 3

        try {
            postProcessBeanFactory(beanFactory);   // 4
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
            invokeBeanFactoryPostProcessors(beanFactory);   // 5
            registerBeanPostProcessors(beanFactory);   // 6
            beanPostProcess.end();
            initMessageSource();   // 7
            initApplicationEventMulticaster();   // 8
            onRefresh();   // 9
            registerListeners();   // 10
            finishBeanFactoryInitialization(beanFactory);   // 11
            finishRefresh();   // 12
        }

        catch (BeansException ex) {
            destroyBeans();   // 当刷新过程(即容器启动过程)中抛出异常时销毁 bean 且清除启动过程中产生的缓存
            cancelRefresh(ex);
            throw ex;
        }

        finally {
            resetCommonCaches();   // 清除刷新额外的不需要的缓存 如 bean definition map 缓存等
            contextRefresh.end();
        }
    }
}

  refresh() 方法执行可分为十二步:

  • 1、prepareRefresh() 刷新前的预处理

    即刷新前的预处理,初始化上下文环境,如 缓存 java 环境变量的 systemProperties,缓存系统环境变量的 systemEnvironment,以及缓存配置文件的 propertySource。

  • 2、obtainFreshBeanFactory() 创建 BeanFactory 容器

    即创建 BeanFactory 容器,即实例化 BeanFactory 容器。

  • 3、prepareBeanFactory() 预处理容器

    即预处理容器,设置类加载器;设置 SpEL 解析器(默认选择 StandardBeanExpressionResolver 实现);设置类型转换器(默认选择 PropertyEditorRegistrar 实现);注册特殊 bean 到 resolvableDependencies 缓存中(如 BeanFactory、ApplicationContext 等);注册特殊 BPP 到 beanPostProcessors 缓存中(如 ApplicationContextAwareProcessor(用来解析 Aware 接口)、ApplicationListenerDetector(用来发现事件监听器));注册一些单例 bean(如 environment、systemProperties、systemEnvironment 等)。

  • 4、postProcessBeanFactory() 后置处理容器

    即后置处理容器,此处留给子类实现以对容器进行扩展。

  • 5、invokeBeanFactoryPostProcessor() 调用 BFPP

    即调用 BFPP,通过调用缓存在 beanFactoryPostProcessors 集合中的 BFPP,来补充容器中的 bean 定义(如 @Component 即其派生注解所标注组件、@Mapper 标注的 mapper 接口等 bean 定义都是在此处被解析并加到 beanDefinitionMap 缓存中的)。

  • 6、registerBeanPostProcessor() 注册 BPP

    即注册 BPP,从 beanDefinitionMap 缓存中找到所有 BPP 定义,然后创建 BPP bean 并将其保存到 beanPostProcessors 缓存中。

  • 7、initMessageSource() 初始化消息源

    即初始化消息源,即初始化国际化功能,先判断容器中是否存在,若没有则创建一个并注册到容器中。

  • 8、initApplicationEventMulticaster() 初始化事件派发器

    即初始化事件派发器,先判断容器中是否存在,若没有则创建一个并注册到容器中。

  • 9、onRefresh() 自定义刷新

    即自定义刷新,此处留给子类实现以对容器刷新操作进行扩展,如 spring boot 中在此时初始化了 web 容器(即内嵌 tomcat 容器)。

  • 10、registerListeners() 注册监听器

    即注册监听器,并绑定事件派发器;同时发布早期事件。

  • 11、finishBeanFactoryInitialization() 完成剩余非懒加载的单例 bean 的初始化

    即完成剩余非懒加载的单例 bean 的初始化,通过 beanDefinitionMap 缓存中的 bean 定义信息创建单例 bean,并在创建 bean 的过程中调用 BPP 来对 bean 进行增强。

  • 12、finishRefresh() 完成容器刷新

    即完成容器刷新,此时会清除额外缓存;初始化容器生命周期;发布容器已刷新事件。

11.2 refresh() 时序图
// 404 啦
11.3 三级缓存
11.3.1 三级缓存的定义

  spring 三级缓存是指在 sping ioc 容器中设计了三个不同的用途的 map 集合,来缓存 sping ioc 容器启动和工作过程中的产生的数据。因为这三个缓存在使用当中存在层层递进的关系,所以被称为三级缓存。

  三级缓存被定义在 DefaultSingletonBeanRegistry 类中,其分别是 singletonObjectsearlySingletonObjectssingletonFactories,具体定义如下:

// 一级缓存 key 为 bean name value 为 bean 对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存 key 为 bean name value 为 bean 对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存 key 为 bean name value 为 bean 工厂对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  • singletonObjects

    即一级缓存,用来缓存通过 spring ioc 容器创建的单例 bean。即我们在程序中自定义的组件以及程序框架或第三方组件所定义的 bean 在经过 spring ioc 加载创建后最终都会缓存在这个集合中,当在项目中需要用到某个 bean 时,就会从这个集合中获取。

  • earlySingletonObjects

    即二级缓存,亦可称之为早期缓存,用来缓存早期的 bean 对象。这里的早期需要从 spring bean 生命周期的角度去理解,spring bean 的生命周期从大的方面可以分为 实例化、属性设置、初始化、使用、销毁 五个阶段,而早期的 bean 是指已经实例化但还未进行属性设置的 bean。因此早期的 bean 也可理解为不成熟的 bean,即刚 new 出来,还没进行属性设置(即自动装配或注入),还不用能投入使用,需经过后续的属性设置、初始化加工后才能蜕变为一个成熟的 bean。需要注意的是,若该 bean 需要被代理,则二级缓存中缓存的是该 bean 的代理对象,而不是目标对象。

  • singletonFactories

    即三级缓存,用来缓存 bean 的工厂对象。调用 bean 工厂对象的 getObject() 即可获取 bean 对象。

11.3.2 三级缓存的目的

  三级缓存设计的目的之一,是为了解决循环依赖。

  所谓循环依赖是指,当两个 bean 相互依赖时,在 spring ioc 创建这两个 bean 时就会发生循环依赖。

public class A {
  	@Autowired
  	private B b;
}

public class B {
  	@Autowired
  	private A a;
}

  在上述代码中,定义了连个 bean,分别是 A 和 B,且 A B 相互依赖,表现为通过 @Autowired 给 A 注入了 B,又通过 @Autowired 给 B 注入了 A。当 spring ioc 创建 A 时,发现 A 依赖于 B,则去创建 B,创建 B 时,发现 B 依赖于 A,于是又去创建 A,于是就陷入循环中。呐,这就叫循环依赖。

  三级缓存设计的目的之二,是为了适配 bean 需要被代理的情况。

  代理为什么需要三级缓存,这点和代理的特点有关系,那就是 代理需要目标对象,即想要为一个对象创建代理对象时,需要先存在这个对象,才能为其创建代理对象,换言之,目标对象不存在你就不能为它创建代理对象。同时需要注意,当一个 bean 需要被代理且有其它 bean 依赖该 bean 时,注入到其它 bean 中的是该 bean 的代理对象。

  假设现在要创建 A 和 B bean,且这两个 bean 都需要被代理,则其创建流程如下:

spring-L3-cache

  如图所示,当 A、B 相互依赖且允许循环依赖(allow-circular-references = true),同时 A、B 需要被代理时,其创建流程可以大致分为三个阶段,分别是 创建 A、创建 B、继续创建 A。详细步骤如下:

  • 1、创建 A:

    • 1.1、A 是否能加入正在创建中缓存,因为 A 是第一次创建,所以可以成功加入,所以这里为 true。
    • 1.2、实例化 A,即通过 A 的构造器先 new 一个 A 出来。
    • 1.3、是否暴露早期 A,因为 allow-circular-references = true,即允许循环依赖,即允许提前暴露早期 A,所以这里为 true。
    • 1.4、将 A 包装成工厂对象并放至三级缓存。
    • 1.5、对 A 进行属性设置,此时发现 A 依赖 B。
    • 1.6、去找 B,找的顺序是从一级缓存、二级缓存再到三级缓存,由于此时 B 还未被创建,所以肯定找不到。
  • 2、暂停创建 A,去创建 B:

    • 2.1、B 是否能加入正在创建中缓存,因为 B 是第一次创建,所以可以成功加入,所以这里为 true。
    • 2.2、实例化 B,即通过 B 的构造器先 new 一个 B 出来。
    • 2.3、是否暴露早期 B,因为 allow-circular-references = true,即允许循环依赖,即允许提前暴露早期 B,所以这里为 true。
    • 2.4、将 B 包装成工厂对象并放至三级缓存。
    • 2.5、对 B 进行属性设置,此时发现 B 依赖 A。
    • 2.6、去找 A,从一级到三级,因为在 1.4 步骤中已经将 A 的工厂对象放入三级缓存了,所以这里在三级缓存中取到了 A 的工厂对象。(注:若 allow-circular-references = false,即不允许循环依赖时,在 1.4 步骤中就不会将 A 的工厂对象放入三级缓存,因此在 2.6 步骤时也就找不到,当没找到 A 时,就会去执行创建 A 的逻辑,即回到 1,这时,再执行 1.1 步骤时就会是 false,因为在第一次执行 1.1 步骤时已经将 A 的名字缓存起来了,所以再 add 时就是 false,当这里是 false 时,spring ioc 会认为可能发生了循环依赖,则会抛出循环依赖异常。这个场景可以简单理解为:spring ioc 会记录正在创建中的 bean name,当一个正在创建中的 bean 又要被创建时,那么有可能就是循环依赖造成的)。
    • 2.7、调用 A 工厂对象的 getObject() 方法获取 A 实例,若 A 需要被代理则此时获取到的是 A 的代理对象。
    • 2.8、将 A 放至二级缓存,并将三级缓存中的 A 移除,然后将 A 装配给 B。
    • 2.9、初始化 B,即调用 B 的初始化相关的方法。
    • 2.10、再判断一次是否暴露早期 B。再判断一次是和代理有关,因为创建 bean 的最终结果是返回一个成熟的 bean(实际上是栈上的引用),若允许早期暴露且该 bean 需要被代理,那么一旦暴露出去的 bean 被其它 bean 依赖且这个 bean 工厂对象的 getObject() 一定会被执行,于是该 bean 的代理对象就会被放至二级缓存中,此时就会出现一个问题,二级缓存中的 B 是代理对象,而当前创建中的 B 是目标对象。因为 B 是要被代理的,所以最终也要返回 B 的代理对象的引用。针对此问题,spring ioc 的解决办法是再去一、二级缓存中查找一次 B(在源码中调用 getSingleton() 方法时会传入两个参数,其中第一个为要查找的 bean name,第二个是布尔类型,当其为 false 时表示只查一、二级缓存),若没有,那就说明 B 的工厂对象的 getObject() 方法目前还未被调用,那么方法返回时返回则是 B 原始对象的引用(注:此时,若 B 需要被代理,则会为其创建代理对象,那么方法结束时返回的也是代理对象的引用);若有,则会把二级缓存中 B 代理对象赋值给当前方法中的引用,那么方法结束时返回的也是 B 代理对象的引用。
    • 2.11、将 B 放入一级缓存,并将三级缓存中的 B 移除。
    • B 创建完成。
  • 3、继续创建 A:

    • 3.1、继续对 A 进行属性设置。
    • 3.2、去找 B,从一级到三级,因为在 2.11 步骤中已经将创建好的 B 放至一级缓存,所以这里为 true。
    • 3.3、将 B 装配到 A 中。
    • 3.4、初始化 A,即调用 A 的初始化相关的方法。
    • 3.5、再判断一次是否暴露早期 B。逻辑和 2.10 步骤的逻辑一样,但结果不同。因为在 2.7 、2.8 步骤中已经调用过 A 工厂对象的 getObject() 方法,又因为 A 需要被代理,所以得到是 A 的代理对象,且最终将 A 的代理对象放入了二级缓存,所以此处再调用 getSingleton() 方法时会拿到 A 的代理对象,然后就会将其赋值给当前方法中的引用,最终方法结束时返回的也是个代理对象。
    • 3.6、将 A 放至一级缓存,并将二级缓存中的 A 移除。
    • 3.7、A 创建完成。
11.3.3 三级缓存的必要性

  通过以上的实验再结合三级缓存的两个目的,我们可以大致得出一个结论:一级缓存是用来存储成熟可用的 bean 的,二级缓存是用来存储刚出生不成熟的半吊子 bean 的,三级缓存是用来存储目标对象的(代理对象的创建是依赖于目标对象的,所以 spring 就设计了一个工厂对象集合,即能为其它 bean 提供依赖,又能当代理所需的目标对象来使)(仅个人理解)。

  所以以此来看,三级缓存是有必要的。

  再思考一下,第三极缓存的存在是为了提供每个 bean 的初始对象,我个人觉得把第三极缓存替换成一个缓存所有 bean 的早期对象的集合也不是不行,即在创建 bean 之前先遍历一遍 bean,为所有单例 bean 先创建一个早期 bean 缓存在第三极缓存中。那 spring 为什么不这么做呢?大佬的思想凡人怎么猜透,只是这么假设方便理解而已。

12 @Indexed 用法及原理

12.1 @Indexed 原理

  @Indexed 注解是 spring 5.0 提供的一个关于组件扫描的注解,其目的是提高容器的启动速度。

  在传统的 srping 系列项目中,组件扫描是通过 @ComponentScan 注解或其它方式进行,但原理都是挨个扫描所有 jar 包,然后加载 bean 定义。这种方式的缺点是当项目中组件过多时,势必会加长项目的启动速度。而 @Indexed 注解的原理是,在项目编译时扫描所有包,然后将被 @Indexed 注解或其派生注解所标注的类 (即组件) 记录在 META-INF/spring.components 文件中,以被标注类的全类名为 key,以所标注的注解的全类名为 value。然后在程序启动时,spring 的类路径 bean 定义扫描器 ClassPathBeanDefinitionScanner 会根据 META-INF/spring.components 文件中的键值对进行定向 bean 定义加载。

  该注解有效的缩短了程序的启动时间,因为若不存在 spring.components 文件,则会对 class 目录下的所有资源进行扫描,势必会增大时间开销。

12.2 @Indexed 使用

  需要使用 @Indexed 注解时,只需要引入 spring-context-indexer 依赖即可。

<dependency>
  	<groupId>org.springframework</groupId>
  	<artifactId>spring-context-indexer</artifactId>
  	<optional>true</optional>
</dependency>

  引入此依赖后,项目编译时会自动生成 META-INF/spring.components 文件,且因为 @Component 注解被 @Indexed 注解标注,同时 @Component 注解又是大多数 spring 组件注解的父注解,所以被 @Component 注解或其派生注解标注的类最终都会被记录在 META-INF/spring.components 文件中。

13 @Value 简介与用法

13.1 @Value 简介

  首先,@Value 是 spring 提供的一个关于属性设置(亦可称为属性注入、自动装配等)的注解,其主要利用 KaTeX parse error: Expected 'EOF', got '#' at position 9: {}** 、**#̲{}** 符号来完成属性值的注…{} 主要用来注入环境变量(即由 Envorinment 管理的各种环境参数,如系统配置、项目配置、配置文件、命令行参数等等),后者 #{} 主要用来注入 SpEL 相关的值。

  @Value 注解可以结合 **KaTeX parse error: Expected 'EOF', got '#' at position 43: …et、Map),可以结合 **#̲{}** 实现注入 bean、…{app.name:you}"),注入时会先从配置文件中找 app.name 配置,若此配置的值为空,则会注入默认值 you,若从配置文件中没找到 app.name 配置,亦没有设置默认值,则项目启动时会报错。

13.2 @Value 用法
13.2.1 基本数据类型

  @Value 注入基本数据类型时,同时支持 java 提供的八种基本数据类型和其包装类型。

@Value("${app.one:1}")
private byte one;

@Value("${app.two:100}")
private short two;

@Value("${app.three:1000}")
private int three;

@Value("${app.four:10000}")
private long four;

@Value("${app.five:2.2}")
private float five;

@Value("${app.six:2.22}")
private double six;

@Value("${app.seven:true}")
private boolean seven;

@Value("${app.eight:m}")
private char eight;

@Value("${app.one:1}")
private Byte one;

@Value("${app.two:100}")
private Short two;

@Value("${app.three:1000}")
private Integer three;

@Value("${app.four:10000}")
private Long four;

@Value("${app.five:2.2}")
private Float five;

@Value("${app.six:2.22}")
private Double six;

@Value("${app.seven:true}")
private Boolean seven;

@Value("${app.eight:m}")
private Character eight;
13.2.2 数组

  @Value 注入数组类型时,只配置需要以逗号的方式分割,且可以存在空格,解析 @Value 注解时,spring 会自动去掉空格,同时会帮我们做必要的类型转换。

@Value("${app.you:1,2,3,4,5}")
private int[] you;

@Value("${app.you:1, 2, 3, 4, 5}")
private String[] momo;
13.2.3 集合

  @Value 注入集合时,需要 ${}#{} 共同配合,且不能通过 : 指定默认空值,可以通过 SpEL 的 empty 方法判断是否为空。

  • List:

    注入 List 时,可以直接使用 ${},也可以使用 #{} 配合 split 方法。使用 ${} 时会默认使用 , 进行分割。可以使用 empty 方法进行判空处理。

    // yml 配置为 
    // app:
    // 		momo: 1,2,3,4,5
    @Value("${app.momo:1,2,3,4,5}")
    private List<Integer> momo;
    
    @Value("#{'${app.momo}'.split(',')}")
    private List<Integer> you;
    
    @Value("#{'${app.momo}'.empty ? null : '${app.momo}'.split(',')}")
    private List<Integer> ying;
    
  • Set:

    注入 Set 时和 List 大差不差了。

    @Value("${app.momo:1,2,3,4,5}")
    private List<Integer> momo;
    
    @Value("#{'${app.momo}'.split(',')}")
    private List<Integer> you;
    
    @Value("#{'${app.momo}'.empty ? null : '${app.momo}'.split(',')}")
    private List<Integer> ying;
    
  • Map:

    注入 Map 时,需要注意配置文件的格式,value 需要以 包含,key 包不包含无所谓。

    // yml 配置为 
    // app:
    // 		ying: "{'name':'momo', 'age':'24'}"
    @Value("#{${app.ying}}")
    private Map<String, Object> map;
    
    @Value("#{'${app.ying}'.empty ? null : '${app.ying}'}")
    private Map<String, Object> map;
    
13.2.4 bean

  @Value 还可以通过 #{} 注入 bean,实现类似于 @Autowired 的功能。

// 注入 bean name 为 zed 的 bean
@Value("#{zed}")
private Zed zed;
13.2.5 方法调用

  @Value 还可以通过注入 bean 的方法注入值。

public class Zed {
  private String name = "momo";
  public String getName() {
    return name;
  }
}
@Value("#{zed.name}")
private String name;

@Value("#{zed.getName()}")
private String momo;
13.2.6 静态方法

  @Value 中还可以通过一些静态方法的调用注入值,此时需要用到 T 字符。

// 通过调用 java.lang.Math 类的静态方法 random() 注入值
@Value("#{T(Math).random()}")
private double random;
13.2.7 逻辑运算

  @Value 中还可以进行一些逻辑运算,如字符串拼接、逻辑运算、三目运算等等。

public class Zed {
  private String name = "momo";
  private Integer age = 24;
  public String getName() {
    return name;
  }
  public Integer getAge() {
    return age;
  }
}
// 字符串拼接
@Value("#{zed.name + zed.age}")
private String content;

// 逻辑运算
@Value("#{zed.name.equals('you') and zed.age > 25}")
private Boolean you;

// 三目运算
@Value("#{zed.age > 25 ? zed.age : 24}")
private Integer age;

14 @Autowired 简介及用法

14.1 @Autowired 简介

  @Autowired 注解是 spring 提供的关于自动装配(可理解为简化版的自动注入)的注解,其作用是在创建 bean 时自动为bean 完成自动装配的功能。其可作用于构造器(作用于构造器时可忽略)、属性、方法、方法参数等。其装配时是按照类型进行装配,若为匹配到则按照名称进行装配。

  当代待装配的属性存在多个候选者时:

  • 首先检查被装配的属性是否被 @Qualifier 注解指定要注入的 bean name。
  • 其次检查所有候选者中是否存在被 @Primary 注解所标注的类型的 bean。
  • 然后检查所有候选者中是否存在被 @Priority 注解所标注的类型的 bean,若有则取优先级最高的那个(值越小优先级最高)。
  • 最后在按照被装配的属性名与候选者的 bean name 进行匹配。

  当 @Autowired 与 @Lazy 一同出现时,此时注入进来是要注入 bean 的代理类。当调用注入 bean 的任意方法时将由代理对象创建目标 bean 实例。

14.2 @Autowired 与 @Resource 的区别

  @Autowired 与 @Resource 注解都拥有自动装配的功能,二者区别如下:

  • 相同点:
    • 二者都可作为自动装配的注解使用。
  • 不同点:
    • 来源:@Autowired 由 spring 提供,@Resource 由 java 提供。
    • 作用域:@Autowired 可作用于 构造器、属性、方法、方法参数,@Resource 只可作用于 属性、方法。
    • 注入方式:
      • @Autowired:按类型注入,可配合 @Qualifier 按名称注入。
      • @Resource:
        • 默认情况下按名称注入;若未匹配到,则按类型注入;若未匹配到或匹配到多个则抛出异常。
        • 若指定了 name,则按名称匹配;若未匹配到则抛出异常。
        • 若指定了 type,则按类型匹配;若未匹配到或匹配到多个则抛出异常。
        • 若同时指定了 name 和 type,则同时按名称和类型匹配唯一值;若未匹配到则抛出异常。

  spring 5 开始推荐使用构造注入,即 @Autowired 作用于构造器的方式,构造器可省略;次之选用 @Resource 自动装配;最后选用 @Autowired 自动装配。

14.3 @Autowired 用法
// 定义 bean 接口
public interface Hero {}

// 定义实现了同一接口的 bean 类型
public class Zed implements Hero {}

public class Fizz implements Hero {}

public class Ahri implements Hero {}

public class Irelia implements Hero {}

public class Riven implements Hero {}

// 定义范型接口
public interface Assassin<T> {}

// 定义实现了同一范型接口不同范型类型的 bean 类型
public class Damage implements Assassin<Zed> {}

public class Power implements Assassin<Fizz> {}
public class You {

    @Autowired   // 作用于属性
    private Zed zed;

    private Fizz fizz;

    private Ahri ahri;

    @Autowired   // 作用于工厂 bean
    private Optional<Irelia> irelia;

    @Autowired   // 作用于工厂 bean(此时注入的是工厂 bean 而非 Riven bean )
    private ObjectFactory<Riven> riven;   //(当实际用到 Riven bean 时将由 工厂 bean 的 getObject() 方法创建并返回 Riven bean)

    @Autowired   // 作用于数组属性类型(此时将会收集到容器所有 Hero 的实现类 bean 并注入进来)
    private Hero[] heroes;

    @Autowired   // 作用于集合属性类型(同数组属性类型)
    private List<Hero> heroList;

    @Autowired   // 作用于特殊类型(此时将从 DefaultListableBeanFactory 的 resolvableDependencies 缓存中获取)
    private ApplicationContext context;

    @Autowired   // 作用于范型属性类型
    private Assassin<Zed> zedAssassin;

    @Autowired   // 作用于构造器(作用于构造器时可省略该注解 且 spring 5 推荐使用构造注入)
    public You(Fizz fizz) { this.fizz = fizz; }

    @Autowired   // 作用于 setXxx 方法
    public void setAhri(Ahri ahri) { this.ahri = ahri; }

    @Override
    public String toString() {
        return "You {\n" +
                "zed =" + zed + "\n" +
                "fizz=" + fizz + "\n" +
                "ahri=" + ahri + "\n" +
                "irelia=" + irelia + "\n" +
                "riven=" + riven + "\n" +
                "heroes=" + Arrays.toString(heroes) + "\n" +
                "heroList=" + heroList + "\n" +
                "context=" + context + "\n" +
                "zedAssassin=" + zedAssassin + "\n" +
                '}';
    }
}

15 @Autowired & @Value 原理

 @Autowired & @Value 注解的原理,即 spring 解析 @Autowired 与 @Value 注解的原理。@Autowired@Value 注解是由 AutowiredAnnotationBeanPostProcessor BPP 和 AutowireCandidateResolver 接口及其实现类共同解析的。其中前者负责解析出被此二注解所标注的属性或方法;后者负责解析 @Qualifier、@Value 注解。

15.1 @Autowired & @Value 解析

  @Autowired 与 @Value 注解的解析主要由 AutowiredAnnotationBeanPostProcessor BPP 完成,其会在内部通过调用 AutowireCandidateResolver 接口及其实现类的方法获取到依赖值,最终达到自动装配的目的。其工作流程大致如下:

autowire-resolving-process

// 流程图这么详细 就不用文字描述了吧(手动狗头)

  上述流程中的 DefaultListableBeanFactory#doResolveDependency() 方法主要作用是从容器中找到要注入的依赖值,期间要处理依赖类型为 Stream、Array、Collection、Map 等情况,且当出现多个候选者时要推断出唯一的那个 bean。其工作流程图如下:

autowire-resolving-process

// ......
15.2 @Qualifier & @Value 解析

  spring 提供了 AutowireCandidateResolver 系列接口,专门用来获取 @Autowired 与 @Value 自动装配时所需要的值。其类关系图如下:

spring-autowire-candidate-resolver

  • AutowireCandidateResolver

    定义了一些自动装配相关的方法,主要用来推断一个指定的 bean 定义是否为一个指定依赖的候选者。

  • SimpleAutowireCandidateResolver

    实现了 AutowireCandidateResolver 接口,为其定义的方法提供了默认实现,如同 interface 中的 default 关键字的作用。

  • GenericTypeAwareAutowireCandidateResolver

    用来解析自动装配时的范型相关。

  • QualifierAnnotationAutowireCandidateResolver

    用来解析 @Qualifier、@Value 注解注入时所需要的值。

  • ContextAnnotationAutowireCandidateResolver

    用来解析自动装配时的 @Lazy 注解。

  spring ioc 推荐使用构造注入,其在获取要注入的依赖 bean 时的逻辑和 @Autowired 差不多,区别是构造注入不能解决循环依赖问题。默认情况下,spring 在创建早期 bean 时实际上是调用 bean 对应类的无参构造方法;但如果使用了构造注入,则在创建早期 bean 时会使用自定义的有参构造,当两个相互依赖的 bean 都使用构造注入时,会因 new 不出对方需要的那个早期 bean 而抛出循环依赖异常。虽如此,spring 还是推荐使用构造注入,强制依赖。对于构造注入时的循环依赖问题则可使用 @Lazy 注解解决。

那就像风一样吧,无爱且自由!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值