0000 - Spring 中常用註解原理剖析
1、概述
Spring 框架核心組件之一是 IOC,IOC 則管理 Bean 的創建和 Bean 之間的依賴註入,對於 Bean 的創建可以通過在 XML 裏面使用 <bean/>
標簽來配置,對於 Bean 之間的依賴可以使用構造方法註入、Set 方法註入在 XML 裏面配置。但是使用這種方式會使 XML 變的比較臃腫龐大,並且還需要開發人員一個個的在 XML 裏面配置 Bean 之間的依賴,這簡直是一個災難,還好 Spring 框架給我們提供了一系列的註解讓開發人員從這個災難中解脫出來,比如在一個類的成員變量上標註了一個簡單的 @Autowired 註解就可以實現了 Bean 之間的自動依賴註入,在一個類上標註了一個簡單的 @Component 註解就可以讓一個 Bean 註入到 Spring 容器……而 Spring 框架是如何通過註解簡化我們的工作量,實現這些功能的。
2、註解 Autowired
首先來了解下 AutowiredAnnotationBeanPostProcessor 的類圖結構,如下圖:
可知 AutowiredAnnotationBeanPostProcessor 直接或者間接實現了 Spring 框架的好多擴展接口:
- 實現了 BeanFactoryAware 接口,可以讓 AutowiredAnnotationBeanPostProcessor 獲取到當前 Spring 應用程序上下文管理的 BeanFactory,從而可以獲取到 BeanFactory 裏面所有的 Bean。
- 實現了 MergedBeanDefinitionPostProcessor 接口,可以讓 AutowiredAnnotationBeanPostProcessor 對 BeanFactory 裏面的 Bean 在被實例化前對 Bean 定義進行修改。
- 繼承了 InstantiationAwareBeanPostProcessorAdapter,可以讓 AutowiredAnnotationBeanPostProcessor 在 Bean 實例化後執行屬性設置。
下面看看這些擴展接口在 AutowiredAnnotationBeanPostProcessor 中調用時機,以及在實現依賴註入時候充當了什麽作用,AutowiredAnnotationBeanPostProcessor 的代碼執行時序圖如下:
- 代碼(1)Spring 框架會在創建 AutowiredAnnotationBeanPostProcessor 實例過程中調用 setBeanFactory 方法註入 Spring 應用程序上下文管理的 BeanFactory 到 AutowiredAnnotationBeanPostProcessor 中,所以 AutowiredAnnotationBeanPostProcessor 就可以操作 BeanFactory 裏面的所有的 Bean 了。
- 代碼(2)在 Spring 中每個 Bean 實例化前,Spring 框架都會調用 AutowiredAnnotationBeanPostProcessor 的
postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName)
方法,用來對當前 Bean 的定義(beanDefinition)進行修改,這裏主要通過 findAutowiringMetadata 方法找到當前 Bean 中標註 @Autowired 註解的屬性變量和方法。
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
//根據當前bean信息生成緩存key
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
//緩存中是否存在當前bean的元數據
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
//不存在則收集,並放入緩存
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
這裏是先通過 buildAutowiringMetadata 收集當前 Bean 中的註解信息,其中會先查找當前類裏面的註解信息,對應在變量上標註 @Autowired 的變量會創建一個 AutowiredFieldElement 實例用來記錄註解信息,對應在 set 方法上標註 @Autowired 的方法會創建一個 AutowiredMethodElement 對象來保存註解信息。然後會遞歸解析當前類的直接父類裏面的註解,並把最遠父類到當前類裏面的註解信息依次存放到InjectionMetadata對象(內部使用集合保存所有方法和屬性上的註解元素對象),然後緩存起來以便後面使用,這裏的緩存實際是個並發 map:
private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);
- 代碼(11)則是在對Spring的BeanFactory裏面的bean實例化後初始化前調用 AutowiredAnnotationBeanPostProcessor 的
PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
方法設置依賴註入對象;首先代碼(12)獲取當前 Bean 裏面的依賴元數據信息,由於在步驟(2)時候已經收集到了緩存,所以這裏是直接從緩存獲取的;這裏獲取的就是步驟(2)緩存的 InjectionMetadata 對象;步驟(13)則逐個調用 InjectionMetadata 內部集合裏面存放的屬性和方法註解對象的 inject 方法,通過反射設置依賴的屬性值和反射調用 set 方法設置屬性值。
如果註解加到了變量上則會調用 AutowiredFieldElement 的 inject 方法用來通過反射設置屬性值:
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
...
//解析依賴的bean
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
...
//反射設置依賴屬性值
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
如果註解加到了 set 方法上則調用 AutowiredMethodElement 的 inject 方法通過反射調用 set 方法設置依賴的變量值:
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
...
Method method = (Method) this.member;
Object[] arguments;
...
//解析依賴的bean
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
...
//反射設置調用set方法設置屬性值
if (arguments != null) {
try {
ReflectionUtils.makeAccessible(method);
method.invoke(bean, arguments);
}
catch (InvocationTargetException ex){
throw ex.getTargetException();
}
}
}
這裏需要註意的是 @Autowired 註解有一個布爾變量的 required 屬性,用來決定在依賴註入時候是否檢測依賴的 Bean 在 BeanFactory 裏面是否存在,默認是 true,就是如果不存在就會拋出下面異常:org.springframework.beans.factory.NoSuchBeanDefinitionException
異常,這個讀者可以把 bean-test.xml 裏面的 serviceA 註入代碼去掉測試下。
如果 required 設置為 false,則在依賴註入時候不去檢查依賴的 Bean 是否存在,而是在你具體使用依賴的 Bean 時候才會拋出 NPE 異常:
Exception in thread "main" java.lang.NullPointerException
at com.jiaduo.test.ServiceB.sayHello(ServiceB.java:19)
at com.jiaduo.test.TestAuowired.main(TestAuowired.java:14)
具體做檢驗的地方就是上代碼的 resolveDependency 方法裏面。
註:@Autowired 的使用簡化了我們的開發,其原理是使用 AutowiredAnnotationBeanPostProcessor 類來實現,該類實現了 Spring 框架的一些擴展接口,通過實現 BeanFactoryAware 接口使其內部持有了 BeanFactory(可輕松的獲取需要依賴的的 Bean);通過實現 MergedBeanDefinitionPostProcessor 擴展接口,在 BeanFactory 裏面的每個 Bean 實例化前獲取到每個 Bean 裏面的 @Autowired 信息並緩存下來;通過實現 Spring 框架的 postProcessPropertyValues 擴展接口在 BeanFactory 裏面的每個 Bean 實例後從緩存取出對應的註解信息,獲取依賴對象,並通過反射設置到 Bean 屬性裏面。
3、註解Required
“註解Autowired的簡單使用”小節運行後結果會輸出 serviceA sayHello null,其中 null 是因為沒給 serviceA 裏面的屬性 serviceName 賦值的原因,在開發時候開發人員也會比較容易犯這個錯誤,而要等運行時使用該屬性的時候才知道沒有賦值。那麽有沒有辦法在 Spring 框架進行 Bean 創建時候就進行檢查某些必要的屬性是否被設置了呢?
其實 @Required 就是做這個的,比如如果你想在 Spring 創建 ServiceA 時候就檢查 serviceName 有沒有被設置,你需要在 serviceName 的 set 方法上加入 @Required 註解:
@Required
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
並且需要在 XML 裏面添加下面配置,它是 @Required 註解的處理器:
bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />
加上這些後在運行代碼會輸出下面結果:
可見 Spring 在設置 ServiceA 的實例的屬性時候會檢查該屬性是否被設置,如果沒有則會拋出異常。
通過在 bean-test.xml 裏面添加屬性設置如下:
<bean id="serviceA" class="com.jiaduo.test.ServiceA">
<property name="serviceName" value="Service Name" />
</bean>
然後在運行,輸出結果如下:
serviceA sayHello Service Name
RequiredAnnotationBeanPostProcessor 原理解析
RequiredAnnotationBeanPostProcessor 類似 AutowiredAnnotationBeanPostProcessor 也是間接或者直接實現了 Spring 框架相同的接口。通過實現 BeanFactoryAware 接口內部持有了 BeanFactory(可輕松的獲取需要依賴的Bean);通過實現 Spring 框架的 postProcessPropertyValues 擴展接口在 BeanFactory 裏面的每個 Bean 實例後設置屬性前,檢查標註 @Required 的 set 訪問器對應的屬性是否被設置。
這個邏輯比較簡單,直接看下 RequiredAnnotationBeanPostProcessor 的 postProcessPropertyValues 方法:
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
if (!this.validatedBeanNames.contains(beanName)) {
if (!shouldSkip(this.beanFactory, beanName)) {
List<String> invalidProperties = new ArrayList<>();
for (PropertyDescriptor pd : pds) {
//判斷屬性的set方法是否標註了@Required註解,並且是否該屬性沒有被設置
if (isRequiredProperty(pd) && !pvs.contains(pd.getName())) {
invalidProperties.add(pd.getName());
}
}
//如果發現屬性的set方法標註了@Required註解,但是屬性沒有被設置,則拋出異常
if (!invalidProperties.isEmpty()) {
throw new BeanInitializationException(buildExceptionMessage(invalidProperties, beanName));
}
}
this.validatedBeanNames.add(beanName);
}
return pvs;
}
其中 isRequiredProperty 作用是判斷當前屬性的 set 方法是否標註了 @Required 註解,代碼如下:
protected boolean isRequiredProperty(PropertyDescriptor propertyDescriptor) {
Method setter = propertyDescriptor.getWriteMethod();
return (setter != null && AnnotationUtils.getAnnotation(setter, getRequiredAnnotationType()) != null);
}
註:使用 @Autowired 和 @Required 時候需要註入對應的註解處理器,這很麻煩,所以 Spring 框架添加了一個 <context:annotation-config />
標簽,當你在 XML 裏面引入這個標簽後,就默認註入了 AutowiredAnnotationBeanPostProcessor 和 RequiredAnnotationBeanPostProcessor 。
@Component("serviceA")
public class ServiceA {
...
}
當一個類上標註 @Component 註解時候,Spring 框架會自動註冊該類的一個實例到 Spring 容器,但是我們需要告訴 Spring 框架需要到去哪裏查找標註該註解的類,所以需要在 bean-test.xml 裏面配置如下:
<context:component-scan base-package="com.jiaduo.test" />
其中 base-package 就是告訴 Spring 框架要去查找哪些包下的標註 @Component 註解的類。
下面我們來研究下 Spring 框架是如何解析 <context:component-scan/>
標簽並掃描標註 @Component 註解的 Bean 註冊到 Spring 容器的。
首先看下解析 <context:component-scan/>
標簽的 ComponentScanBeanDefinitionParser 類的時序圖:
- 如上時序圖步驟(3)創建了一個 ClassPathBeanDefinitionScanner 掃描器,代碼如下:
protected ClassPathBeanDefinitionScanner createScanner(XmlReaderContext readerContext, boolean useDefaultFilters) {
return new ClassPathBeanDefinitionScanner(readerContext.getRegistry(), useDefaultFilters,
readerContext.getEnvironment(), readerContext.getResourceLoader());
}
變量 useDefaultFilters 說明是否使用默認的 filters,所謂 filter 也就是過濾器,這裏 ClassPathBeanDefinitionScanner 會掃描指定包路徑裏面的類,但是那些需要的類,就是通過 filter 進行過濾的,默認 useDefaultFilters 為 true,<context:component-scan base-package="com.jiaduo.test"/>
等價於 <context:component-scan base-package="com.jiaduo.test" use-default-filters="true"/>
,會使用下面代碼註冊默認 filters:
protected void registerDefaultFilters() {
//這裏決定指定包路徑下標註@Component註解的類是我們想要的
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
}
catch (ClassNotFoundException ex) {
...
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
}
catch (ClassNotFoundException ex) {
... }
}
- 步驟(4)解析用戶自定義的 BeanNameGenerator 的實現類,用來給掃描的類起一個在 BeanFactory 裏面的名字,配置如下:
<context:component-scan base-package="com.jiaduo.test" name-generator="com.my.name.generator.MyBeanNameGenerator"/>
其中 name-generator 指定 BeanNameGenerator 的實現類的包路徑+類名,內部會創建一個該類的實例。
-
步驟(5)主要用來設置是否對掃描類進行 scope-proxy,我們知道在 XML 裏面配置 Bean 的時候可以指定 scop 屬性來配置該 Bean 的作用域為 singleton、prototype、request、session等,對應後三者來說,Spring 的實現是對標註該作用域的 Bean 進行代理來實現的,而我們知道 Spring 代理為 JDK 代理和 CGLIB 代理(可以參考 Chat:Spring 框架之 AOP 原理剖析),所以步驟(5)作用就是讓用戶通過 scoped-proxy 指定代理方式:
<context:component-scan base-package="com.jiaduo.test" scoped-proxy="no"/>
,這是默認方式不進行代理;scoped-proxy="interfaces" 標示對接口進行代理,也就是使用 JDK 動態代理;scoped-proxy="targetClass" 標示對目標對象進行代理,也就是使用 CGLIB 進行代理。 -
步驟(6)解析用戶自定義過濾器,前面我們說了,默認下 use-default-filters=true,默認掃描之後只會註入標註 @Component 的元素;這裏則允許用戶自定義攔截器,設置需要註冊掃描到的那些類和排除掃描到的那些類,如下配置:
<context:component-scan base-package="com.jiaduo.test"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Component" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
需要註意的是,當使用子標簽 <context:include-filter/>
和 <context:exclude-filter/>
自定義用戶過濾器時候需要這樣設置:use-default-filters="false"
才會生效。
-
代碼(7)則執行具體掃描,其中 basePackages 是註解裏面 base-package 解析後的包路徑列表,我們在指定base-package時候可以通過
,; \t\n
中其中一個分隔符指定多個包路徑,比如:<context:component-scan base-package="com.jiaduo.test;com.jiaduo.test1"/>
。步驟(8)查找當前包路徑下滿足過濾器列表的候選bean,默認是查找所有標註了@Component註解的Bean。步驟(13)則註冊滿足條件的bean到Spring容器。 -
步驟(14)註冊一些組元,比如步驟(15)默認情況下會註冊的前面提到的
<context:annotation-config />
標簽實現的內容,你可以通過下面方式關閉該功能:
<context:component-scan base-package="com.jiaduo.test" annotation-config="false" />
註:當我們在 XML 裏面配置 <context:component-scan/>
標簽後,Spring 框架會根據標簽內指定的包路徑下查找指定過濾條件的 Bean,並可以根據標簽內配置的 BeanNameGenerator 生成 Bean 的名稱,根據標簽內配置的 scope-proxy 屬性配置 Bean 被代理的方式,根據子標簽 <context:include-filter/>,<context:exclude-filter/>
配置自定義過濾條件。
5、註解 @Configuration、@ComponentScan、@Import、@PropertySource、@Bean工作原理
在 Spring 框架中,每個應用程序上下文(ApplicationContext)管理著一個 BeanFactory,應用程序上下文則是對 BeanFactory 和 Bean 的生命周期中的各個環節進行管理。
而應用程序上下文的子類除了有解析 XML 作為 Bean 來源的 ClassPathXmlApplicationContext,還有基於掃描註解類作為 Bean 來源的 AnnotationConfigApplicationContext,本節就結合 AnnotationConfigApplicationContext 應用程序上下文來講解 @Configuration、@ComponentScan、@Import、@PropertySource、@Bean註解的使用與原理。
其中 ServiceA 和 ServiceB 代碼修改如下:
public class ServiceB {
public ServiceA getServiceA() {
return serviceA;
}
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
@Autowired
private ServiceA serviceA;
public void sayHello() {
serviceA.sayHello();
}
}
public class ServiceA {
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
//這裏加了一個註解
@Value("${service.name}")
private String serviceName;
public void sayHello() {
System.out.println("serviceA sayHello " + serviceName);
}
}
其中 ConfigBean 代碼如下:
package com.jiaduo.test.annotation.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.jiaduo.test.annotation.ServiceA;
@Configuration
public class ConfigBean {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
}
其中 ConfigBean2 代碼如下:
package com.jiaduo.test.annotation.sdk.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import com.jiaduo.test.annotation.ServiceA;
import com.jiaduo.test.annotation.ServiceB;
@Configuration
public class ConfigBean2 {
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
}
配置文件 config.properties 內容如下:
service.name=Annotation Learn
其中測試類 TestAnnotation 代碼如下:
@Configuration//(1)
@ComponentScan(basePackages = "com.jiaduo.test.annotation.config") // (2)
@Import(com.jiaduo.test.annotation.sdk.config.ConfigBean2.class) // (3)
@PropertySource(value={"classpath:config.properties"})//(4)
public class TestAnnotaion {
public static void main(String[] args) {
// (5)
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TestAnnotaion.class);
ctx.getBean("serviceB", ServiceB.class).sayHello();// (6)
}
}
運行 TestAnnotaion 的 main 方代碼輸出:serviceA sayHello Annotation Learn。
代碼(5)創建一個 AnnotationConfigApplicationContext 類型的應用程序上下文,構造函數參數為 TestAnnotaion.class。
其內部會解析 TestAnnotaion 類上的 ComponentScan 註解,並掃描 basePackages 指定的包裏面的所有標註 @Configuration 註解的類(這裏會註入 ConfigBean 類到 Spring 容器),然後解析 ConfigBean 內部標註有 @Bean 的方法,把方法內創建的對象註入到 Spring 容器(這裏是把 serviceA 註入到了 Spring 容器)。
然後會解析 TestAnnotaion 類上的 Import 註解,Import 註解作用是把標註 @Configuration 註解裏面創建的 Bean 註入到 Spring 容器,這裏是把 ConfigBean2 裏面創建的 serviceB 註入到了 Spring 容器。
原理剖析
從 Demo 可知一切源於 AnnotationConfigApplicationContext,那麽就從 AnnotationConfigApplicationContext 的構造函數開始,代碼如下:
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
//調用無參構造函數
this();
//註冊含有註解的類
register(annotatedClasses);
//刷新應用程序上下文
refresh();
}
其調用時序圖如下:
-
如上時序圖步驟(1)調用了 AnnotationConfigApplicationContext 的無參構造函數,其內部創建了一個 AnnotatedBeanDefinitionReader 對象,該對象構造函數內部調用 AnnotationConfigUtil.registerAnnotationConfigProcessors 方法註冊了註解處理器(其作用等價於在 XML 裏面配置
<context:annotation-config />
),其中就註冊了 ConfigurationClassPostProcessor 處理器,該處理器就是專門用來處理 @Configuration 註解的,這個後面再講。 -
步驟(4)則是註冊 AnnotationConfigApplicationContext 構造函數裏面傳遞的含有註解的類到 Spring 容器(這裏是註冊 TestAnnotaion 類到 Spring 容器)。
-
步驟(6)刷新應用程序上下文,使用註冊的 ConfigurationClassPostProcessor 處理器解析 TestAnnotaion 上的註解,並註冊相應的 Bean 到 Spring 容器。
下面主要來看下 ConfigurationClassPostProcessor 的處理時序圖:
-
ConfigurationClassPostProcessor 實現了 Spring 框架的 BeanDefinitionRegistryPostProcessor 接口所以具有 postProcessBeanDefinitionRegistry 方法
-
其中步驟(2)遍歷應用程序上下文中的 Bean 查找標註 @Configuration 的 Bean 定義,具體是使用 checkConfigurationClassCandidate 方法檢測,代碼如下:
public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
...
//該類上是否標註了@Configuration註解
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
//該類上是否標註了@Component,@ComponentScan,@Import註解
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
else {
return false;
}
...
return true;
}
可知只有類上標註 @Configuration、@Component、@ComponentScan、@Import 註解的 Bean 才是候選 Bean。
- 步驟(4)創建了一個 ConfigurationClassParser 對象,這個對象就是專門用來解析標註 @Configuration 註解的 Bean 的。這裏首先調用 parse 方法對步驟(2)產生的候選 Bean 進行解析,本文例子是先對 TestAnnotaion 類解析,並會對 TestAnnotaion 上的 @ComponentScan 和 @Import 進行解析,解析出來的 Bean 可能又含有了 @Configuration 註解,那麽把這些新的包含 @Configuration 的 Bean 作為候選 Bean 後然後調用 parse 方法,依次類推直到 parse 解析出來的 Bean 不在包含 @Configuration 註解。其中步驟(7)則是註冊解析到的標註 @Import 的 Bean 和 @Bean 的 Bean 到 Spring 容器。
下面著重講解下 ConfigurationClassParser 的 parse 方法:
-
其中最外層循環是遞歸解析 configuration 類和它的超類中標註 @Configuration 的類,也就是解析完當前類,會設置 sourceClass=sourceClass.getSuperClass();
-
循環內步驟(5)、(6)是解析並處理所有標註 @PropertySources 註解的Bean,具體代碼如下:
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
...
//獲取註解上的值
String[] locations = propertySource.getStringArray("value");
...
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
//設置location裏面的屬性到Spring的環境environment
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
...
}
}
}
private void addPropertySource(PropertySource<?> propertySource) {
//獲取Spring環境environment裏面的屬性集
String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
...
//添加註解@PropertySource裏面的配置文件信息到Spring環境
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
this.propertySourceNames.add(name);
}
- 循環內步驟(7)、(8)是解析標註 @ComponentScan 的類,並註冊掃描到的類到 Spring 容器,並且對掃描到含有 @Configuration 的類在進行解析,具體解析 @ComponentScan 的邏輯如下:
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
//創建一個掃描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
//配置掃描器中bean的BeanNameGenerator
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
//設置scopedProxy
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
//設置資源匹配模式
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
//設置掃描器的過濾條件
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
//是否延遲初始化
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
//解析掃描包路徑
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
basePackages.addAll(Arrays.asList(tokenized));
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
//執行掃描
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
其內部邏輯與 <context:component-scan/>
相似,這裏不再累述了。
- 步驟(10)解析所有標註 @Import 的 Bean,具體註入到 Spring 容器實際是在 ConfigurationClassPostProcessor 的時序圖的步驟(7),其中掃描 @Import 註解的遞歸代碼如下:
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
- 步驟(11)解析所有標註 @Bean 的方法,具體註入操作是在 ConfigurationClassPostProcessor 的時序圖的步驟(7),解析 @Bean 的代碼如下:
private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {
AnnotationMetadata original = sourceClass.getMetadata();
//獲取所有標註@Bean的方法元數據,這些方法返回順序是任意的
Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());
if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {
try {
//使用asm讀取字節碼文件,並獲取標註@Bean的方法到asmMethods,返回的方法的順序和聲明的一樣
AnnotationMetadata asm =
this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();
Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());
if (asmMethods.size() >= beanMethods.size()) {
Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());
for (MethodMetadata asmMethod : asmMethods) {
for (MethodMetadata beanMethod : beanMethods) {
if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {
selectedMethods.add(beanMethod);
break;
}
}
}
if (selectedMethods.size() == beanMethods.size()) {
beanMethods = selectedMethods;
}
}
}
catch (IOException ex) {
}
}
return beanMethods;
}
註: ConfigurationClassPostProcessor 處理器是 Spring 框架處理本節這些註解的關鍵類,本節內容較為復雜,在解析註解使用運用了大量的循環嵌套和遞歸算法,代碼研究起來還是有一定難度的,希望讀者結合時序圖慢慢理解。
0000 - Spring 中常用註解原理剖析