Maven,JAVA世界里最炙手可热的自动化构建工具,与eclipse一起成为JAVA开发的必备工具,本文仅对Maven的日常使用和执行机制做一个入门级介绍,想要获得对Maven的深入理解可以参考网上资料以及《Maven实战》这本书,个人觉得该书深入浅出内容质量挺不错的,当然如果不从事Maven插件或者二次开发的话也没有必要了解过多的内部机制,毕竟Maven只是一个代码的自动构建工具,所能实现的功能也只是把我们从源码经过引入Jar包,编译最后打包等手动操作自动化,程序写多了无需刻意琢磨很多事情自然就明白了。
1.maven install & configure
与ant类似,maven也是基于Java开发的工具,因此使用maven之前要保证系统已经安装JDK,确认方法就是看在shell里能否查看到javac的版本:
brce@ubtu:~/space$ javac -version
javac 1.7.0_121
然后从maven官网上下载Maven的zip包直接解压到本地任意路径,Maven程序目录树如下所示:
.
├── bin
│ ├── mvn
│ ├── ......
├── boot
│ └── plexus-classworlds-2.5.2.jar
├── conf
│ ├── logging
│ │ └── simplelogger.properties
│ ├── settings.xml
│ └── toolchains.xml
├── lib
│ ├── aopalliance-1.0.jar
│ ├── cdi-api-1.0.jar
│ ├── ......
├── LICENSE
├── NOTICE
└── README.txt
重要的目录及文件有两个:一是Maven的执行命令bin
文件夹下的mvn
脚本,二是Maven的配置文件conf
文件夹下的setting.xml
文件。将bin
文件夹的绝对路径添加到环境变量PATH
中就可以在任意路径下执行mvn
命令,"mvn -v"
回显Maven信息,"mvn -h"
回显Maven帮助。setting.xml
文件是Maven程序层面的配置文件,和接下来要介绍的pom.xml
文件(project层面的配置文件)共同实现Maven构建过程中所要使用的各种设置。
2.pom, coordinate, dependency
pom(project object model)是项目层面的配置文件模型,是Maven的核心文件,最基本的pom.xml
文件只涉及当前的项目构建的坐标(coordinate)和所需依赖(dependency),如下所示:
<?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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xin.brucez.pro</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Demo program</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
在Maven的定义中,任何项目最终会从源代码生成一个以单文件形式存在的程序包,包格式可以为jar,zip和war,每个这样的程序包抽象化为构件(artifact),类似于Java包的概念,每个这样的构件都通过groupId
,artifactId
和version
这三个参数构成的坐标唯一指定,所以每个新建的project都要使用上述参数为当前project构件指定坐标,也同样使用这样的坐标指定项目所需的依赖。
artifact(jar,zip,war) <-> coordinate(groupId,artifactId,version)
Maven和ant的最大区别就在于Maven的依赖管理功能,而依赖问题其实就是JAVA程序的CLASSPATH
问题,在不使用maven的情况下,无论是编译还是执行程序指定CLASSPATH
都是一项必不可少的工作,在项目涉及Jar包较多的情况下,整理Jar包都会耗费大量的时间和精力,而这些问题在使用Maven后就简化为在pom.xml里添加相应的依赖,按照任务的不同Maven根据依赖构建CLASSPATH的场景可分为compile_classpath,test_classpath和running_classpath三类,maven的pom提供了<scope>
标签可为依赖定义在何种场景使用,具体细节这里不再详述。
3+.Maven项目文件结构
Maven的pom文件是独立于项目文件存在的,我们往往都是先定义pom.xml文件之后在构建项目文件,使用Maven进行自动化构建的前提是要遵守Maven的项目结构定义,如下所示:
bce@ntu:~/space$ tree
.
├── pom.xml
└── src
├── main
│ └── java
│ └── main
│ └── helloworld.java
└── test
└── java
└── helloworldTest.java
重要的pom.xml
文件一定要放置在项目根目录下,项目的源码放置在根目录下src/main/java
路径下,项目的测试源码放置在根目录下src/test/java
路径下。因为测试框架使用Junit,因此pom.xml文件要定义Junit的依赖如上节所示,注意:测试类命名必须是<class>Test.java
,否则Maven找不到测试类。
helloworld.java源码如下:
brce@ubtu:~/space/src/main/java/main$ cat helloworld.java
package main;
public class helloworld {
public String sayhello() {
return "hello-world!";
}
public static void main(String[] args) {
System.out.println(new helloworld().sayhello());
}
}
helloworldTest.java源码如下:
brce@ubtu:~/space/src/test/java$ cat helloworldTest.java
import static org.junit.Assert.*;
import org.junit.Test;
import main.*;
public class helloworldTest {
@Test
public void testsayhello() {
helloworld hw = new helloworld();
String result = hw.sayhello();
System.out.println(result);
assertEquals("hello-world!", result);
}
}
在根目录下执行mvn clean test
,首先Maven会自动下载很多构件,然后如无意外可看到测试通过:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running helloworldTest
hello-world!
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.141 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
Maven的项目结构是固定的,在不使用IDE如eclipse的情况手动创建文件夹相当的不方便,因此可以使用Maven archetype插件自动构建项目,插件的内容会在接下来进行介绍,在任意路径下执行mvn archetype:generate
在弹出的交互式命令中填入相应的信息即可生成全新的项目框架。
3.local repository & remote repository(artifact/plugin)
Maven仓库是集中存放构件和插件的地方,可分为本地仓库和远程仓库。任何一个Maven运行实例都需要一个本地仓库且有且只有一个,其作用在于存储项目所需的构件/插件(download)以及存放当前项目构建的构件(mvn install),远程仓库则是本地仓库的下载源以及本地仓库的上传源(mvn deploy),构件的仓库路径遵循相应的格式如下所示:
repo path = groupId/artifactId/version/artifactId-version.packaging
a. Maven默认的local repository路径是~/.m2/repository/
,可以在settting.xml
文件中使用标签自定义:
//setting.xml
<localRepository>/home/bruce/repo/</localRepository>
b. 不同于本地仓库无构件和插件的区分<localRepository>
,Maven会区别对待依赖和插件的远程仓库,即使用不同的标签定义,依赖远程仓库使用<repositories>
和子标签<repository>
指定,而插件远程仓库使用<pluginRepositories>
和子标签<pluginRepository>
指定,虽然一般的情况下二者使用的都是Maven中央仓库。Maven的构件和插件的区别在于插件是Maven自己调用的代码包,而构件是项目调用的代码包。和构件一样插件也同样通过坐标唯一识别,在一般的语境下广义的构件概念涵盖插件,或者说插件是中特殊的构件,二者在本地无区别使用相同的本地仓库存储。
Maven的远程仓库一般在pom.xml
里配置(当然也可以配置到setting.xml文件),这样的原因是pom文件往往会跟随构件一起提交到仓库里供别的项目使用,这样项目就会解析pom文件并从该文件配置的远程仓库地址下载依赖构件避免构件源不同导致诸多兼容问题。Maven的超级POM文件里已经定义Maven的中央仓库作为默认的远程repo,所有的项目的pom都是继承该超级pom,超级pom文件存储在Maven程序lib文件夹下的maven-model-builder-3.5.0.jar
,jar包内的路径为org/apache/maven/model/pom-4.0.0.xml
。
//pom.xml
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
远程仓库以<id>
标签指定的ID唯一标识,id相同会覆盖前者仓库配置,也可以在setting.xml
里用<profile>
标签内定义:
//setting.xml
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus-releases</id>
<url>http://nexus-releases</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
<repository>
<id>nexus-snapshots</id>
<url>http://nexus-snapshots</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus-releases</id>
<url>http://nexus-releases</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
<pluginRepository>
<id>nexus-snapshots</id>
<url>http://nexus-snapshots</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
c. 镜像顾名思义就是原有仓库的替代源,在setting.xml
中使用<mirrors>
和子标签<mirror>
定义,同样使用<id>
标签定义唯一标识,使用<mirrorOf>
标签定义匹配的远程仓库,比如<mirrorOf>*</mirrorOf>
就是匹配所有的远程仓库,即所有对远程仓库的访问都会转发到该镜像。一般用来定义私服。
//setting.xml
<mirrors>
<mirror>
<id>mirror1</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
</mirrors>
d. 大多数公共的远程仓库无需验证即可访问下载,但有时也会遇到需要账户密码认证的服务器,这时就需要配置认证信息,不同于仓库信息可以并且优先配置到pom.xml
文件中,为了安全考虑仓库认证信息必须并且只能配置到setting.xml
中,使用<server>
标签,<id>
标签必须和仓库<id>
一致。
//setting.xml
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
e. 关于私服并没有进行介绍,其实类似于之前介绍的镜像,私服其实就可以理解为自己或者组织内部搭建的远程仓库镜像,搜索构件时Maven首先会检查本地仓库,确认没有时候回检索镜像(私服),如果私服也没有该构件,私服会发送请求检索远程仓库下载,即私服类似于Maven本地仓库和远程仓库的中间件。公共的远程仓库是对构件/插件的上传部署有严格的标准,但在组织内部进行开发并将构件上传到私服是常见的操作(mvn deploy),该操作需要在pom中<distributionManagement>
标签内定义仓库信息,如有认证需要还需在setting.xml
中使用<server>
标签定义认证信息。
//pom.xml
<distributionManagement>
<repository>
<id>proj-release</id>
<name>proj release repo</name>
<url>http://192.168.1.200/</url>
</repository>
<snapshotRepository>
<id>proj-snapshot</id>
<name>proj snapshot repo</name>
<url>http://192.168.1.200/</url>
</snapshotRepository>
</distributionManagement>
4.lifecycle, plugin
Maven内建生命周期概念,将项目构建的各个步骤抽象化为特定的lifecycle phase,Maven作为主框架并不设计特定phase的执行代码,而是通过引入插件目标(goal)与生命周期中的Phase绑定执行,Maven的插件机制是其架构核心。当前的Maven架构里存在三套独立的lifecycle,分别是clean,default和site,其中clean周期用来清理项目,default周期用来构建常规项目,而site周期用来构建web项目,每个生命周期都包含若干phase(阶段),同一生命周期的若干阶段存在前后的依赖关系,即指定某一阶段执行的话默认会先调用执行该阶段之前所存在的阶段。
lifecycle -> phase
clean -> (pre-clean, clean, post-clean)
default -> (compile, test-compile, test, package, install, deploy)
site -> (site, site-deploy)
无需了解phase与goal绑定的相关情况,可以直接使用mvn <phase1> <phase2> ...
执行构建,例如之前使用过的mvn clean test,就是先执行的clean生命周期中pre-clean和clean阶段,删除除源码以外的其他文件之后,在执行default生命周期中的test及其之前阶段,编译并执行单元测试。
Maven的插件坐标机制相同于构件,一个插件可以提供多个功能,每个功能都称之为一个goal,可以说每个生命周期里的phase都有一个goal与之对应,可以直接通过mvn命令执行插件的goal,格式如下:
mvn <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal> #separated by spacekey
mvn <plugin-prefix>:<goal> #separated by spacekey
Maven插件的artifactId
一般都是maven-<plugin_name>-plugin
,插件前缀一般是<plugin_name>
。例如mvn help:describe
不使用plugin-perfix的话就是mvn org.apache.maven.plugins:maven-help-plugin:2.2:describe
,执行所需的插件Maven首先会到本地仓库里检索,没有的话就会到插件远程仓库中下载。
mvn构建命令实质上调用的都是插件的代码,一般可以使用-D<property>=<value>
传入参数。例如mvn help:describe -Dplugin=org.apache.maven.plugins:maven-help-plugin
。
Maven默认对大多数常见的phase已经内置绑定了相应插件的goal,如果想要自定义绑定需要在pom.xml
文件中使用<build>
,<plugins>
和<plugin>
标签定义:
//pom.xml
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>bonding</id>
<phase>verify</phase>
<goals>
<goal>goal_name</goal>
<goals>
<execution>
</executions>
</plugin>
</plugins>
</build>
5.Maven Main Running
mvn exec:java -Dexec.mainClass="main.helloworld" -Dexec.args="arg0 arg1 arg2 ..."
,jetty插件用于web项目,mvn jetty:run
。
6.eclipse maven
m2eclipse插件官网地址:http://www.eclipse.org/m2e/
- 安装m2eclipse插件
- perferences-maven选项卡设置installations(Maven home)和user settings(setting.xml&repo)
- new maven project
- run configuration -> exec:java or jetty:run