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官网的文章来了解,如下:
以下是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(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方法。
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方法,如下:
/** * 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的构造函数。
/** * 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<applicationcontextinitializer<?>>(); 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<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());
这一步进行了环境的配置与加载。
if (this.bannermode != banner.mode.off) { printbanner(environment); }
这一步进行了打印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的启动流程,当时我记得是当前线程又起了一个新线程来运行main程序,而现在的已经改成了直接使用反射来启动main程序。
总结
以上所述是小编给大家介绍的spring boot jar的启动原理解析,希望对大家有所帮助
推荐阅读
-
Spring Boot创建可执行jar包的实例教程
-
spring boot jar的启动原理解析
-
Spring Boot创建非可执行jar包的实例教程
-
Spring Boot Maven 打包可执行Jar文件的实现方法
-
spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
-
spring boot 打jar包,获取resource路径下的文件
-
SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
spring boot启动时加载外部配置文件的方法
-
Spring Boot2.3 新特性分层JAR的使用