Spring(七)核心容器 - 鉤子接口

前言

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 的實現,這些都會在後面的文章一一討論。




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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章