承接前文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,并执行相应的事件方法。响应的监听器有LoggingApplicationListenerLiquibaseServiceLocatorApplicationListener

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;
}

内含代码内容过多,笔者此处针对自己的阅读作下小结

  1. args参数是会被包装为SimpleCommandLinePropertySource属性源,对应key为commandLineArgs。用户可通过系统变量设定spring.config.location/spring.config.name等属性

  2. spring.profiles.active系统属性读取,用于不同条件的配置

  3. ApplicationEnvironmentPreparedEvent事件触发,主要的有ConfigFileApplicationListener监听类(其会默认读取application.properties/application.xml/application.yaml配置文件)

    PS:ConfigFileApplicationListener这个类比较重要,其也会去读取配置文件中的spring.profiles.active属性加载Profile;

    另外也去读取EnvironmentPostProcessor接口来统一调用。有兴趣的读者可好好分析一下

  4. 将Environment含有的属性源绑定至key为configurationPropertiesPropertySources类型中,方便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));
}
  1. AnnotatedBeanDefinitionReader注解解析类,其会注册多个postProcessor接口供后续的上下文刷新操作被调用解析@Configuration/@Autowired/@Required

  2. XmlBeanDefinitionReaderXML解析类,其会解析XML配置

  3. 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服务的,笔者后续再分析

springboot情操陶冶-SpringApplication(二)的更多相关文章

  1. springboot情操陶冶-SpringApplication(一)

    SpringApplication是所有springboot的入口类,分析此类有助于我们了解springboot的工作机制.本文以2.0.3.REALEASE版本作分析 SpringApplicati ...

  2. springboot情操陶冶-@Configuration注解解析

    承接前文springboot情操陶冶-SpringApplication(二),本文将在前文的基础上分析下@Configuration注解是如何一步一步被解析的 @Configuration 如果要了 ...

  3. springboot情操陶冶-web配置(二)

    承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...

  4. springboot情操陶冶-web配置(三)

    承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用 MVC简单例子 直接编写一个Controller层的代码,返回格式为json package com ...

  5. springboot情操陶冶-web配置(一)

    承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...

  6. springboot情操陶冶-@ConfigurationProperties注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@ConfigurationProperties注解的使用 @ConfigurationProper ...

  7. springboot情操陶冶-web配置(九)

    承接前文springboot情操陶冶-web配置(八),本文在前文的基础上深入了解下WebSecurity类的运作逻辑 WebSecurityConfigurerAdapter 在剖析WebSecur ...

  8. springboot情操陶冶-web配置(七)

    参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...

  9. springboot情操陶冶-web配置(四)

    承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...

随机推荐

  1. ios系统的Date的兼容问题

    内容来源网上,自己记录下 问题1: var date =new Date(); 这个是获取当前系统时间的对象,在各端都可以 但是: var date =new Date("2017-01-2 ...

  2. STM32CubeMX+Keil裸机代码风格(2)

    10.找到STM32cubeMx所建的工程目录,在工程目录的同级目录下新建一个文件夹用来存放自己写的代码 11.用notepad++打开keil的工程文件,在这里的<Group>前面加上 ...

  3. Educational Codeforces Round 61 Editorial--C. Painting the Fence

    https://codeforces.com/contest/1132/problem/C 采用逆向思维,要求最大的覆盖,就先求出总的覆盖,然后减去删除两个人贡献最少的人 #include<io ...

  4. input type='file'文件上传自定义样式

    使用场景: 在未使用UI库时免不了会用到各种上传文件,那么默认的上传文件样式无法达到项目的要求,因此重写(修改)上传文件样式是必然的,下面的效果是最近项目中自己写的一个效果,写出来做个记录方便以后使用 ...

  5. Eclipse下用NDK编译生成so文件

      我们在安装环境的时候安装了NDK,可以在eclipse下直接生成so文件.NDK的压缩包里面自带了一些sample工程,NDK的文件直接解压到某个目录下即可. 第一次生成so文件的时候,我们先使用 ...

  6. MFC常见问题总结

    1. c++中的函数前面加个LRESULT是什么意思啊?在微软vc提供的头文件中有定义在winnt.h中typedef long LONG;在windef.h中typedef LONG LRESULT ...

  7. linux(Redhat7)安装Apache

    1.下载apache安装包以及安装依赖的包(apr.apr-util.pcre)wget https://mirrors.cnnic.cn/apache/httpd/httpd-2.4.37.tar. ...

  8. 转发: windows如何管理内存

    (1)有三种方法:虚拟内存,内存映射文件,内存堆栈. 虚拟内存是将页文件加载到内存,适用于比较大的对象或结构: 内存映射文件是将磁盘上文件加载到内存,适用于大文件和单机的进程间内存共享: 堆栈就是动态 ...

  9. [转]Linux操作系统tcpdump抓包分析详解

    PS:tcpdump是一个用于截取网络分组,并输出分组内容的工具,简单说就是数据包抓包工具.tcpdump凭借强大的功能和灵活的截取策略,使其成为Linux系统下用于网络分析和问题排查的首选工具. t ...

  10. 给你的WordPress站点添加下雪特效

    今天看到这个教程,感觉挺应景的,就自己尝试了下,效果还行,没截GIF图 方法: 该js文件已支持https,同时已将其及相关雪花图片进行CDN加速处理,可直接调用. 找到WordPress主题的foo ...