Java 项目的构建工具 Maven

发布于:2024-06-28 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、Maven 简介

Maven 是 Apache 旗下的一个开源项目,是一款管理和构建 Java 项目的工具。他有 3 大核心功能:

  1. 依赖管理:方便快捷的管理项目依赖的资源(jar包),避免版本冲突问题(直接在 pom.xml 文件中导入依赖)
  2. 统一项目结构:提供标准、统一的项目结构(两个标准目录:main、test)
  3. 项目构建:maven提供了标准的、跨平台(Linux、Windows、MacOS) 的自动化项目构建方式(清理、编译、测试、打包、发布等插件)

Maven 中有三种模型,其对应关系如下:

Maven 中仓库如图:

  • 本地仓库:自己计算机上的一个目录(用来存储jar包)
  • 中央仓库:由Maven团队维护的全球唯一的。仓库地址:https://repo1.maven.org/maven2/
  • 远程仓库(私服):一般由公司团队搭建的私有仓库

一般来说,Maven 仓库查找 jar 包的顺序是:

当项目中使用坐标引入对应依赖 jar 包后,首先会查找本地仓库中是否有对应的jar包,如果有,则在项目直接引用,如果没有,则去中央仓库中下载对应的jar包到本地仓库。如果还可以搭建远程仓库(私服),将来 jar 包的查找顺序则变为: 本地仓库 --> 远程仓库–> 中央仓库。

二、Maven 安装配置

1、Maven 下载安装

Maven 下载地址:https://maven.apache.org/download.cgi

Maven 是绿色软件,下载压缩包之后,直接解压即可:

2、Maven 配置

进入到conf目录下修改settings.xml配置文件:
(1)配置本地仓库地址
在55行添加如下信息,标签中的路径为自定义的本地仓库路径。

  <localRepository>D:\maven3.8.5\apache-maven-3.8.5-bin\apache-maven-3.8.5\mvn_repo</localRepository>

(2)配置阿里镜像源
由于中央仓库在国外,所以下载 jar 包速度可能比较慢,这里我们使用阿里公司提供了一个远程仓库镜像。需要在160行添加如下配置

<mirror>  
    <id>alimaven</id>  
    <name>aliyun maven</name>  
    <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    <mirrorOf>central</mirrorOf>          
</mirror>

(3)配置环境变量

  1. 在系统变量处新建一个变量 MAVEN_HOME,值为 maven 的解压安装目录
  2. PATH环境变量的值,设置为:%MAVEN_HOME%\bin

验证:mvn -v

三、IDEA 集成 Maven

(1)创建 Maven 项目
可以先创建一个空项目,然后在空项目中导入 Maven 模块:

(2)pom 文件详解

<?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">
    <!--  pom 模型版本  -->
    <modelVersion>4.0.0</modelVersion>

    <!--  当前项目坐标  -->
    <groupId>com.lee</groupId>
    <artifactId>maven_projectA</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--  定义项目的属性  -->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <!-- 打包方式(默认为 jar) -->
    <packaging>jar</packaging>

</project>

关于坐标:

  • Maven中的坐标是资源的唯一标识 , 通过该坐标可以唯一定位资源位置(Jar 包位置)
  • 使用坐标来定义项目或引入项目中需要的依赖,我们的项目如果被其他的项目依赖时,也是需要坐标来引入的。

Maven 坐标主要由一下3个部分组成:

  • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.itheima)
  • artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
  • version:定义当前项目版本号

(3)导入 Maven 项目
导入 Maven 项目只需导入 pom.xml 文件即可:

四、Maven 依赖管理

1、依赖配置

(1)什么是依赖?

指当前项目运行所需要的 Jar 包,一个项目中可以引入多个依赖。实际上这里说的依赖就是我们在 maven 工程的 pom.xml 文件中,引入的 Jar 包坐标

(2)如何添加依赖?

在 Maven 中添加依赖,关键是拿到依赖的坐标,然后再 pom.xml 中的 <dependencies><dependency>依赖坐标</dependency></dependencies> 标签中添加对应的依赖坐标即可,我们可以通过以下方式拿到依赖坐标的方式:

  1. 到mvn的中央仓库(https://mvnrepository.com/)中搜索依赖,得到对应依赖坐标。
  2. 利用 IDEA 工具搜索依赖(快捷键 Alt + Insert)

2、依赖传递

(1)什么是依赖传递?

在 Maven 中依赖具有传递性,早期没有 Maven 的时候,如果我们想引入依赖,但是引入的依赖又间接的依赖了其他的 Jar 包,这个时候我们就需要把所有依赖的 Jar 包都下载下来。但是有了 Maven 之后,我们只需要在 pom.xml 中添加直接依赖的坐标即可,由于maven的依赖具有传递性,所以会自动把所依赖的其他jar包也一起导入。

我们可以将依赖细分为:

  1. 直接依赖:在当前项目中通过依赖配置建立的依赖关系
  2. 间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源

如下图:


  • projectA依赖了 projectB。对于 projectA 来说,projectB 就是直接依赖。
  • 而 projectB 依赖了 projectC 及其他 jar 包。 那么此时,在 projectA 中也会将 projectC 的依赖传递下来。对于 projectA 来说,projectC 就是间接依赖。

(2)Maven 支持排除依赖

例如我们的 maven_projectA 依赖的 maven_projectB 中还依赖了 junit 框架,但是我们并不想依赖将 junit 依赖到 maven_projectA 中,此时我们就可以使用如下方式排除 junit 依赖:

<dependency>
    <groupId>com.lee</groupId>
    <artifactId>maven_projectB</artifactId>
    <version>1.0-SNAPSHOT</version>
   
    <!--排除依赖, 主动断开依赖的资源-->
    <exclusions>
    	<exclusion>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3、依赖范围

依赖范围也就是导入依赖的 Jar 包后,Jar 包的使用范围,可以通过<scope>标签设置其作用范围。如:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.1</version>
    <scope>test</scope>
</dependency>

score 标签取值范围:

scope 编译(main文件夹范围内) 测试(test 文件夹范围内) 打包/运行(package 指令范围内) 范例
compile(默认) Y Y Y log4j
test - Y - junit
provided Y Y - servlet-api
runtime - Y Y jdbc 驱动

4、生命周期

Maven 的生命周期就是为了对所有的构建过程进行抽象和统一。描述了一次项目构建,经历哪些阶段。

Maven 对项目构建的生命周期划分为相互独立的 3 套:

  • clean:清理工作。
  • default:核心工作。如:编译、测试、打包、安装、部署等。
  • site:生成报告、发布站点等。


其中我们主要关注以下几个:

  • clean:移除上一次构建生成的文件
  • compile:编译项目源代码
  • test:使用合适的单元测试框架运行测试(junit)
  • package:将编译后的文件打包,如:jar、war等
  • install:安装项目到本地仓库

注意:在同一套生命周期中,我们在执行后面的生命周期时,前面的生命周期都会执行。

这意味着当运行package生命周期时 clean 不会运行,compile会运行。 因为compile与 package 属于同一套生命周期,而 clean 与 package 不属于同一套生命周期。

Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际工作。在Maven的设计中,实际任务都交由插件来完成的:

在这里插入图片描述

五、Maven 高级特性

1、分模块设计与开发

所谓分模块设计,顾名思义指的就是我们在设计一个 Java 项目的时候,将一个 Java 项目拆分成多个模块进行开发。在分模块之前,我们的项目都整合在一个模块中,随着项目的业务扩张,单模块在管理和维护上都比较困难。并且项目中的通用组件难以复用。

而分模块设计就可以很好地解决上述问题,分模块可以将项目按照功能/结构拆分成若干个子模块,方便项目的管理维护、拓展,也方便模块键的相互调用、资源共享。

比如一个电商项目包括订单管理、商品管理、物流管理……以及一些通用组件等。在开发这个项目的时候我们就可以采用分模块的思想,将项目划分为订单模块、商品模块、物流模块、通用组件模块等。如果当某个模块,比如订单模块需要用通用组件,此时直接在订单模块当中引入通用组件模块的坐标就可以了。此外,由于对模块进行了拆分,可以更方便高效的进行项目管理。

PS:分模块开发需要先针对模块功能进行设计,再进行编码。不会先将工程开发完毕,然后进行拆分。

2、Maven 继承

当我们对项目分模块开发之后,往往会遇到这样的问题:多个模块中引入了相同的依赖,比如 lombok。在实际开发中重复的依赖可能会很多很多。如果每一个 Maven 模块里面,我们都来单独的配置一次,这个过程是相当繁琐的。而下面要介绍的 Maven 中的继承恰好可以解决这个问题:

Maven 中的继承与 Java 中的继承相似,子工程可以继承父工程中的配置信息。可以简化依赖配置、统一管理依赖。
具体实现是通过在子工程中添加下面的 <parent></parent>标签实现的:

<parent>
    <groupId>父工程组织Id</groupId>
    <artifactId>父工程项目Id</artifactId>
    <version>父工程版本号</version>
    <relativePath>父工程相对路径</relativePath>
</parent>

比如 parent 工程作为 A、B、C 工程的父工程,引入了 lombok 依赖,同样可以看到子工程中继承的父工程依赖:

这里需要注意的是父工程打包方式需要配置为 pom

  1. jar:普通模块打包(默认打包方式),springboot项目基本都是jar包(内嵌tomcat运行)
  2. war:普通web程序打包,需要部署在外部的tomcat服务器中运行
  3. pom:父工程或聚合工程,该模块不写代码,仅进行依赖管理
  1. 在子工程中,配置了继承关系之后,坐标中的 groupId 是可以省略的,因为会自动继承父工程的 。
  2. relativePath 指定父工程的pom文件的相对位置(如果不指定(不写<relativePath>标签),默认路径为 ../pom.xml;如果显示不指定(<relativePath/>)将从本地仓库/远程仓库查找该工程)。

PS:Maven 和 Java 一样都不支持多继承,但都支持多重继承。

3、Maven 版本管理

如果项目中各个模块中都公共的这部分依赖,我们可以直接定义在父工程中,从而简化子工程的配置。 然而在项目开发中,还有一部分依赖,是一部分模块独有的。对于一个项目来讲依赖的版本一般是统一的,假如我们的项目要升级,对应的依赖的版本也要升级,共有的依赖直接在父工程中修改就好了,但是对于一些模块独有的依赖修改起来就相当的困难了。

如果项目拆分的模块比较多,每一次更换版本,我们都得找到这个项目中的每一个模块,一个一个的更改。 不仅过程很繁琐,而且很可能一不小心就遗漏掉一个模块。

Maven 也是关注到这一点,很贴心的给我们提供了版本管理的功能:

在maven中,可以在父工程的pom文件中通过 <dependencyManagement></dependencyManagement> 来统一管理依赖版本:
父工程:

<!--统一管理依赖版本-->
<dependencyManagement>
    <dependencies>
        <!--JWT令牌-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
</dependencyManagement>

子工程:

<dependencies>
    <!--JWT令牌-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>
  1. 在父工程中所配置的 <dependencyManagement> 只能统一管理依赖版本,并不会将这个依赖直接引入进来。 这点和 <dependencies> 是不同的, <dependencies> 标签会直接将依赖引入。
  2. 子工程要使用这个依赖,还是需要引入的,只是此时就无需指定 <version> 版本号了,父工程统一管理。变更依赖版本,只需在父工程中统一变更。

这里需要注意一点,Maven 中还支持自定义属性标签功能,可以将父工程中的版本管理进一步解耦:

<!-- 自定义属性 -->
<properties>
    <lombok.version>1.18.24</lombok.version>
    <jjwt.version>0.9.1</jjwt.version>
</properties>

<!-- 引用属性 -->
<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
    </dependency>
</dependencies>

<!--统一管理依赖版本-->
<dependencyManagement>
    <dependencies>
        <!--JWT令牌-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

4、Maven 聚合

分模块设计与开发之后啊,我们的项目被拆分为多个模块,而模块之间的关系,可能错综复杂。子模块之间也可能相互依赖,那么我们在对某个模块进行打包时,就需要先将依赖的模块安装到 Maven 仓库在,再针对模块采取打包操作。

试想一下,如果开发一个大型项目,拆分的模块很多,模块之间的依赖关系错综复杂,那此时要进行项目的打包、安装操作,是非常繁琐的。

Maven 的聚合就是来解决这个问题的,通过 Maven 的聚合就可以轻松实现项目的一键构建(清理、编译、测试、打包、安装等)

Maven 中的聚合就是将多个模块组织成一个整体,同时进行项目构建。其中的聚合工程是一个不具有业务功能的“空”工程(有且仅有一个pom文件)一般来说,继承关系中的父工程与聚合关系中的聚合工程是同一个。使用 Maven 的聚合功能我们就可以实现快速构建项目,即无需根据依赖关系手动构建,直接在聚合工程上构建即可。

    <!--聚合其他模块-->
    <modules>
        <module>maven-projectA</module>
        <module>maven-projectB</module>
        <module>maven-projectC</module>
    </modules>

执行打包 package 命令:

小结:

  1. 聚合用于快速构建项目
  2. 继承用于简化依赖配置、统一管理依赖

5、私服

通常情况下,我们所拆分的模块是可以在同一个公司各个项目组之间进行资源共享的,如何实现一个公司中模块资源共享呢?这就需要使用到 Maven 中的私服了。那么你可能会问了,为什么不使用中央仓库呢?直接把共享模块的 Jar 包资源上传到 Maven 中央仓库,通过引入的依赖坐标,先到本地仓库查找,再到中央仓库查找。其实这个想法是行不通的,主要原因有如下两点:

  1. 中央仓库全球只有一个,不是什么人都可以往中央仓库中来上传jar包的,我们是没有权限操作的
  2. 共享的模块可能涉及到公司敏感信息,不适合放到中央仓库

所以最好的解决方案依然是使用 Maven 私服:

有了私服之后,各个团队就可以直接来连接私服了,连接上私服之后,他就可以把 jar 包直接上传到私服当中,如果需要某些共享模块资源,直接引入坐标,从私服当中将对应的 jar 包下载到自己的本地仓库。如果需要第三方提供的依赖,获取依赖的顺序变为:本地仓库–》私服–》中央仓库。

使用私服完成资源上传和下载:


第一步配置:在maven的配置文件中配置访问私服的用户名、密码。(在maven安装目录下的conf/settings.xml中的servers中配置)

<server>
    <id>maven-releases</id>
    <username>admin</username>
    <password>admin</password>
</server>
    
<server>
    <id>maven-snapshots</id>
    <username>admin</username>
    <password>admin</password>
</server>

第二步配置:设置私服依赖下载的仓库组地址(在maven安装目录下的conf/settings.xml中的mirrors、profiles中配置)

<mirror>
    <id>maven-public</id>
    <mirrorOf>*</mirrorOf>
    <url>http://xxx:xxx/repository/maven-public/</url>
</mirror>
<profile>
    <id>allow-snapshots</id>
        <activation>
        	<activeByDefault>true</activeByDefault>
        </activation>
    <repositories>
        <repository>
            <id>maven-public</id>
            <url>http://xxx:xxx/repository/maven-public/</url>
            <releases>
            	<enabled>true</enabled>
            </releases>
            <snapshots>
            	<enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
</profile>

第三步配置:在项目的pom.xml文件中配置上传资源的位置(url地址)。

<distributionManagement>
    <!-- release版本的发布地址 -->
    <repository>
        <id>maven-releases</id>
        <url>http://xxx:xxx/repository/maven-releases/</url>
    </repository>

    <!-- snapshot版本的发布地址 -->
    <snapshotRepository>
        <id>maven-snapshots</id>
        <url>http://xxx:xxx/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>