基于 spring-boot-start开发的项目,其程序入口并不是我们开发的业务代码中定义了 main 函数的类,而是 Spring Boot 定义的 JarLauncher 类(下文源码反编译自 spring-boot-loader-1.5.8.RELEASE.jar)。
通常使用 spring boot 进行开发时,会定义类似以下程序入口
@SpringBootApplication
/**
* This @SpringBootApplication is a convenience annotation that adds all of the following:
* @Configuration tags the class as a source of bean definitions for the application context.
* @EnableAutoConfiguration tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
* @EnableWebMvc Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath. This flags the application as a web application and activates key behaviors such as setting up a DispatcherServlet.
* @ComponentScan tells Spring to look for other components, configurations, and services in the hello package, allowing it to find the controllers.
* */
@ServletComponentScan
@EnableTransactionManagement
@EnableAsync(proxyTargetClass = true)
public class KiApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(KiApplication.class, args);
}
}
然后,通过增加 maven 插件 spring-boot-maven-plugin 可以将开发好的项目打包为可执行的 *.jar ,虽然在开发的代码中定义了main函数,但是不要以为程序就是从这个类开始执行的。因为,如果打开打包的 *.jar 中 /META-INF/MANIFEST.MF 文件查看,就会发现真实的入口类并不是我们定义了main函数的类,而是 org.springframework.boot.loader.JarLauncher 。如下,Main-Class 定义 jar 的入口类,另外扩展了 Start-Class 定义启动类,这才是我们业务代码中定义了main函数的类。
Manifest-Version: 1.0
Implementation-Title: kiff-starter
Implementation-Version: 1.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: zouzhiyuan
Implementation-Vendor-Id: *.*.*
Spring-Boot-Version: 1.5.8.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: *.*.*.KiApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.0.5
Build-Jdk: 1.8.0_51
Implementation-URL: http://projects.spring.io/spring-boot/kiff-starter
/
进一步查看 JarLauncher 类的源码可以发现,该类声明的 main 函数 调用了父类的 launch() 方法,并且将 args 参数进行透传。
package org.springframework.boot.loader;
import org.springframework.boot.loader.ExecutableArchiveLauncher;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;
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);
}
protected boolean isNestedArchive(Entry entry) {
return entry.isDirectory()?entry.getName().equals("BOOT-INF/classes/"):entry.getName().startsWith("BOOT-INF/lib/");
}
public static void main(String[] args) throws Exception {
(new JarLauncher()).launch(args);
}
}
继续查看 JarLauncher 类继承的 ExecutableArchiveLauncher 类,源代码如下,这个类中的 getMainClass() 方法实际上是在找 MANIFEST.MF 文件中定义的 Start-Class
public abstract class ExecutableArchiveLauncher extends Launcher {
private final Archive archive;
public ExecutableArchiveLauncher() {
try {
this.archive = this.createArchive();
} catch (Exception var2) {
throw new IllegalStateException(var2);
}
}
protected ExecutableArchiveLauncher(Archive archive) {
this.archive = archive;
}
protected final Archive getArchive() {
return this.archive;
}
protected String getMainClass() throws Exception {
Manifest manifest = this.archive.getManifest();
String mainClass = null;
if(manifest != null) {
mainClass = manifest.getMainAttributes().getValue("Start-Class");
}
if(mainClass == null) {
throw new IllegalStateException("No \'Start-Class\' manifest entry specified in " + this);
} else {
return mainClass;
}
}
protected List<Archive> getClassPathArchives() throws Exception {
ArrayList archives = new ArrayList(this.archive.getNestedArchives(new EntryFilter() {
public boolean matches(Entry entry) {
return ExecutableArchiveLauncher.this.isNestedArchive(entry);
}
}));
this.postProcessClassPathArchives(archives);
return archives;
}
protected abstract boolean isNestedArchive(Entry var1);
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
}
}
而 ExecutableArchiveLauncher 类又继承了 Launcher,而 Launcher 类中会实例化一个 MainMethodRunner 类,所以,我们项目中定义的 main 函数最张是由 MainMethodRunner 类来执行的,代码如下,通过反射的方式调用 main 函数。
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});
}
}