Maven項目打包需要注意到的那點事兒
- Maven是什么
- Maven打包插件的作用
- Maven打包后經常出現的問題
- maven構建可運行Jar包
- Maven打包的三種方式
- Maven打包的最簡單的方法
- maven-jar-plugin
- MANIFEST.MF文件部分
- MANIFEST.MF的文件內容
- jar包的拷貝機制
- 在pom.xml中配置
- maven-jar-plugin的局限性
- maven-shade-plugin
- 使用maven-shade-plugin插件打包
- maven-assembly-plugin
- maven指令
Maven是什么
Maven是一個流行的Java構建工具,它提供了許多插件來幫助開發人員自動化構建和部署Java應用程序。其中一個重要的插件是Maven打包插件,它可以將Java項目打包成可執行的JAR或WAR文件。在本文中,我們將深入探討Maven打包插件的技術細節和使用方法。
Maven打包插件的作用
Maven打包插件是一個用于打包Java項目的Maven插件。它可以將項目的源代碼、依賴項和其他資源打包成一個可執行的JAR或WAR文件。這個插件可以自動處理項目的依賴關系,并將它們打包到生成的文件中。此外,它還可以執行其他任務,如壓縮文件、生成文檔等。
首先先梳理一下關于打包 相關的常用的Maven插件工具:
- 使用清理插件:maven-clean-plugin: 執行清理刪除已有target目錄;
- 使用資源插件:maven-resources-plugin: 執行資源文件的處理
- 使用編譯插件:maven-compiler-plugin: 編譯所有源文件生成class文件至target\classes目錄下
- 使用資源插件:maven-resources-plugin: 執行測試資源文件的處理
- 使用編譯插件:maven-compiler-plugin: 編譯測試目錄下的所有源代碼
- 使用插件:maven-surefire-plugin: 運行測試用例
- 使用插件:maven-jar-plugin: 對編譯后生成的文件進行打包,
包名稱默認為:artifactId-version,包文件保存在target目錄下(這個生成的包不能在命令行中直接執行,因為我們還沒有入口類配置到Manifest資源配置文件中去,后續會闡述)。
注意:不管是compile、package還是install等前三個步驟都是必不可少的。
Maven打包后經常出現的問題
- Maven可以使用mvn package指令對項目進行打包,如果使用Java -jar xxx.jar執行運行jar文件,會出現" no main manifest attribute, in xxx.jar"(沒有設置Main-Class)、ClassNotFoundException(找不到依賴包)等錯誤。
這個原因屬于maven沒有執行構建:maven構建可運行Jar包。
maven構建可運行Jar包
要想jar包能直接通過java -jar xxx.jar運行,需要滿足:
1. 在jar包中的META-INF/MANIFEST.MF中指定Main-Class,這樣才能確定程序的入口在哪里
2. 要能加載到依賴包,使用Maven有以下幾種方法可以生成能直接運行的jar包,可以根據需要選擇一種合適的方法
Maven打包的三種方式
Maven打包的最簡單的方法
Maven打包插件的配置非常簡單,只需要在項目的pom.xml文件中添加以下代碼即可。
maven-jar-plugin
首先是在maven項目的pom.xml中添加打包的插件,這里有很多種方式的。最最簡單的就是只使用maven-compiler-plugin、maven-jar-plugin插件,并且指定程序入口。
pom.xml文件為:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>xx</groupId><artifactId>xx</artifactId><version>1.0-SNAPSHOT</version><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifest><addClasspath>true</addClasspath><useUniqueVersions>false</useUniqueVersions><classpathPrefix>lib/</classpathPrefix><mainClass>com.xx.Main</mainClass></manifest></archive></configuration></plugin></plugins></build>
</project>
這個配置中,我們使用了Maven的maven-jar-plugin插件來打包項目。在插件的配置中,我們指定了生成的JAR文件的元數據,包括主類和類路徑。這些信息將被寫入JAR文件的MANIFEST.MF文件中,以便Java虛擬機可以正確地執行JAR文件。
MANIFEST.MF文件部分
- com.xx.Main指定MANIFEST.MF中的Main-Class
- true會在MANIFEST.MF加上Class-Path項并配置依賴包
- lib/指定依賴包所在目錄。
MANIFEST.MF的文件內容
通過maven-jar-plugin插件生成的MANIFEST.MF文件片段:
Class-Path: lib/x.jar lib/xx.jar
Main-Class: com.xxg.Main
當然生成MANIFEST.MF文件還不夠,maven-dependency-plugin插件用于將依賴包拷貝到${project.build.directory}/lib指定的位置,即lib目錄下。
jar包的拷貝機制
配置完成后,通過mvn package指令打包,會在target目錄下生成jar包,并將依賴包拷貝到target/lib目錄下。
在pom.xml中配置
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.xx.Main</mainClass> </manifest> </archive> </configuration> </plugin> <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> </plugins>
</build>
指定了Main-Class,有了依賴包,那么就可以直接通過java -jar xxx.jar運行jar包。這種方式生成jar包有個缺點,就是生成的jar包太多不便于管理,下面兩種方式只生成一個jar文件,包含項目本身的代碼、資源以及所有的依賴包,接下來我們 好好分析一下這種打包方式的局限性。
maven-jar-plugin的局限性
如果一個maven項目中有多個子目錄,每一個子目錄中的pom.xml對應一個項目,它的作用范圍只有這一個目錄下的。比如掃描配置文件,如果要讓一個目錄下的pom.xml掃描另一個目錄下的配置文件,那是做不到的。在打jar包的時候,只運行當前的pom.xml文件。
當然也有其他的打包方法,比如使用spring-boot-maven-plugin插件在打Jar包時,會引入依賴包。
maven-shade-plugin
由于上面的打包過程實在是過于的繁瑣,而且也沒有利用到maven管理項目的特色。接下來我們采用maven中的maven-shade-plugin插件進行資源打包,在pom.xml中,加入如下的信息來加入插件。
使用maven-shade-plugin插件打包
在pom.xml中配置:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.Mani festResourceTransformer"> <mainClass>com.xx.Main</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins>
</build>
配置完成后,執行mvn package即可打包。在target目錄下會生成兩個jar包,注意不是original-xxx.jar文件,而是另外一個。
和maven-assembly-plugin一樣,生成的jar文件包含了所有依賴,所以可以直接運行。如果項目中用到了Spring Framework,將依賴打到一個jar包中,運行時會出現讀取XML schema文件出錯。
原因是Spring Framework的多個jar包中包含相同的文件spring.handlers和spring.schemas,如果生成一個jar包會互相覆蓋。為了避免互相影響,可以使用AppendingTransformer來對文件內容追加合并:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.Mani festResourceTransformer"> <mainClass>com.xxg.Main</mainClass> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.App endingTransformer"> <resource>META-INF/spring.handlers</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.App endingTransformer"> <resource>META-INF/spring.schemas</resource> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins>
</build>
這里面配置了一個configuration標簽內容,在此標簽下面 有一個transformer標簽,用來配置Main函數的入口 ( com.xx.Main),當然此標簽內容很復雜,不是上面寫的那么簡單,上面之所以如此簡單,是因為在所有類中(包括第三方Jar)只有一個Main方法。如果第三方jar中有Main方法,就要進行額外的配置,上面這么配置,不一定能執行成功。
在加入這段代碼到pom.xml之后,我們就可以用maven的命令去打包了。其指令如下:
- mvn clean compile :清除之前target編譯文件并重新編譯
- mvn clean package :對項目進行打包(因為配置過插件,所以jar包是可執行的)
- mvn clean install :安裝項目,然后就可以使用了
上面的方法,我們還需要點擊很多命令去打包。這次利用一個新的插件,可以打包更簡單。同樣,在pom.xml中加入如下代碼。上文的maven-shade-plugin插件代碼可以刪除。
maven-assembly-plugin
使用maven-assembly-plugin插件打包,這里同樣配置了一個manifest標簽來配置Main函數的入口。然后通過如下指令來實現打包。
在pom.xml中配置:
<plugin><artifactId>maven-assembly-plugin</artifactId><version>2.4</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><archive><manifest><mainClass>Main.Main</mainClass></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin>
maven指令
mvn assembly:assembly
含有依賴方面所對應的jar包依賴的核心數據包
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.5.5</version> <configuration> <archive> <manifest> <mainClass>com.xxg.Main</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> </plugins>
</build>
打包方式:
mvn package assembly:single
打包后會在target目錄下生成一個xxx-jar-with-dependencies.jar文件,這個文件不但包含了自己項目中的代碼和資源,還包含了所有依賴包的內容。所以可以直接通過java -jar來運行。此外還可以直接通過mvn package來打包,無需assembly:single,不過需要加上一些配置:
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.5.5</version> <configuration> <archive> <manifest> <mainClass>com.xxg.Main</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins>
</build>
其中package、single即表示在執行package打包時,執行assembly:single,所以可以直接使用mvn package打包。
不過,如果項目中用到spring Framework,用這種方式打出來的包運行時會出錯,使用下面的方法三可以處理。