- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章spring boot jar的启动原理解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
。
1.前言 。
近来有空对公司的open api平台进行了些优化,然后在打出jar包的时候,突然想到以前都是对spring boot使用很熟练,但是从来都不知道spring boot打出的jar的启动原理,然后这回将jar解开了看了下,与想象中确实大不一样,以下就是对解压出来的jar的完整分析.
。
2.jar的结构 。
spring boot的应用程序就不贴出来了,一个较简单的demo打出的结构都是类似,另外我采用的spring boot的版本为1.4.1.RELEASE网上有另外一篇文章对spring boot jar启动的分析,那个应该是1.4以下的,启动方式与当前版本也有着许多的不同.
在mvn clean install后,我们在查看target目录中时,会发现两个jar包,如下:
1
2
|
xxxx.jar
xxx.jar.original
|
这个则是归功于spring boot插件的机制,将一个普通的jar打成了一个可以执行的jar包,而xxx.jar.original则是maven打出的jar包,这些可以参考spring官网的文章来了解,如下:
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#executable-jar 。
以下是spring boot应用打出的jar的部分目录结构,大部分省略了,仅仅展示出其中重要的部分.
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
39
40
41
42
43
44
45
46
47
48
49
50
51
|
.
├── BOOT-INF
│ ├── classes
│ │ ├── application-dev.properties
│ │ ├── application-prod.properties
│ │ ├── application.properties
│ │ ├── com
│ │ │ └── weibangong
│ │ │ └── open
│ │ │ └── openapi
│ │ │ ├── SpringBootWebApplication.
class
│ │ │ ├── config
│ │ │ │ ├── ProxyServletConfiguration.
class
│ │ │ │ └── SwaggerConfig.
class
│ │ │ ├── oauth2
│ │ │ │ ├── controller
│ │ │ │ │ ├── AccessTokenController.
class
│ │ ├── logback-spring.xml
│ │ └──
static
│ │ ├── css
│ │ │ └── guru.css
│ │ ├── images
│ │ │ ├── FBcover1200x628.png
│ │ │ └── NewBannerBOOTS_2.png
│ └── lib
│ ├── accessors-smart-
1.1
.jar
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── com.weibangong.open
│ └── open-server-openapi
│ ├── pom.properties
│ └── pom.xml
└── org
└── springframework
└── boot
└── loader
├── ExecutableArchiveLauncher$
1
.
class
├── ExecutableArchiveLauncher.
class
├── JarLauncher.
class
├── LaunchedURLClassLoader$
1
.
class
├── LaunchedURLClassLoader.
class
├── Launcher.
class
├── archive
│ ├── Archive$Entry.
class
│ ├── Archive$EntryFilter.
class
│ ├── Archive.
class
│ ├── ExplodedArchive$
1
.
class
│ ├── ExplodedArchive$FileEntry.
class
│ ├── ExplodedArchive$FileEntryIterator$EntryComparator.
class
├── ExplodedArchive$FileEntryIterator.
class
|
这个jar除了我们写的应用程序打出的class以外还有一个单独的org包,应该是spring boot应用在打包的使用spring boot插件将这个package打进来,也就是增强了mvn生命周期中的package阶段,而正是这个包在启动过程中起到了关键的作用,另外中jar中将应用所需的各种依赖都打进来,并且打入了spring boot额外的package,这种可以all-in-one的jar也被称之为fat.jar,下文我们将一直以fat.jar来代替打出的jar的名字.
。
3.MANIFEST.MF文件 。
这个时候我们再继续看META-INF中的MANIFEST.MF文件,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Manifest-Version:
1.0
Implementation-Title: open :: server :: openapi
Implementation-Version:
1.0
-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: xiaxuan
Implementation-Vendor-Id: com.weibangong.open
Spring-Boot-Version:
1.4
.
1
.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.PropertiesLauncher
Start-Class: com.weibangong.open.openapi.SpringBootWebApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven
3.3
.
9
Build-Jdk:
1.8
.0_20
Implementation-URL: http:
//maven.apache.org/open-server-openapi
|
这里指定的main-class是单独打入的包中的一个类文件而不是我们的启动程序,然后MANIFEST.MF文件有一个单独的start-class指定的是我们的应用的启动程序.
。
4.启动分析 。
首先我们找到类org.springframework.boot.loader.PropertiesLauncher,其中main方法为:
1
2
3
4
5
|
public
static
void
main(String[] args)
throws
Exception {
PropertiesLauncher launcher =
new
PropertiesLauncher();
args = launcher.getArgs(args);
launcher.launch(args);
}
|
查看launch方法,这个方法在父类Launcher中,找到父类方法launch方法,如下:
1
2
3
4
5
6
7
|
protected
void
launch(String[] args, String mainClass, ClassLoader classLoader)
throws
Exception {
Thread.currentThread().setContextClassLoader(classLoader);
this
.createMainMethodRunner(mainClass, args, classLoader).run();
}
protected
MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
return
new
MainMethodRunner(mainClass, args);
}
|
launch方法最终调用了createMainMethodRunner方法,后者实例化了MainMethodRunner对象并运行了run方法,我们转到MainMethodRunner源码中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package
org.springframework.boot.loader;
import
java.lang.reflect.Method;
public
class
MainMethodRunner {
private
final
String mainClassName;
private
final
String[] args;
public
MainMethodRunner(String mainClass, String[] args) {
this
.mainClassName = mainClass;
this
.args = args ==
null
?
null
:(String[])args.clone();
}
public
void
run()
throws
Exception {
Class mainClass = Thread.currentThread().getContextClassLoader().loadClass(
this
.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod(
"main"
,
new
Class[]{String[].
class
});
mainMethod.invoke((Object)
null
,
new
Object[]{
this
.args});
}
}
|
查看run方法,就很怎么将spring boot的jar怎么运行起来的了,由此分析基本也就结束了.
5、main程序的启动流程 。
讲完了jar的启动流程,现在来讲下spring boot应用中,main程序的启动与加载流程,首先我们看一个spring boot应用的main方法.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package
cn.com.devh;
import
org.springframework.boot.SpringApplication;
import
org.springframework.boot.autoconfigure.SpringBootApplication;
import
org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import
org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import
org.springframework.cloud.netflix.feign.EnableFeignClients;
/**
* Created by xiaxuan on 17/8/25.
*/
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public
class
A1ServiceApplication {
public
static
void
main(String[] args) {
SpringApplication.run(A1ServiceApplication.
class
, args);
}
}
|
转到SpringApplication中的run方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param source the source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public
static
ConfigurableApplicationContext run(Object source, String... args) {
return
run(
new
Object[] { source }, args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param sources the sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public
static
ConfigurableApplicationContext run(Object[] sources, String[] args) {
return
new
SpringApplication(sources).run(args);
}
|
这里的SpringApplication的实例化是关键,我们转到SpringApplication的构造函数.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param sources the bean sources
* @see #run(Object, String[])
* @see #SpringApplication(ResourceLoader, Object...)
*/
public
SpringApplication(Object... sources) {
initialize(sources);
}
private
void
initialize(Object[] sources) {
if
(sources !=
null
&& sources.length >
0
) {
this
.sources.addAll(Arrays.asList(sources));
}
this
.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.
class
));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.
class
));
this
.mainApplicationClass = deduceMainApplicationClass();
}
|
这里的initialize方法中的deduceWebEnvironment()确定了当前是以web应用启动还是以普通的jar启动,如下:
1
2
3
4
5
6
7
8
|
private
boolean
deduceWebEnvironment() {
for
(String className : WEB_ENVIRONMENT_CLASSES) {
if
(!ClassUtils.isPresent(className,
null
)) {
return
false
;
}
}
return
true
;
}
|
其中的WEB_ENVIRONMENT_CLASSES为: 。
1
2
|
private
static
final
String[] WEB_ENVIRONMENT_CLASSES = {
"javax.servlet.Servlet"
,
"org.springframework.web.context.ConfigurableWebApplicationContext"
};
|
只要其中任何一个不存在,即当前应用以普通jar的形式启动.
然后setInitializers方法初始化了所有的ApplicationContextInitializer, 。
1
2
3
4
5
6
7
8
9
10
11
|
/**
* Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
* {@link ApplicationContext}.
* @param initializers the initializers to set
*/
public
void
setInitializers(
Collection<?
extends
ApplicationContextInitializer<?>> initializers) {
this
.initializers =
new
ArrayList<ApplicationContextInitializer<?>>();
this
.initializers.addAll(initializers);
}
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.
class
))**
|
这一步初始化所有Listener.
我们再回到之前的SpringApplication(sources).run(args);处,进入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
|
/**
* 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
;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started();
try
{
ApplicationArguments applicationArguments =
new
DefaultApplicationArguments(
args);
context = createAndRefreshContext(listeners, applicationArguments);
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, ex);
throw
new
IllegalStateException(ex);
}
}
|
这一步进行上下文的创建createAndRefreshContext(listeners, applicationArguments).
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
39
40
41
42
43
44
45
46
47
|
private
ConfigurableApplicationContext createAndRefreshContext(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
ConfigurableApplicationContext context;
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if
(isWebEnvironment(environment) && !
this
.webEnvironment) {
environment = convertToStandardEnvironment(environment);
}
if
(
this
.bannerMode != Banner.Mode.OFF) {
printBanner(environment);
}
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext();
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);
// 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);
// Refresh the context
refresh(context);
if
(
this
.registerShutdownHook) {
try
{
context.registerShutdownHook();
}
catch
(AccessControlException ex) {
// Not allowed in some environments.
}
}
return
context;
}
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
|
这一步进行了环境的配置与加载.
1
2
3
|
if
(
this
.bannerMode != Banner.Mode.OFF) {
printBanner(environment);
}
|
这一步进行了打印spring boot logo,需要更改的话,在资源文件中加入banner.txt,banner.txt改为自己需要的图案即可.
1
2
3
|
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext();
return
(ConfigurableApplicationContext) BeanUtils.instantiate(contextClass)
|
创建上下文,这一步中真正包含了是创建什么容器,并进行了响应class的实例化,其中包括了EmbeddedServletContainerFactory的创建,是选择jetty还是tomcat,内容繁多,留待下一次再讲.
1
2
3
4
5
6
7
8
|
if
(
this
.registerShutdownHook) {
try
{
context.registerShutdownHook();
}
catch
(AccessControlException ex) {
// Not allowed in some environments.
}
}
|
这一步就是当前上下文进行注册,当收到kill指令的时候进行容器的销毁等工作了.
基本到此,启动的分析就结束了,但是还有一些细节讲述起来十分耗时,这个留待后续的博文中再来讲述,今天就到这里.
。
6.总结 。
综上spring boot jar的启动流程基本就是下面几个步骤:
1、我们正常进行maven打包时,spring boot插件扩展maven生命周期,将spring boot相关package打入到jar中,这个jar中包含了应用所打出的jar以外还有spring boot启动程序相关的类文件.
2、我以前看过稍微低一些版本的spring boot的jar的启动流程,当时我记得是当前线程又起了一个新线程来运行main程序,而现在的已经改成了直接使用反射来启动main程序.
总结 。
以上所述是小编给大家介绍的spring boot jar的启动原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我网站的支持! 。
原文链接:http://blog.csdn.net/u012734441/article/details/78325300 。
最后此篇关于spring boot jar的启动原理解析的文章就讲到这里了,如果你想了解更多关于spring boot jar的启动原理解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
本文全面深入地探讨了Docker容器通信技术,从基础概念、网络模型、核心组件到实战应用。详细介绍了不同网络模式及其实现,提供了容器通信的技术细节和实用案例,旨在为专业从业者提供深入的技术洞见和实
📒博客首页:崇尚学技术的科班人 🍣今天给大家带来的文章是《Dubbo快速上手 -- 带你了解Dubbo使用、原理》🍣 🍣希望各位小伙伴们能够耐心的读完这篇文章🍣 🙏博主也在学习阶段,如若发
一、写在前面 我们经常使用npm install ,但是你是否思考过它内部的原理是什么? 1、执行npm install 它背后帮助我们完成了什么操作? 2、我们会发现还有一个成为package-lo
Base64 Base64 是什么?是将字节流转换成可打印字符、将可打印字符转换为字节流的一种算法。Base64 使用 64 个可打印字符来表示转换后的数据。 准确的来说,Base64 不算
目录 协程定义 生成器和yield语义 Future类 IOLoop类 coroutine函数装饰器 总结 tornado中的
切片,这是一个在go语言中引入的新的理念。它有一些特征如下: 对数组抽象 数组长度不固定 可追加元素 切片容量可增大 容量大小成片增加 我们先把上面的理念整理在这
文章来源:https://sourl.cn/HpZHvy 引 言 本文主要论述的是“RPC 实现原理”,那么首先明确一个问题什么是 RPC 呢?RPC 是 Remote Procedure Call
源码地址(包含所有与springmvc相关的,静态文件路径设置,request请求入参接受,返回值处理converter设置等等): spring-framework/WebMvcConfigurat
请通过简单的java类向我展示一个依赖注入(inject)原理的小例子虽然我已经了解了spring,但是如果我需要用简单的java类术语来解释它,那么你能通过一个简单的例子向我展示一下吗?提前致谢。
1、背景 我们平常使用手机和电脑上网,需要访问公网上的网络资源,如逛淘宝和刷视频,那么手机和电脑是怎么知道去哪里去拿到这个网络资源来下载到本地的呢? 就比如我去食堂拿吃的,我需要
大家好,我是飞哥! 现在 iptables 这个工具的应用似乎是越来越广了。不仅仅是在传统的防火墙、NAT 等功能出现,在今天流行的的 Docker、Kubernets、Istio 项目中也经
本篇涉及到的所有接口在公开文档中均无,需要下载 GitHub 上的源码,自己创建私有类的文档。 npm run generateDocumentation -- --private yarn gene
我最近在很多代码中注意到人们将硬编码的配置(如端口号等)值放在类/方法的深处,使其难以找到,也无法配置。 这是否违反了 SOLID 原则?如果不是,我是否可以向我的团队成员引用另一个“原则”来说明为什
我是 C#、WPF 和 MVVM 模式的新手。很抱歉这篇很长的帖子,我试图设定我所有的理解点(或不理解点)。 在研究了很多关于 WPF 提供的命令机制和 MVVM 模式的文本之后,我在弄清楚如何使用这
可比较的 jQuery 函数 $.post("/example/handler", {foo: 1, bar: 2}); 将创建一个带有 post 参数 foo=1&bar=2 的请求。鉴于 $htt
如果Django不使用“延迟查询执行”原则,主要问题是什么? q = Entry.objects.filter(headline__startswith="What") q = q.filter(
我今天发现.NET框架在做计算时遵循BODMAS操作顺序。即计算按以下顺序进行: 括号 订单 部门 乘法 添加 减法 但是我四处搜索并找不到任何文档确认 .NET 绝对 遵循此原则,是否有此类文档?如
已结束。此问题不符合 Stack Overflow guidelines .它目前不接受答案。 我们不允许提出有关书籍、工具、软件库等方面的建议的问题。您可以编辑问题,以便用事实和引用来回答它。 关闭
API 回顾 在创建 Viewer 时可以直接指定 影像供给器(ImageryProvider),官方提供了一个非常简单的例子,即离屏例子(搜 offline): new Cesium.Viewer(
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be
我是一名优秀的程序员,十分优秀!