AnnotationConfigApplicationContext的例項化過程
1,類繼承結構
理解AnnotationConfigApplicationContext
的例項化過程之前先看一下它的類繼承結構:
主要涉及到的類或介面有以下幾個:
GenericApplicationContext
——通用應用上下文,內部持有一個DefaultListableBeanFactory
例項,這個類實現了BeanDefinitionRegistry
介面,可以在它身上使用任意的bean definition讀取器。典型的使用案例是:通過BeanFactoryRegistry
介面註冊bean definitions,然後呼叫refresh()
方法來初始化那些帶有應用上下文語義(org.springframework.context.ApplicationContextAware
org.springframework.beans.factory.config.BeanFactoryPostProcessor
等。關於這兩個介面,在介紹bean的生命週期時進行詳細講解。BeanDefinitionRegistry
——用於持有像RootBeanDefinition
和ChildBeanDefinition
例項的bean definitions的登錄檔介面。DefaultListableBeanFactory
實現了這個介面,因此可以通過相應的方法向beanFactory
裡面註冊bean。GenericApplicationContext
內建一個DefaultListableBeanFactory
AbstractApplicationContext
——ApplicationContext
介面的抽象實現,沒有強制規定配置的儲存型別,僅僅實現了通用的上下文功能。這個實現用到了模板方法設計模式,需要具體的子類來實現其抽象方法。自動通過registerBeanPostProcessors()
方法註冊BeanFactoryPostProcessor
,BeanPostProcessor
和ApplicationListener
的例項用來探測bean factory裡的特殊bean——對比1分析AnnotationConfigRegistry
2,構造方法
/**
* 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();
}
第一個構造器是最基本的無引數構造器,需要通過呼叫register()
方法填充註解類,並進行手動重新整理。在這個構造器裡初始化了一個讀取器和掃描器。
第二個構造器能手動指定beanFactory。
第三個構造器能手動指定註解類。
第四個通過指定包名進行自動掃描並重新整理。
由於AnnotationConfigApplicationContext
是GenericApplicationContext
的子類,在呼叫它的構造器之前會先呼叫父類的構造器,父類構造器會例項化一個DefaultListableBeanFactory
例項,這個就是基於註解配置的應用上下文的IoC容器。SpringBoot預設呼叫第一個無引數構造器。
3,SpringApplication類
首先,使用者的Application類是被@SpringBootApplication
註解的。有必要對@SpringBootApplication
進行了解。
@SpringBootApplication
可以認為是@SpringBootConfiguration
,@EnableAutoConfiguration
和@ComponentScan
三個註解的組合。下面進行詳細分析。
3.1,@SpringBootConfiguration
@SpringBootConfiguration
只是對@Configuration
做了一下包裝,說明編寫的類是一個配置類。
3.2,@EnableAutoConfiguration
@EnableAutoConfiguration
註解使能了自動配置功能。這個註解由@Import(EnableAutoConfigurationImportSelector.class)
註解。在目前的版本中EnableAutoConfigurationImportSelector
被廢棄了,反而用了它的基類AutoConfigurationImportSelector
。
AutoConfigurationImportSelector
用於處理@EnableAutoConfiguration
註解—未完待續!!!
3.3,@ComponentScan
開啟元件掃描,和xml配置的<context:component-scan>
效果一樣
3.4,SpringApplication的執行過程
SpringApplication
的靜態run()
方法通過主類構造一個新的SpringApplication
例項,然後執行真正的run()
方法。
SpringApplication類的初始化
在SpringApplication
的構造器中呼叫了一個initialize()
方法,這個方法的程式碼如下:
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//推斷是否是web環境,如果是會專門針對web上下文進行設定
this.webEnvironment = deduceWebEnvironment();
//設定初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//設定監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
初始化方法主要做了三件事情:
判斷上下文環境,web還是非web?後面會根據上下文環境進行鍼對性的設定
設定初始化器,這裡只獲取
ApplicationContextInitializer
(應用上下文初始化器)。這裡主要執行的程式碼是獲取初始化器對應的工廠例項,程式碼如下:private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
這裡用到了
SpringFactoriesLoader
類。這個類主要用於裝載工廠,工廠資源位於META-INF/spring.factories
檔案中,這個檔案可能在類路徑的多個jar檔案中。然後呼叫createSpringFactoriesInstances()
方法對工廠進行例項化並根據註解進行排序。設定監聽器,具體過程同2.
run()方法的執行
run()
的程式碼如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// 獲取啟動監聽器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 構建應用引數
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 準備應用環境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
// 建立ApplicationContext
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 準備context
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 重新整理context-----!!!很重要
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);
}
}
獲取啟動監聽器。具體實現與獲取初始化器的方式一樣。實際上獲取的是一個
EventPublishingRunListener
物件,這個類能通過一個SimpleApplicationEventMulticaster
物件廣播事件,用到了Executor
多執行緒非同步執行框架,複習!!!!構建應用引數。儲存命令列引數。
準備應用環境。
prepareEnvironment()
的程式碼如下:private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置環境 configureEnvironment(environment, applicationArguments.getSourceArgs()); listeners.environmentPrepared(environment); if (!this.webEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertToStandardEnvironmentIfNecessary(environment); } return environment; }
在構造environment的時候,會根據前面推斷的web環境建立相應的
Environment
物件,如果是web環境,則建立StandardServletEnvironment
物件,否則建立StandardEnvironment
物件,StandardEnvironment
的構造器會先對屬性源進行定製(將系統屬性和系統環境新增到一個MutablePropertySources
維護的list中)。這個物件的主要工作包括property分析、profile相關操作、獲取系統屬性和系統環境等。參考相關類結構。接著是配置環境,
configureEnvironment()
的實現如下:protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { // 配置屬性源 configurePropertySources(environment, args); // 配置profile configureProfiles(environment, args); }
SpringApplication#configurePropertySources()
的實現如下:protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); // 新增預設屬性 if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast(new MapPropertySource("defaultProperties",this.defaultProperties)); } // 新增命令列屬性 if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(new SimpleCommandLinePropertySource( name + "-" + args.hashCode(), args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
在現在這個階段的配置主要是新增預設屬性(defaultProperties)和命令列屬性。
SpringApplication#configureProfiles()
的實現如下:protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { // 獲取啟用的profiles environment.getActiveProfiles(); // ensure they are initialized // But these ones should go first (last wins in a property key clash) Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(profiles.toArray(new String[profiles.size()])); }
AbstractEnvironment#getActiveProfiles()
解析spring.profile.active
屬性,新增到activeProfiles這個集合中。後面的程式碼是為了實現“last win”策略。此時應用環境也準備完畢,它包括系統屬性、系統環境和啟用的profile。之後,通過釋出環境準備完畢事件,這裡,啟動監聽器會繼續對environment進行配置,參考啟動監聽器!!。
建立應用上下文。
SpringApplication#createApplicationContext()
方法通過反射構造應用上下文。AnnotationConfigApplicationContext
的預設構造器會構造一個AnnotationBeanDefinitionReader
物件和ClassPathBeanDefinitionScanner
物件,在構造AnnotationBeanDefinitionReader
物件的過程中會向bean factory添加註解處理器和事件監聽處理器BeanDefinition
,為後續的配置解析作準備。這樣,就能通過JavaConfig構建BeanDefinition
並實現自動掃描。AnnotationConfigApplicationContext
的父類GenericApplicationContext
的預設構造器會構造一個DefaultListableBeanFactory
物件,這樣應用上下文持有一個bean factory的引用,大部分應用只需與應用上下文提供的介面打交道就是因為它對bean factory進行了一層封裝。至此,一個Spring容器已經構造出來了,但是目前這個容器還什麼都沒有,需要根據使用者的配置檔案進行配置才能按照使用者邏輯進行工作。準備應用上下文。
SpringApplication#prepareContext()
的實現如下:private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 使context持有應用環境的引用,同時將應用環境的引用賦給reader和scanner context.setEnvironment(environment); // 實現應用上下文的後置處理 postProcessApplicationContext(context); // 應用初始化器--新增監聽器、logger、warnning等元件 applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // 新增啟動相關的bean context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } // Load the sources Set<Object> sources = getSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 將source bean裝載到應用上下文 load(context, sources.toArray(new Object[sources.size()])); // 日誌配置---參考具體實現 listeners.contextLoaded(context); }
上下文後置處理
SpringApplication#postProcessApplicationContext()
程式碼如下:protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) { context.getBeanFactory().registerSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { if (context instanceof GenericApplicationContext) { ((GenericApplicationContext) context) .setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { ((DefaultResourceLoader) context) .setClassLoader(this.resourceLoader.getClassLoader()); } } }
這段後置處理程式碼主要是註冊
BeanNameGenerator
型別的bean並設定應用上下文的資源載入器和類載入器。SpringApplication#load()
方法會例項化一個BeanDefinitionLoader
物件loader, 然後呼叫它的load()
方法裝載bean,這裡完成的工作就是將使用者編寫的主類作為bean進行註冊。重新整理應用上下文。重新整理上下文實際上是呼叫
AbstractApplicationContext#refresh()
方法,這個方法很複雜,執行完畢後整個應用上下文也就完成了配置。下一節對這個方法進行詳細講解。後置工作。後置工作等分析完了步驟6之後再寫。