javascript
从源码深处体验Spring核心技术--基于注解的IOC初始化
Annotation 的前世今生
從 Spring2.0 以后的版本中,Spring 也引入了基于注解(Annotation)方式的配置,注解(Annotation)是 JDK1.5 中引入的一個新特性,用于簡化 Bean 的配置,可以取代 XML 配置文件。
開發人員對注解(Annotation)的態度也是蘿卜青菜各有所愛,個人認為注解可以大大簡化配置,提高開發速度,但也給后期維護增加了難度。目前來說 XML 方式發展的相對成熟,方便于統一管理。
隨著 Spring Boot 的興起,基于注解的開發甚至實現了零配置。但作為個人的習慣而言,還是傾向于 XML 配置文件和注解(Annotation)相互配合使用。
Spring IOC 容器對于類級別的注解和類內部的注解分以下兩種處理策略:
1)、類級別的注解:如@Component、@Repository、@Controller、@Service 以及 JavaEE6 的 @ManagedBean 和@Named 注解,都是添加在類上面的類級別注解,Spring 容器根據注解的過濾規則掃描讀取注解 Bean 定義類,并將其注冊到 Spring IOC 容器中。
2)、類內部的注解:如@Autowire、@Value、@Resource 以及 EJB 和 WebService 相關的注解等,都是添加在類內部的字段或者方法上的類內部注解,SpringIOC 容器通過 Bean 后置注解處理器解析Bean 內部的注解。下面將根據這兩種處理策略,分別分析 Spring 處理注解相關的源碼。
定位 Bean 掃描路徑
在 Spring 中 管 理 注 解 Bean 定 義 的 容 器 有 兩 個 :
AnnotationConfigApplicationContext 和 AnnotationConfigWebApplicationContex。
這兩個類是專門處理 Spring 注解方式配置的容器,直接依賴于注解作為容器配置信息來源的 IOC 容器。
AnnotationConfigWebApplicationContext是AnnotationConfigApplicationContext 的 Web 版本,兩者的用法以及對注解的處理方式幾乎沒有差別。現在我們以AnnotationConfigApplicationContext 為例看看它的源碼:
通過上面的源碼分析,我們可以看啊到 Spring 對注解的處理分為兩種方式:
1)、直接將注解 Bean 注冊到容器中
可以在初始化容器時注冊;也可以在容器創建之后手動調用注冊方法向容器注冊,然后通過手動刷新容器,使得容器對注冊的注解 Bean 進行處理。
2)、通過掃描指定的包及其子包下的所有類
在初始化注解容器時指定要自動掃描的路徑,如果容器創建以后向給定路徑動態添加了注解 Bean,則需要手動調用容器掃描的方法,然后手動刷新容器,使得容器對所注冊的 Bean 進行處理。
接下來,將會對兩種處理方式詳細分析其實現過程。
讀取 Annotation 元數據
當創建注解處理容器時,如果傳入的初始參數是具體的注解 Bean 定義類時,注解容器讀取并注冊。
1)、AnnotationConfigApplicationContext 通過調用注解 Bean 定義讀取器
AnnotatedBeanDefinitionReader 的 register()方法向容器注冊指定的注解 Bean,注解 Bean 定義讀取器向容器注冊注解 Bean 的源碼如下:
從上面的源碼我們可以看出,注冊注解 Bean 定義類的基本步驟:
a、需要使用注解元數據解析器解析注解 Bean 中關于作用域的配置。
b、使用 AnnotationConfigUtils 的 processCommonDefinitionAnnotations()方法處理注解 Bean 定義類中通用的注解。
c、使用 AnnotationConfigUtils 的 applyScopedProxyMode()方法創建對于作用域的代理對象。
d、通過 BeanDefinitionReaderUtils 向容器注冊 Bean。
下面我們繼續分析這 4 步的具體實現過程2)、AnnotationScopeMetadataResolver 解析作用域元數據
AnnotationScopeMetadataResolver 通過 resolveScopeMetadata()方法解析注解 Bean 定義類的作用域元信息,即判斷注冊的 Bean 是原生類型(prototype)還是單態(singleton)類型,其源碼如下:
上述代碼中的 annDef.getMetadata().getAnnotationAttributes()方法就是獲取對象中指定類型的注解的值。
3)、AnnotationConfigUtils 處理注解 Bean 定義類中的通用注解
AnnotationConfigUtils 類的 processCommonDefinitionAnnotations()在向容器注冊 Bean 之前,首先對注解 Bean 定義類中的通用 Spring 注解進行處理,源碼如下:
4)、AnnotationConfigUtils 根據注解Bean定義類中配置的作用域為其應用相應的代理策略
AnnotationConfigUtils 類的 applyScopedProxyMode()方法根據注解 Bean 定義類中配置的作用域@Scope 注解的值,為 Bean 定義應用相應的代理模式,主要是在 Spring 面向切面編程(AOP)中使用。
源碼如下:
這段為 Bean 引用創建相應模式的代理,這里不做深入的分析。
5)、BeanDefinitionReaderUtils 向容器注冊 Bean
BeanDefinitionReaderUtils 主要是校驗 BeanDefinition 信息,然后將 Bean 添加到容器中一個管理BeanDefinition 的 HashMap 中。
掃描指定包并解析為 BeanDefinition
當創建注解處理容器時,如果傳入的初始參數是注解 Bean 定義類所在的包時,注解容器將掃描給定的包及其子包,將掃描到的注解 Bean 定義載入并注冊。
1)、ClassPathBeanDefinitionScanner 掃描給定的包及其子包
AnnotationConfigApplicationContext 通 過 調 用 類 路 徑 Bean 定 義 掃 描 器 ClassPathBeanDefinitionScanner 掃描給定包及其子包下的所有類,主要源碼如下:
類路徑 Bean 定義掃描器 ClassPathBeanDefinitionScanner 主要通過findCandidateComponents()
方法調用其父類 ClassPathScanningCandidateComponentProvider 類來掃描獲取給定包及其子包下的類。
2)、ClassPathScanningCandidateComponentProvider 掃描給定包及其子包的類
ClassPathScanningCandidateComponentProvider 類的 findCandidateComponents()方法具體實現掃描給定類路徑包的功能,主要源碼如下:
注冊注解 BeanDefinition
AnnotationConfigWebApplicationContext 是 AnnotationConfigApplicationContext 的 Web 版, 它們對于注解 Bean 的注冊和掃描是基本相同的,但是AnnotationConfigWebApplicationContext 對注解 Bean 定義的載入稍有不同,AnnotationConfigWebApplicationContext 注入注解 Bean 定義源碼如下:
以上就是解析和注入注解配置資源的全過程分析。
IOC 容器小結
現在通過上面的代碼,總結一下 IOC 容器初始化的基本步驟:
1、初始化的入口在容器實現中的 refresh()調用來完成。
2、對 Bean 定義載入 IOC 容器使用的方法是 loadBeanDefinition(),
其中的大致過程如下:通過 ResourceLoader 來完成資源文件位置的定位,DefaultResourceLoader是默認的實現,同時上下文本身就給出了 ResourceLoader 的實現,可以從類路徑,文件系統,URL 等方式來定為資源位置。
如果是 XmlBeanFactory 作為 IOC 容器,那么需要為它指定 Bean 定義的資源,也 就 是 說 Bean 定 義 文 件 時 通 過 抽 象 成 Resource 來 被 IOC 容 器 處 理 的 , 容器通過BeanDefinitionReader 來 完 成 定 義 信 息 的 解 析 和 Bean 信 息 的 注 冊 , 往 往 使 用 的 是 XmlBeanDefinitionReader 來 解 析 Bean 的 XML 定 義 文 件 - 實 際 的 處 理 過 程 是 委 托 給 BeanDefinitionParserDelegate 來完成的。
從而得到 bean 的定義信息,這些信息在 Spring 中使用 BeanDefinition對象來表示-這個名字可以讓我們想到loadBeanDefinition(),registerBeanDefinition()這些相關方法。
它們都是為處理 BeanDefinitin 服務的,容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注冊,這由 IOC 實現 BeanDefinitionRegistry 接口來實現。注冊過程就是在 IOC 容器內部維護的一個 HashMap 來保存得到的 BeanDefinition 的過程。
這個 HashMap 是 IOC 容器持有Bean 信息的場所,以后對 Bean 的操作都是圍繞這個 HashMap 來實現的。
然后我們就可以通過 BeanFactory 和 ApplicationContext 來享受到 Spring IOC 的服務了,在使用 IOC容器的時候,我們注意到除了少量粘合代碼,絕大多數以正確 IOC 風格編寫的應用程序代碼完全不用關心如何到達工廠,因為容器將把這些對象與容器管理的其他對象鉤在一起。
基本的策略是把工廠放到已知的地方,最好是放在對預期使用的上下文有意義的地方,以及代碼將實際需要訪問工廠的地方。
Spring本身提供了對聲明式載入web應用程序用法的應用程序上下文,并將其存儲在ServletContext中的框架實現。
以下是容器初始化全過程的時序圖
?
總結
以上是生活随笔為你收集整理的从源码深处体验Spring核心技术--基于注解的IOC初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从源码深处体验Spring核心技术--基
- 下一篇: gradle idea java ssm