先讲重要的:
就是点击运行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包,如下:
-
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, 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程序。

1591

被折叠的 条评论
为什么被折叠?



