# 容器与 bean

# 一。容器接口

以 SpringBoot 的启动类为例:

@Slf4j  
@SpringBootApplication  
public class A01Application {  
    public static void main(String[] args) {  
        SpringApplication.run(A01Application.class, args);  
    }  
}

其中的  run()  方法是有返回值的:

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

在 IDEA 中使用快捷键 Ctrl + Alt + U 查看  ConfigurableApplicationContext  类的类图:
image-20230225223613865
ConfigurableApplicationContext  接口继承了  ApplicationContext  接口,而  ApplicationContext  接口又间接地继承了  BeanFactory  接口,除此之外还继承了其他很多接口,相当于对  BeanFactory  进行了拓展。

# 1. 什么是 BeanFactory

  • 它是  ApplicationContext  的父接口
  • 它才是 Spring 的核心容器,主要的  ApplicationContext  实现 组合 了它的功能,也就是说, BeanFactory  是  ApplicationContext  中的一个成员变量。

常用的  context.getBean("xxx")  方法,其实是调用了  BeanFactory  的  getBean()  方法。

# 2.BeanFactory 能做什么?

进入  BeanFactory  接口,在 IDEA 中使用快捷键 Ctrl + F12 查看这个接口中所有的方法定义:

通过这些方法定义可知, BeanFactory  表面上只有  getBean()  方法,但实际上 Spring 中的控制反转、基本的依赖注入、乃至 Bean 的生命周期中的各个功能都是由它的实现类提供。

查看  DefaultListableBeanFactory  类的类图:


DefaultListableBeanFactory  实现了  BeanFactory  接口,它能管理 Spring 中所有的 Bean,当然也包含 Spring 容器中的那些单例对象。

DefaultListableBeanFactory  还继承了  DefaultSingletonBeanRegistry  类,这个类就是用来管理 Spring 容器中的单例对象。

在 IDEA 提供的类图中选中  DefaultSingletonBeanRegistry ,然后按下 F4 进入这个类。它有一个  Map  类型的成员变量  singletonObjects

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

Map  的 key 就是 Bean 的名字,而 value 是对应的 Bean,即单例对象。

现有如下两个 Bean:

@Component  
public class Component1 {  
}  
  
@Component  
public class Component2 {  
}

查看  singletonObjects  中是否存在这两个 Bean 的信息:

@Slf4j  
@SpringBootApplication  
public class A01Application {  
    public static void main(String[] args) {  
        ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);  
  
        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()));  
  
        context.close();  
    }  
}

运行  main()  方法后,控制台打印出:

component1=indi.mofan.bean.a01.Component1@25a5c7db
component2=indi.mofan.bean.a01.Component2@4d27d9d

# 3.ApplicationContext 的功能

回顾  ConfigurableApplicationContext  类的类图:
image-20230225223613865
ApplicationContext  除了继承  BeanFactory  外,还继承了:

  • MessageSource:使其具备处理国际化资源的能力
  • ResourcePatternResolver:使其具备使用通配符进行资源匹配的能力
  • EnvironmentCapable:使其具备读取 Spring 环境信息、配置文件信息的能力
  • ApplicationEventPublisher:使其具备发布事件的能力

MessageSource  的使用

在 SpringBoot 项目的 resources 目录下创建 messages.properties、messages_en.properties、messages_zh_CN.properties、messages_zh_TW.properties 四个国际化文件,除 messages.properties 外,其余三个文件内容如下:

es
thanks=Thank you
es
thanks=谢谢
es
thanks=謝謝

测试  MessageSource  接口中  getMessage()  方法的使用:

public static void main(String[] args) {  
    ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);  
    // --snip--  
  
    System.out.println(context.getMessage("thanks", null, Locale.ENGLISH));  
    System.out.println(context.getMessage("thanks", null, Locale.SIMPLIFIED_CHINESE));  
    System.out.println(context.getMessage("thanks", null, Locale.TRADITIONAL_CHINESE));  
    context.close();  
}

运行  main()  方法后,控制台打印出:

Thank you
谢谢
謝謝

国际化资源由  ResourceBundleMessageSource  进行处理,使用 “干净” 的 Spring 容器  GenericApplicationContext ,并添加对应的 Bean:

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
  
    context.registerBean("messageSource", MessageSource.class, () -> {  
        ResourceBundleMessageSource ms = new ResourceBundleMessageSource();  
        // 设置编码格式  
        ms.setDefaultEncoding("utf-8");  
        // 设置国际化资源文件的 basename  
        ms.setBasename("messages");  
        return ms;  
    });  
  
    context.refresh();  
  
    System.out.println(context.getMessage("thanks", null, Locale.ENGLISH));  
    System.out.println(context.getMessage("thanks", null, Locale.SIMPLIFIED_CHINESE));  
    System.out.println(context.getMessage("thanks", null, Locale.TRADITIONAL_CHINESE));  
}

ResourcePatternResolver  的使用

public static void main(String[] args) {  
    ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);  
    // --snip--  
  
    Resource[] resources = context.getResources("classpath:application.properties");  
    assert resources.length > 0;  
  
    // 使用 classpath* 可以加载 jar 里类路径下的 resource  
    resources = context.getResources("classpath*:META-INF/spring.factories");  
    assert resources.length > 0;  
    context.close();  
}

EnvironmentCapable  的使用

public static void main(String[] args) {  
    ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);  
    // --snip--  
  
    System.out.println(context.getEnvironment().getProperty("java_home"));  
    System.out.println(context.getEnvironment().getProperty("server.port"));
    context.close();  
}

java_home  是从环境变量中读取, properties.name  则是从 application.yml 配置文件中读取。

#{"author.name":"mofan"}  
properties:  
  name: "mofan"  
  age: 20  
person:  
  gender: "man"

运行  main()  方法后,控制台打印出:

D:\java
8080

ApplicationEventPublisher  的使用

定义事件类  UserRegisteredEvent

public class UserRegisteredEvent extends ApplicationEvent {  
    public UserRegisteredEvent(Object source) {  
        super(source);  
    }  
}

将  Component1  作为发送事件的 Bean:

@Component  
public class Component1 {  
  
    private static final Logger log = LoggerFactory.getLogger(Component1.class);  
  
    @Autowired  
    private ApplicationEventPublisher context;  
  
    public void register() {  
        System.out.println("用户注册");  
        log.debug("用户注册");  
        context.publishEvent(new UserRegisteredEvent(this));  
    }  
}

将  Component2  作为事件监听器:

@Component  
public class Component2 {  
  
    private static final Logger log = LoggerFactory.getLogger(Component2.class);  
  
    @EventListener  
    public void aaa(UserRegisteredEvent event) {  
        System.out.println("{"+event+"}");  
        log.debug("{}", event);  
        System.out.println("发送短信");  
        log.debug("发送短信");  
    }  
}

在  main()  方法中使用  Component1  发送事件:

public static void main(String[] args) {  
    ConfigurableApplicationContext context = SpringApplication.run(A01Application.class, args);    
    context.getBean(Component1.class).register();  
    context.close();  
}

运行  main()  方法后,控制台打印出:

用户注册
[DEBUG] 21:46:36.119 [main] com.kangkang.a01.Component1         - 用户注册 
{com.kangkang.a01.UserRegisteredEvent[source=com.kangkang.a01.Component1@20440c6c]}
[DEBUG] 21:46:36.120 [main] com.kangkang.a01.Component2         - com.kangkang.a01.UserRegisteredEvent[source=com.kangkang.a01.Component1@20440c6c] 
发送短信
[DEBUG] 21:46:36.121 [main] com.kangkang.a01.Component2         - 发送短信 

# 二、容器实现

# 1.BeanFactory 的实现

现有如下类,尝试将  Config  添加到 Bean 工厂中:

@Configuration  
static class Config {  
    @Bean  
    public Bean1 bean1() {  
        return new Bean1();  
    }  
  
    @Bean  
    public Bean2 bean2() {  
        return new Bean2();  
    }  
}  
  
@Slf4j  
static class Bean1 {  
    public Bean1() {  
        log.debug("构造 Bean1()");  
    }  
  
    @Autowired  
    private Bean2 bean2;  
  
    public Bean2 getBean2() {  
        return bean2;  
    }  
}  
  
@Slf4j  
static class Bean2 {  
    public Bean2() {  
        log.debug("构造 Bean2()");  
    }  
}

需要使用到  BeanFactory  的一个实现类:  DefaultListableBeanFactory 。有了 Bean 工厂,还需要定义 Bean,之后再把定义的 Bean 注册到工厂即可。

public static void main(String[] args) {  
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();  
    //bean 的定义(class,scope,初始化,销毁)  
    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(Config.class)  
            .setScope("singleton")  
            .getBeanDefinition();  
    beanFactory.registerBeanDefinition("config", beanDefinition);  
  
    // 只有 config  
    Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);  
}
config

现在 Bean 工厂中 有且仅有一个 名为  config  的 Bean。

解析配置类

根据对  @Configuration  和  @Bean  两个注解的认识可知,Bean 工厂中应该还存在  bean1  和  bean2 ,那为什么现在没有呢?

很明显是现在的  BeanFactory  缺少了解析  @Configuration  和  @Bean  两个注解的能力。

public static void main(String[] args) {  
    // 给 BeanFactory 添加一些常用的后置处理器  
    AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);  
    Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);  
}
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

根据打印出的信息,可以看到有一个名为  org.springframework.context.annotation.internalConfigurationAnnotationProcessor  的 Bean,根据其所含的  ConfigurationAnnotationProcessor  字样,可以知道这个 Bean 就是用来处理  @Configuration  和  @Bean  注解的,将配置类中定义的 Bean 信息补充到  BeanFactory  中。

那为什么在 Bean 工厂中依旧没有  bean1  和  bean2  呢?

现在仅仅是将处理器添加到了 Bean 工厂,还没有使用处理器。

使用处理器很简单,先获取到处理器,然后再使用即可。像  internalConfigurationAnnotationProcessor  这样的 Bean,都有一个共同的类型,名为  BeanFactoryPostProcessor ,因此可以:

public static void main(String[] args) {  
    // 使用后置处理器  
    // BeanFactoryPostProcessor 补充了一些 Bean 的定义  
    beanFactory.getBeansOfType(BeanFactoryPostProcessor.class).values().forEach(i -> i.postProcessBeanFactory(beanFactory));  
    Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);  
}
config
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean1
bean2

依赖注入

bean1  和  bean2  已经被添加到 Bean 工厂中,尝试获取  bean1  中的  bean2 ,查看  bean2  是否成功注入到  bean1  中:

public static void main(String[] args) {  
    // --snip--  
    System.out.println(beanFactory.getBean(Bean1.class).getBean2());  
}
null

bean2  没有成功被注入到  bean1  中。

在先前添加到  BeanFactory  中的后置处理器里,有名为  internalAutowiredAnnotationProcessor  和  internalCommonAnnotationProcessor  的两个后置处理器。前者用于解析  @Autowired  注解,后者用于解析  @Resource  注解,它们都有一个共同的类型  BeanPostProcessor ,因此可以:

public static void main(String[] args) {  
    // --snip--  
    System.out.println("---------------------------------------------");  
    // Bean 后置处理器,对 Bean 的生命周期的各个阶段提供拓展,例如 @AutoWired @Resource...  
    beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);  
    System.out.println(beanFactory.getBean(Bean1.class).getBean2());  
}
补充定义后,输出bean1中的bean2-->
[DEBUG] 15:08:32.146 [main] c.k.a02.TestBeanFactory$Bean1       - 构造 Bean1() 
Logger[com.kangkang.a02.TestBeanFactory$Bean1]
[DEBUG] 15:08:32.161 [main] c.k.a02.TestBeanFactory$Bean2       - 构造 Bean2() 
com.kangkang.a02.TestBeanFactory$Bean2@352c1b98

建立  BeanPostProcessor  和  BeanFactory  的关系后, bean2  被成功注入到  bean1  中了。

除此之外还可以发现:当需要使用 Bean 时,Bean 才会被创建,即按需加载。那有没有什么办法预先就初始化好单例对象呢?

public static void main(String[] args) {  
    // 预先初始化单例对象(完成依赖注入和初始化流程)  
    beanFactory.preInstantiateSingletons();  
    System.out.println("---------------------------------------------");  
    beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);  
    System.out.println(beanFactory.getBean(Bean1.class).getBean2());  
}
补充定义后,输出bean1中的bean2-->
[DEBUG] 15:08:32.146 [main] c.k.a02.TestBeanFactory$Bean1       - 构造 Bean1() 
Logger[com.kangkang.a02.TestBeanFactory$Bean1]
[DEBUG] 15:08:32.161 [main] c.k.a02.TestBeanFactory$Bean2       - 构造 Bean2() 
com.kangkang.a02.TestBeanFactory$Bean2@352c1b98

目前可以知道, BeanFactory  不会 :

  • 主动调用  BeanFactory  后置处理器;
  • 主动添加  Bean  后置处理器;
  • 主动初始化单例对象;
  • 解析  ${}  和  #{}

后置处理器的排序

在最初给出的类信息中进行补充:

@Configuration  
static class Config {  
    // --snip--  
  
    @Bean  
    public Bean3 bean3() {  
        return new Bean3();  
    }  
  
    @Bean  
    public Bean4 bean4() {  
        return new Bean4();  
    }  
}  
  
interface Inter {  
  
}  
  
@Slf4j  
static class Bean3 implements Inter {  
    public Bean3() {  
        log.debug("构造 Bean3()");  
    }  
}  
  
@Slf4j  
static class Bean4 implements Inter {  
    public Bean4() {  
        log.debug("构造 Bean4()");  
    }  
}  
  
@Slf4j  
static class Bean1 {  
    // --snip--  
  
    @Autowired  
    @Resource(name = "bean4")  
    private Inter bean3;  
  
    private Inter getInter() {  
        return bean3;  
    }  
}

向 Bean 工厂中添加了  bean3  和  bean4 ,并且计划在  bean1  中注入  Inter  类型的 Bean。

现在 Bean 工厂中  Inter  类型的 Bean 有两个,分别是  bean3bean4 ,那么会注入哪一个呢?

如果只使用  @Autowired ,首先会按照类型注入,如果同种类型的 Bean 有多个,再按照变量名称注入,如果再注入失败,就报错;如果只使用  @Resource ,也会采取与  @Autowired  一样的注入策略,只不过  @Resource  注解还可以指定需要注入 Bean 的 id(使用  name  属性进行指定),如果指定了需要注入 Bean 的 id,就直接按照指定的 id 进行注入,如果失败就报错。

那如果即使用  @Autowired  又使用  @Resource(name = "bean4")  呢?

public static void main(String[] args) {  
    System.out.println(beanFactory.getBean(Bean1.class).getInter());  
}
com.kangkang.a02.TestBeanFactory$Bean3@21129f1f

根据打印的结果可知, @Autowired  先生效了,这是因为  internalAutowiredAnnotationProcessor  排在  internalCommonAnnotationProcessor  之前。可以查看它们的先后关系:

public static void main(String[] args) {  
    // --snip--  
    beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(i -> {  
        System.out.println(">>>> " + i);  
        beanFactory.addBeanPostProcessor(i);  
    });  
}
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@6385cb26
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@38364841

也可以改变它们的顺序,然后再查看注入的是  bean3  还是  bean4

public static void main(String[] args) {  
    beanFactory.getBeansOfType(BeanPostProcessor.class).values().stream()  
        .sorted(Objects.requireNonNull(beanFactory.getDependencyComparator()))  
        .forEach(i -> {  
            System.out.println(">>>> " + i);  
            beanFactory.addBeanPostProcessor(i);  
        });  
    System.out.println(beanFactory.getBean(Bean1.class).getInter());  
}
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@6385cb26
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@38364841
com.kangkang.a02.TestBeanFactory$Bean3@21129f1f

改变  BeanPostProcessor  的先后顺序后, @Resource(name = "bean4")  生效了,成功注入了  bean4

为什么使用  beanFactory.getDependencyComparator()  后就改变了  BeanPostProcessor  的先后顺序呢?

在调用的  AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);  方法源码中有:

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {  
    registerAnnotationConfigProcessors(registry, null);  
}  
  
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(  
    BeanDefinitionRegistry registry, @Nullable Object source) {  
  
    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);  
    if (beanFactory != null) {  
        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {  
            // 设置比较器  
            beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);  
        }  
        if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {  
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());  
        }  
    }  
}

设置的  AnnotationAwareOrderComparator  比较器会根据设置的  order  信息进行比较。

AutowiredAnnotationBeanPostProcessor  设置的  order  是:

private int order = Ordered.LOWEST_PRECEDENCE - 2;

CommonAnnotationBeanPostProcessor  设置的  order  是:

public CommonAnnotationBeanPostProcessor() {  
    setOrder(Ordered.LOWEST_PRECEDENCE - 3);    
}

值越小,优先级越大,就排在更前面,因此当设置了  AnnotationAwareOrderComparator  比较器后, CommonAnnotationBeanPostProcessor  排在更前面, @Resource  就先生效。

# 2.ApplicationContext 的实现

@Slf4j  
public class A02Application {  
  
    static class Bean1 {  
  
    }  
  
	static class Bean2 {  
	  
	    private Bean1 bean1;  
	  
	    public void setBean1(Bean1 bean1) {  
	        this.bean1 = bean1;  
	    }  
	  
	    public Bean1 getBean1() {  
	        return bean1;  
	    }  
	}
}

ClassPathXmlApplicationContext

较为经典的容器,基于 classpath 下的 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">  
  
    <!-- 控制反转,让 bean1 被 Spring 容器管理 -->  
    <bean id="bean1" class="com.kangkang.a02.TestApplicationContext.Bean1"/>  
  
    <!-- 控制反转,让 bean2 被 Spring 容器管理 -->  
    <bean id="bean2" class="com.kangkang.a02.TestApplicationContext.Bean2">  
        <!-- 依赖注入,建立与 bean1 的依赖关系 -->  
        <property name="bean1" ref="bean1"/>  
    </bean></beans>
private static void testClassPathXmlApplicationContext() {  
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("b01.xml");  
    Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);  
    System.out.println(context.getBean(Bean2.class).getBean1());  
}
bean1
bean2
com.kangkang.a02.TestApplicationContext$Bean1@127a7a2e

FileSystemXmlApplicationContext

与  ClassPathXmlApplicationContext  相比, FileSystemXmlApplicationContext  是基于磁盘路径下 xml 格式的配置文件来创建。

private static void testFileSystemXmlApplicationContext() {  
    // 使用相对路径时,以模块为起点(IDEA 中需要设置 Working directory),也支持绝对路径  
    FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("bean\\src\\main\\resources\\b01.xml");  
    Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);  
    System.out.println(context.getBean(Bean2.class).getBean1());  
}
bean1
bean2
com.kangkang.a02.TestApplicationContext$Bean1@5495333e

从 XML 文件中读取 Bean 的定义

ClassPathXmlApplicationContext  和  FileSystemXmlApplicationContext  都依赖于从 XML 文件中读取 Bean 的信息,而这都利用了  XmlBeanDefinitionReader  进行读取。

private static void testXmlBeanDefinitionReader() {  
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();  
        System.out.println("读取之前...");  
        Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);  
        System.out.println("读取之后...");  
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);  
//        reader.loadBeanDefinitions(new ClassPathResource("b01.xml"));  
        reader.loadBeanDefinitions(new FileSystemResource("bean\\src\\main\\resources\\b01.xml"));  
        Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);  
    }
读取之前...
读取之后...
bean1
bean2

AnnotationConfigApplicationContext

基于 Java 配置类来创建。首先定义配置类:

@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;  
    }  
}
// ⬇️较为经典的容器,基于 java 配置类来创建  
private static void testAnnotationConfigApplicationContext() {  
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);  
    for (String beanDefinitionName : context.getBeanDefinitionNames()) {  
        System.out.println(beanDefinitionName);  
    }  
    System.out.println(context.getBean(Bean2.class).getBean1());  
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
testApplicationContext.Config
bean1
bean2
com.kangkang.a02.TestApplicationContext$Bean1@3899782c

与前面两种基于 XML 创建  ApplicationContext  的方式相比,使用  AnnotationConfigApplicationContext  后,使得容器中多了一些后置处理器相关的 Bean。

如果要在先前的两种方式中也添加上这些 Bean,可以在 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"  
       xmlns:context="http://www.springframework.org/schema/context"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">  
    <bean id="bean1" class="indi.mofan.bean.a02.A02Application.Bean1"/>  
  
    <bean id="bean2" class="indi.mofan.bean.a02.A02Application.Bean2">  
        <property name="bean1" ref="bean1" />  
    </bean>  
  
    <!--  添加后置处理器  -->  
    <context:annotation-config />  
</beans>

AnnotationConfigServletWebServerApplicationContext

@Configuration  
static class WebConfig {  
    @Bean  
    public ServletWebServerFactory servletWebServerFactory() {  
        return new TomcatServletWebServerFactory();  
    }  
  
    @Bean  
    public DispatcherServlet dispatcherServlet() {  
        return new DispatcherServlet();  
    }  
  
    @Bean  
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {  
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");  
    }  
  
    @Bean("/hello")  
    public Controller controller() {  
        return (request, response) -> {  
            response.getWriter().print("hello");  
            return null;        };  
    }  
  
}
// ⬇️较为经典的容器,基于 java 配置类来创建,用于 web 环境  
private static void testAnnotationConfigServletWebServerApplicationContext() {  
    AnnotationConfigServletWebServerApplicationContext context =  
            new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);  
    for (String beanDefinitionName : context.getBeanDefinitionNames()) {  
        System.out.println(beanDefinitionName);  
    }  
}

运行代码,在浏览器中访问  http://localhost:8080/hello  路径则会显示出  hello  字样:

# 三、Bean 的生命周期

自定义一个 SpringBoot 的主启动类:

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

在启动类所在路径下再定义一个类,使其能够被自动装配:

@Component  
public class LifeCycleBean {  
  
    private static final Logger log = LoggerFactory.getLogger(LifeCycleBean.class);  
  
    public LifeCycleBean() {  
        log.info("构造");  
        System.out.println(log);  
    }  
  
    @Autowired  
    public void autowire(@Value("${JAVA_HOME}") String home) {  
        log.debug("依赖注入:{}", home);  
    }  
  
    @PostConstruct  
    public void init() {  
        log.debug("初始化");  
    }  
  
    @PreDestroy  
    public void destroy() {  
        log.debug("销毁");  
    }  
}

运行主启动类,查看控制台的日志信息(只列举主要信息):

[INFO ] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean      - 构造 
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean      - 依赖注入:D:\java 
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean      - 初始化 
[DEBUG] 15:31:41.679 [main] com.kangkang.a03.LifeCycleBean      - 销毁 

除此之外,Spring 还提供了一些对 Bean 生命周期的各个阶段进行拓展的  BeanPostProcessor ,比如  InstantiationAwareBeanPostProcessor  和  DestructionAwareBeanPostProcessor

实现这两个接口,并使用  @Component  注解标记实现类:

@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 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;  
    }  
}

再运行主启动类,查看控制台的日志信息(只列举主要信息):

[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor  - <<<<<< 实例化之前执行, 这里返回的对象会替换掉原本的 bean 
[INFO ] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean      - 构造 
Logger[com.kangkang.a03.LifeCycleBean]
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor  - <<<<<< 实例化之后执行, 这里如果返回 false 会跳过依赖注入阶段 
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor  - <<<<<< 依赖注入阶段执行, 如 @Autowired、@Value、@Resource 
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean      - 依赖注入:D:\java 
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor  - <<<<<< 初始化之前执行, 这里返回的对象会替换掉原本的 bean, 如 @PostConstruct、@ConfigurationProperties 
[DEBUG] 15:31:41.140 [main] com.kangkang.a03.LifeCycleBean      - 初始化 
[DEBUG] 15:31:41.140 [main] c.kangkang.a03.MyBeanPostProcessor  - <<<<<< 初始化之后执行, 这里返回的对象会替换掉原本的 bean, 如代理增强 
[INFO ] 15:31:41.262 [main] o.s.b.a.w.s.WelcomePageHandlerMapping - Adding welcome page: class path resource [static/index.html] 
[INFO ] 15:31:41.457 [main] o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"] 
[INFO ] 15:31:41.462 [main] o.s.b.w.e.tomcat.TomcatWebServer    - Tomcat started on port(s): 8080 (http) with context path '' 
[INFO ] 15:31:41.479 [main] com.kangkang.a03.A03                - Started A03 in 1.299 seconds (JVM running for 2.028) 
[DEBUG] 15:31:41.679 [main] c.kangkang.a03.MyBeanPostProcessor  - <<<<<< 销毁之前执行, 如 @PreDestroy 
[DEBUG] 15:31:41.679 [main] com.kangkang.a03.LifeCycleBean      - 销毁 

为什么实现了  BeanPostProcessor  接口后就能够在 Bean 生命周期的各个阶段进行拓展呢?

这使用了模板方法设计模式。

现有如下代码,模拟  BeanFactory  构造 Bean:

static class MyBeanFactory {  
    private final List<BeanPostProcessor> processors = new ArrayList<>();  
  
    public Object getBean() {  
        Object bean = new Object();  
        System.out.println("构造 " + bean);  
        System.out.println("依赖注入 " + bean);   
        System.out.println("初始化 " + bean);  
        return bean;  
    }  
}

假设现在需要在依赖注入之后,初始化之前进行其他的操作,那首先能想到的就是在这个位置直接书写相关操作的代码,但这会使代码更加臃肿、增加耦合性,显然不是一种好方式。

可以定义一个接口:

interface BeanPostProcessor {  
    void inject(Object bean);  
}

然后对  MyBeanFactory  进行修改:

static class MyBeanFactory {  
    private final List<BeanPostProcessor> processors = new ArrayList<>();  
  
    public Object getBean() {  
        Object bean = new Object();  
        System.out.println("构造 " + bean);  
        System.out.println("依赖注入 " + bean);  
        for (BeanPostProcessor processor : processors) {  
            processor.inject(bean);  
        }  
        System.out.println("初始化 " + bean);  
        return bean;  
    }  
    public void addBeanPostProcessor(BeanPostProcessor processor) {  
        processors.add(processor);  
    }  
}

之后如果需要拓展,调用  MyBeanFactory  实例的  addProcessor()  方法添加拓展逻辑即可:

public static void main(String[] args) {  
    MyBeanFactory beanFactory = new MyBeanFactory();  
    beanFactory.addBeanPostProcessor(bean -> System.out.println("解析@Autowired"));  
    beanFactory.addBeanPostProcessor(bean -> System.out.println("解析@Resource"));  
    Object bean = beanFactory.getBean();  
}
构造 java.lang.Object@7823a2f9
依赖注入 java.lang.Object@7823a2f9
解析@Autowired
解析@Resource
初始化 java.lang.Object@7823a2f9

Bean 生命周期图:

graph LR
创建 --> 依赖注入
依赖注入 --> 初始化
初始化 --> 可用
可用 --> 销毁

# 四、Bean 后置处理器

# 1. 常见的 Bean 后置处理器

现有如下三个类:

public class Bean1 {  
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);  
  
    private Bean2 bean2;  
  
    @Autowired  
    public void setBean2(Bean2 bean2) {  
        log.debug("@Autowired 生效: {}", bean2);  
        this.bean2 = bean2;  
    }  
  
    @Autowired  
    private Bean3 bean3;  
  
    @Resource  
    public void setBean3(Bean3 bean3) {  
        log.debug("@Resource 生效: {}", bean3);  
        this.bean3 = bean3;  
    }  
  
    private String home;  
  
    @Autowired  
    public void setHome(@Value("${JAVA_HOME}") String home) {  
        log.debug("@Value 生效: {}", home);  
        this.home = home;  
    }  
  
    @PostConstruct  
    public void init() {  
        log.debug("@PostConstruct 生效");  
    }  
  
    @PreDestroy  
    public void destroy() {  
        log.debug("@PreDestroy 生效");  
    }  
  
    @Override  
    public String toString() {  
        return "Bean1{" +  
               "bean2=" + bean2 +  
               ", bean3=" + bean3 +  
               ", home='" + home + '\'' +  
               '}';  
    }  
}
public class Bean2 {  
}
public class Bean3 {  
}

Bean2  和  Bean3  很简单,而在  Bean1  中使用了多个注解以实现 Bean 注入和值注入。

public class A04Application {  
    public static void main(String[] args) {  
        // GenericApplicationContext 是一个干净的容器  
        GenericApplicationContext context = new GenericApplicationContext();  
        // 用原始方式注册三个 bean  
        context.registerBean("bean1", Bean1.class);  
        context.registerBean("bean2", Bean2.class);  
        context.registerBean("bean3", Bean3.class);  
  
        // 初始化容器。执行 beanFactory 后置处理器,添加 bean 后置处理器,初始化所有单例  
        context.refresh();  
  
        // 销毁容器  
        context.close();  
    }  
}

运行上述方法后,控制台中只打印了与 Spring 相关的日志信息,也就是说  Bean1  中使用的注解并没有生效。

向  GenericApplicationContext  添加一些与 Bean 后置处理器相关的 Bean,使得  Bean1  中使用的注解能够生效。

public static void main(String[] args) {  
  
    context.registerBean("bean3", Bean3.class);  
  
    // 解析值注入内容  
    context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());  
    // @Autowired @Value  
    context.registerBean(AutowiredAnnotationBeanPostProcessor.class);  
  
    context.refresh();  
}
[DEBUG] 15:39:55.390 [main] com.kangkang.a04.Bean1              - @Resource 生效: com.kangkang.a04.Bean3@78fa769e 
[DEBUG] 15:39:55.413 [main] com.kangkang.a04.Bean1              - @Value 生效: D:\java 
public static void main(String[] args) {
    // @Resource @PostConstruct @PreDestroy  
    context.registerBean(CommonAnnotationBeanPostProcessor.class);  
}
[DEBUG] 15:39:55.390 [main] com.kangkang.a04.Bean1              - @Resource 生效: com.kangkang.a04.Bean3@78fa769e 
[DEBUG] 15:39:55.413 [main] com.kangkang.a04.Bean1              - @Value 生效: D:\java 
[DEBUG] 15:39:55.421 [main] com.kangkang.a04.Bean1              - @Autowired 生效: com.kangkang.a04.Bean2@3fce8fd9 
[DEBUG] 15:39:55.422 [main] com.kangkang.a04.Bean1              - @PostConstruct 生效 
Bean1{bean2=com.kangkang.a04.Bean2@3fce8fd9, bean3=com.kangkang.a04.Bean3@78fa769e, home='D:\java'}
[DEBUG] 15:39:55.424 [main] com.kangkang.a04.Bean1              - @PreDestroy 生效 

解析 @ConfigurationProperties

使用  @ConfigurationProperties  可以指定配置信息的前缀,使得配置信息的读取更加简单。比如:

@ConfigurationProperties(prefix = "java")  
public class Bean4 {  
  
    private String home;  
  
    private String version;  
  
    public String getHome() {  
        return home;  
    }  
  
    public void setHome(String home) {  
        this.home = home;  
    }  
  
    public String getVersion() {  
        return version;  
    }  
  
    public void setVersion(String version) {  
        this.version = version;  
    }  
  
    @Override  
    public String toString() {  
        return "Bean4{" +  
               "home='" + home + '\'' +  
               ", version='" + version + '\'' +  
               '}';  
    }  
}

上述代码用于获取环境变量中  java.home  和  java.version  的信息。

对先前的  main()  方法进行补充:

public static void main(String[] args) {  
    // --snip--  
      
    context.registerBean("bean4", Bean4.class);  
  
    // --snip--  
  
    System.out.println(context.getBean(Bean4.class));  
  
    // --snip--  
}
Bean4(home=null, version=null)

Bean4  成功添加到容器中,但值注入失败了,显然也是因为缺少解析  @ConfigurationProperties  注解的后置处理器。

public static void main(String[] args) {  
    // --snip--  
      
    context.registerBean("bean4", Bean4.class);  
  
    // --snip--  
    // @ConfigurationProperties  
        ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());  
  
    System.out.println(context.getBean(Bean4.class));  
  
    // --snip--  
}
Bean4{home='D:\Java', version='11.0.12'}

# 2.AutowiredAnnotationBeanPostProcessor

通过前文可知  AutowiredAnnotationBeanPostProcessor  用于解析  @Autowired  和  @Value  注解,那它究竟是怎么工作的呢?

public static void main(String[] args) {  
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();  
    // 注册成品 Bean,不再进行 Bean 的创建、依赖注入、初始化等操作  
    beanFactory.registerSingleton("bean2", new Bean2());  
    beanFactory.registerSingleton("bean3", new Bean3());  
    // @Value  
    beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());  
  
    // 查看哪些属性、方法加了 @Autowired,这称之为 InjectionMetadata  
    AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor();  
    postProcessor.setBeanFactory(beanFactory);  
  
    Bean1 bean1 = new Bean1();  
    System.out.println(bean1);  
    // 执行依赖注入,@Autowired、@Value  
    postProcessor.postProcessProperties(null, bean1, "bean1");  
    System.out.println(bean1);  
}
Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 15:45:26.473 [main] com.kangkang.a04.Bean1              - @Value 生效: D:\java 
[DEBUG] 15:45:26.489 [main] com.kangkang.a04.Bean1              - @Autowired 生效: com.kangkang.a04.Bean2@1972e513 
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=null, home=${JAVA_HOME}}

在未调用  AutowiredAnnotationBeanPostProcessor#postProcessProperties()  方法时, Bean1  中的  bean2bean3  和  home  都没有注入成功,而在调用之后,成功注入了  bean2  和  home ,但  home  的值似乎有点奇怪,并没有打印出前文中相同的值,可能是因为没有成功解析  #{}

至于  bean3  为什么没注入成功,是因为  bean3  的注入是利用  @Resource ,而不是  @Autowired 。如果对  Bean1  进行修改:

public class Bean1 {  
	// --snip--  
      
    @Autowired  
    private Bean3 bean3;  
  
    @Resource  
    public void setBean3(Bean3 bean3) {  
        log.info("@Resource 生效: {}", bean3);  
        this.bean3 = bean3;  
    }  
      
    // --snip--  
}

再次运行有:

Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 15:45:26.473 [main] com.kangkang.a04.Bean1              - @Value 生效: D:\java 
[DEBUG] 15:45:26.489 [main] com.kangkang.a04.Bean1              - @Autowired 生效: com.kangkang.a04.Bean2@1972e513 
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=com.kangkang.a04.Bean3@7ae0a9ec, home=${JAVA_HOME}}

成功注入了  bean3 。如果想要成功注入  home ,则需要在  BeanFactory  中添加  #{}  的解析器:

public static void main(String[] args) {  
    // --snip--  
  
    // ${} 的解析器  
    beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);  
  
    // --snip--  
  
    postProcessor.postProcessProperties(null, bean1, "bean1");  
    System.out.println(bean1);  
}
Bean1{bean2=null, bean3=null, home='null'}
[DEBUG] 15:45:26.473 [main] com.kangkang.a04.Bean1              - @Value 生效: D:\java 
[DEBUG] 15:45:26.489 [main] com.kangkang.a04.Bean1              - @Autowired 生效: com.kangkang.a04.Bean2@1972e513 
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=com.kangkang.a04.Bean3@7ae0a9ec, home='D:\java'}

AutowiredAnnotationBeanPostProcessor#postProcessProperties()

源码如下:

@Override  
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {  
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);  
    try {  
        metadata.inject(bean, beanName, pvs);  
    }  
    catch (BeanCreationException ex) {  
        throw ex;  
    }  
    catch (Throwable ex) {  
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);  
    }  
    return pvs;  
}

其中的  findAutowiringMetadata()  用于查找指定的  bean  对象中哪些地方使用了  @Autowired@Value  等与注入相关的注解,并将这些信息封装在  InjectionMetadata  对象中,之后调用其  inject()  方法利用反射完成注入。

findAutowiringMetadata()  方法是一个私有方法,尝试利用反射进行调用并进行断点查看  InjectionMetadata  对象中的信息:

public static void main(String[] args) {  
    // --snip--  
      
    AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor();  
    postProcessor.setBeanFactory(beanFactory);  
  
    Bean1 bean1 = new Bean1();  
  
    Method method = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);  
    method.setAccessible(true);  
    // 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息  
    InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);  
    // 此处断点  
    System.out.println(metadata);  
}

InjectionMetadata  中有一个名为  injectedElements  的集合类型成员变量,根据上图所示, injectedElements  存储了被相关注解标记的成员变量、方法的信息,因为  Bean1  中的  bean3  成员变量、 setBean2()  和  setHome()  方法恰好被  @Autowired  注解标记。

然后按照源码一样,调用  InjectionMetadata#inject()  方法进行依赖注入:

public static void main(String[] args) {  
    // --snip--  
  
    // 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息  
    InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);  
  
    // 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值  
    metadata.inject(bean1, "bean1", null);  
    System.out.println(bean1);  
}
[DEBUG] 13:45:33.838 [main] com.kangkang.a04.Bean1              - @Value 生效: D:\java 
[DEBUG] 13:45:33.857 [main] com.kangkang.a04.Bean1              - @Autowired 生效: com.kangkang.a04.Bean2@1972e513 
Bean1{bean2=com.kangkang.a04.Bean2@1972e513, bean3=com.kangkang.a04.Bean3@7ae0a9ec, home='D:\java'}

调用  inject()  方法后会利用反射进行依赖注入,但在反射之前,肯定得先拿到被注入的对象或值,那这些对象或值是怎么取到的呢?

可以通过以下代码概括:

public static void main(String[] args) {  
    // --snip--  
  
    // 如何按类型查找值  
    Field bean3 = Bean1.class.getDeclaredField("bean3");  
    DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);  
    Object o1 = beanFactory.doResolveDependency(dd1, null, null, null);  
    System.out.println(o1);  
  
    Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);  
    // MethodParameter 构造方法的第二个参数表示需要解析的方法中参数的索引  
    DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);  
    Object o2 = beanFactory.doResolveDependency(dd2, null, null, null);  
    System.out.println(o2);  
  
    Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);  
    DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);  
    Object o3 = beanFactory.doResolveDependency(dd3, null, null, null);  
    System.out.println(o3);  
}
com.kangkang.a04.Bean3@7ae0a9ec
com.kangkang.a04.Bean2@1972e513
D:\java

# 五、BeanFactory 后置处理器

# 1. 常见的 BeanFactory 后置处理器

先引入要用到的依赖:

<dependency>  
    <groupId>org.mybatis.spring.boot</groupId>  
    <artifactId>mybatis-spring-boot-starter</artifactId>  
    <version>2.1.4</version>  
</dependency>  
  
<dependency>  
    <groupId>com.alibaba</groupId>  
    <artifactId>druid</artifactId>  
    <version>1.2.15</version>  
</dependency>  
  
<dependency>  
    <groupId>mysql</groupId>  
    <artifactId>mysql-connector-java</artifactId>  
</dependency>

需要用到的类信息:

@Configuration  
@ComponentScan("com.kangkang.a05.component")  
public class Config {  
    @Bean  
    public Bean1 bean1() {  
        return new Bean1();  
    }  
  
    @Bean  
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {  
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();  
        sqlSessionFactoryBean.setDataSource(dataSource);  
        return sqlSessionFactoryBean;  
    }  
  
    @Bean(initMethod = "init")  
    public DruidDataSource dataSource() {  
        DruidDataSource dataSource = new DruidDataSource();  
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");  
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring_db");  
        dataSource.setUsername("root");  
        dataSource.setPassword("1234");  
        return dataSource;  
    }  
}
public class Bean1 {  
  
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);  
  
    public Bean1() {  
        log.debug("我被 Spring 管理啦");  
    }  
}
@Component  
public class Bean2 {  
  
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);  
  
    public Bean2() {  
        log.debug("我被 Spring 管理啦");  
    }  
}

继续使用  GenericApplicationContext  作为容器,向容器中注册  config

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("config", Config.class);  
  
    context.refresh();  
  
    for (String name : context.getBeanDefinitionNames()) {  
        System.out.println(name);  
    }  
  
    context.close();  
}
config

并没有打印出除  config  以外的 Bean 信息,也就是说  Config  类中的  @ComponentScan  和  @Bean  注解都没有生效。

根据经验,显然是因为缺少某个后置处理器。

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("config", Config.class);  
    // @ComponentScan @Bean @Import @ImportResource  
    context.registerBean(ConfigurationClassPostProcessor.class);  
      
    // --snip--  
}
config
com.kangkang.a05.AtBeanPostProcessor
com.kangkang.a05.MapperPostProcessor
bean1
sqlSessionFactoryBean
dataSource

在使用 MyBatis 时,经常会使用到  @Mapper  注解,而这个注解的解析也需要使用到特定的 BeanFactory 后置处理器。

以下两个接口被  @Mapper  注解标记:

package com.kangkang.a05.mapper;  
  
import org.apache.ibatis.annotations.Mapper;  
  
@Mapper  
public interface Mapper1 {  
}
package com.kangkang.a05.mapper;  
  
import org.apache.ibatis.annotations.Mapper;  
  
@Mapper  
public interface Mapper2 {  
}

然后添加解析  @Mapper  注解的后置处理器:

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("config", Config.class);  
    // @ComponentScan @Bean @Import @ImportResource  
    context.registerBean(ConfigurationClassPostProcessor.class);  
    context.registerBean(MapperScannerConfigurer.class,  
                i -> i.getPropertyValues().add("basePackage", "indi.mofan.bean.a05.mapper"));  
      
    // --snip--  
}

其中的  basePackage  是  MapperScannerConfigurer  中的一个成员变量,表示需要扫描的包路径,设置的值恰好是被  @Mapper  注解标记的接口所在的包路径。

控制台打印的信息中增加了:

mapper1
mapper2

除此之外,还有一些常用的后置处理器并没有在上述信息中体现。

# 2. 模拟实现

移除向容器中添加的  ConfigurationClassPostProcessor  和  MapperScannerConfigurer  两个后置处理器,自行编码模拟它们功能的实现。

组件扫描之 @ComponentScan

在  Bean2  所在包路径下再增加两个类,用于后续测试:

package com.kangkang.a05.component;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Controller;  
  
@Controller  
public class Bean3 {  
  
    private static final Logger log = LoggerFactory.getLogger(Bean3.class);  
  
    public Bean3() {  
        log.debug("我被 Spring 管理啦");  
    }  
}
package com.kangkang.a05.component;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
  
public class Bean4 {  
  
    private static final Logger log = LoggerFactory.getLogger(Bean4.class);  
  
    public Bean4() {  
        log.debug("我被 Spring 管理啦");  
    }  
}

编写  ComponentScanPostProcessor  用于实现  @ComponentScan  注解的解析:

package com.kangkang.a05;  
  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;  
import org.springframework.beans.factory.support.AbstractBeanDefinition;  
import org.springframework.beans.factory.support.BeanDefinitionBuilder;  
import org.springframework.beans.factory.support.BeanDefinitionRegistry;  
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;  
import org.springframework.context.annotation.AnnotationBeanNameGenerator;  
import org.springframework.context.annotation.ComponentScan;  
import org.springframework.core.annotation.AnnotationUtils;  
import org.springframework.core.io.Resource;  
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;  
import org.springframework.core.type.AnnotationMetadata;  
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;  
import org.springframework.core.type.classreading.MetadataReader;  
import org.springframework.stereotype.Component;  
  
import java.io.IOException;  
  
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {  
    @Override  
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {  
        try {  
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);  
            if (componentScan != null) {  
                for (String p : componentScan.basePackages()) {  
                    System.out.println(p);  
                    String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";  
                    System.out.println(path);  
                    CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();  
                    Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);  
                    AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();  
                    for (Resource resource : resources) {  
                        System.out.println(resource);  
                        MetadataReader reader = factory.getMetadataReader(resource);  
                        AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();  
                        if (annotationMetadata.hasAnnotation(Component.class.getName()) ||  
                                annotationMetadata.hasMetaAnnotation(Component.class.getName())) {  
                            AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(reader.getClassMetadata().getClassName()).getBeanDefinition();  
                            String name = generator.generateBeanName(bd, beanDefinitionRegistry);  
                            beanDefinitionRegistry.registerBeanDefinition(name, bd);  
                        }  
                    }  
                }  
            }  
        }catch (IOException e){  
            e.printStackTrace();  
        }  
    }  
  
    @Override  
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {  
  
    }  
}

然后再测试:

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("config", Config.class);  
    context.registerBean(ComponentScanPostProcessor.class);  
  
    context.refresh();  
  
    for (String name : context.getBeanDefinitionNames()) {  
        System.out.println(name);  
    }  
  
    context.close();  
}
[DEBUG] 14:02:56.305 [main] com.kangkang.a05.component.Bean2    - 我被 Spring 管理啦 
[DEBUG] 14:02:56.305 [main] com.kangkang.a05.component.Bean3    - 我被 Spring 管理啦 
config
componentScanPostProcessor
bean2
bean3

没使用  ConfigurationClassPostProcessor  也实现了  @ComponentScan  注解的解析!

@Bean 的解析

Config  类中再增加一个方法作为干扰项:

@Configuration  
@ComponentScan("com.kangkang.a05.component")
public class Config {  
  
    public Bean2 bean2() {  
        return new Bean2();  
    }  
      
    // --snip--  
}

与解析  @ComponentScan  一样,自行编写一个  BeanFactoryPostProcessor  的实现类用于解析  @Bean  注解:

package com.kangkang.a05;  
  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;  
import org.springframework.beans.factory.support.AbstractBeanDefinition;  
import org.springframework.beans.factory.support.BeanDefinitionBuilder;  
import org.springframework.beans.factory.support.BeanDefinitionRegistry;  
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;  
import org.springframework.context.annotation.Bean;  
import org.springframework.core.io.ClassPathResource;  
import org.springframework.core.type.MethodMetadata;  
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;  
import org.springframework.core.type.classreading.MetadataReader;  
  
import java.io.IOException;  
import java.util.Set;  
  
public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {  
    @Override  
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {  
  
        try {  
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();  
            MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/kangkang/a05/Config.class"));  
  
            Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());  
            for (MethodMetadata method : methods) {  
                System.out.println(method);  
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();  
                String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();  
                builder.setFactoryMethodOnBean(method.getMethodName(), "config");  
                builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);  
                if (initMethod.length() > 0) {  
                    builder.setInitMethodName(initMethod);  
                }  
                AbstractBeanDefinition bd = builder.getBeanDefinition();  
                beanDefinitionRegistry.registerBeanDefinition(method.getMethodName(), bd);  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
  
    @Override  
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {  
  
    }  
}

在构造  BeanDefinition  时调用了  setAutowireMode()  方法设置注入模式,这是因为在  Config  类中有一特殊的被  @Bean  标记的方法:

@Bean  
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {  
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();  
    sqlSessionFactoryBean.setDataSource(dataSource);  
    return sqlSessionFactoryBean;  
}

接收一个  DataSource  类型的参数,需要将容器中这个类型的 Bean 进行注入,设置的  AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR  注入模式则能完成这个功能。

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean(AtBeanPostProcessor.class);  
  
    context.refresh();  
  
    for (String name : context.getBeanDefinitionNames()) {  
        System.out.println(name);  
    }  
  
    context.close();  
}
com.kangkang.a05.Config.bean1()
com.kangkang.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.kangkang.a05.Config.dataSource()
[DEBUG] 14:06:12.511 [main] com.kangkang.a05.Bean1              - 我被 Spring 管理啦 
[INFO ] 14:06:12.555 [main] c.a.druid.pool.DruidDataSource      - {dataSource-1} inited 
config
com.kangkang.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource

@Mapper 的解析

@Mapper  注解是在接口上使用的,但根据前文内容可知, @Mapper  被解析后在 Spring 容器中也存在与被标记的接口相关的 Bean。

难道 Spring 能管理接口?

那肯定是不行的,Spring 只能管理对象这是毋庸置疑的。那这些接口是怎么变成对象被 Spring 管理的呢?

这依赖于  MapperFactoryBean  将接口转换为对象。

在  Config  添加注册  Mapper1  和  Mapper2  的方法:

@Bean  
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {  
    MapperFactoryBean<Mapper1> factoryBean = new MapperFactoryBean<>(Mapper1.class);  
    factoryBean.setSqlSessionFactory(sqlSessionFactory);  
    return factoryBean;  
}  
  
@Bean  
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {  
    MapperFactoryBean<Mapper2> factoryBean = new MapperFactoryBean<>(Mapper2.class);  
    factoryBean.setSqlSessionFactory(sqlSessionFactory);  
    return factoryBean;  
}

再运行  main()  方法可以看到容器中存在名为  mapper1  和  mapper2  的 Bean。

这种方式虽然可以完成 Mapper 接口的注册,但每次只能单个注册,不能批量注册。

移除  Config  类中的  mapper1()  和  mapper2()  方法,自行编写  BeanDefinitionRegistryPostProcessor  接口的实现类完成  @Mapper  注解的解析:

package com.kangkang.a05;  
  
import org.mybatis.spring.mapper.MapperFactoryBean;  
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;  
import org.springframework.beans.factory.support.AbstractBeanDefinition;  
import org.springframework.beans.factory.support.BeanDefinitionBuilder;  
import org.springframework.beans.factory.support.BeanDefinitionRegistry;  
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;  
import org.springframework.context.annotation.AnnotationBeanNameGenerator;  
import org.springframework.core.io.Resource;  
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;  
import org.springframework.core.type.ClassMetadata;  
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;  
import org.springframework.core.type.classreading.MetadataReader;  
  
import java.io.IOException;  
  
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {  
    @Override  
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {  
        try {  
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();  
            Resource[] resources = resolver.getResources("classpath:com/kangkang/a05/mapper/**/*.class");  
            AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();  
            CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();  
            for (Resource resource : resources) {  
                MetadataReader reader = factory.getMetadataReader(resource);  
                ClassMetadata classMetadata = reader.getClassMetadata();  
                if(classMetadata.isInterface()){  
                    AbstractBeanDefinition bd = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)  
                            .addConstructorArgValue(classMetadata.getClassName())  
                            .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)  
                            .getBeanDefinition();  
                    AbstractBeanDefinition bd1 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName())  
                            .getBeanDefinition();  
                    String s = generator.generateBeanName(bd1, beanDefinitionRegistry);  
                    beanDefinitionRegistry.registerBeanDefinition(s,bd);  
                }  
            }  
        }catch (IOException e){  
            e.printStackTrace();  
        }  
    }  
  
    @Override  
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {  
  
    }  
}

再测试下:

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("config", Config.class);  
  
    context.registerBean(AtBeanPostProcessor.class);  
    /*  
     * AtBeanPostProcessor 的注册不能少,因为需要容器中存在 SqlSessionFactoryBean  
     * 而 SqlSessionFactoryBean 是在配置类中利用 @Bean 进行注册的  
     */  
    context.registerBean(MapperPostProcessor.class);  
  
    context.refresh();  
  
    for (String name : context.getBeanDefinitionNames()) {  
        System.out.println(name);  
    }  
  
    context.close();  
}
com.kangkang.a05.Config.bean1()
com.kangkang.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.kangkang.a05.Config.dataSource()
[DEBUG] 14:08:25.515 [main] com.kangkang.a05.Bean1              - 我被 Spring 管理啦 
[INFO ] 14:08:25.574 [main] c.a.druid.pool.DruidDataSource      - {dataSource-1} inited 
config
com.kangkang.a05.AtBeanPostProcessor
com.kangkang.a05.MapperPostProcessor
bean1
sqlSessionFactoryBean
dataSource
mapper1
mapper2

容器中存在  mapper1  和  mapper2  两个 Bean。

# 3.【补充】注册创建完成的 Bean

如果要将 Bean 添加到 Spring 容器中,需要先根据配置文件或注解信息为每一个 Bean 生成一个  BeanDefinition ,然后将这些  BeanDefinition  添加到  BeanDefinitionRegistry  中,当创建 Bean 对象时,直接从  BeanDefinitionRegistry  中获取  BeanDefinition  来生成 Bean。

如果生成的 Bean 是单例的,Spring 会将它们保存到  SingletonBeanRegistry  中,后续需要时从这里面寻找,避免重复创建。

那么向 Spring 容器中添加单例 Bean 时,可以跳过注册  BeanDefinition ,直接向  SingletonBeanRegistry  中添加创建完成的 Bean。既然添加的是创建完成的 Bean,所以 这个 Bean 不会经过 Spring 的生命周期。

SingletonBeanRegistry  是一个接口,它有一个子接口名为  ConfigurableListableBeanFactory ,而这恰好是  BeanFactoryPostProcessor  接口中抽象方法的参数:

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

尝试使用  BeanFactoryPostProcessor  注册创建完成的 Bean:

@Slf4j  
public class TestBeanFactoryPostProcessor {  
    public static void main(String[] args) {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();  
        context.registerBean("bean2", Bean2.class);  
        context.registerBean(MyBeanFactoryPostProcessor.class);  
        context.refresh();  
  
        Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);  
        System.out.println(">>>>>>>>>>>>>>>>>>");  
        System.out.println(context.getBean(Bean1.class));  
    }  
  
    static class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {  
        @Override  
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {  
            Bean1 bean1 = new Bean1();  
            bean1.setName("mofan");  
            beanFactory.registerSingleton("bean1", bean1);  
        }  
    }  
  
    @Getter  
    @ToString  
    static class Bean1 {  
        @Setter  
        private String name;  
        private Bean2 bean2;  
  
        @Autowired  
        private void setBean2(Bean2 bean2) {  
            log.debug("依赖注入 bean2");  
            this.bean2 = bean2;  
        }  
  
        @PostConstruct  
        public void init() {  
            log.debug("初始化...");  
        }  
    }  
  
    static class Bean2 {  
    }  
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
bean2
testBeanFactoryPostProcessor.MyBeanFactoryPostProcessor
>>>>>>>>>>>>>>>>>>
TestBeanFactoryPostProcessor.Bean1(name=mofan, bean2=null)

BeanDefinition  的名称数组中不包含  bean1 ,也没有输出任何与经过 Spring 生命周期相关的日志信息,容器中  bean1  里注入的  bean2  也是  null 。这表明通过这种方式注册的 Bean 不会注册  BeanDefinition ,也不会经过 Spring 生命周期。

# 六、Aware 接口

# 1.Aware 接口

Aware 接口用于注入一些与容器相关的信息,比如:

  • BeanNameAware 注入 Bean 的名字
  • BeanFactoryAware 注入 BeanFactory 容器
  • ApplicationContextAware 注入 ApplicationContext 容器
  • EmbeddedValueResolverAware 解析  ${}
public class MyBean implements BeanNameAware, ApplicationContextAware {  
    @Override  
    public void setBeanName(String name) {  
        log.info("当前 Bean: " + this + "名字叫: " + name);  
    }  
  
    @Override  
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
        log.info("当前 Bean: " + this + "容器是: " + applicationContext);  
    }  
}
public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("myBean", MyBean.class);  
  
    context.refresh();  
    context.close();  
}
当前bean com.kangkang.a06.MyBean@6f15d60e 名字叫:com.kangkang.a06.MyBean 
当前bean com.kangkang.a06.MyBean@6f15d60e 容器是:org.springframework.beans.factory.support.DefaultListableBeanFactory@7486b455: defining beans [com.kangkang.a06.MyBean,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; root of factory hierarchy

# 2.InitializingBean

@Slf4j  
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {  
    // --snip--  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        log.info("当前 Bean: " + this + " 初始化");  
    }  
}

再次运行  main()  方法有:

当前bean com.kangkang.a06.MyBean@6f15d60e 名字叫:com.kangkang.a06.MyBean 
当前bean com.kangkang.a06.MyBean@6f15d60e 容器是:org.springframework.beans.factory.support.DefaultListableBeanFactory@7486b455: defining beans [com.kangkang.a06.MyBean,org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; root of factory hierarchy 
当前bean com.kangkang.a06.MyBean@6f15d60e 初始化 

当同时实现  Aware  接口和  InitializingBean  接口时,会先执行  Aware  接口。

BeanFactoryAware  、 ApplicationContextAware  和  EmbeddedValueResolverAware  三个接口的功能可以使用  @Autowired  注解实现, InitializingBean  接口的功能也可以使用  @PostConstruct  注解实现,为什么还要使用接口呢?

@Autowired  和  @PostConstruct  注解的解析需要使用 Bean 后置处理器,属于拓展功能,而这些接口属于内置功能,不加任何拓展 Spring 就能识别。在某些情况下,拓展功能会失效,而内容功能不会失效。

public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {  
   	// --snip--  
  
    @Autowired  
    public void setApplicationContextWithAutowired(ApplicationContext applicationContext) {  
        log.info("当前 Bean: " + this + " 使用 @Autowired 注解,容器是: " + applicationContext);  
    }  
  
    @PostConstruct  
    public void init() {  
        log.info("当前 Bean: " + this + " 使用 @PostConstruct 注解初始化");  
    }  
}

再运行  main()  方法会发现使用的注解没有被成功解析,原因很简单, GenericApplicationContext  是一个干净的容器,其内部没有用于解析这些注解的后置处理器。如果想要这些注解生效,则需要像前文一样添加必要的后置处理器:

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

# 3. 失效的 @Autowired 注解

在某些情况下,尽管容器中存在必要的后置处理器,但  @Autowired  和  @PostConstruct  注解也会失效。

@Configuration  
public class MyConfig1 {  
    @Autowired  
    public void setApplicationContext(ApplicationContext applicationContext) {  
        log.info("注入 ApplicationContext");  
    }  
  
    @PostConstruct  
    public void init() {  
        log.info("初始化");  
    }  
}
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);  
  
    context.refresh();  
    context.close();  
}
com.kangkang.a06.MyConfig1            : 注入 ApplicationContext
com.kangkang.a06.MyConfig1            : 初始化

@Autowired  和  @PostConstruct  注解成功被解析。

如果再对  Config1  进行一点小小的修改呢?

@Configuration  
public class MyConfig1 {  
    // --snip--  
  
    @Bean  
    public BeanFactoryPostProcessor processor1() {  
        return processor -> log.info("执行 processor1");  
    }  
}

在  Config1  中添加了一个被  @Bean  注解标记的  processor1()  方法,用于向容器中添加  BeanFactoryPostProcessor

如果再运行  main()  方法:

com.kangkang.a06.MyConfig1           : 执行 processor1

processor1()  方法成功生效,但  @Autowired  和  @PostConstruct  注解的解析失败了。

对于  context.refresh();  方法来说,它主要按照以下顺序干了三件事:

  1. 执行 BeanFactory 后置处理器;
  2. 添加 Bean 后置处理器;
  3. 创建和初始化单例对象。

比如当 Java 配置类不包括  BeanFactoryPostProcessor  时:

sequenceDiagram 
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor
ac ->> +config : 3. 创建和初始化
bpp ->> config : 3.1 依赖注入扩展(如 @Value 和 @Autowired)
bpp ->> config : 3.2 初始化扩展(如 @PostConstruct)
ac ->> config : 3.3 执行 Aware 及 InitializingBean
config -->> -ac : 3.4 创建成功

BeanFactoryPostProcessor  会在 Java 配置类初始化之前执行。

当 Java 配置类中定义了  BeanFactoryPostProcessor  时,如果要创建配置类中的  BeanFactoryPostProcessor  就必须 提前 创建和初始化 Java 配置类。

在创建和初始化 Java 配置类时,由于  BeanPostProcessor  还未准备好,无法解析配置类中的  @Autowired  等注解,导致  @Autowired  等注解失效:

sequenceDiagram 
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> +config : 3. 创建和初始化
ac ->> config : 3.1 执行 Aware 及 InitializingBean
config -->> -ac : 3.2 创建成功
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor

要解决这个问题也很简单,使用相关接口的功能实现注入和初始化:

@Configuration  
public class MyConfig2 implements InitializingBean, ApplicationContextAware {  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        log.info("初始化");  
    }  
  
    @Override  
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
        log.info("注入 ApplicationContext");  
    }  
  
    @Bean  
    public BeanFactoryPostProcessor processor2() {  
        return processor -> log.info("执行 processor2");  
    }  
}

修改下  main()  方法:

public static void main(String[] args) {  
    GenericApplicationContext context = new GenericApplicationContext();  
    context.registerBean("myConfig2", MyConfig2.class);  
    context.registerBean(AutowiredAnnotationBeanPostProcessor.class);  
    context.registerBean(CommonAnnotationBeanPostProcessor.class);  
  
    // 解析配置类中的注解  
    context.registerBean(ConfigurationClassPostProcessor.class);  
  
    context.refresh();  
    context.close();  
}
com.kangkang.a06.MyConfig2          - 注入 ApplicationContext 
com.kangkang.a06.MyConfig2          - 初始化
com.kangkang.a06.MyConfig2          - 执行 processor2

总结

  1. Aware  接口提供了一种 内置 的注入手段,可以注入 BeanFactory、ApplicationContext;
  2. InitializingBean  接口提供了一种 内置 的初始化手段;
  3. 内置的注入和初始化不受拓展功能的影响,总会被执行,因此 Spring 框架内部的类总是使用这些接口。

# 七、初始化与销毁

初始化和销毁 Bean 的实现有三种:

  1. 依赖于后置处理器提供的拓展功能
  2. 相关接口的功能
  3. 使用  @Bean  注解中的属性进行指定

当同时存在以上三种方式时,它们的执行顺序也将按照上述顺序进行执行。

包含三种初始化方式的 Bean:

@Slf4j  
public class Bean1 implements InitializingBean {  
    @PostConstruct  
    public void init() {  
        log.info("初始化1");  
    }  
  
    @Override  
    public void afterPropertiesSet() throws Exception {  
        log.info("初始化2");  
    }  
  
    public void init3() {  
        log.info("初始化3");  
    }  
}

包含三种销毁方式的 Bean:

public class Bean2 implements DisposableBean {  
    @PreDestroy  
    public void destroy1() {  
        log.info("销毁1");  
    }  
  
    @Override  
    public void destroy() throws Exception {  
        log.info("销毁2");  
    }  
  
    public void destroy3() {  
        log.info("销毁3");  
    }  
}

测试一下:

@SpringBootApplication  
public class A07Application {  
    public static void main(String[] args) {  
        ConfigurableApplicationContext context = SpringApplication.run(A07Application.class, args);  
        context.close();  
    }  
  
    @Bean(initMethod = "init3")  
    public Bean1 bean1() {  
        return new Bean1();  
    }  
  
    @Bean(destroyMethod = "destroy3")  
    public Bean2 bean2() {  
        return new Bean2();  
    }  
}

com.kangkang.a07.Bean1 - 初始化 1
com.kangkang.a07.Bean1 - 初始化 2
com.kangkang.a07.Bean1 - 初始化 3
com.kangkang.a07.Bean2 - 销毁 1
com.kangkang.a07.Bean2 - 销毁 2
com.kangkang.a07.Bean2 - 销毁 3

# 八、Scope

# 1.Scope 的类型与销毁

Scope 用于指定 Bean 的作用范围,有如下五个取值:

  • singleton :单例(默认值)。容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype :多例。每次使用时创建,不会自动销毁,需要调用  DefaultListableBeanFactory#destroyBean()  进行销毁
  • request :作用于 Web 应用的请求范围。每次请求用到此 Bean 时创建,请求结束时销毁
  • session :作用于 Web 应用的会话范围。每个会话用到此 Bean 时创建,会话结束时销毁
  • application :作用于 Web 应用的  ServletContext 。Web 容器用到此 Bean 时创建,容器关闭时销毁

前两个取值不再赘述,重点看下后三个取值。

@Scope("request")  
@Component  
public class BeanForRequest {  
    private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);  
  
    @PreDestroy  
    public void destroy() {  
        log.debug("destroy");  
    }  
}
@Scope("session")  
@Component  
public class BeanForSession {  
    private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);  
  
    @PreDestroy  
    public void destroy() {  
        log.debug("destroy");  
    }  
}
@Scope("application")  
@Component  
public class BeanForApplication {  
    private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);  
  
    @PreDestroy  
    public void destroy() {  
        log.debug("destroy");  
    }  
}

编写一个 Controller 进行测试:

@RestController  
public class MyController {  
  
    @Lazy  
    @Autowired    private BeanForRequest beanForRequest;  
  
    @Lazy  
    @Autowired    private BeanForSession beanForSession;  
  
    @Lazy  
    @Autowired    private BeanForApplication beanForApplication;  
  
    @GetMapping(value = "/test", produces = "text/html")  
    public String test(HttpServletRequest request, HttpSession session) {  
        ServletContext sc = request.getServletContext();  
        String sb = "<ul>" +  
                    "<li>" + "request scope:" + beanForRequest + "</li>" +  
                    "<li>" + "session scope:" + beanForSession + "</li>" +  
                    "<li>" + "application scope:" + beanForApplication + "</li>" +  
                    "</ul>";  
        return sb;  
    }  
  
}

主启动类:

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

如果使用的 JDK 版本大于 8,需要要启动参数中添加如下信息避免报错:

--add-opens java.base/java.lang=ALL-UNNAMED

运行主启动类,在浏览器中访问  http://localhost:8080/test ,页面上显示:

- request scope:com.kangkang.a08.BeanForRequest@39f0ecff
- session scope:com.kangkang.a08.BeanForSession@7b1d988f
- application scope:com.kangkang.a08.BeanForApplication@53ef544f

刷新页面,页面上的信息变化为:

- request scope:com.kangkang.a08.BeanForRequest@28ec257f
- session scope:com.kangkang.a08.BeanForSession@7b1d988f
- application scope:com.kangkang.a08.BeanForApplication@53ef544f

可以看到  request scope  发生了变化, session scope  和  application scope  没有变化。

这是因为刷新页面后就产生了一个新的请求,而  request  的作用范围只在一个请求内,因此每一个新请求就对应一个新的对象。

那要怎么改变  session scope  呢?

换一个浏览器访问  http://localhost:8080/test ,两个浏览器中的会话肯定不是同一个,此时  session scope  应该会发生变化:

- request scope:com.kangkang.a08.BeanForRequest@6622241e
- session scope:com.kangkang.a08.BeanForSession@271f2e25
- application scope:com.kangkang.a08.BeanForApplication@53ef544f

application  的作用范围是  ServletContext ,要想  application scope  发生变化可以重启程序。

销毁

当刷新页面后,除了  request scope  的值发生变化外,在 IDEA 的控制台能看到以下信息:

com.kangkang.a08.BeanForRequest     - destroy 

这表示  request  作用范围的 Bean 进行了销毁,执行了销毁方法。

如果想看到  session  作用范围的 Bean 执行销毁方法,可以等 session 过期时在控制台上看到对应的信息。默认情况下,session 的过期时间是 30 分钟,为了更好地测试,可以在配置文件中添加:

# 修改 session 过期时间为 10s  
server.servlet.session.timeout=10s

这个配置是全局的,如果只想针对某个请求进行配置,则可以:

@GetMapping(value = "/test", produces = "text/html")  
public String test(HttpServletRequest request, HttpSession session) {  
    // 设置 session 过期时间为 10 秒  
    session.setMaxInactiveInterval(10);  
      
    // --snip--  
}

设置 session 过期时间为 10 秒后,并不表示不进行任何操作 10 秒后就能在控制台上看到执行销毁方法的信息,经过测试,大概会等 1 分钟,静静等待 1 分钟左右,控制台上显示:

com.kangkang.a08.BeanForSession       : destroy

很遗憾没有办法看到  application  作用范围的 Bean 执行销毁方法,因为 Spring 似乎并没有对  application  作用范围的 Bean 进行正确的销毁处理,因此在 Servlet 容器销毁时看不到  application  作用范围的 Bean 执行销毁方法。

# 2.Scope 失效分析

现有两个类:

@Component  
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  
public class F1 {  
}
@Getter  
@Component  
public class E {  
    @Autowired  
    private F1 f1;  
}

之后进行测试:

@Slf4j  
@ComponentScan("indi.mofan.bean.a09")  
public class A09Application {  
    public static void main(String[] args) {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);  
  
        E e = context.getBean(E.class);  
        log.info("{}", e.getF1());  
        log.info("{}", e.getF1());  
        log.info("{}", e.getF1());  
  
        context.close();  
    }  
}

现在问题来了: F1  被  @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  标记,之后向  e  中注入了  f1 ,那么  log.info("{}", e.getF1());  打印出的  f1  应该都不是同一个对象吗?

com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F1@715fa8c5 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F1@715fa8c5 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F1@715fa8c5 

获取到的  f1  居然都是同一个,也就是说向单例对象中注入多例对象失败了。

对于单例对象来说,依赖注入仅发生了一次,后续不会再注入其他的  f1 ,因此  e  始终使用的是第一次注入的  f1

graph LR
e1(e 创建)
e2(e set 注入 f)
f1(f 创建)
e1-->f1-->e2

为了解决这个问题,可以使用  @Lazy  生成代理对象,虽然代理对象依旧是同一个,但每次使用代理对象中的方法时,会由代理对象创建新的目标对象:

graph LR
e1(e 创建)
e2(e set 注入 f代理)
f1(f 创建)
f2(f 创建)
f3(f 创建)
e1-->e2
e2--使用f方法-->f1
e2--使用f方法-->f2
e2--使用f方法-->f3

解决方式一

@Getter  
@Component  
public class E {  
    @Lazy  
    @Autowired  
    private F1 f1;  
}

再修改下  main()  方法,打印下  f1  的 Class 信息,查看是否是代理对象:

public static void main(String[] args) {  
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);  
  
    E e = context.getBean(E.class);  
    log.info("{}", e.getF1().getClass());  
    log.info("{}", e.getF1());  
    log.info("{}", e.getF1());  
    log.info("{}", e.getF1());  
  
    context.close();  
}
com.kangkang.a08.useless.A08_1      - class com.kangkang.a08.useless.F1$$EnhancerBySpringCGLIB$$48c18ed4 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F1@724bf25f 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F1@6f6cc7da 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F1@29f3c438 

使用  @Lazy  注解后,注入的是代理对象,每次获取到的  f1  不再是同一个。

解决方式二

除了使用  @Lazy  注解外,可以使用  @Scope  注解的  proxyMode  属性指定代理模式:

@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)  
@Component  
public class F2 {  
}
@Getter  
@Component  
public class E {  
  
    @Autowired  
    private F2 f2;  
}

之后再测试:

@Slf4j  
@ComponentScan("indi.mofan.bean.a09")  
public class A09Application {  
    public static void main(String[] args) {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);  
  
        E e = context.getBean(E.class);  
  
        log.info("{}", e.getF2().getClass());  
        log.info("{}", e.getF2());  
        log.info("{}", e.getF2());  
        log.info("{}", e.getF2());  
  
        context.close();  
    }  
}
com.kangkang.a08.useless.A08_1      - class com.kangkang.a08.useless.F2$$EnhancerBySpringCGLIB$$d60af49b 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F2@2c3158e0 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F2@412ebe64 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F2@6f731759

解决方式三

@Scope("prototype")  
@Component  
public class F3 {  
}
  
@Component  
public class E {  
  
    @Autowired  
    private ObjectFactory<F3> f3;  
  
    public F3 getF3() {  
        return f3.getObject();  
    }  
}
@Slf4j  
@ComponentScan("indi.mofan.bean.a09")  
public class A09Application {  
    public static void main(String[] args) {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);  
  
        E e = context.getBean(E.class);  
  
        log.info("{}", e.getF3());  
        log.info("{}", e.getF3());  
  
        context.close();  
    }  
}
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F3@7a83ccd2 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F3@599a9cb2 

解决方式四

@Scope("prototype")  
@Component  
public class F4 {  
}
  
@Component  
public class E {  
  
    @Autowired  
    private ApplicationContext applicationContext;  
  
    public F4 getF4() {  
        return applicationContext.getBean(F4.class);  
    }  
}
@Slf4j  
@ComponentScan("indi.mofan.bean.a09")  
public class A09Application {  
    public static void main(String[] args) {  
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);  
  
        E e = context.getBean(E.class);  
  
        log.info("{}", e.getF4());  
        log.info("{}", e.getF4());  
  
        context.close();  
    }  
}
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F4@3a1b36a1 
com.kangkang.a08.useless.A08_1      - com.kangkang.a08.useless.F4@5e1a986c

如果对性能要求较高,则推荐使用后两种方式,前两种使用代理会有一定的性能损耗;如果不在乎那点性能损耗,则可以使用第一种方式,这种方式最简单。

四种解决方式虽然不同,但在理念上殊途同归,都是推迟了其他 Scope Bean 的获取,或者说按需加载。

更新于

请我喝[茶]~( ̄▽ ̄)~*

KangKang 微信支付

微信支付

KangKang 支付宝

支付宝