前言
現(xiàn)在Spring Boot使用越來越廣泛躲履,基于注解的配置越來越受歡迎,注解及其相關(guān)的技術(shù)很早就出現(xiàn)了南捂,本文主要是依照源碼跟蹤基于注解的bean的初始化流程夯膀,希望讀者已經(jīng)了解原來流行的xml的配置的bean的初始化流程基礎(chǔ)知識继蜡。本文會概述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)了很多設(shè)計模式思想蝙场,也遵循SOLID設(shè)計原則凌停。
IoC容器在spring中的相關(guān)類按名字分主要是BeanFactory和ApplicationContext。一般ApplicationContext依賴于BeanFactory售滤,更靠近使用者罚拟,所以平常能用到如ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,AnnotationConfigApplicationContext等類來new一個容器對象完箩。
先來看平常開發(fā)很少用的但卻是IoC容器基石的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è)應(yīng)用相關(guān)的功能编整,本文關(guān)注的是資源加載這塊的功能舔稀。
展示了XML相關(guān)的ApplicationContext,最上面繼承了相應(yīng)的BeanFactory接口掌测,沒有繼承下來的實現(xiàn)類或抽象類不是用不到了内贮,而是沒在繼承或?qū)崿F(xiàn)里展現(xiàn)出來,比如在一些ApplicationContext類的方法中會new DefaultListableBeanFactory并使用它汞斧。到ApplicationContext就不僅是接口隔離了昼弟,不同的實現(xiàn)類或者再往下衍生的的子類就會越來越“具體”的進行相應(yīng)實現(xiàn)并且成為一個強大IoC容器铣除。同樣捺宗,圖中只是展現(xiàn)了名為ApplicationContext的類萝快,其他一些實現(xiàn)的接口或父類沒有展現(xiàn)出來。比如重要的接口ResourceLoader仲义,讓ApplicationContext不僅是有IoC容器的特性婶熬,ApplicationContext本身也是相應(yīng)的資源加載器。ApplicationContext中處處體現(xiàn)了模板方法模式的思想埃撵,抽象類調(diào)用抽象方法赵颅,然后由子類來實現(xiàn)具體的方法。不同的描述bean的定義的信息源就會有不同的ApplicationContext子類去實現(xiàn)加載資源的邏輯暂刘。比如ClassPathXmlApplicationContext就會有去classpath下找相應(yīng)的定義bean的xml配置文件并加載到容器中的行為饺谬。Bean從我們手寫的配置文件(不管是xml還是注解等等,對谣拣,注解也可以算做描述“資源”)募寨,到最后可用的對象的大致過程如下:
基于注解的AnnotationConfigApplicationContext
相對于ClassPathXmlApplicationContext等常用容器類,AnnotationConfigApplicationContext是專門加載注解描述信息的容器森缠。這也體現(xiàn)了spring架構(gòu)的易擴展性拔鹰,注解也可以看做是一種描述Bean的資源。
AnnotationConfigApplicationContext主要實現(xiàn)的接口及繼承的類
看一下這幾個相關(guān)的類:
AbstractApplicationContext——ApplicationContext接口的抽象實現(xiàn)贵涵,沒有強制規(guī)定配置的存儲類型列肢,僅僅實現(xiàn)了通用的上下文功能恰画。這個實現(xiàn)用到了模板方法設(shè)計模式,需要具體的子類來實現(xiàn)其抽象方法瓷马。幾乎現(xiàn)在能用到的ApplicationContext都繼承了這個抽象類(可回頭看上面的XML相關(guān)的類也是)拴还。
GenericApplicationContext——會new并持有一個DefaultListableBeanFactory實例(這個類雖然不會被繼承,但是上一節(jié)所描述的體系底層類中會持有這個類的實例來當容器欧聘,剩下的就是再實現(xiàn)其它接口)片林。GenericApplicationContext有了一個真正可以操作的DefaultListableBeanFactory,通過復寫一部分繼承來的方法操作這個內(nèi)含容器怀骤,比如調(diào)用refresh()方法來初始化那些帶有應(yīng)用上下文語義(org.springframework.context.ApplicationContextAware)的bean费封,自動探測org.springframework.beans.factory.config.BeanFactoryPostProcessor等。Generic系列的ApplicationContext的特點就體現(xiàn)在暴露BeanDefinitionRegistry的相應(yīng)實現(xiàn)方法和refresh不重新創(chuàng)建容器晒喷。ClassPathXmlApplicationContext這些容器沒有暴露動態(tài)的注冊BeanDefinition的接口孝偎,只能靠在容器refresh()的時候載入,refresh時會創(chuàng)建容器并走一遍相關(guān)的流程凉敲,比如bean的描述到BeanDefinition的載入,bean的依賴注入寺旺,生命周期的邏輯擴展執(zhí)行(spring提供的可以在bean加載后或整個容器初始化后做的事情這些擴展點)爷抓,GenericApplicationContext可以通過registerBeanDefinition等方法注冊刪除相應(yīng)的BeanDefinition,并且refresh的時候不創(chuàng)建新的容器阻塑,只是為了實例化新注冊的BeanDefinition等蓝撇。
BeanDefinitionRegistry——用于持有像RootBeanDefinition和 ChildBeanDefinition實例的bean definitions的注冊表接口。DefaultListableBeanFactory實現(xiàn)了這個接口陈莽,GenericApplicationContext也實現(xiàn)了這個接口渤昌,GenericApplicationContext只是包了一下相應(yīng)方法。
AnnotationConfigRegistry——注解配置注冊表走搁。用于注解配置應(yīng)用上下文的通用接口独柑,擁有一個注冊配置類和掃描配置類的方法。
GenericApplicationContext平常業(yè)務(wù)用的不多私植,也是因為它的特點忌栅,我們一般不會手動的去添加或刪除BeanDefinition,并希望refresh的時候能夠徹底地刷新容器(重建一個)曲稼。下面對比看下其中的刷新過程邏輯索绪,ClassPathXmlApplicationContext繼承的AbstractRefreshableApplicationContext的刷新容器方法(這個方法是refresh調(diào)用的其中一個,refresh還會做其他事贫悄,比如注冊監(jiān)聽器等等容器初始化會干的事):
可以看到重新創(chuàng)建了一個容器瑞驱,下面再看下GenericApplicationContext的刷新容器方法:
幾乎沒做什么事,只是設(shè)置了一個序列化要用的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,一個用來掃描對應(yīng)路徑下的BeanDefinition读第。
無參的構(gòu)造函數(shù)通過注釋可以看到是讓手動調(diào)用注冊相應(yīng)BeanDefinition并手動refresh來實例化Bean。當然是可以利用兩個成員變量來做這個事的拥刻,兩個成員變量私有怜瞒,用相應(yīng)的方法可以達到目的。
傳入annotatedClasses參數(shù)則是調(diào)用register方法來從類上獲取相應(yīng)的注解般哼,轉(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ù)解析加載相應(yīng)的Bean
if (metadata.isAnnotated(Profile.class.getName())) {
AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
if (!this.environment.acceptsProfiles(profile.getStringArray("value"))) {
return;
}
}
//解析@Scope注解定義的域并設(shè)置到BeanDefinition池摧,如Singleton焦除,Prototype
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
//由beanNameGenerator生成Bean的名稱,這個generator可以通過AnnotationConfigApplicationContext的方法設(shè)置的
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)建相應(yī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設(shè)置路徑值(字符串)册倒,然后在refresh完成了將路徑轉(zhuǎn)化為Resource,并讀取Resource成BeanDefinition磺送,還有后面一系列邏輯驻子。而AnnotationConfigApplicationContext在register方法里就將注解轉(zhuǎn)化為了BeanDefinition并注冊,refresh里做的事要少一些(符合Generic系的ApplicationContext特征)册着。
SpringBoot對AnnotationConfigApplicationContext的利用
一般是在Main函數(shù)中這么啟動SpringBoot的應(yīng)用的:
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上下文進行設(shè)置
this.webEnvironment = deduceWebEnvironment();
//設(shè)置初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//設(shè)置監(jiān)聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//設(shè)置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類,可以做類似任務(wù)執(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);
// 準備應(yīng)用環(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都是相應(yīng)的衍生。
剩下的就是spring boot去初始化和維護這個容器了页徐。