Spring Boot项目的真实程序入口

来源:互联网 发布:淘宝买的战地1激活码 编辑:程序博客网 时间:2024/05/16 21:39

基于 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.0Implementation-Title: kiff-starterImplementation-Version: 1.0-SNAPSHOTArchiver-Version: Plexus ArchiverBuilt-By: zouzhiyuanImplementation-Vendor-Id: *.*.*Spring-Boot-Version: 1.5.8.RELEASEImplementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncherStart-Class: *.*.*.KiApplicationSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Created-By: Apache Maven 3.0.5Build-Jdk: 1.8.0_51Implementation-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});    }}




原创粉丝点击