springboot jar执行原理

先讲重要的:

就是点击运行run springboot项目的时候,先把Application 加入到Set类型的sources 

然后 看webEnvironment springbean中是否有这两个类,如果有就是war启动,没有就是jar启动

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext" };

 

 

看到springboot jar打包之后 解压能看到

然后在META-INF 中 有一个

Manifest-Version: 1.0
Implementation-Title: dstech3-api
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: Administrator
Implementation-Vendor-Id: com.ds.tech
Spring-Boot-Version: 2.0.0.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.ds.tech.Dstech3ApiApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.5.3
Build-Jdk: 1.8.0_144
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
 ot-starter-parent/ares-api

其中有一个Main-Class 和 Start-Class 其中先执行Main-Class 

其中JarLauncher是一个接口,其中可以是jar 和 war包发布

 

 

2.jar的结构


spring boot的应用程序就不贴出来了,一个较简单的demo打出的结构都是类似,另外我采用的spring boot的版本为1.4.1.RELEASE网上有另外一篇文章对spring boot jar启动的分析,那个应该是1.4以下的,启动方式与当前版本也有着许多的不同。

在mvn clean install后,我们在查看target目录中时,会发现两个jar包,如下:

 
  1. xxxx.jar

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

 
  1. .

  2. ├── BOOT-INF

  3. │   ├── classes

  4. │   │   ├── application-dev.properties

  5. │   │   ├── application-prod.properties

  6. │   │   ├── application.properties

  7. │   │   ├── com

  8. │   │   │   └── weibangong

  9. │   │   │   └── open

  10. │   │   │   └── openapi

  11. │   │   │   ├── SpringBootWebApplication.class

  12. │   │   │   ├── config

  13. │   │   │   │   ├── ProxyServletConfiguration.class

  14. │   │   │   │   └── SwaggerConfig.class

  15. │   │   │   ├── oauth2

  16. │   │   │   │   ├── controller

  17. │   │   │   │   │   ├── AccessTokenController.class

  18. │   │   ├── logback-spring.xml

  19. │   │   └── static

  20. │   │   ├── css

  21. │   │   │   └── guru.css

  22. │   │   ├── images

  23. │   │   │   ├── FBcover1200x628.png

  24. │   │   │   └── NewBannerBOOTS_2.png

  25. │   └── lib

  26. │   ├── accessors-smart-1.1.jar

  27. ├── META-INF

  28. │   ├── MANIFEST.MF

  29. │   └── maven

  30. │   └── com.weibangong.open

  31. │   └── open-server-openapi

  32. │   ├── pom.properties

  33. │   └── pom.xml

  34. └── org

  35. └── springframework

  36. └── boot

  37. └── loader

  38. ├── ExecutableArchiveLauncher$1.class

  39. ├── ExecutableArchiveLauncher.class

  40. ├── JarLauncher.class

  41. ├── LaunchedURLClassLoader$1.class

  42. ├── LaunchedURLClassLoader.class

  43. ├── Launcher.class

  44. ├── archive

  45. │   ├── Archive$Entry.class

  46. │   ├── Archive$EntryFilter.class

  47. │   ├── Archive.class

  48. │   ├── ExplodedArchive$1.class

  49. │   ├── ExplodedArchive$FileEntry.class

  50. │   ├── ExplodedArchive$FileEntryIterator$EntryComparator.class

  51.    ├── ExplodedArchive$FileEntryIterator.class

  52.  

这个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文件,如下:

 
  1. Manifest-Version: 1.0

  2. Implementation-Title: open :: server :: openapi

  3. Implementation-Version: 1.0-SNAPSHOT

  4. Archiver-Version: Plexus Archiver

  5. Built-By: xiaxuan

  6. Implementation-Vendor-Id: com.weibangong.open

  7. Spring-Boot-Version: 1.4.1.RELEASE

  8. Implementation-Vendor: Pivotal Software, Inc.

  9. Main-Class: org.springframework.boot.loader.PropertiesLauncher

  10. Start-Class: com.weibangong.open.openapi.SpringBootWebApplication

  11. Spring-Boot-Classes: BOOT-INF/classes/

  12. Spring-Boot-Lib: BOOT-INF/lib/

  13. Created-By: Apache Maven 3.3.9

  14. Build-Jdk: 1.8.0_20

  15. Implementation-URL: http://maven.apache.org/open-server-openapi

这里指定的main-class是单独打入的包中的一个类文件而不是我们的启动程序,然后MANIFEST.MF文件有一个单独的start-class指定的是我们的应用的启动程序。

4.启动分析


首先我们找到类org.springframework.boot.loader.PropertiesLauncher,其中main方法为:

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

  2. PropertiesLauncher launcher = new PropertiesLauncher();

  3. args = launcher.getArgs(args);

  4. launcher.launch(args);

  5. }

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

 
  1.  
  2. protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception {

  3. Thread.currentThread().setContextClassLoader(classLoader);

  4. this.createMainMethodRunner(mainClass, args, classLoader).run();

  5. }

  6.  
  7. protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {

  8. return new MainMethodRunner(mainClass, args);

  9. }

  10.  

launch方法最终调用了createMainMethodRunner方法,后者实例化了MainMethodRunner对象并运行了run方法,我们转到MainMethodRunner源码中,如下:

 
  1. package org.springframework.boot.loader;

  2.  
  3. import java.lang.reflect.Method;

  4.  
  5. public class MainMethodRunner {

  6. private final String mainClassName;

  7. private final String[] args;

  8.  
  9. public MainMethodRunner(String mainClass, String[] args) {

  10. this.mainClassName = mainClass;

  11. this.args = args == null?null:(String[])args.clone();

  12. }

  13.  
  14. public void run() throws Exception {

  15. Class mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);

  16. Method mainMethod = mainClass.getDeclaredMethod("main", new Class[]{String[].class});

  17. mainMethod.invoke((Object)null, new Object[]{this.args});

  18. }

  19. }

查看run方法,详细查看spring boot的jar怎么运行起来的了,由此分析基本也就结束了。

5、main程序的启动流程


讲完了jar的启动流程,现在来讲下spring boot应用中,main程序的启动与加载流程,首先我们看一个spring boot应用的main方法。

 
  1.  
  2. package cn.com.devh;

  3.  
  4. import org.springframework.boot.SpringApplication;

  5. import org.springframework.boot.autoconfigure.SpringBootApplication;

  6. import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

  7. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

  8. import org.springframework.cloud.netflix.feign.EnableFeignClients;

  9.  
  10. /**

  11. * Created by xiaxuan on 17/8/25.

  12. */

  13. @SpringBootApplication

  14. @EnableFeignClients

  15. @EnableEurekaClient

  16. public class A1ServiceApplication {

  17.  
  18. public static void main(String[] args) {

  19. SpringApplication.run(A1ServiceApplication.class, args);

  20. }

  21. }

转到SpringApplication中的run方法,如下:

 
  1. /**

  2. * Static helper that can be used to run a {@link SpringApplication} from the

  3. * specified source using default settings.

  4. * @param source the source to load

  5. * @param args the application arguments (usually passed from a Java main method)

  6. * @return the running {@link ApplicationContext}

  7. */

  8. public static ConfigurableApplicationContext run(Object source, String... args) {

  9. return run(new Object[] { source }, args);

  10. }

  11.  
  12. /**

  13. * Static helper that can be used to run a {@link SpringApplication} from the

  14. * specified sources using default settings and user supplied arguments.

  15. * @param sources the sources to load

  16. * @param args the application arguments (usually passed from a Java main method)

  17. * @return the running {@link ApplicationContext}

  18. */

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

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

  21. }

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

 
  1.  
  2. /**

  3. * Create a new {@link SpringApplication} instance. The application context will load

  4. * beans from the specified sources (see {@link SpringApplication class-level}

  5. * documentation for details. The instance can be customized before calling

  6. * {@link #run(String...)}.

  7. * @param sources the bean sources

  8. * @see #run(Object, String[])

  9. * @see #SpringApplication(ResourceLoader, Object...)

  10. */

  11. public SpringApplication(Object... sources) {

  12. initialize(sources);

  13. }

  14.  
  15. private void initialize(Object[] sources) {

  16. if (sources != null && sources.length > 0) {

  17. this.sources.addAll(Arrays.asList(sources));

  18. }

  19. this.webEnvironment = deduceWebEnvironment();

  20. setInitializers((Collection) getSpringFactoriesInstances(

  21. ApplicationContextInitializer.class));

  22. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

  23. this.mainApplicationClass = deduceMainApplicationClass();

  24. }

  25.  

这里的initialize方法中的deduceWebEnvironment()确定了当前是以web应用启动还是以普通的jar启动,如下:

 
  1. private boolean deduceWebEnvironment() {

  2. for (String className : WEB_ENVIRONMENT_CLASSES) {

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

  4. return false;

  5. }

  6. }

  7. return true;

  8. }

其中的WEB_ENVIRONMENT_CLASSES为:

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

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

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

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

 
  1. /**

  2. * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring

  3. * {@link ApplicationContext}.

  4. * @param initializers the initializers to set

  5. */

  6. public void setInitializers(

  7. Collection> initializers) {

  8. this.initializers = new ArrayList>();

  9. this.initializers.addAll(initializers);

  10. }

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

这一步初始化所有Listener。

我们再回到之前的SpringApplication(sources).run(args);处,进入run方法,代码如下:

 
  1. /**

  2. * Run the Spring application, creating and refreshing a new

  3. * {@link ApplicationContext}.

  4. * @param args the application arguments (usually passed from a Java main method)

  5. * @return a running {@link ApplicationContext}

  6. */

  7. public ConfigurableApplicationContext run(String... args) {

  8. StopWatch stopWatch = new StopWatch();

  9. stopWatch.start();

  10. ConfigurableApplicationContext context = null;

  11. configureHeadlessProperty();

  12. SpringApplicationRunListeners listeners = getRunListeners(args);

  13. listeners.started();

  14. try {

  15. ApplicationArguments applicationArguments = new DefaultApplicationArguments(

  16. args);

  17. context = createAndRefreshContext(listeners, applicationArguments);

  18. afterRefresh(context, applicationArguments);

  19. listeners.finished(context, null);

  20. stopWatch.stop();

  21. if (this.logStartupInfo) {

  22. new StartupInfoLogger(this.mainApplicationClass)

  23. .logStarted(getApplicationLog(), stopWatch);

  24. }

  25. return context;

  26. }

  27. catch (Throwable ex) {

  28. handleRunFailure(context, listeners, ex);

  29. throw new IllegalStateException(ex);

  30. }

  31. }

这一步进行上下文的创建createAndRefreshContext(listeners, applicationArguments),

 
  1. private ConfigurableApplicationContext createAndRefreshContext(

  2. SpringApplicationRunListeners listeners,

  3. ApplicationArguments applicationArguments) {

  4. ConfigurableApplicationContext context;

  5. // Create and configure the environment

  6. ConfigurableEnvironment environment = getOrCreateEnvironment();

  7. configureEnvironment(environment, applicationArguments.getSourceArgs());

  8. listeners.environmentPrepared(environment);

  9. if (isWebEnvironment(environment) && !this.webEnvironment) {

  10. environment = convertToStandardEnvironment(environment);

  11. }

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

  14. printBanner(environment);

  15. }

  16.  
  17. // Create, load, refresh and run the ApplicationContext

  18. context = createApplicationContext();

  19. context.setEnvironment(environment);

  20. postProcessApplicationContext(context);

  21. applyInitializers(context);

  22. listeners.contextPrepared(context);

  23. if (this.logStartupInfo) {

  24. logStartupInfo(context.getParent() == null);

  25. logStartupProfileInfo(context);

  26. }

  27.  
  28. // Add boot specific singleton beans

  29. context.getBeanFactory().registerSingleton("springApplicationArguments",

  30. applicationArguments);

  31.  
  32. // Load the sources

  33. Set sources = getSources();

  34. Assert.notEmpty(sources, "Sources must not be empty");

  35. load(context, sources.toArray(new Object[sources.size()]));

  36. listeners.contextLoaded(context);

  37.  
  38. // Refresh the context

  39. refresh(context);

  40. if (this.registerShutdownHook) {

  41. try {

  42. context.registerShutdownHook();

  43. }

  44. catch (AccessControlException ex) {

  45. // Not allowed in some environments.

  46. }

  47. }

  48. return context;

  49. }

  50.  
    1. // Create and configure the environment

    2. ConfigurableEnvironment environment = getOrCreateEnvironment();

    3. configureEnvironment(environment, applicationArguments.getSourceArgs());

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

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

    2. printBanner(environment);

    3. }

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

     
    1. // Create, load, refresh and run the ApplicationContext

    2. context = createApplicationContext();

    return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass)
    •  

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

     
    1. if (this.registerShutdownHook) {

    2. try {

    3. context.registerShutdownHook();

    4. }

    5. catch (AccessControlException ex) {

    6. // Not allowed in some environments.

    7. }

    8. }

    这一步就是当前上下文进行注册,当收到kill指令的时候进行容器的销毁等工作了。

    基本到此,启动的分析就结束了,但是还有一些细节讲述起来十分耗时,这个留待后续的博文中再来讲述,今天就到这里。

    6.总结


    综上spring boot jar的启动流程基本就是下面几个步骤:

    • 1、我们正常进行maven打包时,spring boot插件扩展maven生命周期,将spring boot相关package打入到jar中,这个jar中包含了应用所打出的jar以外还有spring boot启动程序相关的类文件。

    • 2、我以前看过稍微低一些版本的spring boot的jar的启动流程,当时我记得是当前线程又起了一个新线程来运行main程序,而现在的已经改成了直接使用反射来启动main程序。

    你可能感兴趣的:(java初学,java,微服务)