Maven可以称之为当前项目构建的事实上的标准了,基本上做的项目都是用Maven构建的。一直在使用Maven,但是没有较为全面地了解过,这篇博客主要记录之前使用过程中被忽略的部分,Maven的使用之前写过,传送门:Maven的学习
Maven配置与安装
Maven修改配置文件的最佳实践
Maven的配置文件,位于Maven安装目录/conf/settings.xml
之前都是直接在这个文件中修改Maven配置,实际上这里的配置文件是全局配置,对于当前操作系统所有的用户生效,整台机器上所有用户都会直接受到该配置的影响。最佳实践是我们将这个配置文件拷贝放在 ~/.m2/settings.xml
,(~ 表示C盘/user/用户目录)这是用户范围的设置,可以在不同用户之间形成不同配置,而且互不影响,不推荐直接修改Maven安装目录下的文件,包括全局settings.xml
这样的配置还有一个好处就是,方便Maven升级,直接修改conf目录下的settings.xml,当升级Maven版本后还需要记得修改新的settings.xml,如果使用 ~/.m2
目录下的settings.xml就不需要了
验证Maven是否安装成功
当解压Maven,配置环境变量,修改settings.xml之后推荐使用如下命令验证
mvn help:system
这个命令通过使用Maven的help插件输出当前系统的信息,如果没有help插件会要求联网从仓库中下载,所以推荐将这个命令作为安装Maven后的第一个命令用于验证。mvn -v
命令可以验证Maven是否安装成功,环境变量是否安装成功,但是对于settings.xml的修改没法验证
修改Maven compiler编译的Java版本
通常Maven编译插件支持的版本较低,同时推荐显式指定Java编译版本,可以参看如下配置
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
命令行使用Archetype生成项目骨架
这里记录下项目骨架命令
mvn archetype:generate
接下来会要求输入项目骨架编号
接下来输入groupId,artifactId,version以及包名package即可
打包一个可运行的jar
默认打包生成jar是不能直接运行的,因为带有main方法的类信息不会添加到manifest中(打开jar文件中的META/MANIFEST.MF文件无法看到Main-Class),为了生成可执行的jar文件,需要借助maven-shade-plugin,配置如下
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>Main方法所在类的全路径</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
这样打出来的jar是带有Main-Class信息的可执行jar,使用java -jar
即可运行
依赖
排除依赖
因为依赖传递的问题,可能因为某些原因传递过来的版本不稳定或者想要指定某个版本,可以使用<exclusions>
标签排除某些传递的jar,这也是解决jar冲突的一种方案
<dependency>
<groupId>com.lynu</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>com.lynu</groupId>
<artifactId>project-b</artifactId>
</exclusion>
</exclusions>
</dependency>
这样在当前项目中使用project-a的时候,就把其所依赖的project-b给排除了
exclusions
可以有多个exclusion
元素排除多个依赖,声明exclusion
元素只需要groupId
和artifactId
即可,因为只需要groupId
和artifactId
就可以唯一定位依赖
可选依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
dependency
元素有一个optional
属性,该属性可以指定这个依赖是可选依赖,可选依赖的jar只对当前项目可以使用,如果当前项目作为其他项目的依赖jar,则其他项目不能通过传递得到这个可选依赖,如果需要使用就要显式声明
仓库
配置远程仓库
Maven中仓库只分为本地仓库和远程仓库,远程仓库包括:中央仓库,私服以及其他公共仓库
本地仓库默认会在~/.m2/respository
目录下,该目录一般位于c盘,如果需要自定义的话可以编辑settings.xml
文件设置localRepository元素的值即可。而配置远程仓库,可以在项目的POM中配置
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/maven2/</url>
<releases>
<!--是否下载releases发布版构件-->
<enabled>true</enabled>
<!--更新频率-->
<updatePolicy>daily</updatePolicy>
<!--检查校验文件的策略-->
<checksumPolicy>ignore</checksumPolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<!--仓库文件布局, default表示使用的是Maven2及Maven3的默认布局, 而不是Maven1的布局-->
<layout>default</layout>
</repository>
</repositories>
updatePolicy
:配置Maven从远程仓库检查更新的频率。默认值daily-每天,其他可用值 never-从不检查更新 always-每次构建时检查更新 interval:X-每隔X分钟检查更新(X为整数)
checksumPolicy
:配置Maven检查校验文件的策略。Maven在下载构建或者部署构建到仓库中时,会校验对应的文件,如果校验失败,就会采用配置的策略。默认值warn-校验失败输出警告信息,其他可选值 fail-校验失败停止构建 ignore-完全忽略校验和错误
将jar部署到远程仓库都在Maven的学习中有记录
远程仓库的认证
如果远程仓库需要用户名,密码任何认证,就需要配置认证信息了。认证信息与配置远程仓库信息不同,仓库信息可以直接配置到POM文件中,但是认证信息必须配置到settings.xml
文件中。这是因为POM文件会被提交到SVN/GIT代码仓库中,而settings.xml
在本机,出于安全性考虑
<servers>
<server>
<id>my-repo</id>
<username>repo-user</username>
<password>repo-pwd</password>
</server>
</servers>
id必须与POM中远程仓库的id完全一致,正是这个id将认证信息与仓库配置联系在一起
一些仓库搜索服务
直接去中央仓库中找需要的jar不太明智,可以使用一些带有搜索服务的Maven服务,例如MVNrepository
继承与聚合
Maven的继承与聚合,实际上是两个不同的概念,聚合:将多个module
模块聚集在一起形成一个统一的项目;继承:作为父工程定义一些属性供子工程复用。实际开发中经常将这个两个概念统一在父工程中。父工程只包含一个type为pom的POM.xml
文件, 对子工程进行依赖与插件管理。记录一个通用的父工程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>xxx</groupId>
<artifactId>xxx</artifactId>
<packaging>pom</packaging>
<name>XXX Project</name>
<version>1.0-SNAPSHOT</version>
<!--定义自定义属性-->
<properties></properties>
<!--聚合其他模块-->
<modules>
<module>xxx</module>
</modules>
<!--依赖管理,并不会真正引入依赖-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
</dependency>
</dependencies>
</dependencyManagement>
<!--插件管理,并不会真正引入插件-->
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
在父工程进行依赖管理和插件管理,更多的是在限定使用的版本,而且也没有真实引入插件和依赖,真正引入插件和依赖的地方还是在子工程中,而且子工程中也可以引入不同版本的依赖/插件,或者引入父工程中没有Manager的依赖/插件
当在其他项目中需要使用父工程依赖/插件一样的配置,可以使用import
范围依赖将这一个配置导入
<dependencyManagement>
<dependencies>
<dependency>
<groupId>存在依赖/插件管理的groupId</groupId>
<artifactId>存在依赖/插件管理的artifactId</artifactId>
<version>存在依赖/插件管理的version</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子工程pom.xml
文件中使用parent
标签引入父工程坐标形成继承
<parent>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<!--relativePath用于指定父工程的POM.xml文件位置 相对路径 默认位置../pom.xml 如果路径不同使用该标签修改-->
<!--<relativePath></relativePath>-->
</parent>
只构建指定模块
但项目比较庞大,而且模块众多,一次构建所有模块费时费力,这个时候可以指定所需模块,利用Maven的参数即可,因为模块之间存在依赖关系,构建时必须存在先后顺序,这种关系在Maven中称之为反应堆
- -am:--also make 同时构建所指定的模块及依赖模块
- -amd:--also-make-dependents 同时构建依赖于该模块的模块
- -pl:--projects 构建指定的模块,模块之间使用逗号分隔
- -rf:--resume-from 在反应堆构建顺序基础上指定从哪个模块开始构建
以上几个参数可以组合使用,例如:编译xxx模块,并先构建其依赖的模块(不然xxx模块无法使用)
mvn clean package -pl xxx -am
超级POM
所有pom.xml
文件都默认继承一个超级POM文件,这个文件中指定了一些常用插件的版本,项目源码,配置文件,测试代码/配置文件都路径,项目构建路径等信息,这样的方式是Maven约定优于配置理念的提现,这个超级POM文件位于:$MAVEN_PATH/lib/maven-model-builder-x.x.x.jar
中的org/apache/maven/model/pom-4.0.0.xml
路径下
所有的约定都在这里配置的,如果不想遵循约定(强烈不推荐),例如约定源码目录是:src/main/java
,我们希望改为src/java
,就需要在项目的pom.xml
中的build
元素中修改
<build>
<sourceDirectory>src/java</sourceDirectory>
<build>
测试
跳过测试
测试作为项目构建中很重要的一步,理论上不应该跳过测试,只有测试通过之后的才会进行打包,安装等后续操作,这样也保证了项目的稳定。但是运行测试代码需要较长耗时,而且有些错误的单元测试没有得到修正,所以我们希望可以跳过测试。跳过方法两种
- 通过命令行参数
skipTests
mvn package -DskipTests
这个指令可以跳过测试运行2,但是测试代码还是会进行编译,同样会消耗大量的时间
mvn package -Dmaven.test.skip=true
这样就可以连测试代码编译过程也跳过了
通过命令行参数的方式是一种临时配置,只在当前执行中生效
- 通过Maven插件参数配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
这样的配置等同于mvn package -DskipTests
,不同在于通过插件的配置让项目长时间跳过测试
同样的,想要跳过测试代码的编译过程,如下配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
同样对应命令mvn package -Dmaven.test.skip=true
指定要运行的测试用例
maven-surefire-plugin
提供了test
参数让Maven用户能够在命令行指定要运行的测试用例
mvn test -Dtest=MyTest
可以使用*
星号匹配零个或多个字符,如果指定多个测试用例,可以使用,
逗号分隔
test
参数必须匹配一个或多个测试类,如果没有就会报错,这时可以设置-DfailIfNoTests=false
告诉maven-surefire-plugin
即使没有任何测试也不要报错
mvn test -Dtest -DfailIfNoTests=false
包含与排除测试用例
默认情况下,在测试目录下所有以Test
结尾的类都会当作测试用例,我们一般遵循这个规范就可,但是有的测试类是以Tests
结尾的,这时就需要我们主动包含这样的测试用例
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
<includes>**/*Tests.java</includes>
</configuration>
</plugin>
**
表示任意路径,一个星号*
匹配0个或多个字符
如果需要排除一些测试用例,可以使用excludes
元素
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<excludes>**/*TempDemoTest</excludes>
</configuration>
</plugin>
打包测试代码
一般来说测试代码是不会打包的,但是如果需要的话,也是可以办到的,通过配置maven-jar-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
这样在Maven的生命周期package
阶段就会对测试代码打包,形成类似xxx-tests.jar
文件,但这个文件部署到仓库中之后,就可以通过依赖声明了
<dependency>
<groupId>com.test</groupId>
<artifactId>myTest</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>test-jar</type>
<scop>test</scop>
</dependency>
与一般的以来不同的是,这里使用了特殊的元素type
,而去测试包只能使用特殊的test-jar
类型
web容器插件
配置jetty插件
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.6.16.v20140903</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/test</contextPath>
</webAppConfig>
</configuration>
</plugin>
scanIntervalSeconds
表示扫描项目变更的时间间隔,这里配置的是每隔10秒,默认值是0,表示不扫描,配置这个值可以实现类似热部署的功能
contextPath
表示项目部署之后的context path
例如这里:http://hostname:port/test/
访问应用
在启动之前可以对setting.xml
文件进行一个小修改(可选)。因为默认情况下,只有org.apache.maven.plugins
和org.codehaus.mojo
两个groupId
的插件才可以支持简化命令行调用
<setting>
<pluginGroup>
<pluginGroup>org.mortbay.jetty</pluginGroup>
</pluginGroup>
</setting>
现在可以在命令行中使用如下命令启动jetty-maven-plugin
mvn jetty:run
jetty
默认监听本地8080端口,如果使用其他端口,可以添加jetty.port
参数
mvn jetty:run -Djetty.port=9090
配置tomcat插件
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port>
</configuration>
</plugin>
运行tomcat7插件的命令如下
mvn tomcat7:run
因为这个插件有很长时间没有更新了,所以不是很推荐使用,不如直接部署到tomcat容器中,还可以挑选指定的tomcat版本
Maven属性
Maven的属性有6类,这些属性就相当于Maven中的变量。分别为
- 内置属性:主要有两个内置属性,
${basedir}
表示项目根目录,即包含pom.xml文件所在的目录;${version}
表示项目的版本号 - POM属性:可以适用该类属性应用POM文件中对应元素的值。例如
${project.artifactId}
对应<project><artifactId>
元素的值。常用的POM属性有${project.build.sourceDirectory}
: 项目的主源码目录,默认为src/main/java${project.build.testSourceDirsctory}
:项目的测试源码目录,默认为src/test/java${project.directory}
:项目构建输出目录,默认为target${project.outputDirectory}
:项目主代码输出目录,默认为target/classes${project.testOutputDirectory}
:项目测试代码编译输出目录,默认为target/test-classes${project.groupId}
:项目的GroupId${project.artifactId}
:项目的ArtifactId${project.version}
:项目的version, 与${version}
等价${project.build.finalName}
:项目打包输出文件的名称,默认为${project.artifactId}-${project.version}
- 自定义属性,这个应该是最为常见的,用户可以在POM的
<projects>
元素下自定义Maven属性,例如
<project>
<properties>
<my.prop>hello</my.prop>
</properties>
</project>
然后在POM中使用${my.prop}
的时候就会被自动替换为hello
- Settings属性:用户可以在
settings.
开头的属性引用settins.xml文件中XML元素的值,比较常用的${settings.localRepository}
指向用户本地仓库地址 - Java系统属性:所有Java系统属性都可以被Maven属性引用,例如
${user.home}
指向了用户目录,用户可以使用mvn help:system
参看所有的Java系统属性 - 环境变量属性:所有环境变量都可以使用以
env.
开头的Maven属性引用。例如${env.JAVA_HOME}
指向了JAVA——HOME环境变量的值,用户可以使用mvn help:system
参看所有的环境变量
利用这些属性可以更好地简化POM的配置,例如在多模块项目中,模块之间以来比较常见,这些模块之间一般都是相同的groupId和version,因此就可以如下利用POM属性配置
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>dao</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>util</artifactId>
<version>${project.version}</version>
</dependency>