关于Apache Maven您不知道的5件事

 【IT168 评论】Maven 是为Java开发人员提供的一个极为优秀的构建工具,您也可以使用它来管理您的项目生命周期。作为一个生命周期管理工具,Maven是基于阶段操作的,而不像Ant是基于“任务”构建的。Maven 完成项目生命周期的所有阶段,包括验证、代码生成、编译、测试、打包、集成测试、安装、部署、以及项目网站创建和部署。

  为了更好地理解Maven和传统构建工具的不同,我们来看看构建一个JAR文件和一个EAR文件的过程。使用Ant,您可能需要定义专有任务来组装每个工件。另一方面,Maven 可以为您完成大部分工作:您只需要告诉它是一个 JAR 文件还是一个 EAR 文件,然后指示它来完成 “打包” 过程。Maven 将会找到所需的资源,然后构建文件。

  本文的5个技巧目的是帮助您解决即将出现的一些问题:使用Maven管理您的应用程序的生命周期时,将会出现的编程场景。

  1. 可执行的JAR文件

  使用 Maven 构建一个 JAR 文件比较容易:只要定义项目包装为 “jar”,然后执行包装生命周期阶段即可。但是定义一个可执行 JAR 文件却比较麻烦。采取以下步骤可以更高效:

  在您定义可执行类的 JAR 的 MANIFEST.MF 文件中定义一个 main 类。(MANIFEST.MF 是包装您的应用程序时 Maven 生成的。)

  找到您项目依赖的所有库。

  在您的 MANIFEST.MF 文件中包含那些库,便于您的应用程序找到它们。

  您可以手工进行这些操作,或者要想更高效,您可以使用两个 Maven 插件帮助您完成:maven-jar-plugin 和 maven-dependency-plugin。

  maven-jar-plugin

  maven-jar-plugin 可以做很多事情,但在这里,我们只对使用它来修改默认 MANIFEST.MF 文件的内容感兴趣。在您的 POM 文件的插件部分添加清单 1 所示代码:

  清单 1. 使用 maven-jar-plugin 修改 MANIFEST.MF

None.gif              < plugin >
None.gif                
< groupId > org.apache.maven.plugins </ groupId >
None.gif                
< artifactId > maven - jar - plugin </ artifactId >
None.gif                
< configuration >
None.gif                    
< archive >
None.gif                        
< manifest >
None.gif                            
< addClasspath > true </ addClasspath >
None.gif                            
< classpathPrefix > lib /</ classpathPrefix >
None.gif                            
< mainClass > com.mypackage.MyClass </ mainClass >
None.gif                        
</ manifest >
None.gif                    
</ archive >
None.gif                
</ configuration >
None.gif            
</ plugin >

   所有 Maven 插件通过一个 元素公布了其配置,在本例中,maven-jar-plugin 修改它的 archive 属性,特别是存档文件的 manifest 属性,它控制 MANIFEST.MF 文件的内容。包括 3 个元素:

  addClassPath:将该元素设置为 true 告知 maven-jar-plugin 添加一个 Class-Path 元素到 MANIFEST.MF 文件,以及在 Class-Path 元素中包括所有依赖项。

  classpathPrefix:如果您计划在同一目录下包含有您的所有依赖项,作为您将构建的 JAR,那么您可以忽略它;否则使用 classpathPrefix 来指定所有依赖 JAR 文件的前缀。在清单 1 中,classpathPrefix 指出,相对存档文件,所有的依赖项应该位于 “lib” 文件夹。

  mainClass:当用户使用 lib 命令执行 JAR 文件时,使用该元素定义将要执行的类名。

  maven-dependency-plugin

  当您使用这 3 个元素配置好了 MANIFEST.MF 文件之后,下一步是将所有的依赖项复制到 lib 文件夹。为此,使用 maven-dependency-plugin,如清单 2 所示:

  清单 2. 使用 maven-dependency-plugin 将依赖项复制到库

None.gif              < plugin >
None.gif                
< groupId > org.apache.maven.plugins </ groupId >
None.gif                
< artifactId > maven - dependency - plugin </ artifactId >
None.gif                
< executions >
None.gif                    
< execution >
None.gif                        
< id > copy </ id >
None.gif                        
< phase > install </ phase >
None.gif                        
< goals >
None.gif                            
< goal > copy - dependencies </ goal >
None.gif                        
</ goals >
None.gif                        
< configuration >
None.gif                            
< outputDirectory >
ExpandedBlockStart.gifContractedBlock.gif                              $
... {project.build.directory} / lib
None.gif                            
</ outputDirectory >
None.gif                        
</ configuration >
None.gif                    
</ execution >
None.gif                
</ executions >
None.gif            
</ plugin >

   maven-dependency-plugin 有一个 copy-dependencies,目标是将您的依赖项复制到您所选择的目录。本例中,我将依赖项复制到 build 目录下的 lib 目录(project-home/target/lib)。

  将您的依赖项和修改的 MANIFEST.MF 放在适当的位置后,您就可以用一个简单的命令启动应用程序:

None.gif java - jar jarfilename.jar

 

  2. 定制 MANIFEST.MF

  虽然 maven-jar-plugin 允许您修改 MANIFEST.MF 文件的共有部分,但有时候您需要一个更个性化的 MANIFEST.MF。解决方案是双重的:

  在一个 “模板” MANIFEST.MF 文件中定义您的所有定制配置。

  配置 maven-jar-plugin 来使用您的 MANIFEST.MF 文件,然后使用一些 Maven 配置增强。

  例如,考虑一个包含 Java 代理的 JAR 文件。要运行一个 Java 代理,需要定义 Premain-Class 和设置许可。清单 3 展示了这样一个 MANIFEST.MF 文件的内容:

  清单 3. 在一个定制的 MANIFEST.MF 文件中定义 Premain-Class

None.gif Manifest - Version: 1.0
None.gifPremain
- Class: com.geekcap.openapm.jvm.agent.Agent
None.gifCan
- Redefine - Classes: true
None.gifCan
- Retransform - Classes: true
None.gifCan
- Set - Native - Method - Prefix: true

   在清单3中,我已指定 Premain-Class - com.geekcap.openapm.jvm.agent.Agent 被授权许可来对类进行重定义和再转换。接下来,我更新 maven-jar-plugin 代码来包含 MANIFEST.MF 文件。如清单 4 所示:

  清单 4. 包含 Premain-Class

None.gif              < plugin >
None.gif                
< groupId > org.apache.maven.plugins </ groupId >
None.gif                
< artifactId > maven - jar - plugin </ artifactId >
None.gif                
< configuration >
None.gif                    
< archive >
None.gif                        
< manifestFile >
None.gif                          src
/ main / resources / META - INF / MANIFEST.MF
None.gif                        
</ manifestFile >
None.gif                        
< manifest >
None.gif                            
< addClasspath > true </ addClasspath >
None.gif                            
< classpathPrefix > lib /</ classpathPrefix >
None.gif                            
< mainClass >
None.gif                              com.geekcap.openapm.ui.PerformanceAnalyzer
None.gif                            
</ mainClass >
None.gif                        
</ manifest >
None.gif                    
</ archive >
None.gif                
</ configuration >
None.gif            
</ plugin >

   这是一个很有趣的示例,因为它既定义了一个 Premain-Class — 允许 JAR 文件作为一个 Java 代理运行,也有一个 mainClass — 允许它作为一个可执行的 JAR 文件运行。在这个特殊的例子中,我使用 OpenAPM(我已构建的一个代码跟踪工具)来定义将被 Java 代理和一个用户界面记录的代码跟踪。简而言之,这个示例展示一个显式清单文件与动态修改相结合的力量。

 

  3. 依赖项树

  Maven 一个最有用的功能是它支持依赖项管理:您只需要定义您应用程序依赖的库,Maven 找到它们、下载它们、然后使用它们编译您的代码。

  必要时,您需要知道具体依赖项的来源 — 这样您就可以找到同一个 JAR 文件的不同版本的区别和矛盾。这种情况下,您将需要防止将一个版本的 JAR 文件包含在您的构建中,但是首先您需要定位保存 JAR 的依赖项。

  一旦您知道下列命令,那么定位依赖项往往是相当容易的:

None.gif mvn dependency:tree

   dependency:tree 参数显示您的所有直接依赖项,然后显示所有子依赖项(以及它们的子依赖项,等等)。例如,清单 5 节选自我的一个依赖项所需要的客户端库:

  清单 5. Maven 依赖项树

None.gif [INFO] ------------------------------------------------------------------------
None.gif[INFO] Building Client library
for communicating with the LDE
None.gif[INFO]    task
- segment: [dependency:tree]
None.gif[INFO]
------------------------------------------------------------------------
ExpandedBlockStart.gifContractedBlock.gif[INFO] [dependency:tree
... {execution: default-cli} ]
None.gif[INFO] com.lmt.pos:sis
- client:jar: 2.1 . 14
None.gif[INFO]
+- org.codehaus.woodstox:woodstox - core - lgpl:jar: 4.0 . 7 :compile
None.gif[INFO]
|   \ - org.codehaus.woodstox:stax2 - api:jar: 3.0 . 1 :compile
None.gif[INFO]
+- org.easymock:easymockclassextension:jar: 2.5 . 2 :test
None.gif[INFO]
|    +- cglib:cglib - nodep:jar: 2.2 :test
None.gif[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 参数为我节省了很多调试时间,我希望对您也同样有帮助。

 

  4. 使用配置文件

  多数重大项目至少有一个核心环境,由开发相关的任务、质量保证(QA)、集成和生产组成。管理所有这些环境的挑战是配置您的构建,这必须连接到正确的数据库中,执行正确的脚本集、并为每个环境部署正确的工件。使用 Maven 配置文件让您完成这些任务,而无需为每个环境分别建立明确指令。

  关键在于环境配置文件和面向任务的配置文件的合并。每个环境配置文件定义其特定的位置、脚本和服务器。因此,在我的 pox.xml 文件中,我将定义面向任务的配置文件 “deploywar”,如清单 6 所示:

  清单 6. 部署配置文件

None.gif      < profiles >
None.gif        
< profile >
None.gif            
< id > deploywar </ id >
None.gif            
< build >
None.gif                
< plugins >
None.gif                    
< plugin >
None.gif                        
< groupId > net.fpic </ groupId >
None.gif                        
< artifactId > tomcat - deployer - plugin </ artifactId >
None.gif                        
< version > 1.0 - SNAPSHOT </ version >
None.gif                        
< executions >
None.gif                            
< execution >
None.gif                                
< id > pos </ id >
None.gif                                
< phase > install </ phase >
None.gif                                
< goals >
None.gif                                    
< goal > deploy </ goal >
None.gif                                
</ goals >
None.gif                                
< configuration >
ExpandedBlockStart.gifContractedBlock.gif                                    
< host > $ ... {deploymentManagerRestHost} </ host >
ExpandedBlockStart.gifContractedBlock.gif                                    
< port > $ ... {deploymentManagerRestPort} </ port >
ExpandedBlockStart.gifContractedBlock.gif                                    
< username > $ ... {deploymentManagerRestUsername} </ username >
ExpandedBlockStart.gifContractedBlock.gif                                    
< password > $ ... {deploymentManagerRestPassword} </ password >
None.gif                                    
< artifactSource >
None.gif                                      address
/ target / addressservice.war
None.gif                                    
</ artifactSource >
None.gif                                
</ configuration >
None.gif                            
</ execution >
None.gif                        
</ executions >
None.gif                    
</ plugin >
None.gif                
</ plugins >
None.gif            
</ build >
None.gif        
</ profile >
None.gif    
</ profiles >

   这个配置文件(通过 ID “deploywar” 区别)执行 tomcat-deployer-plugin,被配置来连接一个特定主机和端口,以及指定用户名和密码证书。所有这些信息使用变量来定义,比如 ${deploymentmanagerRestHost}。这些变量在我的 profiles.xml 文件中定义,如清单 7 所示:

  清单 7. profiles.xml

None.gif          <!-- Defines the development deployment information -->
None.gif        
< profile >
None.gif            
< id > dev </ id >
None.gif            
< activation >
None.gif                
< property >
None.gif                    
< name > env </ name >
None.gif                    
< value > dev </ value >
None.gif                
</ property >
None.gif            
</ activation >
None.gif            
< properties >
None.gif                
< deploymentManagerRestHost > 10.50 . 50.52 </ deploymentManagerRestHost >
None.gif                
< deploymentManagerRestPort > 58090 </ deploymentManagerRestPort >
None.gif                
< deploymentManagerRestUsername > myusername </ deploymentManagerRestUsername >
None.gif                
< deploymentManagerRestPassword > mypassword </ deploymentManagerRestPassword >
None.gif            
</ properties >
None.gif        
</ profile >
None.gif
None.gif        
<!-- Defines the QA deployment information -->
None.gif        
< profile >
None.gif            
< id > qa </ id >
None.gif            
< activation >
None.gif                
< property >
None.gif                    
< name > env </ name >
None.gif                    
< value > qa </ value >
None.gif                
</ property >
None.gif            
</ activation >
None.gif            
< properties >
None.gif                
< deploymentManagerRestHost > 10.50 . 50.50 </ deploymentManagerRestHost >
None.gif                
< deploymentManagerRestPort > 58090 </ deploymentManagerRestPort >
None.gif                
< deploymentManagerRestUsername >
None.gif                  myotherusername
None.gif                
</ deploymentManagerRestUsername >
None.gif                
< deploymentManagerRestPassword >
None.gif                  myotherpassword
None.gif                
</ deploymentManagerRestPassword >
None.gif            
</ properties >
None.gif        
</ profile >

   部署 Maven 配置文件

  在清单7的profiles.xml 文件中,我定义了两个配置文件,并根据 env (环境)属性的值激活它们。如果 env 属性被设置为 dev,则使用开发部署信息。如果 env 属性被设置为 qa,那么将使用 QA 部署信息,等等。

  这是部署文件的命令:

None.gif mvn - Pdeploywar - Denv = dev clean install

   -Pdeploywar 标记通知要明确包含 deploywar 配置文件。-Denv=dev 语句创建一个名为 env 的系统属性,并将其值设为 dev,这激活了开发配置。传递 -Denv=qa 将激活 QA 配置。

 

  5. 定制Maven插件

  Maven 有十几个预构建插件供您使用,但是有时候您只想找到自己需要的插件,构建一个定制的 Maven 插件比较容易:

  用 POM packaging 创建一个新项目,设置为 “maven-plugin”。

  包括一个 maven-plugin-plugin 调用,可以定义您的公布插件目标。

  创建一个 Maven 插件 “mojo” 类 (一个扩展 AbstractMojo 的类)。

  为类的 Javadoc 做注释来定义目标,并为每个将被作为配置参数公布的变量做注解。

  实现一个 execute() 方法,该方法在调用您的插件是将被调用。

  例如,清单 8 显示了一个定制插件(为了部署 Tomcat)的相关部分:

  清单 8. TomcatDeployerMojo.java

None.gif package net.fpic.maven.plugins;
None.gif
None.gif
import java.io.File;
None.gif
import java.util.StringTokenizer;
None.gif
None.gif
import net.fpic.tomcatservice64.TomcatDeploymentServerClient;
None.gif
None.gif
import org.apache.maven.plugin.AbstractMojo;
None.gif
import org.apache.maven.plugin.MojoExecutionException;
None.gif
None.gif
import com.javasrc.server.embedded.CommandRequest;
None.gif
import com.javasrc.server.embedded.CommandResponse;
None.gif
import com.javasrc.server.embedded.credentials.Credentials;
None.gif
import com.javasrc.server.embedded.credentials.UsernamePasswordCredentials;
None.gif
import com.javasrc.util.FileUtils;
None.gif
ExpandedBlockStart.gifContractedBlock.gif
/** */ /**
InBlock.gif * Goal that deploys a web application to Tomcat
InBlock.gif *
InBlock.gif * @goal deploy
InBlock.gif * @phase install
ExpandedBlockEnd.gif
*/

None.gif
public class TomcatDeployerMojo extends AbstractMojo
ExpandedBlockStart.gifContractedBlock.gif
... {
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/** *//**
InBlock.gif     * The host name or IP address of the deployment server
InBlock.gif     *
InBlock.gif     * @parameter alias="host" expression="${deploy.host}" @required
ExpandedSubBlockEnd.gif    
*/

InBlock.gif    
private String serverHost;
InBlock.gif    
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/** *//**
InBlock.gif     * The port of the deployment server
InBlock.gif     *
InBlock.gif     * @parameter alias="port" expression="${deploy.port}" default-value="58020"
ExpandedSubBlockEnd.gif    
*/

InBlock.gif    
private String serverPort;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/** *//**
InBlock.gif     * The username to connect to the deployment manager (if omitted then the plugin
InBlock.gif     * attempts to deploy the application to the server without credentials)
InBlock.gif     *
InBlock.gif     * @parameter alias="username" expression="${deploy.username}"
ExpandedSubBlockEnd.gif    
*/

InBlock.gif    
private String username;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/** *//**
InBlock.gif     * The password for the specified username
InBlock.gif     *
InBlock.gif     * @parameter alias="password" expression="${deploy.password}"
ExpandedSubBlockEnd.gif    
*/

InBlock.gif    
private String password;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/** *//**
InBlock.gif     * The name of the source artifact to deploy, such as target/pos.war
InBlock.gif     *
InBlock.gif     * @parameter alias="artifactSource" expression=${deploy.artifactSource}"
InBlock.gif     * @required
ExpandedSubBlockEnd.gif    
*/

InBlock.gif    
private String artifactSource;
InBlock.gif    
ExpandedSubBlockStart.gifContractedSubBlock.gif    
/** *//**
InBlock.gif     * The destination name of the artifact to deploy, such as ROOT.war.
InBlock.gif     * If not present then the
InBlock.gif     * artifact source name is used (without pathing information)
InBlock.gif     *
InBlock.gif     * @parameter alias="artifactDestination"
InBlock.gif     *   expression=${deploy.artifactDestination}"
ExpandedSubBlockEnd.gif    
*/

InBlock.gif    
private String artifactDestination;
InBlock.gif    
InBlock.gif    
public void execute() throws MojoExecutionException
ExpandedSubBlockStart.gifContractedSubBlock.gif    
...{
InBlock.gif        getLog().info(
"Server Host: " + serverHost +
InBlock.gif                      
", Server Port: " + serverPort +
InBlock.gif                      
", Artifact Source: " + artifactSource +
InBlock.gif                      
", Artifact Destination: " + artifactDestination );
InBlock.gif        
InBlock.gif        
// Validate our fields
InBlock.gif
        if( serverHost == null )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
...{
InBlock.gif            
throw new MojoExecutionException(
InBlock.gif              
"No deployment host specified, deployment is not possible" );
ExpandedSubBlockEnd.gif        }

InBlock.gif        
if( artifactSource == null )
ExpandedSubBlockStart.gifContractedSubBlock.gif        
...{
InBlock.gif            
throw new MojoExecutionException(
InBlock.gif              
"No source artifact is specified, deployment is not possible" );
ExpandedSubBlockEnd.gif        }

InBlock.gif
InBlock.gif        ...
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif}

   在这个类的头部,@goal 注释指定 MOJO 执行的目标,而 @phase 指出目标执行的阶段。除了一个映射到一个有真实值的系统属性的表达式之外,每个公布的属性有一个 @phase 注释,通过将被执行的参数指定别名。如果属性有一个 @required 注释,那么它是必须的。如果它有一个 default-value,那么如果没有指定的话,将使用这个值。在 execute() 方法中,您可以调用 getLog() 来访问 Maven 记录器,根据记录级别,它将输出具体消息到标准输出设备。如果插件发生故障,抛出一个 MojoExecutionException 将导致构建失败。

  结束语

  您可以使用 Maven 只进行构建,但是最好的 Maven 是一个项目生命周期管理工具。本文介绍了 5 个大家很少了解的特性,可以帮助您更高效地使用 Maven。

 

转载于:https://www.cnblogs.com/mengheyun/archive/2011/02/06/1949448.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值