1. 程式人生 > >框架源碼系列六:Spring源碼學習之Spring IOC源碼學習

框架源碼系列六:Spring源碼學習之Spring IOC源碼學習

文件創建 mys code array allow 點擊 繼承 listen service()

Spring 源碼學習過程:

技術分享圖片

一、搞明白IOC能做什麽,是怎麽做的

1. 搞明白IOC能做什麽?

  IOC是用為用戶創建、管理實例對象的。用戶需要實例對象時只需要向IOC容器獲取就行了,不用自己去創建,從而達到與具體類解耦。 

2. IOC是怎麽做到的,即它的實現步驟是怎麽樣的?

技術分享圖片

2.1 用戶配置bean定義

我們使用Spring IOC時有幾種方式來配置bean定義呢?

xml的方式:

    <bean id="abean" class="com.study.spring.samples.ABean">
        <constructor-arg 
type="String" value="abean01"></constructor-arg> <constructor-arg ref="cbean"></constructor-arg> </bean> <bean id="cbean" class="com.study.spring.samples.CBean"> <constructor-arg type="String" value="cbean01"></constructor-arg> </
bean>

註解方式:

package com.study.spring.samples;

import com.study.spring.context.config.annotation.Autowired;
import com.study.spring.context.config.annotation.Component;
import com.study.spring.context.config.annotation.Qualifier;
import com.study.spring.context.config.annotation.Value;

@Component(initMethodName 
= "init", destroyMethodName = "destroy") public class ABean { private String name; private CBean cb; @Autowired private DBean dbean; @Autowired public ABean(@Value("leesmall") String name, @Qualifier("cbean01") CBean cb) { super(); this.name = name; this.cb = cb; System.out.println("調用了含有CBean參數的構造方法"); } public ABean(String name, CCBean cb) { super(); this.name = name; this.cb = cb; System.out.println("調用了含有CCBean參數的構造方法"); } public ABean(CBean cb) { super(); this.cb = cb; } }

Java-based容器配置方式:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }

}

上面的AppConfig類等價於:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

想了解java容器配置的朋友請看這篇文章:

Spring Java-based容器配置

2.2 IOC容器加載bean定義

用戶以上面的三種方式配置bean定義以後,Spring IOC容器怎麽來加載用戶的bean定義呢,這就需要我們來告訴它了

xml的方式告訴Spring IOC容器怎麽加載bean定義:

        //類路徑下加載xml配置文件創建bean定義
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

        //文件系統下加載xml配置文件創建bean定義
        ApplicationContext context1 = new FileSystemXmlApplicationContext("e:/study/application.xml");

        //通用的xml方式加載xml配置文件創建bean定義
        ApplicationContext context3 = new GenericXmlApplicationContext("file:e:/study/application.xml");

註解方式告訴Spring IOC容器怎麽加載bean定義

xml方式指定註解要掃描的基礎包:

    <beans>
        <context:component-scan base-package="com.study" />
    </beans>

註解方式指定註解要掃描的基礎包:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages="com.study")
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }

}

也可在代碼中通過API指定註解掃描的基礎包:

        // 掃描註解的方式創建bean定義
        ApplicationContext ctx= new AnnotationConfigApplicationContext();
        ctx.scan("com.study");
        ctx.refresh();
        MyService myService = ctx.getBean(MyService.class);

Java-based容器配置告訴Spring IOC容器怎麽加載bean定義

使用AnnotationConfigApplicationContext告訴Spring IOC容器怎麽加載bean定義配置

跟實例化一個ClassPathXmlApplicationContext時將Spring XML文件用作輸入相似,在實例化一個AnnotationConfigApplicationContext時能夠使用@Configuration類作為輸入。這就等等於Spring容器全然零XML配置:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

AnnotationConfigApplicationContext不局限於僅僅使用@Configuration類。不論什麽@Component或JSR-330註解的類都能夠作為AnnotationConfigApplicationContext構造器的輸入:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

使用無參的構造器實例化AnnotationConfigApplicationContext,然後使用register()方法對容器進行配置。這樣的方式在以編程方式構造一個AnnotationConfigApplicationContext時非常實用:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

啟用scan(String…?)的組件掃描:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages="com.study")
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }

}

scan方法掃描:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.study");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

二、搞明白Spring IOC的從整體到部分

技術分享圖片

IOC整體是由以上幾部分組成起來工作的

三、找到Spring IOC入口,先理清楚主幹流程,然後再去研究各個流程的細節

我們從上面的使用示例,很清楚地看到,我們使用Spring IOC,只需要使用Spring提供的ApplicationContext這個API。ApplicationContext就是IOC容器。ApplicationContext就是Spring IOC的入口,源碼的學習就從它開始!

1. ApplicationContext是什麽

首先來了解ApplicationContext都是什麽,即它都有哪些角色、責任。它通過繼承很多接口而有很多角色。

ApplicationContext繼承的接口(角色)如下:

技術分享圖片

每個角色擁有的職責(方法):

技術分享圖片

再來了解 ApplicationContext 自己中定義的方法:

技術分享圖片

2. Application的子實現

技術分享圖片

說明:

從 AbstarctApplicationContext 之後分為兩類:xml 配置方式的實現和通用實現。它們的基本使用示例如下:

package com.study.leesmall.spring;

import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.study.leesmall.spring.service.Abean;
import com.study.leesmall.spring.service.CombatService;

//Spring不同的方式創建bean實例使用代碼示例
@Configuration
public class TestApplication {

    public static void main(String[] args) {

        //類路徑下加載xml配置文件創建bean定義
        ApplicationContext context1 = new ClassPathXmlApplicationContext("application.xml");
        CombatService cs = context1.getBean(CombatService.class);
        cs.doInit();
        cs.combating();

        //文件系統下加載xml配置文件創建bean定義
        ApplicationContext context2 = new FileSystemXmlApplicationContext("e:/study/application.xml");
        cs = context2.getBean(CombatService.class);
        cs.doInit();
        cs.combating();

        //通用的xml方式加載xml配置文件創建bean定義
        ApplicationContext context3 = new GenericXmlApplicationContext("file:e:/study/application.xml");
        cs = context3.getBean(CombatService.class);
        cs.doInit();
        cs.combating();

        // 掃描註解的方式創建bean定義
        ApplicationContext context4 = new AnnotationConfigApplicationContext(TestApplication.class);
        CombatService cs2 = context4.getBean(CombatService.class);
        cs2.combating();
        
        //通用的方式加載xml配置文件或者掃描指定包下的類創建bean定義
        System.out.println("------------------------------------------------------");
        GenericApplicationContext context5 = new GenericApplicationContext();
        new XmlBeanDefinitionReader(context5).loadBeanDefinitions("classpath:application.xml");
        new ClassPathBeanDefinitionScanner(context5).scan("com.study.leesmall.spring.service");
        // 一定要刷新
        context5.refresh();
        cs2 = context5.getBean(CombatService.class);
        cs2.combating();
        Abean ab = context5.getBean(Abean.class);
        ab.doSomething();
    }

    @Bean
    public CombatService getCombatService() {
        return new CombatService(120);
    }
}

接下來,可以打開每個子去了解它們分別加入了什麽、實現了什麽?

1)ConfigurableApplicationContext 加入了什麽:

技術分享圖片

說明:

void addApplicationListener(ApplicationListener<?> listener):這個方法添加監聽 在這裏可以進行發布訂閱監聽的工作
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor):這個方法可以對bean工廠進行獲取前後的AOP增強
void refresh() throws BeansException, IllegalStateException:這個方法是用來刷新IOC容器的,當往IOC容器裏面註冊了新的Bean定義時,調用這個方法去創建bean實例

2)AbstractApplicationContext裏面對前面的接口就開始有具體的實現了,比如addApplicationListener、addBeanFactoryPostProcessor、refresh等等

技術分享圖片

技術分享圖片

3)通用的實現GenericApplicationContext

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {

    private final DefaultListableBeanFactory beanFactory;

    @Nullable
    private ResourceLoader resourceLoader;

    private boolean customClassLoader = false;

    private final AtomicBoolean refreshed = new AtomicBoolean();

它實現了BeanDefinitionRegistry接口,該接口定義了bean定義信息的註冊行為。即我們可以直接往GenericApplicationContext中註冊bean定義。
了解一下BeanDefinitionRegistry中定義的行為:

技術分享圖片

都有誰實現了 BeanDefinitionRegistry 接口:

技術分享圖片

GenericApplicationContext中持有DefaultListableBeanFactory,GenericApplicationContext的bean定義註冊委托給了持有的DefaultListableBeanFactory

技術分享圖片

org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(String, BeanDefinition)對應代碼:

技術分享圖片
    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }

        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        if (existingDefinition != null) {
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            }
            else if (existingDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding user-defined bean definition for bean ‘" + beanName +
                            "‘ with a framework-generated bean definition: replacing [" +
                            existingDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else if (!beanDefinition.equals(existingDefinition)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Overriding bean definition for bean ‘" + beanName +
                            "‘ with a different definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Overriding bean definition for bean ‘" + beanName +
                            "‘ with an equivalent definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);
        }
        else {
            if (hasBeanCreationStarted()) {
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            }
            else {
                // Still in startup registration phase
                this.beanDefinitionMap.put(beanName, beanDefinition);
                this.beanDefinitionNames.add(beanName);
                this.manualSingletonNames.remove(beanName);
            }
            this.frozenBeanDefinitionNames = null;
        }

        if (existingDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }
View Code

接下來看GenericApplicationContext的兩個子AnnotationConfigApplicationContext和GenericXmlApplicationContext:

AnnotationConfigApplicationContext:

了解它的構造方法、register 方法

技術分享圖片

構造方法:

    /**
     * Create a new AnnotationConfigApplicationContext that needs to be populated
     * through {@link #register} calls and then manually {@linkplain #refresh refreshed}.
     */
    public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

    /**
     * Create a new AnnotationConfigApplicationContext with the given DefaultListableBeanFactory.
     * @param beanFactory the DefaultListableBeanFactory instance to use for this context
     */
    public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
        super(beanFactory);
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

    /**
     * Create a new AnnotationConfigApplicationContext, deriving bean definitions
     * from the given annotated classes and automatically refreshing the context.
     * @param annotatedClasses one or more annotated classes,
     * e.g. {@link Configuration @Configuration} classes
     */
    public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
        this();
        register(annotatedClasses);
        refresh();
    }

    /**
     * Create a new AnnotationConfigApplicationContext, scanning for bean definitions
     * in the given packages and automatically refreshing the context.
     * @param basePackages the packages to check for annotated classes
     */
    public AnnotationConfigApplicationContext(String... basePackages) {
        this();
        scan(basePackages);
        refresh();
    }

從構造函數可以看到要完成註解的掃描是通過註解bean定義讀取器AnnotatedBeanDefinitionReader和掃描器ClassPathBeanDefinitionScanner完成的

GenericXmlApplicationContext :

了解它的構造方法、load 方法

技術分享圖片

構造方法:

    /**
     * Create a new GenericXmlApplicationContext that needs to be
     * {@link #load loaded} and then manually {@link #refresh refreshed}.
     */
    public GenericXmlApplicationContext() {
    }

    /**
     * Create a new GenericXmlApplicationContext, loading bean definitions
     * from the given resources and automatically refreshing the context.
     * @param resources the resources to load from
     */
    public GenericXmlApplicationContext(Resource... resources) {
        load(resources);
        refresh();
    }

    /**
     * Create a new GenericXmlApplicationContext, loading bean definitions
     * from the given resource locations and automatically refreshing the context.
     * @param resourceLocations the resources to load from
     */
    public GenericXmlApplicationContext(String... resourceLocations) {
        load(resourceLocations);
        refresh();
    }

    /**
     * Create a new GenericXmlApplicationContext, loading bean definitions
     * from the given resource locations and automatically refreshing the context.
     * @param relativeClass class whose package will be used as a prefix when
     * loading each specified resource name
     * @param resourceNames relatively-qualified names of resources to load
     */
    public GenericXmlApplicationContext(Class<?> relativeClass, String... resourceNames) {
        load(relativeClass, resourceNames);
        refresh();
    }

從構造函數可以看出GenericXmlApplicationContext 能從不同的輸入中加載bean定義

四、細節分析

從前面的分析,已經對Spring IOC的整體由哪些部分組成有一個了解了,下面就深入去分析各個部分是怎麽實現的

1. Bean定義加載和解析

ApplicationContext如何加載、解析Bean定義的。讀源碼,我們不光是了解一下這個過程,更重要的是看它是如何設計接口、類來配合解決這個問題的,以及有哪些擴展點、靈活之處。

1.1 xml文件的bean配置的加載和解析

xml配置方式的bean定義獲取過程:

技術分享圖片

首先,你自己一定要思考清楚這個。然後才去看源碼,不然你都不知道它在幹嘛。
第一次來學習spring的源碼,該怎麽來看源碼呢?
  一行一行看,理解每一行?這肯定行不通。
對於未知的代碼,我們並不清楚它的接口、類結構,方法設計。那怎麽看呢?
  首先找準一個你熟悉的過程點,比如上面的過程中,我們比較熟悉xml解析,那xml解析的代碼你是可以看懂的。
  然後以debug的方式開啟發現之旅,從入口處開始一步一步往裏跟,直到看到找準的點的代碼。這時一定要記住這個點在哪個類中。下次就可以直接在這裏打斷點了。然後要把一路跟過來的調用棧截圖存下來。這個調用棧將是我們重要的分析作者是如何設計接口、類的源泉。
第一次總是困難的,但之後就輕松了

開始吧,從這裏出發,斷點這行,調試運行

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

通過艱辛之旅,我們得到了調用棧:

技術分享圖片

在這裏我們也可以有捷徑獲得這個調用棧,如果你夠細心,會聯想:

我們知道IOC容器的工作過程是加載xml、解析xml得到bean定義、把bean定義註冊到bean工廠裏面、bean工廠根據bean定義創建bean實例,最終目的是註冊bean定義到bean工廠創建bean實例,那麽我們就根據ClassPathXmlApplicationContext的繼承體系先找到哪個類裏面持有bean工廠,找到持有bean工廠的地方以後先看有沒有註冊bean定義相關的方法,根據繼承體系尋找,最終發現在父類AbstractRefreshableApplicationContext裏面持有Bean工廠DefaultListableBeanFactory:

    /** Bean factory for this context. */
    @Nullable
    private DefaultListableBeanFactory beanFactory;

通過在AbstractRefreshableApplicationContext裏面查找,沒有找到註冊bean定義相關的方法,那麽我們就看DefaultListableBeanFactory的裏面有沒有註冊bean定義相關的方法,最終發現DefaultListableBeanFactory裏面果然有註冊bean定義的方法registerBeanDefinition

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

通過查看DefaultListableBeanFactory繼承體系,我們可以看到DefaultListableBeanFactory實現了BeanDefinitionRegistry這個接口來實現bean定義註冊

技術分享圖片

那麽我們就在registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法裏面打個斷點,然後debug運行前面的示例代碼到這裏:

技術分享圖片

這樣我們就能快速的拿到整個調用棧,而不用一步一步的去debug代碼了:

技術分享圖片

那麽怎麽來具體分析調用棧呢?

主要看調用棧(看從開始到註冊bean定義這件事情)用到了哪些類的哪些方法,看傳參。工作是如何分配、分工協作完成的。
看傳參重點是看輸入的參數(如xml配置文件的路徑)在這些類中是怎麽變化的(代碼的本質其實就是對輸入數據的各種處理得到最終想要的結果),從而知道每一個類是幹什麽用的。分析完整個調用棧以後,想要了解哪一部分就點擊對應的調用棧去分析就行了。

從調用棧可以看到要加載xml、解析xml獲取bean定義、註冊bean定義到bean工廠這些事由以下4個部分組成:

技術分享圖片

說明:

方法裏面含有<init>的表示是在構造函數進行初始化,方法裏面帶有(AbstractApplicationContext).refresh()的表示調用的是AbstractApplicationContext父類的refresh()方法,其他的類似

從方法和參數上我們可以看出這4部分分別是做什麽:
第一部分:初始化IOC容器,創建了內部的BeanFactory,然後將加載xml的工作委托給了XmlBeanDefinitionReader。
第二部分:XmlBeanDefinitionReader完成了對輸入數據:字符串——>Resource——>EncodedResource——>InputSource——>Document的轉變。
第三部分:DefaultBeanDefinitionDocumentReader完成對Document中元素的解析,獲得Bean定義等信息。
第四部分:就是簡單的完成bean定義的註冊。

接下來,你就可以針對每一部分去看你感興趣的處理邏輯、數據結構了。
比如:你可能對第三部分Document中元素的解析很感興趣,想搞清楚它是如何解析xml文檔中的各種標簽元素的。

1.1.1 xml元素的解析

從第三部分的調用棧上,我們可以看到如下變化:

技術分享圖片

1——>2 :Document ——>Element

技術分享圖片

看 doRegisterBeanDefinitions 方法:

技術分享圖片

技術分享圖片

技術分享圖片

接下來看在 parseBeanDefinitions 方法中是如何來處理裏面<beans>的元素的:

技術分享圖片

至於它是如何解析bean的不重要,很簡單的。我們重點關心的是它是如何處理其他名稱空間元素的,因為這裏是個變化點:其他模塊所需要的標簽各異,表示的信息也不同,它也不知道其他模塊會有哪些標簽。
它是如何做到以不變應萬變的?看下面的xml配置示例:

技術分享圖片

就來看 parseCustomElement方法:

技術分享圖片

先來看一下 NamespaceHandler 的繼承體系:

技術分享圖片

NamespaceHandler 裏面定義的方法:

技術分享圖片

請詳細看NamespaceHandler的接口註釋,方法註釋說明了方法的用法。

問題:名稱空間對應的處理器在不同的模塊實現,這裏是如何加載到的?如事務處理的根本就不在現在的這裏。那就要去看圖中的這條語句的方法調用了:

技術分享圖片

進入resolve 方法

技術分享圖片

它是一個接口,那這裏用的是它的什麽實現類對象呢?

技術分享圖片

技術分享圖片

我們看到有兩個屬性classLoader,handlerMappingsLocation。從handlerMappingsLocation這個名字能知道這是處理器與名稱空間的映射的配置所在的地址。從前兩個構造方法,我們看到它給入了一個常量的地址值:

技術分享圖片

可大膽推測它是要到類目錄下去找這個文件。看下我們的 spring 的模塊 jar 包下有沒有這個文件

技術分享圖片

技術分享圖片

接下來來看下 resolve 方法:

技術分享圖片

技術分享圖片

請把NamespaceHandler、NamespaceHandlerResolver的類圖畫出來。思考一下這裏有用到什麽設計原則、設計模式?【很重要】

技術分享圖片

擴展:
1)如果你需要加一個自己開發的模塊(含自定義的bean定義標簽)到spring中,你是否可以做到了。

請看我的文章:

dubbo系列三:dubbo源碼分析(dubbo框架是如何跟spring進行整合的,消費者獲取的代理實例是如何創建的呢、生產者怎麽把內容註冊到註冊中心的,然後消費者是如何獲取這個信息的、dubbo負載均衡策略)

中的dubbo框架是如何跟spring進行整合的

2)spring標簽處理這裏的設計:模塊之間可以靈活組合,配置在各自的模塊中,即插即用。你是否可以把它應用到你的系統設計上。

  策略模式跟工廠模式的組合使用

那麽通用的實現 GenericXmlApplicationContext 加載xml、獲取bean定義、註冊bean定義的調用棧是否也是一樣的呢?

ClassPathXmlApplicationContext加載xml、獲取bean定義、註冊bean定義:

        //類路徑下加載xml配置文件創建bean定義
        ApplicationContext context1 = new ClassPathXmlApplicationContext("application.xml");

斷點在DefaultListableBeanFactory中的註冊方法上:

技術分享圖片

GenericXmlApplicationContext 加載xml、獲取bean定義、註冊bean定義:

        //通用的xml方式加載xml配置文件創建bean定義
        ApplicationContext context3 = new GenericXmlApplicationContext("file:e:/study/application.xml");

斷點還是在DefaultListableBeanFactory中的註冊方法上:

技術分享圖片

經過對比:

ClassPathXmlApplicationContext和GenericXmlApplicationContext 加載xml、獲取bean定義、註冊bean定義的過程是一樣的

1.2 bean工廠DefaultListableBeanFactory

經過前面的分析,我們發現bean定義都是註冊到DefaultListableBeanFactory中。接下來就來認識一下它

在ApplicationContext的兩類實現中,通過查看繼承體系我們都可以看到ApplicationContext中持有DefaultListableBeanFactory:

技術分享圖片

xml配置方式的實現的父類:

技術分享圖片

通用實現:

技術分享圖片

下面我們來看一下ApplicationContext和BeaFatory的關系

技術分享圖片

1.3 註解方式的bean配置的加載和解析

入口:

        // 掃描註解的方式創建bean定義
        AnnotationConfigApplicationContext context4 = new AnnotationConfigApplicationContext("com.study.leesmall.spring.service");

斷點還是在DefaultListableBeanFactory中的註冊方法上:

技術分享圖片

debug到上面的斷點以後拿到的調用棧:

技術分享圖片

在這個調用棧中,我們並發沒有看到它做包掃描的相關工作。從下往上看這個執行棧,點第2個執行棧看代碼:

技術分享圖片

它現在是在做一些初始化的準備處理,從這裏我們獲知,它做了registerAnnotationConfigProcessors。從名字上理解就是註冊了一些註解配置的處理器。到底是一些什麽processors,點方法進去看看:

技術分享圖片

技術分享圖片

註冊各種processor

技術分享圖片

技術分享圖片

它註冊了很多的processor,都是些什麽Processor?點第一個的類名進去看看

技術分享圖片

技術分享圖片

BeanDefinitionRegistryPostProcessor擴展了BeanFactoryPostProcessor,增加了BeanDefinitionRegistry位置的處理,即它可以提前對註冊好的BeanDefinitionRegistry進行前置處理。

下面我們來看一下BeanFactoryPostProcessor:

技術分享圖片

來看看BeanFactoryPostProcessor有哪些實現:

技術分享圖片

這是springIOC中給我們提供的又一個【擴展點】,讓我們可以在beanFactory開始創建Bean實例前對beanFactory進行一些處理!!!

使用示例如下:

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations" value="classpath:application.properties"/>
    </bean>

擴展一個自己的BeanFactoryPostProcessor:

package com.study.leesamll.spring.ext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println(this + "  擴展一個自己的BeanFactoryPostProcessor");
    }

}

入口:

        // 掃描註解的方式創建bean定義
        AnnotationConfigApplicationContext context4 = new AnnotationConfigApplicationContext("com.study.mike.spring.service");
        context4.close();

BeanFactoryPostProcessor的類圖

技術分享圖片

1.3.1 掃描包獲取bean定義的過程是怎樣的?

前面我們分析到,註解方式是在scan方法開始進行掃描的

技術分享圖片

那麽我們就在這個方法的這裏再打個斷點,記住之前的bean工廠註冊方法裏面的斷點保留,debug調試看一下調用棧

技術分享圖片

從調用棧上我們可看到有哪些類參與進來,在哪裏發生的什麽。

下面來看一下是怎麽掃描的:

技術分享圖片

技術分享圖片

下面來看一下掃描候選組件的方法:

技術分享圖片

類圖如下:

技術分享圖片

參考文章:

Spring Java-based容器配置

完整代碼獲取地址:https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-source-study

框架源碼系列六:Spring源碼學習之Spring IOC源碼學習