gpt4 book ai didi

spring boot jar的启动原理解析

转载 作者:qq735679552 更新时间:2022-09-27 22:32:09 27 4
gpt4 key购买 nike

CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.

这篇CFSDN的博客文章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包,如下:

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
.
├── 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文件,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法为:

?
1
2
3
4
5
public static void main(String[] args) throws Exception {
   PropertiesLauncher launcher = new PropertiesLauncher();
   args = launcher.getArgs(args);
   launcher.launch(args);
}

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

?
1
2
3
4
5
6
7
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源码中,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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方法.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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方法,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
  * 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的构造函数.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
  * 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启动,如下:

?
1
2
3
4
5
6
7
8
private boolean deduceWebEnvironment() {
  for (String className : WEB_ENVIRONMENT_CLASSES) {
   if (!ClassUtils.isPresent(className, null )) {
    return false ;
   }
  }
  return true ;
}

其中的WEB_ENVIRONMENT_CLASSES为:   。

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
/**
   * 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方法,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
   * 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).

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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());

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

?
1
2
3
if ( this .bannerMode != Banner.Mode.OFF) {
  printBanner(environment);
}

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

?
1
2
3
// Create, load, refresh and run the ApplicationContext
   context = createApplicationContext();
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass)

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

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

原文链接:http://blog.csdn.net/u012734441/article/details/78325300 。

最后此篇关于spring boot jar的启动原理解析的文章就讲到这里了,如果你想了解更多关于spring boot jar的启动原理解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com