SpringBoot plugin spring-boot-maven-plugin principle, and jar package deployment of SpringBoo project slimming practice

spring-boot-maven-plugin

We directly use maven package (the package packaging function that comes with maven). When packaging the Jar package, the Jar package that the project depends on will not be entered together. When using java -jar When the command starts the project, an error will be reported, and the project cannot be started normally. At this time, we can consider referencing the spring-boot-maven-plugin plugin to create a Jar package for the project.

In the pom.xml of the maven project, the following plug-in is added. When running maven package for packaging, it will be packaged into a JAR (fat jar) file that can be run directly , use the java -jar command to run it directly.

Note: If your project does not inherit the spring-boot-starter-parent POM, you need to do the following configuration to bind the target to repackage.

<build>
<plugins>
<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
</plugins>
</build>

By default, the packaging command of the maven project will not package the dependent jar packages when packaging the Jar package, but the spring-boot-maven-plugin plug-in will package all the dependent jar packages. For example, the BOOT/INF/lib directory of the jar package generated by using the spring-boot-maven-plugin plug-in below contains all dependent jar packages:

After introducing the spring-boot-maven-plugin plug-in, when using the packaging function, the jar or war generated by the mvn package will be repackaged into an executable file, and the original file name will be modified and the .origin suffix will be added.

Internal structure of executable Jar package

Unzip the executable Jar and we can see the following structure:

Executable jar directory structure

├─BOOT-INF
│ ├─classes
│ └─lib
├─META-INF
│ ├─maven
│ ├─app.properties
│ ├─MANIFEST.MF
└─org
    └─spring framework
        └─boot
            └─loader
                ├─archive
                ├─data
                ├─jar
                └─util

Structure comparison

From the above file structure and jar list content, the fatjar packaged by Spring Boot has the following main differences compared with the source jar:

  • The .class files of the main project in the source jar are moved to the BOOT-INF/classes folder of the fatjar.
  • Added BOOT-INF/lib folder, which stores three-party jar files.
  • Added BOOT-INF/classpath.idx to record the loading order of classpath.
  • Add the org/springframework/boot/loader folder, which is the compiled .class file of spring-boot-loader.
  • Add the following attributes to the manifest file MANIFEST.MF:
  • Spring-Boot-Classpath-Index: Record the address of the classpath.idx file.
  • Start-Class: Specify the startup class of Spring Boot.
  • Spring-Boot-Classes: Record the storage path of the .class file of the main project.
  • Spring-Boot-Lib: Record the storage path of the three-party jar file.
  • Spring-Boot-Version: Record Spring Boot version information
  • Main-Class: Specifies the entry class of the jar program (the executable jar is the org.springframework.boot.loader.JarLauncher class).

Let’s focus on two places first: the Jar package description file under META-INF and the BOOT-INF directory.

Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: xxxx
Start-Class: com.xxxx.AppServer
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Version: 2.1.6.RELEASE
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_73
Main-Class: org.springframework.boot.loader.JarLauncher

Above we see a familiar configuration Main-Class: org.springframework.boot.loader.JarLauncher. We can probably guess that this class is the entry point of the entire system.

Look under the BOOT-INF directory again, we will find that there are class files typed out by our project and Jar packages that the project depends on. Seeing this, you may have guessed how Spring Boot started the project.

How does Spring Boot start the project

JarLauncher

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

@Override
protected boolean isNestedArchive(Archive.Entry entry) {
if (entry. isDirectory()) {
return entry.getName().equals(BOOT_INF_CLASSES);
}
return entry.getName().startsWith(BOOT_INF_LIB);
}

public static void main(String[] args) throws Exception {
        //Project entry, the focus is on the launch method
new JarLauncher().launch(args);
}
}
//launch method
protected void launch(String[] args) throws Exception {
    JarFile.registerUrlProtocolHandler();
    //Create LaunchedURLClassLoader. If the root class loader and the extended class loader are not loaded into a certain class, the class will be loaded through the LaunchedURLClassLoader loader. This loader will load classes from the class directory and lib directory under Boot-INF.
    ClassLoader classLoader = createClassLoader(getClassPathArchives());
    //This method will read the Start-Class attribute in the jar description file, and then call the main method of this class through reflection.
    launch(args, getMainClass(), classLoader);
}

A brief summary

  • The entry point of the Spring Boot executable Jar package is the main method of JarLauncher;

  • The execution logic of this method is to create a LaunchedURLClassLoader first, and the logic of this loader to load classes is:

    First determine whether the root class loader and the extended class loader can load a certain class, and if they cannot be loaded, load it from the class and lib directories under Boot-INF;

  • Read the Start-Class attribute, and call the main method of the startup class through the reflection mechanism, so that the main method of the Spring Boot main startup class we developed is successfully called.

Goal

The spring-boot-maven-plugin official document introduces five goals, which are as follows:

  • spring-boot:build-image: Use buildpack to package the program into the container image.
  • spring-boot:build-info: Generate a build-info.properties file based on the content of the current MavenProject
  • spring-boot:help: Display help information. Call mvn spring-boot:help -Ddetail=true -Dgoal= to display parameter details.
  • spring-boot:repackage: The default goal is to repackage the jar packaged by ordinary mvn package into an executable jar/war file containing all program dependencies, and keep the jar packaged by mvn package as the .original suffix
  • spring-boot:run: Run a Spring Boot application.
  • spring-boot:start: Usually used in integration testing scenarios to manage the lifecycle of Spring Boot applications during the mvn integration-test phase.
  • spring-boot:stop: Stops an application that has been started via the start target. Typically called after integration-test has completed.
    The article only analyzes repackage, let’s look directly at the source code structure of spring-boot-maven-plugin:

spring-boot-maven-plugin: exclude provided dependencies when packaging

The spring-boot-maven-plugin plugin provides maven packaging support for spring boot. Dependencies whose scope is provided in the project, such as lombok, mybatis-plus, etc., are only used in the compilation phase, and you can retire after the compilation is completed. When spring maven is packaged, provided dependencies will be excluded from the package, but when springboot maven is packaged, these dependencies will also be included in the lib-provided folder of the war package or the lib folder of the jar package.

The command to build a jar package or war package in the springboot project is repackage, which acts on the package phase of the maven life cycle. After the mvn package is executed, this command is packaged again to generate an executable package. For example, when a jar package is created, an executable jar package is generated. At the same time, rename the jar generated by mvn package to *.origin. By default, repackage will package any dependencies introduced in the project into the package.

Taking lombok as an example, the official provides the following methods to exclude provided dependencies from the springboot project package.

1. Exclude a specific dependency by specifying groupId and artifactId

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <configuration>
               <excludes>
                   <exclude>
                       <groupId>org.projectlombok</groupId>
                       <artifactId>lombok</artifactId>
                   </exclude>
               </excludes>
           </configuration>
       </plugin>
   </plugins>
</build>

2. Specify groupId to exclude all dependencies related to groupId

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <configuration>
               <excludeGroupIds>org.projectlombok</excludeGroupIds>
           </configuration>
       </plugin>
   </plugins>
</build>

2. Exclude dependencies according to the configuration file

 <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <property>
                    <name>environment</name>
                    <value>dev</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.7.12</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                            <excludes>
                                <exclude>
                                    <groupId>org.projectlombok</groupId>
                                    <artifactId>lombok</artifactId>
                                </exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>prod</id>
            <activation>
                <property>
                    <name>environment</name>
                    <value>prod</value>
                </property>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.7.12</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                        <configuration>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

mvn clean package -Pdev Use the dev configuration file to remove lombok dependencies

The jar package deployed by SpringBoot project is thinner

Through the above analysis, there are two folders, classes and lib, under the fat jar. Our compiled code is placed in the classes, and the jar packages we depend on are placed in the lib folder.

The classes part is very small (mine is about 160kb), the lib part is very large (mine is about 70M), so the upload is very slow

Then we can separate and upload the part of the code we wrote ourselves and the part of the maven jar package we depend on. We only need to upload the part of the code we wrote ourselves

Normal packaging

First of all, the packaging method in the pom.xml file of our project is as follows:

 <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

This is the default packaging method in SpringBoot. We first package it in this way to get a jar package. We decompress the jar package. If it cannot be decompressed directly, change the suffix to zip and then decompress it. We only need to get BOOT-INF is enough

Change the packaging method

We do some configuration on the default packaging method in SpringBoot

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <mainClass>com.Application</mainClass>
                <layout>ZIP</layout>
                <includes>
                    <include>
                        <groupId>nothing</groupId>
                        <artifactId>nothing</artifactId>
                    </include>
                </includes>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
  • mainClass, we specify the startup class of the project
  • layout, we specified the packaging method as ZIP, note: it must be uppercase
  • includes, has its own dependent jar, which can be imported here
  • repackage, remove other dependencies, only need to keep the simplest structure

Pack again

We click maven package again to get a jar package, and we can see that the jar package at this time is only 167kb (originally 70M + )

Upload started

We upload the lib directory and the final packaged slimming project jar package to the same directory on the server

use command

nohup java -Dloader.path=./lib -jar ./sbm-0.0.1-SNAPSHOT.jar &
  • -Dloader.path, tell the location of the maven jar package it depends on
  • admin4j-example-1.0.jar, the name of the project jar package
  • nohup, & amp;, make the jar package run in the service background

Summary

Use thin deployment to facilitate each iterative update, without uploading a large jar package every time, thus saving deployment time. But every time you depend on the version of the jar package or there is a new addition, you need to change the data in the lib directory