java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > 使用Assembly打包和部署SpringBoot工程

使用Assembly打包和部署SpringBoot工程方式

作者:大饼酥

文章介绍了SpringBoot项目的两种部署方式:Docker容器部署和FatJar直接部署,FatJar部署存在配置文件隐藏和启动脚本复杂的问题,而Assembly打包方式可以解决这些问题,使得SpringBoot能够加载jar外的配置文件并提供服务化的启动脚本

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 体积也十分小巧。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文