1. Spring Boot 入口——main方法
@SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
從上面代碼可以看出,Annotation定義(@SpringBootApplication)和類定義(SpringApplication.run)最為耀眼,所以分析 Spring Boot 啟動過程,我們就從這兩位開始。
2. 核心注解
2.1 @SpringBootApplication
@SpringBootApplication 是最常用也幾乎是必用的注解,源碼如下:
/**
* Indicates a {@link Configuration configuration} class that declares one or more
* {@link Bean @Bean} methods and also triggers {@link EnableAutoConfiguration
* auto-configuration} and {@link ComponentScan component scanning}. This is a convenience
* annotation that is equivalent to declaring {@code @Configuration},
* {@code @EnableAutoConfiguration} and {@code @ComponentScan}.
* 標示一個聲明有一個或多個的@Bean方法的Configuration類并且觸發自動配置(EnableAutoConfiguration)
* 和組建掃描(ComponentScan)。
* 這是一個相當于@Configuration、@EnableAutoConfiguration、@ComponentScan 三合一的組合注解。
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "exclude")
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class, attribute = "excludeName")
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
從源碼聲明可以看出,@SpringBootApplication相當于 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan ,因此我們直接拆開來分析。
2.2 @SpringBootConfiguration
@SpringBootConfiguration 是繼承自Spring的 @Configuration 注解,@SpringBootConfiguration 作用相當于 @Configuration。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
spring 3.0中增加了@Configuration,@Bean。可基于JavaConfig形式對 Spring 容器中的bean進行更直觀的配置。SpringBoot推薦使用基于JavaConfig的配置形式。
基于xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
<bean id="mockService" class="..MockServiceImpl">
...
</bean>
</beans>
基于JavaConfig配置:
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl();
}
}
總結,@Configuration相當于一個spring的xml文件,配合@Bean注解,可以在里面配置需要Spring容器管理的bean。
2.3 @ComponentScan
@ComponentScan源碼:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* 對應的包掃描路徑 可以是單個路徑,也可以是掃描的路徑數組
* @return
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 和value一樣是對應的包掃描路徑 可以是單個路徑,也可以是掃描的路徑數組
* @return
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 指定具體的掃描類
* @return
*/
Class<?>[] basePackageClasses() default {};
/**
* 對應的bean名稱的生成器 默認的是BeanNameGenerator
* @return
*/
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 處理檢測到的bean的scope范圍
*/
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
/**
* 是否為檢測到的組件生成代理
*/
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* 控制符合組件檢測條件的類文件 默認是包掃描下的
* @return
*/
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* 是否對帶有@Component @Repository @Service @Controller注解的類開啟檢測,默認是開啟的
* @return
*/
boolean useDefaultFilters() default true;
/**
* 指定某些定義Filter滿足條件的組件
* @return
*/
Filter[] includeFilters() default {};
/**
* 排除某些過來器掃描到的類
* @return
*/
Filter[] excludeFilters() default {};
/**
* 掃描到的類是都開啟懶加載 ,默認是不開啟的
* @return
*/
boolean lazyInit() default false;
}
基于xml配置:
<context:component-scan base-package="com.youzan" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
基于JavaConfig配置:
@Configuration
@ComponentScan(value = "com.youzan", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class ScanConfig {
}
總結:@ComponentScan通常與@Configuration一起配合使用,相當于xml里面的<context:component-scan>
,用來告訴Spring需要掃描哪些包或類。如果不設值的話默認掃描@ComponentScan注解所在類的同級類和同級目錄下的所有類,所以對于一個Spring Boot項目,一般會把入口類放在頂層目錄中,這樣就能夠保證源碼目錄下的所有類都能夠被掃描到。
2.4 @EnableAutoConfiguration ??
@EnableAutoConfiguration 源碼如下:
/**
* Enable auto-configuration of the Spring Application Context, attempting to guess and
* configure beans that you are likely to need. Auto-configuration classes are usually
* applied based on your classpath and what beans you have defined. For example, If you
* have {@code tomcat-embedded.jar} on your classpath you are likely to want a
* {@link TomcatEmbeddedServletContainerFactory} (unless you have defined your own
* {@link EmbeddedServletContainerFactory} bean).
*
* 啟用Spring應用程序上下文的自動配置,試圖猜測和配置您可能需要的bean。 自動配置類通常基于您的類路徑和您
* 定義的bean應用。 例如,如果您在類路徑中有tomcat-embedded.jar,那么您可能需要一個
* TomcatEmbeddedServletContainerFactory(除非您已定義了自己的 EmbeddedInvletContainerFactory
* bean)。
* <p>
*
* Auto-configuration classes are regular Spring {@link Configuration} beans. They are
* located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
* Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
* often using {@link ConditionalOnClass @ConditionalOnClass} and
* {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
*
* 自動配置類是 @Configuration 注解的bean。 它們使用 SpringFactoriesLoader 機制(針對這個類鍵入)。
* 通常,自動配置bean是 @Conditional bean(通常使用 @ConditionalOnClass 和
* @ConditionalOnMissingBean 注釋)。
*
* @author Phillip Webb
* @author Stephane Nicoll
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
* @see AutoConfigureAfter
* @see SpringBootApplication
*/
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
這里引出了幾個新的注解,@Import、@Conditional、@ConditionalOnClass、@ConditionalOnMissingBean等,@EnableAutoConfiguration 注解的核心是@Import(EnableAutoConfigurationImportSelector.class)
l里面導入的EnableAutoConfigurationImportSelector.class
。
/**
* 核心方法,加載spring.factories文件中的
* org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置類
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class, getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
spring-boot-autoconfigure.jar 包中的 META-INF/spring.factories 里面默認配置了很多aoto-configuration,如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
......
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
例如 WebMvcAutoConfiguration.class:
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(HttpPutFormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.putfilter", name = "enabled", matchIfMissing = true)
public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
return new OrderedHttpPutFormContentFilter();
}
......etc
}
引入這個類,相當于引入了一份webmvc的基本配置,這個類跟其它很多類一樣,重度依賴于Spring Boot注釋。
總的來說,@EnableAutoConfiguration完成了一下功能:
從classpath中搜尋所有的 META-INF/spring.factories 配置文件,并將其中org.springframework.boot.autoconfigure.EnableutoConfiguration 對應的配置項通過反射實例化為對應的標注了@Configuration的JavaConfig形式的IoC容器配置類,然后匯總為一個并加載到IoC容器。
2.5 @Import
相當于xml里面的<import/>
,允許導入 Configuration注解類 、ImportSelector 和 ImportBeanDefinitionRegistrar的實現類,以及普通的Component類。
2.6 @Conditional
Spring Boot的強大之處在于使用了 Spring 4 框架的新特性:@Conditional注釋,此注解使得只有在特定條件滿足時才啟用一些配置。這也 Spring Boot “智能” 的關鍵注解。Conditional大家族如下:
@ConditionalOnBean
@ConditionalOnClass
@ConditionalOnExpression
@ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnNotWebApplication
@ConditionalOnResource
@ConditionalOnWebApplication
以@ConditionalOnClass注解為例:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
核心實現類為OnClassCondition.class
,這個注解實現類 Condition 接口:
public interface Condition {
/**
* 決定是否滿足條件的方法
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
2.7 總結
上面所有的注解都在做一件事:注冊bean到spring容器。他們通過不同的條件不同的方式來完成:
- @SpringBootConfiguration 通過與 @Bean 結合完成Bean的 JavaConfig配置;
- @ComponentScan 通過范圍掃描的方式,掃描特定注解注釋的類,將其注冊到Spring容器;
- @EnableAutoConfiguration 通過 spring.factories 的配置,并結合 @Condition 條件,完成bean的注冊;
- @Import 通過導入的方式,將指定的class注冊解析到Spring容器;
3. Spring Boot 啟動流程
3.1 SpringApplication的實例化
下面開始分析關鍵方法:
SpringApplication.run(Application.class, args);
相應實現:
// 參數對應的就是Application.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
// 最終運行的這個重載方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
這里最終還是會構造一個 SpringApplication 的實例,然后運行它的run方法。
思考:
1、為什么要在靜態方法里面實例化 ?
2、可不可以不實例化 ?
// 構造實例
public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 推斷是否為web環境
this.webEnvironment = deduceWebEnvironment();
// 設置初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 設置監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推斷應用入口類
this.mainApplicationClass = deduceMainApplicationClass();
}
在構造函數中,主要做了4件事情:
3.1.1 推斷應用類型是否是Web環境
// 相關常量
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private boolean deduceWebEnvironment() {
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return false;
}
}
return true;
}
這里通過判斷是否存在 Servlet 和 ConfigurableWebApplicationContext 類來判斷是否是Web環境,上文提到的 @Conditional 注解也有基于 class 來判斷環境, 所以在 Spring Boot 項目中 jar包 的引用不應該隨意,不需要的依賴最好去掉。
3.1.2 設置初始化器(Initializer)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
這里出現了一個概念 - 初始化器。
先來看看代碼,再來嘗試解釋一下它是干嘛的:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 這里的入參type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 使用Set保存names來去重 避免重復配置導致多次實例化
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根據names來進行實例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 對實例進行排序 可用 Ordered接口 或 @Order注解 配置順序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
該方法會加載所有配置的 ApplicationContextInitializer 并進行實例化,加載 ApplicationContextInitializer 是在SpringFactoriesLoader.loadFactoryNames
方法里面進行的:
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
這個方法會嘗試從類路徑的 META-INF/spring.factories 讀取相應配置文件,然后進行遍歷,讀取配置文件中Key為:org.springframework.context.ApplicationContextInitializer 的 value。以 spring-boot 這個包為例,它的 META-INF/spring.factories 部分定義如下所示:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer
因此這兩個類名會被讀取出來,然后放入到集合中,準備開始下面的實例化操作:
// 關鍵參數:
// type: org.springframework.context.ApplicationContextInitializer.class
// names: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
初始化步驟很直觀,類加載,確認被加載的類確實是 org.springframework.context.ApplicationContextInitializer 的子類,然后就是得到構造器進行初始化,最后放入到實例列表中。
因此,所謂的初始化器就是 org.springframework.context.ApplicationContextInitializer 的實現類,這個接口是這樣定義的:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
ApplicationContextInitializer是一個回調接口,它會在 ConfigurableApplicationContext 容器 refresh() 方法調用之前被調用,做一些容器的初始化工作。
3.1.3. 設置監聽器(Listener)
設置完了初始化器,下面開始設置監聽器:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
實現方式與Initializer一樣:
// 這里的入參type是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
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;
}
同樣,還是以spring-boot這個包中的 spring.factories 為例,看看相應的 Key-Value :
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener
至于 ApplicationListener 接口,它是 Spring 框架中一個相當基礎的接口,代碼如下:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
這個接口基于JDK中的 EventListener 接口,實現了觀察者模式。對于 Spring 框架的觀察者模式實現,它限定感興趣的事件類型需要是 ApplicationEvent 類型的子類,而這個類同樣是繼承自JDK中的 EventObject 類。
3.1.4. 推斷應用入口類(Main)
this.mainApplicationClass = deduceMainApplicationClass();
這個方法的實現有點意思:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
它通過構造一個運行時異常,通過異常棧中方法名為main的棧幀來得到入口類的名字。
思考:
1、獲取堆棧信息的方式?
Thread.currentThread().getStackTrace();
new RuntimeException().getStackTrace();
至此,對于SpringApplication實例的初始化過程就結束了。
3.2 SpringApplication.run方法
完成了實例化,下面開始調用run方法:
// 運行run方法
public ConfigurableApplicationContext run(String... args) {
// 此類通常用于監控開發過程中的性能,而不是生產應用程序的一部分。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設置java.awt.headless系統屬性,默認為true
// Headless模式是系統的一種配置模式。在該模式下,系統缺少了顯示設備、鍵盤或鼠標。
configureHeadlessProperty();
// KEY 1 - 獲取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 通知監聽者,開始啟動
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// KEY 2 - 根據SpringApplicationRunListeners以及參數來準備環境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 準備Banner打印器 - 就是啟動Spring Boot的時候打印在console上的ASCII藝術字體
Banner printedBanner = printBanner(environment);
// KEY 3 - 創建Spring上下文
context = createApplicationContext();
// 注冊異常分析器
analyzers = new FailureAnalyzers(context);
// KEY 4 - Spring上下文前置處理
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// KEY 5 - Spring上下文刷新
refreshContext(context);
// KEY 6 - Spring上下文后置處理
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, exceptionReporters, ex);
throw new IllegalStateException(ex);
}
}
這個run方法包含的內容有點多的,根據上面列舉出的關鍵步驟逐個進行分析:
3.2.1 獲取RunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
這里仍然利用了 getSpringFactoriesInstances 方法來獲取實例:
// 這里的入參:
// type: SpringApplicationRunListener.class
// parameterTypes: new Class<?>[] { SpringApplication.class, String[].class };
// args: SpringApplication實例本身 + main方法傳入的args
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;
}
所以這里還是從 META-INF/spring.factories 中讀取Key為 org.springframework.boot.SpringApplicationRunListener 的Values:
比如在spring-boot包中的定義的spring.factories:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
我們來看看這個EventPublishingRunListener是干嘛的:
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
* <p>
* Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
* before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
// ...
}
從類文檔可以看出,它主要是負責發布SpringApplicationEvent事件的,它會利用一個內部的ApplicationEventMulticaster在上下文實際被刷新之前對事件進行處理。至于具體的應用場景,后面用到的時候再來分析。
3.2.2 準備Environment環境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 創建環境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 發布環境準備好的事件
listeners.environmentPrepared(environment);
// 非Web環境處理
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
配置環境的方法:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
所以這里實際上包含了兩個步驟:
- 配置 Property Sources
- 配置 Profiles,為應用程序環境配置哪些配置文件處于active(活動)狀態。
對于Web應用而言,得到的environment變量是一個StandardServletEnvironment的實例。得到實例后,會調用前面RunListeners中的environmentPrepared方法:
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
在這里,定義的廣播器就派上用場了,它會發布一個 ApplicationEnvironmentPreparedEvent 事件。
那么有發布就有監聽,在構建 SpringApplication 實例的時候不是初始化過一些 ApplicationListeners 嘛,其中的Listener就可能會監聽ApplicationEnvironmentPreparedEvent事件,然后進行相應處理。
所以這里 SpringApplicationRunListeners 的用途和目的也比較明顯了,它實際上是一個事件中轉器,它能夠感知到Spring Boot啟動過程中產生的事件,然后有選擇性的將事件進行中轉。為何是有選擇性的,看看它的實現就知道了:
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
它的 contextPrepared 方法實現為空,沒有利用內部的 initialMulticaster 進行事件的派發。因此即便是外部有 ApplicationListener 對這個事件有興趣,也是沒有辦法監聽到的。
那么既然有事件的轉發,是誰在監聽這些事件呢,在這個類的構造器中交待了:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
前面在構建 SpringApplication 實例過程中設置的監聽器在這里被逐個添加到了 initialMulticaster 對應的 ApplicationListener 列表中。所以當 initialMulticaster 調用 multicastEvent 方法時,這些 Listeners 中定義的相應方法就會被觸發了。
3.2.3 創建Spring Context
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
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);
}
// WEB應用的上下文類型
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
// 非WEB應用的上下文類型
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
AnnotationConfigEmbeddedWebApplicationContext 是個很重要的類,以后再深入分析。
思考:ssm項目中有幾個上下文環境,Spring Boot中有幾個上下文環境,為什么?
3.2.4 Spring Context前置處理
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 將環境和上下文關聯起來
context.setEnvironment(environment);
// 為上下文配置Bean生成器以及資源加載器(如果它們非空)
postProcessApplicationContext(context);
// 調用初始化器
applyInitializers(context);
// 觸發Spring Boot啟動過程的contextPrepared事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 添加兩個Spring Boot中的特殊單例Beans - springApplicationArguments以及springBootBanner
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 加載sources - 對于DemoApplication而言,這里的sources集合只包含了它一個class對象
Set<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 加載動作 - 構造BeanDefinitionLoader并完成Bean定義的加載
load(context, sources.toArray(new Object[sources.size()]));
// 觸發Spring Boot啟動過程的contextLoaded事件
listeners.contextLoaded(context);
}
關鍵步驟:
配置Bean生成器以及資源加載器(如果它們非空):
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());
}
}
}
調用初始化器
protected void applyInitializers(ConfigurableApplicationContext context) {
// Initializers是經過排序的
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
這里終于用到了在創建 SpringApplication實例 時設置的初始化器了,依次對它們進行遍歷,并調用initialize方法。
3.2.5 Spring Context刷新 ??
private void refreshContext(ConfigurableApplicationContext context) {
// 由于這里需要調用父類一系列的refresh操作,涉及到了很多核心操作,因此耗時會比較長,本文不做具體展開
refresh(context);
// 注冊一個關閉容器時的鉤子函數
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
// 調用父類的refresh方法完成容器刷新的基礎操作
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext)applicationContext).refresh();
}
注冊關閉容器時的鉤子函數的默認實現是在 AbstractApplicationContext 類中:
public void registerShutdownHook() {
if(this.shutdownHook == null) {
this.shutdownHook = new Thread() {
public void run() {
synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
AbstractApplicationContext.this.doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
如果沒有提供自定義的shutdownHook,那么會生成一個默認的,并添加到Runtime中。默認行為就是調用它的doClose方法,完成一些容器銷毀時的清理工作。
3.2.6 Spring Context后置處理
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
所謂的后置操作,就是在容器完成刷新后,依次調用注冊的Runners。Runners可以是兩個接口的實現類:
- org.springframework.boot.ApplicationRunner
- org.springframework.boot.CommandLineRunner
CommandLineRunner、ApplicationRunner 接口是在容器啟動成功后的最后一步回調(類似開機自啟動)
這兩個接口有什么區別呢:
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
其實沒有什么不同之處,除了接口中的run方法接受的參數類型是不一樣的以外。一個是封裝好的 ApplicationArguments 類型,另一個是直接的String不定長數組類型。因此根據需要選擇相應的接口實現即可。