一、基本介绍
1.部署方式介绍
目前来说,SpringBoot 项目有如下两种常见的部署方式:
- 一种是使用 docker 容器去部署。将 Spring Boot 的应用构建成一个 docker image,然后通过容器去启动镜像,这种方式在需要部署大规模的应用、以及应用扩展时是非常方便的,属于目前工业级的部署方案,但是需要掌握 docker 的生态圈技术。
- 另一种则是使用 FatJar 直接部署启动(将一个 jar 及其依赖的三方 jar 全部打到一个包中,这个包即为 FatJar),这是很多初学者或者极小规模情况下的一个简单应用部署方式。
2.使用 assembly 的优势
(1)上面介绍的 Fatjar 部署方案存在一些缺陷。因为我们如果直接构建一个 Spring Boot 的 FatJar 交由运维人员部署的话,整个配置文件都被隐藏到 jar 中,想要针对不同的环境修改配置文件就变成了一件很困难的事情。如果存在环境不确定,或者需要启动脚本启动项目的时候,这种直接通过 jar 的方式后续会需要处理很多工作。
(2)而通过 assembly 将 Spring Boot 服务化打包,便能解决上面提到的两个问题:
- 使得 Spring Boot 能够加载 jar 外的配置文件。
- 提供一个服务化的启动脚本,这个脚本一般是 shell 或者 windows 下的 bat ,有了 Spring Boot 的应用服务脚本后,就可以很容易的去启动和停止 Spring Boot 的应用了。
Maven Assembly插件地址:http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html
二、项目配置
1.添加插件
(1)编辑项目的 pom.xml 文件,加入 assembly 打包插件:
1<build> 2 <plugins> 3 <plugin> 4 <groupId>org.springframework.boot</groupId> 5 <artifactId>spring-boot-maven-plugin</artifactId> 6 </plugin> 7 <plugin> 8 <!--主要使用的是maven提供的assembly插件完成--> 9 <artifactId>maven-assembly-plugin</artifactId> 10 <version>3.1.1</version> 11 <configuration> 12 <descriptors> 13 <!--具体的配置文件--> 14 <descriptor>src/main/assembly/assembly.xml</descriptor> 15 </descriptors> 16 </configuration> 17 <executions> 18 <execution> 19 <id>make-assembly</id> 20 <!--绑定到maven操作类型上--> 21 <phase>package</phase> 22 <!--运行一次--> 23 <goals> 24 <goal>single</goal> 25 </goals> 26 </execution> 27 </executions> 28 </plugin> 29 </plugins> 30</build>
(2)从上面代码可以看出,我把 assembly 的配置都放在 main/assembly 目录下(具体目录里面的文件接下来会创建)。大家可以根据习惯使用其他目录,只要对上面配置做相应修改即可。

2.编写服务“启动/停止”脚本
(1)首先在 assembly 目录下创建一个 bin 文件夹,然后在该文件夹下创建一个 start.sh 文件,这个是 linux 环境下的启动脚本,具体内容如下:
注意:开头的项目名称、jar 包名称不用我们手动设置,这里使用参数变量,在项目打包后这些参数自动会替换为 pom 的 profiles 中 properties 的值(assembly 配置文件需要开启属性替换功能),下面另外两个配置文件也同理。
1#!/bin/bash 2 3# 项目名称 4SERVER_NAME="${project.artifactId}" 5 6# jar名称 7JAR_NAME="${project.build.finalName}.jar" 8 9# 进入bin目录 10cd `dirname $0` 11# bin目录绝对路径 12BIN_DIR=`pwd` 13# 返回到上一级项目根目录路径 14cd .. 15# 打印项目根目录绝对路径 16# `pwd` 执行系统命令并获得结果 17DEPLOY_DIR=`pwd` 18 19# 外部配置文件绝对目录,如果是目录需要/结尾,也可以直接指定文件 20# 如果指定的是目录,spring则会读取目录中的所有配置文件 21CONF_DIR=$DEPLOY_DIR/config 22# SERVER_PORT=`sed '/server.port/!d;s/.*=//' config/application.properties | tr -d '\r'` 23# 获取应用的端口号 24SERVER_PORT=`sed -nr '/port: [0-9]+/ s/.*port: +([0-9]+).*/\1/p' config/application.yml` 25 26PIDS=`ps -f | grep java | grep "$CONF_DIR" |awk '{print $2}'` 27if [ "$1" = "status" ]; then 28 if [ -n "$PIDS" ]; then 29 echo "The $SERVER_NAME is running...!" 30 echo "PID: $PIDS" 31 exit 0 32 else 33 echo "The $SERVER_NAME is stopped" 34 exit 0 35 fi 36fi 37 38if [ -n "$PIDS" ]; then 39 echo "ERROR: The $SERVER_NAME already started!" 40 echo "PID: $PIDS" 41 exit 1 42fi 43 44if [ -n "$SERVER_PORT" ]; then 45 SERVER_PORT_COUNT=`netstat -tln | grep $SERVER_PORT | wc -l` 46 if [ $SERVER_PORT_COUNT -gt 0 ]; then 47 echo "ERROR: The $SERVER_NAME port $SERVER_PORT already used!" 48 exit 1 49 fi 50fi 51 52# 项目日志输出绝对路径 53LOGS_DIR=$DEPLOY_DIR/logs 54# 如果logs文件夹不存在,则创建文件夹 55if [ ! -d $LOGS_DIR ]; then 56 mkdir $LOGS_DIR 57fi 58STDOUT_FILE=$LOGS_DIR/catalina.log 59 60# JVM Configuration 61JAVA_OPTS=" -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true " 62JAVA_DEBUG_OPTS="" 63if [ "$1" = "debug" ]; then 64 JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n " 65fi 66 67JAVA_JMX_OPTS="" 68if [ "$1" = "jmx" ]; then 69 JAVA_JMX_OPTS=" -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false " 70fi 71 72JAVA_MEM_OPTS="" 73BITS=`java -version 2>&1 | grep -i 64-bit` 74if [ -n "$BITS" ]; then 75 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 " 76else 77 JAVA_MEM_OPTS=" -server -Xms512m -Xmx512m -XX:PermSize=128m -XX:SurvivorRatio=2 -XX:+UseParallelGC " 78fi 79 80# 加载外部log4j2文件的配置 81LOG_IMPL_FILE=log4j2.xml 82LOGGING_CONFIG="" 83if [ -f "$CONF_DIR/$LOG_IMPL_FILE" ] 84then 85 LOGGING_CONFIG="-Dlogging.config=$CONF_DIR/$LOG_IMPL_FILE" 86fi 87CONFIG_FILES=" -Dlogging.path=$LOGS_DIR $LOGGING_CONFIG -Dspring.config.location=$CONF_DIR/ " 88echo -e "Starting the $SERVER_NAME ..." 89nohup java $JAVA_OPTS $JAVA_MEM_OPTS $JAVA_DEBUG_OPTS $JAVA_JMX_OPTS $CONFIG_FILES -jar $DEPLOY_DIR/lib/$JAR_NAME > $STDOUT_FILE 2>&1 & 90 91COUNT=0 92while [ $COUNT -lt 1 ]; do 93 echo -e ".\c" 94 sleep 1 95 if [ -n "$SERVER_PORT" ]; then 96 COUNT=`netstat -an | grep $SERVER_PORT | wc -l` 97 else 98 COUNT=`ps -f | grep java | grep "$DEPLOY_DIR" | awk '{print $2}' | wc -l` 99 fi 100 if [ $COUNT -gt 0 ]; then 101 break 102 fi 103done 104 105 106echo "OK!" 107PIDS=`ps -f | grep java | grep "$DEPLOY_DIR" | awk '{print $2}'` 108echo "PID: $PIDS" 109echo "STDOUT: $STDOUT_FILE"
(2)接着创建一个 stop.sh 文件,这个是 linux 环境下的停止脚本,具体内容如下:
1#!/bin/bash 2 3# 项目名称 4APPLICATION="${project.artifactId}" 5 6# 项目启动jar包名称 7APPLICATION_JAR="${project.build.finalName}.jar" 8 9# 通过项目名称查找到PI,然后kill -9 pid 10PID=$(ps -ef | grep "${APPLICATION_JAR}" | grep -v grep | awk '{ print $2 }') 11if [[ -z "$PID" ]] 12then 13 echo ${APPLICATION} is already stopped 14else 15 echo kill ${PID} 16 kill -9 ${PID} 17 echo ${APPLICATION} stopped successfully 18fi
(3)最后创建一个 start.bat 文件,这个是 Windows 环境下的启动脚本,具体内容如下:
1echo off 2 3set APP_NAME=${project.build.finalName}.jar 4set LOG_IMPL_FILE=log4j2.xml 5set LOGGING_CONFIG= 6if exist ../config/%LOG_IMPL_FILE% ( 7 set LOGGING_CONFIG=-Dlogging.config=../config/%LOGGING_CONFIG% 8) 9set CONFIG= -Dlogging.path=../logs %LOGGING_CONFIG% -Dspring.config.location=../config/ 10 11set DEBUG_OPTS= 12if ""%1"" == ""debug"" ( 13 set DEBUG_OPTS= -Xloggc:../logs/gc.log -verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=../logs 14 goto debug 15) 16 17set JMX_OPTS= 18if ""%1"" == ""jmx"" ( 19 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 20 goto jmx 21) 22 23echo "Starting the %APP_NAME%" 24java -Xms512m -Xmx512m -server %DEBUG_OPTS% %JMX_OPTS% %CONFIG% -jar ../lib/%APP_NAME% 25echo "java -Xms512m -Xmx512m -server %DEBUG_OPTS% %JMX_OPTS% %CONFIG% -jar ../lib/%APP_NAME%" 26goto end 27 28:debug 29echo "debug" 30java -Xms512m -Xmx512m -server %DEBUG_OPTS% %CONFIG% -jar ../lib/%APP_NAME% 31goto end 32 33:jmx 34java -Xms512m -Xmx512m -server %JMX_OPTS% %CONFIG% -jar ../lib/%APP_NAME% 35goto end 36 37:end 38pause
3.创建打包配置文件
最后,我们在 assembly 文件夹下创建一个 assembly.xml 配置文件,具体内容如下:
1<assembly> 2 <!-- 3 必须写,否则打包时会有 assembly ID must be present and non-empty 错误 4 这个名字最终会追加到打包的名字的末尾,如项目的名字为 hangge-test-0.0.1-SNAPSHOT, 5 则最终生成的包名为 hangge-test-0.0.1-SNAPSHOT-bin.tar.gz 6 --> 7 <id>bin</id> 8 <!-- 打包的类型,如果有N个,将会打N个类型的包 --> 9 <formats> 10 <format>tar.gz</format> 11 <!--<format>zip</format>--> 12 </formats> 13 <includeBaseDirectory>true</includeBaseDirectory> 14 <!--文件设置--> 15 <fileSets> 16 <!-- 17 0755->即用户具有读/写/执行权限,组用户和其它用户具有读写权限; 18 0644->即用户具有读写权限,组用户和其它用户具有只读权限; 19 --> 20 <!-- 将src/main/assembly/bin目录下的所有文件输出到打包后的bin目录中 --> 21 <fileSet> 22 <directory>src/main/assembly/bin</directory> 23 <outputDirectory>bin</outputDirectory> 24 <fileMode>0755</fileMode> 25 <!--如果是脚本,一定要改为unix.如果是在windows上面编码,会出现dos编写问题--> 26 <lineEnding>unix</lineEnding> 27 <filtered>true</filtered><!-- 是否进行属性替换 --> 28 </fileSet> 29 <!-- 将src/main/assembly/config目录下的所有文件输出到打包后的config目录中 --> 30 <fileSet> 31 <directory>src/main/assembly/config</directory> 32 <outputDirectory>config</outputDirectory> 33 <fileMode>0644</fileMode> 34 </fileSet> 35 <!-- 将src/main/resources下配置文件打包到config目录 --> 36 <fileSet> 37 <directory>src/main/resources</directory> 38 <outputDirectory>/config</outputDirectory> 39 <includes> 40 <include>**/*.xml</include> 41 <include>**/*.properties</include> 42 <include>**/*.yml</include> 43 </includes> 44 <filtered>true</filtered><!-- 是否进行属性替换 --> 45 </fileSet> 46 <!-- 将项目启动jar打包到lib目录中 --> 47 <fileSet> 48 <directory>target</directory> 49 <outputDirectory>lib</outputDirectory> 50 <includes> 51 <include>*.jar</include> 52 </includes> 53 </fileSet> 54 <!-- 将项目说明文档打包到docs目录中 --> 55 <fileSet> 56 <directory>.</directory> 57 <outputDirectory>docs</outputDirectory> 58 <includes> 59 <include>*.md</include> 60 </includes> 61 <fileMode>0644</fileMode> 62 </fileSet> 63 <fileSet> 64 <directory>docs</directory> 65 <outputDirectory>docs</outputDirectory> 66 <fileMode>0644</fileMode> 67 </fileSet> 68 <fileSet> 69 <directory>src/main/assembly/docs</directory> 70 <outputDirectory>docs</outputDirectory> 71 <fileMode>0644</fileMode> 72 </fileSet> 73 </fileSets> 74 </assembly>
三、打包测试
1.打包项目
(1)我们使用 mvn package 命令对项目进行打包:

(2)打包后在 target 下便会生成一个名为 xxx.tar.gz 的压缩文件:

(3)将这个压缩包解压后可以看到内部包含的目录如下:

2.启动服务
(1)其中 bin 目录有如下几个启动文件:
Linux、macOS 系统:执行 start.sh 启动服务,执行 stop.sh 停止服务。
Windows 系统:双击 start.bat 即可启动服务

(2)服务启动后,相应的日志文件会生成到 logs 目录下(logs 目录会自动创建):

3.修改配置
(1)我们修改 config 文件夹下面的配置文件,我这里的配置文件是 application.properties:

(2)这里我们将服务端口改成 9090:
server.port=9090
(3)重启服务,可以看到端口确实发生了变化,说明外部配置文件加载成功:
