在本文的 參考資料 部分,您將發現大量介紹 Maven 的入門教程。本文的 5 個技巧目的是幫助您解決即將出現的一些問題:使用 Maven 管理您的應用程序的生命週期時,將會出現的編程場景。java
使用 Maven 構建一個 JAR 文件比較容易:只要定義項目包裝爲 「jar」,而後執行包裝生命週期階段便可。可是定義一個可執行 JAR 文件卻比較麻煩。採起如下步驟能夠更高效:node
main
類。(MANIFEST.MF 是包裝您的應用程序時 Maven 生成的。)您能夠手工進行這些操做,或者要想更高效,您可使用兩個 Maven 插件幫助您完成:maven-jar-plugin
和 maven-dependency-plugin
。web
maven-jar-plugin
能夠作不少事情,但在這裏,咱們只對使用它來修改默認 MANIFEST.MF 文件的內容感興趣。在您的 POM 文件的插件部分添加清單 1 所示代碼:數據庫
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.mypackage.MyClass</mainClass> </manifest> </archive> </configuration> </plugin>
全部 Maven 插件經過一個 <configuration>
元素公佈了其配置,在本例中,maven-jar-plugin
修改它的 archive
屬性,特別是存檔文件的 manifest
屬性,它控制 MANIFEST.MF 文件的內容。包括 3 個元素:express
addClassPath
:將該元素設置爲 true 告知 maven-jar-plugin
添加一個 Class-Path
元素到 MANIFEST.MF 文件,以及在 Class-Path
元素中包括全部依賴項。classpathPrefix
:若是您計劃在同一目錄下包含有您的全部依賴項,做爲您將構建的 JAR,那麼您能夠忽略它;不然使用classpathPrefix
來指定全部依賴 JAR 文件的前綴。在清單 1 中,classpathPrefix
指出,相對存檔文件,全部的依賴項應該位於 「lib
」 文件夾。mainClass
:當用戶使用 lib
命令執行 JAR 文件時,使用該元素定義將要執行的類名。當您使用這 3 個元素配置好了 MANIFEST.MF 文件以後,下一步是將全部的依賴項複製到 lib
文件夾。爲此,使用 maven-dependency-plugin
,如清單 2 所示:apache
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>install</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/lib </outputDirectory> </configuration> </execution> </executions> </plugin>
maven-dependency-plugin
有一個 copy-dependencies
,目標是將您的依賴項複製到您所選擇的目錄。本例中,我將依賴項複製到build
目錄下的 lib
目錄(project-home/target/lib
)。編程
將您的依賴項和修改的 MANIFEST.MF 放在適當的位置後,您就能夠用一個簡單的命令啓動應用程序:api
java -jar jarfilename.jar
回頁首tomcat
雖然 maven-jar-plugin
容許您修改 MANIFEST.MF
文件的共有部分,但有時候您須要一個更個性化的 MANIFEST.MF。解決方案是雙重的:服務器
maven-jar-plugin
來使用您的 MANIFEST.MF 文件,而後使用一些 Maven 配置加強。例如,考慮一個包含 Java 代理的 JAR 文件。要運行一個 Java 代理,須要定義 Premain-Class
和設置許可。清單 3 展現了這樣一個 MANIFEST.MF 文件的內容:
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
被受權許可來對類進行重定義和再轉換。接下來,我更新 maven-jar-plugin
代碼來包含 MANIFEST.MF 文件。如清單 4 所示:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestFile> src/main/resources/META-INF/MANIFEST.MF </manifestFile> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass> com.geekcap.openapm.ui.PerformanceAnalyzer </mainClass> </manifest> </archive> </configuration> </plugin>
這是一個頗有趣的示例,由於它既定義了一個 Premain-Class
— 容許 JAR 文件做爲一個 Java 代理運行,也有一個 mainClass
— 容許它做爲一個可執行的 JAR 文件運行。在這個特殊的例子中,我使用 OpenAPM
(我已構建的一個代碼跟蹤工具)來定義將被 Java 代理和一個用戶界面記錄的代碼跟蹤。簡而言之,這個示例展現一個顯式清單文件與動態修改相結合的力量。
Maven 一個最有用的功能是它支持依賴項管理:您只須要定義您應用程序依賴的庫,Maven 找到它們、下載它們、而後使用它們編譯您的代碼。
必要時,您須要知道具體依賴項的來源 — 這樣您就能夠找到同一個 JAR 文件的不一樣版本的區別和矛盾。這種狀況下,您將須要防止將一個版本的 JAR 文件包含在您的構建中,可是首先您須要定位保存 JAR 的依賴項。
一旦您知道下列命令,那麼定位依賴項每每是至關容易的:
mvn dependency:tree
dependency:tree
參數顯示您的全部直接依賴項,而後顯示全部子依賴項(以及它們的子依賴項,等等)。例如,清單 5 節選自個人一個依賴項所須要的客戶端庫:
[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
出了問題,好比出現兩個版本,1.2 和 1.3,那麼這個依賴項樹可能會向我顯示,1.2 工件是直接由 easymockclassextension
庫導入的。
dependency:tree
參數爲我節省了不少調試時間,我但願對您也一樣有幫助。
多數重大項目至少有一個核心環境,由開發相關的任務、質量保證(QA)、集成和生產組成。管理全部這些環境的挑戰是配置您的構建,這必須鏈接到正確的數據庫中,執行正確的腳本集、併爲每一個環境部署正確的工件。使用 Maven 配置文件讓您完成這些任務,而無需爲每一個環境分別創建明確指令。
關鍵在於環境配置文件和麪向任務的配置文件的合併。每一個環境配置文件定義其特定的位置、腳本和服務器。所以,在個人 pox.xml 文件中,我將定義面向任務的配置文件 「deploywar
」,如清單 6 所示:
<profiles> <profile> <id>deploywar</id> <build> <plugins> <plugin> <groupId>net.fpic</groupId> <artifactId>tomcat-deployer-plugin</artifactId> <version>1.0-SNAPSHOT</version> <executions> <execution> <id>pos</id> <phase>install</phase> <goals> <goal>deploy</goal> </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
」 區別)執行 tomcat-deployer-plugin
,被配置來鏈接一個特定主機和端口,以及指定用戶名和密碼證書。全部這些信息使用變量來定義,好比 ${deploymentmanagerRestHost}
。這些變量在個人 profiles.xml 文件中定義,如清單 7 所示:
<!-- Defines the development deployment information --> <profile> <id>dev</id> <activation> <property> <name>env</name> <value>dev</value> </property> </activation> <properties> <deploymentManagerRestHost>10.50.50.52</deploymentManagerRestHost> <deploymentManagerRestPort>58090</deploymentManagerRestPort> <deploymentManagerRestUsername>myusername</deploymentManagerRestUsername> <deploymentManagerRestPassword>mypassword</deploymentManagerRestPassword> </properties> </profile> <!-- Defines the QA deployment information --> <profile> <id>qa</id> <activation> <property> <name>env</name> <value>qa</value> </property> </activation> <properties> <deploymentManagerRestHost>10.50.50.50</deploymentManagerRestHost> <deploymentManagerRestPort>58090</deploymentManagerRestPort> <deploymentManagerRestUsername> myotherusername </deploymentManagerRestUsername> <deploymentManagerRestPassword> myotherpassword </deploymentManagerRestPassword> </properties> </profile>
在 清單 7 的 profiles.xml 文件中,我定義了兩個配置文件,並根據 env
(環境)屬性的值激活它們。若是 env
屬性被設置爲 dev
,則使用開發部署信息。若是 env
屬性被設置爲 qa
,那麼將使用 QA 部署信息,等等。
這是部署文件的命令:
mvn -Pdeploywar -Denv=dev clean install
-Pdeploywar
標記通知要明確包含 deploywar
配置文件。-Denv=dev
語句建立一個名爲 env
的系統屬性,並將其值設爲 dev
,這激活了開發配置。傳遞 -Denv=qa
將激活 QA 配置。
Maven 有十幾個預構建插件供您使用,可是有時候您只想找到本身須要的插件,構建一個定製的 Maven 插件比較容易:
maven-plugin
」。maven-plugin-plugin
調用,能夠定義您的公佈插件目標。mojo
」 類 (一個擴展 AbstractMojo
的類)。execute()
方法,該方法在調用您的插件是將被調用。例如,清單 8 顯示了一個定製插件(爲了部署 Tomcat)的相關部分:
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 執行的目標,而 @phase
指出目標執行的階段。除了一個映射到一個有真實值的系統屬性的表達式以外,每一個公佈的屬性有一個 @phase
註釋,經過將被執行的參數指定別名。若是屬性有一個 @required
註釋,那麼它是必須的。若是它有一個 default-value
,那麼若是沒有指定的話,將使用這個值。在 execute()
方法中,您能夠調用 getLog()
來訪問 Maven 記錄器,根據記錄級別,它將輸出具體消息到標準輸出設備。若是插件發生故障,拋出一個 MojoExecutionException
將致使構建失敗。