【手撕Spring源码】容器与Bean

文章目录

  • 什么是容器?
  • 容器接口
  • 容器实现
    • BeanFactory容器实现
    • ApplicationContext容器实现
  • Bean的生命周期
  • 模板方法设计模式
  • Bean后处理器
  • BeanFactory 后处理器
  • 有关后处理器(PostProcessor)的调用时期
  • Aware 接口 和 InitializingBean 接口
  • 初始化和销毁
  • Scope

什么是容器?

Spring 包含并管理应用对象的配置和生命周期,在这个意义上它是一种用于承载对象的容器,你可以配置你的每个 Bean 对象是如何被创建的,这些 Bean 可以创建一个单独的实例或者每次需要时都生成一个新的实例,以及它们是如何相互关联构建和使用的。

如果一个 Bean 对象交给 Spring 容器管理,那么这个 Bean 对象就应该以类似零件的方式被拆解后存放到 Bean 的定义中,这样相当于一种把对象解耦的操作,可以由 Spring 更加容易的管理,就像处理循环依赖等操作。

当一个 Bean 对象被定义存放以后,再由 Spring 统一进行装配,这个过程包括 Bean 的初始化、属性填充等,最终我们就可以完整的使用一个 Bean 实例化后的对象了。

容器接口

此节我们要:

  • 了解BeanFactory能做那些事?
  • ApplicationContext能有哪些拓展功能
  • 事件解耦

Spring中比较有名的容器接口有两个一个就是BeanFactory接口、另一个就是ApplicationContext 接口。

  • BeanFactory 接口,典型功能有:

    • getBean
  • ApplicationContext 接口,是 BeanFactory 的子接口。它扩展了 BeanFactory 接口的功能,如:

    • 国际化
    • 通配符方式获取一组 Resource 资源
    • 整合 Environment 环境(能通过它获取各种来源的配置信息)
    • 事件发布与监听,实现组件之间的解耦

我们平时使用SpringBoot跑一个项目的时候,启动类都是类似这样的:

public class HmDianPingApplication {

    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }

}

我们使用run方法来启动一个SpringBoot程序,它有两个参数:

  • 启动类的类型
  • main方法传过来的命令行参数

这个run方法其实有一个返回值,这个返回值就是我们的Spring容器:

ConfigurableApplicationContext context = SpringApplication.run(A01.class, args);

那么这个ConfigurableApplicationContext和我们的两个容器接口有什么关系吗?我们可以Ctrl + Alt + u 查看一下类图:

【手撕Spring源码】容器与Bean_第1张图片

我们可以看到ConfigurableApplicationContext其实是继承于ApplicationContext接口,而ApplicationContext接口又是继承于BeanFactory接口,可想而知ApplicationContext接口应该是对BeanFactory接口做了一些功能拓展。

这个地方我们回答一个问题?到底什么是BeanFactory?

  • 它是 ApplicationContext 的父接口
  • 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能
    • 我们可以看看下面的代码,getBean方法直接就是调用的BeanFactory中的方法:
      【手撕Spring源码】容器与Bean_第2张图片
    • BeanFactory是ApplicationContext 的一个成员变量

然后我们解决第二个问题:BeanFactory 能干点啥?

我们直接看这个接口中的抽象方法发现比较简单:

【手撕Spring源码】容器与Bean_第3张图片
似乎比较有用的就是getBean方法,实际上我们不能光看接口还要看他的实现,控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供了。

而这个实现类是DefaultListableBeanFactory

【手撕Spring源码】容器与Bean_第4张图片
我们可以发现BeanFactory只是其实现的所有接口中一个很小的部分。

注意:
DefaultListableBeanFactory 本身不具有自动解析配置文件进行 Bean 注册的功能(ApplicationContext的实现类一般是有的)。
DefaultListableBeanFactory 是一个 BeanFactory 的基本实现,它可以:

  1. 注册 BeanDefinition
  2. 对 BeanDefinition 进行依赖注入
  3. 管理 Bean 的生命周期
  4. 容器启动和关闭时执行指定的回调
  5. 父 BeanFactory 的委托等

但是,它自己不具备解析配置文件的能力。如果要使用 DefaultListableBeanFactory 配合配置文件,需要我们手动解析配置文件,然后将解析得到的 BeanDefinition 注册到 DefaultListableBeanFactory 中。
我们可以把DefaultListableBeanFactory 想象成一个厨房,洗菜、炒菜都可以在厨房里进行,但是把菜送到厨房并不属于厨房的功能范围。

DefaultListableBeanFactory能管理所有的bean,其中我们最为熟悉的就是它可以管理单例对象,而这一功能就是他的一个父类DefaultSingletonBeanRegistry实现的,他有一个成员变量singletonObjects用来管理所有的单例对象:

【手撕Spring源码】容器与Bean_第5张图片

其中key是bean的名字,值就是对象的实例。为了能够更清楚的认识这个singletonObjects我们使用反射看一看他,这里我们定义了自己的两个bean我们找找看:

Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
        singletonObjects.setAccessible(true);
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        Map<String, Object> map = (Map<String, Object>) singletonObjects.get(beanFactory);
        map.entrySet().stream().filter(e -> e.getKey().startsWith("component"))
                .forEach(e -> {
                    System.out.println(e.getKey() + "=" + e.getValue());
                });

然后我们解决最后一个问题:ApplicationContext 比 BeanFactory 多点啥?

我们可以从ApplicationContext 多继承的接口来解决这个问题:

【手撕Spring源码】容器与Bean_第6张图片

  • MessageSource:一个国际化的消息访问器,它的主要作用是根据 Locale 来解析消息,以支持国际化
    在这里插入图片描述
    在这里插入图片描述

    将来这个Locale的信息是从浏览器的请求头中获取的

  • ResourcePatternResolver:一个资源模式解析器,它的主要作用是根据指定的位置和模式解析资源。

    1. 根据路径加载资源文件。可以加载类路径下的资源文件(classpath:),文件系统下的资源文件(file:),URL 等。
    2. 支持 Ant 风格的路径匹配,可以使用 ?、* 等通配符匹配多个资源。
    3. 资源可以是 properties 文件,XML 配置文件,图片,文本文件等。
    4. 找不到资源也不会报错,返回 null。
    • 我们举一个例子:
      在这里插入图片描述
      在这里插入图片描述

      • classpath代表从类路径下寻找,如果是file代表从磁盘目录下开始寻找
      • 如果要寻找的文件在jar包里,就要加一个*号,这样才能找到
  • ApplicationEventPublisher:一个事件发布器接口,它的主要作用是在 Spring 应用中发布事件。

    • 发送方:继承ApplicationEvent定义一个事件。然后使用容器的publishEvent方法发送。source代表事件的事件源。
      【手撕Spring源码】容器与Bean_第7张图片

      在这里插入图片描述

    • 接收方(监听器):在Spring中任何一个组件都可以作为监听器,发的是什么事件就要用对应的事件类型来接收:
      【手撕Spring源码】容器与Bean_第8张图片

    • 事件的作用:给我们提供了一种解耦方式
      【手撕Spring源码】容器与Bean_第9张图片

  • EnvironmentCapable:其主要作用就是允许组件访问 Environment(这个Environment中封装着环境信息,由spring在读取配置文件的时候维护的),从而读取环境变量、Profile()、配置文件等信息
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

我们前面说过BeanFactory是不具有自动解析配置文件进行Bean注册的,但是ApplicationContext却可以,就是因为这个ResourcePatternResolver。
ResourcePatternResolver 的作用是根据指定的 location pattern 解析出 Resource 资源。ApplicationContext 使用 ResourcePatternResolver 进行如下资源加载:
【手撕Spring源码】容器与Bean_第10张图片
ResourcePatternResolver 返回的 Resource 表示各种资源抽象,ApplicationContext 根据 Resource 类型调用相应的 Reader 进行资源解析和处理
所以,总结来说,ApplicationContext 使用 ResourcePatternResolver 进行资源加载,这使得 ApplicationContext 可以从各种位置以各种方式加载资源,具有较强的配置灵活性。

最后我们进行一个总结:

  1. 到底什么是 BeanFactory

    • 它是 ApplicationContext 的父接口
    • 它才是 Spring 的核心容器, 主要的 ApplicationContext 实现都【组合】了它的功能,【组合】是指 ApplicationContext 的一个重要成员变量就是 BeanFactory
  2. BeanFactory 能干点啥

    • 表面上只有 getBean
    • 实际上控制反转、基本的依赖注入、直至 Bean 的生命周期的各种功能,都由它的实现类提供
    • 例子中通过反射查看了它的成员变量 singletonObjects,内部包含了所有的单例 bean
  3. ApplicationContext 比 BeanFactory 多点啥

    • ApplicationContext 组合并扩展了 BeanFactory 的功能
    • 国际化、通配符方式获取一组 Resource 资源、整合 Environment 环境、事件发布与监听
    • 新学一种代码之间解耦途径,事件解耦

注意

  • 如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,这是因为这些版本的 jdk 默认不允许跨 module 反射
  • 事件发布还可以异步,请自行查阅 @EnableAsync,@Async 的用法

容器实现

在此节我们将会了解到:

  • BeanFactory实现的特点
  • ApplicationContext的常见实现和用法
  • 内嵌容器、注册DispatcherServlet

Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,这里出于怀旧的原因,把它们都列出来,供大家参考

  • DefaultListableBeanFactory,是 BeanFactory 最重要的实现,像控制反转依赖注入功能,都是它来实现
  • ClassPathXmlApplicationContext,从类路径查找 XML 配置文件,创建容器(旧)
  • FileSystemXmlApplicationContext,从磁盘路径查找 XML 配置文件,创建容器(旧)
  • XmlWebApplicationContext,传统 SSM 整合时,基于 XML 配置文件的容器(旧)
  • AnnotationConfigWebApplicationContext,传统 SSM 整合时,基于 java 配置类的容器(旧)
  • AnnotationConfigApplicationContext,Spring boot 中非 web 环境容器(新)
  • AnnotationConfigServletWebServerApplicationContext,Spring boot 中 servlet web 环境容器(新)
  • AnnotationConfigReactiveWebServerApplicationContext,Spring boot 中 reactive web 环境容器(新)

另外要注意的是,后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来

BeanFactory容器实现

这里我们以DefaultListableBeanFactory为例进行容器的创建。

我们刚开始创建一个容器对象的时候里面是没有bean的,所以我们要往其中添加一些bean的定义,然后容器会根据定义帮我们创建bean,最后我们就可以通过容器获得想要的bean

bean 的定义包括以下几个方面:

  1. class:bean 的实际实现类。可以是普通类,也可以是抽象类或接口。
  2. name:bean 的名称。用于从容器中获取 bean 对象。
  3. scope:bean 的作用域。包括 singleton、prototype、request、session、application 等。
  4. constructor arguments:bean 构造方法的参数值。用于依赖注入。
  5. properties:bean 的属性值。用于依赖注入。
  6. autowire:bean 的自动装配方式。包括 no、byName、byType、constructor。
  7. lazy-init:bean 的延迟初始化。
  8. initialization and destruction methods:bean 的初始化方法和销毁方法。
  9. depends-on:bean 所依赖的其他 bean。

一个 bean 的定义示例:

<bean id="helloBean" name="hello" class="com.example.HelloBean" scope="singleton">
    <constructor-arg value="HelloSpring"/>
    <property name="message" value="Hello World"/>
bean>

这个 bean 定义包含:

  • id 和 name: helloBean 和 hello
  • class: com.example.HelloBean
  • scope: singleton
  • constructor-arg: 为构造方法传入 “HelloSpring” 参数
  • property: 为 message 属性设置 “Hello World” 值

在容器启动时,会根据 bean 的定义创建 bean 实例,即执行:

HelloBean bean = new HelloBean("HelloSpring");
bean.setMessage("Hello World");

创建的 bean 实例会存入单例缓存池中,key 为 bean 的名称,之后可以通过名称从容器中获取这个 bean 对象。

所以总结来说,bean 的定义决定了 bean 的特征, 告诉 Spring 要实例化什么,依赖关系是什么,作用域是什么,如何创建这个 bean 等。根据 bean 的定义,Spring 能够初始化 bean 并且将它插入到应用程序中。

我们来看一段代码:

public class TestBeanFactory {

    public static void main(String[] args) {
    	//创建一个容器类
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // bean 的定义(class, scope, 初始化, 销毁)
        //这里构建一个bean的定义,指明了类型和Scope
        //genericBeanDefinition就是用来指定类型的
        AbstractBeanDefinition beanDefinition =
                BeanDefinitionBuilder.genericBeanDefinition(Config.class).setScope("singleton").getBeanDefinition();
        //根据定义进行注册,执行bean的名称,创建bean
        beanFactory.registerBeanDefinition("config", beanDefinition);
        //打印一下现在容器中有的bean的定义
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
	
	//这里不能使用非静态内部类,因为它需要外部类的实例才能实例化内部类
    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2() {
            return new Bean2();
        }
    }


    static class Bean1 {
        private static final Logger log = LoggerFactory.getLogger(Bean1.class);

        public Bean1() {
            log.debug("构造 Bean1()");
        }

        @Autowired
        private Bean2 bean2;

        public Bean2 getBean2() {
            return bean2;
        }
    }

    static class Bean2 {
        private static final Logger log = LoggerFactory.getLogger(Bean2.class);

        public Bean2() {
            log.debug("构造 Bean2()");
        }
    }
}

Tips:
在上面的代码中我们可以看见registerBeanDefinition方法,这个方法是将一个BeanDefinition注册到BeanDefinitionRegistry中,也就是将一个bean定义纳入Spring IoC容器的管理
BeanDefinitionRegistry是Spring容器中的一个接口,它定义了注册和获取BeanDefinition的方法,你可以当作容器中Bean定义的大管家。其典型实现是:

  • DefaultListableBeanFactory:Spring中最常用的BeanFactory实现,它实现了BeanDefinitionRegistry接口。
  • AnnotationConfigApplicationContext:注解式Spring应用上下文,它以 DefaultListableBeanFactory 作为delegate(装饰者模式),所以也实现了BeanDefinitionRegistry接口。
    【手撕Spring源码】容器与Bean_第11张图片

registerBeanDefinition方法是BeanDefinitionRegistry接口中的方法,其定义是:

void registerBeanDefinition(String beanName, BeanDefinition >beanDefinition) 
                                    throws BeanDefinitionStoreException;

它接收两个参数:

  • beanName:注册的bean名称
  • beanDefinition:用于描述bean的BeanDefinition实例

当调用registerBeanDefinition方法时,Spring容器就将接收到的BeanDefinition实例与指定的beanName绑定,并纳入管理。这意味着Spring会对该bean创建实例化,配置和初始化等过程的管理。
举个例子,在AnnotationConfigApplicationContext中,我们可以这样注册一个BeanDefinition:

public static void main(String[] args) {
   AnnotationConfigApplicationContext ctx = new >AnnotationConfigApplicationContext();
   RootBeanDefinition beanDefinition = new >RootBeanDefinition(Person.class);
   ctx.registerBeanDefinition("person", beanDefinition);
   ctx.refresh();
   Person person = ctx.getBean(Person.class);
   // ...
} 

这里,我们创建了一个RootBeanDefinition,描述了Person类型的bean,然后调用registerBeanDefinition方法注册到ApplicationContext中。在refresh()之后,该bean就可以从容器中获取了。

所以,registerBeanDefinition方法就是向Spring IoC容器注册一个新的bean的方式,将其纳入容器的管理之下,这是向容器中动态添加bean定义的手段。Bean定义一旦注册,容器就可以对其进行初始化、依赖注入等管理工作了。

根据结果我们发现现在容器中只有config的定义,但是根据我们的常识,这里的config是一种配置类,在里面加了@Bean注解之后,我们的容器中应该也有Bean1和Bean2的定义才对。

也就是说Config上的@Configuration注解,包括@Bean注解都没有被解析。换句话说我们这里的DefaultListableBeanFactory没有解析注解的能力,功能并不完整。

于是这里我们引入一个注解相关的工具类:AnnotationConfigUtils。他有一个静态方法registerAnnotationConfigProcessors(注册注释配置处理器),它可以给 BeanFactory 添加一些常用的后处理器(PostProcessor)(当然不只是BeanFactory的后处理器,还有Bean的后处理器,后面会说到)。其实就是对我们容器功能的拓展。

更详细的说AnnotationConfigUtils.registerAnnotationConfigProcessors方法的作用是注册用于处理注解配置的处理器,这些处理器将被用于处理@Configuration、@Import、@ComponentScan等注解。

具体来说,该方法会注册以下几个处理器:

  • ConfigurationClassPostProcessor:用于处理@Configuration注解,将@Configuration注解标注的类解析成一个或多个BeanDefinition对象,并将其注册到BeanFactory中。

  • AutowiredAnnotationBeanPostProcessor:用于处理@Autowired、@Value等注解,将这些注解标注的字段或方法参数注入对应的Bean实例。

  • CommonAnnotationBeanPostProcessor:用于处理JSR-250注解,如@Resource、@PostConstruct等。

  • PersistenceAnnotationBeanPostProcessor:用于处理JPA注解,如@PersistenceContext、@Transactional等。

通过注册这些处理器,Spring能够自动识别和处理各种注解配置,从而更加方便地进行依赖注入和组件扫描等操作。

我们在加上这个方法之后,然后执行每一个处理器,再次遍历容器:

【手撕Spring源码】容器与Bean_第12张图片
【手撕Spring源码】容器与Bean_第13张图片
这个时候就能看到我们在配置类中定义的两个Bean了。

这个地方要注意:
@Configuration 注解表明这个类可以用来配置 Spring BeanDefinition。可以理解为:

  • 一个@Configuration 标注的类相当于一个 XML 配置文件
  • 在@Configuration 类中用 @Bean 注解的方法相当于 XML 文件中的 定义

但这个配置类本身不会被加入到 Spring 容器中

但是当我们接下来去调用Bean1中的getBean2方法时却发现获得的是null:
【手撕Spring源码】容器与Bean_第14张图片

换句话说我们的@Autowired注解没有生效。那么我们怎么解决这个问题呢?

这里我们就要引入一个新的概念叫做Bean的后处理器,前面使用的是BeanFactory的后处理器,他补充了一些Bean的定义。而Bean的后处理器针对Bean生命周期的各个阶段提供扩展,例如@Autowired、@Resource …

我们把这几个处理器进行调用(不用注册,因为前面已经注册过了),就能发现bean1中的bean2依赖被成功注入了:
【手撕Spring源码】容器与Bean_第15张图片

可以看到我们的BeanFactoryPostProcessor拿到之后,需要执行postProcessBeanFactory方法才能起作用,而BeanPostProcessor直接注册到容器中就行,这是因为我们无法在bean的生命周期中准确的执行方法,所以执行BeanPostProcessor接口中的方法交给容器来做就行。(我们后面会说明这里Spring使用了模板方法设计模式,在生命周期中统一对BeanPostProcessor接口进行了回调)

然后我们很容易会发现,我们的容器是延时进行bean的创建的,也就是用的时候。它自身先开始只会保存一些bean的定义。但是对于一些单例对象来说,我们希望在getBean之前就将这些对象给创建好,这个时候我们就可以调用一个方法叫做DefaultListableBeanFactory.preInstantiateSingletons(),这个方法会预先实例化所有的单例对象。

这里我们小小的总结一下:
BeanFactory 不会做的事

  1. 不会主动调用 BeanFactory 后处理器(ApplicationContext中有一个refresh方法会做)
  2. 不会主动添加 Bean 后处理器(ApplicationContext中有一个refresh方法会做)
  3. 不会主动初始化单例(ApplicationContext中有一个refresh方法会做)
  4. 不会解析 ${ } 与 #{ }

由此可见BeanFactory只是一个基础容器,他的很多功能事先都没有加入体系中来。而ApplicationContext就把这些准备工作给做好了,对开发人员更加的友好

还要注意一点:bean 后处理器会有排序的逻辑。

最后总结一下:

  • beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象
    • 我们平时使用的配置类、xml、组件扫描等方式都是生成 bean definition 对象注册到 beanFactory 当中
    • bean definition 描述了这个 bean 的创建蓝图:scope 是什么、用构造还是工厂创建、初始化销毁方法是什么,等等
  • beanFactory 需要手动调用 beanFactory 后处理器对它做增强
    • 例如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition
  • beanFactory 需要手动添加 bean 后处理器,以便对后续 bean 的创建过程提供增强
    • 例如 @Autowired,@Resource 等注解的解析都是 bean 后处理器完成的
    • bean 后处理的添加顺序会对解析结果有影响,见视频中同时加 @Autowired,@Resource 的例子
  • beanFactory 需要手动调用方法来初始化单例
  • beanFactory 需要额外设置才能解析 ${} 与 #{}

ApplicationContext容器实现

其有四个较为经典的实现类:

    // ⬇️较为经典的容器, 基于 classpath 下 xml 格式的配置文件来创建
    private static void testClassPathXmlApplicationContext() {

    }

    // ⬇️⬇️较为经典的容器, 基于磁盘路径下 xml 格式的配置文件来创建
    private static void testFileSystemXmlApplicationContext() {

    }

    // ⬇️较为经典的容器, 基于 java 配置类来创建
    private static void testAnnotationConfigApplicationContext() {

    }

    // ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境
    private static void testAnnotationConfigServletWebServerApplicationContext() {

        
    }

我们先来看看前两种,他们比较类似,都是基于xml配置文件,一个是从磁盘路径下加载配置,另一个是从类路径下加载配置,使用情况如下:

    // ⬇️较为经典的容器, 基于 classpath 下 xml 格式的配置文件来创建
    private static void testClassPathXmlApplicationContext() {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("a02.xml");

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

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

    // ⬇️基于磁盘路径下 xml 格式的配置文件来创建
    private static void testFileSystemXmlApplicationContext() {
        FileSystemXmlApplicationContext context =
                new FileSystemXmlApplicationContext(
                        "src\\main\\resources\\a02.xml");
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

配置文件:
【手撕Spring源码】容器与Bean_第16张图片

然后我们来说说他们的原理,这里以ClassPathXmlApplicationContext为例,我们前面也说过,其内部肯定需要BeanFactory的支持(BeanFactory负责bean的管理功能),那ClassPathXmlApplicationContext是怎么实现将xml文件解析完之后添加到BeanFactory中去的呢?

其内部是借助了XmlBeanDefinitionReader,我们可以使用下面的代码试验一下:

		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        System.out.println("读取之前...");
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }
        System.out.println("读取之后...");
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new FileSystemResource("src\\main\\resources\\a02.xml"));
        for (String name : beanFactory.getBeanDefinitionNames()) {
            System.out.println(name);
        }

【手撕Spring源码】容器与Bean_第17张图片

然后我们再来看看第三种实现:

    // ⬇️较为经典的容器, 基于 java 配置类来创建
    private static void testAnnotationConfigApplicationContext() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(Config.class);

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

        System.out.println(context.getBean(Bean2.class).getBean1());
    }

    @Configuration
    static class Config {
        @Bean
        public Bean1 bean1() {
            return new Bean1();
        }

        @Bean
        public Bean2 bean2(Bean1 bean1) {
            Bean2 bean2 = new Bean2();
            bean2.setBean1(bean1);
            return bean2;
        }
    }

我们运行之后发现,跟前面的xml相比,容器中多了几个bean(我们前面提到过的后处理器):

【手撕Spring源码】容器与Bean_第18张图片
而如果使用前面那两种基于xml配置文件的ApplicationContext容器类的话,我们就需要手动配置一下:
【手撕Spring源码】容器与Bean_第19张图片

最后我们来看看第四类:

    // ⬇️较为经典的容器, 基于 java 配置类来创建, 用于 web 环境
    private static void testAnnotationConfigServletWebServerApplicationContext() {
		//这个地方一定要注意是AnnotationConfigServletWebServerApplicationContext,否则会出现容器不能持续运行的问题
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    @Configuration
    static class WebConfig {
        @Bean
        public ServletWebServerFactory servletWebServerFactory(){
            return new TomcatServletWebServerFactory();
        }
        @Bean
        public DispatcherServlet dispatcherServlet() {
            return new DispatcherServlet();
        }
        @Bean
        public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
            return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        }

    }

要想启动一个web项目,那么容器中有三个bean不能少:

  • ServletWebServerFactory
  • DispatcherServlet
  • DispatcherServletRegistrationBean

我们先来看看ServletWebServerFactory

ServletWebServerFactory 是一个 Servlet Web 服务器工厂接口,用于在 Spring 应用中启动嵌入式的 Servlet 容器。
在Spring Boot 应用中,我们通常使用嵌入式 Tomcat、Jetty 或 Undertow 等 Servlet 容器,而不用打包成 WAR 文件部署到 standalone Servlet 容器中。

ServletWebServerFactory 接口的实现类就是这些嵌入式 Servlet 容器工厂,主要有:

  • TomcatServletWebServerFactory:嵌入式 Tomcat 工厂
  • JettyServletWebServerFactory:嵌入式 Jetty 工厂
  • UndertowServletWebServerFactory:嵌入式 Undertow 工厂

在 Spring Boot 应用中,我们通常不需要手动配置这些工厂,因为根据你添加的依赖,Spring Boot 会自动选择一个工厂配置。例如:

  • 添加 spring-boot-starter-tomcat,会自动使用 TomcatServletWebServerFactory
  • 添加 spring-boot-starter-jetty,会自动使用 JettyServletWebServerFactory
  • 添加 spring-boot-starter-undertow,会自动使用 UndertowServletWebServerFactory

但是,如果需要定制嵌入式 Servlet 容器的配置,我们可以手动添加一个 ServletWebServerFactory bean。例如:

@Bean
public ServletWebServerFactory servletContainer() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    factory.setPort(9000);
    factory.setSessionTimeout(10 * 60);
    return factory;
}

这里我们定制了 Tomcat 服务器,设置端口为 9000 和 session 超时时间为 10 分钟。

ServletWebServerFactory 的其他用法还有:

  • 添加多个 ServletWebServerFactory 来启动多个嵌入式 Servlet 容器实例。
  • 根据环境不同选择不同的 ServletWebServerFactory,例如在 Windows 环境下选择 Undertow。
  • 进行更深层次的 Servlet 容器配置。ServletWebServerFactory 只暴露了部分配置选项,深层次配置可以直接调用 Servlet 容器的 API。

所以,总结来说,ServletWebServerFactory 的主要作用是:

  1. 在 Spring Boot 应用中启动嵌入式的 Servlet 容器(Tomcat、Jetty、Undertow)
  2. 可以定制 Servlet 容器配置,覆盖默认配置
  3. 启动多个 Servlet 容器实例
  4. 根据环境选择不同的 Servlet 容器

至于DispatcherServlet这个大家应该都熟悉,对网络请求进行拦截,我们一般叫他前置控制器,相当于Springweb程序的入口。

现在内嵌web容器有了,DispatcherServlet有了,但是他们两个还没有联系,我们要将DispatcherServlet注册在web容器中。

而DispatcherServletRegistrationBean 是一个 Servlet 注册 bean,用于向 Servlet 容器注册 DispatcherServlet。

有了这三个bean之后,我们就能启动一个web项目了,但是我们还什么都不能,于是我们再添加一个控制器Controller:
【手撕Spring源码】容器与Bean_第20张图片
这里注意:约定如果bean的name是/开头的,就代表拦截路径。

Bean的生命周期

一个受 Spring 管理的 bean,生命周期主要阶段有

创建
依赖注入
初始化
可用
销毁
  1. 创建:根据 bean 的构造方法或者工厂方法来创建 bean 实例对象
  2. 依赖注入:根据 @Autowired,@Value 或其它一些手段,为 bean 的成员变量填充值、建立关系
  3. 初始化:回调各种 Aware 接口,调用对象的各种初始化方法
  4. 销毁:在容器关闭时,会销毁所有单例对象(即调用它们的销毁方法)
    • prototype 对象也能够销毁,不过需要容器这边主动调用

我们使用代码来验证一下:

@Component
public class LifeCycleBean {
    private static final Logger log = LoggerFactory.getLogger(LifeCycleBean.class);

    public LifeCycleBean() {
        log.debug("构造");
    }

    @Autowired
    public void autowire(@Value("${JAVA_HOME}") String home) {
        log.debug("依赖注入: {}", home);
    }

    @PostConstruct
    public void init() {
        log.debug("初始化");
    }

    @PreDestroy
    public void destroy() {
        log.debug("销毁");
    }
}

测试类:

@SpringBootApplication
public class A03 {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A03.class, args);
        context.close();
    }
}

运行结果:
【手撕Spring源码】容器与Bean_第21张图片

大家应该还记得我们前面提到过的Bean后处理器,它与我们bean的生命周期息息相关,它可以为bean生命周期的各个阶段提供功能的拓展,我们接下来看一段代码:

@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

    private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor.class);

    @Override
    public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            log.debug("<<<<<< 销毁之前执行, 如 @PreDestroy");
    }

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            log.debug("<<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean");
        return null;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean")) {
            log.debug("<<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段");
//            return false;
        }
        return true;
    }

    @Override
    public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            log.debug("<<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource");
        return pvs;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            log.debug("<<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            log.debug("<<<<<< 初始化之后执行, 这里返回的对象会替换掉原本的 bean, 如代理增强");
        return bean;
    }
}

我们执行一下:
【手撕Spring源码】容器与Bean_第22张图片
在这里插入图片描述

创建前后的增强

  • postProcessBeforeInstantiation
    • 这里返回的对象若不为 null 会替换掉原本的 bean,并且仅会走 postProcessAfterInitialization 流程
  • postProcessAfterInstantiation
    • 这里如果返回 false 会跳过依赖注入阶段

依赖注入前的增强

  • postProcessProperties
    • 如 @Autowired、@Value、@Resource

初始化前后的增强

  • postProcessBeforeInitialization
    • 这里返回的对象会替换掉原本的 bean
    • 如 @PostConstruct、@ConfigurationProperties
  • postProcessAfterInitialization
    • 这里返回的对象会替换掉原本的 bean
    • 如代理增强

销毁之前的增强

  • postProcessBeforeDestruction
    • 如 @PreDestroy

模板方法设计模式

模板方法模式通过定义一个算法骨架,并将某些步骤的实现延迟到子类,使得子类可以在不改变算法结构的情况下,重新定义该算法的某些特定步骤,从而实现算法的扩展与封装。

模板方法中使用动静结合的方式实现方法的可拓展性:

  • 动:不确定的部分,抽象成子类或者接口,进行回调
  • 静:固定的算法骨架

通俗点说指大流程已经固定好了, 通过接口回调(bean 后处理器)在一些关键点前后提供扩展

我们看一个例子:

现在我们模拟一个BeanFactory容器:

    // 模板方法  Template Method Pattern
    static class MyBeanFactory {
        public Object getBean() {
            Object bean = new Object();
            System.out.println("构造 " + bean);
            System.out.println("依赖注入 " + bean); // @Autowired, @Resource
            System.out.println("初始化 " + bean);
            return bean;
        }
}

刚开始我们设计代码的时候没有考虑到后期的扩展性,后面要添加新的功能,我们只能不断的往里面积累代码,最后这个方法会变得庞大冗杂。

这里我们可以使用模板方法设计模式改造一下上面的代码:

例如我们想在依赖注入之后添加一些逻辑,那么我们可以把依赖注入后的逻辑抽象成一个接口,然后再在依赖注入之后一个个进行调用

public class TestMethodTemplate {

    public static void main(String[] args) {
        MyBeanFactory beanFactory = new MyBeanFactory();
        beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Autowired"));
        beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Resource"));
        beanFactory.getBean();
    }

    // 模板方法  Template Method Pattern
    static class MyBeanFactory {
        public Object getBean() {
            Object bean = new Object();
            System.out.println("构造 " + bean);
            System.out.println("依赖注入 " + bean); // @Autowired, @Resource
            for (BeanPostProcessor processor : processors) {
                processor.inject(bean);
            }
            System.out.println("初始化 " + bean);
            return bean;
        }

        private List<BeanPostProcessor> processors = new ArrayList<>();

        public void addBeanPostProcessor(BeanPostProcessor processor) {
            processors.add(processor);
        }
    }

    static interface BeanPostProcessor {
        public void inject(Object bean); // 对依赖注入阶段的扩展
    }
}

Bean后处理器

  1. @Autowired 等注解的解析属于 bean 生命周期阶段(依赖注入, 初始化)的扩展功能,这些扩展功能由 bean 后处理器来完成
  2. 每个后处理器各自增强什么功能
    • AutowiredAnnotationBeanPostProcessor 解析 @Autowired 与 @Value
    • CommonAnnotationBeanPostProcessor 解析 @Resource、@PostConstruct、@PreDestroy
    • ConfigurationPropertiesBindingPostProcessor 解析 @ConfigurationProperties
  3. 另外 ContextAnnotationAutowireCandidateResolver 负责获取 @Value 的值,解析 @Qualifier、泛型、@Lazy 等(该注解较为复杂,后面会详细讲解)(这个不是Bean后处理器)

然后我们来详细说说AutowiredAnnotationBeanPostProcessor这个Bean后处理器的运行原理

要向了解这个方法,我们就把他单拿出来用,我们写了下面的测试代码:

// AutowiredAnnotationBeanPostProcessor 运行分析
public class DigInAutowired {
    public static void main(String[] args) throws Throwable {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.registerSingleton("bean2", new Bean2()); // 创建过程,依赖注入,初始化
        beanFactory.registerSingleton("bean3", new Bean3());
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); // @Value


        AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
        processor.setBeanFactory(beanFactory); //依赖注入部分跟Bean有关,需要得到BeanFactory的支持
        Bean1 bean1 = new Bean1();
        
        System.out.println(bean1);
        processor.postProcessProperties(null, bean1, "bean1"); // 执行依赖注入 @Autowired @Value
        System.out.println(bean1);

    }
}

这里我们的bean1中需要三种依赖:
【手撕Spring源码】容器与Bean_第23张图片

图片勘误:此时Bean3上是没有@Autowired注解的

在 Spring 中,@Autowired 注解标注在方法上表示该方法需要自动注入依赖的 bean。Spring 会在调用该方法时,自动从上下文中查找名称与参数类型相匹配的 bean,并将其注入方法

运行结果:

【手撕Spring源码】容器与Bean_第24张图片

这里的${}没有被解析,我们可以加一句:

beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders); // ${} 的解析器

在调用了AutowiredAnnotationBeanPostProcessor中的postProcessProperties方法之后,我们的依赖成功注入,说明就是这个方法起到了作用,接下来我们就来看看它是如何执行的:

【手撕Spring源码】容器与Bean_第25张图片
我们可以打断点看一下,这个InjectionMetadata:
【手撕Spring源码】容器与Bean_第26张图片

在这里插入图片描述

与我们这两个方法正好对应:
【手撕Spring源码】容器与Bean_第27张图片

图片勘误:此时Bean3上是没有@Autowired注解的

而在inject方法中,其逻辑就是将方法参数或者属性使用DependencyDescriptor 包一层,然后我们就可以借助beanFactory.doResolveDependency 方法进行查找,过程如下:

		//@Autowired作用于Bean3
        Field bean3 = Bean1.class.getDeclaredField("bean3");
        DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);
        Object o = beanFactory.doResolveDependency(dd1, null, null, null);
        System.out.println(o);
		
		//@Autowired作用于setBean2方法
        Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
        DependencyDescriptor dd2 =
                new DependencyDescriptor(new MethodParameter(setBean2, 0), true);
        Object o1 = beanFactory.doResolveDependency(dd2, null, null, null);
        System.out.println(o1);
		
		//@Autowired作用于setHome方法
        Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
        DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
        Object o2 = beanFactory.doResolveDependency(dd3, null, null, null);
        System.out.println(o2);

对应下图中的三部分:

【手撕Spring源码】容器与Bean_第28张图片

最后我们总结一下AutowiredAnnotationBeanPostProcessor这个Bean后处理器的运行原理:

  1. AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata 用来获取某个 bean 上加了 @Value @Autowired 的成员变量,方法参数的信息,表示为 InjectionMetadata
  2. InjectionMetadata 可以完成依赖注入
  3. InjectionMetadata 内部根据成员变量,方法参数封装为 DependencyDescriptor 类型
  4. 有了 DependencyDescriptor,就可以利用 beanFactory.doResolveDependency 方法进行基于类型的查找

BeanFactory 后处理器

实验代码如下,跟前面的思路差不多(控制变量):
【手撕Spring源码】容器与Bean_第29张图片

这里的初始化容器会有两个操作:

  • 会把BeanFactory的后处理器执行完
  • 创建每个单例Bean

可以得出结论:

  • ConfigurationClassPostProcessor 可以解析
    • @ComponentScan
    • @Bean
    • @Import
    • @ImportResource
  • MapperScannerConfigurer 可以解析
    • Mapper 接口
  • @ComponentScan, @Bean, @Mapper 等注解的解析属于核心容器(即 BeanFactory)的扩展功能
  • 这些扩展功能由不同的 BeanFactory 后处理器来完成,其实主要就是补充了一些 bean 定义

接下来我们通过模拟来弄清ConfigurationClassPostProcessor是如何解析@ComponentScan注解的。

//在初始化容器的时候会回调这个BeanFactory后处理器
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override // context.refresh
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            //在指定的类、方法、字段或者构造器上查找指定类型的注解。如果找到,则返回该注解,否则返回 null。
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {
                for (String p : componentScan.basePackages()) {
                    //System.out.println(p);
                    //将包名转化为路径名方便后面资源的查找
                    // com.zyb.a05.component -> classpath*:com/zyb/a05/component/**/*.class
                    String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
                    //System.out.println(path);
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                    //一个工具类:根据类上的注解生成 bean 的名称
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
                    for (Resource resource : resources) {
                        // System.out.println(resource);
                        MetadataReader reader = factory.getMetadataReader(resource);
                        // System.out.println("类名:" + reader.getClassMetadata().getClassName());
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
                        // System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
                        // System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
                        if (annotationMetadata.hasAnnotation(Component.class.getName())
                            || annotationMetadata.hasMetaAnnotation(Component.class.getName())) {                         
                            //创建一个Bean的定义
                            AbstractBeanDefinition bd = BeanDefinitionBuilder
                                    .genericBeanDefinition(reader.getClassMetadata().getClassName())
                                    .getBeanDefinition();
                            //根据类上的注解生成 bean 的名称
                            String name = generator.generateBeanName(bd, beanFactory);
                            //根据定义注册Bean                              
                            beanFactory.registerBeanDefinition(name, bd);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里该类实现的BeanDefinitionRegistryPostProcessor也是一种BeanFactory后处理器:
【手撕Spring源码】容器与Bean_第30张图片
在Spring容器初始化时,它会自动检测并调用所有注册的BeanDefinitionRegistryPostProcessor,对Bean定义进行处理,然后再将其实例化
该后处理器允许我们在Spring bean定义加载完成后,实例化bean之前,修改bean定义。
BeanDefinitionRegistryPostProcessor接口定义了两个方法:

  • postProcessBeanDefinitionRegistry():在bean定义加载后,实例化前被调用。可以修改bean定义。
  • postProcessBeanFactory():在bean定义加载后,实例化前被调用。可以在这里获得BeanFactory,但是此时不能获得bean实例。

基本逻辑就是:

  • 通过ComponentScan注解拿到扫描的包
  • 拿到包下的所有类文件
  • 判断哪些类文件上有Component注解或者其派生注解
  • 将符合要求的类进行注册

注意点:

  • 有关路径中的符号:

    • * 代表 0 到多个字符,例如:*.txt 代表所有 txt 文件
    • ** 代表 0 到多个文件夹,例如:/app1/**/app2/** 代表 app1 文件夹下任意层文件夹,以及其下的 app2 任意层文件夹
  • 在 Spring 中,我们可以使用 MetadataReader 来读取类信息,例如类名称、接口、注解等。但是获取 MetadataReader 对象的开销是比较大的,每次都从类路径下加载类信息会影响性能。

  • CachingMetadataReaderFactory 就是为了解决这个问题而存在的。它 内部维护了一个 MetadataReader 实例的缓存,可以重用已经创建的 MetadataReader 实例,而不需要每次都完全重新加载类信息。

总结一下:

  1. Spring 操作元数据的工具类 CachingMetadataReaderFactory
  2. 通过注解元数据(AnnotationMetadata)获取直接或间接标注的注解信息
  3. 通过类元数据(ClassMetadata)获取类名,AnnotationBeanNameGenerator 生成 bean 名
  4. 解析元数据是基于 ASM 技术

ASM 是一个 Java 字节码操控框架。它允许我们以二进制形式修改字节码,或者动态生成类。
ASM 可以用于:

  1. 动态生成类:在运行时根据某些规则生成类的字节码。
  2. 修改类:在运行时为类添加字段或方法,修改类中的方法体等。
  3. 分析类:在运行时获取类的详细信息,如类继承关系,类中的方法,字段等。
  4. 转换类:在运行时将类文件从一种格式转换成另一种格式,如从 Java class 文件转换为 dex 文件等。

ASM 的工作原理是:它分析类文件的字节码,将之转换为更高层的 ASM 元素(称为 Node)来表示,如 MethodNode、FieldNode 等。我们可以通过添加或修改这些 Node 来生成新的类,或修改已有类。

接下来我们还要注意一点:

【手撕Spring源码】容器与Bean_第31张图片

在这一段配置类代码中我们其实可以看见两种定义bean的方法:

  • Configuration类 + @Bean
  • Configuration类 + 工厂Bean(图中红方框部分)

第一种我们非常熟悉,那第二种中的工厂Bean是什么呢?

我们就以这个SqlSessionFactoryBean来说:
【手撕Spring源码】容器与Bean_第32张图片
那么这个FactoryBean是个什么东东?

在Spring中,FactoryBean接口是一个工厂bean的接口。实现这个接口的bean会在 Spring 容器中被当做工厂bean。当一个bean依赖工厂bean时,会调用getObject()方法获得bean实例。它允许你在 Bean 的实例化过程中执行一些自定义的逻辑。

【手撕Spring源码】容器与Bean_第33张图片

工厂Bean通常遵循以下模式:

  1. 实现 org.springframework.beans.factory.FactoryBean 接口
  2. 在 getObject() 方法中返回要从工厂中生成的 Bean 实例
  3. 返回要生成的 Bean 的类型,在 getObjectType() 方法中指定
  4. 在 isSingleton() 方法中指定生成的 Bean 是否为单例

工厂Bean可以和@Configuration配置类很好地结合来定义bean。
比如,我们可以在配置类中定义一个工厂后处理器方法,使用@Bean注解来注册这个工厂后处理器:

@Configuration
public class AppConfig {
    @Bean
    public MyFactoryBean myFactoryBean() {
        return new MyFactoryBean();
    }
}

然后工厂后处理器MyFactoryBean可以在getObject()方法中定义bean:

public class MyFactoryBean implements FactoryBean<Example> {
    @Override
    public Example getObject() throws Exception {
        return new Example();
    }
}

在这种方式下,Spring会:

  1. 实例化配置类AppConfig
  2. 调用myFactoryBean()方法,并将返回的MyFactoryBean实例注册为一个bean
  3. 从MyFactoryBean获取最终bean实例,调用getObject()方法返回的Example实例也会被注册为一个bean

所以,最终会注册两个bean:

  • myFactoryBean:MyFactoryBean实例
  • example:Example实例

并且example bean是由myFactoryBean工厂后处理器生成的。

客户端可以像注入普通bean一样注入example:

public class Client {
    @Autowired
    private Example example;
} 

这样,我们就通过配置类和工厂Bean灵活地定义了bean。配置类定义工厂Bean,工厂Bean又自定义了最终的bean。

这种方式将工厂Bean的灵活性和@Configuration带来的便利性很好结合在一起,是在Spring中定义bean的一种很实用的模式。

接下来我们还是使用代码模拟一下这个解析过程:

public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
        	//首先建立一个元数据读取工厂,方便后面的元数据读取
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            //对指定路径上的类资源进行元数据读取
            MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/itheima/a05/Config.class"));
            //得到所有带有@Bean注解的方法
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
            for (MethodMetadata method : methods) {
                System.out.println(method);
                //创建BeanDefinitionBuilder 
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                //提供工厂方法的名字,同时指定工厂对象
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                //因为 工厂方法有参数我们这里要设置自动装配的模式
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                //得到bean定义
                AbstractBeanDefinition bd = builder.getBeanDefinition();
                //通过BeanFactory进行bean的注册
                beanFactory.registerBeanDefinition(method.getMethodName(), bd);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意:

  • 因为 工厂方法有参数,所以我们这里要设置自动装配的模式
  • 对于工厂方法和构造方法的参数,如果想使用自动装配的话,都是AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR类型
  • 我们可以把配置类想象成一个工厂的角色,配置类中用@Bean标注的方法充当的是工厂方法的角色。

最后我们来模拟解析Mapper 接口

首先我们要弄明白Mapper接口是怎么被Spring管理的?

Spring会根据Mapper接口自动生成代理实现类,这就是Spring管理Mapper接口的方式。

具体步骤是:

  1. 在Spring配置中注册Mapper接口,通常使用@MapperScan注解扫描包下的所有Mapper接口:
    @MapperScan("com.example.mapper")
    @Configuration
    public class AppConfig {
    }
    
  2. Spring会根据 Mapper接口生成对应的代理对象,代理对象会实现Mapper接口的方法。
  3. 当调用Mapper接口的方法时,实际执行的是代理对象的方法。代理对象会从SqlSessionFactory中获得SqlSession,并调用SqlSession执行数据库操作。
  4. SqlSession是由Spring使用SqlSessionFactory创建的。SqlSessionFactory是根据DataSource和Mapper XML或注解配置获得的。

所以,整个流程是:

  1. 注册Mapper接口(通常使用@MapperScan)
  2. Spring生成Mapper接口的代理对象
  3. 程序调用Mapper接口方法,实际上调用的是代理对象方法
  4. 代理对象通过SqlSession执行数据库操作
  5. SqlSession是从SqlSessionFactory获得的,SqlSessionFactory是从DataSource配置的

看完上面这个流程之后,我们可以很容易产生一个疑问:这个Mapper的代理对象是从哪里来的

这里我们就要引入另外一个重要的组件:MapperFactoryBean

MapperFactoryBean是一个工厂bean,用于生成Mapper接口代理对象。也就是说,Spring实际上是通过MapperFactoryBean来管理Mapper接口的。
当我们在Spring配置中注册一个Mapper接口 bean 时,Spring会使用MapperFactoryBean来生成这个bean

整个过程是:

  1. 在 Spring 配置中定义 Mapper接口bean,并指定 class 为 MapperFactoryBean:
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.example.mapper.UserMapper" />
    bean>
    
  2. Spring会实例化一个MapperFactoryBean,并注入指定的mapperInterface属性。
  3. Spring会从MapperFactoryBean中调用getObject()方法获取bean实例。
  4. 在getObject()方法中,MapperFactoryBean会构建Mapper接口的代理对象并返回。
  5. 返回的代理对象会被注册为id为userMapper的bean,这个bean就是UserMapper接口的代理实现。
  6. 然后我们可以像注入普通bean那样注入UserMapper,实际获得的是一个代理对象。

所以,MapperFactoryBean起到了一个工厂bean该有的作用 - 根据某些属性生成一个bean实例。这里,它根据mapperInterface属性生成了Mapper接口的代理对象bean。

可以看出,Spring管理Mapper接口的关键步骤之一就是使用MapperFactoryBean生成代理对象。而MapperFactoryBean正是MyBatis-Spring模块提供的一个工厂bean,用于这一目的。

接下来我们开始写模拟代码,效果如下图:

【手撕Spring源码】容器与Bean_第34张图片

public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
        try {
            //创建一个路径匹配资源解析器
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            //获取该路径下的所有class文件
            Resource[] resources = resolver.getResources("classpath:com/zyb/a05/mapper/**/*.class");
            //创建一个注解beanname生成器,方便后面bean的命名
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
            //创建MetadataReader的工厂,为MetadataReader的使用做准备
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
            for (Resource resource : resources) {
            	//拿到MetadataReader
                MetadataReader reader = factory.getMetadataReader(resource);
                //获得类的元数据
                ClassMetadata classMetadata = reader.getClassMetadata();
                //判断当前类是不是接口
                if (classMetadata.isInterface()) {
                	//开始生成对应MapperFactoryBean的定义
                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)  //指定Bean的类型
                            .addConstructorArgValue(classMetadata.getClassName())  
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                            .getBeanDefinition();
                     //这里的定义仅仅只是为了生成名字,所以我们后面没有向容器里进行注册
                    AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
                    String name = generator.generateBeanName(bd2, beanFactory);
                    //使用bd的定义、bd2的name进行注册
                    beanFactory.registerBeanDefinition(name, bd);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

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

    }
}

【手撕Spring源码】容器与Bean_第35张图片

有关后处理器(PostProcessor)的调用时期

BeanFactoryPostProcessor 的 postProcessBeanFactory() 方法会在以下时机被调用:

  1. 所有 BeanDefinition 被加载,但是还未实例化任何 bean 之前。
  2. 也就是说,它会在初始化容器时,修改 bean 的定义,为接下来的 bean 实例初始化做准备。

其调用时机在 Spring 容器初始化阶段,大致流程如下:

  1. Spring 资源加载,读取xml/注解等并解析生成 BeanDefinition。
  2. 调用 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry() 方法。此处可以添加/移除 BeanDefinition。
  3. 调用 BeanFactoryPostProcessor 的 postProcessBeanFactory() 方法。此处可以修改 BeanDefinition 属性等。
  4. bean 实例化阶段 - 根据 BeanDefinition 创建 bean 实例。
  5. 调用 BeanPostProcessor 的 postProcessBeforeInitialization() 方法。
  6. 调用初始化回调方法如@PostConstruct 或 init-method。
  7. 调用 BeanPostProcessor 的 postProcessAfterInitialization() 方法。
  8. 整个 Spring 容器加载完成。

一定心中有一个大致的顺序,Spring容器在初始化的时候首先解析得到bean的定义,根据定义在容器中注册bean,注册bean之后开始进入Bean的生命周期:创建 --> 依赖注入 --> 初始化 --> 可用 --> 销毁

所以,BeanFactoryPostProcessor 的调用时机明确在第 3 步 - 在所有的 BeanDefinition 加载完成但在任何 bean 实例化之前。我们可以利用这个时机点对 BeanDefinition 进行批量修改。

举个简单的例子:

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
            if (definition.getScope().equals(BeanDefinition.SCOPE_PROTOTYPE)) {
                definition.setScope(BeanDefinition.SCOPE_SINGLETON);
            }
        }
    }
} 

这个后置处理器会将所有 prototype 作用域的 bean 修改为 singleton 作用域。它的调用时机就在所有的 BeanDefinition 加载完成后,bean 实例化之前。所以它可以对 bean 进行批量修改。

另外,BeanDefinitionRegistryPostProcessor 的调用时机在 BeanFactoryPostProcessor 之前,它允许添加或移除 BeanDefinition。所以,如果同时实现了这两个接口,需要注意在 postProcessBeanDefinitionRegistry() 中定义的 BeanDefinition 在 postProcessBeanFactory() 中是可用的。

总结一下:

  • BeanFactoryPostProcessor 在 bean 实例化之前,BeanDefinition 加载完成后被调用。
  • 它可以批量修改 BeanDefinition 的属性,为后续的 bean 实例化做准备。
  • 其调用时机在 Spring 容器初始化过程中的一个阶段,在 BeanDefinitionRegistryPostProcessor 之后,bean 实例化之前。
  • 理解其调用时机,有助于我们更好地利用 BeanFactoryPostProcessor 定制化 bean 定义。
  • 当同时实现 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 时,需要注意二者调用顺序及其影响。

BeanFactoryPostProcessor 为我们提供了一个强大的扩展点,让我们可以在容器启动时定制修改 BeanDefinition。

Aware 接口 和 InitializingBean 接口

首先我们要知道Aware接口有什么用?

Aware接口是Spring框架中的一个接口,它可以让Bean感知到自己所处的环境,例如感知到Spring容器、应用上下文等。Spring框架提供了多个Aware接口,可以让Bean感知到不同的环境。

具体来说,当一个Bean实现了某个Aware接口时,Spring容器会自动调用该Bean对应的接口方法,并将相应的环境信息作为参数传递给该方法。通过这种方式,Bean可以获取到自己所处的环境信息,并进行相应的处理

也就是说:实现这些Aware接口,我们就可以在bean初始化的时候获得Spring容器中的一些对象,进而对bean进行进一步的操作。一句话说Aware 接口提供了一种【内置】 的注入手段

下面是几个常用的Aware接口:

  • BeanNameAware:该接口可以让Bean感知到自己在Spring容器中的名称。

  • ApplicationContextAware:该接口可以让Bean感知到自己所处的应用上下文。

  • BeanFactoryAware:该接口可以让Bean感知到自己所处的Bean工厂。

例如,以下代码演示了一个实现了ApplicationContextAware接口的Bean:

public class MyBean implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void doSomething() {
        // 使用ApplicationContext进行相应的处理
    }
}

在这个例子中,MyBean实现了ApplicationContextAware接口,并重写了setApplicationContext方法。在该方法中,我们保存了传入的ApplicationContext实例,以便在后续的处理中使用。

需要注意的是,Aware接口只是一种感知机制,它并不会改变Bean的行为。因此,如果一个Bean实现了Aware接口,但没有对相应的环境信息进行处理,那么该接口就没有实际作用。

然后我们再来看看InitializingBean接口,这是个什么东东?

InitializingBean是一个接口,它有一个方法:

void afterPropertiesSet() throws Exception;

实现这个接口的bean会在容器设置完所有必要属性之后,且在初始化之前调用afterPropertiesSet()方法

跟Aware接口一样我们可以把它理解为:
InitializingBean 接口提供了一种【内置】的初始化手段

这给我们提供了一个机会,在bean初始化之前对其进行一些自定义的初始化处理。比如,在afterPropertiesSet()方法中,我们可以:

  • 检查必要的属性是否都被设置
  • 对某些属性做最后的处理
  • 调用初始化方法等

一个简单的例子:

public class MyBean implements InitializingBean {
    private String message;
    
    public void setMessage(String message) {
        this.message = message;
    }
    
    @Override
    public void afterPropertiesSet() {
        System.out.println(message);
    }
}

然后在Spring配置中定义这个bean:

<bean id="myBean" class="com.example.MyBean">
    <property name="message" value="Init done!" /> 
bean> 

在Spring容器初始化这个bean时,会发生以下流程:

  1. 通过setter方法设置必要属性,此时message属性被设置为"Init done!"
  2. 调用afterPropertiesSet()方法,打印出message属性的值
  3. bean初始化完成

所以,afterPropertiesSet()方法就提供了一个回调,让我们在bean初始化之前加入自定义逻辑。这也是与Aware接口类似的一个目的,只不过方式上稍有不同:

  • Aware接口回调发生在依赖注入之后
  • 而InitializingBean回调发生在依赖注入之后,但在初始化之前。
  • Aware接口和InitializingBean接口的回调时机是在Bean实例化之后,因此它们都可以获取到Bean的实例,并进行相应的操作。但是,Aware接口的回调时机在依赖注入之前,因此它无法获取到其他Bean的实例。而InitializingBean接口的回调时机在依赖注入之后,因此它可以获取到其他Bean的实例,并进行相应的初始化工作。

关于Aware 接口 和 InitializingBean 接口,我们还要说

  • 内置的注入和初始化不受扩展功能的影响,总会被执行
  • 而扩展功能受某些情况影响可能会失效
  • 因此 Spring 框架内部的类常用内置注入和初始化

这几句话可能不是很好理解,我们举一个例子:

有的功能用 @Autowired 就能实现啊, 为啥还要用 Aware 接口呢?

简单地说:

  • @Autowired 的解析需要用到 bean 后处理器, 属于扩展功能
  • 而 Aware 接口属于内置功能, 不加任何扩展, Spring 就能识别
    某些情况下, 扩展功能会失效, 而内置功能不会失效

我们看一段代码:

@Configuration
public class MyConfig1 {

    private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        log.debug("注入 ApplicationContext");
    }

    @PostConstruct
    public void init() {
        log.debug("初始化");
    }

    @Bean //  beanFactory 后处理器
    public BeanFactoryPostProcessor processor1() {
        return beanFactory -> {
            log.debug("执行 processor1");
        };
    }

}

测试类:

/*
    Aware 接口及 InitializingBean 接口
 */
public class A06 {
    private static final Logger log = LoggerFactory.getLogger(A06.class);

    public static void main(String[] args) {

        GenericApplicationContext context = new GenericApplicationContext();

        context.registerBean("myConfig1", MyConfig1.class);

        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        context.registerBean(CommonAnnotationBeanPostProcessor.class);
        context.registerBean(ConfigurationClassPostProcessor.class);


        /*
            Java 配置类在添加了 bean 工厂后处理器后,
                你会发现用传统接口方式的注入和初始化仍然成功, 而 @Autowired 和 @PostConstruct 的注入和初始化失败
         */

        context.refresh(); // 1. beanFactory 后处理器,  2. 添加 bean 后处理器, 3. 初始化单例
        context.close();
    }
}

在执行了refresh()方法之后,容器中会大致依次执行以下操作(还有很多动作没有列出):

  • beanFactory 后处理器
  • 添加 bean 后处理器
  • 初始化单例

结果:
在这里插入图片描述
可以看出来@PostConstruct、@Autowired注解都失效了,这是为什么呢?

此处想要完全理解一定要弄清楚后处理器、 Aware 接口及 InitializingBean 接口的回调时机!!!

Java 配置类不包含 BeanFactoryPostProcessor 的情况

ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor 3. 创建和初始化 3.1 依赖注入扩展(如 @Value 和 @Autowired) 3.2 初始化扩展(如 @PostConstruct) 3.3 执行 Aware 及 InitializingBean 3.4 创建成功 ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类

Java 配置类包含 BeanFactoryPostProcessor 的情况

因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类(你不创建怎么执行方法得到Bean),而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效

ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类 3. 创建和初始化 3.1 执行 Aware 及 InitializingBean 3.2 创建成功 1. 执行 BeanFactoryPostProcessor 2. 注册 BeanPostProcessor ApplicationContext BeanFactoryPostProcessor BeanPostProcessor Java配置类

注意
解决方法:

  • 用内置依赖注入和初始化取代扩展依赖注入和初始化
  • 用静态工厂方法代替实例工厂方法,避免工厂对象提前被创建

如果需要在BeanFactoryPostProcessor中使用@Autowired和@PostConstruct注解,可以考虑使用ApplicationContextAware接口和InitializingBean接口来实现依赖注入和初始化。具体来说,可以实现ApplicationContextAware接口来获取ApplicationContext实例,然后使用该实例来进行依赖注入;同时,可以实现InitializingBean接口来实现Bean的初始化逻辑。

例如:

@Configuration
public class MyConfig1 implements ApplicationContextAware, InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
        log.debug("注入 ApplicationContext");
    }

    @Override
    public void afterPropertiesSet() {
        log.debug("初始化");
    }

    @Bean
    public BeanFactoryPostProcessor processor1() {
        return beanFactory -> {
            log.debug("执行 processor1");
        };
    }
}

在这个例子中,实现了ApplicationContextAware接口和InitializingBean接口,分别用于依赖注入和初始化。这样,在BeanFactoryPostProcessor执行时,可以通过ApplicationContext实例来进行依赖注入;同时,在Bean实例化后,可以通过InitializingBean接口的实现方法来进行初始化。

初始化和销毁

Spring 提供了多种初始化手段,除了@PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是

  1. @PostConstruct 标注的初始化方法
  2. InitializingBean 接口的初始化方法
  3. @Bean(initMethod) 指定的初始化方法

与初始化类似,Spring 也提供了多种销毁手段,执行顺序为

  1. @PreDestroy 标注的销毁方法
  2. DisposableBean 接口的销毁方法
  3. @Bean(destroyMethod) 指定的销毁方法

示例代码:

@SpringBootApplication
public class A07_1 {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A07_1.class, args);
        context.close();

    }

    @Bean(initMethod = "init3")
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean(destroyMethod = "destroy3")
    public Bean2 bean2() {
        return new Bean2();
    }
}
public class Bean1 implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    @PostConstruct
    public void init1() {
        log.debug("初始化1");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化2");
    }

    public void init3() {
        log.debug("初始化3");
    }
}
public class Bean2 implements DisposableBean {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    @PreDestroy
    public void destroy1() {
        log.debug("销毁1");
    }

    @Override
    public void destroy() throws Exception {
        log.debug("销毁2");
    }

    public void destroy3() {
        log.debug("销毁3");
    }
}

Scope

在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope

  • singleton,容器启动时创建(未设置延迟),容器关闭时销毁(单例与容器同生共死)
  • prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁(也就是说多例的销毁不归容器管理)
  • request,每次请求用到此 bean 时创建,请求结束时销毁
  • session,每个会话用到此 bean 时创建,会话结束时销毁
  • application,web 容器用到此 bean 时创建,容器停止时销毁

有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃

但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有

  • @Lazy
  • @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
  • ObjectFactory
  • ApplicationContext.getBean

解决方法虽然不同,但理念上殊途同归: 都是推迟其它 scope bean 的获取

我们举一个例子:

以单例注入多例为例

有一个单例对象 E

@Component
public class E {
    private static final Logger log = LoggerFactory.getLogger(E.class);

    private F f;

    public E() {
        log.info("E()");
    }

    @Autowired
    public void setF(F f) {
        this.f = f;
        log.info("setF(F f) {}", f.getClass());
    }

    public F getF() {
        return f;
    }
}

要注入的对象 F 期望是多例

@Component
@Scope("prototype")
public class F {
    private static final Logger log = LoggerFactory.getLogger(F.class);

    public F() {
        log.info("F()");
    }
}

测试

E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);

输出

com.zyb.demo.cycle.F@6622fc65
com.zyb.demo.cycle.F@6622fc65

发现它们是同一个对象,而不是期望的多例对象

对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F

e 创建
e set 注入 f
f 创建

解决

  • 仍然使用 @Lazy 生成代理
  • 代理对象虽然还是同一个,但当每次使用代理对象的任意方法时,由代理创建新的 f 对象
使用f方法
使用f方法
使用f方法
e 创建
e set 注入 f代理
f 创建
f 创建
f 创建
@Component
public class E {

    @Autowired
    @Lazy
    public void setF(F f) {
        this.f = f;
        log.info("setF(F f) {}", f.getClass());
    }

    // ...
}

注意

  • @Lazy 加在也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
  • @Autowired 加在 set 方法的目的类似

输出

E: setF(F f) class com.zyb.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.zyb.demo.cycle.F@3a6f2de3
F: F()
com.zyb.demo.cycle.F@56303b57

从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型

你可能感兴趣的:(手撕框架源码,spring,spring,boot,java,源码)