本文描述了如何使用maven构建一个简单的Hello World项目
1.编写POM
Maven项目的核心是pom.xml(Project Object Model),pom定义了项目的基本信息,用于描述项目如何构建,声明项目依赖等等。先为Hello World项目编写一个简单的pom.xml。
首先创建一个hello-world的文件夹,在该目录下新建一个名为pom.xml的文件,并输入以下内容:
<?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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>hello-world</artifactId> <version>1.0-SNAPSHOT</version> <name>Maven Hello World Project</name> </project>
modelVersion指定当前pom模型的版本,对于Maven2及Maven3来说,它只能是4.0.0。这段xml代码中最重要的是groupId、artifactId和version三行。这三个元素定义了一个项目基本的坐标。
groupId定义了项目属于哪个组,这个组往往和项目所在的组织或公司存在关联。如果你的公司是mycom,有一个项目叫myapp,那么groupId就应该是com.mycom.myapp。
artifactId定义了当前Maven项目在组中唯一的ID,我们为这个Hello World项目定义artifactId为hello-world。
version制定了项目当前版本------1.0SNAPSHOT。SNAPSHOT意为快照,说明该项目还处于开发中,是不稳定版本。
最后一个name元素声明了一个对于用户更为友好的项目名称,虽然这不是必须的。
2.编写主代码
默认情况下,Maven假设项目主代码位于src/main/java目录,于是我们在hello-world文件夹下创建文件com/juvenxu/mvnbook/helloworld/HelloWorld.java,其内容如下:
package com.juvenxu.mvnbook.helloworld; public class HelloWorld{ public String sayHello(){ return "Hello Maven"; } public static void main(String [] args){ System.out.println(new HelloWorld().sayHello()); } }
代码编写完毕后,使用Maven进行编译,在hello-world文件夹下运行命令mvn clean compile。经过Maven编译,所有输出都在targer目录中。
3.编写测试代码
Maven项目中默认的主代码目录是src/main/java,对应的,Maven项目中默认的测试代码目录是src/test/java。因此需要创建该目录。
在编写测试代码之前,首先需要为Hello World项目添加一个JUnit依赖,修改项目的pom.xml文件:
<?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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.juvenxu.mvnbook</groupId> <artifactId>hello-world</artifactId> <version>1.0-SNAPSHOT</version> <name>Maven Hello World Project</name> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> </dependencies> </project>
代码中添加了dependencies元素,该元素下可以包含多个dependency元素以声明项目的依赖,这里添加了一个junit依赖。有了这段声明,Maven就能自动从中央仓库下载junit-4.7.jar。scope为依赖范围,值为test表明该依赖只对测试有效。也就是说,在测试代码中使用import junit代码没有问题,如果在主代码中使用import junit代码,则会造成编译错误。如果不声明依赖范围,默认为compile,表示该依赖对主代码和测试代码都有效。
配置了测试依赖,下面在src/test/java目录下创建测试类:
package com.juvenxu.mvnbook.helloworld; import static org.junit.Assert.assertEquals; import org.junit.Test; public class HelloWorldTest{ @Test public void testSayHello(){ HelloWorld helloWorld = new HelloWorld(); String result = helloWorld.sayHello(); assertEquals("Hello Maven", result); } }
编写完代码,使用mvn clean test命令执行测试,检查其返回值是否正确。
4.打包
在编译、测试之后,下一个重要步骤就是打包(package)。Hello World的pom中没有指定打包类型,默认的打包类型为jar。执行命令mvn clean package进行打包,在项目的target目录中将会生成一个名为hello-world-1.0-SNAPSHOT.jar的文件。至此,可以复制这个jar文件到其它项目的classpath中从而使用HelloWorld类。
5.依赖范围
上面提到,JUnit依赖的范围是test,实际上,Maven在编译项目主代码的时候使用一套classpath,在执行测试的时候会使用另一套classpath。依赖范围就是用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系,Maven有以下几种依赖范围:
①compile:编译依赖范围,如果没有指定,将默认使用该依赖范围,它对于编译、测试、运行三种classpath都有效。
②test:测试依赖范围,只对于测试classpath有效,在编译主代码或者运行项目时将无法使用此类依赖。
③provided:已提供依赖范围,对于编译和测试classpath有效,但在运行时无效。
④runtime:运行时依赖范围,对于测试和运行classpath有效,但编译时无效。
⑤system:系统依赖范围,对于编译和测试classpath有效,但在运行时无效。
下表表示了各种依赖范围与三种classpath的关系:
作用域 | 编译时有效 | 测试时有效 | 运行时有效 | 实例 |
compile | √ | √ | √ | spring-core.jar |
test | √ | junit.jar | ||
runtime | √ | √ | mysql-connector-java.jar | |
provided | √ | √ | servlet-api.jar | |
system | √ | √ | JDK的rt.jar |
6.传递依赖
如果A依赖于B,B依赖于C,那么说A对B是第一直接依赖,B对C是第二直接依赖,A对于C是传递性依赖。现在假设有如下两种依赖关系:
A -> B ->C -> X (X版本是1.0)
A -> D -> X (X版本是2.0)
X是A的传递性依赖,但是两条依赖路径上有两个版本的X,哪么哪个X会被Maven解析使用呢?两个版本都解析显然是不对的,因为那会造成依赖重复,因此必须选一个。Maven依赖调节的第一原则是:路径最近者优先。该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。
但遇到路径长度相同时,在Maven2.0.8及之前版本中,是不确定解析哪个依赖。在Maven2.0.9开始,为了避免构建的不确定性,Maven定义了依赖调节的第二原则:第一声明者优先,在依赖路径相等的前提下,在pom中依赖声明的顺序决定了谁会被解析使用,即顺序最靠前的那个依赖将会被解析使用。
简而言之:先看路径长短,优先依赖路径短的;如果路径长度相同,优先依赖先声明的。
依赖传递不会传递scope为test的包,传递compile依赖范围的包。
如果产生包冲突,可以使用<exclusions>标签排除依赖。
7.聚合
当一个项目中有多个模块(module)的时候,如果分别对每个模块都进行编译,会显得十分麻烦,此时需要使用maven的聚合,将多个模块聚合在一起管理:
8.依赖继承
当一个项目有多个模块时,我们可以把各模块所依赖的包统统放在父模块中管理,包括依赖的包名、版本号,而在子模块中则不需要再写版本号了。子模块继承父模块时,需要添加以下内容:
<parent> <groupId>org.konghao.cms</groupId> <artifactId>cms-parent</artifactId> <version>0.0.1-SNAPSHOT</version> <relativePath>../cms-parent/pom.xml</relativePath> </parent>
在父模块中添加依赖的时候,需要这样使用标签:
<dependencyManagement>
<dependencies>
<dependency>
而在子模块中,没有<dependencyManagement>标签。dependencyManagement能够让子模块继承父模块的依赖管理。
父模块中,<packaging>属性的值为pom,子模块不为pom,应该为jar或war。通常我们会将继承和聚合都写在父模块中。
注意:继承一定是pom文件路径,而聚合则是模块的路径。