Maven的使用与配置

343 阅读7分钟

Maven是一个项目构建,管理的工具。现代Java项目大部分都使用,但也有一些项目使用GradleMaven仍然是主流,学习使用maven十分有必要

Maven下载

打开apache mavenMaven下载地址

找到DownloadFiles模块,Mac/Linux系统下载.tar.gz后缀的压缩包,windows系统下载.zip后缀的压缩

下载后解压到目标目录即可

开始

  1. 解压后,新建环境变量名为M2_HOME,指向maven的解压目录(类似于配置JAVA_HOME),unix环境可以用tar -zxvf xx.tar.gz进行解压
  2. 添加环境变量使终端可访问mvnunix系统修改shell相关配置文件中,添加两条命令
M2_HOME=[将tar.gz解压后的目录]
PATH=$PATH:$M2_HOME/bin
export PATH

windows系统只需要通过GUI添加一条PATH环境变量即可:%M2_HOME%\bin

  1. 环境变量添加后,在命令行工具中即可使用mvn命令
  2. 输入mvn helper:system会到远程仓库中下载一些默认的jar包,成功后会输出一系列log

修改本地仓库目录和镜像源

默认情况下,maven会在当前用户目录下创建一个~/.m2文件夹,默认情况下会将所有相关依赖jar都下载刀此处,如果不想(尤其是windows用户C盘紧张),可以更改为其他目录。

打开maven目录下的config/setting.xml,添加几处配置

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
  <localRepository>~/xxxx/你的目标存放目录</localRepository>
  <pluginGroups>
  </pluginGroups>
  <proxies>
  </proxies>
  <servers>
  </servers>
  <mirrors>
    <mirror>
      <id>alimaven</id>
      <name>aliyun maven</name>
      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
      <mirrorOf>central</mirrorOf>        
    </mirror>
  </mirrors>
</settings>
  • 默认文件中,会有很多注释,这里我去掉精简了下,主要看localhostRepository标签,通过这个选项修改默认的仓库地址,目录不存在时会自动创建

  • 配置镜像地址,国内访问maven中心仓库的速度不见得乐观,可以改为阿里云的镜像地址,添加了mirror标签,并设置了国内镜像地址

如果不想在根配置文件配置源,也可以在某个项目的pom.xml(后面会说到)进行单独配置

    <repositories>
        <repository>
            <id>maven-ali</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </snapshots>
        </repository>
    </repositories>

仓库的分类

maven下载依赖时,会从远程中央仓库中(需要网络)下载,但是如果下载一次后,便会存在本地缓存仓库,后续解析依赖时,如果检索到本地仓库存在,便不会再去远程仓库中下载

所以可以分为两类

  1. 本地仓库,开发电脑上的文件夹,里面存放着下载好的jar
  2. 远程仓库,这里又分为几种情况,因为都需要网络进行操作,所以都可以归类为远程仓库
    • 中央仓库,maven核心仓库
    • 私服/镜像仓库,类似于aliyun这种就属于镜像仓库,中央仓库有的基本都有。私服:可能基于某种安全考虑,会在内网搭建一个内网私服,只能在当前的局域网内使用,外网无法访问

配置maven

maven很强大的一点在于,通过约定大于配置的规则,让很多原本需要手动管理的模块依赖(第三方库)变得不再繁琐,如A.jar中依赖了B.jar,如果不引入B.jar则无法使用A.jar的类,他们之间的关系就叫做依赖,而maven可以通过简单的配置进行项目依赖管理。

maven项目有它专属的约定项目结构,如下图(和idea开发工具没关系,这里只是用它打开项目,不会用到任何功能,项目目录结构符合即可)

pomProject Object Model的缩写,项目对象模型,maven把一个项目当做一个模型使用

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.mgl</groupId>
    <artifactId>testMvn</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>8</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>
</project>
  • modelVersion对于maven2/3版本只能写4.0.0,指定遵循哪个版本的项目描述符

  • groupId为组织id,一般用域名倒写

  • artifactId为项目模块名称

  • version为版本号,groupId+artifactId+version组成了唯一标识,通常称三个的组合为坐标gav

关于properties的配置项

  • project.build.sourceEncoding指定了编译时maven读取文件使用的编码,不设置的话默认时平台的编码,可以用mvn -v查看
  • java.version定义了一个变量方便使用,其他地方通过${标签名}引用
  • maven.compiler.source是配置java源代码使用的最高语言特性版本,如源代码使用了java8中的特性:lambda表达式,source却指定了java7,那么编译必定不通过,反之则可以
  • maven.compiler.target配置编译指定class字节码文件最低可运行的JVM版本,如指定了java8,则class文件会含有相关版本信息,当跑在虚拟机时如果不兼容,则会发生问题

实际上就是指定了javac工具的选项(maven也是java编写的),一般指定为当前JDK版本即可

如此,当前项目就是一个比较标准且精简的maven项目

Maven常用的生命周期,命令,插件

有了项目结构后,还要知道如何使用maven

  • mvn clean此命令是用于清理编译后的产出目录(compile命令的编译输出文件)
  • mvn compile会将所有src/main/java目录下的java文件编译成class字节码文件,并且输出到当前工作目录的target目录中(也就是clean命令会删除的目录)
  • mvn test-compile会将所有src/test/java目录下的java文件编译成class字节码文件
  • 这里值得一提的是,会按照一个固定周期执行,如果执行了test-compile那么,compile也会被执行,并且先于test-compile后面的以此类推。以一个例子来类比:把大象塞进冰箱分为四步,第一步举起大象,第二步打开冰箱门,第三步把大象塞进冰箱,第四步关上冰箱门。compile就是打开冰箱门这一步,而test-compile就是把大象塞进冰箱,执行这一步时必定要先打开冰箱门(也就是compile
  • mvn test会执行src/test/main中的测试程序,一般用于单元测试
  • mvn package打包动作,默认配置为jar,可以通过pom.xml进行修改,执行后会将当前项目的src/main/java的代码,依赖(决定于依赖的scope,后面会说)和资源进行编译打包成jar,对于test目录的内容不关心
  • mvn installjar包安装到本地的maven仓库,也就是上面修改过的本地缓存目录,后续在pom.xml中便能利用坐标进行引入依赖

mavn compile

首先在main/java/cn/mgl/Cat.java文件中,随意编写点代码

接着在当前工作目录执行mvn compile

将会看到以上的日志输出,其中maven有一些生命周期的默认插件,在执行mvn help:system时就已经安装好了,如果没有那么执行时也会将缺少的依赖进行下载

  • maven-resources-plugin:2.6插件会将src/main/resources目录中所有文件复制到输出目录下
  • maven-compiler-plugin:3.1会将src/main/java目录中所有java文件编译成字节码文件

观察项目目录

可以看到有一个target/classes文件夹,这里放的就是目前项目中被编译后的class文件,为了演示效果,特地建了一个空的application.yml,默认规则会将资源文件原封不动的复制到编译目录当中

在平时用maven架构开发项目时, classpath: 一般指的就是target/classes目录

mvn clean

这个命令比较简单,执行后就会将target文件夹整个删除

mvn test-compile

作用与mvn-compile类似,区别只是作用的目录不一样,比较简单不演示

mvn test(延伸出安装依赖)

这个命令作用在于运行单元测试代码,所谓的单元测试简单的理解为就是对项目中每个java编写的方法进行运行测试检验是否符合预期,这个行为可以通过一些主流测试框架来完成,如junit

回到pom.xml中,使用dependencies标签进行依赖声明

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    省略...
    <properties>
      省略...
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

dependencies是关键,它用于声明当前项目中有哪些依赖项,在此处配置后,即可通过maven将所有junit依赖关系中的依赖包(包括其自身)下载到本地中使用

子标签dependency可以有多个,其中的子标签写法其实就是上述讲过的坐标,所有类库的依赖信息都可以在中央仓库中寻找,地址为mvnrepository.com/,如想引入mybatis在网站中搜索mybatis,找到对应的maven坐标复制到当前项目中的pom.xml

需要关注一下其中的scope标签,这个标签的作用置顶依赖的作用范围,也就是在maven构建的哪些阶段生效(编译,测试,打包,安装,部署)

  • compile不写时默认就是这个值,作用是在每一个阶段都需要该依赖
  • test只有在test阶段才需要的依赖,其他阶段不会被编译等行为
  • provider只有编译阶段依赖有效。比如编写一个web项目,最终会打包成一个war包丢给tomcat服务器,那么服务器本身有servletjar包,只需要保证编译不报错,这种情况适合使用这个scope

接着在src/test/java目录中的java文件编写如下代码

package cn.mgl;

import org.junit.Assert;
import org.junit.Test;

public class TestCat {
    @Test
    public void test001() {
        int i = 100;
        Assert.assertEquals(100, i);
    }
}

接着运行mvn test

可以看到maven-surefire-plugin:2.12.4的作用就是帮助项目执行测试代码,并且将编写的测试程序执行后输出测试结果报告

mvn package

没有在pom.xml中指定packing标签的值时,默认就是jar,所以执行后,会将src/main编译后的产物打包成一个jar包,其实就是一个压缩包,将后缀名改成rar就可以用解压软件解压

其中对应的插件为maven-jar-plugin:2.4,在target目录下会有一个产出文件,命名规则是根据项目的artifactId+version生成的

mvn install

执行命令后,会将打包后的jar包安装的本地依赖目录当中

目录是根据groupId进行生成的,这个试例用的groupIdcn.mgl那么就会生成cn/mgl目录,在本地的项目中,想用这个jar包时只需要在pom.xml添加坐标即可使用

到这里,基本的maven命令都有了个初步认识,上面所有列出的插件都是可以进行配置化的,以及可以自己添加一些额外的插件供maven的特定阶段去使用。如一开始编写pom.xml中,有一个配置sourcetaget的配置项,可以通过在对应的compiler中进行配置,如下方示例

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

像其他的默认插件如果没有定制化需求,可以不编写

如何将src/main中的非java文件也编译到target当中

在有些情况下,可能确实是需要这种处理,如使用mybatis框架时,有一种mapper.xmlmapper[interface]需要放到同一个目录下的要求,此时就必须通过resources标签才进行资源的配置化

为了演示效果,特地添加了一个aa.js的空文件到cn.mgl包下,此时执行mvn compile后,target目录下并不会有这个文件存在

pom.xml添加配置

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.js</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>*</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

这里添加了两个resource标签,原因是如果手动配置后,默认的规则会失效,那么resources里的文件就不会被替换,所以需要将其补到规则当中

  • directory配置资源的目录
  • <include>**/*.js</include>的作用是在directory目录下的任意结构下的js文件都复制到编译后到产出目录当中

执行mvn compile后,资源就都复制成功了

通过IDEA使用Maven

上面的示例中仅仅是用idea打开了项目,但maven命令都是通过命令行输入执行的,目的是想描述mavenidea无强关联关系,使用其他工具或者不使用工具都是可以使用maven构建项目的。

如果用idea创建的maven项目,有GUI供方便使用(idea新旧版都一样)

  • Lifecycle表示生命周期,双击某一项会执行对应的命令,比如双击途中的Lifecycle:clean,实际上等价于在当前工作目录执行mvn clean
  • Plugin表示maven构建过程用到的插件依赖
  • Dependencies表示当前项目中所有依赖项及传递性依赖

点击黄框中的按钮都可以进行加载依赖项

maven的父级依赖

pom.xml中有一个可以标签的标签为parent,它的作用类似于Java中的继承,方便配置的复用,常用的框架Springboot中就有这种使用,在使用一些内置依赖时不需要写版本后,maven也能找到对应的目标,原因就是父pom中有相关配置

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> 
    </parent>

这是一个基于SpringBoot搭建的应用项目,pom文件的开头就写了一个parent标签,说明项目是一个父子结构模式

  • relativePath是指定去哪个地方找这一份需要继承的pom描述,当指为空(空标签)时,会去仓库找对应的pom。当不写的时候,默认就是../pom.xml,像多模块的项目,最外层定义一个pom.xml为父pom,与这个文件同级别创建子模块,子模块又有各自的pom并且继承了父pom,来达到可复用,可管理的作用

idea中按住ctrl点击artifactId可以跳转到父pom(自己去仓库找也行)

<!-- 可以看到父pom又继承了一个dependencies -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.5.5</version>
</parent>

此时会发现父pom也有父pom,再递进查看爷pom文件内容

 <properties>
    <activemq.version>5.16.3</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.91</appengine-sdk.version>
    <artemis.version>2.17.0</artemis.version>
    <aspectj.version>1.9.7</aspectj.version>
    <assertj.version>3.19.0</assertj.version>
    <atomikos.version>4.0.6</atomikos.version>
    <awaitility.version>4.0.3</awaitility.version>
   <!-- 省略 -->
<properties>
  

主要看这个文件的核心定义,这里爷爷pom定义了一堆的properties,对一些仓用的依赖进行了版本定义

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-amqp</artifactId>
        <version>${activemq.version}</version>
      </dependency>
      <dependency>
        <groupId>org.apache.activemq</groupId>
        <artifactId>activemq-blueprint</artifactId>
        <version>${activemq.version}</version>
      </dependency>
         <!-- 省略 -->
  </dependencyManagement>

dependencyManagement标签,顾名思义就是用来管理依赖的,在这里定义的依赖默认不会被下载,只有当真正的子pom中引入时,才会下载对应包(某种意义的按需引入)

Springboot的用法就是一个很好的实践,在有需要进行多模块依赖管理时,可以参考这种设计

maven解决依赖冲突规则

在某些情况下,一些不同的依赖他们自身或传递依赖引入了不同版本的同一个包。例如项目引入了a和b依赖,a依赖引入了d包的1.0版本,而b依赖引入了d包的2.0,此时就会发生所谓的依赖冲突问题

maven有它自己的解决机制,首先会去检测当前pom的传递性依赖树,基准是不允许出现同名但不同版本的jar包,否则就会根据特定规则去解决保证只留存一个

  • 当相同依赖(忽略版本)在依赖树中为级别时,在pom.xml声明越前的优先级越高

  • 当相同依赖(忽略版本)在依赖树中不同级别时,距离根节点越近的优先级越高

这个图是通过idea的一个插件【maven dependency helper】提供,当前项目中含有mybatis-spring-boot-starter两个同名但不同版本的依赖包,跟据规则,层级较浅的优先级高一点,所以maven会抛弃调1.3.2jar包,采用2.1.3

不用插件的话,通过ideamaven工具栏也可以看出低版本的依赖被置灰了,冲突中胜出的会高亮

以及在pom文件右键选择Diagrams->Show Dependencies...也可以查看依赖结构树

上面说的是maven的规则,有有时候这种规则最终存留的jar包版本可能并不是预期想要的,x.jar在依赖树中有1.0,2.0,3.0三个版本,若按照maven解决冲突的机制,存在下来的可能是1.0或者2.0,实际需要的是3.0,此时有以下几种方式可以解决

  1. pom.xml中声明目标依赖版本,此处优先级最高,按照maven规则会淘汰掉其他传递性依赖中的版本
  2. 在确定某个插件不需要其传递性依赖时,通过exclusions标签配置需要排除的jar包信息,已上面演示的springboot项目为例,在引入分页插件依赖时,手动的将mybatis-spring-boot-starter依赖去掉
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

再次查看依赖树,分页插件当中已经没有mybatis-spring-boot-starter:1.3.2的相关依赖了

可以通过命令mvn dependency:tree查看项目当前的依赖树,不过输出的内容是经过maven解决了依赖冲突之后的树结构

使用maven第三方插件

Flyway是一个数据库的版本插件

官方使用地址:flywaydb.org/documentati…

  1. 首先在项目pom中添加一个build下的plugin
        <plugins>
            <plugin>
                <groupId>org.flywaydb</groupId>
                <artifactId>flyway-maven-plugin</artifactId>
                <version>5.2.4</version>
                <configuration>
                    <url>jdbc:mysql://localhost:3306/【你的数据库名称】</url>
                    <user>【你的用户名】</user>
                    <password>【你的密码】</password>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.26</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>

在添加插件时,可以给插件添加一些依赖,在plugin标签里用dependency,配置一些插件属性可以通过configuration标签,具体可以配置哪些需要查看插件的文档,这里设置一些链接信息,导入maven依赖,在idea的插件视图中应该能看到和flyway相关的信息

使用方式有两种,要么借助idea的视图双击某一个节点,即可执行对应命令,第二种使用命令行方式,比如想执行migrate,那么在当前工作目录输入mvn flyway:migrate

使用flyway需要在resources目录下创建db/migration,这个目录放固定格式的sql文件,插件执行时会扫描这个资源目录,创建一个sql文件,文件名格式有要求:V1__Create__User.sql,要以V[版本号]开头,后面的描述间隔都是以双下划线分割

执行插件动作前请确定数据库可以访问,执行mvn flyway:migrate

查看数据库即可看到表已经创建成功

maven的内容还有很多,想要更深入了解都可以在官网文档或者第三方maven插件中寻找相关使用说明

完:)