maven:关于jar/war打包(胖包瘦包)

可以先戳这个了解:大小不同的包名称

==================================================================================

最近在看Spring Boot,有一段关于打包的描述:

Executable jars and Java

Java does not provide a standard way to load nested jar files (jar files that are themselves contained within a jar). This can be problematic if you are looking to distribute a self-contained application.

To solve this problem, many developers use “uber” jars. An uber jar packages all the classes from all the application’s dependencies into a single archive. The problem with this approach is that it becomes hard to see which libraries are in your application. It can also be problematic if the same filename is used (but with different content) in multiple jars.

Spring Boot takes a different approach and lets you actually nest jars directly.

Spring Boot通过插件重新打包,打的包是嵌套的Jar包nested jar files (jar files that are themselves contained within a jar),即Jar of Jars

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

这个方法打的包,依赖的第三方包依然以jar包的形式存在,可以避免因为解压所有依赖第三方jar包(shade与assembly方式)而出现同包同类被覆盖的问题,但由于这样不被JVM原生支持,因为JVM的ClassLoader 仅支持装载嵌套class的jar包,所以需要自定义ClassLoader以支持嵌套 jar的jar包,看一眼MANIFEST.MF就明白了。

完整配置如下:

    <!-- 使用spring-boot-maven-plugin打fat Jar包 -->
        <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <!--<includes>
                    <include>**/*.xml</include>
                </includes>-->
                <excludes>
                    <exclude>%regex[.*\.java]</exclude>
                </excludes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

*******************************************************************************

试了一下maven的打包,如果不用任何插件的话:

Jar包:只打包当前工程的class(不会打包依赖的第三方包),也就是上图中的Skinny包

War包:打包当前工程的class,依赖的第三方包按照java web的目录结构,放到了WEB-INF/lib下

********************************************************************************

默认的maven编译插件

  <build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${project.build.sourceEncoding}</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

通过shade和assembly方式打成fat/uber包,由于需要把所以依赖的第三包全部解压,然后与当前工程的class一起打成一个Jar包,缺点也很明显,也就是上面Spring Boot提到的

The problem with this approach is that it becomes hard to see which libraries are in your application. It can also be problematic if the same filename is used (but with different content) in multiple jars.

使用maven的shade打包插件:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.4</version>
    <configuration>
        <transformers>
            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>com.etoak.tian.PatchPkg</mainClass>
            </transformer>
        </transformers>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>

使用maven的assembly打包插件:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.3.0</version>
    <configuration>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
    </configuration>
    <executions>
        <execution>
            <id>assemble-all</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

感兴趣的可以对比这两种方式的区别。

*****************************************************

微服务开发过程中,多个模块均依赖一些共同的第三方包,若每个模块都打成fat/uber包的话,每次因为修改业务代码或者配置文件,又要重新打包(包括所有依赖的第三方包)再ship部署,很不科学,所有能不能把所有的依赖第三方包分离出来,把业务相关的配置文件也分离出来?这样如果依赖包和配置文件没有变动的话,只需要重新打包当前工程中的业务相关class。

 <build>
        <!-- 排除本工程的配置及资源文件(统一在ws工程配置) -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>**/*.properties</exclude>
                    <exclude>**/*.xml</exclude>
                    <exclude>**/*.yml</exclude>
                </excludes>
            </resource>
        </resources>

        <plugins>
            <!-- maven打包插件(打包成skinny Jar) begin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <classesDirectory>target/classes/</classesDirectory>
                    <finalName>${project.artifactId}-${project.version}</finalName>
                    <!-- Jar包只保留class文件 -->
                    <includes>
                        <!--<include>**/*.class</include>-->
                        <include>%regex[.*\.class]</include>
                    </includes>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                </configuration>
            </plugin>
            <!-- maven打包插件 end -->
        </plugins>
    </build>

参考示例:

<build>
    <finalName>名称</finalName>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <fork>false</fork>
                <source>${java.version}</source>
                <target>${java.version}</target>
                <encoding>UTF-8</encoding>
                <meminitial>2048m</meminitial>
                <maxmem>20480m</maxmem>
            </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>
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>


完整的配置如下:

<!-- 打成skiny Jar包,分离依赖的第三方包以及配置文件 -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <!--<includes>
                    <include>**/*.xml</include>
                </includes>-->
                <excludes>
                    <exclude>%regex[.*\.java]</exclude>
                </excludes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**</include>
                </includes>
            </resource>
        </resources>


        <plugins>
            <!--<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>-->

            <!-- 配置将要打包的skinny Jar文件 begin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <classesDirectory>target/classes/</classesDirectory>
                    <finalName>${project.artifactId}-${project.version}</finalName>
                    <!-- 配置MANIFEST.MF文件,将分离出的lib和resources添加到classpath中,指定入口类 -->
                    <archive>
                        <manifest>
                            <mainClass>com.etoak.demo06.Demo06Application</mainClass>
                            <useUniqueVersions>false</useUniqueVersions>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>./resources/</Class-Path>
                        </manifestEntries>
                    </archive>
                    <!-- Jar包只保留class文件 -->
                    <includes>
                        <!--<include>**/*.class</include>-->
                        <include>%regex[.*\.class]</include>
                    </includes>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                </configuration>
            </plugin>
            <!-- 配置将要打包的skinny Jar文件 end -->

            <!-- 分离当前工程依赖的所有第三方包并存放到lib目录下 begin -->
            <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>
                            <type>jar</type>
                            <includeTypes>jar</includeTypes>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <!-- 是否排除间接依赖的包,默认false -->
                            <!--<excludeTransitive>false</excludeTransitive>-->
                            <!-- jar包是否去掉版本信息,默认false -->
                            <stripVersion>true</stripVersion>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- 分离当前工程依赖的所有第三方包并存放到lib目录下 end -->


            <!-- 分离当前工程的配置文件并存放到resources目录下 begin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <resources>
                                <!-- src/main/java目录下所有的非源码文件 -->
                                <resource>
                                    <directory>src/main/java</directory>
                                    <!-- 是否区分环境进行过滤 -->
                                    <filtering>false</filtering>
                                    <excludes>
                                        <exclude>%regex[.*\.java]</exclude>
                                    </excludes>
                                </resource>
                                <!-- src/main/resources目录下所有的文件 -->
                                <resource>
                                    <directory>src/main/resources</directory>
                                    <filtering>false</filtering>
                                    <includes>
                                        <include>**</include>
                                    </includes>
                                </resource>
                            </resources>
                            <outputDirectory>${project.build.directory}/resources</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- 分离当前工程的配置文件并存放到resources目录下 end -->
        </plugins>
    </build>

PS:还有一个问题没有解决,以下是当前工程的依赖tree

[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.4.5:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.4.5:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.4.5:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.4.5:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.4.5:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.3:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.13.3:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.27:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.4.5:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.11.4:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.11.4:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.11.4:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.4:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.4:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.4:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.4.5:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.45:compile
[INFO] |  |  +- org.glassfish:jakarta.el:jar:3.0.3:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.45:compile
[INFO] |  +- org.springframework:spring-web:jar:5.3.6:compile
[INFO] |  |  \- org.springframework:spring-beans:jar:5.3.6:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.3.6:compile
[INFO] |     +- org.springframework:spring-aop:jar:5.3.6:compile
[INFO] |     +- org.springframework:spring-context:jar:5.3.6:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.3.6:compile
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:2.4.5:test
[INFO]    +- org.springframework.boot:spring-boot-test:jar:2.4.5:test
[INFO]    +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.4.5:test
[INFO]    +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO]    |  +- net.minidev:json-smart:jar:2.3:test
[INFO]    |  |  \- net.minidev:accessors-smart:jar:1.2:test
[INFO]    |  |     \- org.ow2.asm:asm:jar:5.0.4:test
[INFO]    |  \- org.slf4j:slf4j-api:jar:1.7.30:compile
[INFO]    +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:test
[INFO]    |  \- jakarta.activation:jakarta.activation-api:jar:1.2.2:test
[INFO]    +- org.assertj:assertj-core:jar:3.18.1:test
[INFO]    +- org.hamcrest:hamcrest:jar:2.2:test
[INFO]    +- org.junit.jupiter:junit-jupiter:jar:5.7.1:test
[INFO]    |  +- org.junit.jupiter:junit-jupiter-api:jar:5.7.1:test
[INFO]    |  |  +- org.apiguardian:apiguardian-api:jar:1.1.0:test
[INFO]    |  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO]    |  |  \- org.junit.platform:junit-platform-commons:jar:1.7.1:test
[INFO]    |  +- org.junit.jupiter:junit-jupiter-params:jar:5.7.1:test
[INFO]    |  \- org.junit.jupiter:junit-jupiter-engine:jar:5.7.1:test
[INFO]    |     \- org.junit.platform:junit-platform-engine:jar:1.7.1:test
[INFO]    +- org.mockito:mockito-core:jar:3.6.28:test
[INFO]    |  +- net.bytebuddy:byte-buddy:jar:1.10.22:test
[INFO]    |  +- net.bytebuddy:byte-buddy-agent:jar:1.10.22:test
[INFO]    |  \- org.objenesis:objenesis:jar:3.1:test
[INFO]    +- org.mockito:mockito-junit-jupiter:jar:3.6.28:test
[INFO]    +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO]    |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO]    +- org.springframework:spring-core:jar:5.3.6:compile
[INFO]    |  \- org.springframework:spring-jcl:jar:5.3.6:compile
[INFO]    +- org.springframework:spring-test:jar:5.3.6:test
[INFO]    \- org.xmlunit:xmlunit-core:jar:2.7.0:test

compile范围的包是32个,test范围的包是28个,打包之后lib下面有60个包,但是MANIFEST.MF中的Class-Path才指定了32个jar,lib中怎么把test的给排除掉?有成功的小伙伴麻烦给我留个言,学习一下。

另外,使用spring-boot-maven-plugin打包的时候还发现一个问题,所有依赖的starter相关的包,比如

spring-boot-starter-2.4.5.jar
spring-boot-starter-json-2.4.5.jar
spring-boot-starter-logging-2.4.5.jar
spring-boot-starter-tomcat-2.4.5.jar
spring-boot-starter-web-2.4.5.jar

BOOT-INF/lib中并没有出现,而是多出来一个spring-boot-jarmode-layertools-2.4.5.jar包,这个原理是什么?是SpringBoot把所有的starter合并了吗?

20231018补充:

如果工程新增一个lib,每次都需要打包主工程,可以考虑在MANIFEST.MF不添加jar到classpath,而是在启动主工程的时候动态指定,例如:

java -Dloader.path=./lib -jar mp-code-generator.jar


有的时候只想把spring boot工程打包当作一个类库,需要排除掉所有的配置文件以及启动类:

<build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <exclude>*.properties</exclude>
                    <exclude>*.xml</exclude>
                    <exclude>*.yml</exclude>
                </excludes>
            </resource>
        </resources>

        <plugins>
            <!-- 排除启动类 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <excludes>
                        <exclude>
                            <!-- **/XxxApplication.java -->
com/etoak/XxxApplication.java
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

或者

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.etoak.MainClass</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

指定打包输出目录:

<plugin> 
        <artifactId>maven-antrun-plugin</artifactId>  
        <executions> 
          <execution> 
            <id>copy</id>  
            <phase>package</phase>  
            <goals> 
              <goal>run</goal> 
            </goals>  
            <configuration> 
              <tasks> 
                <copy todir="E:\libs"> 
                  <fileset dir="${project.build.directory}"> 
                    <include name="*.jar"/> 
                  </fileset> 
                </copy> 
              </tasks> 
            </configuration> 
          </execution> 
        </executions> 
      </plugin> 

推送仓库:

<distributionManagement>
    <repository>
        <id>xxx</id>
        <name>xxx</name>
        <url>xxx</url>
    </repository>
    <snapshotRepository>
        <id>xxx-snapshots</id>
        <name>xxx-snapshots</name>
        <url>xxx-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

XXL-job打包参考:

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- docker -->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>0.4.13</version>
                <configuration>
                    <!-- made of '[a-z0-9-_.]' -->
                    <imageName>${project.artifactId}:${project.version}</imageName>
                    <dockerDirectory>${project.basedir}</dockerDirectory>
                    <resources>
                        <resource>
                            <targetPath>/</targetPath>
                            <directory>${project.build.directory}</directory>
                            <include>${project.build.finalName}.jar</include>
                        </resource>
                    </resources>
                </configuration>
            </plugin>
        </plugins>
    </build>

Maven常用properties

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wsdhla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值