0.
0.0. 历史文章整理
玩转 Spring Boot 入门篇
玩转 Spring Boot 集成篇(MySQL、Druid、HikariCP)
玩转 Spring Boot 集成篇(MyBatis、JPA、事务支持)
玩转 Spring Boot 集成篇(Redis)
玩转 Spring Boot 集成篇(Actuator、Spring Boot Admin)
玩转 Spring Boot 集成篇(RabbitMQ)
玩转 Spring Boot 集成篇(@Scheduled、静态、动态定时任务)
玩转 Spring Boot 集成篇(任务动态管理代码篇)
玩转 Spring Boot 集成篇(定时任务框架Quartz)
玩转 Spring Boot 原理篇(源码环境搭建)
玩转 Spring Boot 原理篇(核心注解知多少)
玩转 Spring Boot 原理篇(自动装配前凑之自定义Starter)
玩转 Spring Boot 原理篇(自动装配源码剖析)
玩转 Spring Boot 原理篇(启动机制源码剖析)
玩转 Spring Boot 原理篇(内嵌Tomcat实现原理&优雅停机源码剖析)
玩转 Spring Boot 应用篇(搭建菜菜的店铺)
玩转 Spring Boot 应用篇(解决菜菜店铺商品超卖问题)
玩转 Spring Boot 应用篇(引入Redis解决店铺高并发读的问题)
玩转 Spring Boot 应用篇(引入RabbitMQ解决店铺下单峰值问题)
玩转 Spring Boot 应用篇(序列号生成器服务实现)
0.1. 疑问
- 开发出的 Spring Boot 应用,该如何打包?
- 项目不可避免要引入三方依赖包,这种场景下该如何打包呢?
- Spring Boot 应用编译打包后,该如何发布呢?
本次重点探讨 Spring Boot 应用的打包和发布。
1. 创建项目(ToyApp)
为了演示需要,基于 Spring Boot 创建一个 WEB 项目 ToyApp。
稍微注意一点:本次引入了一个三方依赖包(idgenerator-spring-boot-starter 是自定义的一个序列号生成器 starter)。
准备好环境,下面一起瞅瞅引入了三方依赖的 Spring Boot 该如何打包?
2. 编译打包
采用 IDEA 集成的 Maven 环境来对 Spring Boot 项目编译打包,可谓是超级 easy。
此时,通过反编译工具来瞅瞅编译打包成功之后的 ToyApp-0.0.1-SNAPSHOT.jar 里面的东西是否缺失,尤其是引入的三方依赖 jar 包。
通过反编译,发现引入的三方依赖 jar 包,没有打进去,咋回事儿?
由于 scope 为 system 的包 maven 默认是不打包进去的,所以解决方案就很简单:只需在 pom.xml 中指定一下 includeSystemScope 就可以。
代码语言:javascript复制<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
</build>
再次编译打包,然后对打成的 jar 包反编译观察一二。
至此,Spring Boot 项目打包就完成了,如果经历过传统的项目开发,相对传统的打包方式,你会知道着实简单不少。
3. 应用发布
3.1 一行命令的演化
运行 Spring Boot 打包之后的 jar 方式也简单,只需一行命令就行。
代码语言:javascript复制java -jar /Users/caicai/ToyApp-0.0.1-SNAPSHOT.jar
执行效果:
此时,服务是启动了,但是不能关闭这个窗口,一旦关闭服务就停止了,不得不考虑后台运行,并且还想看日志,所以这一行命令演变成了这个鸟样。
代码语言:javascript复制java -jar /Users/caicai/ToyApp-0.0.1-SNAPSHOT.jar > ToyApp.out &
执行启动时,效果如下,然后就可以轻松看日志输出了。
如果项目组中你既是研发又充当运维的角色,到这基本就完事儿了,因为相信通过熟练操作,会形成肌肉反应,你肯定能记住这一行命令。
不过,若是分工明确,生产权限隔离的话,一般都是运维同事来操作发布,所以还得想办法让运维同事省力,不得不考虑脚本化。
3.2 两个脚本
每个项目组的风格不同,而我习惯采取如下目录结构进行管理。
代码语言:javascript复制.
├── bin(脚本存放目录)
│ ├── start.sh
│ └── stop.sh
├── lib(jar包存放目录)
│ └── ToyApp-0.0.1-SNAPSHOT.jar
└── logs(日志目录)
├── toyapp.gc.log
└── toyapp.out
首先创建项目目录例如 toyapp,然后分别创建 bin、lib、logs 目录;把 ToyApp-0.0.1-SNAPSHOT.jar 拷贝至 lib 目录下;bin 目录主要存放脚本。
- 启动脚本(start.sh)
在 bin 目录下,创建 start.sh,应用启动脚本。
代码语言:javascript复制#!/bin/bash
#配置 Java 环境变量
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home
export PATH=$JAVA_HOME/bin:$PATH
#定义应用名
App_Name=toyapp
#定义应用所在目录
App_Path=/Users/caicai/${App_Name}
#定义可执行文件的路径
JAR_PATH=${App_Path}/lib/ToyApp-0.0.1-SNAPSHOT.jar
#jvm启动参数
JAVA_OPTS="-Duser.timezone=GMT 8 -server -Xms4096m -Xmx4096m -XX:MaxMetaspaceSize=256m -Xloggc:${App_Path}/logs/${App_Name}.gc.log -XX: UseConcMarkSweepGC -XX: UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5 -XX: PrintGC -XX: PrintGCTimeStamps -XX: PrintGCDetails -XX: PrintGCApplicationStoppedTime"
#启动JAVA进程函数
CURRENT_COUNT=`ps -ef|grep java |grep ${App_Name} |grep -vc grep`
if [ $CURRENT_COUNT -eq 0 ]
then
Log_Name=$(echo ${App_Name}|awk -F"-" '{ print $NF }')
nohup java -Dfunc_type=${App_Name} $JAVA_OPTS -Dfile.encoding=utf-8 -jar $JAR_PATH > ${App_Path}/logs/${Log_Name}.out 2>&1 &
PROCESS_ID=`ps -ef | grep "${App_Name}" |grep -v grep | awk '{ print $2 }'`
echo "