承接前文springboot情操陶冶-SpringApplication(一),本文將對run()方法作下詳細的解析
SpringApplication#run()
main函數經常調用的run()方法是我們分析的關鍵,先上源碼
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 讀取java.awt.headless系統變量,默認為true.常用於linux圖片的渲染
configureHeadlessProperty();
// 獲取SpringApplicationRunListener接口集合並實例化調用公共接口starting()
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// environment configuration
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// spring.beaninfo.ignore屬性讀取
configureIgnoreBeanInfo(environment);
// springboot的banner樣圖
Banner printedBanner = printBanner(environment);
// 創建spring應用上下文(尚未刷新)
context = createApplicationContext();
// SpringBootExceptionReporter接口集合讀取
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// applicationContext configuration
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 刷新spring應用上下文
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// SpringApplicationRunListener接口的started()方法調用
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// SpringApplicationRunListener接口的running()方法調用
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
以上的代碼注釋有點多,筆者分塊來進行羅列分析
SpringApplication#getRunListeners()
獲取SpringApplicationRunListener接口集合並實例化,根據前文得知,讀取的是META\spring.factories文件中的對應屬性,
此處筆者以org.springframework.boot.context.event.EventPublishingRunListener為例。
先觀察下其構造函數
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);
}
}
注意application.getListeners()方法,根據前文得知,其會拿到類型為ApplicationListener的集合並存入至SimpleApplicationEventMulticaster廣播類中;
其余方法,比如starting()/started()/environmentPrepared()等等方法均是由其統一調用所有的ApplicationListener接口的對應事件。筆者此處以starting()為例
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
其會找尋支持響應ApplicationStartingEvent事件的Listeners,並執行相應的事件方法。響應的監聽器有LoggingApplicationListener和LiquibaseServiceLocatorApplicationListener等
SpringApplication#prepareEnvironment()
Environment環境准備工作
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 解析args參數和spring.profiles.active配置讀取
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 觸發ApplicationEnvironmentPreparedEvent事件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
內含代碼內容過多,筆者此處針對自己的閱讀作下小結
-
args參數是會被包裝為SimpleCommandLinePropertySource屬性源,對應key為
commandLineArgs
。用戶可通過系統變量設定spring.config.location/spring.config.name等屬性 -
spring.profiles.active
系統屬性讀取,用於不同條件的配置 -
ApplicationEnvironmentPreparedEvent事件觸發,主要的有ConfigFileApplicationListener監聽類(其會默認讀取application.properties/application.xml/application.yaml配置文件)
PS:ConfigFileApplicationListener這個類比較重要,其也會去讀取配置文件中的
spring.profiles.active
屬性加載Profile;
另外也去讀取EnvironmentPostProcessor接口來統一調用。有興趣的讀者可好好分析一下 -
將Environment含有的屬性源綁定至key為
configurationProperties
的PropertySources類型中,方便springboot全局搜索上下文所含有的屬性
SpringApplication#configureIgnoreBeanInfo()
讀取spring.beaninfo.ignore
屬性,默認為true,主要是用於忽略bean的基本信息
SpringApplication#printBanner()
創建banner打印對象,其默認打印的樣圖如下(作用於console)。具體的讀者感興趣可自行閱讀原理
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
SpringApplication#createApplicationContext()
創建應用上下文對象,其會根據判斷出來的應用類型來創建相應的上下文。
應用類型 | 上下文對應class類 |
---|---|
SERVLET | org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext |
REACTIVE | org.springframework.boot.web.servlet.context.AnnotationConfigReactiveWebServerApplicationContext |
NONE | org.springframework.context.annotation.AnnotationConfigApplicationContext |
很明顯,全部會應用注解方式來加載上下文。
SpringApplication#prepareContext()
對已創建的上下文對象作下預備工作
SpringApplication#applyInitializers()
啟動相應的初始化類,這些初始化類均是ApplicationContextInitializer接口的實現類
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
// 檢查相應的ApplicationContextInitializer實體類所接收的泛型實體class
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
// 只對接收的泛型為ConfigurableApplicationContext.class進行相應的初始化
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
針對上面的代碼注釋,初始化類基本有ConfigurationWarningsApplicationContextInitializer/ContextIdApplicationContextInitializer/DelegatingApplicationContextInitializer/ServerPortInfoApplicationContextInitializer等。
有興趣的可自行分析相應的初始化類都執行了哪些操作
SpringApplication#load()
加載beans到相應的上下文對象ApplicationContext中
// 此處的source一般為main函數所在的class,也可通過SpringApplication#setSource()來新增
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// 加載beans
loader.load();
}
筆者此處最關心這個BeanDefinitionLoader會耍什么花樣,繼續往下
先從構造函數開始
BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
Assert.notNull(registry, "Registry must not be null");
Assert.notEmpty(sources, "Sources must not be empty");
this.sources = sources;
// 注解解析類
this.annotatedReader = new AnnotatedBeanDefinitionReader(registry);
// xml解析類
this.xmlReader = new XmlBeanDefinitionReader(registry);
if (isGroovyPresent()) {
this.groovyReader = new GroovyBeanDefinitionReader(registry);
}
// classpath掃描類,並屏蔽指定的sources類
this.scanner = new ClassPathBeanDefinitionScanner(registry);
this.scanner.addExcludeFilter(new ClassExcludeFilter(sources));
}
AnnotatedBeanDefinitionReader注解解析類,其會注冊多個postProcessor接口供后續的上下文刷新操作被調用解析
@Configuration/@Autowired/@Required
。XmlBeanDefinitionReaderXML解析類,其會解析XML配置
ClassPathBeanDefinitionScanner掃描classpath環境下指定的包以及指定class類
再看下處理方法load()
public int load() {
int count = 0;
for (Object source : this.sources) {
count += load(source);
}
return count;
}
private int load(Object source) {
Assert.notNull(source, "Source must not be null");
if (source instanceof Class<?>) {
return load((Class<?>) source);
}
if (source instanceof Resource) {
return load((Resource) source);
}
if (source instanceof Package) {
return load((Package) source);
}
if (source instanceof CharSequence) {
return load((CharSequence) source);
}
throw new IllegalArgumentException("Invalid source type " + source.getClass());
}
// 筆者重點關注此處
private int load(Class<?> source) {
if (isGroovyPresent()
&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
// Any GroovyLoaders added in beans{} DSL can contribute beans here
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
GroovyBeanDefinitionSource.class);
load(loader);
}
// 查看class是否被@Component修飾過。此處多用於加載main類
if (isComponent(source)) {
// 注冊bean
this.annotatedReader.register(source);
return 1;
}
return 0;
}
此處的load()方法主要是對設置的sources類進行判斷是否被@Component
注解,是則注入至bean工廠。即我們常寫的main()函數類一般會加上@SpringApplication
注解,其最終會被AnnotatedBeanDefinitionReader處理。
SpringApplication#refresh()
刷新上下文對象,筆者此處就不展開了,可參考之前的文章Spring源碼情操陶冶-AbstractApplicationContext
異常處理
springboot的異常處理機制是通過讀取SpringBootExceptionReporter接口類來對不同的異常進行不同的輸出,感興趣的可自行閱讀
小結
通過上述的分析,基本對springboot的工作原理有了一定的了解,最主要的其實還是其會將公共的配置放置於META\spring.factories文件中,我們以后只要多關注此文件就會明白的更多。
至於@SpringBootApplication
注解是如何為springboot服務的,筆者后續再分析