说明
Maven 打包 jar 包可分为两种:可执行 jar 包和不可执行 jar 包。顾名思义,可执行就是可以通过命令启动程序:java -jar xxx-demo.jar
,不可执行则使用该命令会报no main manifest attribute, in xxx.jar
错误。
maven 打包 jar 包的四种方式
可执行 jar 包的必要条件:
程序代码中必须要有 main 方法;
在 jar 包中META-INF/MANIFEST.MF中指定Main-Class;
要能加载到依赖包。
不可执行 jar 包:
程序代码中是否有 main 方法无所谓
打成的 jar 包中META-INF/MANIFEST.MF中不会指定Main-Class。
依赖根据需要而决定。
不可执行 agent jar 包:
程序中需要有 premain 方法
打成的 jar 包中META-INF/MANIFEST.MF中要指定Premain-Class 以及 Agent-Class。
依赖需要打包。
使用maven-shade-plugin插件打包
使用maven-shade-plugin
打包,执行命令mvn clean package
,会在 target 目录下生成两个 jar 文件:xxx-demo.jar
和original-xxx-demo.jar
,其中xx-demo
是项目名称,当然这个名字会根据你的其他插件有所变化。没有 original 开头的 jar 包是包含了依赖jar 的,有 original 的则是不包含的。
需要注意的是,该插件打包依赖的时候,是把其他依赖文件的 class 文件连同包名一起拷贝到目标 jar 目录,并不是把依赖 jar 的 jar 包 copy 到目标 jar 目录下面。
1 2 3 4 5 6 7 8 9 10 11 ├── org │ └── objectweb │ └── asm │ ├── AnnotationVisitor.class │ ├── AnnotationWriter.class │ ├── Attribute.class │ ├── ByteVector.class │ ├── ClassAdapter.class │ ├── ClassReader.class │ ├── ClassVisitor.class │ ├── ClassWriter.class
如上树状结构,是项目依赖asm:asm-all:3.1.jar
包,打包后的目标 jar 里面的结构。
使用 maven-shade-plugin 插件时,某些情况下需要指定<id>
标签,如果不指定则会出现如下错误:
shade for parameter resource: Cannot find ‘resource’ in
class org.apache.maven.plugins.shade.resource.ManifestResourceTransformer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-shade-plugin</artifactId > <version > 3.0.0</version > <executions > <execution > <id > share-my-jar</id > <phase > package</phase > <goals > <goal > shade</goal > </goals > <configuration > <createDependencyReducedPom > false</createDependencyReducedPom > <artifactSet > <excludes > <exclude > com.google.code.findbugs:jsr305</exclude > <exclude > org.slf4j:*</exclude > <exclude > log4j:*</exclude > </excludes > </artifactSet > <filters > <filter > <artifact > *:*</artifact > <excludes > <exclude > META-INF/*.SF</exclude > <exclude > META-INF/*.DSA</exclude > <exclude > META-INF/*.RSA</exclude > </excludes > </filter > </filters > <transformers > <transformer implementation ="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" > <mainClass > com.openmind.asm.Main</mainClass > </transformer > <transformer implementation ="org.apache.maven.plugins.shade.resource.AppendingTransformer" > <resource > META-INF/spring.handlers</resource > </transformer > <transformer implementation ="org.apache.maven.plugins.shade.resource.AppendingTransformer" > <resource > META-INF/spring.schemas</resource > </transformer > </transformers > </configuration > </execution > </executions > </plugin >
使用maven-assembly-plugin打包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-assembly-plugin</artifactId > <version > 3.1.0</version > <configuration > <descriptorRefs > <descriptorRef > jar-with-dependencies</descriptorRef > </descriptorRefs > <archive > <manifest > <mainClass > </mainClass > </manifest > </archive > </configuration > <executions > <execution > <id > make-assembly</id > <phase > package</phase > <goals > <goal > single</goal > </goals > </execution > </executions > </plugin >
上面配置后,执行mvn clean package
后会在 target 目录下生成一个xxx-jar-with-dependencies.jar文件,这个文件不但包含了自己项目中的代码和资源,还包含了所有依赖包的内容。所以可以直接通过java -jar来运行。
如果maven-assembly-plugin
插件没有配置此段:
1 2 3 4 5 6 7 8 9 <executions > <execution > <id > make-assembly</id > <phase > package</phase > <goals > <goal > single</goal > </goals > </execution > </executions >
则需要在执行命令的时候加上assembly:single
,其中<phase>package</phase>
、<goal>single</goal>
表示在执行 package 打包时,执行 assembly:single
,所以当配置 executions 的代码块后可以直接使用mvn package
打包。
使用 maven-jar-plugin 和 maven-dependency-plugin 插件打包
maven-jar-plugin
用于生成META-INF/MANIFEST.MF文件的部分内容,<mainClass>com.xxg.Main</mainClass>
指定 MANIFEST.MF 中的 Main-Class,<addClasspath>true</addClasspath>
会在MANIFEST.MF加上Class-Path项并配置依赖包,<classpathPrefix>lib/</classpathPrefix>
指定依赖包所在目录,生成的信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Manifest-Version: 1.0 Implementation-Title: crack-agent Implementation-Version: 0.0 .1 -SNAPSHOT Premain-Class: com.openmind.asm.AgentMain Agent-Class: com.openmind.asm.AgentMain Can-Redefine-Classes: true Can-Retransform-Classes: true Main-Class: com.openmind.asm.Main Class-Path: lib/asm-3.1.jar lib/asm-all-3.1.jar Build-Jdk-Spec: 1.8 Created-By: Maven Archiver 3.4 .0
上面生成的清单信息与下面的配置有关。
与 Javaagent 有关的信息来自于 manifestEntries 配置标签相关
与入口和依赖包有关的信息与 manifest 配置标签相关
其他信息都是一些基本信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-jar-plugin</artifactId > <configuration > <classesDirectory > target/classes/</classesDirectory > <archive > <manifestEntries > <Agent-Class > com.openmind.asm.AgentMain</Agent-Class > <Premain-Class > com.openmind.asm.AgentMain</Premain-Class > <Can-Redefine-Classes > true</Can-Redefine-Classes > <Can-Retransform-Classes > true</Can-Retransform-Classes > <Class-Path > .</Class-Path > </manifestEntries > <manifest > <addClasspath > true</addClasspath > <classpath-prefix > lib/</classpath-prefix > <mainClass > com.openmind.asm.Main</mainClass > <useUniqueVersions > false</useUniqueVersions > </manifest > </archive > </configuration > </plugin > <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 > <type > jar</type > <includeTypes > jar</includeTypes > <includeScope > runtime</includeScope > <outputDirectory > ${project.build.directory}/lib</outputDirectory > </configuration > </execution > </executions > </plugin >
maven-dependency-plugin
插件用于将依赖包拷贝到<outputDirectory>${project.build.directory}/lib</outputDirectory>
指定的位置,即lib目录下。
注意,此方式生成的 jar 包中并没有 lib 目录,而包含依赖包的 lib 目录在 target 目录下面,和生成的目标 jar 通同级。如果要执行该 jar 包,就需要将 lib 目录一同复制到环境目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 >. ├── classes │ └── com │ └── openmind │ └── asm │ ├── AgentMain.class │ ├── Main.class │ ├── MyClassFileTransformer.class │ ├── MyClassVistor.class │ └── MyMethodVistor.class ├── crack-agent-0.0.1-SNAPSHOT.jar └── lib ├── asm-3.1.jar └── asm-all-3.1.jar
不过这种方式生成jar包有个缺点,就是生成的jar包太多不便于管理,而使用maven-shade-plugin插件打包
和使用maven-assembly-plugin打包
两种方式只生成单个jar文件,包含项目本身的代码、资源以及所有的依赖包。因此,就需要一种方式把 lib 包和目标 jar 整合在一起,那就是下面的使用maven-assembly-plugin引入配置打包
。
使用maven-assembly-plugin引入配置打包
不过,maven-assembly-plugin
插件大多数的时候还有另一种用法,需要额外配置一个 assembly.xml 文件配合该插件进行打包,该插件可以和maven-dependency-plugin
一起使用,也可单独使用。
不管是哪一种,如果需要用到清单相关信息,就必须增加maven-jar-plugin
插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-jar-plugin</artifactId > <configuration > <archive > <manifestEntries > <Agent-Class > com.openmind.asm.AgentMain</Agent-Class > <Premain-Class > com.openmind.asm.AgentMain</Premain-Class > <Can-Redefine-Classes > true</Can-Redefine-Classes > <Can-Retransform-Classes > true</Can-Retransform-Classes > </manifestEntries > <manifest > <addClasspath > true</addClasspath > <classpath-prefix > lib/</classpath-prefix > <mainClass > com.openmind.asm.Main</mainClass > </manifest > </archive > </configuration > </plugin >
单独使用maven-assembly-plugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-assembly-plugin</artifactId > <version > 3.1.0</version > <configuration > <descriptors > <descriptor > assembly.xml</descriptor > </descriptors > </configuration > <executions > <execution > <id > make-assembly</id > <phase > package</phase > <goals > <goal > single</goal > </goals > </execution > </executions > </plugin >
下面是 assembly.xml 的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <assembly xmlns ="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd" > <id > dist</id > <formats > <format > tar.gz</format > </formats > <includeBaseDirectory > false</includeBaseDirectory > <fileSets > <fileSet > <directory > ${project.build.directory}/dubbo/META-INF/assembly/bin</directory > <outputDirectory > bin</outputDirectory > <fileMode > 0755</fileMode > </fileSet > </fileSets > <dependencySets > <dependencySet > <outputDirectory > lib</outputDirectory > </dependencySet > </dependencySets > <files > <file > <source > core/target/${project.build.finalName}.jar</source > <outputDirectory > .</outputDirectory > <filtered > false</filtered > </file > </files > </assembly >
上面的配置打包会出现什么问题呢?就是自身的 jar 包打了两遍。第一遍是在<dependencySet>
标签中引入了一个包含版本号的 jar 包,第二次引入在<file>
标签中。当然,<file>
标签是可以不要的。我们把压缩包xxx-demo-dist.tar.gz
解压之后就会发现文件结构(此处项目名为crack-agent,在 build 标签中配置 finalName 为 crack-agent):
1 2 3 4 5 6 7 . ├── crack-agent.jar └── lib ├── asm-3.1.jar ├── asm-all-3.1.jar └── crack-agent-0.0.1-SNAPSHOT.jar
我们再解压目录执行java -jar crack-agent.jar
运行 ok!
配合maven-dependency-plugin
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-dependency-plugin</artifactId > <version > 3.1.1</version > <executions > <execution > <id > unpack</id > <phase > package</phase > <goals > <goal > unpack</goal > </goals > <configuration > <artifactItems > <artifactItem > <groupId > com.alibaba</groupId > <artifactId > dubbo</artifactId > <version > 2.5.3</version > <outputDirectory > ${project.build.directory}/dubbo</outputDirectory > <includes > META-INF/assembly/**</includes > </artifactItem > </artifactItems > </configuration > </execution > <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 > <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-assembly-plugin</artifactId > <version > 3.1.0</version > <configuration > <descriptors > <descriptor > assembly.xml</descriptor > </descriptors > <archive > <manifest > <mainClass > com.openmind.asm.AgentMain</mainClass > </manifest > </archive > </configuration > <executions > <execution > <id > make-assembly</id > <phase > package</phase > <goals > <goal > single</goal > </goals > </execution > </executions > </plugin >
下面是新配置的 assembly.xml(主要删除了<dependencySets>
的配置信息):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <assembly > <id > dist</id > <formats > <format > tar.gz</format > </formats > <includeBaseDirectory > false</includeBaseDirectory > <fileSets > <fileSet > <directory > ${project.build.directory}/dubbo/META-INF/assembly/bin</directory > <outputDirectory > bin</outputDirectory > <fileMode > 0755</fileMode > </fileSet > <fileSet > <directory > target/lib</directory > <outputDirectory > lib</outputDirectory > <fileMode > 0755</fileMode > <includes > <include > %regex[^((?!sources).)*\.jar$]</include > </includes > </fileSet > </fileSets > <files > <file > <source > target/${project.build.finalName}.jar</source > <outputDirectory > .</outputDirectory > <filtered > false</filtered > </file > </files > </assembly >
我们可以考到压缩包中内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 . ├── bin │ ├── dump.sh │ ├── restart.sh │ ├── server.sh │ ├── start.bat │ ├── start.sh │ └── stop.sh ├── crack-agent.jar └── lib ├── asm-3.1.jar └── asm-all-3.1.jar
配合maven-dependency-plugin
的好处是,处理资源更加灵活了,想用哪里的资源就去哪里获取资源,我可以 unpack拿取其他 jar 包的配置信息,我也可以 copy-dependencies,复制其他 jar 包。
spring-boot-maven-plugin+maven-dependency-plugin打包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <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 > <type > jar</type > <includeTypes > jar</includeTypes > <includeScope > runtime</includeScope > <outputDirectory > ${project.build.directory}/libs </outputDirectory > <excludeArtifactIds > demo-client</excludeArtifactIds > </configuration > </execution > </executions > </plugin > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.0.0.RELEASE</version > <configuration > <fork > true</fork > <mainClass > cn.xxx.yyy.AppMain</mainClass > </configuration > <executions > <execution > <goals > <goal > repackage</goal > </goals > </execution > </executions > </plugin >
spring-boot-maven-plugin 插件打包,会将依赖的 jar 包打包到目标 jar 一起。只针对 springboot 项目。打包jar 解压后看到如下目录层级:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 . ├── BOOT-INF │ ├── classes │ │ ├── app.xml │ │ ├── application-default.properties │ │ ├── cn │ │ ├── logback-spring.xml │ │ ├── mybatis │ │ └── spring │ └── lib │ ├── HdrHistogram-2.1.10.jar │ ├── android-json-0.0.20131108.vaadin1.jar │ ├── ... 省略 N 行 │ ├── spring-boot-2.0.0.RELEASE.jar │ ├── spring-boot-actuator-2.0.0.RELEASE.jar │ ├── spring-boot-actuator-autoconfigure-2.0.0.RELEASE.jar │ ├── spring-boot-autoconfigure-2.0.0.RELEASE.jar │ ├── spring-boot-starter-2.0.0.RELEASE.jar │ └── zookeeper-3.4.8.jar ├── META-INF │ ├── MANIFEST.MF │ └── maven │ └── org.springframework.boot └── org └── springframework └── boot └── loader ├── ExecutableArchiveLauncher.class ├── JarLauncher.class ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class ├── LaunchedURLClassLoader.class ├── Launcher.class ├── MainMethodRunner.class ├── PropertiesLauncher$1.class ├── PropertiesLauncher$ArchiveEntryFilter.class ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class ├── PropertiesLauncher.class ├── WarLauncher.class ├── archive ├── data ├── jar └── util
如果需要分离将依赖包和目标 jar 包分离,需要注释掉spring-boot-maven-plugin
包,加入maven-jar-plugin
包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-jar-plugin</artifactId > <configuration > <classesDirectory > target/classes/</classesDirectory > <archive > <manifest > <mainClass > com.tsingning.worksite.Application</mainClass > <useUniqueVersions > false</useUniqueVersions > <addClasspath > true</addClasspath > <classpathPrefix > lib/</classpathPrefix > </manifest > <manifestEntries > <Class-Path > .</Class-Path > </manifestEntries > </archive > </configuration > </plugin >
关于maven-jar-plugin
更多细节,可以参考上面其他组合的配置。
其他插件
maven-compiler-plugin 编译插件
1 2 3 4 5 6 7 8 9 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-compiler-plugin</artifactId > <version > 3.1</version > <configuration > <source > ${maven.compiler.source}</source > <target > ${maven.compiler.target}</target > </configuration > </plugin >
proguard-maven-plugin 代码混淆插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <plugin > <groupId > com.github.wvengen</groupId > <artifactId > proguard-maven-plugin</artifactId > <version > 2.0.11</version > <executions > <execution > <phase > package</phase > <goals > <goal > proguard</goal > </goals > </execution > </executions > <configuration > <attach > true</attach > <obfuscate > true</obfuscate > <attachArtifactClassifier > pg</attachArtifactClassifier > <proguardInclude > ${basedir}/proguard.conf</proguardInclude > <libs > <lib > ${java.home}/lib/rt.jar</lib > <lib > ${java.home}/lib/jce.jar</lib > </libs > <injar > classes</injar > <outjar > kara-pg.jar</outjar > <outputDirectory > ${project.build.directory}</outputDirectory > </configuration > </plugin >
maven-surefire-plugin 测试用例插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <version > 2.18.1</version > <configuration > <argLine > ${argLine} -Xmx1024m</argLine > <threadCount > 1</threadCount > <properties > <property > <name > junit</name > <value > false</value > </property > </properties > </configuration > <dependencies > <dependency > <groupId > org.apache.maven.surefire</groupId > <artifactId > surefire-testng</artifactId > <version > 3.0.0-M1</version > </dependency > <dependency > <groupId > org.apache.maven.surefire</groupId > <artifactId > surefire-junit47</artifactId > <version > 3.0.0-M1</version > </dependency > </dependencies > </plugin >
jacoco-maven-plugin 单元测试覆盖率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <plugin > <groupId > org.jacoco</groupId > <artifactId > jacoco-maven-plugin</artifactId > <version > 0.8.0</version > <executions > <execution > <id > default-prepare-agent</id > <goals > <goal > prepare-agent</goal > </goals > </execution > <execution > <id > default-report</id > <phase > prepare-package</phase > <goals > <goal > report</goal > </goals > </execution > </executions > </plugin >