- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Spring Boot启动流程分析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
引言 。
早在15年的时候就开始用spring boot进行开发了,然而一直就只是用用,并没有深入去了解spring boot是以什么原理怎样工作的,说来也惭愧。今天让我们从spring boot启动开始,深入了解一下spring boot的工作原理.
为什么用spring boot 。
在使用一个东西或者一个工具之前,我们总是会问自己,我为什么要用?用他能给我带来什么好处?
* 最大的好处就是spring boot遵从了java**约定大于配置**不用面对一大堆的配置文件,spring boot是根据你用的包来决定提供什么配置.
* 服务器以jar包的形式内嵌于项目中,对于微服务满天飞的情况,spring boot天生适合微服务架构,方便部署.
* 提供devtools从此改代码就需重启成为历史.
有优点就一定有缺点,缺点来源于优点优点来源于缺点(感觉在说哲学问题了哈哈哈) 。
* 正因为配置对开发者不透明,不看源码会不清楚spring boot如何进行诸如JDBC加载、事务管理等,出现错误也很难调错.
* 自动配置之后要自定义配置需编码javaConfig,需要了解这些配置类api.
* 版本迭代太快,新版本对老版本改动太多导致不兼容,比如1.3.5之前的springBootTest和1.4.0之后的springBootTest.
只有合适的架构才是最好的架构如果能接受spring boot这些缺点,spring boot确实是一个可以提高开发效率的不错的选择.
启动流程 。
扯了这么多,该上正题了,让我们来看看spring boot是怎样启动和启动做了哪些事情.
以下代码是spring boot项目标准的启动方式,使用注解@SpringBootApplication并且在main方法中调用SpringApplication的run方法,就可以完成。我们就从这个run方法开始看看spring boot的启动过程.
1
2
3
4
5
6
|
@SpringBootApplication
public
class
Application {
public
static
void
main(String[] args){
SpringApplication.run(Application.
class
,args);
}
}
|
我们进入run方法,可以看到最终是调用了 new SpringApplication(sources).run(args);new SpringApplication(sources).run(args); 这个方法,可以看到,springBoot的启动可以分为两个部分,第一部分:SpringApplication的实例化;第二部分:调用该实例运行run方法。我们先来看看这个SpringApplication的实例化过程.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
void
initialize(Object[] sources) {
if
(sources !=
null
&& sources.length >
0
) {
this
.sources.addAll(Arrays.asList(sources));
}
//判定是否为webEnvironment
this
.webEnvironment = deduceWebEnvironment();
//实例化并加载所有可以加载的ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.
class
));
//实例化并加载所有可以加载的ApplicationListener
setListeners((Collection)
getSpringFactoriesInstances(ApplicationListener.
class
));
this
.mainApplicationClass = deduceMainApplicationClass();
}
|
关键点在两个set方法上** 。
setInitializers((Collection) getSpringFactoriesInstances( 。
ApplicationContextInitializer.class))** 和 **setListeners((Collection) 。
getSpringFactoriesInstances(ApplicationListener.class))** 这两个方法一毛一样,挑实例化ApplicationContextInitializer讲一讲.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
<T> Collection<?
extends
T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//拿到类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//使用loadFactoryNames方法载入所有的ApplicationContextInitializer的类全限定名
Set<String> names =
new
LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//使用反射将所有的ApplicationContextInitializer实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return
instances;
}
|
自动配置的关键就是这个 getSpringFactoriesInstances方法,确切的说是这个方法里的loadFactoryNames方法,浪我们看看这个loadFactoryNames方法干了啥,咋就能实现自动配置.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
static
List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try
{
Enumeration<URL> urls = classLoader !=
null
?classLoader.getResources(
"META-INF/spring.factories"
):ClassLoader.getSystemResources(
"META-INF/spring.factories"
);
ArrayList result =
new
ArrayList();
while
(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
Properties properties = PropertiesLoaderUtils.loadProperties(
new
UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return
result;
}
catch
(IOException var8) {
throw
new
IllegalArgumentException(
"Unable to load ["
+ factoryClass.getName() +
"] factories from location ["
+
"META-INF/spring.factories"
+
"]"
, var8);
}
}
|
可以看到这个方法就做了一件事,就是从META-INF/spring.factories这个路径取出所有”url”来,我们可以去到这个路径下看看到底是些啥?
1
2
3
4
5
6
|
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
|
这下大家都应该明白了,spring是通过将所有你加载的jar包中找到它需要的ApplicationContextInitializer来进行动态的配置的,只要你有用到特定的maven包,初始化的时候会找这个包下的META-INF/spring.factories的需要的类比如ApplicationContextInitializer进行实例化bean,你就可以用了,不需要任何配置.
说到这已经将所有SpringApplication实例化说完了,只是在加载完ApplicationContextInitializer和ApplicationListener这之后还有一步,就是找到启动类所在的位置并且设入属性mainApplicationClass中.
接下来让我们回到new SpringApplication(sources).run(args)方法来看看run方法是怎么run的.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
ConfigurableApplicationContext run(String... args) {
//开启启动计时器,项目启动完会打印执行时间出来
StopWatch stopWatch =
new
StopWatch();
stopWatch.start();
ConfigurableApplicationContext context =
null
;
FailureAnalyzers analyzers =
null
;
configureHeadlessProperty();
//获取SpringApplicationRunListener并启动监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try
{
ApplicationArguments applicationArguments =
new
DefaultApplicationArguments(
args);
//环境变量的加载
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//启动后console的打印出来的一堆配置信息
Banner printedBanner = printBanner(environment);
//终极大boss->ApplicationContext实例化
context = createApplicationContext();
analyzers =
new
FailureAnalyzers(context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context,
null
);
stopWatch.stop();
if
(
this
.logStartupInfo) {
new
StartupInfoLogger(
this
.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
return
context;
}
catch
(Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw
new
IllegalStateException(ex);
}
}
|
从这个方法里面做的最关键的三件事情就是:
获取监听器并启动 。
加载环境变量,该环境变量包括system environment、classpath environment和用户自己加的application.properties 。
创建ApplicationContext 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private
void
prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if
(
this
.logStartupInfo) {
logStartupInfo(context.getParent() ==
null
);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
context.getBeanFactory().registerSingleton(
"springApplicationArguments"
,
applicationArguments);
if
(printedBanner !=
null
) {
context.getBeanFactory().registerSingleton(
"springBootBanner"
, printedBanner);
}
// Load the sources
Set<Object> sources = getSources();
Assert.notEmpty(sources,
"Sources must not be empty"
);
load(context, sources.toArray(
new
Object[sources.size()]));
listeners.contextLoaded(context);
}
|
前两点没什么好说的,重点说说第三个,创建ApplicationContext。创建applicationContext又分为几部:实例化applicationContext、prepareContext、refreshContext。实例化applicationContext会根据在之前我们说的webEnvironment这个属性判断是使用webContext类AnnotationConfigEmbeddedWebApplicationContext还是普通context类AnnotationConfigApplicationContext(在这里我们使用的是webContext为例)然后通过反射进行实例化。applicationContext实例化完了会进入prepareContext流程,这个prepareContext方法会加载之前准备好的environment进入context中,然后如果有beanNameGenerator和resourceLoader那么提前创建bean加载进applicationContext,但是一般这两个都是空的,所以直接进入applyInitializers方法,将之前实例化的所有initializers进行初始化,所有的bean就是在这里进行bean的扫描和加载的因这次讲的是启动过程,所以不再细讲。最后把创建好的applicationContext设置进入listener,prepareContext过程就结束了。最后是refreshContext,这个就和spring的bean加载过程一致了,bean的注入、beanFactory、postProcessBeanFactory等等,详情可以去看看spring bean的生命周期.
总结 。
spring boot 初始化内容还是很多的,但是总结起来就四点:
* 创建SpringApplication实例,判定环境,是web环境还是普通环境。加载所有需要用到的Initializers和Listeners,这里使用约定大于配置的理念揭开了自动配置的面纱.
* 加载环境变量,环境变量包括system environment、classpath environment、application environment(也就是我们自定义的application.properties配置文件) 。
* 创建SpringApplicationRunListeners 。
* 创建ApplicationContext,设置装配context,在这里将所有的bean进行扫描最后在refreshContext的时候进行加载、注入。最终将装配好的context作为属性设置进SpringApplicationRunListeners,这就完成了一个spring boot项目的启动.
以上所述是小编给大家介绍的Spring Boot启动流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我网站的支持! 。
原文链接:http://blog.csdn.net/nethackatschool/article/details/78051220 。
最后此篇关于Spring Boot启动流程分析的文章就讲到这里了,如果你想了解更多关于Spring Boot启动流程分析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
有人可以解释一下 spring-boot-parent 和 spring-boot-starter-parent 之间的区别吗,正如我在下面附加的 GIT HUB 代码链接之一中看到的,他们为 spr
我有与 jersey 框架集成的 Spring Boot 应用程序。 现在,当我尝试运行该应用程序时,它只是停留在 Spring 启动徽标上,之后没有任何 react 。 我也尝试添加 -X ,但徽标
我指的是 Spring Boot 关于 的文档自动配置 和 执行器 模块: 自动配置: Spring Boot AutoConfiguration attempts to automatically
我正在尝试将 apache log4j 集成到我的 Spring boot 应用程序中。这是我的 build.gradle 文件: build.gradle buildscript { rep
使用 Spring Boot Maven 插件的以下命令在生产中启动 Spring Boot 应用程序是否是一个好主意或实践? mvn spring-boot:run 最佳答案 不,这是个坏主意。 您
据我所知,spring boot 和 spring session 为我们提供了一站式自动配置,但是当我的应用程序使用 session redis 和应用程序缓存 redis 时,不是同一个 redi
我希望使用Spring Boot创建一个新的Web应用程序。不幸的是,我的服务器在技术堆栈方面相当有限。它安装了Java 5。 谁能告诉我spring boot是否可以在Java 1.5上运行以及什么
我有3个实体 CarWash(设置Wash) Wash(car_wash_id FK到CarWash) WashComment(wash_id FK到Wash) 有什么办法可以写这个查询 @Qu
我一直在关注this文章。 我正在尝试在Spring-boot应用程序中优雅地处理gRPC错误,的主要目标是能够在gRPC客户端中获取错误状态。 在上面的文章之后,我坚持为异常添加拦截器。如何在Spr
我有一个要使用的自定义log4j布局插件。在IntelliJ中运行或与./gradlew bootRun一起运行时,插件可以正常工作。不使用./gradlew bootJar构建启动jar。 启用-D
我想在给定范围 (5001-5100) 的随机端口上启动 Spring Cloud 应用程序(Spring Boot 1.5.14,Spring Cloud Edgware.SR4)。我知道我们可以使
任何人都可以向我展示或指出不使用 spring boot gradle 插件的 spring boot gradle 项目。 我正在寻找类似不使用 gradle 插件的 spring boot sta
我当时尝试包含上述依赖项之一,但找不到任何区别: spring boot starter web:我可以看到 Flux 和 Mono 类并制作一个响应式(Reactive)休息 Controller
我们一直在为我们的应用程序使用 Springboot 1.X。 现在准备开始一些新的应用程序,想知道我们是应该使用 SpringBoot2.0 还是坚持使用 SpringBoot 1.X? 对一种方式
我希望记录应用程序正在加载 application-profile.propeties 或 application.yml。怎么做。在哪种方法中,我可以听取它并检测它是成功加载还是失败。 最佳答案 您
当我在 pom.xml 中添加简单的 spring-boot-starter-data-jpa 依赖项时,在 pom.xml 文件中出现错误。如果我删除该依赖项,则不会再有错误。我不确定为什么会发生这
我希望记录应用程序正在加载 application-profile.propeties 或 application.yml。怎么做。在哪种方法中,我可以听取它并检测它是成功加载还是失败。 最佳答案 您
我在网上看了很多关于 spring-boot-devtools 的文章和问题,但仍然无法弄清楚为什么它对我不起作用。每次运行我的应用程序时,我都会得到以下信息: 17:54:28.057 [main]
我正在尝试将现有的 Spring 应用程序移植到 Spring Boot。我不使用 spring-boot-starter-data-solr 启动器,但是我的类路径上有 apache solrj (
(这主要是一个历史问题。Pivotal 建议所有论坛讨论都在 StackOverflow 上进行,这就是我在这里问它的原因。) Spring Boot 项目用来证明将应用程序的类和依赖项从可执行 ja
我是一名优秀的程序员,十分优秀!