Many beginners will be confused, how does Spring Boot package the application code and all dependencies into an independent Jar package, because after the traditional Java project is packaged into a Jar package, you need to specify the dependencies through the -classpath attribute to be able to run. Today we will analyze and explain the startup principle of SpringBoot.
Spring Boot packaging plugin
Spring Boot provides a maven project packaging plugin named spring-boot-maven-plugin
, as follows:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
You can easily package the Spring Boot project into a jar package. This way we no longer need to deploy web server containers like Tomcat, Jetty, etc.
Let's take a look at what the packaged structure of Spring Boot looks like. When we open the target directory, we find that there are two jar packages:
Among them, springboot-0.0.1-SNAPSHOT.jar
is a packaged plug-in provided by Spring Boot, which is marked as Fat Jar in a new format, including all dependencies;
And springboot-0.0.1-SNAPSHOT.jar.original
is generated by Java's native packaging method, and only contains the content of the project itself.
Organizational structure of SpringBoot FatJar
The structure after we expand the executable Jar from Spring Boot is as follows:
- BOOT-INF directory: contains our project code (classes directory) and required dependencies (lib directory);
- META-INF directory: The metadata of the Jar package is provided through the
MANIFEST.MF
file, and the startup class of the jar is declared; org.springframework.boot.loader
: Spring Boot's loader code, which implements the magic source of Jar in Jar loading.
We can see that if the BOOT-INF
directory is removed, it will be a very common and standard Jar package, including meta information and executable code parts. Its /META-INF/MAINFEST.MF
specifies the startup meta information of the Jar package, and org.springframework.boot.loader
performs the corresponding logical operation.
MAINFEST.MF meta information
The content of 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.JarLauncher
It is equivalent to a Properties configuration file, each line is a configuration item. Let's focus on two configuration items:
- Main-Class configuration item: The startup class of the jar package specified by Java, which is set to the JarLauncher class of the spring-boot-loader project to start the Spring Boot application.
- Start-Class configuration item: The main startup class specified by Spring Boot, which is set to the Application class we defined here.
- Spring-Boot-Classes configuration item: Specify the entry for loading application classes.
- Spring-Boot-Lib configuration item: Specify the library to load application dependencies.
Startup principle
The startup principle of Spring Boot is shown in the following figure:
Source code analysis
JarLauncher
The JarLauncher class is the startup class for the Spring Boot jar package. The complete class diagram is as follows:
The WarLauncher class is the startup class for the Spring Boot war package. The startup class org.springframework.boot.loader.JarLauncher
is not introduced into the project, but added by the spring-boot-maven-plugin
plugin repackage.
Next, let's take a look at the source code of JarLauncher, which is relatively simple, as shown in the following figure:
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() {
}
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() {
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 {
//调用基类 Launcher 定义的 launch 方法
new JarLauncher().launch(args);
}
}
It mainly depends on its main method, which calls the launch method defined by the base class Launcher, which is the parent class of ExecutableArchiveLauncher
. Let's take a look at the source code of the Launcher
base class:
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() throws Exception;
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
return getClassPathArchives().iterator();
}
@Deprecated
protected List<Archive> getClassPathArchives() throws Exception {
throw new IllegalStateException("Unexpected call to getClassPathArchives()");
}
protected final Archive createArchive() 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() {
return false;
}
protected Archive getArchive() {
return null;
}
}
- The launch method first creates the class loader and then determines whether the jar has the
jarmode
attribute set in theMANIFEST.MF
file. - If not set, the value of launchClass is returned from
getMainClass()
, which is implemented by a subclass ofPropertiesLauncher
and returns the value of theStart-Class
property configured in MANIFEST.MF. - Call
createMainMethodRunner
method, construct aMainMethodRunner
object and call its run method.
PropertiesLauncher
@Override
protected String getMainClass() throws Exception {
//加载 jar包 target目录下的 MANIFEST.MF 文件中 Start-Class配置,找到springboot的启动类
String mainClass = getProperty(MAIN, "Start-Class");
if (mainClass == null) {
throw new IllegalStateException("No '" + MAIN + "' or 'Start-Class' specified");
}
return mainClass;
}
MainMethodRunner
The executor of the main method of the target class, the mainClassName at this time is assigned the value of the Start-Class attribute configured in MANIFEST.MF, which is com.listenvision.SpringbootApplication
, and then the main method of SpringbootApplication is executed by reflection, so as to achieve the effect of starting Spring Boot.
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() 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 });
}
}
Summarize
- The jar package is similar to the zip compressed file, except that there is one more
META-INF/MANIFEST.MF
file than the zip file, which is automatically created when the jar package is built. - Spring Boot provides a plug-in spring-boot-maven-plugin, which is used to package the program into an executable jar package.
- Use java -jar to start the Spring Boot jar package. The first entry class to be called is
JarLauncher
, and theMainMethodRunner
object is built after internally calling the launch ofLauncher
, and finally the main method of SpringbootApplication is called by reflection to achieve the startup effect.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。