maven打包jar依赖到外部

此去经年,夏梦未央。我以为你会是我最温暖的夏阳。只是我忘了,我们都无力阻止四季的轮转

Posted by yishuifengxiao on 2021-01-24

springBoot项目可以直接打包成jar并运行,无需自己安装配置Tomcat或者其他服务器,假设项目以常规的方式打包成一个整体的jar包部署,即配置文件和第三方依赖包都包含在jar包里,就会有以下的问题:

(1)项目运行的过程中,需要改动配置文件的话需要重新打包并部署。

(2)多个第三方依赖包都相近的项目部署在同一台服务器时,各自的jar包都包含了相同的第三方依赖包,这样第三方依赖包冗余造成了服务器资源的浪费以及降低了项目部署的效率。

将各项目的配置文件、第三方依赖包都提取到jar包外统一管理,这样既提升了项目打包效率又节约了服务器的磁盘消耗,同时项目的运维也是非常的方便,改动配置文件后只需要重启下服务就行了,无需重新构建部署。

一 配置文件放jar包外部管理

SpringBoot读取配置文件(application.properties)的优先级为:

  • jar包同级目录的config目录
  • jar包同级目录
  • classPath(即resources目录)的config目录
  • classPath目录

还有一种最高优先级的方式是项目启动时通过命令的方式指定配置文件:

java –jar -Dspring.config.location=xxx/xxx/xxxx.properties demo.jar

如果SpringBoot项目在优先级更高的位置找到了配置,就会忽略优先级更低的配置

所以我们只需要把配置文件拷贝出来放到与jar包同级的config目录中即可

二 依赖包放到外部

2.1 拷贝第三方依赖文件到指定目录

首先拷贝第三方依赖文件到指定目录,在项目里增加以下依赖

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<includeScope>compile</includeScope>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>

将仓库中的jar包打到对应的目录中。

使用mvn package打包后会在项目对应的target的lib下生成项目依赖的jar包

插件目标(goals)标签说明:

  • analyze:分析项目依赖,确定哪些是已使用已声明的,哪些是已使用未声明的,哪些是未使用已声明的

  • analyze-dep-mgt:分析项目依赖,列出已解析的依赖项与dependencyManagement中定义的依赖项不匹配的部分

  • analyze-report:分析项目依赖关系,并生成一个报告,该报告总结一下内容:使用和声明;使用和未声明的;未使用和声明

  • analyze-duplicate:分析pom.xml中的标记,确定重复声明的依赖项

  • build-classpath:告诉Maven以java.cp中使用的类路径格式从本地存储库输出依赖项的路径。类路径文件也可以与主要工件一起附加和安装/部署

  • copy:获取插件配置部分中定义的工件列表,并将它们复制到指定位置,重命名它们或根据需要剥离版本。如果本地存储库或者反应堆中都不存在远程工件,则此目标可以解决这些工件。

  • copy-dependencies:列出项目直接依赖项和(可选)传递性依赖项的列表,并将其复制到指定位置,如果需要,剥离版本。该目标也可以从命令行运行。

  • display-ancestors:显示项目所有祖先pom。在想要了解项目所有的父poms的连续集成系统中,这可能很有用。该目标也可以从命令行运行。

  • get:最终以可传递方式从指定的远程存储库解析单个工件

  • go-offline:让maven解决该项目所依赖的所有内容(依赖项、插件、报告),以准备脱机

  • list:解析别名,列出项目的所有依赖项

  • list-repositores:显示项目所有依赖关系,然后列出使用的存储库

  • properties:为每个项目依赖项设置一个属性,该属性包含文件系统上工件

  • purge-local-repository:清除本地存储库中的依赖,并重新解析

  • resolve:告诉Maven解析所有依赖项并显示版本。JAVA 9注意: 在使用Java 9运行时将显示模块名称。

  • resolve-plugins:告诉Maven解决插件及其依赖项

  • sources:告诉Maven解析所有依赖项及其源附件、并显示版本

  • tree:显示该项目的依赖关系树

  • unpack:与copy功能一直,但是会解压缩

  • unpack-dependencies:与copy-dependencies功能一致,只是会解压

2.2 编译出不带lib文件夹的jar包

修改pom.xml配置,编译出不带lib文件夹的jar包

<!--打包跳过测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<!--打包跳过测试-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.testdocker.TestdockerApplication</mainClass>
<!--这里不是错误-->
<layout>ZIP</layout>
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>

将jar包放入项目部署文件夹下

三 运行项目

一个完整的pom依赖示例如下

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.yishuifengxiao.work.zhaosheng.App</mainClass>
<!--这里不是错误-->
<layout>ZIP</layout>
<includes>
<include>
<groupId>nothing</groupId>
<artifactId>nothing</artifactId>
</include>
</includes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- copy资源文件 -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<includeScope>compile</includeScope>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>

</plugins>
</build>

将解压出来的lib文件夹,jar包,配置文件放到同一目录,运行下面命令

相对路径:java -Dloader.path=\lib -jar demo.jar

绝对路径:java -Dloader.path=/path/to/lib -jar demo.jar

-Dloader.path=lib文件夹路径

运行命令如下:

java -Dloader.path=lib -Dspring.config.location=resources/ -jar  demo.jar



nohup java -Dloader.path=lib -Dspring.config.location=resources/ -jar demo.jar &

注意

1、使用-Dloader.path需要在打包的时候增加ZIP,不指定的话-Dloader.path不生效。对于多个微服务瘦身打包建议使用maven-jar-plugin打包,避免因为spring-boot-maven-plugin打包机制导致的一些应用启动问题(已踩坑)

2、若存在不同版本依赖:比如项目A依赖Y库的1.0版本,项目B依赖Y库的2.0版本,那么可能会出现版本依赖冲突(两个版本不兼容的情况下),解决方案:

  2.1、能做到版本一致就保持使用同一个版本,保证版本一致。可以使用maven的版本依赖管理进行处理,即在父pom文件使用统一管理依赖版本

  2.2、让项目各自依赖所需的版本并打进war包中,把其他同版本的jar包放在同一个共享包下

测试发现依赖在查找时从上往下找,匹配到就用第一个,如下图会使用comm-0.0.1.jar版本的

四 第二种方案

另外一种启动方案是可以不加-Dloader.path=”D:develop/shared/fjar”来指定路径,直接使用如下指令启动

java -jar demo.jar

使用上述启动的话需要添加maven-jar-plugin插件,配置属性,另外在处理一些读取可执行jar中的文件时,可以使用maven-jar-plugin插件替换spring-boot-maven-plugin进行打包操作

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!--addClasspath表示需要加入到类构建路径-->
<addClasspath>true</addClasspath>
<!--classpathPrefix指定生成的Manifest文件中Class-Path依赖lib前面都加上路径,构建出lib/xx.jar-->
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.common.util.CommonUtilsApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>

上述插件效果就是在打成的包里META_INF目录下的MANIFEST.MF文件里增加Class-path对应jar,这样在后面应用刚启动时就会根据Class-Path的只去加载需要的版本依赖(解决在共享目录里存在多版本加载引用冲突问题),这个效果就等效加参数-classpath xxx(具体的jar)。此时就是将需要的jar目录lib放在和要运行的xxx.jar同级目录即可,启动时就可以不加-Dloader.path参数了,如果lib目录和要运行的xxx.jar不在同级目录的话,则需要使用-Dloader.path来启动

<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>(Main Class)</mainClass>
</manifest>
<manifestEntries>
<Class-Path>. resources/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<targetPath>${project.build.directory}/resources</targetPath>
</resource>
</resources>

这次使用了maven-jar-plugin,另外通过resource标签指定了resource文件夹的目录,这个方法需要将打包后的resource文件一并拷贝出来,修改后的pom文件如下:

之后将lib、resource、jar包一起拷贝出来,直接运行即可

java -jar xxx.jar

五 配置示例

5.1 完整配置示例

第一种配置

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<targetPath>${project.build.directory}/resources</targetPath>
</resource>
</resources>
<!-- jar包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!--打包跳过测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<!--打包跳过测试-->
<!-- 分离lib -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 依赖包输出目录,将来不打进jar包里 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<!-- 打jar包时忽略配置文件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!-- <excludes>-->
<!-- <exclude>**/*.yml</exclude>-->
<!-- <exclude>**/*.xml</exclude>-->
<!-- </excludes>-->
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.yishuifengxiao.work.demo.App</mainClass>
</manifest>
<manifestEntries>
<Class-Path>. resources/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>

</build>

第二个配置示例

<build>
<!-- jar包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!--打包跳过测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<!--打包跳过测试-->
<!-- 分离lib -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 依赖包输出目录,将来不打进jar包里 -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<!-- copy资源文件 -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- 打jar包时忽略配置文件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!-- <excludes>-->
<!-- <exclude>**/*.yml</exclude>-->
<!-- <exclude>**/*.xml</exclude>-->
<!-- </excludes>-->
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.yishuifengxiao.work.demo.App</mainClass>
</manifest>
<manifestEntries>
<Class-Path>. resources/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- spring boot repackage -->
<!-- <plugin>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-maven-plugin</artifactId>-->
<!-- <configuration>-->
<!-- <mainClass>com.yishuifengxiao.work.zhaosheng.App</mainClass>-->
<!-- &lt;!&ndash;这里不是错误&ndash;&gt;-->
<!-- <layout>ZIP</layout>-->
<!-- <includes>-->
<!-- <include>-->
<!-- <groupId>non-exists</groupId>-->
<!-- <artifactId>non-exists</artifactId>-->
<!-- </include>-->
<!-- </includes>-->
<!-- </configuration>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <goals>-->
<!-- <goal>repackage</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- </plugin>-->
</plugins>

</build>

5.2 通用配置

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>pro</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<build>
<!-- jar包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<!--打包跳过测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
<!--打包跳过测试-->
<!-- 分离lib -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 依赖包输出目录,将来不打进jar包里 --> <outputDirectory>${project.build.directory}/lib</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<!-- copy资源文件 -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- 打jar包时忽略配置文件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.yishuifengxiao.work.zhaosheng.App</mainClass>
</manifest>
<manifestEntries>
<Class-Path>. resources/</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</profile>


</profiles>

打包命令如下

mvn clean install -P pro

运行命令

java -jar xxx.jar

六 jar启动脚本

#!/bin/sh

# 该脚本为Linux下启动java程序的脚本
#
# author: luandy
# date: 2021/1/15
#
# 特别注意:
# 该脚本使用系统kill命令来强制终止指定的java程序进程。
# 所以在杀死进程前,可能会造成数据丢失或数据不完整。如果必须要考虑到这类情况,则需要改写此脚本,
#
#
# 根据实际情况来修改以下配置信息 ##################################

# JAVA应用程序的名称
APP_NAME=demo
# JAVA应用程序端口号
SERVER_PORT=9999
# jar包存放路径
JAR_PATH='/app/uid-consumer'
# jar包名称
JAR_NAME=uid-consumer-1.1.0-SNAPSHOT.jar
# PID 代表是PID文件
JAR_PID=$JAR_NAME\.pid
# 日志输出文件
LOG_FILE=logs

# java虚拟机启动参数
JAVA_OPTS="-Xms512m -Xmx512m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m -XX:ParallelGCThreads=4 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=utf-8 -Duser.timezone=GMT+08"

# 这里指定了时区和utf-8编码
# 根据实际情况来修改以上配置信息 ##################################


# 检查程序是否处于运行状态
is_exist() {
# 查询出应用服务的进程id,grep -v 是反向查询的意思,查找除了grep操作的run.jar的进程之外的所有进程
pid=`ps -ef|grep $JAR_NAME|grep -v grep|awk '{print $2}' `

# [ ]表示条件测试。注意这里的空格很重要。要注意在'['后面和']'前面都必须要有空格
# [ -z STRING ] 如果STRING的长度为零则返回为真,即空是真
# 如果不存在返回0,存在返回1
if [ -z "${pid}" ]; then
return 0
else
return 1
fi
}

# ######### Shell脚本中$0、$?、$!、$$、$*、$#、$@等的说明 #########

# $$ Shell本身的PID(ProcessID,即脚本运行的当前 进程ID号)
# $! Shell最后运行的后台Process的PID(后台运行的最后一个进程的 进程ID号)
# $? 最后运行的命令的结束代码(返回值)即执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
# $- 显示shell使用的当前选项,与set命令功能相同
# $* 所有参数列表。如"$*"用「"」括起来的情况、以"$1 $2$n"的形式输出所有参数,此选项参数可超过9个。
# $@ 所有参数列表。如"$@"用「"」括起来的情况、以"$1" "$2" … "$n" 的形式输出所有参数。
# $# 添加到Shell的参数个数
# $0 Shell本身的文件名
# $1$n 添加到Shell的各参数值。$1是第1参数、$2是第2参数…。

# 服务启动方法
start() {
is_exist
if [ $? -eq "1" ]; then
echo "$APP_NAME is already running pid is ${pid}"
else
# jar服务启动脚本
nohup java $JAVA_OPTS -Xloggc:$LOG_FILE/gc/gclog.log -XX:HeapDumpPath=$LOG_FILE/gc/HeapDump.hprof -jar $JAR_PATH/$JAR_NAME >./$LOG_FILE/run.log 2>&1 &
echo $! > $JAR_PID
echo "start $APP_NAME successed pid is $! "
tail -1000f $LOG_FILE/run.log
fi
}

# 服务停止方法
stop() {
# is_exist
pidf=$(cat $JAR_PID)
# echo "$pidf"
echo "pid = $pidf begin kill $pidf"
kill $pidf
rm -rf $JAR_PID
sleep 2
# 判断服务进程是否存在
is_exist
if [ $? -eq "1" ]; then
echo "pid = $pid begin kill -9 $pid"
kill -9 $pid
sleep 2
echo "$APP_NAME process stopped!"
else
echo "$APP_NAME is not running!"
fi
}

# 服务运行状态查看方法
status() {
is_exist
if [ $? -eq "1" ]; then
echo "$APP_NAME is running,pid is ${pid}"
else
echo "$APP_NAME is not running!"
fi
}

# 重启服务方法
restart() {
# 调用服务停止命令
stop
# 调用服务启动命令
start
}

# 帮助说明,用于提示输入参数信息
usage() {
echo "Usage: sh run-service.sh [ start | stop | restart | status ]"
exit 1
}

###################################
# 读取脚本的第一个参数($1),进行判断
# 参数取值范围:{ start | stop | restart | status }
# 如参数不在指定范围之内,则打印帮助信息
###################################
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
'start')
start
;;
'stop')
stop
;;
'restart')
restart
;;
'status')
status
;;
*)
usage
;;
esac
exit 0

参考链接

https://segmentfault.com/q/1010000037555356

https://www.cnblogs.com/kingsonfu/p/11431743.html

https://www.toutiao.com/i6917650637664780808/