Maven是构建工具,能把项目抽象成POM(project object model),Maven使用POM对项目进行构建、打包、文档化等操作。最重要的是解决了项目需要类库的依赖管理,简化了项目开发环境搭建的过程,使得我们开发一个从简单到大型的复杂项目变得很容易。
Maven介绍
Maven采用了不同方式对项目构建进行抽象,比如源码位置总是在src/main/java,配置文件则在src/main/resources中,编译好的类总是放在项目的target目录下,总的来说,Maven实现了以下目标:
- 使构建项目变得很容易,Maven屏蔽了构建的复杂过程。比如,你只需要输入maven package就可以构建整个Java项目。
- 统一了构建项目的方式,不同人、不同公司的项目都有同样的描述项目和构建项目的方式,Maven通过pom.xml来描述项目,并提供一系列插件来构建项目。
- 提出了一套开发项目的最佳实践,而不用每个项目都有不同结构和构建方式,比如源代码在src/main/java中,测试代码在src/test/java中,项目需要的配置文件则放在src/main/resources中。
- 包含不同环境项目的构建方式
- 解决了类库依赖的问题,只需要声明使用的类库,Maven会自动从仓库下载依赖的jar包,并能协助你管理jar包之间的冲突。
POM文件
Maven的核心是pom.xml,用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>me.rowkey</groupId>
<artifactId>test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>antares</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>nexus-suishen</id>
<name>Nexus suishen</name>
<url>http://maven.etouch.cn/nexus/content/groups/public/</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</snapshots>
</repository>
</repositories>
<properties>
<slf4j.version>1.7.21</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf5j.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
pom.xml通常有以下元素:
groupId:表示项目所属的组,通常是一个公司或组织的名称,如org.springframework;
artifactId: 项目唯一的标志,比如,有spring-boot-starter-web、spring-boot-devtools。groupId和artifactId能唯一标识一个项目或者一个库,我们通常称之为项目坐标。
packaing: 项目的类型,常用的有jar和war两种,jar表示项目会打包成一个jar包,这是Spring Boot的默认使用方式。
version:项目的版本号
通常来说,项目版本号分三段,主版本号.次版本号.修订版本号。主版本号变动代表架构变动或者不兼容实现,次版本号是兼容性修改、功能增强,修订版本则是bug修复。
modelVersion: 代表pom文件的Maven的版本
properties 是全局属性的配置
dependencies:此元素下包含了多个dependency,用来声明项目的依赖,这是pom最核心的部分。
dependency:包含在dependencies中,用来声明项目的依赖。
scope: scope代表此类库与项目的关系,默认是compile,也就是编译和打包都需要此类库。test表示仅仅在单元测试的时候需要;provided表示在编译阶段需要此类库,但打包阶段不需要,这是因为项目的目标环境已经提供了。runtime表示在编译和打包的时候都不需要,但在运行的时候需要。
build:此项在pom中可选,build包含了多个插件plugin,用来辅助项目构建。
plugins:对插件的管理
pom的继承
可以通过parent实现POM的继承以完成统一配置管理,子POM中的配置优先级高于父POM。
例如,以Spring Boot为基础的项目中,pom.xml文件中往往有如下属性:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
这样spring-boot-starter-*依赖就不需要指明version版本了,起到了统一控制版本的作用:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
能够继承的元素如下:
- groupId,version
- Project Config
- Dependencies
- Plugin configuration
此外,<dependencyManagement>和<pluginManagement>可以统一做依赖和插件的配置管理,不同于<dependencies>和<plugins>的是,如果子POM中没有声明<dependency>和<plugin>则并不生效。
标准Web项目结构
在Maven中,一个Web项目的标准结构,如下所示:
其中:
- src/main/java Java代码目录
- src/main/resources 配置文件目录
- src/main/webapp webapp根目录
- src/test/java 测试代码目录
- src/test/resources 测试配置目录
- target/classes 代码编译结果目标目录
- target/test-classes 测试代码编译结果目标目录
自定义项目结构
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
</configuration>
</plugin>
</plugins>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>test/resources</directory>
</testResource>
</testResources>
<directory>build</directory>
</build>
这里,Java代码目录移到了./src中,测试代码目录移到了./test/java中,测试资源也移到了./test/resources中,同时编译结果目录变成了./build。此外,在maven-war-plugin中,也把Web目录的war源码目录改为了./WebContent。
配置仓库
在最开始的maven配置文件中,有个repository的配置。此外,Maven还有一个镜像库的配置,即在Maven的setting.xml中配置Maven镜像库。和pom.xml中的repository不同的是,镜像会拦截住对远程中央库的请求,只在镜像库中进行依赖的搜索以及下载。而如果只是配置了repository,那么当在repository中找不到想应的依赖时,会继续去远程中央库进行搜索和下载。
添加仓库镜像
进入Maven的安装目录, 进入conf目录,编辑settings.xml。
-
找到mirrors元素,添加如下仓库镜像
<mirror> <id>nexus-aliyun</id> <mirrorOf>central</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror>
注意:镜像不一定包含所有的依赖库,如果Maven在构建项目中报出缺少依赖库,请暂时注释掉这个镜像并重新构建,切换到中心仓库。
项目构建流程
Maven的构建生命周期中几个常见阶段如下:
- validate:验证项目以及相关信息是否正确。
- compile:编译源代码和相关资源文件。
- test:对测试代码进行测试。
- package:根据不同的项目类型进行打包。
- verify:验证打包的正确性。
- install:将打好的包,安装到本地。
- deploy:将打好的包发布到远程库中。
profile
现实开发中一个常见的需求就是需要根据不同的环境打包不同的文件。Maven中的profile即可解决此问题。
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<resources.dir>src/main/resources/dev</resources.dir>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<resources.dir>src/main/resources/test</resources.dir>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<resources.dir>src/main/resources/prod</resources.dir>
</properties>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>${resources.dir}}</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
如此,分为dev、test和prod共3种环境,对应每一种环境,其资源文件路径都不一样。在使用MVN时,使用-p参数指定profile即可生效。
Maven常用插件
- maven-source-plugin:源码发布插件,绑定在compile阶段,执行jar goal,将源码以jar包的形式发布出去。
- maven-javadoc-plugin:javadoc插件,将源码的javadoc发布出去。
- maven-tomcat7-plugin:此插件可以直接使用Tomcat运行Web项目,常用的命令是mvn tomcat7:run。同样的还有jetty-maven-plugin
- maven-shade-plugin:此插件是Maven常用的打包插件,一般将其绑定在package阶段,执行其shade goal。其能够将源码和依赖的第三方资源打包在一起以供独立运行。
- maven-assesmbly-plugin:和maven-shade-plugin一样也是打包插件,但是其功能更加强大,输出压缩包格式除了jar外,还支持tar、zip、gz等。
- maven-gpg-plugin:此插件是jar包的签名插件,可以对自己发布的jar包进行签名。
Maven的常用命令
mvn compile:编译Maven工程,进入项目目录,运行mvn compile
mvn package:编译并打包工程,根据pom.xml中元素packaging是jar还是war进行打包。
mvn install:打包并安装到本地仓库。如果你的项目是一个基础类库,本地其他项目也需要,则需要安装到本地仓库。这样,其他本地Maven项目就可以通过项目坐标引用。
mvn deploy:同install,但打包并安装到远程仓库。
mvn clean:删除target目录。
将依赖复制到指定目录:mvn dependency:copy-dependencies -DoutputDirectory=./lib
部署非Maven项目的jar包:mvn deploy:deploy-file -DgroupId=[groupId] -DartifactId=[artifactId] -Dversion=[version] -Dpackaging=jar -Dfile=[jarFilePath] -Durl=[repositoryUrl]
执行指定类中的main方法:mvn exec:java -Dexec.mainClass=[mainClass]
查看依赖树:mvn dependency:tree
执行指定的测试用例:mvn test -Dtest=[ClassName]#[MethodName] 其中#[MethodName]为要运行的方法,支持*通配符
跳过测试阶段且不编译测试用例类: mvn -Dmaven.test.skip=true ...
跳过测试阶段但编译测试用例类:mvn -DskipTests ...
使用指定的POM文件或者指定目录下的pom.xml运行:mvn -f [file/dir] ...
此外,可以使用-q参数是Maven的日志输出只包含错误信息。
Maven仓库
Maven的仓库有两大类,第一类是远程仓库,包括中心仓库,位于http://search.maven.org/; 还包括镜像仓库,比如国内常用的镜像http://maven.aliyun.com/nexus/,还有利用nexus软件自己搭建的公司私服。
还有一类是本地仓库,位于用户目录的.m2目录下,远程仓库加载的库总是会先放到本地仓库作为缓存。
最后
下面是一些提示:
在项目版本号中加入SNAPSHOT后缀作为快照版本,可以使得Maven每次都能自动获取最新版本而无需频繁更新版本号。
mvn -DEAME=test 可以传递给POM参数,使用${NAME}引用即可。
在dependency中设置optional为true,可以使得此依赖不传递出去。
由于Maven自定义plugin很复杂,不够灵活,因此很多时候都是结合Ant的灵活性和Maven一起使用的。
-
日常开发中一个工程可能比较庞大,这时可以把这个工程拆分成多个子模块来管理。一个多模块工程包含一个父POM,在其中定义了它的子模块,每个子模块都是一个独立的工程。
<project> ... <packaging>pom</packaging> <modules> <module>module-1</module> <module>module-2</module> </modules> </project>
参考书籍:
- 《Java工程师修炼之道》
- 《Spring Boot2精髓》