使用Assembly打包和部署SpringBoot工程方式
1、Spring Boot项目的2种部署方式
目前来说,Spring Boot 项目有如下 2 种常见的部署方式。
1、一种是使用 docker 容器去部署。将 Spring Boot 的应用构建成一个 docker image,然后通过容器去启动镜像。这种方式在需要部署大规模的应用以及对应用进行扩展时,是非常方便的,属于目前工业级的部署方案,但是需要掌握 docker 的生态圈技术。
2、另一种则是使用 FatJar 直接部署启动(将一个 jar 及其依赖的三方 jar 全部打到一个包中,这个包即为 FatJar)。这是很多初学者或者极小规模情况下的一个简单应用部署方式。
2、Assembly 的优势
上面介绍的 Fatjar 部署方案存在以下缺陷。
1、如果直接构建一个 Spring Boot 的 FatJar 交由运维人员部署的话,整个配置文件都被隐藏到 jar 中,想要针对不同的环境修改配置文件就变成了一件很困难的事情。
2、如果需要启动脚本启动项目的时候,这种直接通过 jar 的方式后续会需要处理很多工作。
而通过 assembly 将 Spring Boot 服务化打包,便能解决上面提到的 2 个问题。
1、使得 Spring Boot 能够加载 jar 外的配置文件。
2、提供一个服务化的启动脚本,这个脚本一般是 shell 或者 windows 下的 bat ,有了 Spring Boot 的应用服务脚本后,就可以很容易的去启动和停止 Spring Boot 的应用了。
3、项目配置
3.1、添加插件
编辑项目的 pom.xml 文件,加入 assembly 打包插件。
<build> <!-- 指定需要打包编译的文件 --> <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> <plugins> <!-- 指定启动类,将依赖打成外部jar包 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.5</version> <configuration> <!-- 不打包配置文件 --> <excludes> <exclude>*.xml</exclude> <exclude>*.properties</exclude> <exclude>*.yml</exclude> </excludes> <archive> <!-- 生成的jar中,不要包含pom.xml和pom.properties这两个文件 --> <addMavenDescriptor>false</addMavenDescriptor> <manifest> <!-- 是否要把第三方jar加入到类构建路径 --> <addClasspath>true</addClasspath> <!-- 外部依赖jar包的最终位置 --> <classpathPrefix>../lib</classpathPrefix> <!-- 项目启动类 --> <mainClass>com.example.TestApplication</mainClass> </manifest> <manifestEntries> <!--MANIFEST.MF 中 Class-Path 加入配置文件目录--> <Class-Path>../config/</Class-Path> <Implementation-Title>${project.artifactId}</Implementation-Title> <Implementation-Version>${project.version}</Implementation-Version> <Build-Time>${maven.build.timestamp}</Build-Time> </manifestEntries> </archive> </configuration> </plugin> <!-- 拷贝配置文件到config目录下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>2.7</version> <executions> <execution> <id>copy-resources</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>*.xml</include> <include>*.properties</include> <include>*.yml</include> </includes> </resource> </resources> <outputDirectory>${project.build.directory}/config</outputDirectory> </configuration> </execution> </executions> </plugin> <!-- 将依赖jar包拷贝到lib目录下 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.8</version> <executions> <execution> <phase>prepare-package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/lib</outputDirectory> </configuration> </execution> </executions> </plugin> <!-- 打包插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4.1</version> <configuration> <finalName>${project.artifactId}</finalName> <appendAssemblyId>false</appendAssemblyId> <descriptors> <!--具体的配置文件--> <descriptor>src/main/assembly/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <!--绑定到maven操作类型上--> <phase>package</phase> <!--运行一次--> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <!-- 打包时跳过测试 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.17</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build>
从上面代码可以看出,把 assembly 的配置都放在 main/assembly 目录下(具体目录里面的文件接下来会创建)。
3.2、编写服务启动/停止/重启脚本
在 assembly 目录下创建一个 bin 文件夹,然后在该文件夹下创建 start.sh 文件,这个是 linux 环境下的启动脚本,具体内容如下。
Tip:开头的项目名称、jar 包名称不用我们手动设置,这里使用参数变量,在项目打包后这些参数自动会替换为 pom 的 profiles 中 properties 的值(assembly 配置文件需要开启属性替换功能),下面另外两个配置文件也同理。
#!/bin/bash # 项目名称 SERVER_NAME="${project.artifactId}" # jar名称 APPLICATION="${project.build.finalName}" # 进入bin目录 cd `dirname $0` # bin目录绝对路径 BIN_PATH=`pwd` # 返回到上一级项目根目录路径 cd .. # 打印项目根目录绝对路径 # `pwd` 执行系统命令并获得结果 BASE_PATH=`pwd` # 外部配置文件绝对目录,如果是目录需要/结尾,也可以直接指定文件 # 如果指定的是目录,spring则会读取目录中的所有配置文件 CONFIG_PATH=$BASE_PATH/config LOG_PATH=$BASE_PATH/logs/${SERVER_NAME} JAVA_OPT="-server -Xms1024m -Xmx1024m -Xmn512m" JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" APPLICATION_JAR=$(ls $BASE_PATH/boot/$APPLICATION.jar) PROCESS_ID=$(ps -ef|grep $BASE_PATH/boot/$APPLICATION|grep -v grep|awk '{print $2}') if [ ! -n "$PROCESS_ID" ];then echo "$SERVER_NAME服务,进程正在启动中,请稍等..." file="nohup.out" if [ ! -f "$file" ]; then touch "$file" fi source /etc/profile nohup java ${JAVA_OPT} -jar ${APPLICATION_JAR} > /dev/null & tail -f "${LOG_PATH}/system/info.log" else echo "$SERVER_NAME服务,进程(id:$PROCESS_ID)已存在,启动失败" fi
创建 stop.sh 文件,这个是 linux 环境下的停止脚本,具体内容如下。
#!/bin/bash # 项目名称 APPLICATION="${project.artifactId}" # 项目启动jar包名称 APPLICATION_JAR="${project.build.finalName}.jar" # 通过项目名称查找到PID,然后kill -9 pid PROCESS_ID=$(ps -ef|grep "${APPLICATION_JAR}" |grep -v grep|awk '{print $2}') if [[ -z "$PROCESS_ID" ]] then echo "$APPLICATION服务没有启动,请进一步验证是否需要停止进程" else echo "$APPLICATION服务,进程已存在,正在kill进程,进程ID:$PROCESS_ID" kill -9 ${PROCESS_ID} echo "$APPLICATION服务,进程$PROCESS_ID停止成功" fi
创建 restart.sh 文件,这个是 linux 环境下的重启脚本,具体内容如下。
#!/bin/bash # 项目名称 APPLICATION="${project.artifactId}" # 进入bin目录 cd `dirname $0` # bin目录绝对路径 BIN_PATH=`pwd` echo "$APPLICATION服务正在停止" sh $BIN_PATH/stop.sh echo "$APPLICATION服务正在重启" sh $BIN_PATH/start.sh
创建 server.sh 文件,这个是 linux 环境下根据指令执行服务启动、停止、重启、查看状态的脚本,具体内容如下。
#!/bin/bash # 项目名称 SERVER_NAME="${project.artifactId}" # jar名称 APPLICATION=${project.build.finalName} # 进入bin目录 cd `dirname $0` # bin目录绝对路径 BIN_PATH=`pwd` # 返回到上一级项目根目录路径 cd .. # 打印项目根目录绝对路径 # `pwd` 执行系统命令并获得结果 BASE_PATH=`pwd` # 外部配置文件绝对目录,如果是目录需要/结尾,也可以直接指定文件 CONFIG_PATH=$BASE_PATH/config APP_NAME=$BASE_PATH/boot/$APPLICATION.jar LOG_PATH=$BASE_PATH/logs/${SERVER_NAME} JAVA_OPT="-server -Xms512m -Xmx1024m -Xmn512m" JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" #判断用户是否为lbs loginUser() { USER=`whoami` if [ ${USER} != "lbs" ];then echo "Please use user 'lbs'" exit 1 fi } loginUser # 使用说明,用来提示输入参数 usage() { echo "Usage: sh server.sh [start|stop|restart|status]" exit 1 } # 检查程序是否在运行 is_exist() { # 根据关键字过滤进程PID,关键字由业务方自定义 pid=$(ps -ef | grep $APP_NAME | grep -v grep | awk '{print $2}') if [ -z "${pid}" ]; then return 1 else return 0 fi } # 启动程序 start() { # 启动程序时,可酌情根据各自服务启动条件做出相应的调整 is_exist if [ $? -eq "0" ]; then echo "${SERVER_NAME}服务进程已存在,pid=${pid}" else if [ ! -f "$LOG_PATH" ]; then touch "$LOG_PATH" fi source /etc/profile nohup java $JAVA_OPT -jar $APP_NAME >$LOG_PATH 2>&1 & # 应以非阻塞方式执行服务启动命令,避免脚本一直阻塞在这里无法退出 # 业务方应对其服务启动时间进行预估,如果从 命令下发到端口开启并对外提供服务 期间的时长超过了1分钟 # 那么业务方则需酌情在此处使用sleep来阻塞脚本,避免因启动时间过长导致持续交付系统误判 # 这个阻塞的时间按照各业务方不同服务自行设定 # 且执行启动命令后,相关的服务日志应存储到指定的文件 echo "${SERVER_NAME}服务进程启动成功" fi } # 停止程序 stop() { # 服务停止方式及具体方法由业务方指定,避免因直接kill掉进程而影响线上业务 # 并且确保stop函数执行结束后,服务进程不存在,避免影响后续操作 is_exist if [ $? -eq "0" ]; then # 如果服务需要平稳的停止,保证业务流无问题,那么可使用不限于循环等方式,保证stop执行后已经停止了该服务 # 否则后续操作可能会影响相关的业务,务必确保stop函数执行结果的准确性 kill -9 $pid echo "${SERVER_NAME}服务进程停止成功,pid=$pid" else echo "${SERVER_NAME}服务没有启动,请进一步验证是否需要停止进程" fi } # 程序状态 status() { is_exist if [ $? -eq "0" ]; then echo "${SERVER_NAME}服务进程正在运行,pid=${pid}" else echo "${SERVER_NAME}服务进程没有启动" fi } # 重启程序 restart() { stop start } # 主方法入口,接收参数可支持start\stop\status\restart\ case "$1" in "start") start ;; "stop") stop ;; "status") status ;; "restart") restart ;; *) usage ;; esac
创建 start.bat 文件,这个是 Windows 环境下的启动脚本,具体内容如下。
echo off :: 项目名称 set APP_NAME=${project.artifactId} :: jar名称 set APP_JAR=${project.build.finalName}.jar echo "开始启动服务 %APP_NAME%" java -Xms512m -Xmx512m -server -jar ../boot/%APP_JAR% echo "java -Xms512m -Xmx512m -server -jar ../boot/%APP_JAR%" goto end :end pause
3.3、创建打包配置文件
最后,我们在 assembly 文件夹下创建一个 assembly.xml 配置文件,具体内容如下。
<assembly> <!-- 必须写,否则打包时会有 assembly ID must be present and non-empty 错误 这个名字最终会追加到打包的名字的末尾,如项目的名字为 test-0.0.1-SNAPSHOT, 则最终生成的包名为 test-0.0.1-SNAPSHOT-assembly.tar.gz --> <id>assembly</id> <!-- 打包的类型,如果有N个,将会打N个类型的包 --> <formats> <format>tar.gz</format> <!--<format>zip</format>--> </formats> <includeBaseDirectory>true</includeBaseDirectory> <!--第三方依赖设置--> <dependencySets> <dependencySet> <!-- 不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录 --> <useProjectArtifact>false</useProjectArtifact> <outputDirectory>lib</outputDirectory> <unpack>false</unpack> </dependencySet> </dependencySets> <!--文件设置--> <fileSets> <!-- 0755->即用户具有读/写/执行权限,组用户和其它用户具有读写权限; 0644->即用户具有读写权限,组用户和其它用户具有只读权限; --> <!-- 将src/main/assembly/bin目录下的所有文件输出到打包后的bin目录中 --> <fileSet> <directory>${basedir}/src/main/assembly/bin</directory> <outputDirectory>bin</outputDirectory> <fileMode>0755</fileMode> <!--如果是脚本,一定要改为unix.如果是在windows上面编码,会出现dos编写问题--> <lineEnding>unix</lineEnding> <filtered>true</filtered><!-- 是否进行属性替换 --> </fileSet> <!-- 将src/main/resources下配置文件打包到config目录 --> <fileSet> <directory>${basedir}/src/main/resources</directory> <outputDirectory>/config</outputDirectory> <includes> <include>*.xml</include> <include>*.properties</include> <include>*.yml</include> </includes> <filtered>true</filtered><!-- 是否进行属性替换 --> </fileSet> <!-- 将第三方依赖打包到lib目录中 --> <fileSet> <directory>${basedir}/target/lib</directory> <outputDirectory>lib</outputDirectory> <fileMode>0755</fileMode> <includes> <include>*.jar</include> </includes> </fileSet> <!-- 将项目启动jar打包到boot目录中 --> <fileSet> <directory>${basedir}/target</directory> <outputDirectory>boot</outputDirectory> <includes> <include>${project.build.finalName}.jar</include> </includes> </fileSet> </fileSets> </assembly>
3.4、打包测试
配置修改完毕后,我们对项目进行打包。将生成的压缩包解压后可以发现,boot 文件夹下项目 jar 包和lib文件夹下第三方 jar 分开了,并且项目 jar 体积也十分小巧。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
解决ApplicationContext获取不到Bean的问题
这篇文章主要介绍了解决ApplicationContext获取不到Bean的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-06-06SpringBoot webSocket实现发送广播、点对点消息和Android接收
这篇文章主要介绍了SpringBoot webSocket实现发送广播、点对点消息和Android接收,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。2017-03-03
最新评论