spring boot jarの起動原理解析
1.はじめに
最近は暇があって、会社のopen appiプラットフォームを最適化しました。そして、jarカバンを作る時、以前はspring bootの使用に慣れていたと思いましたが、spring bootが打ち出したjarの起動原理を知らなかったので、今回はjarを解いてみました。
2.jarの構造
spring bootのアプリケーションは貼り付けません。簡単なデモテープの構造は似ています。また、私が採用したspring bootのバージョンは1.4.1.RELEASEのウェブ上にもう一つの文章があります。spring boot jarの起動に対する分析は1.4以下のはずです。起動式と現在のバージョンは多くの違いがあります。
mvn clean install後、私達はtargetディレクトリを見ていると、二つのjarカバンが見つかります。
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#executable-jar
以下はspring bootアプリケーションが打ち出したjarの部分ディレクトリ構造です。ほとんどは省略されています。その中の重要な部分だけを示しています。
3.MANIFEST.MFファイル
この時、META-INFのMANIFEST.MFファイルを見続けます。以下の通りです。
4.分析を開始する
まず、クラスorg.springframe ebook.boot.loader.Proptiess Launcherを見つけました。ここでmain方法は:
5、mainプログラムの起動フロー
jarの起動プロセスを説明しました。今からspring bootアプリケーションでは、mainプログラムの起動とローディングフローは、まずspring bootアプリケーションのmain方法を見ます。
そしてset Initializers法は、すべてのAppliation Controtext Initializerを初期化し、
以前のSpringApplicationに戻ります。に入ります。コードは以下の通りです。
基本はここまでです。起動の分析は終わりましたが、細かいところまで説明するのはとても時間がかかります。これは後のブログでもう一度説明します。今日はここまでです。
6.まとめ
以上のように、spring boot jarの起動フローは基本的に次のステップです。
1、私達が正常にmavenパッケージを行う時、spring bootプラグインはmavenライフサイクルを拡張して、spring boot関連packageをjarに打ち込みます。このjarにはアプリケーションが打ち出したjar以外にspring boot起動プログラムに関する種類のファイルが含まれています。
2、ちょっと低いバージョンのspring bootのJarの起動プロセスを見たことがありますが、当時は現在のスレッドがまた新しいスレッドを立ててメールプログラムを実行していたのを覚えています。今は直接反射を使ってMainプログラムを起動しました。
締め括りをつける
以上は小编で绍介したspring boot jarの起动原理解析です。皆さんに何かお聞きしたいことがあれば、メッセージをください。小编はすぐに皆さんに返事します。ここでも私たちのサイトを応援してくれてありがとうございます。
最近は暇があって、会社のopen appiプラットフォームを最適化しました。そして、jarカバンを作る時、以前はspring bootの使用に慣れていたと思いましたが、spring bootが打ち出したjarの起動原理を知らなかったので、今回はjarを解いてみました。
2.jarの構造
spring bootのアプリケーションは貼り付けません。簡単なデモテープの構造は似ています。また、私が採用した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カバンにしました。xx.jar.origginnalは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は私達の書いたアプリケーションが打ち出したクラス以外にもう一つのオリジナルパッケージがあります。spring bootアプリケーションはパッケージのspring bootプラグインを使ってこのpackageを打ち込みます。つまりmvnライフサイクルの中のpackage段階を強めました。このカバンは起動過程で重要な役割を果たしました。また、jarでは、アプリケーションに必要なさまざまな依存性が打ち込まれ、spring bootに追加のpackageが打ち込まれています。このall-inn-oneのjarはfat.jarとも言われています。これからは、jarの代わりにfat.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.springframe ebook.boot.loader.Proptiess Launcherを見つけました。ここで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のオブジェクトを具体化して実行しました。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);
}
}
SprigAplicationに移行する方法は、以下の通りです。
/**
* 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);
}
ここのSprigAplicationの実用化が鍵となり、SprigAplicationの構成関数に移ります。
/**
* 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()は、現在ウェブアプリケーションで起動しているか、それとも通常の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形式で起動されます。そしてset Initializers法は、すべてのAppliation Controtext Initializerを初期化し、
/**
* 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に戻ります。に入ります。コードは以下の通りです。
/**
* 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);
}
}
このステップはコンテキストの作成createAndRefresh Contectを行い、
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);
}
このステップでスプリングブックのロゴをプリントしました。変更が必要なら、リソースファイルにbanner.txtを入れて、banner.txtを自分の必要なパターンに変更すればいいです。
// Create, load, refresh and run the ApplicationContext
context = createApplicationContext();
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass)
文脈を作成するには、このステップにはどのような容器を作成するかが含まれています。また、classの実用化に応答して、EmbodServletContiner Factoryの作成が含まれています。jeyかtomcatかを選択します。
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
このステップは現在のコンテキストで登録し、キルコマンドを受けたときに容器の廃棄などを行います。基本はここまでです。起動の分析は終わりましたが、細かいところまで説明するのはとても時間がかかります。これは後のブログでもう一度説明します。今日はここまでです。
6.まとめ
以上のように、spring boot jarの起動フローは基本的に次のステップです。
1、私達が正常にmavenパッケージを行う時、spring bootプラグインはmavenライフサイクルを拡張して、spring boot関連packageをjarに打ち込みます。このjarにはアプリケーションが打ち出したjar以外にspring boot起動プログラムに関する種類のファイルが含まれています。
2、ちょっと低いバージョンのspring bootのJarの起動プロセスを見たことがありますが、当時は現在のスレッドがまた新しいスレッドを立ててメールプログラムを実行していたのを覚えています。今は直接反射を使ってMainプログラムを起動しました。
締め括りをつける
以上は小编で绍介したspring boot jarの起动原理解析です。皆さんに何かお聞きしたいことがあれば、メッセージをください。小编はすぐに皆さんに返事します。ここでも私たちのサイトを応援してくれてありがとうございます。