此教程的目标是介绍Maven的基本知识及常见操作。
0. 前言
在没有Maven之前,当我们开发一个Java项目的时候,项目目录结构没有一个统一的标准规范,很多时候配置文件、测试文件、前端页面放哪里都是根据开发人员自己的喜好决定。
同时我们经常需要引用其他jar包,并下载下来放到项目中。然后点击编译的时候会出现ClassNotFoundException,找了半天可能发现,竟然还是少了某个jar包。
其实在Maven之前也是有类似的项目管理构建工具,比如make或Ant,但是使用那些构建工具配置繁琐,效率比较低。
为了解决以上问题,Van Zyl, Jason,也就是“Maven他爸”出现了,他给Java世界带来了一种全新的项目构建方式,给无数Java码农带来了福音。
他开发的Maven的主要解决了两个问题:
- 统一Java项目管理规范
- 统一管理jar包
所以,为了更加愉快地开发,并且让后面维护系统的人员也愉快。让我们开始愉快地使用Maven吧。
1. 安装使用
首先,我们可从Maven官网上面去下载Maven压缩包,解压后,配置环境变量:
- MAVEN_HOME=D:softwareapache-maven-3.5.0-binapache-maven-3.5.0
- MAVEN_OPTS=-Xms128m -Xmx512m
具体路径可改为自己本地路径,并配置不同大小MAVEN_OPTS可让Maven跑得快点。
然后打开cmd,输入mvn -v,出现Maven的版本信息提示,说明安装成功!
其次,我们需要修改一下Maven的配置文件:apache-maven-3.5.0confsettings.xml,新增一个本地目录作为本地仓库并修改localRepository路径。
同时,修改中央仓库地址,推荐使用阿里云镜像:
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
<!-- 阿里云仓库 -->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
<!-- 中央仓库1 -->
<mirror>
<id>repo1</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo1.maven.org/maven2/</url>
</mirror>
<!-- 中央仓库2 -->
<mirror>
<id>repo2</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://repo2.maven.org/maven2/</url>
</mirror>
Maven寻找jar包首先会从本地仓库中去找,如果没有,再到私服(如果有配置公司私服仓库的话)去找,最后再到中央仓库中去寻找。
2. 基本概念
接着,我们需要了解一些Maven的基本概念。
2.1 Maven项目的生命周期
Maven把一个项目的构建分为不同的生命周期,这个过程通常包括:
- 验证(validate): 验证项目正确和所有必要信息是可用的。
- 编译(complile): 编译项目源码。
- 测试(test): 使用合适的单元测试框架测试编译后的代码。这些测试代码不需要被打包或者发布。
- 打包(package): 将编译后的代码打包成指定的格式,比如jar包或者war包。
- 校验(verify): 对集成测试结果进行任意检查,以确保满足质量标准。
- 安装(install): 将打成的jar包安装到本地仓库,以便其他本地项目依赖使用。
- 发布(deploy): 在构建环境中完成,将最终包复制到远程存储库以与其他开发人员和项目共享。
2.2 Maven的标准工程结构
-项目目录
-pom.xml 用于maven的配置文件
-src 源代码目录
-src/main 工程源代码目录
-src/main/java 工程java源代码目录
-src/main/resource 工程的资源目录
-src/test 单元测试目录
-src/test/java
-target 输出目录,所有的输出物都存放在这个目录下
-target/classes 编译之后的class文件
这个目录结构并不是不可更改的,但是Maven的理念是“约定大于配置”,这些配置是默认的,除非必要,并不需要去修改那些约定内容。采用"约定优于配置"的策略可以减少修改配置的工作量,也可以降低学习成本,更重要的是,给项目引入了统一的规范。
2.3 Maven的版本规范
Maven使用以下几个元素来唯一定位某一个输出物: groupId: artifactId: packaging: version。
- groupId: 团体、组织的标识符。团体标识的约定是,它以创建这个项目的组织名称的逆向域名(reverse domain name)开头。一般对应着JAVA的包的结构。例如org.apache
- artifactId: 单独项目的唯一标识符。比如我们的tomcat, commons等。不要在artifactId中包含点号(.)。
- version: 一个项目的特定版本。
- packaging: 项目的类型,默认是jar,描述了项目打包后的输出。类型为jar的项目产生一个JAR文件,类型为war的项目产生一个web应用。
- SNAPSHOT: 这个版本一般用于开发过程中,表示不稳定的版本。
- LATEST: 指某个特定构件的最新发布,这个发布可能是一个发布版,也可能是一个snapshot版,具体看哪个时间最后。
- RELEASE: 指最后一个发布版。
3. 创建Maven项目
创建Maven项目的方法有许多种,最基本的可以使用mvn archetype:generate命令行来创建,但通常我们都直接使用IDE来帮我们生成。下面就简单介绍一下使用IntelliJ IDEA来创建。
3.1 使用IDEA创建Maven项目
File -> New -> Project.. 选择合适的模板
输入项目的基本信息,groupId是项目组织唯一的标识符,实际对应JAVA的包的结构,是main目录里java的目录结构。
artifactId就是项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称。
再选择本地Maven目录和配置文件,
最后点击Finish,就会自动创建一个Maven工程。
3.2 理解pom.xml
pom.xml 称为 Project Object Model(项目对象模型),它用于描述整个 Maven 项目,所以也称为 Maven 描述文件。当执行一个任务或目标时,Maven通过读取POM的配置信息来执行。同时,一个项目可以使用一个父pom来管理多个子模块,每个模块又单独拥有一个pom.xml配置文件。
<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>
<!-- The Basics -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- Build Settings -->
<build>...</build>
<reporting>...</reporting>
<!-- More Project Information -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- Environment Settings -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
由上图可以看出,除了项目的基本信息(Maven 坐标、打包方式等)以外,每个 pom.xml 都还包括:
-
Lifecycle(生命周期)
-
Plugins(插件)
-
Dependencies(依赖)
每个插件又包括了一些多个 Goal(目标),以 compiler 插件为例,它包括以下目标:
-
**compiler:
-
**:用于显示 compiler 插件的使用帮助。
-
compiler:compile:用于编译 main 目录下的 Java 代码。
-
compiler:testCompile:用于编译 test 目录下的 Java 代码。
另外,pom.xml中定义的依赖包,其中有个Scope(作用域),它表示该依赖包在什么时期起作用,分别有下列几种情况:
-
compile:默认作用域,在编译、测试、运行时有效
-
test:对于测试时有效
-
runtime:对于测试、运行时有效
-
provided:对于编译、测试时有效,但在运行时无效
-
system:与 provided 类似,但依赖于系统资源
3.2.1 常用标签
pom使用XML描述,常见的一些标签功能说明如下:
- modelVersion:这个是 POM 的版本号,现在都是 4.0.0 的,必须得有,但不需要修改。
- groupId、artifactId、version:分别表示 Maven 项目的组织名、构件名、版本号,它们三个合起来就是 Maven 坐标,根据这个坐标可以在 Maven 仓库中对应唯一的 Maven 构件。
- packaging:表示该项目的打包方式,war 表示打包为 war 文件,默认为 jar,表示打包为 jar 文件。
- name、url:表示该项目的名称与 URL 地址,意义不大,可以省略。
- build: 表示与构建相关的配置。
- parent: maven 支持继承功能。子 POM 可以使用 parent 指定父 POM ,然后继承其配置。
- dependencyManagement: 在项目的 parent 层,可以通过 dependencyManagement 元素来管理 jar 包的版本,让子项目中引用一个依赖而不用显示的列出版本号.
- dependencies: 定义该项目的依赖关系,其中每一个 dependency 对应一个 Maven 项目,可见 Maven 坐标再次出现,还多了一个 scope,表示作用域。相对于 dependencyManagement,所有声明在父项目中 dependencies 里的依赖都会被子项目自动引入,并默认被所有的子项目继承。
- properties: 属性列表。定义的属性可以在 pom.xml 文件中任意处使用。使用方式为 ${propertie} 。
- repositories: 远程仓库列表,用来获取本地需要的依赖包和扩展。
- pluginRepositories: 远程插件仓库列表,用来获取本地需要的插件。
- modules: 模块列表,创建多模块项目中使用。
- profiles: 用于在不同的环境下应用不同的配置。一套配置即称为一个Profile。比如开发、测试、生产不同环境需要不同配置,再根据激活条件激活某个profile。
- optional: 让其他项目知道,当使用此项目时,不需要这种依赖性才能正常工作。
- exclusions:包含一个或多个排除元素,每个排除元素都包含一个表示要排除的依赖关系的 groupId 和 artifactId。与可选项不同,可能或可能不会安装和使用,排除主动从依赖关系树中删除自己。
- resources: 资源元素的列表,每个资源元素描述与此项目关联的文件和何处包含文件。
- targetPath: 指定从构建中放置资源集的目录结构。目标路径默认为基本目录。将要包装在 jar 中的资源的通常指定的目标路径是META-INF。
- filtering: 值为 true 或 false。表示是否要为此资源启用过滤。
- directory: 值定义了资源的路径。构建的默认目录是${basedir}/src/main/resources
- includes: 一组文件匹配模式,指定目录中要包括的文件,使用*作为通配符。
- excludes: 与 includes 类似,指定目录中要排除的文件,使用*作为通配符。
3.2.2 常用插件
Maven中使用插件只需要在pom.xml文件中添加plugin就行:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
常用的一些插件如下:
插件名 | 描述 |
---|---|
maven-resources-plugin | 文件资源配置 |
maven-compiler-plugin | 编译源代码 |
maven-surefire-plugin | 单元测试使用 |
maven-surefire-plugin | 单元测试使用 |
maven-jar-plugin | 打jar包 |
maven-war-plugin | 打war包 |
maven-assembly-plugin | 打包含依赖包的完整包 |
maven-shade-plugin | 打包含依赖的全包且可以配置主类 |
maven-source-plugin | 含源码打包 |
maven-exec-plugin | 执行命令 |
maven-javadoc-plugin | 生成java文档 |
mybatis-generator-maven-plugin | 生成mybatis映射 |
tomcat-maven-plugin | 相当于使用内置tomcat启动、调试、运行项目 |
spring-boot-maven-plugin | 将spring boot项目打成可执行的jar包 |
tomcat-maven-plugin | 相当于使用内置tomcat启动、调试、运行项目 |
3.2.3 常用命令
生命周期 | 描述 |
---|---|
mvn validate | 验证项目是否正确,以及所有为了完整构建必要的信息是否可用 |
mvn generate-sources | 生成所有需要包含在编译过程中的源代码 |
mvn generate-resources | 生成所有需要包含在打包过程中的资源文件 |
mvn compile | 编译项目的源代码 |
mvn test-compile | 编译测试代码 |
mvn test | 运行应用程序中的单元测试 |
mvn site | 生成项目相关信息的网站 |
mvn package | 将编译好的代码打包成可分发的格式,如JAR,WAR |
mvn install | 安装包至本地仓库,以备本地的其它项目作为依赖使用 |
mvn clean | 清除目标目录中的生成结果 |
mvn clean compile | 将.java类编译为.class文件 |
mvn clean package | 先清除目标目录中的生成结果,再进行打包 |
mvn dependency:list | 查看已解析依赖 |
mvn dependency:tree | 查看依赖树 |
mvn help:active-profiles | 查看当前激活的profiles |
mvn -Dmaven.test.skip=true | 忽略测试文档编译 |
3.2.4 常见问题
3.2.4.1 查找最新版本jar包
可以在http://mvnrepository.com/站点搜寻你想要的jar包版本。例如,想要使用log4j,可以找到需要的版本号,然后拷贝对应的maven标签信息,将其添加到pom .xml文件中。
3.2.4.2 IDEA 修改 JDK 版本后编译报错
- 错误现象
修改 JDK 版本,指定 maven-compiler-plugin 的 source 和 target 为 1.8 。
然后,在 Intellij IDEA 中执行 maven 指令,报错:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.0:compile (default-compile) on project apollo-common: Fatal error compiling: 无效的目标版本: 1.8 -> [Help 1]
- 错误原因
maven 的 JDK 源与指定的 JDK 编译版本不符。
- 解决方案
IDEA查看项目的 Project Settings中的Project SDK是否正确,
SDK 路径是否正确
查看 Settings > Maven 的配置
JDK for importer 是否正确
Runner 是否正确。
3.2.4.3 重复依赖引用
- 错误现象
有时候我们引用的依赖包还会再引用不同的一些第三方包,如果我们本地也引用了那些包或者其他依赖包中也引用了,并且版本不同,这时候就会出现jar包冲突。主要有两种情况:第一类是依赖了同一个jar包的多个版本。第二类是同样的类出现在了依赖的两个及以上的不同Jar包中。
经常会出现一些ClassNotFoundException,NoSuchMethodError,NoClassDefFoundError等异常。
- 错误原因
同一个类出现在不同jar包,不同jar包的类有差异,加载的类不是我们程序期望的版本。
-
解决方案
- 如果有异常堆栈信息,根据错误信息即可定位导致冲突的类名
- 若步骤1无法定位冲突的类来自哪个Jar包,可在应用程序启动时加上JVM参数-verbose:class或者-XX:+TraceClassLoading,日志里会打印出每个类的加载信息,如来自哪个Jar包
- 定位了冲突类的Jar包之后,通过mvn dependency:tree -Dverbose -Dincludes=
: 查看是哪些地方引入的Jar包的这个版本 - 确定Jar包来源之后,如果是第一类Jar包冲突,则可用
排除不需要的Jar包版本或者在依赖管理 中申明版本;若是第二类Jar包冲突,如果可排除,则用 排掉不需要的那个Jar包,若不能排,则需考虑Jar包的升级或换个别的Jar包。
3.2.4.4 Profile使用
<profiles>
<profile>
<!-- 本地开发环境 -->
<id>dev</id>
<properties>
<profiles.active>dev</profiles.active>
</properties>
<activation>
<!-- 设置默认激活这个配置 -->
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<!-- 发布环境 -->
<id>release</id>
<properties>
<profiles.active>release</profiles.active>
</properties>
</profile>
<profile>
<!-- 测试环境 -->
<id>beta</id>
<properties>
<profiles.active>beta</profiles.active>
</properties>
</profile>
</profiles>
义了三个环境,分别是dev(开发环境)、beta(测试环境)、release(发布环境),其中开发环境是默认激活的(activeByDefault为true)。
读取配置文件的过程如下:
1. 通过profile选中你要使用的环境
2. 通过package命令,将环境变量注入到application.properties中
3. 项目中加载application.xml文件
另外,profile根据不同需要可定义在不同位置,
1. 针对于特定项目的profile配置我们可以定义在该项目的pom.xml中。
2. 针对于特定用户的profile配置,我们可以在用户的settings.xml文件中定义profile。该文件在用户家目录下的“.m2”目录下。
3. 全局的profile配置。全局的profile是定义在Maven安装目录下的“conf/settings.xml”文件中的。
其中激活方式有几种:
1. 在pom.xml中设置<activeByDefault>true</activeByDefault>
2. 打包时使用-P参数显示激活一个profile,比如mvn package –Pdev激活开发环境
3. 根据环境来激活profile,比如不同的操作系统激活不同的profile,也可以根据jdk版本的不同激活不同的profileelper
我们可以使用mvn help:active-profiles命令来查看当前激活的profile。