前言
本文主要介绍我本人在使用maven打包一个普通spring项目使用网上各种教程都没有成功,最后多次尝试分析后成功的过程。
因为平时开发都是开发SSM、SpringBoot这一类web应用,或者为公司开发一些公共组件(导出成jar引入主项目)这样,开发控制台程序也都是使用使用基础的开发模式,建立普通的java项目然后手工导入依赖jar包,最后使用开发工具导出可执行jar包,没有接触过使用maven开发控制台程序导出jar包运行。
第一次尝试
根据自己掌握的知识点,肯定是使用maven-jar-plugin插件进行打包操作
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<archive>
<manifest>
<mainClass>org.test.App</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
执行maven package命令,没报错,去到target目录一看,输出的jar包只有十几K?这明显有问题呀,但是还是使用java -jar执行了一下报错:
很明显项目的依赖包没有被打包出来。于是去百度相关资料…
第二次尝试
关于maven如何打包一个普通的spring项目(输出一个执行main方法的jar包),网上很多资料是说使用maven-assembly-plugin这个插件进行打包。
第一次尝试打包使用以下配置:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<!-- 此处指定main方法入口的class -->
<mainClass>org.test.App</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
</execution>
</executions>
</plugin>
可以正常打包,但是在执行的时候出现以下错误:
Offending resource: class path resource [*****.xml]
于是百度相关错误,总结下来就是说执行的程序中缺少了Spring一些内置的配置文件,于是使用winRAR打开jar包查看内部的结构,发现所有的依赖jar包全部被解压成一个个class文件:
众所周知,spring内部大部分配置文件都是放在“META-INF”目录下,而且文件名称还是一样的,这些Spring的依赖包被解压后,所有的同名的内置配置文件岂不都相互覆盖了?难怪jar包执行起来报上面那个错误!
第三次尝试
于是找到新的方向:怎么使用maven-assembly-plugin插件打包出来的jar包中依赖包不被解压?
查找资料后,发现之前复制网上的pom build配置,是因为使用了maven-assembly-plugin中一个自带的配置文件,而这个配置文件中配置了打包是将所有依赖包解压!
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.test.App</mainClass>
</manifest>
</archive>
<!-- 这个的意思是依据一个id为“jar-with-dependencies”的配置文件内容来执行打包
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs> -->
<descriptors>
<!-- 这里的意思是依据指定的配置文件进行打包 -->
<descriptor>src/main/resource/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
jar-with-dependencies执行的默认配置文件内容:
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>true</unpack> <!-- 是否解压依赖jar包 -->
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
</fileSet>
</fileSets>
</assembly>
自定义配置文件assembly.xml:
<assembly>
<id>with-dependence</id><!-- 配置文件的标识,同时生成的jar包名字会把这个文本包含进去 -->
<formats>
<format>jar</format><!-- 打包类型,此处为jar -->
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>false</unpack><!-- 是否解压依赖jar包 -->
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.outputDirectory}</directory>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
</assembly>
执行 maven clean package 命令进行打包,正常执行,输出的jar包也有十几兆了,我以为已经可以了!执行后还是报错:
我打开jar包查看了一下内部的结构,依赖jar包也都在里面:
为什么会执行报错找不到依赖呢?
第四次尝试
脑洞了一下,使用SpringBoot的spring-boot-maven-plugin生成可执行jar包会怎么样:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.zc.largecash.cb.removeredis.App</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
导出的可执行jar包,可以正常运行!!!
查看了SpringBoot的spring-boot-maven-plugin生成可执行jar包内部文件结构,
然后再对比以前自己使用myeclipse导出的控制台程序,发现使用maven导出的jar包中“META-INF”下MANIFEST.MF缺少“Class-Path”!
于是手工编辑了MANIFEST.MF文件,将“Class-Path”加上:
Class-Path: ./a1.jar ./a2.jar ./b1.jar .........
执行,发现还是不行,结合MyEclipse导出的控制台程序中的MANIFEST.MF文件:
以及导出的jar包中,出现了一些eclipse的类:
认为应该原生的不支持读取jar包内的依赖jar包的class,于是将依赖jar包复制出来,放置在导出的可执行jar包同级目录,执行正常!
maven-jar-plugin也可以正常打包Spring普通项目
第三次尝试,与第一次使用maven-jar-plugin导出的可执行jar包,执行时出现的错误是一样的,于是回过头查看maven-jar-plugin导出的可执行jar包内的MANIFEST.MF文件,发现也是缺少“Class-Path”这一项参数的
找到maven-jar-plugin与“Class-Path”相关的资料发现:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<mainClass>org.test.App</mainClass>
添加以下2项配置,可以使得导出的可执行jar中MANIFEST.MF文件带有“Class-Path”这一项
<addClasspath>true</addClasspath><!-- true会在MANIFEST.MF加上Class-Path项并配置依赖包 -->
<classpathPrefix>lib/</classpathPrefix><!-- lib/指定依赖包所在目录 -->
</manifest>
</archive>
</configuration>
</plugin>
执行mevan package命令,成功,但是发现2个问题:
- 依赖jar包没有被导出
- 配置文件必须包含在导出的可执行jar包内部,不然无法被扫描到
以下为MANIFEST.MF文件内容:
查阅相关资料,2个问题都得以解决:
问题1. 使用maven-dependency-plugin插件,可以导出项目依赖jar包到指定目录:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<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>
这样一来,maven-jar-plugin配合使用maven-dependency-plugin就可以很好的导出可执行jar包
问题2.
想将配置文件放置在可执行jar包外面,很简单,只需要编辑一下MANIFEST.MF文件中“Class-Path”,添加你配置文件准备放置的位置的相对路径(或者绝对路径,绝对路径应该用的很少),如“ ./ ”则代表可执行jar包同级目录,这样就能将配置文件放在jar包可执行jar包同级目录也能被程序正常读取。
另外需要注意,“Class-Path”配置的值中,会有很多项,各项之间一定要用空格隔开,不能使用其他字符,并且需要确保每一行不能有太多字符,及时换行,每一行最前面需要有空格!
手工编辑MANIFEST.MF文件“Class-Path”值,毕竟还是太繁琐,每次打包都需要操作一遍,目前还没有找到maven-jar-plugin可以配置导出jar包时给MANIFEST.MF文件“Class-Path”值添加自定义信息!希望有大佬来解答一下这个问题
总结
- maven-assembly-plugin默认配置打包会将所有依赖jar包解压,这样Spring内置的很多同名配置文件将会相互覆盖,导致输出的可执行jar包执行报:Offending resource: class path resource [*****.xml]错误
- maven-jar-plugin无法将依赖jar包一并导出,需要配合使用maven-dependency-plugin
- 另外附上一片介绍MANIFEST.MF文件的博文