spring boot jar的启动原理解析

网友投稿 307 2023-02-18


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包,如下:

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的部分目录结构,大部分省略了,仅仅展示出其中重要的部分。

.

├── 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文件,如下:

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方法为:

public static void main(String[] args) throws Exception {

PropertiesLauncher launcher = new PropertiesLauncher();

args = launcher.getArgs(args);

launcher.launch(args);

}

查看launch方法,这个方法在父类Launcher中,找到父类方法launch方法,如下:

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源码中,如下:

package org.springframework.boot.loader;

import java.lang.reflect.Method;

public class MainMethodRunner {

private final String mainClassName;

private final String[] args;

public MainMethodRunner(Strihttp://ng 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方法。

package cn.com.devh;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.sZkebVNUCVpringframework.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方法,如下:

/**

* 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 mhttp://ethod)

* @return the running {@link ApplicationContext}

*/

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {

return new SpringApplication(sources).run(args);

}

这里的SpringApplication的实例化是关键,我们转到SpringApplication的构造函数。

/**

* 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启动,如下:

private boolean deduceWebEnvironment() {

for (String className : WEB_ENVIRONMENT_CLASSES) {

if (!ClassUtils.isPresent(className, null)) {

return false;

}

}

return true;

}

其中的WEB_ENVIRONMENT_CLASSES为:

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",

"org.springframework.web.context.ConfigurableWebApplicationContext" };

只要其中任何一个不存在,即当前应用以普通jar的形式启动。

然后setInitializers方法初始化了所有的ApplicationContextInitializer,

/**

* 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>();

this.initializers.addAll(initializers);

}

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class))**

这一步初始化所有Listener。

我们再回到之前的SpringApplication(sources).run(args);处,进入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;

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),

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 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());

这一步进行了环境的配置与加载。

if (this.bannerMode != Banner.Mode.OFF) {

printBanner(environment);

}

ZkebVNUCV

这一步进行了打印spring boot logo,需要更改的话,在资源文件中加入banner.txt,banner.txt改为自己需要的图案即可。

// Create, load, refresh and run the ApplicationContext

context = createApplicationContext();

return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass)

创建上下文,这一步中真正包含了是创建什么容器,并进行了响应class的实例化,其中包括了EmbeddedServletContainerFactory的创建,是选择jetty还是tomcat,内容繁多,留待下一次再讲。

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的启动流程,当时我http://记得是当前线程又起了一个新线程来运行main程序,而现在的已经改成了直接使用反射来启动main程序。

总结

以上所述是给大家介绍的spring boot jar的启动原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:好用的接口测试工具(常用的接口测试工具)
下一篇:Vue使用枚举类型实现HTML下拉框步骤详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~