文章

SpringBoot + Maven 打包部署示例

前言

在现代企业级应用开发中,快速构建、高效部署和易于维护是至关重要的。Spring Boot 以其简洁的配置和强大的自动化功能,极大地简化了这一过程。本文将介绍如何使用 SpringBoot 结合 Maven 进行项目构建和打包。示例最终会构建出目录结构为 lib、conf、bin、logs 的 zip 包,内含着项目运行所需要的 jar 包、配置文件、启动脚本。

配置

项目结构:

-- src
-- -- bin
-- -- -- start.sh
-- -- -- stop.sh
-- -- -- run.sh
-- -- -- restart.sh
-- -- main
-- -- -- assembly
-- -- -- -- assembly.xml
-- -- -- java
-- -- -- resources
-- pom.xml

pom.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
    </parent>
    <groupId>cn.phixlin</groupId>
    <artifactId>package-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>


    <build>

        <finalName>${project.artifactId}</finalName>

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!-- 依赖包打入 lib 下 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/lib</outputDirectory>
                            <overWriteReleases>true</overWriteReleases>
                            <overWriteSnapshots>true</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                            <includeScope>compile</includeScope>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <!-- 只执行一次 -->
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <appendAssemblyId>false</appendAssemblyId>
                            <archive>
                                <manifest>
                                    <!--jar 包的入口 main 方法 -->
                                    <mainClass>cn.phixlin.PackageApplication</mainClass>
                                </manifest>
                            </archive>
                            <!-- 生成的 jar 包的名称前缀 -->
                            <finalName>${project.build.finalName}</finalName>
                            <descriptors>
                                <!-- assembly 插件的配置文件路径 -->
                                <descriptor>src/main/assembly/assembly.xml</descriptor>
                            </descriptors>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>


</project>

assembly.xml 配置:

<assembly>
    <!-- 可自定义,这里指定的是项目环境 -->
    <id>prod</id>
    <formats>
        <!-- 打包的类型,如果有 N 个,将会打 N 个类型的包 -->
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <!--
            0755-> 即用户具有读 / 写 / 执行权限,组用户和其它用户具有读写权限;
            0644-> 即用户具有读写权限,组用户和其它用户具有只读权限;
        -->
        <!-- 将 src/bin 目录下的所有文件输出到打包后的 bin 目录中 -->
        <fileSet>
            <directory>${basedir}/src/bin</directory>
            <outputDirectory>bin</outputDirectory>
            <fileMode>0755</fileMode>
            <includes>
                <include>**.sh</include>
            </includes>
        </fileSet>
        <!-- 指定输出 target/classes 中的配置文件到 conf 目录中 (只复制配置文件, 不复制 xml 文件,xml 文件已在启动的 jar 中存在) -->
        <fileSet>
            <directory>${basedir}/target/classes</directory>
            <outputDirectory>conf</outputDirectory>
            <fileMode>0644</fileMode>
            <includes>
                <include>application.yml</include>
                <include>application-${env.devMode}.yml</include>
                <include>bootstrap.yml</include>
                <include>*.properties</include>
            </includes>
        </fileSet>

        <!-- 将第三方依赖打包到 lib 目录中 -->
        <fileSet>
            <directory>${basedir}/target/lib</directory>
            <outputDirectory>lib</outputDirectory>
            <fileMode>0644</fileMode>
        </fileSet>

        <!-- 将项目启动 jar 打包到 lib 目录中 -->
        <fileSet>
            <directory>${basedir}/target</directory>
            <outputDirectory>lib</outputDirectory>
            <fileMode>0644</fileMode>
            <includes>
                <include>${project.build.finalName}.jar</include>
            </includes>
        </fileSet>


        <!-- 包含根目录下的文件 -->
        <fileSet>
            <directory>${basedir}</directory>
            <includes>
                <include>NOTICE</include>
                <include>LICENSE</include>
                <include>*.md</include>
            </includes>
        </fileSet>

    </fileSets>
</assembly>

bin 脚本

start.sh

#!/bin/bash

cd `dirname $0`
BIN_DIR=`pwd`
cd ..
DEPLOY_DIR=`pwd`
CONF_DIR=$DEPLOY_DIR/conf/


APPLICATION="package-demo"
APPLICATION_JAR="${APPLICATION}.jar"

SERVER_NAME=""

SERVER_PORT=""
SERVER_HOST=""
SERVER_PROTOCOL=""
VM_ARGS_PERM_SIZE='PermSize'
VM_ARGS_METASPACE_SIZE='MetaspaceSize'
JAVA_8_VERSION="180"


if [ -z "$SERVER_HOST" ]; then
    SERVER_HOST='127.0.0.1'
fi

if [ -z "$SERVER_NAME" ]; then
    SERVER_NAME=$APPLICATION
fi

PIDS=`ps -ef | grep java | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
if [ -n "$PIDS" ]; then
    echo "ERROR: The $SERVER_NAME already started!"
    echo "PID: $PIDS"
    exit 1
fi

if [ -n "$SERVER_PORT" ]; then
    SERVER_PORT_COUNT=`netstat -tln | grep $SERVER_PORT | wc -l`
    if [ $SERVER_PORT_COUNT -gt 0 ]; then
        echo "ERROR: The $SERVER_NAME port $SERVER_PORT already used!"
        exit 1
    fi
fi

LOGS_DIR=$DEPLOY_DIR/logs

if [ ! -d $LOGS_DIR ]; then
    mkdir $LOGS_DIR
fi
STDOUT_FILE=$LOGS_DIR/stdout.log

LIB_DIR=$DEPLOY_DIR/lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print"'$LIB_DIR'/"$0}'|tr "\n" ":"`

JAVA_OPTS="-Djava.awt.headless=true -Djava.net.preferIPv4Stack=true"
JAVA_DEBUG_OPTS=""
if [ "$1" = "debug" ]; then
    JAVA_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
fi
JAVA_JMX_OPTS=""
if [ "$1" = "jmx" ]; then
    JAVA_JMX_OPTS="-Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
fi
JAVA_MEM_OPTS=""
# set jvm args by different java version
JAVA_VERSION=`java -fullversion 2>&1 | awk -F[\"\.]'{print $2$3$4}' |awk -F"_" '{print $1}'`
echo $JAVA_VERSION
VM_ARGS=${VM_ARGS_PERM_SIZE}
# if you use dubbo in java 9
TEMP_VERSION=$(echo ${JAVA_VERSION} | grep "+")
if [[ "$TEMP_VERSION" != "" ]]; then
        JAVA_VERSION=$(echo ${JAVA_VERSION} | awk -F"+" '{print $1}')
fi
# compare java version
if [ "${JAVA_VERSION}" -ge ${JAVA_8_VERSION} ]; then
    VM_ARGS=${VM_ARGS_METASPACE_SIZE}
fi

# MaxInlineLevel=15 is the default since JDK 14 and can be removed once older JDKs are no longer supported
BITS=`java -version 2>&1 | grep -i 64-bit`
JAVA_MEM_OPTS=" -server -Xmx4g -Xms2g -XX:${VM_ARGS}=256m -Xss256k -XX:+DisableExplicitGC -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails "

if [ -n "$BITS" ]; then
    JAVA_MEM_OPTS=" -server -Xmx4g -Xms2g -Xmn256m -XX:${VM_ARGS}=128m -Xss512k -XX:LargePageSizeInBytes=128m -XX:+DisableExplicitGC -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8 -XX:G1ReservePercent=15 "
else
    JAVA_MEM_OPTS=" -server -Xms1g -Xmx2g -XX:${VM_ARGS}=128m -XX:SurvivorRatio=2 -XX:+UseParallelGC "
fi

echo -e "Starting the $SERVER_NAME ...\c"
nohup java ${JAVA_JMX_OPTS} ${JAVA_OPTS} ${JAVA_MEM_OPTS} -Xbootclasspath/a:${CONF_DIR} -jar ${DEPLOY_DIR}/lib/${APPLICATION_JAR} --spring.config.location=${CONF_DIR} -DJM.SNAPSHOT.PATH=${CONF_DIR}/nacos/download > $STDOUT_FILE 2>&1 &

COUNT=0
while [ $COUNT -lt 1 ]; do  
    echo -e ".\c"
    sleep 1 
    if [ -n "$SERVER_PORT" ]; then
        if [ "$SERVER_PROTOCOL" == "dubbo" ]; then
    	    COUNT=`(sleep 1; echo -e '\n'; sleep 1; echo status; sleep 1)| telnet $SERVER_HOST $SERVER_PORT | grep -c OK`
        else
            COUNT=`netstat -an | grep $SERVER_PORT | wc -l`
        fi
    else
    	COUNT=`ps -ef | grep java | grep -v grep | grep "$DEPLOY_DIR" | awk '{print $2}' | wc -l`
    fi
    if [ $COUNT -gt 0 ]; then
        break
    fi
done

echo "OK!"
PIDS=`ps -ef | grep java | grep -v grep | grep "$DEPLOY_DIR" | awk '{print $2}'`
echo "PID: $PIDS"
echo "STDOUT: $STDOUT_FILE"

stop.sh

#!/bin/bash

cd `dirname $0`
BIN_DIR=`pwd`
cd ..
DEPLOY_DIR=`pwd`
CONF_DIR=$DEPLOY_DIR/conf/

SERVER_NAME="package-demo"

if [ -z "$SERVER_NAME" ]; then
    SERVER_NAME=`hostname`
fi

PIDS=`ps -ef | grep java | grep -v grep | grep "$CONF_DIR" |awk '{print $2}'`
if [ -z "$PIDS" ]; then
    echo "ERROR: The $SERVER_NAME does not started!"
    exit 1
fi

echo -e "Stopping the $SERVER_NAME ...\c"
for PID in $PIDS ; do
    kill $PID > /dev/null 2>&1
done

COUNT=0
while [ $COUNT -lt 1 ]; do  
    echo -e ".\c"
    sleep 1
    COUNT=1
    for PID in $PIDS ; do
        PID_EXIST=`ps -f -p $PID | grep java`
        if [ -n "$PID_EXIST" ]; then
            COUNT=0
            break
        fi
    done
done

echo "OK!"
echo "PID: $PIDS"

run.sh

#!/bin/bash


cd `dirname $0`
if [ "$1" = "start" ]; then
	./start.sh
else
	if [ "$1" = "stop" ]; then
		./stop.sh
	else
		if [ "$1" = "debug" ]; then
			./start.sh debug
		else
			if [ "$1" = "restart" ]; then
				./restart.sh
			else
				echo "ERROR: Please input argument: start or stop or debug or restart"
			fi
		fi
	fi
fi

restart.sh

#!/bin/bash

cd `dirname $0`
./stop.sh
./start.sh

测试

  1. 执行 maven 打包命令: mvn clean package -Dmaven.test.skip=true
  2. 上传并解压到服务器: unzip package-demo-1.0.0.zip
  3. 启动项目: cd package-demo-1.0.0 && sh bin/run.sh start
  4. 查看项目是否启动成功: ps -ef | grep package-demo

后记

  1. 需要根据自己的项目名和版本号修改脚本
  2. 需要根据项目实际修改 JVM 启动参数
License:  CC BY 4.0