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(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,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的启动原理解析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持! 您可能感兴趣的文章:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |