前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程,这一篇带大家了解一下springboot项目创建上下文的过程。

SpringApplication引导类

SpringApplication类用于启动或者引导springboot项目,直接应用在java main方法中。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判断当前web应用程序类型
this.webApplicationType = deduceWebApplicationType();
//找到*META-INF/spring.factories*中声明的所有ApplicationContextInitializer的实现类并将其实例化
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//找到*META-INF/spring.factories*中声明的所有ApplicationListener的实现类并将其实例化
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//获得当前执行main方法的类对象
this.mainApplicationClass = deduceMainApplicationClass();
}

springboot项目WebApplicationType分为三种:非web类型,web类型(spring-mvc),响应式web类型(spring-webflux)

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" }; private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler"; private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet"; private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}

下面的run方法是springboot项目启动的核心代码。

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(); //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,
//找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
//之后逐个调用其started()方法,广播SpringBoot要开始执行了。
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args); //创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
//并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//是否搜索BeanInfo类
configureIgnoreBeanInfo(environment);
//Banner打印
Banner printedBanner = printBanner(environment); //根据WebApplicationType的值来决定创建何种类型的ApplicationContext对象
context = createApplicationContext(); //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context); //为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
//并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
//之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
//这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
prepareContext(context, environment, listeners, applicationArguments,
printedBanner); //初始化所有自动配置类,调用ApplicationContext的refresh()方法
refreshContext(context); //空方法
afterRefresh(context, applicationArguments); /关闭任务执行时间监听器
stopWatch.stop(); //如果开启日志,则打印执行是时间
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
} //调用所有的SpringApplicationRunListener的started()方法,广播SpringBoot已经完成了ApplicationContext初始化的全部过程。
listeners.started(context); //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
//我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
//调用所有的SpringApplicationRunListener的running()方法,广播SpringBoot已经可以处理服务请求了。
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

由上文可知,默认WebApplicationType是WebApplicationType.SERVLET,所以默认的上下文是AnnotationConfigServletWebServerApplicationContext。

//应用程序非web环境
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext"; //应用程序web环境
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext"; //应用程序响应式web环境
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"; public enum WebApplicationType { //应用程序不需要任何应用服务器
NONE, //应用程序内嵌web服务器
SERVLET, //应用程序内嵌响应式web服务器
REACTIVE } protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

AnnotationConfigServletWebServerApplicationContext类结构层次如下。

父类ServletWebServerApplicationContext创建内嵌web应用服务器如下。

@Override
protected void onRefresh() {
super.onRefresh();
try {
//创建web应用服务
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
} private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//获取ServletWebServerFactory类型的web服务器工厂类,比如TomcatServletWebServerFactory,JettyServletWebServerFactory,UndertowServletWebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}

Springmvc项目上下文和Springboot项目上下文浅析

Springmvc项目上下文

Springmvc项目的rootcontext的创建时通过 xml中 配置的org.springframework.web.context.ContextLoaderListener,其父类ContextLoader中有一个初始化上下文的方法,如下。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext);

上下文创建好之后调用

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

其中 servletContext的实例是 org.apache.catalina.core.ApplicationContext;

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = org.springframework.web.context.WebApplicationContext.ROOT)

这样rootcontext就创建好了,并且放入了servletContext中保存。rootcontext获取方式如下。

WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());

Springmvc项目中的DispatcherServlet是在xml中按照servlet格式配置的,这种方式创建的servlet实例没有被spring容器管理。

DispatcherServlet实现了ApplicationContextAware接口,有一个成员变量来保存此servlet对应的上下文,如下。
/** WebApplicationContext for this servlet */
private WebApplicationContext webApplicationContext;

这种情况下webApplicationContext变量是无法注入的【DispatcherServlet实例没有被spring容器管理】。看一下DispatcherServlet的父类FrameworkServlet是如何初始化上下文的,如下。

protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//springmvc项目这块的判断为false;springboot项目为ture。
if (this.webApplicationContext != null) { ......

DispatcherServlet所属上下文的存储

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
...
}

DispatcherServlet所属上下文获取org.springframework.web.servlet.support.RequestContextUtils。

Springboot项目上下文

Springboot项目中,调试代码时发现DispatcherServlet的父类FrameworkServlet在初始化上下文的时候rootcontext 和 DispatcherServlet成员变量webApplicationContext保存的是一个实例,即AnnotationConfigServletWebServerApplicationContext实例。
上面也提到了DispatcherServlet【对应的实例被spring容器管理】实现了ApplicationContextAware接口,webApplicationContext保存的上下文是通过自动注入而来。
RootContext(AnnotationConfigServletWebServerApplicationContext)保存到servletcontext中的操作,如下。
//ServletWebServerApplicationContext
protected void prepareWebApplicationContext(ServletContext servletContext) {
...
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
...
}

总结

经过三篇文章的分析,相信大家已经明白了Springmvc项目默认是有两个上下文(Root webapplicationcontext 和 Servlet webapplicationcontext,对应的类型是XmlServletWebServerApplicationContext),而Springboot项目默认是一个上下文,对应的类型是AnnotationConfigServletWebServerApplicationContext。如果有什么疑问,请关注订阅号,进行私聊。

 

Springboot源码——应用程序上下文分析的更多相关文章

  1. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  2. SpringBoot源码篇:深度分析SpringBoot如何省去web.xml

    一.前言 从本博文开始,正式开启Spring及SpringBoot源码分析之旅.这可能是一个漫长的过程,因为本人之前阅读源码都是很片面的,对Spring源码没有一个系统的认识.从本文开始我会持续更新, ...

  3. SpringBoot源码解析系列文章汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的SpringBoot源码解析系列文章的汇总,当你使用SpringBoot不仅仅满足于基本使用时.或者出去面试被面试官虐了时.或者说想要深入了解一下 ...

  4. SpringBoot是如何实现自动配置的?--SpringBoot源码(四)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三 ...

  5. 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...

  6. SpringBoot的启动流程是怎样的?SpringBoot源码(七)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 温故而知新 本篇接 SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六) 温故而知新, ...

  7. springboot源码解读

    springboot源码从main函数开始 public static void main(String[] args) { ApplicationContext app = SpringApplic ...

  8. Springboot源码分析之项目结构

    Springboot源码分析之项目结构 摘要: 无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 4. ...

  9. SpringBoot源码学习系列之异常处理自动配置

    SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...

随机推荐

  1. php使用flock堵塞写入文件和非堵塞写入文件

    php使用flock堵塞写入文件和非堵塞写入文件 堵塞写入代码:(全部程序会等待上次程序运行结束才会运行,30秒会超时) <?php $file = fopen("test.txt&q ...

  2. SparkStreaming基础

    * SparkStreaming基础 打开之前构建好的Maven工程,如何构建?请参看SparkCore基础(二)的最后部分. 在SparkCore中,我们操作的数据都在RDD中,是Spark的一个抽 ...

  3. python 字符串匹配问题

    想匹配html = <div class="back fl"><a href="javascript:void(0);" onclick=&q ...

  4. B-Boxes

    http://agc010.contest.atcoder.jp/tasks/agc010_b Problem Statement There are N boxes arranged in a ci ...

  5. 设置cookie的方法

    设置cookie的方法 1.登录之后后端返回的cookie放在响应的数据里,我们可以取到值, 这样就设置上了一个cookie,然后由于我们需要在三个环境里进行操作,开发环境.测试环境.生产环境.刚刚上 ...

  6. opencv3.4.1和vs2017配置

    官网下载opencv,双击之后会将文件提取出来,提取出来的文件放在一个合适的位置(选个好地方,不要乱改,环境的配置依赖于这个目录),我放在了D:\program下 配置环境变量: 右键此电脑--> ...

  7. Laravel核心解读--HTTP内核

    Http Kernel Http Kernel是Laravel中用来串联框架的各个核心组件来网络请求的,简单的说只要是通过public/index.php来启动框架的都会用到Http Kernel,而 ...

  8. Win10平台下通过VMware虚拟机安装Win7、Ubuntu、Mac

    1.安装VMware14.1.1 下载地址:https://download.csdn.net/download/jasonczy/10611423 产品秘钥: CG54H-D8D0H-H8DHY-C ...

  9. ADL & 实参相依的查找 & 成员与非成员的查找

    也就是会根据实参,所处在的名字空间,来查找对应名字空间里面的函数. 对于<<也是常见的场景,会根据实际要打印出来的下一个操作数,来决定调用哪个命名空间里面的函数. 注意,不同命名空间里面的 ...

  10. Linux异常关机后,Mysql启动出错ERROR 2002 (HY000)

    Linux异常关机后,Mysql启动或訪问时,出错: ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/ ...