基本介绍
Maven是什么:一款用于Java项目的项目管理和自动构建的Apache基金会开源工具,可以管理项目之间的各种关系,包括依赖、继承、聚合关系。Maven关注两个方面:软件的构建过程和软件依赖关系。使用xml文件来描述一个软件的基本信息、依赖项、构建顺序、目录、以及需要的插件。
Maven怎么来的:ANT项目管理工具的改进版,贯彻coc(约定大于配置)思想,将一些常用的构建步骤预定义给用户使用(如compile、package、test等)。
怎么安装和使用。如果使用IDEA,则IDEA内置了Maven,不需要再额外安装。可以在设置中找到内部绑定的版本,如图所示。
也可以自己下载最新版本的maven,官网地址:https://maven.apache.org/download.cgi,解压后设置环境变量即可。要在IDEA中使用,只要在IDEA的设置中设置自定义maven的路径就可以使用了。Maven的安装目录有如下结构:
- bin 命令行可执行文件
- conf 其中的settings.xml是核心(全局)配置文件。最开始没有.m2目录,要第一次运行过mvn命令才创建。可以运行mvn help:system。有了.m2目录之后,把settings.xml拷贝到.m2即可。
- boot maven启动需要的jar包。
Maven项目标识
在Maven中,通常通过groupId, artifactId, version唯一标识一个项目。groupId类似namespace,一般是公司名称或者个人域名。artifactId可以理解为项目名,version表示项目版本,给予项目时间上的精确指定。
一个典型的项目标识如下:
<groupId>io.kubernetes</groupId>
<artifactId>client-java</artifactId>
<version>17.0.0</version>
Maven项目结构
大部分maven项目组织源代码都会包含一些Maven项目固定的结构:
- src-main
- java 源代码文件
- resources 用到的yaml xml等资源文件
- src-test
- java 测试源代码
- resources 测试用到的yaml xml等
- target 编译后内容所在的文件
根目录中最重要的文件是pom.xml,是对项目的描述,称为项目对象模型。其中包含的信息有:
- 项目的名称和版本
- 项目如何构建(包括使用的插件)
- 项目和其他项目的关系:聚合、继承、依赖
- 属性(变量)定义
- 管理信息(如可以约束统一的依赖版本)
Maven项目分类
Maven项目有三种类型:
- pom项目。逻辑项目,用在父项目或者聚合项目,里面不写代码。用来做jar包版本的控制,工程聚合。用
<packaging> pom </packaging>
表示这是一个pom项目。 - jar项目。打包成jar包。
- war项目。打包成war包。
Maven项目关系
Maven项目管理的核心就在于可以管理多个项目之间的关系。
Maven项目的关系有:
- 依赖:项目A中需要导入项目B的jar包,则A依赖于B。在A的pom中需要将B的标识加入dependencies中。
- 继承:项目A是B的子项目,会继承B的pom文件,称为A继承B。在A的pom中需要将B的标识加入到parent标签中。
- 聚合:项目A是B的父项目,要求构建A的时候会构建B和所有其他子模块,称为A聚合了B。在A的pom中将B的名称加入到modules中即可。
项目依赖关系规则:
- 依赖传递:如果B依赖C,A依赖B,则A也依赖于C。
- 最短路径优先原则:A->C(2.0) A->B->C(1.0),则还是C(2.0)会被使用。
- 最先声明原则:如果依赖的两个包版本冲突,且路径长度一样,则选择先写的那个里的。
- 依赖排除:一个dependency下如果配了exclusion,则exclusion中的依赖不会传递过来。
- 依赖范围:scope,常用的compile(默认,表示编译和运行时都生效),runtime(只有运行时生效,如JDBC驱动)、provided(不会打到jar包里)、system(通过systemPath手动从文件导入依赖)、test(测试代码中才使用)、import(父工程中dependencyManagement中使用,有import之后子工程必须使用这个版本的依赖)
Tip: 在IDEA中可以在Maven面板中查看每一个项目的依赖项
Maven仓库
分为本地仓库和远程仓库。使用jar包时,如果本地仓库没有,maven会自动到远程仓库中下载依赖,下载到本地仓库,然后再使用。
远程仓库:不在本机的一切仓库都是远程仓库,分为 中央仓库 和私服仓库。
- 中央仓库:mvnrepository.com
- 私服仓库:自己搭建的仓库
本地仓库用来缓存远程仓库,还有自己部署的一些构件。setting.xml中的localRepository位置配置。
镜像仓库:可以指定给某个远程仓库做镜像的仓库。mirror标签指定。
仓库优先级:
- 先在本地仓库中查找
- 然后在setting.xml中指定的仓库查找
- 镜像仓库
- 中央仓库
Settings.xml
这个文件是Maven的全局配置文件,通常位于~/.m2目录下。包含的配置内容通常有:
本地仓库地址(目录)。
<localRepository>/path/to/custom/repository</localRepository>
镜像仓库地址。写在mirrors标签下。
<mirror> <id>aliyunmaven</id> <mirrorOf>Central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>
远程仓库。先在servers标签下定义server,然后配置相应的仓库。
<server> <id>ClawsGitea</id> <configuration> <httpHeaders> <property> <name>Authorization</name> <value>token xxx</value> </property> </httpHeaders> </configuration> </server> <repositories> <repository> <id>ClawsGitea</id> <url>https://gitea.claws.top/api/packages/jingjiecb/maven</url> </repository> </repositories>
一些profile定义和激活。profile是一组可选的配置,可以定义在什么条件下激活,激活时可以覆盖掉一些默认配置。后面会详细讲解。
Maven插件
绝大多数Maven的功能都是插件提供的(包括常用的compile、test、package)。而运行插件的方式也很简单:mvn <plugin-name>:<goal-name>
。因此,编译动作其实可以用mvn compiler:compile
来执行。
当然,平时编译的时候没有用过mvn comiler:compile
,因为每次都需要自己逐个调用插件的功能真的是太麻烦了。用户使用的mvn compile
命令通常就够用了。原理:compile是maven生命周期的一个阶段,compiler插件的compile goal关联在compile阶段中,执行mvn compile
时,会自动执行所有关联的插件的goal,就可以触发编译了。
这些默认的插件不需要显式地写在项目的pom文件中,maven在运行时会自动先把这些插件加上(体现COC约定大于配置)。使用
mvn help:effective-pom
命令就可以查看maven生成的最终pom文件是什么样子的。
Maven生命周期
maven的生命周期分为了多个有先后顺序的阶段,默认的阶段序列是:
- validate
- generate-sources
- process-sources
- generate-resources
- process-resources
- compile
- process-test-sources
- process-test-resources
- test-compile
- test
- package
- install
- deploy
在指定一个阶段进行执行时,maven会顺序执行这个阶段以及之前的所有阶段中管理的goal。例如,compiler插件的compile这个goal关联在compile阶段,而surefire插件的test插件关联在test阶段。因此如果执行mvn compile
则会执行compiler:complie,而如果执行mvn test
则会执行compiler:compile和surefire:test。
常用的clean阶段并没有出现在这个默认生命周期中,这是因为并不是需要每次构建的时候都一定要清楚之前的产物,这样做有一点浪费。因此clean被放到了自己专用的生命周期lifecycle中。
Maven常用命令
- clean 清理上次的构建
- compile 编译
- package 编译打jar包
- install 编译打jar包,安装到本地仓库
- deploy 编译打包,发布到远程仓库
- mvn clean package -DskipTests 跳过测试打包
在命令行使用mvn创建项目:
mvn archetype:generate -DgroupId=top.claws -DartifactId=testMavenCmd -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false -DproxySet=true -DproxyHost=localhost -DproxyPort=7890
看起来似乎有点复杂,学习了Maven插件的内容就知道,这里执行了archetype插件的generate这个goal,通过一些环境变量的设置生成了一个项目。前面两个变量指定了要创建项目的groupId和artifactId;中间两个参数描述了使用哪个原型来创建,以及原型的版本号;最后interactiveMode表示是否以交互模式,询问一些配置再创建,如果false则插件将不再询问,直接采用默认的配置创建项目。
Maven常用插件
编译器插件:方便变更jdk版本。org.apache.maven.plugins maven-compiler-plugin插件。示例版本3.2
资源拷贝插件:正常情况下resources下的资源会被拷贝到target/classes下,其他地方的不会被拷贝,如果想拷贝其他地方的资源文件,就要使用到资源拷贝插件。注意:一旦自己配置了build 的 resources,maven就不会默认把resources下的文件复制到classes下了,需要自己全部配全。拷贝到classes时,会保留原来的目录层级。
tomcat 插件:一个自己可以手动操控运行的插件。先建一个wer项目,在main/webapp路径下放上一个index.jsp页面,之后在pom.xml中进行插件配置。配置完成后,在右侧的Maven面板的plugins下的tomcat7就能找到run命令,运行run就可以看到服务器跑起来了。
<plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> </configuration> </plugin>
以上是一些简单使用和介绍,接下来会从原理上来解释一下maven各项使用。
Maven Properties
属性,可以理解为变量。通过
<properties>
<!--最常见的property之一。这个property会被maven-compiler-plugin使用,来判断源代码的JDK版本-->
<maven.compiler.source>11</maven.compiler.source>
<!--这是一个自定义的名为laji的property,值为yes-->
<laji>yes</laji>
</properties>
学习这部分时,可以用
mvn help:evaluate -Dexpression=laji
类似的命令来求laji这个property的最终的值,方便调试
重复定义变量时,后面的变量会覆盖前面的值。
<properties>
<!--这是一个自定义的名为laji的property,值为yes-->
<laji>yes</laji>
<laji>no</laji>
</properties>
<!--结果:laji = no-->
Maven Profile
profile 是可选的配置,可以通过activation定义激活条件。profile既可以定义在settings.xml中,也可以定义在pom.xml中。
<profiles>
<profile>
<!--该profile的名字-->
<id>haha</id>
<!--该profile的激活条件-->
<activation>
<property>
<name>env</name>
<value>test</value>
</property>
</activation>
<!--下面是可选的配置内容-->
<properties>
<laji>ohno</laji>
</properties>
</profile>
</profiles>
以上profile表示:当env变量为test(也就是执行mvn test)时,laji属性的值会赋为ohno。这是一种按照环境变量进行激活的方式。
可以使用
mvn help:active-profiles
来查看目前激活的profile有哪些。
接下来举例说明各个激活方式。
按照环境变量激活
<profiles>
<profile>
<id>haha</id>
<activation>
<!--当ok环境变量为true时激活-->
<property>
<name>ok</name>
<value>true</value>
</property>
</activation>
<properties>
<laji>ohno</laji>
</properties>
</profile>
</profiles>
例如,使用mvn verify -Dok=true
命令时会激活上面的profile。
在settings.xml中指定激活的profile id
<activateProfiles>
<activateProfile>haha</activateProfile>
</activateProfiles>
上述settings会自动激活使用profile haha。
指定默认激活
<profiles>
<profile>
<id>haha</id>
<activation>
<!--默认情况下激活-->
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<laji>ohno</laji>
</properties>
</profile>
</profiles>
上面的profile会默认激活。
根据操作系统类型激活
<profiles>
<profile>
<id>haha</id>
<activation>
<!--在指定操作系统环境下激活-->
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
</activation>
<properties>
<laji>ohno</laji>
</properties>
</profile>
</profiles>
根据文件存在情况激活
<profiles>
<profile>
<id>haha</id>
<activation>
<!--存在a或者确实group时激活-->
<file>
<exists>a</exists>
<missing>target/generated-sources/axistools/wsdl2java/com/companyname/group</missing>
</file>
</activation>
<properties>
<laji>ohno</laji>
</properties>
</profile>
</profiles>
注意:如果有多个激活条件同时存在,则任意一个满足的时候profile就会激活。
运行命令时手动激活
运行mvn verify -P haha
时,激活haha profile。
运行mvn verify -P !haha
时,禁用haha profile(不管之前是否被激活)。
Maven多JDK配置案例
结合IDEA,实战展示如何给不同的模块配置不同的JDK(本次展示的时jdk1.8和jdk11两种),体现maven管理的灵活。
检查环境
命令行运行:确认已经配置了环境变量JAVA_HOME。这个是maven运行前必须的环境变量。因为maven本身也是java开发的工具,必须运行在java虚拟机上,所以需要先安装好java才能使用。这里使用的是jdk17版本。需要注意的是,由于javac向后兼容,因此jdk17中的javac可以构建jdk8(52)以及java17(55)版本的class文件。
有些时候遇到有些maven插件必须运行在指定版本以上的jre中,就要保证这里的JAVA_HOME给出的是符合要求的jdk。这里的JAVA_HOME版本是maven运行的版本。如果这里使用的jdk版本过低,后面就没救了。
对应在IDEA里面,要在Project Structure中设置对应版本的Project SDK,如下图所示。
配置pom.xml
新建两个modules: part1-8和part2-11,分别计划用jdk8和jdk11进行编写和编译。
在part1的pom中声明:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
在part2的pom中声明:
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
之后右键找到reload project,如图:
再看project Structure,发现两个module的Language Level被自动更新了。说明配置起作用了。
编译文件 查看结果
点击右侧maven面板中的compile命令,进行编译。编译完成后来到左侧导航,发现多了target目录。找到classes对应的编译出的class文件,打开可以看到版本。发现part2模块编译确实得到了55(对应java11的版本),而part1模块编译得到了52(对应java8)的class文件版本。
原因maven.compiler.source和maven.compiler.target两个属性的值会被maven-compiler-plugin读取使用,给每个模块构建对应要求的版本的class文件。
JDK版本错误系列问题
首先明确一点:编译时jdk版本和运行时jre版本独立。高版本的JDK可以编译和引用低版本模块,高版本JRE可以运行低版本的模块。一个模块编写的时候使用的jdk版本,和最终系统运行时使用的jdk版本并不一定要相同。例如模块可以使用jdk1.8编写,被系统的其他模块依赖,最终系统可以用jdk11运行,并不矛盾。但是要注意的是,最终运行时,在类加载阶段会检查加载进来的类的版本,如果jre是1.8版本的,但是依赖的一个类(要被加载的类)是55版本(对应java11),则会报Runtime Exception。
因此:
- 使用Maven等工具构建时,maven构建时环境变量JAVA_HOME指定的jdk版本>=所以使用到的模块的jdk版本,就可以构建成功。否则构建会报错,形如:类文件具有错误的版本 55.0, 应为 52.0
- 运行时,运行环境使用的jvm版本又要>=所有模块使用的jdk版本就行。否则,类加载时会抛出异常java.lang.UnsupportedClassVersionError
对应在IDEA里面,在Project Structure中指定的模块的Dependencies中的module sdk就是构建时使用的jdk(如图1);而Run configuration中指定的时运行时的jdk版本(如图2),两者相互独立。如果构建jdk版本低,会出现构建时类文件具有错误的版本 55.0, 应为 52.0这样的错误;如果运行时jdk版本低,则会出现java.lang.UnsupportedClassVersionError。
所以,遇到类文件错误版本的构建报错,则应该升级本地JAVA_HOME中的版本,或者对应更换IDEA的模块JDK;遇到java.lang.UnsupportedClassVersionError则应该提高运行使用的JRE版本,或者对应修改IDEA中Run Configuration的版本(通常这个IDEA会自动使用合适的,因此这类错误比较少见)。并不需要真正更改项目的依赖。
其他技巧
使用代理
mvn clean package -DproxySet=true -DproxyHost=localhost -DproxyPort=7890