SpringBoot项目打包规范v1.0

背景

传统springboot构建的项目打包后形成一个巨大的可执行jar包、配置文件不易修改,线上部署更新包传输时间长。基于此规范执行mvn package命令后将会形成结构分明易于部署的tar包,将开发的业务代码与依赖的jar进行分离,为线上环境提供增量部署能力。

效果

基于此规范以demo-package项目为例,最终生成的tar包目录结构如下:

demo-package    
├── bin                                   // 运行脚本目录
│       └── startup.sh                    // 启动脚本
│       └── shutdown.sh                   // 停止脚本
│       └── daemon.sh                     // 守护进程脚本
├── config                                // 配置文件目录
│       └── logback.xml                   // 日志配置
│       └── banner.txt                    // banner配置
│       └── application.yml               // 应用权限
├── lib                                   // 项目依赖目录
├── demo-package.jar                      // 业务代码jar包

一、 添加运行脚本

1. 新建目录

${basedir}/src/main/bin

2. 添加启动脚本

在路径${basedir}/src/main/bin下添加startup.sh文件,脚本支持检查java运行环境,配置了gc日志,内存溢出时生成堆栈快照,默认使用prod配置文件,使用其他配置文件时通过-m参数修改,如果-m dev则默认开启调试端口5005;

#!/bin/bash
#
########################################################
#
# 脚本名称:startup.sh
#
# 功 能: 服务启动
#
# 用 法: sh startup.sh
#
# 作 者: xiechenglong
#
# 日 期: 2023-01-12
#
########################################################
#
cygwin=false
darwin=false
os400=false
case "`uname`" in
CYGWIN*) cygwin=true;;
Darwin*) darwin=true;;
OS400*) os400=true;;
esac
error_exit ()
{
    echo "ERROR: $1 !!"
    exit 1
}
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=$HOME/jdk/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/usr/java
[ ! -e "$JAVA_HOME/bin/java" ] && JAVA_HOME=/opt/taobao/java
[ ! -e "$JAVA_HOME/bin/java" ] && unset JAVA_HOME

if [ -z "$JAVA_HOME" ]; then
  if $darwin; then

    if [ -x '/usr/libexec/java_home' ] ; then
      export JAVA_HOME=`/usr/libexec/java_home`

    elif [ -d "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home" ]; then
      export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"
    fi
  else
    JAVA_PATH=`dirname $(readlink -f $(which javac))`
    if [ "x$JAVA_PATH" != "x" ]; then
      export JAVA_HOME=`dirname $JAVA_PATH 2>/dev/null`
    fi
  fi
  if [ -z "$JAVA_HOME" ]; then
        error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better!"
  fi
fi

export MODE="prod"
while getopts ":m:" opt
do
    case $opt in
        m)
            MODE=$OPTARG;;
        ?)
        echo "Unknown parameter"
        exit 1;;
    esac
done

export JAVA_HOME
export JAVA="$JAVA_HOME/bin/java"
export BASE_DIR=`cd $(dirname $0)/..; pwd`

for ITEM in $(ls ${BASE_DIR}); do
  if [ ${ITEM##*.} = jar ]; then
    SERVICE_JAR=${ITEM}
  fi
done
export SERVICE_NAME=${SERVICE_JAR%.*}

#jvm configuration
JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${BASE_DIR}/logs/heapdump.hprof"
JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages"
JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\1/p')
if [[ "$JAVA_MAJOR_VERSION" -ge "9" ]] ; then
  JAVA_OPT="${JAVA_OPT} -Xlog:gc*:file=${BASE_DIR}/logs/gc.log:time,tags:filecount=10,filesize=102400"
else
  JAVA_OPT="${JAVA_OPT} -Xloggc:${BASE_DIR}/logs/gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"
fi

if [[ "${MODE}" == "dev" ]]; then
  echo "${SERVICE_NAME} is starting with dev"
  JAVA_OPT="${JAVA_OPT} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
fi
JAVA_OPT="${JAVA_OPT} -Dservice.home=${BASE_DIR}"
JAVA_OPT="${JAVA_OPT} --Dspring.profiles.active=${MODE}"
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/${SERVICE_NAME}.jar"

# check the start.out log output file
if [ ! -d "${BASE_DIR}/logs" ]; then
  mkdir ${BASE_DIR}/logs
fi
if [ ! -f "${BASE_DIR}/logs/start.out" ]; then
  touch "${BASE_DIR}/logs/start.out"
fi

# start
echo "$JAVA ${JAVA_OPT}" > ${BASE_DIR}/logs/start.out 2>&1 &
nohup "$JAVA" ${JAVA_OPT} >> ${BASE_DIR}/logs/start.out 2>&1 &

echo "${SERVICE_NAME} is starting,you can check the ${BASE_DIR}/logs/start.out"

3. 添加停止脚本

在路径${basedir}/src/main/bin下添加shutdown.sh文件

#!/bin/bash
#
########################################################
#
# 脚本名称:shutdown.sh
#
# 功 能: 服务停止
#
# 用 法: sh shutdown.sh
#
# 作 者: xiechenglong
#
# 日 期: 2023-01-12
#
########################################################
#
export BASE_DIR=`cd $(dirname $0)/..; pwd`
for ITEM in $(ls ${BASE_DIR}); do
  if [ ${ITEM##*.} = jar ]; then
    SERVICE_JAR=${ITEM}
  fi
done
export SERVICE_NAME=${SERVICE_JAR%.*}

echo "stop ${SERVICE_NAME}"
PID=""
query(){
  PID=`ps -ef |grep java|grep ${SERVICE_NAME}|grep -v grep|awk '{print $2}'`
}
query
if [ x"$PID" != x"" ]; then
    kill -TERM $PID
    echo "${SERVICE_NAME} (pid:$PID) exiting..."
    while [ x"$PID" != x"" ]
    do
        sleep 1
        query
    done
    echo "${SERVICE_NAME} exited."
else
    echo "${SERVICE_NAME} already stopped."
fi

4. 添加守护进程脚本

在路径${basedir}/src/main/bin下添加daemon.sh文件,守护进程脚本用于系统内存溢出时自动重启,需要nohup后台启动。

#!/bin/bash
#
########################################################
#
# 脚本名称:daemon.sh
#
# 功 能: 守护进程
#
# 用 法: sh daemon.sh
#
# 作 者: xiechenglong
#
# 日 期: 2023-02-24
#
########################################################
#
#根目录
BASE_DIR=`cd $(dirname $0)/..; pwd`
#获取服务名
for ITEM in $(ls ${BASE_DIR}); do
  if [ ${ITEM##*.} = jar ]; then
    SERVICE_JAR=${ITEM}
  fi
done
SERVICE_NAME=${SERVICE_JAR%.*}
#定义监听日志文件
LOG_FILE=${BASE_DIR}/logs/error.log
#定义监听日志行数
LOG_NO=50
#循环监听OutOfMemoryError
while true
do
  if [ `tail -n ${LOG_NO} ${LOG_FILE} | grep OutOfMemoryError | wc -l` -ne 0 ];then
    echo "The ${SERVICE_NAME} is restarting"
    sh ${BASE_DIR}/bin/shutdown.sh && sh ${BASE_DIR}/bin/startup.sh
    for (( i=0; i<${LOG_NO}; i++ ))
    do
        echo "The ${SERVICE_NAME} is restarted" >> ${LOG_FILE}
    done
  fi
  sleep 5
done

二、 添加assembly.xml

在路径${basedir}/src/main/resources下添加assembly.xml文件

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <!-- 可自定义,这里指定的是项目名称,打包唯一标识-->
    <id>${project.version}</id>
    <formats>
        <!-- 打包的类型,如果有N个,将会打N个类型的包 -->
        <format>tar.gz</format>
    </formats>
    <!--外层是否包一层-->
    <includeBaseDirectory>true</includeBaseDirectory>
    <fileSets>
        <!-- config files -->
        <fileSet>
            <directory>${basedir}/src/main/resources</directory>
            <excludes>
                <exclude>assembly.xml</exclude>
            </excludes>
            <includes>
                <include>*.properties</include>
                <include>*.yml</include>
                <include>*.xml</include>
                <include>banner.txt</include>
            </includes>
            <!--设置文件权限-->
            <fileMode>0644</fileMode>
            <!--设置配置文件输出的外部文件夹-->
            <outputDirectory>/config</outputDirectory>
        </fileSet>
        <!-- scripts -->
        <fileSet>
            <directory>${basedir}/src/main/bin</directory>
            <includes>
                <include>*.sh</include>
            </includes>
            <fileMode>0755</fileMode>
            <lineEnding>unix</lineEnding>
            <!--设置项目相关脚本输出的外部文件夹-->
            <outputDirectory>/bin</outputDirectory>
        </fileSet>
        <!-- executable jar -->
        <fileSet>
            <directory>${project.build.directory}</directory>
            <includes>
                <include>*.jar</include>
                <!--注意此处是将项目依赖的第三方jar全部copy至assembly打包出来的目录中
                如果没有此段,则lib目录是在target目录下,而不会存放于assembly打包的目录中-->
                <include>/lib/*</include>
            </includes>
            <fileMode>0755</fileMode>
            <!--设置项目jar以及项目依赖的第三方jar输出的外部文件夹-->
            <outputDirectory>/</outputDirectory>
        </fileSet>
    </fileSets>
</assembly>

三、 添加logback.xml

在路径${basedir}/src/main/resources下添加logback.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 日志存放路径 -->
    <property name="log.path" value="${service.home}/logs"/>
    <!-- 日志输出格式 -->
    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/>

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 系统日志输出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>INFO</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 系统模块日志级别控制  -->
    <logger name="com.inspur" level="info"/>
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn"/>

    <root level="info">
        <appender-ref ref="console"/>
    </root>

    <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="file_info"/>
        <appender-ref ref="file_error"/>
    </root>
</configuration>

四、 添加banner.txt

在路径${basedir}/src/main/resources下添加banner.txt文件

Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}

五、 修改pom.xml

pom.xml文件中build标签修改为如下内容,mainClass标签中的启动类按照实际修改

    <build>
        <!--jar包名字 -->
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <!-- maven编译 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
            <!-- 打包时跳过测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
            <!--将项目中代码文件打成jar包 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!-- 打包后的jar包中不包括配置文件 -->
                    <!-- 通常是指classpath下目录下的文件,这样可以避免编写时的找不到相应文件 -->
                    <excludes>
                        <exclude>*.properties</exclude>
                        <exclude>*.yml</exclude>
                        <exclude>*.xml</exclude>
                        <exclude>banner.txt</exclude>
                    </excludes>
                    <archive>
                        <manifest>
                            <!-- 项目启动类 -->
                            <mainClass>com.inspur.demo.DemoPackageApplication</mainClass>
                            <!-- 依赖的jar的目录前缀 -->
                            <classpathPrefix>./lib/</classpathPrefix>
                            <!-- 将依赖加进 Class-Path -->
                            <addClasspath>true</addClasspath>
                            <!-- 是否使用唯一的时间戳快照版本而不是-快照版本。默认值为 true -->
                            <useUniqueVersions>false</useUniqueVersions>
                        </manifest>
                        <!-- 将config目录加入classpath目录 -->
                        <manifestEntries>
                            <Class-Path>./config/</Class-Path>
                        </manifestEntries>
                    </archive>

                </configuration>
            </plugin>
            <!--设置jar所依赖的三方jar包存放的路径 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dep</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>
                                copy-dependencies
                            </goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!--利用maven的assembly插件打包-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <!--生成的tar包是否携带版本号-->
                    <appendAssemblyId>false</appendAssemblyId>
                    <outputDirectory>target</outputDirectory>
                    <descriptors>
                        <descriptor>src/main/resources/assembly.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>${project.version}</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值