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的启动原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!