1. 程式人生 > >0000 - Spring 中常用註解原理剖析

0000 - Spring 中常用註解原理剖析

遞歸算法 子類 實例化 iad ron dep ssl 行修改 rda

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 。

4、@Component 註解

@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 中常用註解原理剖析