Spring IoC 基于注解的Bean加載過程

前言

現(xiàn)在Spring Boot使用越來越廣泛,基于注解的配置越來越受歡迎,注解及其相關(guān)的技術(shù)很早就出現(xiàn)了,本文主要是依照源碼跟蹤基于注解的bean的初始化流程,希望讀者已經(jīng)了解原來流行的xml的配置的bean的初始化流程基礎知識。本文會概述xml相關(guān)的初始化基本原理,這一塊若在閱讀過程中有疑問讀者可以自己搜索相關(guān)詳細內(nèi)容,而關(guān)于注解到Bean Definition(這個不知道可以先往下看,看不懂可以去詳細了解基于XML的IoC容器加載過程)的流程會盡量詳細。

概述:基于XML的IoC容器初始化流程

IoC容器本身也是一個類,這個類通過定義相關(guān)的屬性來記錄容器的狀態(tài),存放容器的組件等等,定義相關(guān)的方法來進行對容器的各種操作和容器本身運轉(zhuǎn)自己需要的操作。當然現(xiàn)在spring ioc的容器功能很強大,如果要定義成一個非常大的類那就很不好擴展和維護,所以IoC容器類的定義是分了很多層級的,體現(xiàn)了很多設計模式思想,也遵循SOLID設計原則。
IoC容器在spring中的相關(guān)類按名字分主要是BeanFactory和ApplicationContext。一般ApplicationContext依賴于BeanFactory,更靠近使用者,所以平常能用到如ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,AnnotationConfigApplicationContext等類來new一個容器對象。
先來看平常開發(fā)很少用的但卻是IoC容器基石的BeanFactory家族:

BeanFactory的類圖

其中有<<interface>>標識的是接口,沒有的是類,名字前綴為Abstract的為抽象類,spring的命名很規(guī)范。

BeanFactory就分了很多層,接口按功能分開遵從接口隔離原則,約束了spring內(nèi)部對象處理時的行為。這種分層看起來復雜,但是對遵循開閉原則進行擴展是非常有利的,而且抽象類或接口相互關(guān)聯(lián)是靠抽象和抽象之間關(guān)聯(lián)(面向接口編程,依賴倒轉(zhuǎn)),具體的實現(xiàn)就比較容易加在層次之間,有相當?shù)撵`活性。比如從頂端分出來的ListableBeanFactory、HierarchicalBeanFactory、AutowireCapableBeanFactory就分別規(guī)定了容器的列出組件的行為、容器之間的父子關(guān)系(比如Spring MVC的servlet context容器的父容器是root context)、容器的裝配行為(依賴注入)。因為java是單繼承,但是可以實現(xiàn)多個接口,所以這些接口的行為最后又可以在一個匯總的類上體現(xiàn)。上圖并沒有將這些類相關(guān)的所有的接口或父類給列出來,范圍限制在了名為BeanFactory的類。

再來看ApplicationContext,ApplicationContext與BeanFactory多了很多與企業(yè)應用相關(guān)的功能,本文關(guān)注的是資源加載這塊的功能。

ApplicationContext的類圖

展示了XML相關(guān)的ApplicationContext,最上面繼承了相應的BeanFactory接口,沒有繼承下來的實現(xiàn)類或抽象類不是用不到了,而是沒在繼承或?qū)崿F(xiàn)里展現(xiàn)出來,比如在一些ApplicationContext類的方法中會new DefaultListableBeanFactory并使用它。到ApplicationContext就不僅是接口隔離了,不同的實現(xiàn)類或者再往下衍生的的子類就會越來越“具體”的進行相應實現(xiàn)并且成為一個強大IoC容器。同樣,圖中只是展現(xiàn)了名為ApplicationContext的類,其他一些實現(xiàn)的接口或父類沒有展現(xiàn)出來。比如重要的接口ResourceLoader,讓ApplicationContext不僅是有IoC容器的特性,ApplicationContext本身也是相應的資源加載器。ApplicationContext中處處體現(xiàn)了模板方法模式的思想,抽象類調(diào)用抽象方法,然后由子類來實現(xiàn)具體的方法。不同的描述bean的定義的信息源就會有不同的ApplicationContext子類去實現(xiàn)加載資源的邏輯。比如ClassPathXmlApplicationContext就會有去classpath下找相應的定義bean的xml配置文件并加載到容器中的行為。Bean從我們手寫的配置文件(不管是xml還是注解等等,對,注解也可以算做描述“資源”),到最后可用的對象的大致過程如下:

Bean在加載過程中的狀態(tài)演變

基于注解的AnnotationConfigApplicationContext

相對于ClassPathXmlApplicationContext等常用容器類,AnnotationConfigApplicationContext是專門加載注解描述信息的容器。這也體現(xiàn)了spring架構(gòu)的易擴展性,注解也可以看做是一種描述Bean的資源。

AnnotationConfigApplicationContext主要實現(xiàn)的接口及繼承的類

注解相關(guān)的類

看一下這幾個相關(guān)的類:

  • AbstractApplicationContext——ApplicationContext接口的抽象實現(xiàn),沒有強制規(guī)定配置的存儲類型,僅僅實現(xiàn)了通用的上下文功能。這個實現(xiàn)用到了模板方法設計模式,需要具體的子類來實現(xiàn)其抽象方法。幾乎現(xiàn)在能用到的ApplicationContext都繼承了這個抽象類(可回頭看上面的XML相關(guān)的類也是)。

  • GenericApplicationContext——會new并持有一個DefaultListableBeanFactory實例(這個類雖然不會被繼承,但是上一節(jié)所描述的體系底層類中會持有這個類的實例來當容器,剩下的就是再實現(xiàn)其它接口)。GenericApplicationContext有了一個真正可以操作的DefaultListableBeanFactory,通過復寫一部分繼承來的方法操作這個內(nèi)含容器,比如調(diào)用refresh()方法來初始化那些帶有應用上下文語義(org.springframework.context.ApplicationContextAware)的bean,自動探測org.springframework.beans.factory.config.BeanFactoryPostProcessor等。Generic系列的ApplicationContext的特點就體現(xiàn)在暴露BeanDefinitionRegistry的相應實現(xiàn)方法和refresh不重新創(chuàng)建容器。ClassPathXmlApplicationContext這些容器沒有暴露動態(tài)的注冊BeanDefinition的接口,只能靠在容器refresh()的時候載入,refresh時會創(chuàng)建容器并走一遍相關(guān)的流程,比如bean的描述到BeanDefinition的載入,bean的依賴注入,生命周期的邏輯擴展執(zhí)行(spring提供的可以在bean加載后或整個容器初始化后做的事情這些擴展點),GenericApplicationContext可以通過registerBeanDefinition等方法注冊刪除相應的BeanDefinition,并且refresh的時候不創(chuàng)建新的容器,只是為了實例化新注冊的BeanDefinition等。

  • BeanDefinitionRegistry——用于持有像RootBeanDefinition和 ChildBeanDefinition實例的bean definitions的注冊表接口。DefaultListableBeanFactory實現(xiàn)了這個接口,GenericApplicationContext也實現(xiàn)了這個接口,GenericApplicationContext只是包了一下相應方法。

  • AnnotationConfigRegistry——注解配置注冊表。用于注解配置應用上下文的通用接口,擁有一個注冊配置類和掃描配置類的方法。

GenericApplicationContext平常業(yè)務用的不多,也是因為它的特點,我們一般不會手動的去添加或刪除BeanDefinition,并希望refresh的時候能夠徹底地刷新容器(重建一個)。下面對比看下其中的刷新過程邏輯,ClassPathXmlApplicationContext繼承的AbstractRefreshableApplicationContext的刷新容器方法(這個方法是refresh調(diào)用的其中一個,refresh還會做其他事,比如注冊監(jiān)聽器等等容器初始化會干的事):

AbstractRefreshableApplicationContext

可以看到重新創(chuàng)建了一個容器,下面再看下GenericApplicationContext的刷新容器方法:


GenericApplicationContext

幾乎沒做什么事,只是設置了一個序列化要用的id。

講了這么多GenericApplicationContext是因為AnnotationConfigApplicationContext繼承自它,也有它的特性,下面我們來剖析AnnotationConfigApplicationContext。

AnnotationConfigApplicationContext的構(gòu)造方法

成員變量及構(gòu)造方法:

private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;

/**
 * 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, 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();
}

AnnotationConfigApplicationContext有兩個成員變量,從名字來看一個用來讀取注解定義的BeanDefinition,一個用來掃描對應路徑下的BeanDefinition。
無參的構(gòu)造函數(shù)通過注釋可以看到是讓手動調(diào)用注冊相應BeanDefinition并手動refresh來實例化Bean。當然是可以利用兩個成員變量來做這個事的,兩個成員變量私有,用相應的方法可以達到目的。


AnnotationConfigApplicationContext的結(jié)構(gòu)

傳入annotatedClasses參數(shù)則是調(diào)用register方法來從類上獲取相應的注解,轉(zhuǎn)化成BeanDefinition到容器中。然后調(diào)用refresh方法(這個就是AbstractApplicationContext的refresh方法,模板方法模式再調(diào)用子類的一些實現(xiàn)方法),前面講了主要是把新注冊的BeanDefinition實例化(lazy-init的不會實例化,但是會有綁一些事件這些refresh要進行的操作),refresh涉及的整個流程其實是非常繁瑣的,會調(diào)很多層數(shù)深的函數(shù)。

傳入basePackages參數(shù)調(diào)用scan方法來掃描某一包下的所有類(包括接口)的注解。

AnnotationConfigApplicationContext的register和scan方法

AnnotationConfigApplicationContext的register方法:

public void register(Class<?>... annotatedClasses) {
    Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
    this.reader.register(annotatedClasses);
}

其中reader是構(gòu)造函數(shù)初始化的AnnotatedBeanDefinitionReader,它的register及相關(guān)方法:

public void register(Class<?>... annotatedClasses) {
    for (Class<?> annotatedClass : annotatedClasses) {
        registerBean(annotatedClass);
    }
}

public void registerBean(Class<?> annotatedClass) {
    registerBean(annotatedClass, null, (Class<? extends Annotation>[]) null);
}

public void registerBean(Class<?> annotatedClass, Class<? extends Annotation>... qualifiers) {
    registerBean(annotatedClass, null, qualifiers);
}

public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
    //解析能轉(zhuǎn)化成BeanDefinition的注解的所在類,創(chuàng)建Spring容器中對BeanDefinition的封裝的數(shù)據(jù)結(jié)構(gòu)
    AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
    AnnotationMetadata metadata = abd.getMetadata();
    //Spring的Profile機制,和maven的profile機制差不多,只有當Profile被激活時才繼續(xù)解析加載相應的Bean
    if (metadata.isAnnotated(Profile.class.getName())) {
        AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
        if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) {
            return;
        }
    }
    //解析@Scope注解定義的域并設置到BeanDefinition,如Singleton,Prototype
    ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
    abd.setScope(scopeMetadata.getScopeName());
    //由beanNameGenerator生成Bean的名稱,這個generator可以通過AnnotationConfigApplicationContext的方法設置的
    String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
    //處理BeanDefinition的通用注解:@Primary,@DependsOn,@Lazy,@Role
    AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
    //處理限定符注解@Qualifier,這個注解為了區(qū)分有歧義的Bean,比如在Autowire裝配的時候可以注入的Bean有兩個,就可以通過加這個注解區(qū)分
    if (qualifiers != null) {
        for (Class<? extends Annotation> qualifier : qualifiers) {
            if (Primary.class.equals(qualifier)) {
                abd.setPrimary(true);
            }
            else if (Lazy.class.equals(qualifier)) {
                abd.setLazyInit(true);
            }
            else {
                abd.addQualifier(new AutowireCandidateQualifier(qualifier));
            }
        }
    }
    //封裝Bean名字和BeanDefinition
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
    //根據(jù)注解Bean定義類中配置的作用域,創(chuàng)建相應的代理對象
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    //向容器中注冊BeanDefinition
    BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}

AnnotationConfigApplicationContext的scan方法:

public void scan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    this.scanner.scan(basePackages);
}

其中reader是構(gòu)造函數(shù)初始化的ClassPathBeanDefinitionScanner,它在構(gòu)造的時候指定(通過boolean參數(shù))是否使用默認的掃描過濾規(guī)則。即Spring默認掃描配置:@Component、@Repository、@Service、@Controller注解的Bean,同時也支持JavaEE6的@ManagedBean和JSR-330的@Named注解。ClassPathBeanDefinitionScanner的scan及相關(guān)方法:

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    doScan(basePackages);

    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    //創(chuàng)建一個集合,存放掃描到Bean定義的封裝類
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
    //遍歷掃描所有給定的包
    for (String basePackage : basePackages) {
        //調(diào)用父類ClassPathScanningCandidateComponentProvider的方法  
        //掃描給定類路徑,按照過濾規(guī)則(默認規(guī)則是掃描@Component等)獲取符合條件的Bean定義  
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        //遍歷掃描到的Bean
        for (BeanDefinition candidate : candidates) {
            //接下來同樣是處理Scope,Bean名字,通用注解
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    //返回掃到的BeanDefinition
    return beanDefinitions;
}

經(jīng)常用到的注解@ComponentScan,這個注解定義在@Configuration類上,但是register方法注冊的時候只是將其載進了BeanDefinition,并沒有進行處理,其實對@ComponentScan是進行了調(diào)用類似scan方法的過程,但什么時候調(diào)的呢?不在register流程中,這個注解到BeanDefinition里并注冊到容器后,在refresh的過程中會調(diào)用到BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,在這個方法執(zhí)行過程中會處理@ComponentScan注解并載入BeanDefinition進行處理。

還有一個要注意的點,ClassPathXmlApplicationContext在構(gòu)造時先是setLocations設置路徑值(字符串),然后在refresh完成了將路徑轉(zhuǎn)化為Resource,并讀取Resource成BeanDefinition,還有后面一系列邏輯。而AnnotationConfigApplicationContext在register方法里就將注解轉(zhuǎn)化為了BeanDefinition并注冊,refresh里做的事要少一些(符合Generic系的ApplicationContext特征)。

SpringBoot對AnnotationConfigApplicationContext的利用

一般是在Main函數(shù)中這么啟動SpringBoot的應用的:

SpringApplication.run(Application.class, args);

參數(shù)args就是把main函數(shù)的參數(shù)傳遞一下,第一個參數(shù)是一個類,這個類就是AnnotationConfigApplicationContext需要的annotatedClasses,run函數(shù)有重載的,可以傳一個annotatedClass數(shù)組也可以只傳一個:

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

這時候還沒用到AnnotationConfigApplicationContext呢,所以參數(shù)名先叫sources,后面會傳給它的??梢钥吹秸{(diào)用的SpringApplication的構(gòu)造函數(shù)(new了一個SpringApplication),構(gòu)造函數(shù)主要調(diào)用了initialize方法:

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    //推斷是否是web環(huán)境,如果是會專門針對web上下文進行設置
    this.webEnvironment = deduceWebEnvironment();
    //設置初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    //設置監(jiān)聽器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //設置Main函數(shù),這個就是我們寫的程序的Main函數(shù)
    this.mainApplicationClass = deduceMainApplicationClass();
}

新建完SpringApplication馬上又調(diào)用了它的run方法,這個run方法就不再是一開始從我們main函數(shù)調(diào)的那些靜態(tài)方法了,這個是個普通方法,參數(shù)只有一個args。

public ConfigurableApplicationContext run(String... args) {
    //spring的StopWatch類,可以做類似任務執(zhí)行時間控制
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    //這個引用真正會用到的ApplicationContext,在后面的邏輯中決定用什么
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    // spring boot啟動監(jiān)聽器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        //將參數(shù)包裝一下
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 準備應用環(huán)境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        // 創(chuàng)建ApplicationContext
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        // 準備context
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // refresh!
        refreshContext(context);
        // 后期工作
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

終于用到AnnotationConfigApplicationContext了,就在createApplicationContext()方法里:

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            //若非Web環(huán)境,那么就用AnnotationConfigApplicationContext
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

沒錯,DEFAULT_CONTEXT_CLASS的值就是"org.springframework.context.annotation.AnnotationConfigApplicationContext"。
至于Web環(huán)境在spring boot里就是用的AnnotationConfigEmbeddedWebApplicationContext,這個類還有AnnotationConfigWebApplicationContext都是相應的衍生。

剩下的就是spring boot去初始化和維護這個容器了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容