第一章 Maven概述

一、为什么要学习Maven?

1、Maven 作为依赖管理工具

①jar 包的规模

随着我们使用越来越多的框架,或者框架封装程度越来越高,项目中使用的jar包也越来越多。项目中,一个模块里面用到上百个jar包是非常正常的。
比如下面的例子,我们只用到 SpringBoot、SpringCloud 框架中的三个功能:

  • Nacos 服务注册发现
  • Web 框架环境
  • 图模板技术 Thymeleaf

最终却导入了 106 个 jar 包:

org.springframework.security:spring-security-rsa:jar:1.0.9.RELEASE:compile
com.netflix.ribbon: ribbon:jar:2.3.0:compile
org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.3.6.RELEASE:compile
commons-configuration:commons-configuration:jar:1.8:compile
org.apache.logging.log4j:log4j-api:jar:2.13.3:compile
org.springframework:spring-beans:jar:5.2.11.RELEASE:compile
org.springframework.cloud:spring-cloud-starter-netflix-ribbon:jar:2.2.6.RELEASE:compile
org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.39:compile
com.alibaba.cloud:spring-cloud-alibaba-commons:jar:2.2.6.RELEASE:compile
org.bouncycastle:bcprov-jdk15on:jar:1.64:compile
org.springframework.security:spring-security-crypto:jar:5.3.5.RELEASE:compile
org.apache.httpcomponents:httpasyncclient:jar:4.1.4:compile
com.google.j2objc:j2objc-annotations:jar:1.3:compile
com.fasterxml.jackson.core:jackson-databind:jar:2.11.3:compile
io.reactivex:rxjava:jar:1.3.8:compile
ch.qos.logback:logback-classic:jar:1.2.3:compile
org.springframework:spring-web:jar:5.2.11.RELEASE:compile
io.reactivex:rxnetty-servo:jar:0.4.9:runtime
org.springframework:spring-core:jar:5.2.11.RELEASE:compile
io.github.openfeign.form:feign-form-spring:jar:3.8.0:compile
io.github.openfeign.form:feign-form:jar:3.8.0:compile
com.netflix.ribbon:ribbon-loadbalancer:jar:2.3.0:compile
org.apache.httpcomponents:httpcore:jar:4.4.13:compile
org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile
org.slf4j:jul-to-slf4j:jar:1.7.30:compile
com.atguigu.demo:demo09-base-entity:jar:1.0-SNAPSHOT:compile
org.yaml:snakeyaml:jar:1.26:compile
org.springframework.boot:spring-boot-starter-logging:jar:2.3.6.RELEASE:compile
io.reactivex:rxnetty-contexts:jar:0.4.9:runtime
org.apache.httpcomponents:httpclient:jar:4.5.13:compile
io.github.openfeign:feign-core:jar:10.10.1:compile
org.springframework.boot:spring-boot-starter-aop:jar:2.3.6.RELEASE:compile
org.hdrhistogram:HdrHistogram:jar:2.1.9:compile
org.springframework:spring-context:jar:5.2.11.RELEASE:compile
commons-lang:commons-lang:jar:2.6:compile
io.prometheus:simpleclient:jar:0.5.0:compile
ch.qos.logback:logback-core:jar:1.2.3:compile
org.springframework:spring-webmvc:jar:5.2.11.RELEASE:compile
com.sun.jersey:jersey-core:jar:1.19.1:runtime
javax.ws.rs:jsr311-api:jar:1.1.1:runtime
javax.inject:javax.inject:jar:1:runtime
org.springframework.cloud:spring-cloud-openfeign-core:jar:2.2.6.RELEASE:compile
com.netflix.ribbon:ribbon-core:jar:2.3.0:compile
com.netflix.hystrix:hystrix-core:jar:1.5.18:compile
com.netflix.ribbon:ribbon-transport:jar:2.3.0:runtime
org.springframework.boot:spring-boot-starter-json:jar:2.3.6.RELEASE:compile
org.springframework.cloud:spring-cloud-starter-openfeign:jar:2.2.6.RELEASE:compile
com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.11.3:compile
com.sun.jersey.contribs:jersey-apache-client4:jar:1.19.1:runtime
io.github.openfeign:feign-hystrix:jar:10.10.1:compile
io.github.openfeign:feign-slf4j:jar:10.10.1:compile
com.alibaba.nacos:nacos-client:jar:1.4.2:compile
org.apache.httpcomponents:httpcore-nio:jar:4.4.13:compile
com.sun.jersey:jersey-client:jar:1.19.1:runtime
org.springframework.cloud:spring-cloud-context:jar:2.2.6.RELEASE:compile
org.glassfish:jakarta.el:jar:3.0.3:compile
org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.3:compile
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.11.3:compile
org.springframework.cloud:spring-cloud-commons:jar:2.2.6.RELEASE:compile
org.aspectj:aspectjweaver:jar:1.9.6:compile
com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:jar:2.2.6.RELEASE:compile
com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
com.alibaba.spring:spring-context-support:jar:1.0.10:compile
jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
org.bouncycastle:bcpkix-jdk15on:jar:1.64:compile
com.netflix.netflix-commons:netflix-commons-util:jar:0.3.0:runtime
com.fasterxml.jackson.core:jackson-annotations:jar:2.11.3:compile
com.google.guava:guava:jar:29.0-jre:compile
com.google.guava:failureaccess:jar:1.0.1:compile
org.springframework.boot:spring-boot:jar:2.3.6.RELEASE:compile
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.11.3:compile
com.atguigu.demo:demo08-base-api:jar:1.0-SNAPSHOT:compile
org.springframework.cloud:spring-cloud-starter-netflix-archaius:jar:2.2.6.RELEASE:compile
org.springframework.boot:spring-boot-autoconfigure:jar:2.3.6.RELEASE:compile
org.slf4j:slf4j-api:jar:1.7.30:compile
commons-io:commons-io:jar:2.7:compile
org.springframework.cloud:spring-cloud-starter:jar:2.2.6.RELEASE:compile
org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.39:compile
io.reactivex:rxnetty:jar:0.4.9:runtime
com.fasterxml.jackson.core:jackson-core:jar:2.11.3:compile
com.google.code.findbugs:jsr305:jar:3.0.2:compile
com.netflix.archaius:archaius-core:jar:0.7.6:compile
org.springframework.boot:spring-boot-starter-web:jar:2.3.6.RELEASE:compile
commons-codec:commons-codec:jar:1.14:compile
com.netflix.servo:servo-core:jar:0.12.21:runtime
com.google.errorprone:error_prone_annotations:jar:2.3.4:compile
org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
com.atguigu.demo:demo10-base-util:jar:1.0-SNAPSHOT:compile
org.checkerframework:checker-qual:jar:2.11.1:compile
org.thymeleaf:thymeleaf-spring5:jar:3.0.11.RELEASE:compile
commons-fileupload:commons-fileupload:jar:1.4:compile
com.netflix.ribbon:ribbon-httpclient:jar:2.3.0:compile
com.netflix.netflix-commons:netflix-statistics:jar:0.1.1:runtime
org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
org.springframework:spring-jcl:jar:5.2.11.RELEASE:compile
com.alibaba.nacos:nacos-common:jar:1.4.2:compile
commons-collections:commons-collections:jar:3.2.2:runtime
javax.persistence:persistence-api:jar:1.0:compile
com.alibaba.nacos:nacos-api:jar:1.4.2:compile
org.thymeleaf:thymeleaf:jar:3.0.11.RELEASE:compile
org.springframework:spring-aop:jar:5.2.11.RELEASE:compile
org.springframework.boot:spring-boot-starter:jar:2.3.6.RELEASE:compile
org.springframework.boot:spring-boot-starter-tomcat:jar:2.3.6.RELEASE:compile
org.springframework.cloud:spring-cloud-netflix-ribbon:jar:2.2.6.RELEASE:compile
org.springframework:spring-expression:jar:5.2.11.RELEASE:compile
org.springframework.cloud:spring-cloud-netflix-archaius:jar:2.2.6.RELEASE:compile

而如果使用 Maven 来引入这些 jar 包只需要配置三个『依赖』:

  1. <!-- Nacos 服务注册发现启动器 -->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  5. </dependency>
  6. <!-- web启动器依赖 -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-web</artifactId>
  10. </dependency>
  11. <!-- 视图模板技术 thymeleaf -->
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  15. </dependency>

②jar 包的来源

  • 这个jar包所属技术的官网。官网通常是英文界面,网站的结构又不尽相同,甚至找到下载链接还发现需要通过特殊的工具下载。
  • 第三方网站提供下载。问题是不规范,在使用过程中会出现各种问题。
    • jar包的名称
    • jar包的版本
    • jar包内的具体细节
  • 而使用 Maven 后,依赖对应的 jar 包能够自动下载,方便、快捷又规范。

    ③jar 包之间的依赖关系

    框架中使用的 jar 包,不仅数量庞大,而且彼此之间存在错综复杂的依赖关系。依赖关系的复杂程度,已经上升到了完全不能靠人力手动解决的程度。另外,jar 包之间有可能产生冲突。进一步增加了我们在 jar 包使用过程中的难度。
    下面是前面例子中 jar 包之间的依赖关系:
    Maven入门 - 图1
    而实际上 jar 包之间的依赖关系是普遍存在的,如果要由程序员手动梳理无疑会增加极高的学习成本,而这些工作又对实现业务功能毫无帮助。
    而使用 Maven 则几乎不需要管理这些关系,极个别的地方调整一下即可,极大的减轻了我们的工作量。

    2、Maven 作为构建管理工具

    ①你没有注意过的构建

    你可以不使用 Maven,但是构建必须要做。当我们使用 IDEA 进行开发时,构建是 IDEA 替我们做的。

    ②脱离 IDE 环境仍需构建

    Maven入门 - 图2

    3、结论

  • 管理规模庞大的 jar 包,需要专门工具。

  • 脱离 IDE 环境执行构建操作,需要专门工具。

    二、什么是 Maven?

    Maven 是 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建依赖管理支持的工具。
    image.png

    1、构建

    Java 项目开发过程中,构建指的是使用『原材料生产产品』的过程。

  • 原材料

    • Java 源代码
    • 基于 HTML 的 Thymeleaf 文件
    • 图片
    • 配置文件
  • 产品
    • 一个可以在服务器上运行的项目

构建过程包含的主要的环节:

  • 清理:删除上一次构建的结果,为下一次构建做好准备
  • 编译:Java 源程序编译成 *.class 字节码文件
  • 测试:运行提前准备好的测试程序
  • 报告:针对刚才测试的结果生成一个全面的信息
  • 打包
    • Java工程:jar包
    • Web工程:war包
  • 安装:把一个 Maven 工程经过打包操作生成的 jar 包或 war 包存入 Maven 仓库
  • 部署

    • 部署 jar 包:把一个 jar 包部署到 Nexus 私服服务器上
    • 部署 war 包:借助相关 Maven 插件(例如 cargo),将 war 包部署到 Tomcat 服务器上

      2、依赖

      如果 A 工程里面用到了 B 工程的类、接口、配置文件等等这样的资源,那么我们就可以说 A 依赖 B。例如:
  • junit-4.12 依赖 hamcrest-core-1.3

  • thymeleaf-3.0.12.RELEASE 依赖 ognl-3.1.26
    • ognl-3.1.26 依赖 javassist-3.20.0-GA
  • thymeleaf-3.0.12.RELEASE 依赖 attoparser-2.0.5.RELEASE
  • thymeleaf-3.0.12.RELEASE 依赖 unbescape-1.1.6.RELEASE
  • thymeleaf-3.0.12.RELEASE 依赖 slf4j-api-1.7.26

依赖管理中要解决的具体问题:

  • jar 包的下载:使用 Maven 之后,jar 包会从规范的远程仓库下载到本地
  • jar 包之间的依赖:通过依赖的传递性自动完成
  • jar 包之间的冲突:通过对依赖的配置进行调整,让某些jar包不会被导入

    3、Maven 的工作机制

    Maven入门 - 图4

    第二章 Maven核心程序解压和配置

    一、Maven核心程序解压与配置

    1、Maven 官网地址

    首页:
    Maven – Welcome to Apache Maven(opens new window)
    下载页面:
    Maven – Download Apache Maven(opens new window)
    下载链接:
    Maven入门 - 图5
    具体下载地址:https://dlcdn.apache.org/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.zip

    2、解压Maven核心程序

    核心程序压缩包:apache-maven-3.8.4-bin.zip,解压到非中文、没有空格的目录。例如:
    image.png
    在解压目录中,我们需要着重关注 Maven 的核心配置文件:conf/settings.xml

    3、指定本地仓库

    本地仓库默认值:用户家目录/.m2/repository。由于本地仓库的默认位置是在用户的家目录下,而家目录往往是在 C 盘,也就是系统盘。将来 Maven 仓库中 jar 包越来越多,仓库体积越来越大,可能会拖慢 C 盘运行速度,影响系统性能。所以建议将 Maven 的本地仓库放在其他盘符下。配置方式如下:
    1. <!-- localRepository
    2. | The path to the local repository maven will use to store artifacts.
    3. |
    4. | Default: ${user.home}/.m2/repository
    5. <localRepository>/path/to/local/repo</localRepository>
    6. -->
    7. <localRepository>D:\maven-repository</localRepository>
    本地仓库这个目录,我们手动创建一个空的目录即可。
    记住:一定要把 localRepository 标签从注释中拿出来
    注意:本地仓库本身也需要使用一个非中文、没有空格的目录。

    4、配置阿里云提供的镜像仓库

    Maven 下载 jar 包默认访问境外的中央仓库,而国外网站速度很慢。改成阿里云提供的镜像仓库,访问国内网站,可以让 Maven 下载 jar 包的时候速度更快。配置的方式是:

    ①将原有的例子配置注释掉

    1. <!-- <mirror>
    2. <id>maven-default-http-blocker</id>
    3. <mirrorOf>external:http:*</mirrorOf>
    4. <name>Pseudo repository to mirror external repositories initially using HTTP.</name>
    5. <url>http://0.0.0.0/</url>
    6. <blocked>true</blocked>
    7. </mirror> -->

    ②加入我们的配置

    将下面 mirror 标签整体复制到 settings.xml 文件的 mirrors 标签的内部。
    1. <mirror>
    2. <id>nexus-aliyun</id>
    3. <mirrorOf>central</mirrorOf>
    4. <name>Nexus aliyun</name>
    5. <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    6. </mirror>

    5、配置 Maven 工程的基础 JDK 版本

    如果按照默认配置运行,Java 工程使用的默认 JDK 版本是 1.5,而我们熟悉和常用的是 JDK 1.8 版本。修改配置的方式是:将 profile 标签整个复制到 settings.xml 文件的 profiles 标签内。
    1. <profile>
    2. <id>jdk-1.8</id>
    3. <activation>
    4. <activeByDefault>true</activeByDefault>
    5. <jdk>1.8</jdk>
    6. </activation>
    7. <properties>
    8. <maven.compiler.source>1.8</maven.compiler.source>
    9. <maven.compiler.target>1.8</maven.compiler.target>
    10. <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    11. </properties>
    12. </profile>

    二、配置环境变量

    1、检查 JAVA_HOME 配置是否正确

    Maven 是一个用 Java 语言开发的程序,它必须基于 JDK 来运行,需要通过 JAVA_HOME 来找到 JDK 的安装位置。
    image.png
    可以使用下面的命令验证: ```latex C:\Users\Administrator>echo %JAVA_HOME% D:\software\Java

C:\Users\Administrator>java -version java version “1.8.0_141” Java(TM) SE Runtime Environment (build 1.8.0_141-b15) Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)

  1. <a name="Dbb2g"></a>
  2. ### 2、配置 MAVEN_HOME
  3. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/26803611/1651993987169-8f5d16ea-e164-4e94-a0a1-ddd47cf703bf.png#clientId=u8f232595-53a3-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u22bd925a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=183&originWidth=653&originalType=url&ratio=1&rotation=0&showTitle=false&size=5822&status=done&style=none&taskId=ub6eba783-682a-4c85-a836-4b655958d0a&title=)
  4. > **TIP**
  5. > 配置环境变量的规律:
  6. > XXX_HOME 通常指向的是 bin 目录的上一级
  7. > PATH 指向的是 bin 目录
  8. <a name="HXCcB"></a>
  9. ### 3、配置PATH
  10. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/26803611/1651993987174-03326338-82e0-4adf-8d76-aa31b87b3284.png#clientId=u8f232595-53a3-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u930ae7b6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=527&originalType=url&ratio=1&rotation=0&showTitle=false&size=9462&status=done&style=none&taskId=ua36ad498-89b3-4582-a335-fe7efe729f7&title=)
  11. <a name="AlOYQ"></a>
  12. ### 4、验证
  13. ```latex
  14. C:\Users\Administrator>mvn -v
  15. Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
  16. Maven home: D:\software\apache-maven-3.8.4
  17. Java version: 1.8.0_141, vendor: Oracle Corporation, runtime: D:\software\Java\jre
  18. Default locale: zh_CN, platform encoding: GBK
  19. OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

第三章 使用Maven:命令行环境

一、实验一:根据坐标创建 Maven 工程

1、Maven 核心概念:坐标

①数学中的坐标

image.png
使用 x、y、z 三个『向量』作为空间的坐标系,可以在『空间』中唯一的定位到一个『点』

②Maven中的坐标

[1]向量说明

使用三个『向量』『Maven的仓库』唯一的定位到一个『jar』包。

  • groupId:公司或组织的 id
  • artifactId:一个项目或者是项目中的一个模块的 id
  • version:版本号

    [2]三个向量的取值方式
  • groupId:公司或组织域名的倒序,通常也会加上项目名称

    • 例如:com.atguigu.maven
  • artifactId:模块的名称,将来作为 Maven 工程的工程名
  • version:模块的版本号,根据自己的需要设定
    • 例如:SNAPSHOT 表示快照版本,正在迭代过程中,不稳定的版本
    • 例如:RELEASE 表示正式版本

举例:

  • groupId:com.atguigu.maven
  • artifactId:pro01-atguigu-maven
  • version:1.0-SNAPSHOT

    ③坐标和仓库中 jar 包的存储路径之间的对应关系

    坐标:
    1. <groupId>javax.servlet</groupId>
    2. <artifactId>servlet-api</artifactId>
    3. <version>2.5</version>
    上面坐标对应的 jar 包在 Maven 本地仓库中的位置:
    Maven本地仓库根目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar
    一定要学会根据坐标到本地仓库中找到对应的 jar 包。

    2、实验操作

    ①创建目录作为后面操作的工作空间

    例如:D:\maven-workspace\space201026

    WARNING 此时我们已经有了三个目录,分别是:

    • Maven 核心程序:中军大帐
    • Maven 本地仓库:兵营
    • 本地工作空间:战场

②在工作空间目录下打开命令行窗口

Maven入门 - 图9

③使用命令生成Maven工程

Maven入门 - 图10
运行 mvn archetype:generate 命令
下面根据提示操作

Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 7:【直接回车,使用默认值】 Define value for property ‘groupId’: com.atguigu.maven Define value for property ‘artifactId’: pro01-maven-java Define value for property ‘version’ 1.0-SNAPSHOT: :【直接回车,使用默认值】 Define value for property ‘package’ com.atguigu.maven: :【直接回车,使用默认值】 Confirm properties configuration: groupId: com.atguigu.maven artifactId: pro01-maven-java version: 1.0-SNAPSHOT package: com.atguigu.maven Y: :【直接回车,表示确认。如果前面有输入错误,想要重新输入,则输入 N 再回车。】

④调整

Maven 默认生成的工程,对 junit 依赖的是较低的 3.8.1 版本,我们可以改成较适合的 4.12 版本。
自动生成的 App.java 和 AppTest.java 可以删除。

  1. <!-- 依赖信息配置 -->
  2. <!-- dependencies复数标签:里面包含dependency单数标签 -->
  3. <dependencies>
  4. <!-- dependency单数标签:配置一个具体的依赖 -->
  5. <dependency>
  6. <!-- 通过坐标来依赖其他jar包 -->
  7. <groupId>junit</groupId>
  8. <artifactId>junit</artifactId>
  9. <version>4.12</version>
  10. <!-- 依赖的范围 -->
  11. <scope>test</scope>
  12. </dependency>
  13. </dependencies>

⑤自动生成的 pom.xml 解读

  1. <!-- project标签:根标签,表示对当前工程进行配置、管理 -->
  2. <project xmlns="" ...>
  3. <!-- modelVersion标签:从Maven2开始就固定是4.0.0 -->
  4. <!-- 代表当前pom.xml所采用的标签结构 -->
  5. <modelVersion>4.0.0</modelVersion>
  6. <!-- 当前Maven工程的坐标 -->
  7. <!-- 在本地仓库中安装后对应的路径: -->
  8. <!-- com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.jar -->
  9. <groupId>com.atguigu.maven</groupId>
  10. <artifactId>pro01-maven-java</artifactId>
  11. <version>1.0-SNAPSHOT</version>
  12. <!-- 当前Maven工程的打包方式,可选值有下面三种: -->
  13. <!-- jar:表示这个工程是一个Java工程 -->
  14. <!-- war:表示这个工程是一个Web工程 -->
  15. <!-- pom:表示这个工程是“管理其他工程”的工程 -->
  16. <packaging>jar</packaging>
  17. <name>pro01-maven-java</name>
  18. <url>http://maven.apache.org</url>
  19. <properties>
  20. <!-- 工程构建过程中读取源码时使用的字符集 -->
  21. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  22. </properties>
  23. <!-- 当前工程所依赖的jar包 -->
  24. <dependencies>
  25. <!-- 使用dependency配置一个具体的依赖 -->
  26. <dependency>
  27. <!-- 在dependency标签内使用具体的坐标依赖我们需要的一个jar包 -->
  28. <groupId>junit</groupId>
  29. <artifactId>junit</artifactId>
  30. <version>4.12</version>
  31. <!-- scope标签配置依赖的范围 -->
  32. <scope>test</scope>
  33. </dependency>
  34. </dependencies>

3、Maven核心概念:POM

①含义

POM:Project Object Model,项目对象模型。和 POM 类似的是:DOM(Document Object Model),文档对象模型。它们都是模型化思想的具体体现。

②模型化思想

POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。

③对应的配置文件

POM 理念集中体现在 Maven 工程根目录下 pom.xml 这个配置文件中。所以这个 pom.xml 配置文件就是 Maven 工程的核心配置文件。其实学习 Maven 就是学这个文件怎么配置,各个配置有什么用。

4、Maven核心概念:约定的目录结构

①各个目录的作用

Maven入门 - 图11
另外还有一个 target 目录专门存放构建操作输出的结果。

②约定目录结构的意义

Maven 为了让构建过程能够尽可能自动化完成,所以必须约定目录结构的作用。例如:Maven 执行编译操作,必须先去 Java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放在 target 目录。

③约定大于配置

Maven 对于目录结构这个问题,没有采用配置的方式,而是基于约定。这样会让我们在开发过程中非常方便。如果每次创建 Maven 工程后,还需要针对各个目录的位置进行详细的配置,那肯定非常麻烦。
目前开发领域的技术发展趋势就是:约定大于配置,配置大于编码。

二、实验二:在 Maven 工程中编写代码

1、主体程序

Maven入门 - 图12
主体程序指的是被测试的程序,同时也是将来在项目中真正要使用的程序。

  1. package com.atguigu.maven;
  2. public class Calculator {
  3. public int sum(int i, int j){
  4. return i + j;
  5. }
  6. }

2、测试程序

Maven入门 - 图13

  1. package com.atguigu.maven;
  2. import org.junit.Test;
  3. import com.atguigu.maven.Calculator;
  4. // 静态导入的效果是将Assert类中的静态资源导入当前类
  5. // 这样一来,在当前类中就可以直接使用Assert类中的静态资源,不需要写类名
  6. import static org.junit.Assert.*;
  7. public class CalculatorTest{
  8. @Test
  9. public void testSum(){
  10. // 1.创建Calculator对象
  11. Calculator calculator = new Calculator();
  12. // 2.调用Calculator对象的方法,获取到程序运行实际的结果
  13. int actualResult = calculator.sum(5, 3);
  14. // 3.声明一个变量,表示程序运行期待的结果
  15. int expectedResult = 8;
  16. // 4.使用断言来判断实际结果和期待结果是否一致
  17. // 如果一致:测试通过,不会抛出异常
  18. // 如果不一致:抛出异常,测试失败
  19. assertEquals(expectedResult, actualResult);
  20. }
  21. }

三、实验三:执行 Maven 的构建命令

1、要求

运行 Maven 中和构建操作相关的命令时,必须进入到 pom.xml 所在的目录。如果没有在 pom.xml 所在的目录运行 Maven 的构建命令,那么会看到下面的错误信息:

The goal you specified requires a project to execute but there is no POM in this directory

mvn -v 命令和构建操作无关,只要正确配置了 PATH,在任何目录下执行都可以。而构建相关的命令要在 pom.xml 所在目录下运行——操作哪个工程,就进入这个工程的 pom.xml 目录。

2、清理操作

mvn clean
效果:删除 target 目录

3、编译操作

主程序编译:mvn compile
测试程序编译:mvn test-compile
主体程序编译结果存放的目录:target/classes
测试程序编译结果存放的目录:target/test-classes

4、测试操作

mvn test
测试的报告存放的目录:target/surefire-reports

5、打包操作

mvn package
打包的结果——jar 包,存放的目录:target

6、安装操作

mvn install(可以使用复合命令 mvn clean install)

[INFO] Installing D:\maven-workspace\space201026\pro01-maven-java\target\pro01-maven-java-1.0-SNAPSHOT.jar to D:\maven-rep1026\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.jar [INFO] Installing D:\maven-workspace\space201026\pro01-maven-java\pom.xml to D:\maven-rep1026\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.pom

安装的效果是将本地构建过程中生成的 jar 包存入 Maven 本地仓库。这个 jar 包在 Maven 仓库中的路径是根据它的坐标生成的。
坐标信息如下:

  1. <groupId>com.atguigu.maven</groupId>
  2. <artifactId>pro01-maven-java</artifactId>
  3. <version>1.0-SNAPSHOT</version>

在 Maven 仓库中生成的路径如下:

D:\maven-rep1026\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-SNAPSHOT.jar

另外,安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地仓库。所以我们在 Maven 的本地仓库中想看一个 jar 包原始的 pom.xml 文件时,查看对应 XXX.pom 文件即可,它们是名字发生了改变,本质上是同一个文件。

四、实验四:创建 Maven 版的 Web 工程

1、说明

使用 mvn archetype:generate 命令生成 Web 工程时,需要使用一个专门的 archetype。这个专门生成 Web 工程骨架的 archetype 可以参照官网看到它的用法:
Maven入门 - 图14
参数 archetypeGroupId、archetypeArtifactId、archetypeVersion 用来指定现在使用的 maven-archetype-webapp 的坐标。

2、操作

注意:如果在上一个工程的目录下执行 mvn archetype:generate 命令,那么 Maven 会报错:不能在一个非 pom 的工程下再创建其他工程。所以不要再刚才创建的工程里再创建新的工程,请回到工作空间根目录来操作。
然后运行生成工程的命令:

mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4

下面的操作按照提示执行:

Define value for property ‘groupId’: com.atguigu.maven Define value for property ‘artifactId’: pro02-maven-web Define value for property ‘version’ 1.0-SNAPSHOT: :【直接回车,使用默认值】 Define value for property ‘package’ com.atguigu.maven: :【直接回车,使用默认值】 Confirm properties configuration: groupId: com.atguigu.maven artifactId: pro02-maven-web version: 1.0-SNAPSHOT package: com.atguigu.maven Y: :【直接回车,表示确认】

3、生成的pom.xml

确认打包的方式是war包形式

  1. <packaging>war</packaging>

4、生成的Web工程的目录结构

image.png
webapp 目录下有 index.jsp
WEB-INF 目录下有 web.xml

5、创建 Servlet

①在 main 目录下创建 java 目录

image.png

②在 java 目录下创建 Servlet 类所在的包的目录

image.png

③在包下创建 Servlet 类

  1. package com.atguigu.maven;
  2. import javax.servlet.http.HttpServlet;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import javax.servlet.ServletException;
  6. import java.io.IOException;
  7. public class HelloServlet extends HttpServlet{
  8. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  9. response.getWriter().write("hello maven web");
  10. }
  11. }

④在 web.xml 中注册 Servlet

  1. <servlet>
  2. <servlet-name>helloServlet</servlet-name>
  3. <servlet-class>com.atguigu.maven.HelloServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>helloServlet</servlet-name>
  7. <url-pattern>/helloServlet</url-pattern>
  8. </servlet-mapping>

6、在 index.jsp 页面编写超链接

  1. <html>
  2. <body>
  3. <h2>Hello World!</h2>
  4. <a href="helloServlet">Access Servlet</a>
  5. </body>
  6. </html>

JSP全称是 Java Server Page,和 Thymeleaf 一样,是服务器端页面渲染技术。这里我们不必关心 JSP 语法细节,编写一个超链接标签即可。

7、编译

此时直接执行 mvn compile 命令出错:
DANGER
程序包 javax.servlet.http 不存在
程序包 javax.servlet 不存在
找不到符号
符号: 类 HttpServlet
……
上面的错误信息说明:我们的 Web 工程用到了 HttpServlet 这个类,而 HttpServlet 这个类属于 servlet-api.jar 这个 jar 包。此时我们说,Web 工程需要依赖 servlet-api.jar 包。
Maven入门 - 图18

8、配置对 servlet-api.jar 包的依赖

对于不知道详细信息的依赖可以到https://mvnrepository.com/网站查询。使用关键词搜索,然后在搜索结果列表中选择适合的使用。
Maven入门 - 图19
比如,我们找到的 servlet-api 的依赖信息:

  1. <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
  2. <dependency>
  3. <groupId>javax.servlet</groupId>
  4. <artifactId>javax.servlet-api</artifactId>
  5. <version>3.1.0</version>
  6. <scope>provided</scope>
  7. </dependency>

这样就可以把上面的信息加入 pom.xml。重新执行 mvn compile 命令。

9、将 Web 工程打包为 war 包

运行 mvn package 命令,生成 war 包的位置如下图所示:
image.png

10、将 war 包部署到 Tomcat 上运行

将 war 包复制到 Tomcat/webapps 目录下
image.png
启动 Tomcat:
image.png
image.png
通过浏览器尝试访问:http://localhost:8080/pro02-maven-web/index.jsp

五、实验五:让 Web 工程依赖 Java 工程

1、观念

明确一个意识:从来只有 Web 工程依赖 Java 工程,没有反过来 Java 工程依赖 Web 工程。本质上来说,Web 工程依赖的 Java 工程其实就是 Web 工程里导入的 jar 包。最终 Java 工程会变成 jar 包,放在 Web 工程的 WEB-INF/lib 目录下。

2、操作

在 pro02-maven-web 工程的 pom.xml 中,找到 dependencies 标签,在 dependencies 标签中做如下配置:

  1. <!-- 配置对Java工程pro01-maven-java的依赖 -->
  2. <!-- 具体的配置方式:在dependency标签内使用坐标实现依赖 -->
  3. <dependency>
  4. <groupId>com.atguigu.maven</groupId>
  5. <artifactId>pro01-maven-java</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. <scope>compile</scope> <!-- 默认,可以不写 -->
  8. </dependency>

3、在 Web 工程中,编写测试代码

①补充创建目录

pro02-maven-web\src\test\java\com\atguigu\maven

②确认 Web 工程依赖了 junit

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.12</version>
  5. <scope>test</scope>
  6. </dependency>

③创建测试类

把 Java 工程的 CalculatorTest.java 类复制到 pro02-maven-wb\src\test\java\com\atguigu\maven 目录下

4、执行Maven命令

①测试命令

mvn test
说明:测试操作中会提前自动执行编译操作,测试成功就说明编译也是成功的。

②打包命令

mvn package
Maven入门 - 图24
通过查看 war 包内的结构,我们看到被 Web 工程依赖的 Java 工程确实是会变成 Web 工程的 WEB-INF/lib 目录下的 jar 包。
image.png

③查看当前 Web 工程所依赖的 jar 包的列表

mvn dependency:list

[INFO] The following files have been resolved:
[INFO] org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile
[INFO] junit:junit:jar:4.12:test

说明:javax.servlet:javax.servlet-api:jar:3.1.0:provided 格式显示的是一个 jar 包的坐标信息。格式是:

groupId:artifactId:打包方式:version:依赖的范围

这样的格式虽然和我们 XML 配置文件中坐标的格式不同,但是本质上还是坐标信息,大家需要能够认识这样的格式,将来从 Maven 命令的日志或错误信息中看到这样格式的信息,就能够识别出来这是坐标。进而根据坐标到Maven 仓库找到对应的jar包,用这样的方式解决我们遇到的报错的情况。

④以树形结构查看当前 Web 工程的依赖信息

mvn dependency:tree

[INFO] com.atguigu.maven:pro02-maven-web:war:1.0-SNAPSHOT
[INFO] +- junit:junit:jar:4.12:test
[INFO] | - org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] - com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile

我们在 pom.xml 中并没有依赖 hamcrest-core,但是它却被加入了我们依赖的列表。原因是:junit 依赖了hamcrest-core,然后基于依赖的传递性,hamcrest-core 被传递到我们的工程了。

六、实验六:测试依赖的范围

1、依赖范围

标签的位置:dependencies/dependency/scope
标签的可选值:compile/test/provided/system/runtime/import

①compile 和 test 对比

main目录(空间) test目录(空间) 开发过程(时间) 部署到服务器(时间)
compile 有效 有效 有效 有效
test 无效 有效 有效 无效

②compile 和 provided 对比

main目录(空间) test目录(空间) 开发过程(时间) 部署到服务器(时间)
compile 有效 有效 有效 有效
provided 有效 有效 有效 无效

③结论

  • compile:通常使用的第三方框架的 jar 包这样在项目实际运行时真正要用到的 jar 包都是以 compile 范围进行依赖的。比如 SSM 框架所需jar包。
  • test:测试过程中使用的 jar 包,以 test 范围依赖进来。比如 junit。
  • provided:在开发过程中需要用到的“服务器上的 jar 包”通常以 provided 范围依赖进来。比如 servlet-api、jsp-api。而这个范围的 jar 包之所以不参与部署、不放进 war 包,就是避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。说白了就是:“服务器上已经有了,你就别带啦!

    2、测试

    ①验证 compile 范围对 main 目录有效

    main目录下的类:HelloServlet 使用compile范围导入的依赖:pro01-atguigu-maven 验证:使用compile范围导入的依赖对main目录下的类来说是有效的 有效:HelloServlet 能够使用 pro01-atguigu-maven 工程中的 Calculator 类 验证方式:在 HelloServlet 类中导入 Calculator 类,然后编译就说明有效。

②验证test范围对main目录无效

测试方式:在主体程序中导入org.junit.Test这个注解,然后执行编译。
具体操作:在pro01-maven-java\src\main\java\com\atguigu\maven目录下修改Calculator.java

  1. package com.atguigu.maven;
  2. import org.junit.Test;
  3. public class Calculator {
  4. public int sum(int i, int j){
  5. return i + j;
  6. }
  7. }

执行Maven编译命令:

[ERROR] /D:/maven-workspace/space201026/pro01-maven-java/src/main/java/com/atguigu/maven/Calculator.java:[3,17] 程序包org.junit不存在

③验证test和provided范围不参与服务器部署

其实就是验证:通过compile范围依赖的jar包会放入war包,通过test范围依赖的jar包不会放入war包。
Maven入门 - 图26

④验证provided范围对测试程序有效

测试方式是在pro02-maven-web的测试程序中加入servlet-api.jar包中的类。
修改:pro02-maven-web\src*test\java\com\atguigu\maven\CalculatorTest.java*

  1. package com.atguigu.maven;
  2. import javax.servlet.http.HttpServlet;
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5. import javax.servlet.ServletException;
  6. import org.junit.Test;
  7. import com.atguigu.maven.Calculator;
  8. // 静态导入的效果是将Assert类中的静态资源导入当前类
  9. // 这样一来,在当前类中就可以直接使用Assert类中的静态资源,不需要写类名
  10. import static org.junit.Assert.*;
  11. public class CalculatorTest{
  12. @Test
  13. public void testSum(){
  14. // 1.创建Calculator对象
  15. Calculator calculator = new Calculator();
  16. // 2.调用Calculator对象的方法,获取到程序运行实际的结果
  17. int actualResult = calculator.sum(5, 3);
  18. // 3.声明一个变量,表示程序运行期待的结果
  19. int expectedResult = 8;
  20. // 4.使用断言来判断实际结果和期待结果是否一致
  21. // 如果一致:测试通过,不会抛出异常
  22. // 如果不一致:抛出异常,测试失败
  23. assertEquals(expectedResult, actualResult);
  24. }
  25. }

然后运行Maven的编译命令:mvn compile
然后看到编译成功。

七、实验七:测试依赖的传递性

1、依赖的传递性

①概念

A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖的情况下,A 里面能不能直接使用 C?

②传递的原则

在 A 依赖 B,B 依赖 C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。

  • B 依赖 C 时使用 compile 范围:可以传递
  • B 依赖 C 时使用 test 或 provided 范围:不能传递,所以需要这样的 jar 包时,就必须在需要的地方明确配置依赖才可以。

    2、使用 compile 范围依赖 spring-core

    测试方式:让 pro01-maven-java 工程依赖 spring-core
    具体操作:编辑 pro01-maven-java 工程根目录下 pom.xml
    1. <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    2. <dependency>
    3. <groupId>org.springframework</groupId>
    4. <artifactId>spring-core</artifactId>
    5. <version>4.0.0.RELEASE</version>
    6. </dependency>
    使用 mvn dependency:tree 命令查看效果:

    [INFO] com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT
    [INFO] +- junit:junit:jar:4.12:test
    [INFO] | - org.hamcrest:hamcrest-core:jar:1.3:test
    [INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile
    [INFO] - commons-logging:commons-logging:jar:1.1.1:compile

还可以在 Web 工程中,使用 mvn dependency:tree 命令查看效果(需要重新将 pro01-maven-java 安装到仓库):

[INFO] com.atguigu.maven:pro02-maven-web:war:1.0-SNAPSHOT
[INFO] +- junit:junit:jar:4.12:test
[INFO] | - org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] - com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile
[INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile
[INFO] - commons-logging:commons-logging:jar:1.1.1:compile

3、验证 test 和 provided 范围不能传递

从上面的例子已经能够看到,pro01-maven-java 依赖了 junit,但是在 pro02-maven-web 工程中查看依赖树的时候并没有看到 junit。
要验证 provided 范围不能传递,可以在 pro01-maven-java 工程中加入 servlet-api 的依赖。

  1. <dependency>
  2. <groupId>javax.servlet</groupId>
  3. <artifactId>javax.servlet-api</artifactId>
  4. <version>3.1.0</version>
  5. <scope>provided</scope>
  6. </dependency>

效果还是和之前一样:

[INFO] com.atguigu.maven:pro02-maven-web:war:1.0-SNAPSHOT
[INFO] +- junit:junit:jar:4.12:test
[INFO] | - org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] - com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile
[INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile
[INFO] - commons-logging:commons-logging:jar:1.1.1:compile

八、实验八:测试依赖的排除

1、概念

当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。
Maven入门 - 图27
所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。

2、配置方式

  1. <dependency>
  2. <groupId>com.atguigu.maven</groupId>
  3. <artifactId>pro01-maven-java</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. <scope>compile</scope>
  6. <!-- 使用excludes标签配置依赖的排除 -->
  7. <exclusions>
  8. <!-- 在exclude标签中配置一个具体的排除 -->
  9. <exclusion>
  10. <!-- 指定要排除的依赖的坐标(不需要写version) -->
  11. <groupId>commons-logging</groupId>
  12. <artifactId>commons-logging</artifactId>
  13. </exclusion>
  14. </exclusions>
  15. </dependency>

3、测试

测试的方式:在 pro02-maven-web 工程中配置对 commons-logging 的排除

  1. <dependency>
  2. <groupId>com.atguigu.maven</groupId>
  3. <artifactId>pro01-maven-java</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. <scope>compile</scope>
  6. <!-- 使用excludes标签配置依赖的排除 -->
  7. <exclusions>
  8. <!-- 在exclude标签中配置一个具体的排除 -->
  9. <exclusion>
  10. <!-- 指定要排除的依赖的坐标(不需要写version) -->
  11. <groupId>commons-logging</groupId>
  12. <artifactId>commons-logging</artifactId>
  13. </exclusion>
  14. </exclusions>
  15. </dependency>

运行 mvn dependency:tree 命令查看效果:

[INFO] com.atguigu.maven:pro02-maven-web:war:1.0-SNAPSHOT
[INFO] +- junit:junit:jar:4.12:test
[INFO] | - org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] - com.atguigu.maven:pro01-maven-java:jar:1.0-SNAPSHOT:compile
[INFO] - org.springframework:spring-core:jar:4.0.0.RELEASE:compile

发现在 spring-core 下面就没有 commons-logging 了。

九、实验九:继承

1、概念

Maven工程之间,A 工程继承 B 工程

  • B 工程:父工程
  • A 工程:子工程

本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置。

2、作用

在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本。
它的背景是:

  • 对一个比较大型的项目进行了模块拆分。
  • 一个 project 下面,创建了很多个 module。
  • 每一个 module 都需要配置自己的依赖信息。

它背后的需求是:

  • 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。
  • 使用同一个框架内的不同 jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
  • 使用框架时所需要的 jar 包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索。

通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。

3、举例

在一个工程中依赖多个 Spring 的 jar 包

[INFO] +- org.springframework:spring-core:jar:4.0.0.RELEASE:compile
[INFO] | - commons-logging:commons-logging:jar:1.1.1:compile
[INFO] +- org.springframework:spring-beans:jar:4.0.0.RELEASE:compile
[INFO] +- org.springframework:spring-context:jar:4.0.0.RELEASE:compile
[INFO] +- org.springframework:spring-expression:jar:4.0.0.RELEASE:compile
[INFO] +- org.springframework:spring-aop:jar:4.0.0.RELEASE:compile
[INFO] | - aopalliance:aopalliance:jar:1.0:compile

使用 Spring 时要求所有 Spring 自己的 jar 包版本必须一致。为了能够对这些 jar 包的版本进行统一管理,我们使用继承这个机制,将所有版本信息统一在父工程中进行管理。

4、操作

①创建父工程

创建的过程和前面创建 pro01-maven-java 一样。
工程名称:pro03-maven-parent
工程创建好之后,要修改它的打包方式:

  1. <groupId>com.atguigu.maven</groupId>
  2. <artifactId>pro03-maven-parent</artifactId>
  3. <version>1.0-SNAPSHOT</version>
  4. <!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是 pom -->
  5. <packaging>pom</packaging>

只有打包方式为 pom 的 Maven 工程能够管理其他 Maven 工程。打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他 Maven 工程的工程。

②创建模块工程

模块工程类似于 IDEA 中的 module,所以需要进入 pro03-maven-parent 工程的根目录,然后运行 mvn archetype:generate 命令来创建模块工程。
假设,我们创建三个模块工程:
image.png

③查看被添加新内容的父工程 pom.xml

下面 modules 和 module 标签是聚合功能的配置

  1. <modules>
  2. <module>pro04-maven-module</module>
  3. <module>pro05-maven-module</module>
  4. <module>pro06-maven-module</module>
  5. </modules>

④解读子工程的pom.xml

  1. <!-- 使用parent标签指定当前工程的父工程 -->
  2. <parent>
  3. <!-- 父工程的坐标 -->
  4. <groupId>com.atguigu.maven</groupId>
  5. <artifactId>pro03-maven-parent</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. </parent>
  8. <!-- 子工程的坐标 -->
  9. <!-- 如果子工程坐标中的groupId和version与父工程一致,那么可以省略 -->
  10. <!-- <groupId>com.atguigu.maven</groupId> -->
  11. <artifactId>pro04-maven-module</artifactId>
  12. <!-- <version>1.0-SNAPSHOT</version> -->

⑤在父工程中配置依赖的统一管理

  1. <!-- 使用dependencyManagement标签配置对依赖的管理 -->
  2. <!-- 被管理的依赖并没有真正被引入到工程 -->
  3. <dependencyManagement>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.springframework</groupId>
  7. <artifactId>spring-core</artifactId>
  8. <version>4.0.0.RELEASE</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework</groupId>
  12. <artifactId>spring-beans</artifactId>
  13. <version>4.0.0.RELEASE</version>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework</groupId>
  17. <artifactId>spring-context</artifactId>
  18. <version>4.0.0.RELEASE</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework</groupId>
  22. <artifactId>spring-expression</artifactId>
  23. <version>4.0.0.RELEASE</version>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework</groupId>
  27. <artifactId>spring-aop</artifactId>
  28. <version>4.0.0.RELEASE</version>
  29. </dependency>
  30. </dependencies>
  31. </dependencyManagement>

⑥子工程中引用那些被父工程管理的依赖

关键点:省略版本号

  1. <!-- 子工程引用父工程中的依赖信息时,可以把版本号去掉。 -->
  2. <!-- 把版本号去掉就表示子工程中这个依赖的版本由父工程决定。 -->
  3. <!-- 具体来说是由父工程的dependencyManagement来决定。 -->
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.springframework</groupId>
  7. <artifactId>spring-core</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.springframework</groupId>
  11. <artifactId>spring-beans</artifactId>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework</groupId>
  15. <artifactId>spring-context</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework</groupId>
  19. <artifactId>spring-expression</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework</groupId>
  23. <artifactId>spring-aop</artifactId>
  24. </dependency>
  25. </dependencies>

⑦在父工程中升级依赖信息的版本

  1. ……
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-beans</artifactId>
  5. <version>4.1.4.RELEASE</version>
  6. </dependency>
  7. ……

然后在子工程中运行mvn dependency:list,效果如下:
[INFO] org.springframework:spring-aop:jar:4.1.4.RELEASE:compile
[INFO] org.springframework:spring-core:jar:4.1.4.RELEASE:compile
[INFO] org.springframework:spring-context:jar:4.1.4.RELEASE:compile
[INFO] org.springframework:spring-beans:jar:4.1.4.RELEASE:compile
[INFO] org.springframework:spring-expression:jar:4.1.4.RELEASE:compile

⑧在父工程中声明自定义属性

  1. <!-- 通过自定义属性,统一指定Spring的版本 -->
  2. <properties>
  3. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  4. <!-- 自定义标签,维护Spring版本数据 -->
  5. <atguigu.spring.version>4.3.6.RELEASE</atguigu.spring.version>
  6. </properties>

在需要的地方使用${}的形式来引用自定义的属性名:

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-core</artifactId>
  4. <version>${atguigu.spring.version}</version>
  5. </dependency>

真正实现“一处修改,处处生效”。

5、实际意义

Maven入门 - 图29
编写一套符合要求、开发各种功能都能正常工作的依赖组合并不容易。如果公司里已经有人总结了成熟的组合方案,那么再开发新项目时,如果不使用原有的积累,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。
如上图所示,公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子系统各取所需即可。

十、实验十:聚合

1、聚合本身的含义

部分组成整体
Maven入门 - 图30
动画片《战神金刚》中的经典台词:“我来组成头部!我来组成手臂!”就是聚合关系最生动的体现。

2、Maven 中的聚合

使用一个“总工程”将各个“模块工程”汇集起来,作为一个整体对应完整的项目。

  • 项目:整体
  • 模块:部分

    概念的对应关系: 从继承关系角度来看:

    • 父工程
    • 子工程

    从聚合关系角度来看:

    • 总工程
    • 模块工程

3、好处

  • 一键执行 Maven 命令:很多构建命令都可以在“总工程”中一键执行。以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。
  • 配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。

    4、聚合的配置

    在总工程中配置 modules 即可:
    1. <modules>
    2. <module>pro04-maven-module</module>
    3. <module>pro05-maven-module</module>
    4. <module>pro06-maven-module</module>
    5. </modules>

    5、依赖循环问题

    如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
    DANGER
    [ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
    这个错误的含义是:循环引用。

    第四章 使用Maven:IDEA环境

    各个 IDEA 不同版本在具体操作方面存在一定差异,这里我们以 2019.3.3 版本为例进行演示。其它版本大家灵活变通即可。 Maven入门 - 图31

一、创建父工程

1、创建 Project

image.png
Maven入门 - 图33
Maven入门 - 图34

2、开启自动导入

创建 Project 后,IDEA 会自动弹出下面提示,我们选择『Enable Auto-Import』,意思是启用自动导入。
image.png
这个自动导入一定要开启,因为 Project、Module 新创建或 pom.xml 每次修改时都应该让 IDEA 重新加载 Maven 信息。这对 Maven 目录结构认定、Java 源程序编译、依赖 jar 包的导入都有非常关键的影响。
另外也可以通过 IDEA 的 Settings 设置来开启:
Maven入门 - 图36

二、配置Maven信息

每次创建 Project 后都需要设置 Maven 家目录位置,否则 IDEA 将使用内置的 Maven 核心程序(不稳定)并使用默认的本地仓库位置。这样一来,我们在命令行操作过程中已下载好的 jar 包就白下载了,默认的本地仓库通常在 C 盘,还影响系统运行。
配置之后,IDEA 会根据我们在这里指定的 Maven 家目录自动识别到我们在 settings.xml 配置文件中指定的本地仓库。
Maven入门 - 图37

三、创建Java模块工程

Maven入门 - 图38
Maven入门 - 图39

四、创建Web模块工程

1、创建模块

按照前面的同样操作创建模块,此时这个模块其实还是一个Java模块

2、修改打包方式

Web 模块将来打包当然应该是 war 包。

  1. <packaging>war</packaging>

3、Web 设定

首先打开项目结构菜单:
image.png
然后到 Facets 下查看 IDEA 是否已经帮我们自动生成了 Web 设定。正常来说只要我们确实设置了打包方式为 war,那么 IDEA 2019 版就会自动生成 Web 设定。
Maven入门 - 图41
另外,对于 IDEA 2018 诸版本没有自动生成 Web 设定,那么请参照下面两图,我们自己创建:
Maven入门 - 图42
Maven入门 - 图43

4、借助IDEA生成web.xml

image.png
Maven入门 - 图45

5、设置 Web 资源的根目录

结合 Maven 的目录结构,Web 资源的根目录需要设置为 src/main/webapp 目录。
Maven入门 - 图46
image.png

五、其他操作

1、在IDEA中执行Maven命令

①直接执行

Maven入门 - 图48

②手动输入

image.png
image.png
Maven入门 - 图51
image.png
如果有需要,还可以给命令后面附加参数:
image.png

# -D 表示后面要附加命令的参数,字母 D 和后面的参数是紧挨着的,中间没有任何其它字符 # maven.test.skip=true 表示在执行命令的过程中跳过测试 mvn clean install -Dmaven.test.skip=true

2、在IDEA中查看某个模块的依赖信息

Maven入门 - 图54

3、工程导入

Maven工程除了自己创建的,还有很多情况是别人创建的。而为了参与开发或者是参考学习,我们都需要导入到 IDEA 中。下面我们分几种不同情况来说明:

①来自版本控制系统

目前我们通常使用的都是 Git(本地库) + 码云(远程库)的版本控制系统,结合 IDEA 的相关操作方式请点这里(opens new window)查看克隆远程库部分。

②来自工程目录

直接使用 IDEA 打开工程目录即可。下面咱们举个例子:

[1]工程压缩包

假设别人发给我们一个 Maven 工程的 zip 压缩包:maven-rest-demo.zip。从码云或GitHub上也可以以 ZIP 压缩格式对项目代码打包下载。

[2]解压

如果你的所有 IDEA 工程有一个专门的目录来存放,而不是散落各处,那么首先我们就把 ZIP 包解压到这个指定目录中。
Maven入门 - 图55

[3]打开

只要我们确认在解压目录下可以直接看到 pom.xml,那就能证明这个解压目录就是我们的工程目录。那么接下来让 IDEA 打开这个目录就可以了。
image.png
Maven入门 - 图57

[4]设置 Maven 核心程序位置

打开一个新的 Maven 工程,和新创建一个 Maven 工程是一样的,此时 IDEA 的 settings 配置中关于 Maven 仍然是默认值:
Maven入门 - 图58
所以我们还是需要像新建 Maven 工程那样,指定一下 Maven 核心程序位置:
Maven入门 - 图59

4、模块导入

①情景重现

在实际开发中,通常会忽略模块(也就是module)所在的项目(也就是project)仅仅导入某一个模块本身。这么做很可能是类似这样的情况:比如基于 Maven 学习 SSM 的时候,做练习需要导入老师发给我们的代码参考。
Maven入门 - 图60

②导入 Java 类型模块

[1]找到老师发的工程目录

image.png

[2]复制我们想要导入的模块目录

image.png

[3]粘贴到我们自己工程目录下

这个工程(project)是我们事先在 IDEA 中创建好的。
Maven入门 - 图63


image.png

[4]在 IDEA 中执行导入

image.png


Maven入门 - 图66


Maven入门 - 图67


Maven入门 - 图68


Maven入门 - 图69

[5]修改 pom.xml

刚刚导入的 module 的父工程坐标还是以前的,需要改成我们自己的 project。
Maven入门 - 图70


Maven入门 - 图71

[6]最终效果

Maven入门 - 图72

③导入 Web 类型模块

其它操作和上面演示的都一样,只是多一步:删除多余的、不正确的 web.xml 设置。如下图所示:
Maven入门 - 图73

第五章 其他核心概念

一、生命周期

我们开发一个项目的时候,通常有这些环节:创建项目、编写代码、清理已编译的代码、编译代码、执行单元测试、打包、集成测试、验证、部署、生成站点等,这些环节组成了项目的生命周期,这些过程也叫做项目的构建过程,几乎所有的项目都由这些环节中的其中几个,创建项目和编写代码是我们程序员需要多参与的,其他的都可以做成自动化的方式。
用过ant的朋友回忆一下,在maven出现以前,开发人员每天都在对项目进行清理、编译、执行单元测试、打包、部署等操作,虽然大家都在做这些工作,但是没有一个统一的标准,项目和项目之间,公司和公司之间,大多数都是各写各的,写法是千奇百怪,能满足自身需求就可以了,但是换个项目就得从头再来,这些操作又需要重新编写脚本。
而maven出来之后,项目生命周期中的这些环节都被简化了,被规范化了,maven出现之前,项目的结构没有一个统一的标准,所以生命周期中各个环节对应的自动化脚本也是各种各样,而maven约定好了项目的结构,源码的位置、资源文件的位置、测试代码的位置、测试用到的资源文件的位置、静态资源的位置、打包之后文件的位置等,这些都是maven约定好的,所以清理代码用一个命令mvn clean就可以完成,不需要我们去配置清理的目标目录;用mvn compile命令就可以完成编译的操作;用mvn test就可以自动运行测试用例;用mvn package就可以将项目打包为jar、war格式的包,能够如此简单,主要还是maven中约定大于配置的结果。

1、作用

为了让构建过程自动化完成,Maven 设定了三个生命周期,生命周期中的每一个环节对应构建过程中的一个操作。

2、三个生命周期

maven将项目的生命周期抽象成了3套生命周期,每套生命周期又包含多个阶段,每套中具体包含哪些阶段是maven已经约定好的,但是每个阶段具体需要做什么,是用户可以自己指定的。
maven中定义的3套生命周期:

  1. clean生命周期
  2. default生命周期
  3. site生命周期

上面这3套生命周期是相互独立的,没有依赖关系的,而每套生命周期中有多个阶段,每套中的多个阶段是有先后顺序的,并且后面的阶段依赖于前面的阶段,而用户可以直接使用mvn命令来调用这些阶段去完成项目生命周期中具体的操作,命令是:
mvn 生命周期阶段
通俗点解释:
maven中的3套生命周期相当于maven定义了3个类来解决项目生命周期中需要的各种操作,每个类中有多个方法,这些方法就是指具体的阶段,方法名称就是阶段的名称,每个类的方法是有顺序的,当执行某个方法的时候,这个方法前面的方法也会执行。具体每个方法中需要执行什么,这个是通过插件的方式让用户去配置的,所以非常灵活。
用户执行mvn 阶段名称就相当于调用了具体的某个方法。

clean生命周期

clean生命周期的目的是清理项目,它包含三个阶段:

生命周期阶段 描述
pre-clean 执行一些需要在clean之前完成的工作
clean 移除所有上一次构建生成的文件
post-clean 执行一些需要在clean之后立刻完成的工作

用户可以通过mvn pre-clean来调用clean生命周期中的pre-clean阶段需要执行的操作。
调用mvn post-clean会执行上面3个阶段所有的操作,上文中有说过,每个生命周期中的后面的阶段会依赖于前面的阶段,当执行某个阶段的时候,会先执行其前面的阶段。

default生命周期

这个是maven主要的生命周期,主要被用于构建应用,包含了23个阶段。

生命周期阶段 描述
validate 校验:校验项目是否正确并且所有必要的信息可以完成项目的构建过程。
initialize 初始化:初始化构建状态,比如设置属性值。
generate-sources 生成源代码:生成包含在编译阶段中的任何源代码。
process-sources 处理源代码:处理源代码,比如说,过滤任意值。
generate-resources 生成资源文件:生成将会包含在项目包中的资源文件。
process-resources 编译:复制和处理资源到目标目录,为打包阶段最好准备。
compile 处理类文件:编译项目的源代码。
process-classes 处理类文件:处理编译生成的文件,比如说对Java class文件做字节码改善优化。
generate-test-sources 生成测试源代码:生成包含在编译阶段中的任何测试源代码。
process-test-sources 处理测试源代码:处理测试源代码,比如说,过滤任意值。
generate-test-resources 生成测试源文件:为测试创建资源文件。
process-test-resources 处理测试源文件:复制和处理测试资源到目标目录。
test-compile 编译测试源码:编译测试源代码到测试目标目录.
process-test-classes 处理测试类文件:处理测试源码编译生成的文件。
test 测试:使用合适的单元测试框架运行测试(Juint是其中之一)。
prepare-package 准备打包:在实际打包之前,执行任何的必要的操作为打包做准备。
package 打包:将编译后的代码打包成可分发格式的文件,比如JAR、WAR或者EAR文件。
pre-integration-test 集成测试前:在执行集成测试前进行必要的动作。比如说,搭建需要的环境。
integration-test 集成测试:处理和部署项目到可以运行集成测试环境中。
post-integration-test 集成测试后:在执行集成测试完成后进行必要的动作。比如说,清理集成测试环境。
verify 验证:运行任意的检查来验证项目包有效且达到质量标准。
install 安装:安装项目包到本地仓库,这样项目包可以用作其他本地项目的依赖。
deploy 部署:将最终的项目包复制到远程仓库中与其他开发者和项目共享。

site生命周期

site生命周期的目的是建立和发布项目站点,Maven能够基于pom.xml所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。主要包含以下4个阶段:

阶段 描述
pre-site 执行一些需要在生成站点文档之前完成的工作
site 生成项目的站点文档
post-site 执行一些需要在生成站点文档之后完成的工作,并且为部署做准备
site-deploy 将生成的站点文档部署到特定的服务器上

3、特点

  • 前面三个生命周期彼此是独立的。
  • 在任何一个生命周期内部,执行任何一个具体环节的操作,都是从本周期最初的位置开始执行,直到指定的地方。(本节记住这句话就行了,其他的都不需要记)

Maven 之所以这么设计其实就是为了提高构建过程的自动化程度:让使用者只关心最终要干的即可,过程中的各个环节是自动执行的。

4、实例

mvn clean

该命令是调用clean生命周期的clean阶段,实际执行的阶段为clean生命周期中的pre-clean和clean阶段。

mvn test

该命令调用default生命周期的test阶段,实际上会从default生命周期的第一个阶段(validate)开始执行一直到test阶段结束。这里面包含了代码的编译,运行测试用例。

mvn clean install

这个命令中执行了两个阶段:clean和install,从上面3个生命周期的阶段列表中找一下,可以看出clean位于clean生命周期的表格中,install位于default生命周期的表格中,所以这个命令会先从clean生命周期中的pre-clean阶段开始执行一直到clean生命周期的clean阶段;然后会继续从default生命周期的validate阶段开始执行一直到default生命周期的install阶段。
这里面包含了清理上次构建的结果,编译代码,测试,打包,将打好的包安装到本地仓库。

mvn clean deploy

这个命令也比较常用,会先按顺序执行clean生命周期的[pre-clean,clean]这个闭区间内所有的阶段,然后按序执行default生命周期的[validate,deploy]这个闭区间内的所有阶段(也就是default生命周期中的所有阶段)。这个命令内部包含了清理上次构建的结果、编译代码、运行单元测试、打包、将打好的包安装到本地仓库、将打好的包发布到私服仓库。

二、插件和目标

1、插件

maven插件主要是为maven中生命周期中的阶段服务的,maven中只是定义了3套生命周期,以及每套生命周期中有哪些阶段,具体每个阶段中执行什么操作,完全是交给插件去干的。例如:编译就是由 maven-compiler-plugin-3.1.jar 插件来执行的。
maven中的插件就相当于一些工具,比如编译代码的工具,运行测试用例的工具,打包代码的工具,将代码上传到本地仓库的工具,将代码部署到远程仓库的工具等等,这些都是maven中的插件。
插件可以通过mvn命令的方式调用直接运行,或者将插件和maven生命周期的阶段进行绑定,然后通过mvn 阶段的方式执行阶段的时候,会自动执行和这些阶段绑定的插件。

2、目标

maven中的插件以jar的方式存在于仓库中,和其他构件是一样的,也是通过坐标进行访问,每个插件中可能为了代码可以重用,一个插件可能包含了多个功能,比如编译代码的插件,可以编译源代码、也可以编译测试代码;插件中的每个功能就叫做插件的目标(Plugin Goal),每个插件中可能包含一个或者多个插件目标(Plugin Goal)
Default 生命周期中有 compile 和 test-compile 两个和编译相关的环节,这两个环节对应 compile 和 test-compile 两个目标,而这两个目标都是由 maven-compiler-plugin-3.1.jar 插件来执行的。

①目标参数

插件目标是用来执行任务的,那么执行任务肯定是有参数配的,这些就是目标的参数,每个插件目标对应于java中的一个类,参数就对应于这个类中的属性。

②列出插件所有目标

mvn 插件goupId:插件artifactId[:插件version]:help
mvn 插件前缀:help
上面插件前缀的先略过,我们先看第一种效果。

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 --- [INFO] org.apache.maven.plugins:maven-clean-plugin:2.5 Maven Clean Plugin The Maven Clean Plugin is a plugin that removes files generated at build-time in a project's directory. This plugin has 2 goals: clean:clean Goal which cleans the build. This attempts to clean a project's working directory of the files that were generated at build-time. By default, it discovers and deletes the directories configured in project.build.directory, project.build.outputDirectory, project.build.testOutputDirectory, and project.reporting.outputDirectory. Files outside the default may also be included in the deletion by configuring the filesets tag. clean:help Display help information on maven-clean-plugin. Call mvn clean:help -Ddetail=true -Dgoal= to display parameter details.

上面列出了maven-clean-plugin这个插件所有的目标,有2个,分别是clean:clean、clean:help,分号后面的部分是目标名称,分号前面的部分是插件的前缀,每个目标的后面包含对这个目标的详细解释说明,关于前缀的后面会有详细介绍。

③查看插件目标参数列表

mvn 插件goupId:插件artifactId[:插件version]:help -Dgoal=目标名称 -Ddetail
mvn 插件前缀:help -Dgoal=目标名称 -Ddetail
上面命令中的-Ddetail用户输出目标详细的参数列表信息,如果没有这个,目标的参数列表不会输出出来,看效果。

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help -Dgoal=help -Ddetail [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 --- [INFO] org.apache.maven.plugins:maven-clean-plugin:2.5 Maven Clean Plugin The Maven Clean Plugin is a plugin that removes files generated at build-time in a project's directory. clean:help Display help information on maven-clean-plugin. Call mvn clean:help -Ddetail=true -Dgoal= to display parameter details. Available parameters: detail (Default: false) If true, display all settable properties for each goal. Expression: ${detail} goal The name of the goal for which to show help. If unspecified, all goals will be displayed. Expression: ${goal} indentSize (Default: 2) The number of spaces per indentation level, should be positive. Expression: ${indentSize} lineLength (Default: 80) The maximum length of a display line, should be positive. Expression: ${lineLength}

上面列出了clean插件的help目标的详细参数信息。
注意上面参数详细参数说明中有Expression: ${xxx}这样的部分,这种表示给这个运行的目标传参,可以通过mvn -Dxxx这种方式传参,xxx为${xxx}中的xxx部分,这个xxx有时候和目标参数的名称不一致,所以这点需要注意,运行带参数的目标,看一下效果:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-clean-plugin:help -Dgoal=help -Ddetail=false [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:help (default-cli) @ maven-chat06 --- [INFO] org.apache.maven.plugins:maven-clean-plugin:2.5 Maven Clean Plugin The Maven Clean Plugin is a plugin that removes files generated at build-time in a project's directory. clean:help Display help information on maven-clean-plugin. Call mvn clean:help -Ddetail=true -Dgoal= to display parameter details. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.332 s [INFO] Finished at: 2019-11-18T15:14:56+08:00 [INFO] ------------------------------------------------------------------------

上面传了一个detail=false,上面未输出目标的详细参数信息。

④命令行运行插件

mvn 插件goupId:插件artifactId[:插件version]:插件目标 [-D目标参数1] [-D目标参数2] [-D目标参数n]
mvn 插件前缀:插件目标 [-D目标参数1] [-D目标参数2] [-D目标参数n]
案例:
maven中运行测试用例使用到的插件坐标是:

  1. <dependency>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-surefire-plugin</artifactId>
  4. <version>2.12.4</version>
  5. </dependency>

我们看一下这个插件有哪些目标:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:help [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:2.12.4:help (default-cli) @ maven-chat06 --- [INFO] Maven Surefire Plugin 2.12.4 Surefire is a test framework project. This plugin has 2 goals: surefire:help Display help information on maven-surefire-plugin. Call mvn surefire:help -Ddetail=true -Dgoal= to display parameter details. surefire:test Run tests using Surefire. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.662 s [INFO] Finished at: 2019-11-18T15:26:26+08:00 [INFO] ------------------------------------------------------------------------

maven-surefire-plugin插件有2个目标help和test,描述中可以看出test目标是用来运行测试用例的。
我们看一下test目标对应的参数列表:
test目标对应的参数太多,我们只列出了部分参数,如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:help -Dgoal=test -Ddetail=true [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:2.12.4:help (default-cli) @ maven-chat06 --- [INFO] Maven Surefire Plugin 2.12.4 Surefire is a test framework project. surefire:test Run tests using Surefire. Available parameters: skip (Default: false) Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you enable it using the 'maven.test.skip' property, because maven.test.skip disables both running the tests and compiling the tests. Consider using the skipTests parameter instead.

大家认真看一下skip这个参数说明,这个参数默认是false,如果设置为true的时候,项目将跳过测试代码的编译和测试用例的执行,可以maven.test.skip这个属性来进行命令行传参,将其传递给test目标的skip属性,这个通过-D传递的参数名称就和目标参数名称不一样了,所以需要注意-D后面并不一定是参数名称。
我们来运行一下test目标看看效果。
先看一下不加参数的效果:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 --- [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.640 s [INFO] Finished at: 2019-11-18T15:33:48+08:00 [INFO] ------------------------------------------------------------------------

加maven.skip.test=true的效果如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test -Dmaven.test.skip=true [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 --- [INFO] Tests are skipped. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.652 s [INFO] Finished at: 2019-11-18T15:34:45+08:00 [INFO] ------------------------------------------------------------------------

对比一下上面2个输出,下面的多了一行如下:
[INFO] Tests are skipped.
说明跳过了测试的执行。

⑤插件传参的2种方式

刚才上面讲了一种通过-D后面跟用户属性的方式给用户传参,还有一种方式,在pom.xml中properties的用户自定义属性中进行配置,如下:
修改项目maven-chat06的pom.xml,properties中加入:

  1. <maven.test.skip>true</maven.test.skip>

cmd中运行:

  1. mvn org.apache.maven.plugins:maven-surefire-plugin:test

效果如下:

D:\code\IdeaProjects\maven-chat06>mvn org.apache.maven.plugins:maven-surefire-plugin:test [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 --- [INFO] Tests are skipped. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.638 s [INFO] Finished at: 2019-11-18T15:46:04+08:00 [INFO] ------------------------------------------------------------------------

输出中也有Tests are skipped.,说明也跳过了测试,和-Dmaven.test.skip=true效果一样。
上面说的都是插件目标的东西,那么插件目标是如何和生命周期关联起来的呢?继续向下看。

⑥获取插件目标详细描述信息的另外一种方式

mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目标名称 -Ddetail
mvn help:describe -Dplugin=插件前缀 -Dgoal=目标名称 -Ddetail
上面这个命令调用的是help插件的describe这个目标,这个目标可以列出其他指定插件目标的详细信息,看效果:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=org.apache.maven.plugins:maven-surefire-plugin -Dgoal=test -Ddetail [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 --- [INFO] Mojo: 'surefire:test' surefire:test Description: Run tests using Surefire. Implementation: org.apache.maven.plugin.surefire.SurefirePlugin Language: java Bound to phase: test Available parameters: additionalClasspathElements Additional elements to be appended to the classpath. argLine User property: argLine Arbitrary JVM options to set on the command line. skip (Default: false) User property: maven.test.skip Set this to 'true' to bypass unit tests entirely. Its use is NOT RECOMMENDED, especially if you enable it using the 'maven.test.skip' property, because maven.test.skip disables both running the tests and compiling the tests. Consider using the skipTests parameter instead.

可以拿这种和上面获取插件目标参数详情列表对比一下,上面这个更详细一些,参数说明中多了一行User property: 属性名称,这个属性名称可以通过两种方式传递:

  1. mvn命令-D属性名称的方式传递
  2. pom.xml中properties中定义的方式指定。

现在可以大家估计可以知道我们一直用的-Dmaven.test.skip为什么可以跳过测试代码的编译和单元测试的执行了吧。

3、插件前缀

运行插件的时候,可以通过指定插件坐标的方式运行,但是插件的坐标信息过于复杂,也不方便写和记忆,所以maven中给插件定义了一些简捷的插件前缀,可以通过插件前缀来运行指定的插件。
可以通过下面命令查看到插件的前缀:
mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version]
示例效果:

D:\code\IdeaProjects\maven-chat06>mvn help:describe -Dplugin=org.apache.maven.plugins:maven-surefire-plugin [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-help-plugin:3.2.0:describe (default-cli) @ maven-chat06 --- [INFO] org.apache.maven.plugins:maven-surefire-plugin:2.12.4 Name: Maven Surefire Plugin Description: Surefire is a test framework project. Group Id: org.apache.maven.plugins Artifact Id: maven-surefire-plugin Version: 2.12.4 Goal Prefix: surefire

输出中的Goal Prefix:部分对应的就是插件的前缀,上面这个插件的前缀是surefire。
我们使用前缀来运行一下插件感受一下效果:

D:\code\IdeaProjects\maven-chat06>mvn surefire:test [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-surefire-plugin:2.12.4:test (default-cli) @ maven-chat06 --- [INFO] Tests are skipped. [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 0.934 s [INFO] Finished at: 2019-11-18T16:18:42+08:00 [INFO] ------------------------------------------------------------------------

上面通过别名来运行插件maven-surefire-plugin的test目标,是不是简洁了很多。
上面用了很多mvn help:这个命令,这个调用的是maven-help-plugin插件的功能,help是插件的前缀,它的坐标是:

  1. <dependency>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-help-plugin</artifactId>
  4. <version>3.2.0</version>
  5. </dependency>

4、插件绑定

maven只是定义了生命周期中的阶段,而没有定义每个阶段中具体的实现,这些实现是由插件的目标来完成的,所以需要将阶段和插件目标进行绑定,来让插件目标帮助生命周期的阶段做具体的工作,生命周期中的每个阶段支持绑定多个插件的多个目标。
当我们将生命周期中的阶段和插件的目标进行绑定的时候,执行mvn 阶段就可以执行和这些阶段绑定的插件目标。

①maven内置绑定

maven为了让我们不用做任何配置就可以实现一些项目的构建操作,比如运行mvn clean就可以帮我们清理代码,运行mvn install就可以将构件安装到本地仓库,所以maven帮我们做了一些事情,maven内部已经提供了很多默认的插件,而将一些阶段默认和这些插件阶段绑定好了,所以我们不用做任何配置就可以执行清理代码、编译代码、测试、打包、安装到本地仓库、上传到远程仓库等阶段的操作,是因为maven已经默认给这些阶段绑定好了插件目标,所以不需要我们再去配置,就直接可以运行,这些都是maven内置绑定帮我们做的事情,我们来看看maven有哪些内置绑定。

clean生命周期阶段与插件绑定关系
生命周期阶段 插件:目标
pre-clean

| | clean | maven-clean-plugin:clean | | post-clean |

|

clean周期中只有clean阶段默认绑定了maven-clean-plugin插件的clean目标。maven-clean-plugin插件的clean目标作用就是删除项目的输出目录。

default生命周期阶段与插件绑定关系

default生命周期中有23个阶段,我只列出有默认绑定的,其他的没有列出的没有绑定任何插件,因此没有任何实际的行为。

生命周期阶段 插件:目标 执行任务
process-resources maven-resources-plugin:resources 复制主资源文件至主输出目录
compile maven-compiler-plugin:compile 编译主代码至主输出目录
process-test-resources maven-resources-plugin:testResources 复制测试资源文件至测试输出目录
test-compile maven-compiler-plugin:testCompile 编译测试代码至测试输出目录
test maven-surefile-plugin:test 执行测试用例
package maven-jar-plugin:jar 创建项目jar包
install maven-install-plugin:install 将输出构件安装到本地仓库
deploy maven-deploy-plugin:deploy 将输出的构件部署到远程仓库

site生命周期阶段与插件绑定关系
生命周期阶段 插件:目标
pre-site

| | site | maven-site-plugin:site | | post-site |

| | site-deploy | maven-site-plugin:deploy |

②自定义绑定

除了默认绑定的一些操作,我们自己也可以将一些阶段绑定到指定的插件目标上来完成一些操作,这种自定义绑定让maven项目在构件的过程中可以执行更多更丰富的操作。
常见的一个案例是:创建项目的源码jar包,将其安装到仓库中,内置插件绑定关系中没有涉及到这一步的任务,所以需要用户自己配置。
插件maven-source-plugin的jar-no-fork可以帮助我们完成该任务,我们将这个目标绑定在default生命周期的verify阶段上面,这个阶段没有任何默认绑定,verify是在测试完成之后并将构件安装到本地仓库之前执行的阶段,在这个阶段我们生成源码,配置如下:
在maven-chat06中的pom.xml加入如下配置:

  1. <build>
  2. <plugins>
  3. <plugin>
  4. <groupId>org.apache.maven.plugins</groupId>
  5. <artifactId>maven-source-plugin</artifactId>
  6. <version>3.2.0</version>
  7. <executions>
  8. <!-- 使用插件需要执行的任务 -->
  9. <execution>
  10. <!-- 任务id -->
  11. <id>attach-source</id>
  12. <!-- 任务中插件的目标,可以指定多个 -->
  13. <goals>
  14. <goal>jar-no-fork</goal>
  15. </goals>
  16. <!-- 绑定的阶段 -->
  17. <phase>verify</phase>
  18. </execution>
  19. </executions>
  20. </plugin>
  21. </plugins>
  22. </build>

注意上面配置的attach-source,后面输出中会有。
id:任务的id,需唯一,如果不指定,默认为default。
每个插件的配置在pom.xml的plugins元素中只能写一次,否则会有警告。

5.POM.xml插件配置详解

①插件目标共享参数配置

build->plugins->plugin中配置:

  1. <!-- 插件参数配置,对插件中所有的目标起效 -->
  2. <configuration>
  3. <目标参数名>参数值</目标参数名>
  4. </configuration>

configuration节点下配置目标参数的值,节点名称为目标的参数名称,上面这种配置对当前插件的所有目标起效,也就是说这个插件中所有的目标共享此参数配置。
到此为止,跳过测试已经讲了3种了:

  1. mvn -Dmaven.test.skip=tue
  2. properties中配置true
  3. build中配置插件参数的方式

上面这个配置参数方式对当前插件的所有目标有效,如果想对指定的目标进行配置呢,用下面的方式。

②插件目标参数配置

project->build->plugins->plugin->executions->execution元素中进行配置,如下:

  1. <!-- 这个地方配置只对当前任务有效 -->
  2. <configuration>
  3. <目标参数名>参数值</目标参数名>
  4. </configuration>

上面这种配置常用于自定义插件绑定,只对当前任务有效。

③获取maven插件信息

上面我们介绍了,可以通过下面命令获取插件详细介绍信息
mvn help:describe -Dplugin=插件goupId:插件artifactId[:插件version] -Dgoal=目标名称 -Ddetail
mvn help:describe -Dplugin=插件前缀 -Dgoal=目标名称 -Ddetail
更多maven插件的帮助文档可以参考maven的官方网站,上面有详细的介绍,建议大家去看看,地址:
http://maven.apache.org/plugins/image.png

6.插件解析机制

为了方便用户使用和配置插件,maven不需要用户提供完整的插件坐标信息,就可以解析到正确的插件,不过我建议使用插件配置的时候最好还是配置完整的坐标信息,不然不利于新人的理解和问题的排查。

①插件仓库

与其他maven构件一样,插件构件也是基于坐标存储在maven仓库中,有需要的时候,maven会从本地查找插件,如果不存在,则到远程仓库查找,找到了以后下载到本地仓库,然后使用。
大家回忆一下,上一章讲过的,pom.xml中可以配置依赖的构件的仓库地址,如下:

  1. <repositories>
  2. <repository>
  3. <id>maven-nexus</id>
  4. <url>http://localhost:8081/repository/maven-public/</url>
  5. <releases>
  6. <enabled>true</enabled>
  7. </releases>
  8. <snapshots>
  9. <enabled>true</enabled>
  10. </snapshots>
  11. </repository>
  12. </repositories>

但是插件仓库的配置和这个有一点不一样,插件的是在pluginRepositories->pluginRepository元素中配置的,如下:

  1. <pluginRepositories>
  2. <pluginRepository>
  3. <id>myplugin-repository</id>
  4. <url>http://repo1.maven.org/maven2/</url>
  5. <releases>
  6. <enabled>true</enabled>
  7. </releases>
  8. </pluginRepository>
  9. </pluginRepositories>

看一下上面2段配置,repository中的配置和pluginRepository中的子元素是一样的,这个主意下就可以了。

②插件的默认groupId

在pom.xml中配置插件的时候,如果是官方的插件,可以省略groupId。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.javacode2018</groupId>
  7. <artifactId>maven-chat06</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <packaging>jar</packaging>
  10. <properties>
  11. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  12. </properties>
  13. <build>
  14. <plugins>
  15. <plugin>
  16. <artifactId>maven-compiler-plugin</artifactId>
  17. <version>3.1</version>
  18. <configuration>
  19. <compilerVersion>1.8</compilerVersion>
  20. <source>1.8</source>
  21. <target>1.8</target>
  22. </configuration>
  23. </plugin>
  24. </plugins>
  25. </build>
  26. </project>

上面用到了maven-compiler-plugin,这个插件是编译代码的,是maven官方提供的插件,我们省略了groupId。
上面这个插件用于编译代码的,编译代码的时候需要指定编译器的版本,源码的版本,目标代码的版本,都是用的是1.8。
大家回头去看一下,文章最开始的时候,在properties中有几个属性值是1.8的配置,这几个值默认会被maven-compiler-plugin这个插件的上面3个参数获取,具体可以去看一下这个插件compile目标的参数说明。
运行下面命令:

D:\code\IdeaProjects\maven-chat06>mvn clean compile [INFO] Scanning for projects... [INFO] [INFO] -------------------< com.javacode2018:maven-chat06 >-------------------- [INFO] Building maven-chat06 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ maven-chat06 --- [INFO] Deleting D:\code\IdeaProjects\maven-chat06\target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ maven-chat06 --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-chat06 --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to D:\code\IdeaProjects\maven-chat06\target\classes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.692 s [INFO] Finished at: 2019-11-18T18:11:34+08:00 [INFO] ------------------------------------------------------------------------

上面pom.xml省略了插件的groupId配置,如下:
org.apache.maven.plugins
maven在解析该插件的时候,会自动给这个插件补上默认的官方的groupId,所以可以正常运行,但是不建议大家这么使用,容易让新手比较懵逼。

③插件前缀的解析

前面说过了使用mvn命令调用插件的时候,可以使用插件的前缀来代替繁琐的插件坐标的方式,那么maven是如何根据插件的前缀找到对应的插件的呢?
插件前缀与插件groupId:artifactId是一一对应的关系,这个关系的配置存储在仓库的元数据中,元数据位于下面2个xml中:
~/.m2/repository/org/apache/maven/plugins/maven-metadata-central.xml
~/.m2/repository/org/codehaus/mojo/maven-metadata-central.xml
也可以通过在settings.xml中配置,让maven检查其他grouId上的插件元数据中前缀和插件关系的配置,如下:

  1. <settings>
  2. <pluginGroups>
  3. <pluginGroup>com.your.plugins</pluginGroup>
  4. </pluginGroups>
  5. </settings>

pluginGroups中有多个pluginGroup,可以配置你自己插件的元数据所在的groupId,然后可以通过前缀来访问你自己的插件元数据目录,此处先不细说,这个后面文章中讲自定义插件的时候会再次说明。

7.查看项目最终pom.xml文件

我们的pom.xml默认会继承maven顶级的一个父类pom.xml,顶级的pom.xml中指定了很多默认的配置,如生命周期中的阶段和很多插件的绑定,这些如果我们想看到,到哪里看呢?
mvn命令在项目中执行的时候,我们的pom.xml和父类的pom.xml最终会进行合并,当我们的pom.xml写的比较复杂的时候,最终合并之后是什么效果呢,我们可以通过下面这个命令查看:
mvn help:effective-pom

三、仓库

  • 本地仓库:在当前电脑上,为电脑上所有 Maven 工程服务
  • 远程仓库:需要联网
    • 局域网:我们自己搭建的 Maven 私服,例如使用 Nexus 技术。
    • Internet
      • 中央仓库
      • 镜像仓库:内容和中央仓库保持一致,但是能够分担中央仓库的负载,同时让用户能够就近访问提高下载速度,例如:Nexus aliyun

建议:不要中央仓库和阿里云镜像混用,否则 jar 包来源不纯,彼此冲突。
专门搜索 Maven 依赖信息的网站:https://mvnrepository.com/