Spring Boot项目的真实程序入口

基于 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 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 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});
    }
}




你可能感兴趣的:(Java)