Springboot源码——应用程序上下文分析
前两篇(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项目上下文
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容器管理。
/** 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项目上下文
//ServletWebServerApplicationContext
protected void prepareWebApplicationContext(ServletContext servletContext) {
...
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
...
}
总结
经过三篇文章的分析,相信大家已经明白了Springmvc项目默认是有两个上下文(Root webapplicationcontext 和 Servlet webapplicationcontext,对应的类型是XmlServletWebServerApplicationContext),而Springboot项目默认是一个上下文,对应的类型是AnnotationConfigServletWebServerApplicationContext。如果有什么疑问,请关注订阅号,进行私聊。
Springboot源码——应用程序上下文分析的更多相关文章
- SpringBoot源码分析之SpringBoot的启动过程
SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30 | 分类于 springboot | 0 Comments | 阅读次数 SpringB ...
- SpringBoot源码篇:深度分析SpringBoot如何省去web.xml
一.前言 从本博文开始,正式开启Spring及SpringBoot源码分析之旅.这可能是一个漫长的过程,因为本人之前阅读源码都是很片面的,对Spring源码没有一个系统的认识.从本文开始我会持续更新, ...
- SpringBoot源码解析系列文章汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的SpringBoot源码解析系列文章的汇总,当你使用SpringBoot不仅仅满足于基本使用时.或者出去面试被面试官虐了时.或者说想要深入了解一下 ...
- SpringBoot是如何实现自动配置的?--SpringBoot源码(四)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 助力SpringBoot自动配置的条件注解ConditionalOnXXX分析--SpringBoot源码(三 ...
- 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...
- SpringBoot的启动流程是怎样的?SpringBoot源码(七)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 温故而知新 本篇接 SpringBoot内置的各种Starter是怎样构建的? SpringBoot源码(六) 温故而知新, ...
- springboot源码解读
springboot源码从main函数开始 public static void main(String[] args) { ApplicationContext app = SpringApplic ...
- Springboot源码分析之项目结构
Springboot源码分析之项目结构 摘要: 无论是从IDEA还是其他的SDS开发工具亦或是https://start.spring.io/ 进行解压,我们都会得到同样的一个pom.xml文件 4. ...
- SpringBoot源码学习系列之异常处理自动配置
SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...
随机推荐
- 使用java源代码生成Kettle 4.4
kettle作为ETL工具.其功能日趋完好,已得到广大数据挖掘爱好者的青睐.又由于他是java开源项目.为适应项目需求.有必要研究其源代码,最好可以集成到Java项目中.作为项目执行流程的一个重要环节 ...
- zjnu(1181)——石子合并
这道题算是最简单的区间dp了..非常久之前写的,搞懂原理了就1A. 传送门:problem_id=1181">http://acm.zjnu.edu.cn/CLanguage/show ...
- nodejs01
一.Node.js简介 1.1 简介 V8引擎本身就是用于Chrome浏览器的JS解释部分,但是Ryan Dahl这哥们,鬼才般的,把这个V8搬到了服务器上,用于做服务器的软件. Node.js是一个 ...
- Python: PS 滤镜--碎片特效
本文用 Python 实现 PS 滤镜中的碎片特效,这个特效简单来说就是将图像在 上,下,左,右 四个方向做平移,然后将四个方向的平移的图像叠加起来做平均.具体的效果图可以参考之前的博客 http:/ ...
- Gcc/MinGW/Cygwin/Msys 分别是什么?
一.GCC的历史 GCC是一个原本用于Unix-like系统下编程的编译器. 不过,现在GCC也有了许多Win32下的移植版本. 所以,也许对于许多Windows开发者来说,GCC还是一个比较陌生的东 ...
- NOIP2017 小凯的疑惑 解题报告(赛瓦维斯特定理)
题目描述 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的.现在小 凯想知道在无法准确支付的物品中,最贵的 ...
- luogu 1280 尼克的任务
题目描述 尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成. 尼克的一个工作日为N分钟,从第一分钟开始 ...
- SharePoint InfoPath 保存无法发布问题
设计完表单以后提示以下错误 错误描述 InfoPath无法保存下列表单:******* 此文档库已被重命名或删除,或者网络问题导致文件无法保存.如果此问题持续存在,请于网络管理员联系. 可参考网站 & ...
- Blink Coordinate Spaces
Blink Coordinate Spaces Blink Coordinate Spaces Types of Zoom There are two types of zoom in Chromiu ...
- Linux防火墙iptables介绍
介绍网络防火墙是通过一个或多个允许或拒绝的规则来过滤网络流量的网络设备或软件.网络防火墙还可以执行更复杂的任务,例如网络地址转换,带宽调整,提供加密隧道以及更多与网络流量相关的任务.而我们的任务就是需 ...