spring boot jar的启动原理解析
1.前言
近来有空对公司的openapi平台进行了些优化,然后在打出jar包的时候,突然想到以前都是对springboot使用很熟练,但是从来都不知道springboot打出的jar的启动原理,然后这回将jar解开了看了下,与想象中确实大不一样,以下就是对解压出来的jar的完整分析。
2.jar的结构
springboot的应用程序就不贴出来了,一个较简单的demo打出的结构都是类似,另外我采用的springboot的版本为1.4.1.RELEASE网上有另外一篇文章对springbootjar启动的分析,那个应该是1.4以下的,启动方式与当前版本也有着许多的不同。
在mvncleaninstall后,我们在查看target目录中时,会发现两个jar包,如下:
xxxx.jar xxx.jar.original
这个则是归功于springboot插件的机制,将一个普通的jar打成了一个可以执行的jar包,而xxx.jar.original则是maven打出的jar包,这些可以参考spring官网的文章来了解,如下:
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#executable-jar
以下是springboot应用打出的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包,应该是springboot应用在打包的使用springboot插件将这个package打进来,也就是增强了mvn生命周期中的package阶段,而正是这个包在启动过程中起到了关键的作用,另外中jar中将应用所需的各种依赖都打进来,并且打入了springboot额外的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:PlexusArchiver Built-By:xiaxuan Implementation-Vendor-Id:com.weibangong.open Spring-Boot-Version:1.4.1.RELEASE Implementation-Vendor:PivotalSoftware,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:ApacheMaven3.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方法为:
publicstaticvoidmain(String[]args)throwsException{
PropertiesLauncherlauncher=newPropertiesLauncher();
args=launcher.getArgs(args);
launcher.launch(args);
}
查看launch方法,这个方法在父类Launcher中,找到父类方法launch方法,如下:
protectedvoidlaunch(String[]args,StringmainClass,ClassLoaderclassLoader)throwsException{
Thread.currentThread().setContextClassLoader(classLoader);
this.createMainMethodRunner(mainClass,args,classLoader).run();
}
protectedMainMethodRunnercreateMainMethodRunner(StringmainClass,String[]args,ClassLoaderclassLoader){
returnnewMainMethodRunner(mainClass,args);
}
launch方法最终调用了createMainMethodRunner方法,后者实例化了MainMethodRunner对象并运行了run方法,我们转到MainMethodRunner源码中,如下:
packageorg.springframework.boot.loader;
importjava.lang.reflect.Method;
publicclassMainMethodRunner{
privatefinalStringmainClassName;
privatefinalString[]args;
publicMainMethodRunner(StringmainClass,String[]args){
this.mainClassName=mainClass;
this.args=args==null?null:(String[])args.clone();
}
publicvoidrun()throwsException{
ClassmainClass=Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
MethodmainMethod=mainClass.getDeclaredMethod("main",newClass[]{String[].class});
mainMethod.invoke((Object)null,newObject[]{this.args});
}
}
查看run方法,就很怎么将springboot的jar怎么运行起来的了,由此分析基本也就结束了。
5、main程序的启动流程
讲完了jar的启动流程,现在来讲下springboot应用中,main程序的启动与加载流程,首先我们看一个springboot应用的main方法。
packagecn.com.devh;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.cloud.client.discovery.EnableDiscoveryClient;
importorg.springframework.cloud.netflix.eureka.EnableEurekaClient;
importorg.springframework.cloud.netflix.feign.EnableFeignClients;
/**
*Createdbyxiaxuanon17/8/25.
*/
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
publicclassA1ServiceApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(A1ServiceApplication.class,args);
}
}
转到SpringApplication中的run方法,如下:
/**
*Statichelperthatcanbeusedtoruna{@linkSpringApplication}fromthe
*specifiedsourceusingdefaultsettings.
*@paramsourcethesourcetoload
*@paramargstheapplicationarguments(usuallypassedfromaJavamainmethod)
*@returntherunning{@linkApplicationContext}
*/
publicstaticConfigurableApplicationContextrun(Objectsource,String...args){
returnrun(newObject[]{source},args);
}
/**
*Statichelperthatcanbeusedtoruna{@linkSpringApplication}fromthe
*specifiedsourcesusingdefaultsettingsandusersuppliedarguments.
*@paramsourcesthesourcestoload
*@paramargstheapplicationarguments(usuallypassedfromaJavamainmethod)
*@returntherunning{@linkApplicationContext}
*/
publicstaticConfigurableApplicationContextrun(Object[]sources,String[]args){
returnnewSpringApplication(sources).run(args);
}
这里的SpringApplication的实例化是关键,我们转到SpringApplication的构造函数。
/**
*Createanew{@linkSpringApplication}instance.Theapplicationcontextwillload
*beansfromthespecifiedsources(see{@linkSpringApplicationclass-level}
*documentationfordetails.Theinstancecanbecustomizedbeforecalling
*{@link#run(String...)}.
*@paramsourcesthebeansources
*@see#run(Object,String[])
*@see#SpringApplication(ResourceLoader,Object...)
*/
publicSpringApplication(Object...sources){
initialize(sources);
}
privatevoidinitialize(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启动,如下:
privatebooleandeduceWebEnvironment(){
for(StringclassName:WEB_ENVIRONMENT_CLASSES){
if(!ClassUtils.isPresent(className,null)){
returnfalse;
}
}
returntrue;
}
其中的WEB_ENVIRONMENT_CLASSES为:
privatestaticfinalString[]WEB_ENVIRONMENT_CLASSES={"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext"};
只要其中任何一个不存在,即当前应用以普通jar的形式启动。
然后setInitializers方法初始化了所有的ApplicationContextInitializer,
/**
*Setsthe{@linkApplicationContextInitializer}thatwillbeappliedtotheSpring
*{@linkApplicationContext}.
*@paraminitializerstheinitializerstoset
*/
publicvoidsetInitializers(
Collection>initializers){
this.initializers=newArrayList>();
this.initializers.addAll(initializers);
}
setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class))**
这一步初始化所有Listener。
我们再回到之前的SpringApplication(sources).run(args);处,进入run方法,代码如下:
/**
*RuntheSpringapplication,creatingandrefreshinganew
*{@linkApplicationContext}.
*@paramargstheapplicationarguments(usuallypassedfromaJavamainmethod)
*@returnarunning{@linkApplicationContext}
*/
publicConfigurableApplicationContextrun(String...args){
StopWatchstopWatch=newStopWatch();
stopWatch.start();
ConfigurableApplicationContextcontext=null;
configureHeadlessProperty();
SpringApplicationRunListenerslisteners=getRunListeners(args);
listeners.started();
try{
ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(
args);
context=createAndRefreshContext(listeners,applicationArguments);
afterRefresh(context,applicationArguments);
listeners.finished(context,null);
stopWatch.stop();
if(this.logStartupInfo){
newStartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(),stopWatch);
}
returncontext;
}
catch(Throwableex){
handleRunFailure(context,listeners,ex);
thrownewIllegalStateException(ex);
}
}
这一步进行上下文的创建createAndRefreshContext(listeners,applicationArguments),
privateConfigurableApplicationContextcreateAndRefreshContext(
SpringApplicationRunListenerslisteners,
ApplicationArgumentsapplicationArguments){
ConfigurableApplicationContextcontext;
//Createandconfiguretheenvironment
ConfigurableEnvironmentenvironment=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,refreshandruntheApplicationContext
context=createApplicationContext();
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if(this.logStartupInfo){
logStartupInfo(context.getParent()==null);
logStartupProfileInfo(context);
}
//Addbootspecificsingletonbeans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
//Loadthesources
Set
这一步进行了环境的配置与加载。
if(this.bannerMode!=Banner.Mode.OFF){
printBanner(environment);
}
这一步进行了打印springbootlogo,需要更改的话,在资源文件中加入banner.txt,banner.txt改为自己需要的图案即可。
//Create,load,refreshandruntheApplicationContext context=createApplicationContext(); return(ConfigurableApplicationContext)BeanUtils.instantiate(contextClass)
创建上下文,这一步中真正包含了是创建什么容器,并进行了响应class的实例化,其中包括了EmbeddedServletContainerFactory的创建,是选择jetty还是tomcat,内容繁多,留待下一次再讲。
if(this.registerShutdownHook){
try{
context.registerShutdownHook();
}
catch(AccessControlExceptionex){
//Notallowedinsomeenvironments.
}
}
这一步就是当前上下文进行注册,当收到kill指令的时候进行容器的销毁等工作了。
基本到此,启动的分析就结束了,但是还有一些细节讲述起来十分耗时,这个留待后续的博文中再来讲述,今天就到这里。
6.总结
综上springbootjar的启动流程基本就是下面几个步骤:
1、我们正常进行maven打包时,springboot插件扩展maven生命周期,将springboot相关package打入到jar中,这个jar中包含了应用所打出的jar以外还有springboot启动程序相关的类文件。
2、我以前看过稍微低一些版本的springboot的jar的启动流程,当时我记得是当前线程又起了一个新线程来运行main程序,而现在的已经改成了直接使用反射来启动main程序。
总结
以上所述是小编给大家介绍的springbootjar的启动原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!