一 Maven介绍
Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(ProjectLifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。 当你使用Maven的时候,你用一个明确定义的项目对象模型来描述你的项目,然后 Maven 可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。
从Apache maven网站上下载maven并安装,maven目录下/conf/setting.xml中定义的是全局配置信息,里面包含了项目公用的localRepository(私有仓库)位置,pluginGroups,proxies,servers,profiles等属性,xml里文档注释很详细,具体含义可以参见文档,比较重要的有localRepository,profiles等,建议将配置文件放入~/.m2/settings.xml,实现用户范围控制。
<!-- localRepository | The path to the local repository maven will use to store artifacts. | | Default: ~/.m2/repository <localRepository>/path/to/local/repo</localRepository> --> <localRepository>d:/rr</localRepository>
pom是描述项目对象模型的,每个maven项目必须包含pom.xml,一个简单的pom中包含的信息有:坐标信息、依赖管理、属性值等。
超级POM定义了一组被所有项目共享的默认设置。它是Maven安装的一部分,可在/usr/local/maven/lib中的maven-xxx.jar文件中找到。如果你看一下这个JAR文件,你会看到在包org.apache.maven.project下看到一个名为pom-4.0.0.xml的文件。从这个文件可以看到默认的属性值和默认执行的插件。
二 Maven 变量
1 Maven提供了三个隐式的变量:
env
暴露了你操作系统或者shell的环境变量
project
暴露了POM。你可以使用点标记(.)的路径来引用POM元素的值${project.artifactId},你可能在老的构建中看到使用${pom.xxx}或者仅仅${xxx}来引用POM属性。这些方法已被弃用,我们只应该使用${project.xxx}。所有pom中的元素都可以用 project. 前缀进行引用,以下是部分常用的
${project.build.directory } results in the path to your "target" dir, this is the same as ${pom.project.build.directory }
${project.build. outputD irectory } results in the path to your "target/classes" dir
${project.name } refers to the name of the project.
${project.version } refers to the version of the project.
${project.build.finalName } refers to the final name of the file created when the built project is packaged
settings
暴露了Maven settings信息如:
${settings.offline}会引用~/.m2/settings.xml文件中offline元素的值
${settings.localRepository } refers to the path of the user's local repository.
2 内置属性
主要有两个常用内置属性:
${basedir}表示项目根目录,即包含pom.xml文件的目录
${version}表示项目版本
3 Java系统属性
所有Java系统属性都可以使用Maven属性引用,例如${user.home}指向了用户目录。
可以通过命令行mvn help:system查看所有的Java系统属性
4 环境变量属性
所有环境变量都可以使用以env.开头的Maven属性引用。例如${env.JAVA_HOME}指代了JAVA_HOME环境变量的值。也可以通过命令行mvn help:system查看所有环境变量。
系统的环境变量通过 env. 前缀引用,如:
${env.M2_HOME } returns the Maven2 installation path.
${java.home } specifies the path to the current JRE_HOME environment use with relative paths to get for example:
<jvm>${java.home}../bin/java.exe</jvm>
5 使用系统属性
Java系统属性,所有可以通过java.lang.System中getProperties()方法访问的属性都被暴露成POM属性。一些系统属性的例子是:hudson,/home/hudson,/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0/jre,和Linux。一个完整的系统属性列表可以在java.lang.System类的Javadoc中找到。
6 自定义属性
通过pom.xml或者settings.xml中的properties元素设置自己的属性,如:
<project> ... <properties> <foo>bar</foo> </properties> ... </project>
7 上级工程的变量
上级工程的pom中的变量用前缀 ${project.parent } 引用. 上级工程的版本也可以这样引用: ${parent.version }.
二 Maven 依赖管理
Maven一个强大的功能是它能够帮助管理项目的依赖,一个项目对其他项目或包的依赖范围分为如下几种:
test | 当你只有在测试的时候才引用类库的时候,你就要使用测试范围依赖 |
compile | 编译范围(compile)依赖。(默认)如果你的项目在编译,测试,和运行中都依赖于一个类库 |
provided | 当你的开发过程只有在编译和测试时需要一个类库,而该类库在运行的时候由容器提供,那么你就需要使用已提供范围的依赖,例如,如果你开发了一个web应用,你可能在编译classpath中需要可用的Servlet API来编译一个servlet,但是你不会想要在打包好的WAR中包含这个Servlet API;这个Servlet API JAR由你的应用服务器或者servlet容器提供 |
runtime | runtime依赖在运行和测试系统的时候需要,但在编译的时候不需要。比如,你可能在编译的时候只需要JDBC API JAR,而只有在运行的时候才需要JDBC驱动实现。 |
system | system范围依赖与provided类似,但是你必须显式的提供一个对于本地系统中JAR文件的路径。这么做是为了允许基于本地对象编译,而这些对象是系统类库的一部分。这样的构件应该是一直可用的,Maven也不会在仓库中去寻找它。如果你将一个依赖范围设置成系统范围,你必须同时提供一个systemPath元素。注意该范围是不推荐使用的(你应该一直尽量去从公共或定制的Maven仓库中引用依赖)。 |
可选依赖:
<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>1.4.1</version> <optional>true</optional> </dependency>
在你将这些依赖声明为可选之后,你就需要在依赖于my-project的项目中显式的引用对应的依赖。例如,如果你正编写一个应用,它依赖于my-project,并且想要使用EHCache实现,你就需要在你项目添加如下的dependency元素,在理想的世界中,你不需要使用可选依赖。你可以将EHCache相关的代码放到yproject-ehcache子模块中,将SwarmCache相关的代码放到my-project-swarmcache子模块中,而非创建一个带有一系列可选依赖的大项目。
依赖界限
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>[3.8,4.0)</version> <scope>test</scope> </dependency>
传递依赖
范围如何影响传递性依赖,行表示直接依赖,行与列的交叉就是为某个传递性依赖指定的范围。表中的空格意思是该传递性依赖被忽略)
compile | provided | runtime | test | |
compile | compile | - | runtime | - |
provided | provided | provided | provided | - |
runtime | runtime | - | runtime | - |
test | test | - | test | - |
如果project-a包含一个对于project-b的测试范围依赖,后者包含一个对于project-c的编译范围依赖。project-c将会是project-a的测试范围传递性依赖。
排除一个传递性依赖
<dependency> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>org.sonatype.mavenbook</groupId> <artifactId>project-b</artifactId> </exclusion> </exclusions> </dependency>
排除并替换一个传递性依赖
<dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> <version>3.2.5.ga</version> <exclusions> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-jta_1.1_spec</artifactId> <version>1.1</version> </dependency> </dependencies>
当Maven使用一种“最近者胜出”方式解决依赖的时候,它会用到依赖的深度。当使用依赖归类技术(pom类型依赖)的时候,会把依赖推入整个树的更深一层。当在选择用pom归类依赖或者用父POM的dependenctManagement的时候,需要留意这一点。
二 Maven 生命周期
生命周期(lifecyle) | 阶段(phrase) | ||||||||||||||||||||||||||||||||||||||||||
清理(clean) | pre-clean clean post-clean | ||||||||||||||||||||||||||||||||||||||||||
默认(default)(有时候也称为构建) |
| ||||||||||||||||||||||||||||||||||||||||||
站点(site) | pre-site site post-site site-deploy 默认绑定到站点生命周期的目标是: 1. site - site:site 2. site-deploy -site:deploy 通过运行如下命令从一个Maven项目生成一个站点: mvn site |
三 Maven 常用命令
maven命令都是由插件来实现的,常用的插件和命令见http://maven.apache.org/plugins/index.html
罗列下常用的命令:
mvn help:system 打印出所有的Java系统属性和环境变量
mvn help:describe -Dplugin=xxxgroupId:xxxartifactId -Dfull
mvn help:describe -Dplugin=exec -Dfull
help:effective-pom 打印项目的有效POM 有效POM是指合并了所有父POM(包括Super POM)后的XML,当你不确定POM的某些信息从何而来时,就可以查看有效POM
help:effective-settings 打印项目的有效settings
mvn help:active-profiles
mvn help:describe -Dcmd=package
mvn help:describe -Dcmd=jar:jar
mvn help:all-profiles 和mvn help:active-profiles 帮助查看项目的Profile
mvn archetype:generate maven3创建maven项目
maven2创建普通java项目,最好用稳定版本
mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate
mvn archetype:create -DgroupId=xxx \
-DartifactId=xxx \
-DpackageName=xxx \
-Dversion=1.0
创建Maven的Web项目:
mvn archetype:create -DgroupId=packageName -DartifactId=webappName -DarchetypeArtifactId=maven-archetype-webapp
mvn clean compile 编译 maven3.0之前compile:compile使用javac编译器,从3.0后使用javax.tool.JavaCompiler
mvn test 运行到 test 阶段为止的所有生命周期阶段
mvn test -Dmaven.test.failure.ignore=true 忽略测试失败
mvn install -Dmaven.test.skip=true 跳过单元测试
mvn clean package 打包
mvn clean install 打包并上传到maven本地库
mvn site生成站点信息,在target/site目录下index.html打开可浏览
直接执行main函数命令
mvn install
mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main exec插件不用添加依赖lib包到class路径,它会自动获取依赖包
mvn jetty:run 使用jetty插件启动项目
mvn dependency:resolve 打印出已解决依赖的列表
mvn dependency:tree 项目的整个依赖树
mvn dependency:analyse帮助你发现对于依赖的直接引用想要查看完整的依赖踪迹,包含那些因为冲突或者其它原因而被拒绝引入的构件,如果你有直接使用到的却未声明的依赖,该目标就会发出警告
打开 Maven 的调试标记运行:
mvn install -X
mvn跟开源测试覆盖率统计工具 合成,比如
mvn cobertura:cobertura
之后在target/site/cobertura下看到index.html文件
将依赖的文件安装到本地库
mvn install:install-file
-Dfile=<path-to-file>
-DgroupId=<group-id>
-DartifactId=<artifact-id>
-Dversion=<version>
-Dpackaging=<packaging>
-DgeneratePom=true
四 Maven 插件介绍
一个Maven插件是一个单个或者多个目标的集合。Maven插件的例子有一些简单但核心的插件,像Jar插件,它包含了一组创建JAR文件的目标,Compiler插件,它包含了一组编译源代码和测试代码的目标,或者Surefire插件,它包含一组运行单元测试和生成测试报告的目标。而其它的,更有专门的插件包括:Hibernate3插件,用来集成流行的持久化框架Hibernate,JRuby插件,它让你能够让运行ruby称为Maven构建的一部分或者用
Ruby来编写Maven插件。Maven也提供了自定义插件的能力。一个定制的插件可以用Java编写,或者用一些其它的语言如Ant,Groovy,beanshell和Ruby.
执行命令mvn help:describe -Dplugin=war -Dfull >> a.log
文件a.log中内容如下:
Name: Maven WAR Plugin Description: Builds a Web Application Archive (WAR) file from the project output and its dependencies. Group Id: org.apache.maven.plugins Artifact Id: maven-war-plugin Version: 2.1.1 Goal Prefix: war This plugin has 5 goals: war:exploded Description: Create an exploded webapp in a specified directory. Implementation: org.apache.maven.plugin.war.WarExplodedMojo Language: java Bound to phase: package Available parameters: archive The archive configuration to use. See Maven Archiver Reference. archiveClasses (Default: false) User property: archiveClasses Whether a JAR file will be created for the classes in the webapp. Using this optional configuration parameter will make the compiled classes to be archived into a JAR file and the classes directory will then be excluded from the webapp. cacheFile (Default: ${project.build.directory}/war/work/webapp-cache.xml) Required: true The file containing the webapp structure cache. containerConfigXML User property: maven.war.containerConfigXML The path to a configuration file for the servlet container. Note that the file name may be different for different servlet containers. Apache Tomcat uses a configuration file named context.xml. The file will be copied to the META-INF directory. dependentWarExcludes The comma separated list of tokens to exclude when doing a WAR overlay. Deprecated. Use <overlay>/<excludes> instead dependentWarIncludes The comma separated list of tokens to include when doing a WAR overlay. Default is '**' Deprecated. Use <overlay>/<includes> instead ...... ......
文件中描述了插件的坐标,目标(goals),目标参数,目标执行的mojo类,目标默认绑定阶段
打开其中war目标的mojo类org.apache.maven.plugin.war.WarMojo(在仓库的目录jar中,根据插件的坐标,可以找到jar位置在%本地仓库%/org/apache/maven/plugins/maven-war-plugin)
war:war Description: Build a WAR file. Implementation: org.apache.maven.plugin.war.WarMojo Language: java Bound to phase: package
package org.apache.maven.plugin.war;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.apache.maven.archiver.MavenArchiver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.war.util.ClassesPackager;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.jar.ManifestException;
import org.codehaus.plexus.archiver.war.WarArchiver;
import org.codehaus.plexus.util.StringUtils;
public class WarMojo extends AbstractWarMojo
{
private String outputDirectory;
private String warName;
private String classifier;
private String packagingExcludes;
private String packagingIncludes;
private WarArchiver warArchiver;
private MavenProjectHelper projectHelper;
private boolean primaryArtifact;
private boolean failOnMissingWebXml;
private boolean attachClasses;
private String classesClassifier;
public WarMojo()
{
this.primaryArtifact = true;
this.failOnMissingWebXml = true;
this.attachClasses = false;
this.classesClassifier = "classes";
}
public void execute()
throws MojoExecutionException, MojoFailureException
{
File warFile = getTargetWarFile();
try
{
performPackaging(warFile);
}
catch (DependencyResolutionRequiredException e)
{
throw new MojoExecutionException("Error assembling WAR: " + e.getMessage(), e);
}
catch (ManifestException e)
{
throw new MojoExecutionException("Error assembling WAR", e);
}
catch (IOException e)
{
throw new MojoExecutionException("Error assembling WAR", e);
}
catch (ArchiverException e)
{
throw new MojoExecutionException("Error assembling WAR: " + e.getMessage(), e);
}
}
private void performPackaging(File warFile)
throws IOException, ArchiverException, ManifestException, DependencyResolutionRequiredException, MojoExecutionException, MojoFailureException
{
getLog().info("Packaging webapp");
buildExplodedWebapp(getWebappDirectory());
MavenArchiver archiver = new MavenArchiver();
archiver.setArchiver(this.warArchiver);
archiver.setOutputFile(warFile);
getLog().debug("Excluding " + Arrays.asList(getPackagingExcludes()) + " from the generated webapp archive.");
getLog().debug("Including " + Arrays.asList(getPackagingIncludes()) + " in the generated webapp archive.");
this.warArchiver.addDirectory(getWebappDirectory(), getPackagingIncludes(), getPackagingExcludes());
File webXmlFile = new File(getWebappDirectory(), "WEB-INF/web.xml");
if (webXmlFile.exists())
{
this.warArchiver.setWebxml(webXmlFile);
}
if (!(this.failOnMissingWebXml))
{
getLog().debug("Build won't fail if web.xml file is missing.");
this.warArchiver.setIgnoreWebxml(false);
}
archiver.createArchive(getProject(), getArchive());
if (isAttachClasses())
{
ClassesPackager packager = new ClassesPackager();
File classesDirectory = packager.getClassesDirectory(getWebappDirectory());
if (classesDirectory.exists())
{
getLog().info("Packaging classes");
packager.packageClasses(classesDirectory, getTargetClassesFile(), getJarArchiver(), getProject(), getArchive());
this.projectHelper.attachArtifact(getProject(), "jar", getClassesClassifier(), getTargetClassesFile());
}
}
String classifier = this.classifier;
if (classifier != null)
{
this.projectHelper.attachArtifact(getProject(), "war", classifier, warFile);
}
else
{
Artifact artifact = getProject().getArtifact();
if (this.primaryArtifact)
{
artifact.setFile(warFile);
}
else if ((artifact.getFile() == null) || (artifact.getFile().isDirectory()))
{
artifact.setFile(warFile);
}
}
}
protected static File getTargetFile(File basedir, String finalName, String classifier, String type)
{
if (classifier == null)
{
classifier = "";
}
else if ((classifier.trim().length() > 0) && (!(classifier.startsWith("-"))))
{
classifier = "-" + classifier;
}
return new File(basedir, finalName + classifier + "." + type);
}
protected File getTargetWarFile()
{
return getTargetFile(new File(getOutputDirectory()), getWarName(), getClassifier(), "war");
}
protected File getTargetClassesFile()
{
return getTargetFile(new File(getOutputDirectory()), getWarName(), getClassesClassifier(), "jar");
}
public String getClassifier()
{
return this.classifier;
}
public void setClassifier(String classifier)
{
this.classifier = classifier;
}
public String[] getPackagingExcludes()
{
if (StringUtils.isEmpty(this.packagingExcludes))
{
return new String[0];
}
return StringUtils.split(this.packagingExcludes, ",");
}
public void setPackagingExcludes(String packagingExcludes)
{
this.packagingExcludes = packagingExcludes;
}
public String[] getPackagingIncludes()
{
if (StringUtils.isEmpty(this.packagingIncludes))
{
return { "**" };
}
return StringUtils.split(this.packagingIncludes, ",");
}
public void setPackagingIncludes(String packagingIncludes)
{
this.packagingIncludes = packagingIncludes;
}
public String getOutputDirectory()
{
return this.outputDirectory;
}
public void setOutputDirectory(String outputDirectory)
{
this.outputDirectory = outputDirectory;
}
public String getWarName()
{
return this.warName;
}
public void setWarName(String warName)
{
this.warName = warName;
}
public WarArchiver getWarArchiver()
{
return this.warArchiver;
}
public void setWarArchiver(WarArchiver warArchiver)
{
this.warArchiver = warArchiver;
}
public MavenProjectHelper getProjectHelper()
{
return this.projectHelper;
}
public void setProjectHelper(MavenProjectHelper projectHelper)
{
this.projectHelper = projectHelper;
}
public boolean isPrimaryArtifact()
{
return this.primaryArtifact;
}
public void setPrimaryArtifact(boolean primaryArtifact)
{
this.primaryArtifact = primaryArtifact;
}
public boolean isAttachClasses()
{
return this.attachClasses;
}
public void setAttachClasses(boolean attachClasses)
{
this.attachClasses = attachClasses;
}
public String getClassesClassifier()
{
return this.classesClassifier;
}
public void setClassesClassifier(String classesClassifier)
{
this.classesClassifier = classesClassifier;
}
public boolean isFailOnMissingWebXml()
{
return this.failOnMissingWebXml;
}
public void setFailOnMissingWebXml(boolean failOnMissingWebXml)
{
this.failOnMissingWebXml = failOnMissingWebXml;
}
}
上面为反编译器显示的jar,注解信息有丢失,war插件的war目标被执行时,WarMojo的execute()方法会被执行,WarMojo类的属性通过注解接收maven传递过来的参数或者默认参数(如果没有传参)。有些Mojo有默认绑定的阶段,当不进行任何设置时,该阶段就会执行这个目标。如compile插件的compile目标默认绑定在default生命周期的compile阶段。
default生命周期jar打包方式绑定的插件:
generate-resources | plugin:descriptor |
process-resources | resources:resources |
compile | compile:compile |
process-test-resources | resources:testResources |
test-compile | compiler:testCompile |
test | surefire:test |
package | jar:jar |
install | install:install |
deploy | deploy:deploy |
default生命周期pom打包方式绑定的插件:
package | site:attach-descriptor |
install | install:install |
deploy | deploy:deploy |
通过编写mojo可以自定义插件,详情见下篇。