maven-shade-plugin简介
“This plugin provides the capability to package the artifact in an uber-jar, including its dependencies and to shade - i.e. rename - the packages of some of the dependencies.”,这句话简单的概述了maven-shade-plugin的作用,那就是作为一个超级jar包(uber-jar),在基本的管理依赖方面,需要支持更多的功能,如:
1,将依赖的jar包打包时传入到当前classes目录(默认在lib文件下)。
2,将依赖的jar包重命名,比如你在当前项目中依赖了tomcat-9.0的jar包,但是需求是需要项目在tomcat-8.5中允许,在出现版本冲突的时候,你可以使用maven-shade-plugin将你依赖的tomcat-9.0的jar包重命名并添加至你的classes目录下。
maven-shade-plugin使用
- 对jar包进行添加或排除
<project> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <artifactSet> <excludes> <exclude>classworlds:classworlds</exclude> <exclude>junit:junit</exclude> <exclude>jmock:*</exclude> <exclude>*:xml-apis</exclude> <exclude>org.apache.maven:lib:tests</exclude> <exclude>log4j:log4j:jar:</exclude> </excludes> </artifactSet> </configuration> </execution> </executions> </plugin> </plugins> </build> ... </project>
- 对jar包里面的类进行添加或排除
<project> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <filters> <filter> <artifact>junit:junit</artifact> <includes> <include>junit/framework/**</include> <include>org/junit/**</include> </includes> <excludes> <exclude>org/junit/experimental/**</exclude> <exclude>org/junit/runners/**</exclude> </excludes> </filter> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin> </plugins> </build> ... </project>
- 自动移除没有使用的依赖
<project> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>true</minimizeJar> </configuration> </execution> </executions> </plugin> </plugins> </build> ... </project>
- 组合使用
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>log4j:log4j</artifact>
<includes>
<include>**</include>
</includes>
</filter>
<filter>
<artifact>commons-logging:commons-logging</artifact>
<includes>
<include>**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
-
设置编译后的classes目录位置
如果需要将某个jar用作其他项目的依赖项,但是又因为其依赖项中直接包含类,因此会由于类路径上的重复类而导致类加载冲突。 为了解决这个问题,可以重新定位shade组件中包含的类,以创建其字节码的私有副本:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>org.codehaus.plexus.util</pattern>
<shadedPattern>org.shaded.plexus.util</shadedPattern>
<excludes>
<exclude>org.codehaus.plexus.util.xml.Xpp3Dom</exclude>
<exclude>org.codehaus.plexus.util.xml.pull.*</exclude>
</excludes>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
当然,我们也可以进一步的缩小其作用范围:
<project>
...
<relocation>
<pattern>org.codehaus.plexus.util</pattern>
<shadedPattern>org.shaded.plexus.util</shadedPattern>
<includes>
<include>org.codehaud.plexus.util.io.*</include>
</includes>
</relocation>
...
</project>
默认情况下,shade插件会覆盖基于项目的jar包,而生成包含所有依赖的jar包。但有时需要原始的jar包和shade后的jar包同时被部署,可以配置如下:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>jackofall</shadedClassifierName> <!-- 名称会作为后缀在shade构件jar包后 -->
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
想要生成一个可以执行的jar包,就需要指定当前应用程序下main类的位置:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.sonatype.haven.HavenCli</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
此代码段通过配置特殊的资源转换器在带shade插件的的JAR中的 MANIFEST.MF 中设置Main-Class条目。 其他条目也可以通过 manifestEntries 部分中的键值对添加到MANIFEST.MF:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>org.sonatype.haven.ExodusCli</Main-Class>
<Build-Number>123</Build-Number>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
ManifestResourceTransformer
ManifestResourceTransformer允许替换MANIFEST.MF中的现有条目并添加新条目:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>${app.main.class}</Main-Class>
<X-Compile-Source-JDK>${maven.compile.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compile.target}</X-Compile-Target-JDK>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
IncludeResourceTransformer
IncludeResourceTransformer允许以给定名称将项目文件包含在包中。
例如,以下示例在包中将README.txt作为META-INF目录中的README包含在内:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
<resource>META-INF/README</resource>
<file>README.txt</file>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
IncludeResourceTransformer
DontIncludeResourceTransformer允许以指定给定值结尾的方式排除资源。
例如,以下示例排除了所有以.txt结尾的资源。
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resource>.txt</resource>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
AppendingTransformer
某些jar包含具有相同文件名的其他资源(例如属性文件)。 为避免覆盖,您可以选择通过将它们的内容附加到一个文件中来合并它们。 一个很好的例子是同时聚合spring-context和plexus-spring jars。 它们都具有META-INF / spring.handlers文件,Spring使用该文件来处理XML模式名称空间。 您可以使用AppendingTransformer合并具有该特定名称的所有文件的内容,如下所示:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<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>
</plugins>
</build>
...
</project>
对于xml文件,您可以使用XmlAppendingTransformer:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.XmlAppendingTransformer">
<resource>META-INF/magic.xml</resource>
<!-- Add this to enable loading of DTDs
<ignoreDtd>false</ignoreDtd>
-->
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
PluginXmlResourceTransformer
提供某些接口的实现的JAR文件通常带有META-INF/services/目录,该目录将接口映射到其实现类以供服务定位器查找。 要重新定位这些实现类的类名,并将同一接口的多个实现合并到一个服务条目中,可以使用ServicesResourceTransformer:
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
maven-shade-plugin原理介绍
原理是利用了ASM框架来修改字节码:
static class RelocatorRemapper
extends Remapper
{
private final Pattern classPattern = Pattern.compile( "(\\[*)?L(.+);" );
List<Relocator> relocators;
RelocatorRemapper( List<Relocator> relocators )
{
this.relocators = relocators;
}
public boolean hasRelocators()
{
return !relocators.isEmpty();
}
public Object mapValue( Object object )
{
if ( object instanceof String )
{
String name = (String) object;
String value = name;
String prefix = "";
String suffix = "";
Matcher m = classPattern.matcher( name );
if ( m.matches() )
{
prefix = m.group( 1 ) + "L";
suffix = ";";
name = m.group( 2 );
}
for ( Relocator r : relocators )
{
if ( r.canRelocateClass( name ) )
{
value = prefix + r.relocateClass( name ) + suffix;
break;
}
else if ( r.canRelocatePath( name ) )
{
value = prefix + r.relocatePath( name ) + suffix;
break;
}
}
return value;
}
return super.mapValue( object );
}
public String map( String name )
{
String value = name;
String prefix = "";
String suffix = "";
Matcher m = classPattern.matcher( name );
if ( m.matches() )
{
prefix = m.group( 1 ) + "L";
suffix = ";";
name = m.group( 2 );
}
for ( Relocator r : relocators )
{
if ( r.canRelocatePath( name ) )
{
value = prefix + r.relocatePath( name ) + suffix;
break;
}
}
return value;
}
}