Spring源码(5)-钩子接口


一、前言

Spring 提供了非常多的扩展接口,官方将这些接口称之为钩子,这些钩子会在特定的时间被回调,以此来增强 Spring 功能,众多优秀的框架也是通过扩展这些接口,来实现自身特定的功能,如 SpringBoot、mybatis 等。

1、Aware 系列接口

Aware 从字面意思理解就是“知道”、“感知”的意思,是用来获取 Spring 内部对象的接口。Aware 自身是一个顶级接口,它有一系列子接口,在一个 Bean 中实现这些子接口并重写里面的 set 方法后,Spring 容器启动时,就会回调该 set 方法,而相应的对象会通过方法参数传递进去。我们以其中的 ApplicationContextAware 接口为例。

(1)ApplicationContextAware

大部分 Aware 系列接口都有一个规律,它们以对象名称为前缀,获取的就是该对象,所以 ApplicationContextAware 获取的对象是 ApplicationContext 。

该回调方法的执行时机是在 Bean 依赖注入阶段之后,初始化之前

public interface ApplicationContextAware extends Aware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

ApplicationContextAware 源码非常简单,其继承了 Aware 接口,并定义一个 set 方法,参数就是 ApplicationContext 对象,当然,其它系列的 Aware 接口也是类似的定义。其具体使用方式如下:

public class Test implements ApplicationContextAware {

    private ApplicationContext applicationContext;

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

在 Spring 启动过程中,会回调 setApplicationContext 方法,并传入 ApplicationContext 对象,之后就可对该对象进行操作。其它系列的 Aware 接口也是如此使用。具体的调用时机会在后面详细介绍。

以下是几种常用的 Aware 接口:

  • BeanNameAware:获取 Bean 的名称。

  • BeanFactoryAware:获取 BeanFactory 对象,它是基础的容器接口。

  • EnvironmentAware:获取 Environment 对象,它表示整个的运行时环境,可以设置和获取配置属性。

  • ApplicationEventPublisherAware:获取 ApplicationEventPublisher 对象,它是用来发布事件的。

  • ResourceLoaderAware:获取 ResourceLoader 对象,它是获取资源的工具。

可能有个疑问,就是这些大部分明明能通过 @Autowired 注入来获取对应的对象,那为啥还需要这几个接口呢?因为 @Autowired 的实现需要通过后处理器实现,如果没加对应的后处理器的话,则无法获取(一般不会有这种情况),同时在下文介绍的@Autowired失效情况中,也可用到这个;Aware 接口时 Spring 内置的回调手段,不会出现失效的情况,所以在很多源码中使用了这些接口。

ApplicationContextAware在源码中的使用案例

/**
 * Spring Context 工具类
 *
 * @author Mark sunlightcs@gmail.com
 */
@Component
public class SpringContextUtils implements ApplicationContextAware {
    public static ApplicationContext applicationContext; 

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

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }

}

(2)BeanNameAware

@Component
public class MyBean implements BeanNameAware {


    /**
     * 在创建bean时回调该方法,参数为bean的名称。
     */
    @Override
    public void setBeanName(String name) {
        System.out.println(name + "注册到工厂中了....");
    }
}

该回调方法的执行时机是在 Bean 依赖注入阶段之后,初始化之前

2、InitializingBean

InitializingBean 是一个可以在 Bean 的生命周期执行自定义操作的接口,凡是实现该接口的 Bean,在初始化阶段都可以执行自定义的操作。

在初始化阶段执行,@PostConstruct之后、postProcessAfterInitialization 之前执行

public interface InitializingBean {

    void afterPropertiesSet() throws Exception;
}

从 InitializingBean 源码中可以看出它有一个 afterPropertiesSet 方法,当一个 Bean 实现该接口时,在 Bean 的初始化阶段,会回调 afterPropertiesSet 方法,其初始化阶段具体指 Bean 设置完属性之后。

该接口使用方式如下:

@Component
public class Test implements InitializingBean {

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

定义启动类:

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

结果:

...
2020-02-24 08:43:41.435  INFO 26193 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpTraceFilter' to: [/*]
2020-02-24 08:43:41.435  INFO 26193 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'webMvcMetricsFilter' to: [/*]
Test 执行初始化
2020-02-24 08:43:41.577  INFO 26193 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2020-02-24 08:43:41.756  INFO 26193 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@23529fee: startup date [Mon Feb 24 08:43:39 CST 2020]; root of context hierarchy
...

最终,afterPropertiesSet 方法被执行并打印输出语句。

3、BeanPostProcessor

BeanPostProcessorInitializingBean 有点类似,也是可以在 Bean 的生命周期执行自定义操作,一般称之为 Bean 的后置处理器,不同的是,
BeanPostProcessor 可以在 Bean 初始化前、后执行自定义操作,且针对的目标也不同,InitializingBean 针对的是实现 InitializingBean 接口的 Bean,而 BeanPostProcessor针对的是所有的 Bean。

public interface BeanPostProcessor {

    // Bean 初始化前调用
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    // Bean 初始化后调用
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

所有的 Bean 在初始化前、后都会回调接口中的 postProcessBeforeInitializationpostProcessAfterInitialization 方法,入参是当前正在初始化的 Bean 对象和 BeanName。值得注意的是 Spring 内置了非常多的 BeanPostProcessor ,以此来完善自身功能,这部分会在后面文章深入讨论。

这里通过自定义 BeanPostProcessor 来了解该接口的使用方式:

// 一般自定义的 BeanPostProcessor 命名格式都是以 BeanPostProcessor 为后缀。
@Component
public class TestBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + " 初始化前执行操作");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName + " 初始化后执行操作");
        return bean;
    }
}

结果

...
2020-02-24 23:37:08.949  INFO 26615 --- [           main] com.loong.diveinspringboot.test.Main     : No active profile set, falling back to default profiles: default
2020-02-24 23:37:08.994  INFO 26615 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@2133814f: startup date [Mon Feb 24 23:37:08 CST 2020]; root of context hierarchy
2020-02-24 23:37:09.890  INFO 26615 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
org.springframework.context.event.internalEventListenerProcessor 初始化前执行操作
org.springframework.context.event.internalEventListenerProcessor 初始化后执行操作
org.springframework.context.event.internalEventListenerFactory 初始化前执行操作
org.springframework.context.event.internalEventListenerFactory 初始化后执行操作
main 初始化前执行操作
main 初始化后执行操作
test 初始化前执行操作
Test 执行初始化
test 初始化后执行操作
...
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration初始化前执行操作
2020-02-24 23:37:13.097  INFO 26615 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-02-24 23:37:13.195  INFO 26615 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-24 23:37:13.207  INFO 26615 --- [           main] com.loong.diveinspringboot.test.Main     : Started Main in 4.657 seconds (JVM running for 5.078)
...

可以看到,输出的结果中不仅包括自定义的 Test,还包括 Spring 内部的 Bean 。

BeanPostProcessor 使用场景其实非常多,因为它可以获取正在初始化的 Bean 对象,然后可以依据该 Bean 对象做一些定制化的操作,如:判断该 Bean 是否为某个特定对象、获取 Bean 的注解元数据等。事实上,Spring 内部也正是这样使用的,这部分也会在后面章节详细讨论。

@PostConstruct

@PostConstruct 注解好多人以为是Spring提供的。其实是Java自己的注解。

BeanPostProcessor 其实就是 @PostConstruct 在 Spring 中的实现。

BeanPostProcessor 有个实现类 CommonAnnotationBeanPostProcessor,就是专门处理@PostConstruct @PreDestroy注解。

Java中该注解的说明:@PostConstruct 该注解被用来修饰一个非静态的void()方法。被@PostConstruct 修饰的方法会在服务器加载 Servlet 的时候运行,并且只会被服务器执行一次。PostConstruct 在构造函数之后执行,init()方法之前执行。

通常我们会是在 Spring 框架中使用到 @PostConstruct 注解 该注解的方法在整个Bean初始化中的执行顺序:

Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

当bean创建完成空对象,就开始进行@Autowired@PostConstruct 赋值

如果想在生成对象时候完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用 @PostConstruct 注解一个方法来完成初始化,@PostConstruct 注解的方法将会在依赖注入完成后被自动调用。

应用:在静态方法中调用依赖注入的Bean中的方法。

package com.example.studySpringBoot.util;

import com.example.studySpringBoot.service.MyMethorClassService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class MyUtils {

    private static MyUtils          staticInstance = new MyUtils();

    @Autowired
    private MyMethorClassService    myService;

    @PostConstruct
    public void init(){
        staticInstance.myService = myService;
    }

    public static Integer invokeBean(){
        return staticInstance.myService.add(10,20);
    }
}

应用2:服务器启动设置 jvm 时区

当出现本地和服务器中,时间不一致的现象时,多半是服务器中 jvm 的时区不对。

在 SpringBoot 启动类中加入以下代码即可在启动时设置 jvm 的时区。

@PostConstruct
void started() {
    TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
}

4、BeanFactoryPostProcessor

BeanFactoryPostProcessor 是 Bean 工厂的后置处理器,一般用来修改上下文中的 BeanDefinition,修改 Bean 的属性值。

可以对bean的定义(配置元数据)进行处理;也就是spring ioc运行BeanFactoryPostProcessor在容器实例化任何其他的bean之前读取配置元数据,并有可能修改它;如果业务需要,可以配置多个BeanFactoryPostProcessor的实现类,通过”order”控制执行次序(要实现Ordered接口)。

public interface BeanFactoryPostProcessor {

    // 入参是一个 Bean 工厂:ConfigurableListableBeanFactory。该方法执行时,所有 BeanDefinition 都已被加载,但还未实例化 Bean。
    // 可以对其进行覆盖或添加属性,甚至可以用于初始化 Bean。
    void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}

BeanFactoryPostProcessor 源码非常简单,其提供了一个 postProcessBeanFactory 方法,当所有的 BeanDefinition 被加载时,该方法会被回调。值得注意的是,Spring 内置了许多 BeanFactoryPostProcessor 的实现,以此来完善自身功能。

这里,我们来实现一个自定义的 BeanFactoryPostProcessor

@Component
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String beanNames[] = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            System.out.println(beanDefinition);
        }
    }
}

主要是通过 Bean 工厂获取所有的 BeanDefinition

接着启动程序:

2020-02-25 21:46:00.754  INFO 28907 --- [           main] ConfigServletWebServerApplicationContext : ...
2020-02-25 21:46:01.815  INFO 28907 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : ...
Root bean: class [org.springframework.context.annotation.ConfigurationClassPostProcessor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
Root bean: class [org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
...
2020-02-25 21:46:04.926  INFO 28907 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : ...
2020-02-25 21:46:04.989  INFO 28907 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : ...
2020-02-25 21:46:04.993  INFO 28907 --- [           main] com.loong.diveinspringboot.test.Main     : ...

可以看到,BeanDefinition 正确输出,里面是一些 Bean 的相关定义,如:是否懒加载、Bean 的 Class 以及 Bean 的属性等。

5、ImportSelector

ImportSelector 是一个较为重要的扩展接口,通过该接口可动态的返回需要被容器管理的类,不过一般用来返回外部的配置类。可在标注 @Configuration 注解的类中,通过 @Import 导入 ImportSelector 来使用。

public interface ImportSelector {

    // 方法入参是注解的元数据对象,返回值是类的全路径名数组
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

selectImports 方法返回的是类的全路径名。

自定义 ImportSelector

public class TestImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        if (importingClassMetadata.hasAnnotation("")) {
            // 判断是否包含某个注解
        }

        // 返回 Test 的全路径名,Test 会被放入到 Spring 容器中
        return new String[]{"com.loong.diveinspringboot.test.Test"};
    }
}

selectImports 方法中可以针对通过 AnnotationMetadata 对象进行逻辑判断,AnnotationMetadata 存储的是注解元数据信息,根据这些信息可以动态的返回需要被容器管理的类名称。

定义的 Test 类:

public class Test {
    public void hello() {
        System.out.println("Test -- hello");
    }
}

这里,我们没有对 Test 标注 @Component 注解,所以,Test 不会自动加入到 Spring 容器中。

@SpringBootApplication
@Import(TestImportSelector.class)
public class Main {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication();
        ConfigurableApplicationContext run = springApplication.run(Main.class);
        Test bean = run.getBean(Test.class);
        bean.hello();
    }
}

之后通过 @Import 导入自定义的 TestImportSelector ,前面也说过,@Import 一般配合 @Configuration 使用,而 @SpringBootApplication 中包含了 @Configuration 注解。之后,通过 getBean 方法从容器中获取 Test 对象,并调用 hello 方法。

2020-02-26 08:01:41.712  INFO 29546 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-02-26 08:01:41.769  INFO 29546 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-26 08:01:41.773  INFO 29546 --- [           main] com.loong.diveinspringboot.test.Main     : Started Main in 4.052 seconds (JVM running for 4.534)
Test -- hello

最终,结果正确输出。

6、ImportBeanDefinitionRegistrar

该接口和 ImportSelector 类似,也是配合 @Import 使用,不过 ImportBeanDefinitionRegistrar 更为直接一点,它可以直接把 Bean 注册到容器中。

public interface ImportBeanDefinitionRegistrar {

    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

入参除了注解元数据对象 AnnotationMetadata 外,还多了一个 BeanDefinitionRegistry 对象,在前面的文章讲过,该对象定义了关于 BeanDefinition 的一系列的操作,如:注册、移除、查询等。

自定义 ImportBeanDefinitionRegistrar

public class TestRegistrar implements ImportBeanDefinitionRegistrar {
    // 一般通过 AnnotationMetadata 进行业务判断,然后通过 BeanDefinitionRegistry 直接注册 Bean
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Test.class);
        beanDefinition.setLazyInit(true);
        registry.registerBeanDefinition(Test.class.getName(), beanDefinition);
    }
}

这里,主要通过 BeanDefinitionRegistry 手动注册 Test 类的 BeanDefinition,并设置懒加载属性。

ImportSelectorImportBeanDefinitionRegistrar 是实现 @Enable 模式注解的核心接口,而 @Enable 模式注解在 Spring、SpringBoot、SpringCloud 中被大量使用,其依靠这些注解来实现各种功能及特性,是较为重要的扩展接口,我们会在后面的文章中反复讨论,包括 ImportSelectorImportBeanDefinitionRegistrar 是如何被 Spring 调用的、以及一些重要的 @Enable 注解实现。

值得注意的是,SpringBoot 外部化配置、自动装配特性就是通过 @Enable 注解配合 ImportSelectorImportBeanDefinitionRegistrar 接口来实现的,这部分在前面的 SpringBoot 系列的文章中已经讨论过,感兴趣的同学可自行翻阅。

7、FactoryBean

FactoryBean 也是一种 Bean,不同于普通的 Bean,它是用来创建 Bean 实例的,属于工厂 Bean,不过它和普通的创建不同,它提供了更为灵活的方式,其实现有点类似于设计模式中的工厂模式和修饰器模式。

Spring 框架内置了许多 FactoryBean 的实现,它们在很多应用如(Spring的AOP、ORM、事务管理)及与其它第三框架(ehCache)集成时都有体现。

public interface FactoryBean<T> {
    // 该方法会返回该 FactoryBean “生产”的对象实例,我们需要实现该方法以给出自己的对象实例化逻辑
    T getObject() throws Exception;

    // Bean的类型
    Class<?> getObjectType();

    // 是否是单例
    default boolean isSingleton() {
        return true;
    }
}

自定义 FactoryBean

@Component
public class TestFactoryBean implements FactoryBean<Test> {
    @Override
    public Test getObject() throws Exception {

        // 这里可以灵活的创建 Bean,如:代理、修饰

        return new Test();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

Test 类:

public class Test {
    public void hello() {
        System.out.println("Test -- hello");
    }
}

启动类:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication();
        ConfigurableApplicationContext run = springApplication.run(Main.class);
        Test bean = (Test) run.getBean("testFactoryBean");
        bean.hello();
    }
}

输出

2020-02-27 23:16:00.334  INFO 32234 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-27 23:16:00.338  INFO 32234 --- [           main] com.loong.diveinspringboot.test.Main     : Started Main in 3.782 seconds (JVM running for 4.187)
Test -- hello

可以看到,启动类中 getBean 的参数是 testFactoryBean ,从这可以看出,当容器中的 Bean 实现了 FactoryBean 后,通过 getBean(String BeanName) 获取到的 Bean 对象并不是 FactoryBean 的实现类对象,而是这个实现类中的 getObject() 方法返回的对象。如果想获取 FactoryBean 的实现类,需通过这种方式:getBean(&BeanName),在 BeanName 之前加上&。

8、ApplicationListener

ApplicationListener 是 Spring 实现事件机制的核心接口,属于观察者设计模式,一般配合 ApplicationEvent 使用。在 Spring 容器启动过程中,会在相应的阶段通过 ApplicationContext 发布 ApplicationEvent 事件,之后所有的 ApplicationListener 会被回调,根据事件类型,执行不同的操作。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    void onApplicationEvent(E event);
}

onApplicationEvent方法中,通过 instanceof 判断 event 的事件类型。

自定义 ApplicationListener

@Component
public class TestApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof TestApplicationEvent) {
            TestApplicationEvent testApplicationEvent = (TestApplicationEvent) event;
            System.out.println(testApplicationEvent.getName());
        }
    }
}

当自定义的 TestApplicationListener 被回调时,判断当前发布的事件类型是否是自定义的 TestApplicationEvent,如果是则输出事件名称。

自定义 TestApplicationEvent

public class TestApplicationEvent extends ApplicationEvent {

    private String name;

    public TestApplicationEvent(Object source, String name) {
        super(source);
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

启动类:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication();
        ConfigurableApplicationContext run = springApplication.run(Main.class);
        run.publishEvent(new TestApplicationEvent(new Main(),"Test 事件"));
    }
}

通过 ApplicationContext 发布 TestApplicationEvent 事件。当然也可以在业务代码中通过 ApplicationContextAware 获取 ApplicationContext 发布事件。

结果:

2020-02-27 08:37:10.972  INFO 30984 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-02-27 08:37:11.026  INFO 30984 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-27 08:37:11.029  INFO 30984 --- [           main] com.loong.diveinspringboot.test.Main     : Started Main in 3.922 seconds (JVM running for 4.367)
Test 事件

ApplicationListener 也被 SpringBoot 进行扩展,来实现自身特定的事件机制。这部分也在前面的文章讨论过,感兴趣的同学可自行翻阅。

最后

Spring 的钩子接口就介绍到这,值得注意的是,Spring 的许多核心功能也是通过其内置的钩子接口来实现的,特别是一些核心注解,如:@Component@Bean 的实现,这些都会在后面的文章一一讨论。


  目录