Maven 管理項目文件周期5個技巧
Maven 是為 Java™ 開發(fā)人員提供的一個極為優(yōu)秀的構(gòu)建工具,您也可以使用它來管理您的項目生命周期。作為一個生命周期管理工具,Maven 是基于階段操作的,而不像 Ant 是基于 “任務(wù)” 構(gòu)建的。Maven 完成項目生命周期的所有階段,包括驗證、代碼生成、編譯、測試、打包、集成測試、安裝、部署、以及項目網(wǎng)站創(chuàng)建和部署。
為了更好地理解 Maven 和傳統(tǒng)構(gòu)建工具的不同,我們來看看構(gòu)建一個 JAR 文件和一個 EAR 文件的過程。使用 Ant,您可能需要定義專有任務(wù)來組裝每個工件。另一方面,Maven 可以為您完成大部分工作:您只需要告訴它是一個 JAR 文件還是一個 EAR 文件,然后指示它來完成 “打包” 過程。Maven 將會找到所需的資源,然后構(gòu)建文件。
本文的 5 個技巧目的是幫助您解決即將出現(xiàn)的一些問題:使用 Maven 管理您的應(yīng)用程序的生命周期時,將會出現(xiàn)的編程場景。
1. 技巧可執(zhí)行的 JAR 文件
使用 Maven 構(gòu)建一個 JAR 文件比較容易:只要定義項目包裝為 “jar”,然后執(zhí)行包裝生命周期階段即可。但是定義一個可執(zhí)行 JAR 文件卻比較麻煩。采取以下步驟可以更高效:
在您定義可執(zhí)行類的 JAR 的 MANIFEST.MF 文件中定義一個 main 類。(MANIFEST.MF 是包裝您的應(yīng)用程序時 Maven 生成的。)
找到您項目依賴的所有庫。
在您的 MANIFEST.MF 文件中包含那些庫,便于您的應(yīng)用程序找到它們。
您可以手工進(jìn)行這些操作,或者要想更高效,您可以使用兩個 Maven 插件幫助您完成:maven-jar-plugin 和 maven-dependency-plugin。
maven-jar-plugin
maven-jar-plugin 可以做很多事情,但在這里,我們只對使用它來修改默認(rèn) MANIFEST.MF 文件的內(nèi)容感興趣。在您的 POM 文件的插件部分添加清單 1 所示代碼:
清單 1. 使用 maven-jar-plugin 修改 MANIFEST.MF
- <plugin>
- <groupId>org.apache.maven.pluginsgroupId>
- <artifactId>maven-jar-pluginartifactId>
- <configuration>
- <archive>
- <manifest>
- <addClasspath>trueaddClasspath>
- <classpathPrefix>lib/classpathPrefix>
- <mainClass>com.mypackage.MyClassmainClass>
- manifest>
- archive>
- configuration>
- plugin>
所有 Maven 插件通過一個
- addClassPath:將該元素設(shè)置為 true 告知 maven-jar-plugin 添加一個 Class-Path 元素到 MANIFEST.MF 文件,以及在 Class-Path 元素中包括所有依賴項。
- classpathPrefix:如果您計劃在同一目錄下包含有您的所有依賴項,作為您將構(gòu)建的 JAR,那么您可以忽略它;否則使用 classpathPrefix 來指定所有依賴 JAR 文件的前綴。在清單 1 中,classpathPrefix 指出,相對存檔文件,所有的依賴項應(yīng)該位于 “lib” 文件夾。
- mainClass:當(dāng)用戶使用 lib 命令執(zhí)行 JAR 文件時,使用該元素定義將要執(zhí)行的類名。
maven-dependency-plugin
當(dāng)您使用這 3 個元素配置好了 MANIFEST.MF 文件之后,下一步是將所有的依賴項復(fù)制到 lib 文件夾。為此,使用 maven-dependency-plugin,如清單 2 所示:
清單 2. 使用 maven-dependency-plugin 將依賴項復(fù)制到庫
- <plugin>
- <groupId>org.apache.maven.pluginsgroupId>
- <artifactId>maven-dependency-pluginartifactId>
- <executions>
- <execution>
- <id>copyid>
- <phase>installphase>
- <goals>
- <goal>copy-dependenciesgoal>
- goals>
- <configuration>
- <outputDirectory>
- ${project.build.directory}/lib
- outputDirectory>
- configuration>
- execution>
- executions>
- plugin>
maven-dependency-plugin 有一個 copy-dependencies,目標(biāo)是將您的依賴項復(fù)制到您所選擇的目錄。本例中,我將依賴項復(fù)制到 build 目錄下的 lib 目錄(project-home/target/lib)。
將您的依賴項和修改的 MANIFEST.MF 放在適當(dāng)?shù)奈恢煤螅涂梢杂靡粋€簡單的命令啟動應(yīng)用程序:
- java -jar jarfilename.jar
#p#
技巧2. 定制 MANIFEST.MF
雖然 maven-jar-plugin 允許您修改 MANIFEST.MF 文件的共有部分,但有時候您需要一個更個性化的 MANIFEST.MF。解決方案是雙重的:
在一個 “模板” MANIFEST.MF 文件中定義您的所有定制配置。
配置 maven-jar-plugin 來使用您的 MANIFEST.MF 文件,然后使用一些 Maven 配置增強(qiáng)。
例如,考慮一個包含 Java 代理的 JAR 文件。要運行一個 Java 代理,需要定義 Premain-Class 和設(shè)置許可。清單 3 展示了這樣一個 MANIFEST.MF 文件的內(nèi)容:
清單 3. 在一個定制的 MANIFEST.MF 文件中定義 Premain-Class
- Manifest-Version: 1.0
- Premain-Class: com.geekcap.openapm.jvm.agent.Agent
- Can-Redefine-Classes: true
- Can-Retransform-Classes: true
- Can-Set-Native-Method-Prefix: true
在 清單 3 中,我已指定 Premain-Class - com.geekcap.openapm.jvm.agent.Agent 被授權(quán)許可來對類進(jìn)行重定義和再轉(zhuǎn)換。接下來,我更新 maven-jar-plugin 代碼來包含 MANIFEST.MF 文件。如清單 4 所示:
清單 4. 包含 Premain-Class
- <plugin>
- <groupId>org.apache.maven.pluginsgroupId>
- <artifactId>maven-jar-pluginartifactId>
- <configuration>
- <archive>
- <manifestFile>
- src/main/resources/META-INF/MANIFEST.MF
- manifestFile>
- <manifest>
- <addClasspath>trueaddClasspath>
- <classpathPrefix>lib/classpathPrefix>
- <mainClass>
- com.geekcap.openapm.ui.PerformanceAnalyzer
- mainClass>
- manifest>
- archive>
- configuration>
- plugin>
Maven 3Maven 2 已確立了它作為一種最流行和最易使用的開源 Java 生命周期管理工具的地位。Maven 3,2010 年 9 月升級為 alpha 5,帶來一些期待已久的改進(jìn)。在 參考資料 部分尋找 Maven 的新功能。
這是一個很有趣的示例,因為它既定義了一個 Premain-Class — 允許 JAR 文件作為一個 Java 代理運行,也有一個 mainClass — 允許它作為一個可執(zhí)行的 JAR 文件運行。在這個特殊的例子中,我使用 OpenAPM(我已構(gòu)建的一個代碼跟蹤工具)來定義將被 Java 代理和一個用戶界面記錄的代碼跟蹤。簡而言之,這個示例展示一個顯式清單文件與動態(tài)修改相結(jié)合的力量。
#p#
技巧3. 依賴項樹
Maven 一個最有用的功能是它支持依賴項管理:您只需要定義您應(yīng)用程序依賴的庫,Maven 找到它們、下載它們、然后使用它們編譯您的代碼。
必要時,您需要知道具體依賴項的來源 — 這樣您就可以找到同一個 JAR 文件的不同版本的區(qū)別和矛盾。這種情況下,您將需要防止將一個版本的 JAR 文件包含在您的構(gòu)建中,但是首先您需要定位保存 JAR 的依賴項。
一旦您知道下列命令,那么定位依賴項往往是相當(dāng)容易的:
- mvn dependency:tree
dependency:tree 參數(shù)顯示您的所有直接依賴項,然后顯示所有子依賴項(以及它們的子依賴項,等等)。例如,清單 5 節(jié)選自我的一個依賴項所需要的客戶端庫:
清單 5. Maven 依賴項樹
- [INFO] ------------------------------------------------------------------------
- [INFO] Building Client library for communicating with the LDE
- [INFO] task-segment: [dependency:tree]
- [INFO] ------------------------------------------------------------------------
- [INFO] [dependency:tree {execution: default-cli}]
- [INFO] com.lmt.pos:sis-client:jar:2.1.14
- [INFO] +- org.codehaus.woodstox:woodstox-core-lgpl:jar:4.0.7:compile
- [INFO] | \- org.codehaus.woodstox:stax2-api:jar:3.0.1:compile
- [INFO] +- org.easymock:easymockclassextension:jar:2.5.2:test
- [INFO] | +- cglib:cglib-nodep:jar:2.2:test
- [INFO] | \- org.objenesis:objenesis:jar:1.2:test
在 清單 5 中您可以看到 sis-client 項目需要 woodstox-core-lgpl 和 easymockclassextension 庫。easymockclassextension 庫反過來需要 cglib-nodep 庫和 objenesis 庫。如果我的 objenesis 出了問題,比如出現(xiàn)兩個版本,1.2 和 1.3,那么這個依賴項樹可能會向我顯示,1.2 工件是直接由 easymockclassextension 庫導(dǎo)入的。
dependency:tree 參數(shù)為我節(jié)省了很多調(diào)試時間,我希望對您也同樣有幫助。
#p#
技巧4. 使用配置文件
多數(shù)重大項目至少有一個核心環(huán)境,由開發(fā)相關(guān)的任務(wù)、質(zhì)量保證(QA)、集成和生產(chǎn)組成。管理所有這些環(huán)境的挑戰(zhàn)是配置您的構(gòu)建,這必須連接到正確的數(shù)據(jù)庫中,執(zhí)行正確的腳本集、并為每個環(huán)境部署正確的工件。使用 Maven 配置文件讓您完成這些任務(wù),而無需為每個環(huán)境分別建立明確指令。
關(guān)鍵在于環(huán)境配置文件和面向任務(wù)的配置文件的合并。每個環(huán)境配置文件定義其特定的位置、腳本和服務(wù)器。因此,在我的 pox.xml 文件中,我將定義面向任務(wù)的配置文件 “deploywar”,如清單 6 所示:
清單 6. 部署配置文件
- <profiles>
- <profile>
- <id>deploywarid>
- <build>
- <plugins>
- <plugin>
- <groupId>net.fpicgroupId>
- <artifactId>tomcat-deployer-pluginartifactId>
- <version>1.0-SNAPSHOTversion>
- <executions>
- <execution>
- <id>posid>
- <phase>installphase>
- <goals>
- <goal>deploygoal>
- goals>
- <configuration>
- <host>${deploymentManagerRestHost}host>
- <port>${deploymentManagerRestPort}port>
- <username>${deploymentManagerRestUsername}username>
- <password>${deploymentManagerRestPassword}password>
- <artifactSource>
- address/target/addressservice.war
- artifactSource>
- configuration>
- execution>
- executions>
- plugin>
- plugins>
- build>
- profile>
- profiles>
這個配置文件(通過 ID “deploywar” 區(qū)別)執(zhí)行 tomcat-deployer-plugin,被配置來連接一個特定主機(jī)和端口,以及指定用戶名和密碼證書。所有這些信息使用變量來定義,比如 ${deploymentmanagerRestHost}。這些變量在我的 profiles.xml 文件中定義,如清單 7 所示:
清單 7. profiles.xml
- <profile>
- <id>devid>
- <activation>
- <property>
- <name>envname>
- <value>devvalue>
- property>
- activation>
- <properties>
- <deploymentManagerRestHost>10.50.50.52deploymentManagerRestHost>
- <deploymentManagerRestPort>58090deploymentManagerRestPort>
- <deploymentManagerRestUsername>myusernamedeploymentManagerRestUsername>
- <deploymentManagerRestPassword>mypassworddeploymentManagerRestPassword>
- properties>
- profile>
- <profile>
- <id>qaid>
- <activation>
- <property>
- <name>envname>
- <value>qavalue>
- property>
- activation>
- <properties>
- <deploymentManagerRestHost>10.50.50.50deploymentManagerRestHost>
- <deploymentManagerRestPort>58090deploymentManagerRestPort>
- <deploymentManagerRestUsername>
- myotherusername
- deploymentManagerRestUsername>
- <deploymentManagerRestPassword>
- myotherpassword
- deploymentManagerRestPassword>
- properties>
- profile>
部署 Maven 配置文件
在 清單 7 的 profiles.xml 文件中,我定義了兩個配置文件,并根據(jù) env (環(huán)境)屬性的值激活它們。如果 env 屬性被設(shè)置為 dev,則使用開發(fā)部署信息。如果 env 屬性被設(shè)置為 qa,那么將使用 QA 部署信息,等等。
這是部署文件的命令:
- mvn -Pdeploywar -Denv=dev clean install
-Pdeploywar 標(biāo)記通知要明確包含 deploywar 配置文件。-Denv=dev 語句創(chuàng)建一個名為 env 的系統(tǒng)屬性,并將其值設(shè)為 dev,這激活了開發(fā)配置。傳遞 -Denv=qa 將激活 QA 配置。
#p#
技巧5. 定制 Maven 插件
Maven 有十幾個預(yù)構(gòu)建插件供您使用,但是有時候您只想找到自己需要的插件,構(gòu)建一個定制的 Maven 插件比較容易:
用 POM packaging 創(chuàng)建一個新項目,設(shè)置為 “maven-plugin”。
包括一個 maven-plugin-plugin 調(diào)用,可以定義您的公布插件目標(biāo)。
創(chuàng)建一個 Maven 插件 “mojo” 類 (一個擴(kuò)展 AbstractMojo 的類)。
為類的 Javadoc 做注釋來定義目標(biāo),并為每個將被作為配置參數(shù)公布的變量做注解。
實現(xiàn)一個 execute() 方法,該方法在調(diào)用您的插件是將被調(diào)用。
例如,清單 8 顯示了一個定制插件(為了部署 Tomcat)的相關(guān)部分:
清單 8. TomcatDeployerMojo.java
- package net.fpic.maven.plugins;
- import java.io.File;
- import java.util.StringTokenizer;
- import net.fpic.tomcatservice64.TomcatDeploymentServerClient;
- import org.apache.maven.plugin.AbstractMojo;
- import org.apache.maven.plugin.MojoExecutionException;
- import com.javasrc.server.embedded.CommandRequest;
- import com.javasrc.server.embedded.CommandResponse;
- import com.javasrc.server.embedded.credentials.Credentials;
- import com.javasrc.server.embedded.credentials.UsernamePasswordCredentials;
- import com.javasrc.util.FileUtils;
- /**
- * Goal that deploys a web application to Tomcat
- *
- * @goal deploy
- * @phase install
- */
- public class TomcatDeployerMojo extends AbstractMojo
- {
- /**
- * The host name or IP address of the deployment server
- *
- * @parameter alias="host" expression="${deploy.host}" @required
- */
- private String serverHost;
- /**
- * The port of the deployment server
- *
- * @parameter alias="port" expression="${deploy.port}" default-value="58020"
- */
- private String serverPort;
- /**
- * The username to connect to the deployment manager (if omitted then the plugin
- * attempts to deploy the application to the server without credentials)
- *
- * @parameter alias="username" expression="${deploy.username}"
- */
- private String username;
- /**
- * The password for the specified username
- *
- * @parameter alias="password" expression="${deploy.password}"
- */
- private String password;
- /**
- * The name of the source artifact to deploy, such as target/pos.war
- *
- * @parameter alias="artifactSource" expression=${deploy.artifactSource}"
- * @required
- */
- private String artifactSource;
- /**
- * The destination name of the artifact to deploy, such as ROOT.war.
- * If not present then the
- * artifact source name is used (without pathing information)
- *
- * @parameter alias="artifactDestination"
- * expression=${deploy.artifactDestination}"
- */
- private String artifactDestination;
- public void execute() throws MojoExecutionException
- {
- getLog().info( "Server Host: " + serverHost +
- ", Server Port: " + serverPort +
- ", Artifact Source: " + artifactSource +
- ", Artifact Destination: " + artifactDestination );
- // Validate our fields
- if( serverHost == null )
- {
- throw new MojoExecutionException(
- "No deployment host specified, deployment is not possible" );
- }
- if( artifactSource == null )
- {
- throw new MojoExecutionException(
- "No source artifact is specified, deployment is not possible" );
- }
- ...
- }
- }
在這個類的頭部,@goal 注釋指定 MOJO 執(zhí)行的目標(biāo),而 @phase 指出目標(biāo)執(zhí)行的階段。除了一個映射到一個有真實值的系統(tǒng)屬性的表達(dá)式之外,每個公布的屬性有一個 @phase 注釋,通過將被執(zhí)行的參數(shù)指定別名。如果屬性有一個 @required 注釋,那么它是必須的。如果它有一個 default-value,那么如果沒有指定的話,將使用這個值。在 execute() 方法中,您可以調(diào)用 getLog() 來訪問 Maven 記錄器,根據(jù)記錄級別,它將輸出具體消息到標(biāo)準(zhǔn)輸出設(shè)備。如果插件發(fā)生故障,拋出一個 MojoExecutionException 將導(dǎo)致構(gòu)建失敗。
結(jié)束語
您可以使用 Maven 只進(jìn)行構(gòu)建,但是最好的 Maven 是一個項目生命周期管理工具。本文介紹了 5 個大家很少了解的特性,可以幫助您更高效地使用 Maven。
【編輯推薦】