1. 程式人生 > >Spring(七)核心容器 - 鉤子介面

Spring(七)核心容器 - 鉤子介面

目錄

  • 前言
  • 1、Aware 系列介面
  • 2、InitializingBean
  • 3、BeanPostProcessor
  • 4、BeanFactoryPostProcessor
  • 5、ImportSelector
  • 6、ImportBeanDefinitionRegistrar
  • 7、FactoryBean
  • 8、ApplicationListener
  • 最後

前言

Spring 提供了非常多的擴充套件介面,官方將這些介面稱之為鉤子,這些鉤子會在特定的時間被回撥,以此來增強 Spring 功能,眾多優秀的框架也是通過擴充套件這些介面,來實現自身特定的功能,如 SpringBoot、mybatis 等。

1、Aware 系列介面

Aware 從字面意思理解就是“知道”、“感知”的意思,是用來獲取 Spring 內部物件的介面。Aware 自身是一個頂級介面,它有一系列子介面,在一個 Bean 中實現這些子介面並重寫裡面的 set 方法後,Spring 容器啟動時,就會回撥該 set 方法,而相應的物件會通過方法引數傳遞進去。我們以其中的 ApplicationContextAware 介面為例。

ApplicationContextAware

大部分 Aware 系列介面都有一個規律,它們以物件名稱為字首,獲取的就是該物件,所以 ApplicationContextAware 獲取的物件是 ApplicationContext 。

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 介面:

  • BeanFactoryAware:獲取 BeanFactory 物件,它是基礎的容器介面。
  • BeanNameAware:獲取 Bean 的名稱。
  • EnvironmentAware:獲取 Environment 物件,它表示整個的執行時環境,可以設定和獲取配置屬性。
  • ApplicationEventPublisherAware:獲取 ApplicationEventPublisher 物件,它是用來發布事件的。
  • ResourceLoaderAware:獲取 ResourceLoader 物件,它是獲取資源的工具。

2、InitializingBean

InitializingBean 是一個可以在 Bean 的生命週期執行自定義操作的介面,凡是實現該介面的 Bean,在初始化階段都可以執行自定義的操作。

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

BeanPostProcessor 和 InitializingBean 有點類似,也是可以在 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 在初始化前、後都會回撥介面中的 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法,入參是當前正在初始化的 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;
    }
}

啟動類:

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

結果:

...
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 內部也正是這樣使用的,這部分也會在後面章節詳細討論。

4、BeanFactoryPostProcessor

BeanFactoryPostProcessor 是 Bean 工廠的後置處理器,一般用來修改上下文中的 BeanDefinition,修改 Bean 的屬性值。

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 。

接著啟動程式:

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

結果:

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,並設定懶載入屬性。

ImportSelector 和 ImportBeanDefinitionRegistrar 是實現 @Enable 模式註解的核心介面,而 @Enable 模式註解在 Spring、SpringBoot、SpringCloud 中被大量使用,其依靠這些註解來實現各種功能及特性,是較為重要的擴充套件介面,我們會在後面的文章中反覆討論,包括 ImportSelector 和 ImportBeanDefinitionRegistrar 是如何被 Spring 呼叫的、以及一些重要的 @Enable 註解實現。

值得注意的是,SpringBoot 外部化配置、自動裝配特性就是通過 @Enable 註解配合 ImportSelector 和 ImportBeanDefinitionRegistrar 介面來實現的,這部分在前面的 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 的實現,這些都會在後面的文章一一討論。




以上就是本章內容,如果文章中有錯誤或者需要補充的請及時提出,本人感激不