spring boot 2.0 源码分析(三)
通过上一章的源码分析,我们知道了spring boot里面的listeners到底是什么(META-INF/spring.factories定义的资源的实例),以及它是创建和启动的,今天我们继续深入分析一下SpringApplication实例变量中的run函数中的其他内容。还是先把run函数的代码贴出来:
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
**在listeners启动了以后,我们来看一下ApplicationArguments applicationArguments **
= new DefaultApplicationArguments(args); 在DefaultApplicationArguments的构造函数里,我们跟踪过去发现其最终调用的SimpleCommandLineArgsParser.parse函数:
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
String[] var3 = args;
int var4 = args.length;
for(int var5 = 0; var5 < var4; ++var5) {
String arg = var3[var5];
if(arg.startsWith("--")) {
String optionText = arg.substring(2, arg.length());
String optionValue = null;
String optionName;
if(optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf(61));
optionValue = optionText.substring(optionText.indexOf(61) + 1,
optionText.length());
} else {
optionName = optionText;
}
if(optionName.isEmpty() || optionValue != null && optionValue.isEmpty()) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
} else {
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
从这段代码中我们看到DefaultApplicationArguments其实是读取了命令行的参数。
小发现:通过分析这个函数的定义,你是不是想起了spring boot启动的时候,用命令行参数自定义端口号的情景?
java -jar MySpringBoot.jar --server.port=8000
接着往下看:ConfigurableEnvironment environment = this.prepareEnvironment(listeners, ex);
通过这行代码我们可以看到spring boot把前面创建出来的listeners和命令行参数,传递到prepareEnvironment函数中来准备运行环境。来看一下prepareEnvironment函数的真面目:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
在这里我们看到了环境是通过getOrCreateEnvironment创建出来的,再深挖一下getOrCreateEnvironment的源码:
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
if (this.webApplicationType == WebApplicationType.SERVLET) {
return new StandardServletEnvironment();
}
return new StandardEnvironment();
}
通过这段代码我们看到了如果environment 已经存在,则直接返回当前的环境。
小思考:在什么情况下会出现environment 已经存在的情况?提示:我们前面讲过,可以自己初始化SpringApplication,然后调用run函数,在初始化SpringApplication和调用run函数之间,是不是可以发生点什么?
下面的代码判断了webApplicationType是不是SERVLET,如果是,则创建Servlet的环境,否则创建基本环境。我们来挖一挖webApplicationType是在哪里初始化的:
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";
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
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;
}
通过这段代码,我们发现了原来spring boot是通过检查当前环境中是否存在
org.springframework.web.servlet.DispatcherServlet类来判断当前是否是web环境的。
接着往下看,获得了ConfigurableEnvironment环境以后,通过后面的代码对环境进行“微调”。
通过this.configureIgnoreBeanInfo(environment);如果System中的spring.beaninfo.ignore属性为空,就把当前环境中的属性覆盖上去:
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if(System.getProperty("spring.beaninfo.ignore") == null) {
Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore",
Boolean.class, Boolean.TRUE);
System.setProperty("spring.beaninfo.ignore", ignore.toString());
}
}
通过Banner printedBanner = this.printBanner(environment);这行代码打印出spring boot的Banner。还记得spring boot启动的时候,在控制台显示的那个图片吗?这里不作深究,继续往下看:
context = this.createApplicationContext();创建了应用上下文:
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
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);
}
通过这里我们看到,spring boot是根据不同的webApplicationType的类型,来创建不同的ApplicationContext的。
总结:通过上面的各种深挖,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文。时间不早了,今天就跟大家分享到这里,下一篇文章会继续跟大家分享spring boot 2.0源码的实现。
spring boot 2.0 源码分析(三)的更多相关文章
- spring boot 2.0 源码分析(一)
在学习spring boot 2.0源码之前,我们先利用spring initializr快速地创建一个基本的简单的示例: 1.先从创建示例中的main函数开始读起: package com.exam ...
- spring boot 2.0 源码分析(四)
在上一章的源码分析里,我们知道了spring boot 2.0中的环境是如何区分普通环境和web环境的,以及如何准备运行时环境和应用上下文的,今天我们继续分析一下run函数接下来又做了那些事情.先把r ...
- spring boot 2.0 源码分析(二)
在上一章学习了spring boot 2.0启动的大概流程以后,今天我们来深挖一下SpringApplication实例变量的run函数. 先把这段run函数的代码贴出来: /** * Run the ...
- spring boot 2.0 源码分析(五)
在上一篇文章中我们详细分析了spring boot是如何准备上下文环境的,今天我们来看一下run函数剩余的内容.还是先把run函数贴出来: /** * Run the Spring applicati ...
- Spring Boot 自动配置 源码分析
Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...
- Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三)
Solr4.8.0源码分析(22)之SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及P ...
- Spring中Bean命名源码分析
Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...
- Spring Cloud 学习 之 Spring Cloud Eureka(源码分析)
Spring Cloud 学习 之 Spring Cloud Eureka(源码分析) Spring Boot版本:2.1.4.RELEASE Spring Cloud版本:Greenwich.SR1 ...
- tomcat源码分析(三)一次http请求的旅行-从Socket说起
p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...
随机推荐
- Python中通过open()操作文件时的文件中文名乱码问题
最近在用Python进行文件操作的时候,遇到创建中文文件名的乱码问题. Python默认是不支持中文的,一般我们在程序的开头加上#-*-coding:utf-8-*-来解决这个问题,但是在我用open ...
- 使用sos查看.NET对象内存布局
前面我们图解了.NET里各种对象的内存布局,我们再来从调试器和clr源码的角度来看一下对象的内存布局.我写了一个测试程序来加深对.net对象内存布局的了解: using System; using S ...
- 3D空间中射线与轴向包围盒AABB的交叉检测算法【转】
引言 在上一节中,我讲述了如何实现射线与三角形的交叉检测算法.但是,我们应该知道,在游戏开发中,一个模型有很多的三角形构成,如果要对所有的物体,所有的三角形进行这种检测,就算现在的计算机运算能力,也是 ...
- C# 委托和事件,简单示例说明问题
先看看示例效果 按照国际惯例,得先说说概念. 委托(C# 编程指南) 事件(C# 编程指南) 以上内容来自MSDN. 委托源码 [委托] 概念和代码都有了.剩下的就是应用了,要是只知道概念不会用,那还 ...
- .NET Core 运行时标识符 (RID) 目录
RID 是什么? RID 是运行时标识符的缩写. RID 用于标识其中将运行应用程序或资产(即程序集)的目标操作系统. 其外观类似如下:“ubuntu.14.04-x64”.“win7-x64”.“o ...
- wpf简单进度条
UserControl x:Class="WpfApplication1.UserControl2" xmlns="http://schemas.microsoft.co ...
- DevExpress WinForms Controls 学习系列1
一个偶然的机会,接触到DevExpress.项目是WinForm的,应用了DevExpress.为了使项目具备维护开发的生命力,我觉得有必要系统的学习一下DevExpress,今天是学习笔记的第一篇. ...
- 微信小程序开发教程(一)—介绍和准备
前言: 因为客户需要,也为了更好的发展我们公司的产品,所以决定扩展移动端.但是由于公司没有原生安卓开发人员,而且开发安卓成本比较高,所以公司一致决定开发微信小程序,也是由于微信小程序最近的火热. 后台 ...
- window10下Docker安装
首先window版本必须是10,如果是win7那么安装方法有所不同,win10是官方支持安装的.笔者安装的是Community社区版,版本信息如下: 1.去docker官网下载win10安装包: ht ...
- java 附件上传、下载前后端代码
前言:业务需要:附件上传,需要同时满足浏览器上传,和APP上传附件,并且浏览器端不可使用form表单提交,因为表单提交无法直接获取返回值,除非刷新页面才可显示上传的附件.所以此处使用ajaxfileu ...