芝法酱学习笔记(0.4)——SpringBoot多模块项目打包,resource分离,lib分离,启动脚本

前言

上期讲了如何在windows平台搭建Java后端的开发环境,并给出了一个简单的hello world级别的多模块代码示例。但上期仅仅是在IDEA中运行,和正式的生产环境完全不同。

本期将讲解,如何配置SpringBoot多模块项目的maven打包,并分离出lib和resource。

真实的项目,不可能是运行在IDEA里的。实际的生产,一般有两种模式。一则把写好的项目打包成Jar包,通过命令启动jar包;另一种则是把项目打包成一个docker镜像,使用镜像启动。我们这里先讨论Jar包启动的情况。

一、打包配置

1.1 默认情况

我们直接在根目录点击package,会在各自模块生成一个target的文件夹,里面就有我们打包好的包。

我们随便找一个包,打开看看,会发现我们打的包异常的小,只有几十k。

拿我们的主业务模块general-test来说,包只包含3个模块,我们的代码,maven相关配置以及静态资源。application.yml放在最外层。

毫无疑问,这样的包是无法直接通过java -jar命令运行的。

1.2 springboot 打包插件

在根pom中,添加打包插件的版本引用

xml 复制代码
<build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                    <version>${spring-boot.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

而后,在general-test的pom下,添加如下内容

xml 复制代码
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

再次clean,package后,观察现象。我们首先可以看到,包的大小已经正常了

使用winRar打开,可以发现如下目录

org中,放了SpringFramework的相关内容,

META-INF放了maven的pom引用以及jar包服务信息

BOOT-INF下结构如下

lib放置了项目引用需要的包,包括自己写的core-common,core-enum-memo

而classes下,则放着本包的代码。

我们在打好包的位置,单击右键,打开命令行。

java -jar general-test-1.0.0.jar

即可启动服务器。

如果出现找不到favicon.ico的错误,可以在resources/static下放一个你喜欢的美少女头像,记得转成ico格式哦。

1.3 资源和配置文件的分离

2.2这样做,我们就会发现一个问题,配置文件被打到jar包里,运维人员无法轻易的修改配置文件。那样,配置文件的意义在哪里呢?还不如直接写到Java文件里。

在生产上,一般有2种做法。一种是使用配置中心,不过这要整上一套SpringCloud的东西(当然用K8S也可以实现)。我们这里讲一个简单的方法,通过修改打包方式来解决。

首先,在Resource中,把想要分离的文件拷出去:

xml 复制代码
<build>
        <resources>
            <!--打包时,把这些文件拷贝到外面-->
            <resource>
                <directory>src/main/java/indi/zhifa/study2024/class002/busy/generalTest/business/report</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <targetPath>${project.build.directory}/resources/mybatis</targetPath>
            </resource>
            <resource>
                <directory>src/main/resources/static</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
                <targetPath>${project.build.directory}/resources/static</targetPath>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.yml</include>
                    <include>*.properties</include>
                </includes>
                <targetPath>${project.build.directory}/config</targetPath>
            </resource>

            <!--打包时,为了idea能启动,还要向classes里拷贝一份-->

            <resource>
                <directory>src/main/java/indi/zhifa/study2024/class002/busy/generalTest/business/report</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <targetPath>${project.build.directory}/classes/mybatis</targetPath>
            </resource>
            <resource>
                <directory>src/main/resources/static</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
                <targetPath>${project.build.directory}/classes/static</targetPath>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*.yml</include>
                    <include>*.properties</include>
                </includes>
                <targetPath>${project.build.directory}/classes</targetPath>
            </resource>
        </resources>
        ......
 </build>

这样打包已经实现了需求,但把包解压开后发现,资源仍然存在于包中(相当于浪费了空间)

那,我们再加个打包的配置,排除这些文件

xml 复制代码
<build>
	<plugins>
		<plugin>
        	<groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
            	<finalName>${jar-name}</finalName><!--主义properties里定义该变量,打包后jar包的名字-->
                <!--排除掉配置,资源等,毕竟放到外面了-->
                <excludes>
                        <exclude>*.yml</exclude>
                        <exclude>*.properties</exclude>
                        <exclude>mybatis/**/*.xml</exclude>
                        <exclude>static/**/*</exclude>
                        <exclude>templates/**/*</exclude>
                    </excludes>
                </configuration>
            </plugin>
            <!--把${jar-name}包再打进包含springframework的包中-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <finalName>${jar-name}</finalName>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
	</plugins>
</build>

如此,我们再点击package,打包出的内容就会如图所示:

1.4 启动jar包

把jar包,config文件夹,resources文件夹站到linux系统的相应位置,我这里是/WORK/APP/study2024-class003

打开8080~8089的防火墙

bash 复制代码
ufw allow 8080/tcp
...

输入启动命令

bash 复制代码
java -jar nbr.jar --spring.config.additional-location=config/ study2024-class003-001

可以看出,程序启动成功了。

访问192.168.0.64/doc.html,即可看到swagger页面,查询一个报表接口,发现没有问题。

1.5 把lib包分离

其实有一说一,把lib包分离在实际的生产中,意义其实没有那么大。唯一的作用就是在公司的测试部署机放在外网,减少打包部署的带宽消耗。但这也会造个坑,就是当我们升级包时,可能会忘记把更新的包拷过去,导致莫名其妙的报错,这非常搞心态。

pom做如下修改:

xml 复制代码
	<plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <finalName>${jar-name}</finalName>
                    <addResources>true</addResources>
                    <includes>
                        <include>
                            <groupId>nothing</groupId>
                            <artifactId>nothing</artifactId>
                        </include>
                    </includes>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <finalName>${jar-name}</finalName>
                    <archive>
                        <!-- 指定资源文件目录,与打包的jar文件同级目录 -->
                        <manifestEntries>
                            <Class-Path>resources/</Class-Path>
                        </manifestEntries>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                    <!--排除掉配置,资源等,毕竟放到外面了-->
                    <excludes>
                        <exclude>*.yml</exclude>
                        <exclude>*.properties</exclude>
                        <exclude>mybatis/**/*.xml</exclude>
                        <exclude>static/**/*</exclude>
                        <exclude>templates/**/*</exclude>
                    </excludes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>

再进行打包,观察现象

我们看到,我们的包重新变小了。

把新打的包重新拷到相应的地方。而后再输入如下命令:

bash 复制代码
java -jar nbr.jar -D loader.path=./lib --spring.config.additional-location=config/ --logging.config=config/logback.xml study2024-class003-001

发现毫无问题。

二、部署环境

2.1 通常的4种环境

不考虑那种有中台的千人大厂情况,一般开发团队,通常会有4套部署环境。

  • local
    本地开发环境,即IDEA的环境,用户个人调试
  • dev
    开发环境,部署在公司内部的开发服务器中,用于前后端联调。该分支服务通常不稳定,经常发生代码变更。
    更严谨点的做法,dev也可以认为是一个测试环境,公司内部服务器部署dev,dev-feature1,dev-feature2...feature表示某个新增功能,通常有1个或几个程序员共同开发。测试先测试通过feature分支,而后把feature合并到dev上。当测试发现dev上有bug,则即刻要求开发进行修复,拉取部署bugfix分支,交由测试测试通过后,合并到dev分支上。
    但测试的最终验收,以test分支为主。
    dev环境下所用的数据库等中间件地址,很多时候是与我们本地开发不同的。但有时候为了方便,也采用相同的数据。
  • test
    测试环境,部署在公司内部的测试服务器中,也可以直接部署在外网的云服务器上。该服务用于交付测试,代码不经常变动。测试通过后打tag,可发到线上环境。
    该环境的数据库等中间件,与dev环境也不同。
    个人观点,这个环境下的数据,不要让开发人员污染。放有意义的测试数据。最好由专业的测试人员提供录入。
  • prod
    线上部署环境。通常,线上环境所用的数据库等中间件,与测试环境也是不同的。

2.2 profile配置

正因为不同的环境,可能有不同的配置。打包时要自动区分这点,所以通常会在pom文件中声明这4种环境。

由于我们这里是学习配置,只保留local和test,local表示在IDEA里跑,test表示放linux机器上(没linux的可以用WSL,我前面的文章介绍过)

xml 复制代码
<profiles>
        <!--本地-->
        <profile>
            <id>local</id>
            <properties>
                <!-- 环境标识,需要与配置文件的名称相对应 -->
                <spring.profiles.active>local</spring.profiles.active>
            </properties>
            <activation>
                <!-- 默认环境 -->
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <!--测试-->
        <profile>
            <id>test</id>
            <properties>
                <spring.profiles.active>test</spring.profiles.active>
            </properties>
        </profile>
    </profiles>

2.3 把先前的yml配置文件和日志xml分环境

我们现在把这种配置文件这样

为了能够正常运行,以及把对应文件拷到外面的config文件夹中,xml可以做如下配置

xml 复制代码
			<resource>
                <directory>src/main/config/${spring.profiles.active}</directory>
                <includes>
                    <include>*.yml</include>
                    <include>*.properties</include>
                    <include>logback.xml</include>
                </includes>
                <targetPath>${project.build.directory}/config</targetPath>
            </resource>

			<resource>
                <directory>src/main/config/${spring.profiles.active}</directory>
                <includes>
                    <include>*.yml</include>
                    <include>*.properties</include>
                    <include>logback.xml</include>
                </includes>
                <targetPath>${project.build.directory}/classes</targetPath>
            </resource>

点击compile,观察现象

我想,这样就实现了

三、maven打包命令

实际开发时,可不会在IDEA里打包,而是在jenkins中,在linux下使用命令打包。

我们先在windows中尝试一下:

powershell 复制代码
mvn clean package -pl busy/general-test -am -Ptest

嗯,这就成功了。

四、创建一个启动和关闭脚本

实际开发中,我们不可能用java -jar来启动服务。另外,同一个jar包,可能会部署多个实例。这时,我们可以写一个shell脚本来做启动和关闭。

在如图位置创建这俩脚本

在resource的配置中,添加拷贝脚本的配置

xml 复制代码
            <resource>
                <directory>src/main/bin/${spring.profiles.active}</directory>
                <includes>
                    <include>*.sh</include>
                    <include>*.bat</include>
                </includes>
                <targetPath>${project.build.directory}/bin</targetPath>
            </resource>

4.1 启动脚本startup.sh

shell 复制代码
#!/bin/bash

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
}

#参数解析
VERSION=''
appName=''
PORT='8081'
ARGS=`getopt -o v:a:p: --long version:,appName:,port: -n "$0" -- "$@"`
if [ $? != 0 ]; then
    echo "Terminating..."
    exit 1
fi
echo ARGS=[$ARGS]
eval set -- "${ARGS}"

while true
do
    case "$1" in
        -v|--version)
            case "$2" in
              "")
                VERSION=''
                shift 1;
                ;;
              *)
                VERSION=$2
                shift 2;
                ;;
             esac
            ;;
        -a|--appName)
            case "$2" in
              "")
                 appName='main'
                 shift 1;
                 ;;
              *)
                appName=$2
                shift 2;
                ;;
            esac
            ;;
        -p|--port)
          case "$2" in
            "")
               PORT='8081'
               shift 1;
               ;;
           *)
               PORT=$2
               shift 2;
               ;;
             esac
             ;;
        --) shift ; break ;;
        *) echo "Internal error!" ; exit 1 ;;
    esac
done



SERVER="nbr"
if [[ -z "$VERSION" ]]; then
    SERVER_JAR="$SERVER.jar"
else
    SERVER_JAR="$SERVER-$VERSION.jar"
fi

SPACE="study2024"
SERV_NAME="general-test"
PROFILE="test"

which java
JAVA="$JAVA_HOME/bin/java"
echo $JAVA

BASE_DIR=`cd $(dirname $0)/..; pwd`
CUSTOM_SEARCH_LOCATIONS=file:${BASE_DIR}/config/

#===========================================================================================
# JVM Configuration
#===========================================================================================
#JAVA_OPT="${JAVA_OPT} -Xms512m -Xmx512m -Xss128k -XX:MaxGCPauseMillis=300 -Xlog:gc:../logs/gc.log"
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/${SERVER_JAR}"
JAVA_OPT="${JAVA_OPT} -D loader.path=./lib"
JAVA_OPT="${JAVA_OPT} --spring.profiles.active=${PROFILE}"
JAVA_OPT="${JAVA_OPT} --server.port=${PORT}"
JAVA_OPT="${JAVA_OPT} --spring.config.additional-location=${CUSTOM_SEARCH_LOCATIONS}"
JAVA_OPT="${JAVA_OPT} --logging.config=${BASE_DIR}/config/logback.xml"


JAVA_MAJOR_VERSION=$($JAVA -version 2>&1 | sed -E -n 's/.* version "([0-9]*).*$/\1/p')


if [ ! -d "${BASE_DIR}/logs" ]; then
  mkdir ${BASE_DIR}/logs
fi

echo "$JAVA ${JAVA_OPT}"


# start
echo "$JAVA ${JAVA_OPT}" > ${BASE_DIR}/logs/start.out 2>&1 &
nohup $JAVA ${JAVA_OPT} ${SPACE}.${SERV_NAME}-${appName} >> ${BASE_DIR}/logs/start.out 2>&1 &
echo "${SPACE}.${SERV_NAME}-${appName} is starting,you can check the ${BASE_DIR}/logs/start.out"
echo $! > pid.txt

4.2 关闭脚本shutdown.sh

shell 复制代码
#!/bin/bash

SPACE="study2024"
SERV_NAME="general-test"
PROFILE="test"

#参数解析
appName='app001'
ARGS=`getopt -o a: --long appName: -n "$0" -- "$@"`
if [ $? != 0 ]; then
    echo "Terminating..."
    exit 1
fi
#echo ARGS=[$ARGS]
eval set -- "${ARGS}"
#echo formatted parameters=[$@]

while true
do
    case "$1" in
        -a|--appName)
            case "$2" in
              "")
                 appName='main'
                 shift 1;
                 ;;
              *)
                appName=$2
                shift 2;
                ;;
            esac
            ;;
        --) shift ; break ;;
        *) echo "Internal error!" ; exit 1 ;;
    esac
done

#cd `dirname $0`/..
target_dir=`pwd`

pid=$(cat "$target_dir/pid.txt")

if [[ -z "$pid" ]]; then
  pid=`ps ax | grep -i "$SPACE.$SERV_NAME-${appName}" | grep ${target_dir} | grep java | grep -v grep | awk '{print $1}'`
fi

if [ -z "$pid" ] ; then
        echo "No $SPACE.$SERV_NAME-${appName} running."
        exit 0;
fi

rm pid.txt

echo "The ${SPACE}.${SERV_NAME}-${appName}(${pid}) is running..."
kill ${pid}
echo "Send shutdown request to ${SPACE}.${SERV_NAME}-${appName}(${pid}) OK"

4.3 启动关闭实验

把该脚本拖到linux,如图所示

cd进bin中,把这俩脚本赋予可执行权限

bash 复制代码
cd bin
chmod +x startup.sh
chmod +x shutdown.sh

启动命令

bash 复制代码
./startup -p 8083

查看是否启动成功

bash 复制代码
lsof -i:8083

可见,程序启动成功了。

关闭程序

bash 复制代码
./shutdown.sh

再次查看是否进程还在

bash 复制代码
lsof -i:8083

观察到已经没有。

五、代码展示

不多讲,还是移步我的码云

相关推荐
CZIDC12 分钟前
Linux系统安全-开发中注意哪些操作系统安全
linux·安全·系统安全
czhc114007566315 分钟前
LINUX 5 vim cat zip unzip
linux·编辑器·vim
明灯L1 小时前
《深度剖析 Linux 权限管理:从基础到进阶,解锁系统安全密钥》
linux·运维·全网最全权限管理·小白0基础
是覆盖对于变化1 小时前
ubuntu22.04 进入不了系统设置
linux·ubuntu
应以大橘为重2 小时前
interrupt子系统中的数据结构
linux·数据结构·驱动开发
kfepiza2 小时前
硬盘分区格式方案之 MBR(Master Boot Record)主引导记录详解 笔记250407
linux·windows·笔记
mzak2 小时前
已经安装了pip,出现pip command not found【解决方法】
linux·pip·python3
sszdzq2 小时前
maven
java·maven
爱的叹息2 小时前
解决 IntelliJ IDEA 中 Maven 项目左侧项目视图未显示顶层目录问题的详细步骤说明
java·maven·intellij-idea
南风与鱼2 小时前
Linux 线程池
linux·线程池