我们启动一个SpringBoot项目只需要执行java -jar xxx.jar命令就行了,这个时候我们就启动了一个web服务器容器,可以在浏览器上通过接口地址访问对应的接口了,但是整个启动过程对于我们来说是透明的,还是有必要了解下的。
启动前置
- java -jar命令:这个命令会找到jar中的manifest文件,然后执行里面配置的Main-Class类;
- MANIFEST文件:
Spring Boot中MANIFEST文件是/META-INF/MANIFEST.MF文件,看下它里面的内容:
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: sdk-api-demo
Implementation-Version: 1.0.0
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.xxx.xxx.XxxApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.1
Created-By: Maven Jar Plugin 3.2.0
Main-Class: org.springframework.boot.loader.JarLauncher
最后面配置了我们需要的Main-Class,所以当我们执行java -jar命令时,实际执行的是org.springframework.boot.loader.JarLauncher类。往上看还有一个Start-Class参数配置,可以看到这个参数配置的值才是我们应用项目的启动类,所以可以猜测应该是在JarLauncher类中有对XxxApplication类的调用。
- JarLauncher类:这是整个应用启动的入口类,他会加载/BOOT-INF/lib/下所有的依赖jar包,并且会创建一个新线程来执行main方法,看下它的源码,哦,对了,想要看源码要先加入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<scope>provided</scope>
</dependency>
源码:
public class JarLauncher extends ExecutableArchiveLauncher {
static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
static final String BOOT_INF_LIB = "BOOT-INF/lib/";
public JarLauncher() {
}
protected JarLauncher(Archive archive) {
super(archive);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}
public static void main(String[] args) throws Exception {
new JarLauncher().launch(args);
}
}
这个类实际上没什么内容,主要看下launch方法,这里的launch方法调用的是Launcher#launch方法:
protected void launch(String[] args) throws Exception {
JarFile.registerUrlProtocolHandler();
ClassLoader classLoader = createClassLoader(getClassPathArchives());
// 看这里的getMainClass方法
launch(args, getMainClass(), classLoader);
}
这里的getMainClass()方法会去读取上面Start-Class参数所配置的class路径,ExecutableArchiveLauncher#getMainClass:
@Override
protected String getMainClass() throws Exception {
// MANIFEST文件
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if (manifest != null) {
// 得到Start-Class配置的值
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if (mainClass == null) {
throw new IllegalStateException(
"No 'Start-Class' manifest entry specified in " + this);
}
return mainClass;
}
得到Start-Class配置的值之后,经过一系列的调用栈,会到MainMethodRunner#run方法:
public void run() throws Exception {
// mainClassName就是Start-Class的值
Class<?> mainClass = Thread.currentThread().getContextClassLoader()
.loadClass(this.mainClassName);
// 获取main方法
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
// 利用反射来调用配置类的main方法
mainMethod.invoke(null, new Object[] { this.args });
}
到这里,整个流程就到了我们熟悉的XxxApplication.main(String[] args)方法中了。
从XxxApplication.main(String[] args)方法开始启动
从XxxApplication中的main方法开始,就是启动spring容器的流程了,哦,还要启动web容器。从main方法沿着调用栈可以找到SpringApplication#run方法的调用。
- SpringApplication#run:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 看这里
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
先忽略其他的,看看refreshContext(context)的调用,这个是spring容器初始化的方法,然后再沿着调用栈就可以到SpringApplication#refresh方法。
- SpringApplication#refresh方法:
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
看到了吧,调用了AbstractApplicationContext#refresh方法,看到ioc容器初始化过程的对这个方法太熟悉了,剩下的过程就是spring的内容了。哦,还有容器的启动,在refresh方法中,有一个onRefresh方法的调用,这个方法是一个钩子方法,在spring中没有任何实现,容器的启动就是在这个方法中实现的。
- ReactiveWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start reactive web server",
ex);
}
}
- ReactiveWebServerApplicationContext#createWebServer
private void createWebServer() {
WebServer localServer = this.webServer;
// 这个主要是为了兼容外置web容器启动
// 如果已经有了webServer,就不会再去启动一个webServer了
if (localServer == null) {
this.webServer = getWebServerFactory().getWebServer(getHttpHandler());
}
initPropertySources();
}
看注释吧,接着看下getWebServer方法,我们一般用的是tomcat,所以就看下tomcat的实现吧。
- TomcatReactiveWebServerFactory#getWebServer
@Override
public WebServer getWebServer(HttpHandler httpHandler) {
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
prepareContext(tomcat.getHost(), servlet);
// 看这里
return new TomcatWebServer(tomcat, getPort() >= 0);
}
这个方法主要是创建了一个Tomcat对象,填充了一些属性,主要的创建工作还是在new TomcatWebServer中,new TomcatWebServer中主要是调用了TomcatWebServer#initialize方法,直接看这个方法吧。
- TomcatWebServer#initialize:
private void initialize() throws WebServerException {
TomcatWebServer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource())
&& Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// 看这里,tomcat启动了
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
// 绑定类加载器
ContextBindings.bindClassLoader(context, context.getNamingToken(),
getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// 启动一个后台线程运行,不能main方法跑完了,tomcat线程就不在了。
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
这个方法就是启动tomcat的方法,到这里,tomcat也启动起来了。