Many beginners will be confused about how Spring Boot packages the application code and all its dependencies into a single Jar, because traditional Java projects packaged into jars need to specify the dependencies through the -classpath property to run. We will analyze and explain the startup principle of SpringBoot today.

Spring Boot packages plug-ins

The spring-boot-Maven-plugin plugin for maven projects is provided as follows:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
Copy the code

Spring Boot projects can be easily jar packages. This eliminates the need to deploy Web server containers like Tomcat, Jetty, and so on.

The target directory contains two jar packages:

Springboot-0.0.1 – snapshot. jar is a package plug-in provided by Spring Boot that is formatted as a Fat JAR containing all dependencies.

Springboot-0.0.1 – snapshot.jar. original is a Java native package that contains only the content of the project itself.

Organizational structure of SpringBoot FatJar

The structure of the Spring Boot executable Jar is as follows:

  • Boot-inf: contains our project code (the classes directory) and any required dependencies (the lib directory);
  • Meta-inf directory: YesMANIFEST.MFThe file provides metadata for the Jar package and declares the Jar startup class;
  • org.springframework.boot.loader: Spring Boot loader code, implementation of Jar in Jar loading magic source.

As you can see, if you remove the boot-INF directory, this is a very normal and standard Jar package, including META information and executable code parts, whose/meta-INF/mainfex.mf specifies the BOOT META information of the Jar package, . Org. Springframework. The boot loader perform corresponding logic operations.

MAINFEST. MF meta information

The meta information is as follows:

Manifest-version: 1.0 Spring-boot-classpath -Index: boot-INF/Classpath. Idx implementation-title: Springboot implementation-version: 0.0.1 -snapshot spring-boot-layers-index: boot-INF /layers.idx start-class: com.listenvision.SpringbootApplication Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: Boot-inf /lib/ build-jdk-spec: 1.8 spring-boot-version: 2.5.6 Created-By: Maven Jar Plugin 3.2.0 main-class: org.springframework.boot.loader.JarLauncherCopy the code

It is equivalent to a Properties configuration file, with each line being a configuration item. Let’s focus on two configuration items:

  • Main-class configuration item: startup Class of the JAR package specified by Java. Set it as the JarLauncher Class of the Spring-boot-Loader project to start the Spring Boot application.
  • Start-class configuration item: The main Boot Class specified by Spring Boot, in this case set to the Application Class we defined.
  • Spring-boot-classes configuration item: Specifies the entry to load application Classes.
  • Spring-boot-lib: specifies the library that the application depends on to load.

Start the principle

The startup principle of Spring Boot is shown in the following figure:

Source code analysis

JarLauncher

The JarLauncher class is the launcher class for the Spring Boot JAR package. The complete class diagram is as follows:

The WarLauncher class is the Boot class for the Spring Boot War package. Start class org. Springframework. Boot. Loader. JarLauncher is not for the project introduced in class, but the spring – the boot – maven – repackage the plugin plug-ins add in.

Next, let’s take a look at the source code of JarLauncher, which is relatively simple, as shown below:

public class JarLauncher extends ExecutableArchiveLauncher {

    private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx";
    
    static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
        if (entry.isDirectory()) {
            return entry.getName().equals("BOOT-INF/classes/");
        }
        return entry.getName().startsWith("BOOT-INF/lib/");
    };
    
    public JarLauncher(a) {}protected JarLauncher(Archive archive) {
        super(archive);
    }
    
    @Override
    protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
        // Only needed for exploded archives, regular ones already have a defined order
        if (archive instanceof ExplodedArchive) {
            String location = getClassPathIndexFileLocation(archive);
            return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location);
        }
        return super.getClassPathIndex(archive);
    }
    
   
    private String getClassPathIndexFileLocation(Archive archive) throws IOException { Manifest manifest = archive.getManifest(); Attributes attributes = (manifest ! =null)? manifest.getMainAttributes() :null; String location = (attributes ! =null)? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) :null;
        return(location ! =null)? location : DEFAULT_CLASSPATH_INDEX_LOCATION; }@Override
    protected boolean isPostProcessingClassPathArchives(a) {
        return false;
    }
    
    @Override
    protected boolean isSearchCandidate(Archive.Entry entry) {
        return entry.getName().startsWith("BOOT-INF/");
    }
    
    @Override
    protected boolean isNestedArchive(Archive.Entry entry) {
        return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
    }
    
    public static void main(String[] args) throws Exception {
        // Invoke the launch method defined by the base class Launcher
        newJarLauncher().launch(args); }}Copy the code

Basically see its main method, call the base class of the definition of the Launcher launch method, and the Launcher is ExecutableArchiveLauncher parent class. Let’s look at the source of the base class Launcher:

Launcher

public abstract class Launcher {
    private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher";
    
    protected void launch(String[] args) throws Exception {
        if(! isExploded()) { JarFile.registerUrlProtocolHandler(); } ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); String jarMode = System.getProperty("jarmode"); String launchClass = (jarMode ! =null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass();
        launch(args, launchClass, classLoader);
    }
    
    @Deprecated
    protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
        return createClassLoader(archives.iterator());
    }
    
    protected ClassLoader createClassLoader(Iterator<Archive> archives) throws Exception {
        List<URL> urls = new ArrayList<>(50);
        while (archives.hasNext()) {
            urls.add(archives.next().getUrl());
        }
        return createClassLoader(urls.toArray(new URL[0]));
    }
    
    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
        return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
    }
    
    protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception {
        Thread.currentThread().setContextClassLoader(classLoader);
        createMainMethodRunner(launchClass, args, classLoader).run();
    }
    
    protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) {
        return new MainMethodRunner(mainClass, args);
    }
    protected abstract String getMainClass(a) throws Exception;
    
    protected Iterator<Archive> getClassPathArchivesIterator(a) throws Exception {
        return getClassPathArchives().iterator();
    }
    
    @Deprecated
    protected List<Archive> getClassPathArchives(a) throws Exception {
        throw new IllegalStateException("Unexpected call to getClassPathArchives()");
    }
    
    protected final Archive createArchive(a) throws Exception { ProtectionDomain protectionDomain = getClass().getProtectionDomain(); CodeSource codeSource = protectionDomain.getCodeSource(); URI location = (codeSource ! =null)? codeSource.getLocation().toURI() :null; String path = (location ! =null)? location.getSchemeSpecificPart() :null;
        if (path == null) {
            throw new IllegalStateException("Unable to determine code source archive");
        }
        File root = new File(path);
        if(! root.exists()) {throw new IllegalStateException("Unable to determine code source archive from " + root);
        }
        return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
    }
    
    protected boolean isExploded(a) {
        return false;
    }
    
    protected Archive getArchive(a) {
        return null; }}Copy the code
  1. The launch method first creates the class loader and then determines if the JAR has set the jarmode property in the manifest.mf file.

  2. If not, the launchClass value is returned from getMainClass(), which is implemented by the PropertiesLauncher subclass and returns the start-class property value configured in manifest.mf.

  3. Call the createMainMethodRunner method to build a MainMethodRunner object and call its run method.

PropertiesLauncher

@Override
protected String getMainClass(a) throws Exception {
    // Load the manifest. MF file in the jar package target directory to find the Start Class of SpringBoot
    String mainClass = getProperty(MAIN, "Start-Class");
    if (mainClass == null) {
        throw new IllegalStateException("No '" + MAIN + "' or 'Start-Class' specified");
    }
    return mainClass;
}
Copy the code

MainMethodRunner

Target Class the main method of actuators, the mainClassName assigned for the MANIFEST. The MF configured in the Start – Class attribute value, namely com. Listenvision. SpringbootApplication, Spring Boot is then started by reflecting the Main method that executes Spring BootApplication.

public class MainMethodRunner {
    private final String mainClassName;
    private final String[] args;
    public MainMethodRunner(String mainClass, String[] args) {
        this.mainClassName = mainClass;
        this.args = (args ! =null)? args.clone() :null;
    }
    public void run(a) throws Exception { Class<? > mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader());
        Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
        mainMethod.setAccessible(true);
        mainMethod.invoke(null.new Object[] { this.args }); }}Copy the code

conclusion

  1. The JAR package is similar to the zip file, except with a meta-INF/manifest.mf file, which is automatically created when the JAR package is built.

  2. Spring Boot provides a plug-in spring-boot-Maven-plugin that packages programs into an executable JAR package.

  3. Use java-jar to start the JAR package of Spring Boot. The first entry class is JarLauncher, and the MainMethodRunner object is constructed after the launch of Launcher is called internally. The boot effect is finally achieved by calling the main method of SpringbootApplication by reflection.