Spring Boot项目可以通过spring-boot-maven-plugin插件打包生成一个可执行的jar包,这样可以脱离web容器(例如tomcat)直接运行。但默认情况下spring-boot-maven-plugin打出来的包是一个fat jar,即将所有的依赖全部打进了jar包当中,这样的jar包体积很大,每次更新系统的时候都需要完整替换整个jar包(本地还好,如果是云服务器,网速慢了每次上传文件都想砸电脑π__π)。此外,系统切换环境时,也同时需要切换配置参数,虽然可以使用配置中心或者利用命令行参数修改配置,但有时候也免不了直接需要修改配置文件,这样的话就有必要将配置文件从jar包中分离出来,单独存放。
Spring社区大概也考虑到了部分开发者有这样的需求,所以提供了spring-boot-thin-launcher这个插件用来将项目的依赖和配置从jar包中分离出去。这个插件虽然是放到spring-projects-experimental(意思就是实验性质的项目)当中的,但从我使用的经验来看应该是比较稳定的,能够满足绝大部分场景的需求。废话少说,还是先上代码吧:
maven-compiler-plugin
${java.version}
UTF-8
org.springframework.boot
spring-boot-maven-plugin
${spring-boot.version}
com.example.Application
repackage
org.springframework.boot.experimental
spring-boot-thin-layout
1.0.12.RELEASE
org.springframework.boot.experimental
spring-boot-thin-maven-plugin
1.0.12.RELEASE
resolve
resolve
false
maven-antrun-plugin
compile
run
在maven-antrun-plugin中可以根据需要定义一些额外的任务,比如移动其它的配置文件,或者将外部的一些文件加入到项目目录中(比如执行脚本等)。
这样配置后,打包以后的target目录大概是这样的:
其中thin目录下面就可以作为整个项目的部署目录了,config是配置文件的存放目录,respository下面是所有的依赖包,backend-2.0.jar中仅包含了项目自身的资源,体积比之前小太多了。
运行原理
那为什么执行时,系统知道自动去外部加载依赖和配置呢?这个问题需要首先了解打包后的结构,把backend-2.0.jar解压之后,会发现除了我们自己的类和资源以外,还多了一个类:
org.springframework.boot.loader.wrapper.ThinJarWrapper,其实我们在运行项目时,这个类才是真正的项目入口,它会在默认位置查找项目相关的依赖,如果没有找到,甚至还会从指定的maven仓库中直接下载,所以启动时系统能够识别到外部的依赖包。至于外部配置,是因为Spring Boot框架在读取配置文件时,会默认读取几个目录下的配置文件,其中优先级最高的就是当前目录下的config目录(所以config目录的名字不能改成其它的)。
执行jar包的时候需要注意,要额外添加一个参数来指定依赖包所在的仓库位置(在我们的配置中就在jar包的当前文件夹),例如:-Dthin.root=. 默认的位置是${user.home}/.m2,如果仓库中没有需要的依赖,启动jar包时还会自动连接远程仓库进行下载,导致启动时间非常长,这一点需要注意。spring-boot-thin-launcher还有很多可配置的参数,具体可以到 官网 上自行查看。
另外附上一个通用的spring-boot-thin-launcher打包文件的启动脚本:
#!/bin/bash
#这里指定需要运行的jar包的名字
APP_NAME="your-jar-name.jar"
JVM_ARGS="-Xms512M -Xmx2048M -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/log/heap.hprof"
THIN_ARGS="-Dthin.root=. -Dthin.offline=true"
#使用说明,用来提示输入参数
usage() {
echo "Usage: sh 执行脚本.sh [start|stop|restart|status|run]"
exit 1
}
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}' `
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#后台启动
start(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is already running. pid=${pid} ."
else
echo "${APP_NAME} running with args: nohup java $THIN_ARGS -jar $JVM_ARGS $APP_NAME "
nohup java $THIN_ARGS -jar $JVM_ARGS $APP_NAME >> catalina.out 2>&1 &
fi
}
#前台启动
run(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is already running. pid=${pid} ."
else
echo "${APP_NAME} running with args: java $THIN_ARGS -jar $JVM_ARGS $APP_NAME"
java $THIN_ARGS -jar $JVM_ARGS $APP_NAME
fi
}
#停止方法
stop(){
is_exist
if [ $? -eq "0" ]; then
kill -9 $pid
else
echo "${APP_NAME} is not running"
fi
}
#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo "${APP_NAME} is running. Pid is ${pid}"
else
echo "${APP_NAME} is NOT running."
fi
}
#重启
restart(){
stop
start
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"start")
start
;;
"run")
run
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac