欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

spring boot jar的启动原理解析

程序员文章站 2022-10-10 17:26:47
 1.前言 近来有空对公司的open api平台进行了些优化,然后在打出jar包的时候,突然想到以前都是对spring boot使用很熟练,但是从来都不知道sp...

 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官网的文章来了解,如下:

以下是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, load, 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的启动原理解析,希望对大家有所帮助