At present, the Spring Boot project has the following two common deployment methods
- One is to use the docker container to deploy. Build the Spring Boot application into a docker image, and then start the image through the container. This method is very convenient when large-scale applications need to be deployed and extended. It belongs to the current industrial deployment scheme, but it needs to master docker's ecosystem technology.
- The other is to use FatJar to deploy and start directly (a jar and its dependent third-party jars are all typed into a package, which is called FatJar). This is a simple application deployment method for many beginners or small-scale cases.
Advantages of Assembly
The FatJar deployment scheme described above has some defects. Because if we directly build a FatJar of Spring Boot
If it is deployed by the operation and maintenance personnel, the entire configuration file is hidden in the jar
In, it becomes very difficult to modify the configuration file for different environments. If the environment is uncertain, or the project needs to be started by the startup script, this can be done directly through jar
There will be a lot of work to be done in the follow-up.
The two problems mentioned above can be solved by packaging Spring Boot as a service through assembly
- Enables Spring Boot to load configuration files outside the jar.
- Provide a service-oriented startup script, which is generally a shell or bat under windows. With the application service script of Spring Boot, you can easily start and stop the application of Spring Boot.
Project configuration
Add plug-in
(1) Edit the project's POM XML file and add the assembly packaging plug-in.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<!--The main use is maven Provided assembly Plug in complete-->
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptors>
<!--Specific configuration file-->
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!--Bind to maven Operation type-->
<phase>package</phase>
<!--Run once-->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
(2) As can be seen from the above code, put the assembly configuration in the main/assembly directory (the files in the specific directory will be created next).
Packaging and deploying Spring Boot projects using Assembly
Script service start / stop
First, create a bin folder in the assembly directory, and then create a start SH file, this is linux
The specific contents of the startup script in the environment are as follows.
Tip: the project name and jar package name at the beginning need not be set manually. Parameter variables are used here. After the project is packaged, these parameters will be automatically replaced by the profiles of pom The value of properties (the assembly configuration file needs to enable the property replacement function). The same is true for the other two configuration files below.
#!/bin/bash
# entry name
SERVER_NAME="${project.artifactId}"
# jar name
JAR_NAME="${project.build.finalName}.jar"
# Enter the bin directory
cd `dirname $0`
# bin directory absolute path
BIN_DIR=`pwd`
# Return to the root directory path of the previous level project
cd ..
# Print the absolute path of the project root directory
# `pwd ` execute system commands and get results
DEPLOY_DIR=`pwd`
# The absolute directory of the external configuration file. If the directory needs / end, you can also specify the file directly
# If you specify a directory, spring will read all configuration files in the directory
CONF_DIR=$DEPLOY_DIR/config
# SERVER_PORT=`sed '/server.port/!d;s/.*=//' config/application.properties | tr -d '\r'`
# Get the port number of the application
SERVER_PORT=`sed -nr '/port: [0-9]+/ s/.*port: +([0-9]+).*/\1/p' config/application.yml`
PIDS=`ps -f | grep java | grep "$CONF_DIR" |awk '{print $2}'`
if [ "$1" = "status" ]; then
if [ -n "$PIDS" ]; then
echo "The $SERVER_NAME is running...!"
echo "PID: $PIDS"
exit 0
else
echo "The $SERVER_NAME is stopped"
exit 0
fi
fi
if [ -n "$PIDS" ]; then
echo "ERROR: The $SERVER_NAME already started!"
echo "PID: $PIDS"
exit 1
fi
if [ -n "$SERVER_PORT" ]; then
SERVER_PORT_COUNT=`netstat -tln | grep $SERVER_PORT | wc -l`
if [ $SERVER_PORT_COUNT -gt 0 ]; then
echo "ERROR: The $SERVER_NAME port $SERVER_PORT already used!"
exit 1
fi
fi
# Project log output absolute path
LOGS_DIR=$DEPLOY_DIR/logs
# If the logs folder does not exist, create a folder
if [ ! -d $LOGS_DIR ]; then
mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/catalina.log
# JVM Configuration
JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true "
JAVA_DEBUG_OPTS=""
if [ "$1" = "debug" ]; then
JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n "
fi
JAVA_JMX_OPTS=""
if [ "$1" = "jmx" ]; then
JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false "
fi
JAVA_MEM_OPTS=""
BITS=`java -version 2>&1 | grep -i 64-bit`
if [ -n "$BITS" ]; then
JAVA_MEM_OPTS=" -server -Xmx512m -Xms512m -Xmn256m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70 "
else
JAVA_MEM_OPTS=" -server -Xms512m -Xmx512m -XX:PermSize=128m -XX:SurvivorRatio=2 -XX:+UseParallelGC "
fi
# Load configuration of external log4j2 file
LOG_IMPL_FILE=log4j2.xml
LOGGING_CONFIG=""
if [ -f "$CONF_DIR/$LOG_IMPL_FILE" ]
then
LOGGING_CONFIG="-Dlogging.config=$CONF_DIR/$LOG_IMPL_FILE"
fi
CONFIG_FILES=" -Dlogging.path=$LOGS_DIR $LOGGING_CONFIG -Dspring.config.location=$CONF_DIR/ "
echo -e "Starting the $SERVER_NAME ..."
nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS $CONFIG_FILES -jar $DEPLOY_DIR/lib/$JAR_NAME > $STDOUT_FILE 2>&1 &
COUNT=0
while [ $COUNT -lt 1 ]; do
echo -e ".\c"
sleep 1
if [ -n "$SERVER_PORT" ]; then
COUNT=`netstat -an | grep $SERVER_PORT | wc -l`
else
COUNT=`ps -f | grep java | grep "$DEPLOY_DIR" | awk '{print $2}' | wc -l`
fi
if [ $COUNT -gt 0 ]; then
break
fi
done
echo "OK!"
PIDS=`ps -f | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'`
echo "PID: $PIDS"
echo "STDOUT: $STDOUT_FILE"
Then create a stop SH file, which is the stop script in linux environment. The specific contents are as follows.
#!/bin/bash
# entry name
APPLICATION="${project.artifactId}"
# Project startup jar package name
APPLICATION_JAR="${project.build.finalName}.jar"
# Find PI by project name and kill -9 pid
PID=$(ps -ef | grep "${APPLICATION_JAR}" | grep -v grep | awk '{ print $2 }')
if [[ -z "$PID" ]]
then
echo ${APPLICATION} is already stopped
else
echo kill ${PID}
kill -9 ${PID}
echo ${APPLICATION} stopped successfully
fi
Finally, create a start Bat file, which is the startup script under Windows environment. The specific contents are as follows.
echo off
set APP_NAME=${project.build.finalName}.jar
set LOG_IMPL_FILE=log4j2.xml
set LOGGING_CONFIG=
if exist ../config/%LOG_IMPL_FILE% (
set LOGGING_CONFIG=-Dlogging.config=../config/%LOGGING_CONFIG%
)
set CONFIG= -Dlogging.path=../logs %LOGGING_CONFIG% -Dspring.config.location=../config/
set DEBUG_OPTS=
if ""%1"" == ""debug"" (
set DEBUG_OPTS= -Xloggc:../logs/gc.log -verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../logs
goto debug
)
set JMX_OPTS=
if ""%1"" == ""jmx"" (
set JMX_OPTS= -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9888 -Dcom.sun.management.jmxremote.ssl=FALSE -Dcom.sun.management.jmxremote.authenticate=FALSE
goto jmx
)
echo "Starting the %APP_NAME%"
java -Xms512m -Xmx512m -server %DEBUG_OPTS% %JMX_OPTS% %CONFIG% -jar ../lib/%APP_NAME%
echo "java -Xms512m -Xmx512m -server %DEBUG_OPTS% %JMX_OPTS% %CONFIG% -jar ../lib/%APP_NAME%"
goto end
:debug
echo "debug"
java -Xms512m -Xmx512m -server %DEBUG_OPTS% %CONFIG% -jar ../lib/%APP_NAME%
goto end
:jmx
java -Xms512m -Xmx512m -server %JMX_OPTS% %CONFIG% -jar ../lib/%APP_NAME%
goto end
:end
pause
Create packaging profile
Finally, we create an assembly. EXE file in the assembly folder XML configuration file, as follows.
<assembly>
<!--
It must be written, otherwise there will be a problem when packing assembly ID must be present and non-empty error
This name will eventually be appended to the end of the package name, such as the name of the project hangge-test-0.0.1-SNAPSHOT,
The final generated package name is hangge-test-0.0.1-SNAPSHOT-bin.tar.gz
-->
<id>bin</id>
<!-- Type of package, if any N Yes, I will N Packages of type -->
<formats>
<format>tar.gz</format>
<!--<format>zip</format>-->
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<!--File settings-->
<fileSets>
<!--
0755->That is, the user has read/write/Execution permission. Group users and other users have read-write permission;
0644->That is, the user has read-write permission, and the group user and other users have read-only permission;
-->
<!-- take src/main/assembly/bin All files in the directory are exported to the packaged directory bin In the directory -->
<fileSet>
<directory>src/main/assembly/bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
<!--If it is a script, be sure to change it to unix.If it's in windows The code above will appear dos Writing questions-->
<lineEnding>unix</lineEnding>
<filtered>true</filtered><!-- Attribute replacement -->
</fileSet>
<!-- take src/main/assembly/config All files in the directory are exported to the packaged directory config In the directory -->
<fileSet>
<directory>src/main/assembly/config</directory>
<outputDirectory>config</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
<!-- take src/main/resources Package next configuration file to config catalogue -->
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>/config</outputDirectory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.yml</include>
</includes>
<filtered>true</filtered><!-- Attribute replacement -->
</fileSet>
<!-- Start project jar Pack to lib In the directory -->
<fileSet>
<directory>target</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<!-- Package project description documents into docs In the directory -->
<fileSet>
<directory>.</directory>
<outputDirectory>docs</outputDirectory>
<includes>
<include>*.md</include>
</includes>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>docs</directory>
<outputDirectory>docs</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>src/main/assembly/docs</directory>
<outputDirectory>docs</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
</fileSets>
</assembly>
Packaging test
Package project
- We use the mvn package command to package the project.
- After packaging, a file named XXX will be generated under target tar. GZ compressed file.
- After decompressing the compressed package, you can see the following directories contained inside.
Start service
- After unpacking the above package files, there are the following startup files in the bin directory
- Linux and macOS systems: execute start SH starts the service and executes stop SH stop service.
- Windows system: double click start Bat to start the service
- After the service is started, the corresponding log files will be generated into the logs directory (the logs directory will be created automatically)
Modify configuration
- Modify the configuration file under the config folder. The configuration file here is application properties.
- Here we change the service port to 9090.
server.port=9090
- After restarting the service, you can see that the port has indeed changed, indicating that the external configuration file has been loaded successfully.
Package projects separately from dependencies
So far, when using assembly to package the project described above, the project code and all dependent files of the project will be packaged into an executable jar package.
If the project has many dependent packages, the file will be very large. If you have to upload the entire jar package for each release, it will waste both bandwidth and time.
The following describes how to package the external dependencies of the project separately from your own code package, so that when the project is modified, you only need to upload and overwrite the modified package.
Modify configuration
- First, we edit assembly XML configuration file. On the basis of the previous article, the third-party dependency setting is added to add the third-party jar package to the lib directory in the compressed package.
<assembly>
<!--
It must be written, otherwise there will be a problem when packing assembly ID must be present and non-empty error
This name will eventually be appended to the end of the package name, such as the name of the project hangge-test-0.0.1-SNAPSHOT,
The final generated package name is hangge-test-0.0.1-SNAPSHOT-bin.tar.gz
-->
<id>bin</id>
<!-- Type of package, if any N Yes, I will N Packages of type -->
<formats>
<format>tar.gz</format>
<!--<format>zip</format>-->
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<!--Third party dependency settings-->
<dependencySets>
<dependencySet>
<!-- Not using project artifact,Third party jar Don't unpack it, pack it in zip Document lib catalogue -->
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
<!--File settings-->
<fileSets>
<!--
0755->That is, the user has read/write/Execution permission. Group users and other users have read-write permission;
0644->That is, the user has read-write permission, and the group user and other users have read-only permission;
-->
<!-- take src/main/assembly/bin All files in the directory are exported to the packaged directory bin In the directory -->
<fileSet>
<directory>src/main/assembly/bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
<!--If it is a script, be sure to change it to unix.If it's in windows The code above will appear dos Writing questions-->
<lineEnding>unix</lineEnding>
<filtered>true</filtered><!-- Attribute replacement -->
</fileSet>
<!-- take src/main/assembly/config All files in the directory are exported to the packaged directory config In the directory -->
<fileSet>
<directory>src/main/assembly/config</directory>
<outputDirectory>config</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
<!-- take src/main/resources Package next configuration file to config catalogue -->
<fileSet>
<directory>src/main/resources</directory>
<outputDirectory>/config</outputDirectory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.yml</include>
</includes>
<filtered>true</filtered><!-- Attribute replacement -->
</fileSet>
<!-- Start project jar Pack to lib In the directory -->
<fileSet>
<directory>target</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<!-- Package project description documents into docs In the directory -->
<fileSet>
<directory>.</directory>
<outputDirectory>docs</outputDirectory>
<includes>
<include>*.md</include>
</includes>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>docs</directory>
<outputDirectory>docs</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
<fileSet>
<directory>src/main/assembly/docs</directory>
<outputDirectory>docs</outputDirectory>
<fileMode>0644</fileMode>
</fileSet>
</fileSets>
</assembly>
- Then edit the project's POM XML file, which was previously packaged with spring boot Maven plugin. This plug-in will put all the dependencies of the project into the project jar package. We replace it with Maven jar plugin and set it up.
<build>
<plugins>
<!-- Specify the startup class to make the dependency external jar package -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<!-- Generated jar Do not include pom.xml and pom.properties These two documents -->
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<!-- Do you want to send the third party jar Add to class build path -->
<addClasspath>true</addClasspath>
<!-- External dependency jar Final location of package -->
<!-- Because we will be a third party jar And the project jar Put it in the same directory, which is used here./ -->
<classpathPrefix>./</classpathPrefix>
<!-- Project startup class -->
<mainClass>com.example.hanggetest.HanggeTestApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<!--The main use is maven Provided assembly Plug in complete-->
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptors>
<!--Specific configuration file-->
<descriptor>src/main/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!--Bind to maven Operation type-->
<phase>package</phase>
<!--Run once-->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>