Spring是如何處理註解的

若是你看到了註解,那麼必定有什麼代碼在什麼地方處理了它.

Alan Hohnspring

我教Java課程時強調的一點是註解是惰性的。換句話說,它們只是標記,可能具備某些屬性,但沒有本身的行爲。所以,每當你在一段Java代碼上看到一個註解時,就意味着必須有一些其餘的Java代碼來尋找那個註解幷包含真正的智能來作一些有用的東西。數據庫

不幸的是,這種推理的問題在於,確切地肯定哪一段代碼正在處理註解是很是困難的,特別是若是它在庫中。處理註解的代碼可能會使人困惑,由於它使用反射而且必須以很是抽象的方式編寫。因此我認爲值得看看一個作得很好的例子來看看它是如何運行的。緩存

咱們詳細研究一下 Spring 框架中的 InitDestroyAnnotationBeanPostProcessor 類是如何工做的。選擇這個,由於它相對簡單,只作了一些相對容易解釋的事情, 碰巧和我手頭的工做相關。數據結構

Spring Bean 的後處理

首先,我想首先解釋一下 Spring 的用途。Spring 框架所作的一件事就是「依賴注入」。這改變了咱們以往用代碼將模塊串在一塊兒的方式。例如,假設咱們編寫了一些須要鏈接數據庫的應用程序邏輯, 但並想將提供該鏈接的特定硬類編碼到應用程序邏輯中,咱們能夠在構造函數或setter方法中將其表示爲依賴項:架構

class MyApplication {
    private DataConnection data;
    ...
    public void setData(DataConnection data) {
        this.data = data;
    }
    ...
}

固然,若是想的話, 咱們能夠本身編寫一個簡單的庫完成這種依賴注入,從而避免添加對 Spring 的依賴項。可是若是咱們在編寫一個複雜的應用程序, 想將各模塊鏈接在一塊兒,那麼Spring能夠很是方便。app

既然沒有什麼神祕的,若是咱們要讓 Spring 爲咱們注入這些依賴,那麼就會有一個權衡。Spring 須要「知道」依賴關係以及應用程序中的類和對象。Spring 處理這個問題的方法可能是由 Spring 框架對對象進行實例化; 從而能夠在稱爲"應用程序上下文"的大數據結構中跟蹤管理這此對象。框架

後處理和初始化

並且這裏是 InitDestroyBeanPostProcessor 進入的地方 。若是 Spring 要處理實例化,那麼在對象實例化完成以後,可是在應用程序開始真正的運行以前,須要進行一些「額外工做」。須要作的一件「額外工做」就是調用對象來告訴他們何時徹底設置好,這樣他們就能夠進行任何須要的額外初始化。若是咱們使用「setter」注入,如上所述,便經過調用setXxx() 方法注入依賴項,這一點尤爲重要,由於在調用對象的構造函數時這些依賴項並不可用。因此 Spring 須要容許用戶指定在初始化對象後才應該調用的某個方法的名稱。ide

Spring 一直支持使用XML配置文件來定義由 Spring 來實例化的對象,在這種狀況下,有一個 'init-method' 屬性能夠用來指定初始化的方法。顯然,在這種狀況下,它仍然須要反射來實際查找並調用該方法。自Java 5起, 增長了註解,因此Spring 也支持帶註解的標記方法,將它們標識爲Spring應該實例化的對象,識別須要注入的依賴項,以及識別應該調用的初始化和銷燬​​方法。模塊化

最後一項 InitDestroyBeanPostProcessor 由其子類或其中一個子類處理。後處理器是一種特殊的對象,由Spring實例化,實現後處理器接口。由於它實現了這個接口,因此Spring會在每一個Spring實例化的對象上調用一個方法,容許它修改甚至替換該對象。這是Spring採用模塊化架構方法的一部分,能夠更輕鬆地擴展功能。函數

這是怎麼運做的?

事實上, JSR-250 肯定了一些「常見」註解,包括 @PostConstruct, 用於標記初始化方法,@PreDestroy 註解, 用於註解銷燬方法的。不一樣的是,InitDestroyBeanPostProcessor 被設計成能夠處理任何註解集,所以它提供了識別註解的方法:

public void setInitAnnotationType(Class<? extends Annotation> initAnnotationType) {
        this.initAnnotationType = initAnnotationType;
    }
...
    public void setDestroyAnnotationType(Class<? extends Annotation> destroyAnnotationType) {
        this.destroyAnnotationType = destroyAnnotationType;
    }

請注意,這些是普通的 setter 方法,所以這個對象自己可使用 Spring 進行設置。就我而言,我使用Spring 的 StaticApplicationContext,見我之前的文章

一旦 Spring 實例化了各類對象並注入了全部依賴項,它就會在全部後處理器上爲每一個對象調用 postProcessBeforeInitialization 方法 。這使後處理器有機會在初始化以前修改或替換對象。由於已經注入了依賴項,因此這是 InitDestroyAnnotationBeanPostProcessor 調用初始化方法的地方。

LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
    try {
        metadata.invokeInitMethods(bean, beanName);
    }

因爲咱們對代碼如何處理註解感興趣,咱們感興趣 findLifecycleMetadata() 方法,由於這是對類進行檢查的地方。該方法檢查緩存,該緩存用於避免執行超過必要的反射,由於它可能很昂貴。若是還沒有檢查該類,則調用 buildLifecycleMetadata() 方法。該方法的內容以下:

ReflectionUtils.doWithLocalMethods(targetClass, new ReflectionUtils.MethodCallback() {
    @Override
    public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
        if (initAnnotationType != null) {
            if (method.getAnnotation(initAnnotationType) != null) {
                LifecycleElement element = new LifecycleElement(method);
                currInitMethods.add(element);
            }
        }
        ...
    }
});

這裏 ReflectionUtils 是一個方便的類,簡化了反射的使用。除此以外,它還將通過反射的衆多已檢查異常轉換爲未經檢查的異常(?),從而使事情變得更容易。此特定方法僅迭代本地方法(即不是繼承的方法),併爲每一個方法調用回調。

完成全部設置以後,檢查註解的部分很是無聊; 它只是調用Java反射方法來檢查註解,若是找到它,則將該方法存儲爲初始化方法。

總結

事實上,這裏最終發生的事情很簡單,這就是我在教反射時所要作的事情。調試使用註解來控制行爲的代碼可能具備挑戰性,由於從外部來看它很是不透明,因此很難想象發生了什麼(或者沒有發生)和何時發生。但最終,正在發生的事情只是Java代碼; 它可能不會當即顯現出代碼的位置,但它就在那裏。

相關文章
相關標籤/搜索