【Spring註解驅動開發】自定義元件如何注入Spring底層的元件?看了這篇我才真正理解了原理!!

冰河團隊發表於2020-08-19

寫在前面

最近,很多小夥伴出去面試都被問到了Spring問題,關於Spring,細節點很多,面試官也非常喜歡問一些很細節的技術點。所以,在 Spring 專題中,我們儘量把Spring的每個技術細節說清楚,將透徹。

關注 冰河技術 微信公眾號,回覆 “ Spring註解 ” 關鍵字領取原始碼。

如果文章對你有所幫助,歡迎大家留言、點贊、在看和轉發,大家的支援是我持續創作的動力!

概述

自定義元件要想使用Spring容器底層的一些元件(比如:ApplicationContext、BeanFactory等),此時,只需要讓自定義元件實現XxxAware介面即可。此時,Spring在建立物件的時候,會呼叫XxxAware介面定義的方法,注入相關的元件。

XxxAware介面概覽

其實,我們之前使用過XxxAware介面,例如,我們之前建立的Employee類,就實現了ApplicationContextAware介面,Employee類的原始碼如下所示。

package io.mykit.spring.plugins.register.bean;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試ApplicationContextAware
 */
@Component
public class Employee implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

從Employee類的原始碼可以看出,實現ApplicationContextAware介面的話,需要實現setApplicationContext()方法。在IOC容器啟動並建立Employee物件時,Spring會呼叫setApplicationContext()方法,並且會將ApplicationContext物件傳入到setApplicationContext()方法中,我們只需要在Employee類中定義一個ApplicationContext型別的成員變數來接收setApplicationContext()方法的引數,就可以使用ApplicationContext物件了。

其實,在Spring中,類似於ApplicationContextAware介面的設計有很多,本質上,Spring中類似XxxAware介面都繼承了Aware介面,我們來看下Aware介面的原始碼,如下所示。

package org.springframework.beans.factory;
/**
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 */
public interface Aware {

}

可以看到,Aware介面是Spring 3.1版本中引入的介面,在Aware介面中,並未定義任何方法。

接下來,我們看看都有哪些介面繼承了Aware介面,如下所示。

XxxAware介面案例

接下來,我們就挑選幾個常用的XxxAware介面來進行簡單的說明。

ApplicationContextAware介面使用的比較多,我們先來說說這個介面,通過ApplicationContextAware介面我們可以獲取到IOC容器。

首先,我們建立一個Blue類,並實現ApplicationContextAware介面,在實現的setApplicationContext()中將ApplicationContext輸出,如下所示。

package io.mykit.spring.plugins.register.bean;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試ApplicationContextAware介面
 */
public class Blue implements ApplicationContextAware {
    private ApplicationContext applicationContext
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("傳入的ioc:" + applicationContext);
        this.applicationContext = applicationContext;
    }
}

我們也可以為Blue類同時實現幾個XxxAware介面,例如,使Blue類再實現一個BeanNameAware介面,我們可以通過BeanNameAware介面獲取到當前bean在Spring容器中的名稱,如下所示。

package io.mykit.spring.plugins.register.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試ApplicationContextAware介面
 */
public class Blue implements ApplicationContextAware, BeanNameAware {
    private ApplicationContext applicationContext
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("傳入的ioc:" + applicationContext);
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("當前bean的名字");
    }
}

接下來,我們再實現一個EmbeddedValueResolverAware介面,我們通過EmbeddedValueResolverAware介面能夠獲取到StringValue解析器。如下所示。

package io.mykit.spring.plugins.register.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.util.StringValueResolver;
/**
 * @author binghe
 * @version 1.0.0
 * @description 測試ApplicationContextAware介面
 */
public class Blue implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("傳入的ioc:" + applicationContext);
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("當前bean的名字");
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String resolveStringValue = resolver.resolveStringValue("你好${os.name} 年齡:#{20*18}");
        System.out.println("解析後的字串為:" + resolveStringValue);
    }
}

接下來,我們需要在Blue類上標註@Component註解將Blue類新增到IOC容器中,如下所示。

@Component
public class Blue implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {

執行AutowiredTest類的testAutowired02()方法,輸出的結果資訊如下所示。

postProcessBeforeInitialization...autowiredConfig=>io.mykit.spring.plugins.register.config.AutowiredConfig$$EnhancerBySpringCGLIB$$d3c83622@1c93084c
postProcessAfterInitialization...autowiredConfig=>io.mykit.spring.plugins.register.config.AutowiredConfig$$EnhancerBySpringCGLIB$$d3c83622@1c93084c
postProcessBeforeInitialization...personDao=>PersonDao{remark='1'}
postProcessAfterInitialization...personDao=>PersonDao{remark='1'}
postProcessBeforeInitialization...personDao2=>PersonDao{remark='2'}
postProcessAfterInitialization...personDao2=>PersonDao{remark='2'}
postProcessBeforeInitialization...personService=>PersonService{personDao=PersonDao{remark='2'}}
postProcessAfterInitialization...personService=>PersonService{personDao=PersonDao{remark='2'}}
postProcessBeforeInitialization...personController=>io.mykit.spring.plugins.register.controller.PersonController@48ae9b55
postProcessAfterInitialization...personController=>io.mykit.spring.plugins.register.controller.PersonController@48ae9b55
執行了Animal類的無引數構造方法
postProcessBeforeInitialization...animal=>io.mykit.spring.plugins.register.bean.Animal@c267ef4
執行了Animal類的初始化方法。。。。。
postProcessAfterInitialization...animal=>io.mykit.spring.plugins.register.bean.Animal@c267ef4
當前bean的名字:blue
解析後的字串為:你好Windows 10 年齡:360
傳入的ioc:org.springframework.context.annotation.AnnotationConfigApplicationContext@5ecddf8f, started on Wed Aug 19 00:10:13 CST 2020
postProcessBeforeInitialization...blue=>io.mykit.spring.plugins.register.bean.Blue@55182842
postProcessAfterInitialization...blue=>io.mykit.spring.plugins.register.bean.Blue@55182842
Cat類的構造方法...
postProcessBeforeInitialization...cat=>io.mykit.spring.plugins.register.bean.Cat@76505305
Cat的postConstruct()方法...
postProcessAfterInitialization...cat=>io.mykit.spring.plugins.register.bean.Cat@76505305
呼叫了Dog的有參構造方法
postProcessBeforeInitialization...dog=>Dog{cat=io.mykit.spring.plugins.register.bean.Cat@76505305}
postProcessAfterInitialization...dog=>Dog{cat=io.mykit.spring.plugins.register.bean.Cat@76505305}
postProcessBeforeInitialization...employee=>io.mykit.spring.plugins.register.bean.Employee@74235045
postProcessAfterInitialization...employee=>io.mykit.spring.plugins.register.bean.Employee@74235045
postProcessBeforeInitialization...fish=>Fish{cat=io.mykit.spring.plugins.register.bean.Cat@76505305}
postProcessAfterInitialization...fish=>Fish{cat=io.mykit.spring.plugins.register.bean.Cat@76505305}
Fish{cat=io.mykit.spring.plugins.register.bean.Cat@76505305}
Cat的preDestroy()方法...
執行了Animal類的銷燬方法。。。。。

輸出結果中有如下資訊。

當前bean的名字:blue
解析後的字串為:你好Windows 10 年齡:360
傳入的ioc:org.springframework.context.annotation.AnnotationConfigApplicationContext@5ecddf8f, started on Wed Aug 19 00:10:13 CST 2020

說明正確的輸出了結果資訊。

XxxAware原理

XxxAware的底層原理是由XxxAwareProcessor類實現的, 例如,我們這裡以ApplicationContextAware介面為例,ApplicationContextAware介面的底層原理就是由ApplicationContextAwareProcessor類實現的。從ApplicationContextAwareProcessor類的原始碼可以看出,其實現了BeanPostProcessor介面,本質上都是後置處理器。

class ApplicationContextAwareProcessor implements BeanPostProcessor

接下來,我們就以分析ApplicationContextAware介面的原理為例,看看Spring是怎麼將ApplicationContext物件注入到Blue類中的。

我們在Blue類的setApplicationContext()方法上打一個斷點,如下所示。

接下來,我們以debug的方式來執行AutowiredTest類的testAutowired02()方法,

這裡,我們可以看到,實際上ApplicationContext物件已經注入到Blue類中的setApplicationContext()方法中了。我們在IDEA的方法呼叫棧中選擇postProcessBeforeInitialization()方法,如下所示。

我們雙擊IDEA中的postProcessBeforeInitialization()方法的呼叫棧,會在IDEA中自動定位到postProcessBeforeInitialization()方法中,如下所示。

其實,postProcessBeforeInitialization()方法所在的類就是ApplicationContextAwareProcessor。postProcessBeforeInitialization()方法的邏輯比較簡單。

我們來看下在postProcessBeforeInitialization()方法中呼叫的invokeAwareInterfaces()方法,如下所示。

看到這裡,大家是不是有種豁然開朗的感覺!Blue類實現了ApplicationContextAware介面後,Spring為啥會將ApplicationContext物件自動注入到setApplicationContext()方法中就不用說了吧!

其實就是這麼簡單!

重磅福利

關注「 冰河技術 」微信公眾號,後臺回覆 “設計模式” 關鍵字領取《深入淺出Java 23種設計模式》PDF文件。回覆“Java8”關鍵字領取《Java8新特性教程》PDF文件。回覆“限流”關鍵字獲取《億級流量下的分散式限流解決方案》PDF文件,三本PDF均是由冰河原創並整理的超硬核教程,面試必備!!

好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!

寫在最後

如果你覺得冰河寫的還不錯,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫!

相關文章