Maven

249 阅读22分钟

简介

为什么要学习Maven

maven作为依赖管理工具

  • jar包的规模
    • 随着我们使用越来越多的框架,或者框架封装程度越来越高,项目中使用的jar包也越来越多。项目中,一个模块里面用到上百个jar包是非常正常的。
  • jar包的来源
    • 这个ar包所属技术的官网。官网通常是英文界面,网站的结构又不尽相同,甚至找到下载链接还发现需要通过特殊的工具下载。
    • 第三方网站提供下载。问题是不规范,在使用过程中会出现各种问题。
      • jar包的名称
      • jar包的版本
      • jar包内的具体细节
    • 而使用 Maven 后,依赖对应的 jar 包能够自动下载,方便、快捷又规范
  • jar 包之间的依赖关系
    • 框架中使用的 ar 包,不仅数量庞大,而且彼此之间存在错综复杂的依赖关系。依赖关系的复杂程度,已经上升到了完全不能靠人力手动解决的程度。另外,jar 包之间有可能产生冲突。进一步增加了我们在 jar 包使用过程中的难度。

Maven作为构建管理工具

  • 你没有注意过的构建
    • 你可以不使用 Maven,但是构建必须要做。当我们便用IDEA 进行开发时,构建是IDEA 替我们做的。
  • 脱离 IDE 环境仍需构建

总结

  • 管理规模庞大的jar包,需要专门工具
  • 脱离IDE环境执行构建操作,需要专门工具

什么是Maven

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

构建

  • Java项目开发过程中,构建指的是使用原材料生产产品的过程
    • 原材料
      • Java源代码
      • 甚于HTML的Thymeleaf文件
      • 图片
      • 配置文件
      • ...
    • 产品
      • 一个可以在服务器上运行的项目
  • 构建过程包含的主要的环节:
    • 清理: 删除上一次构建的结果,为下一次构建做好准备
    • 编译: Java源程序编译成*.class字节码文件
    • 测试:运行提前准备好的测试程序
    • 报告:针对刚才测试的结果生成一个全面的信息
    • 打包
      • Java工程: jar包
      • Web工程: war包
    • 把一个 Maven 工程经过打包操作生成的 jar 包或 war 包存入Maven 仓库
    • 部署
      • 部署jar包,把一个jar包部署到Nexus私服服务器上
      • 部署war包,借助相关Maven插件(例如cargo),将war包部署到Tomcat服务器上

依赖

  • 如果A工程里面用到了B工程的类、接口、配置文件等等这样的资源,那么我们就可以说A依赖B。
    • junit-4.12依赖hamcrest-core-1.3
    • thymeleaf-3.0.12.RELEASE依赖ognl-3.1.26
      • ognl3.1.26依赖javassist-3.20.0-GA
    • thymeleaf-3.0.12.RELEASE依赖attoparser-2.0.5.RELEASE
    • thymeleaf-3.0.12RELEASE依赖unbescape-1.1.6.RELEASE
  • 依赖管理中要解决的具体问题:
    • jar 包的下载: 使用 Maven 之后,jar 包会从规范的远程仓库下载到本地
    • jar 包之间的依赖: 通过依赖的传递性自动完成
    • jar 包之间的冲突:通过对依赖的配置进行调整,让某些jar包不会被导入
  • Maven的工作机制

Maven安装

Maven核心程序解压和配置

  • 下载、解压
  • 配置
    • conf/settings.xml
  • 指定本地仓库
    • 本地仓库默认值: 用户家目录/.m2/repository。由于本地仓库的默认位置是在用户的家目录下,而家目录往往是在C盘,也就是系统盘。将来 Maven 仓库中jar 包越来越多,仓库体积越来越大,可能会拖慢C 盘运行速度,影响系统性能。所以建议将 Maven 的本地仓库放在其他盘符下。配置方式如下:
      <!-- 7ocalRepository
       | The path to the local repository maven will use to store artifacts.
       | Default: $(user .home]/.m2/repository
      <localRepository>/path/to/local/repo</localRepository>
      --!>
      <localRepository>D: maven-repository</1ocalRepository>
    
    • 本地仓库这个目录,我们手动创建一个空的目录即可.
    • 记住:一定要把-localRepository 标签从注释中拿出来
    • 注意:本地仓库本身也需要使用一个非中文、没有空格的目录
  • 配置阿里云提供的镜像仓库
  • 配置环境变量
    • Maven 下载 jar 包默认访问境外的中央仓库,而国外网站速度很慢。改成阿里云提供的境像仓库,访问国内网站可以让 Maven 下载jar 包的时候速度更快。配置的方式是:将下面 mirror 标签整体复制到 settings.xml文件的 mirrors 标签的内部。
    • 将原有的例子配置注释
    • 加入我们自己的配置
      <mirrors>
          <mirror>
              <id>nexus-aliyun</id>
              <mirrorof>central</mirrorof>
              <name>Nexus aliyun</name>
              <ur1>http://maven.aliyun.com/nexus/content/groups/public</ur1>
           </mirror>
      </mirrors>
    
  • 配置Maven工程的基础JDK版本
    • 如果按照默认配置运行,Java工程使用的默认JDK 版本是 1.5,而我们熟悉和常用的是JDK 1.8-版本。修改配置的方式是: 将 profile 标签整个复制到 settings.xml文件的 profiles 标签内。
      <profiles>
          <profile>
              <id>jdk-1.8</id>
              <activation>
                  <activeByDefault>true</activeByDefault>
                  <jdk>1.8</jdk>
              </activation>
              <properties>
                  <maven.compiler.source>1.8</maven.compiler.source>
                  <maven.compiler.target>1.8</maven.compiler.target>
                  <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
              </properties>
          </profile>
      </profiles>
    

配置环境变量

  • Maven 是一个用Java 语言开发的程序,它必须基于]DK 来运行,需要通过JAVA HOME 来找到]JDK 的安装位置。
  • 配置MAVEN_HOME
    • 配置环境的规律
      • XXX_HOME通常指向的是bin目录的上一级
      • PATH指向的是bin目录
  • 配置PATH
  • 验证
    • mvn -v

Maven命令行

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

  • Maven核心概念:Maven坐标
    • 数学中的坐标:x,y,z
    • Maven中的坐标
      • 向量说明:使用三个向量在Maven的仓库中唯一定位到一个jar包
        • groupId:公司或者组织id
        • artifactId:一个项目或者是项目中的一个模块id
        • version:版本号
      • 三个向量的取值方式
        • groupId:公司或者组织域名的倒叙,通常也会加上项目名称
          • 例如:com.atguigu.maven
        • artifactId:模块的名称,将来作为Maven工程的工程名
        • version:模块的版本号,根据自己的需要设定
          • 例如:SNAPSHOT表示闪照版本,在迭代过程中,不稳定的版本
          • 例如:RELEASE表示正式版本
        • 举例
          • groupId:com.atguigu.maven
          • artifactId:pro01-atguigu-maven
          • vesion:1.0.0.SNAPSHOT
      • 坐标和仓库中jar包的存储路径之间的对象关系
        • 坐标
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        
        • 上面坐标对应的jar包在Maven本地仓库中的位置
        • Maven本地仓库目录\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar
        • 一定要学会根据坐标到本地仓库中找到对应的jar包
  • 实验操作
    • 创建目录作为后面操作的工作空间
      • 此时我们已经有了三个目录
        • Maven核心程序:中军大帐
        • Maven本地仓库:兵营
        • 本地工作空间:战场
    • 在工作空间目录下打开命令行窗口
    • 使用命令生成Maven工程
      • mvn archetype:generate命令
    • 调整
    • Maven默认生成的工程,对junit依赖的是较低的3.8.1版本,我们可以改成较合适的4.12版本
    • 自动生成的App.java和AppTest.java可以删除
      <!-- 依赖配置信息 -->
      <!-- dependencies复数标签,里面包含dependency单数标签 -->
      <dependencies>
          <!-- dependency单数标签:配置一个具体的依赖 -->
          <dependency>
              <!-- 通过坐标来依赖其他jar包 -->
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.12</version>
              <!-- 依赖范围 -->
              <scope>test</scope>
          </dependency>
      </dependencies>
    
    • 自动生成的pom.xml解读
        <!-- 根标签: project,表示对当前工程进行配置、管理-->
        <project xmlns="http://maven,apache.org/POM/4.0.0" xmlns:xsi="http://wwww3,orq/2001/XMISchema-instance" xsi:schemalocation="http://maven.apache.org/POM/4.0.0http://maven,apache ,org/xsd/maven-4.0.0.xsd">
        <!-- modelVersion 标签: 从 Maven 2 开始就固定是 4.0.0 -->
        <!-- 代表当前pom.xml所采用的的标签结构 -->
        <modelVersion>4.0 .0</modelVersion>
        <!-- gav坐标信息 -->
        <!-- groupId标签:坐标向量之一:代表公司或组织开发的某一个项目 -->
        <groupId>com.atguigu.maven</groupId>
        <!-- artifactId标签:坐标向量之一:代表项目下的某一个模块 -->
        <artifactId>pro01-maven-java</artifactId>
        <!-- version标签:坐标向量之一:代表当前模块的版本 -->
        <version>1.0-SNAPSHOT</version>
        <!-- packaging标签:打包方式 -->
        <!-- 取值jar:生成jar包,说明这是一个java工程 -->
        <!-- 取值war:生成war包,说明这是一个web工程 -->
        <!-- 取值pom:说明这个工程是用来管理其他工程的工程 -->
        <packaging>jar</packaging>
        <name>pro01-maven-java</name>
        <ur1>http://mayen apache. orq</ur1>
        <!-- properties标签:在Maven中定义属性值 -->
        <properties>
            <!-- 在构建过程中读取源码时使用的字符集 -->
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        <!-- dependencies 标签: 配置具体依赖信息,可以包含多个dependency子标签 -->
        <dependencies>
            <!-- dependency标签:配置一个具体的依赖信息 -->
            <dependency>
                <!-- 坐标信息:导入哪个jar包,就是配置它的坐标信息即可 -->
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <!-- scope标签:配置当前依赖的范围 -->
                <scope>test</scope>
            </dependency>
        </dependencies>
    
  • Maven核心概念:POM
    • 含义
      • POM: Project Object Model,项目对象模型。和POM 类似的是: DOM (Document Objet Model),文档对象模型。它们都是模型化思想的具体体现
    • 模型化思想
      • POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了,我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与现实事物相关的数据。
    • 对应的配置文件
      • POM 理念集中体现在 Maven 工程根目录下 pom,xml 这个配置文件中。所以这个 pom.xml配置文件就是Maven工程的核心配置文件,其实学习Maven就是学这个文件怎么配置,各个配置有什么用。
  • Maven核心概念:约定的目录结构
    • 各个目录的作用
        pro01-maven-java
            |-src                    -->源码目录
                |-main               -->主题程序目录
                    |-java           -->java源代码
                        |-com        -->package目录
                    |-resources      -->配置文件
                |-test               -->测试程序目录
                    |-java           -->java源代码
                        |-com        -->package目录
            |-target
        //另外还有一个target目录存放构建操作输出的结果
    
    • 约定目录结构的意义
      • Maven 为了让构建过程能够尽可能自动化完成,所以必须约定目录结构的作用。例如: Maven 执行编译操作,必须先去Java 源程序目录读取Java 源代码,然后执行编译,最后把编译结果存放在 target 目录
    • 约定大于配置
      • Maven 对于目录结构这个问题,没有采用配置的方式,而是基于约定,这样会让我们在开发过程中非常方便。如果每次创建Maven工程后,还需要针对各个目录的位置进行详细的配置,那肯定非常麻烦。
      • 目前开发领域的技术发展趋势就是: 约定大于配置,配置大于编码。

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

  • 主体程序
  pro01-maven-java             -->工程根目录
      |-src                    -->源码目录
          |-main               -->主题程序目录
              |-java           -->java源代码
                  |-com        -->package目录   -->{
                      |-atguigu                 --> java程序中的package部分
                          |-maven               -->} 类放在此目录下
              |-resources      -->配置文件    

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

  • 要求
    • 运行 Maven 中和构建操作相关的命令时,必须进入到 pomxml 所在的目录。如果没有在 pomxml 所在的目录运行 Maven 的构建命令,那么会看到下面的错误信息:
        The goal you specified requires a project to execute but there is no poM in thisdirectory
        //mvn -v 命令和构建操作无关,只要正确配置了 PATH,在任何目录下执行都可以。而构建相关的命令要在pom.xml所在的目录下运行,操作哪个工程,就进入这个工程的pom.xml目录
    
  • 清理操作
    • mvn clean
      • 效果:删除target目录
  • 编译操作
    • 主程序编译:mvn compile
    • 测试程序编译:mvn test-compile
    • 主体程序编译结构存放的目录:target/classes
    • 测试程序编译结果存放的目录:target/test-classes
  • 测试操作
    • mvn test
    • 测试的报告存放的目录:target/surefire-reports
  • 打包操作
    • mvn package
    • 打包结果--jar包,存放的目录:target
  • 安装操作
    • mvn install
      • 安装的效果是将本地构建过程中生成的jar包存入Maven本地仓库。这个jar包在 Mave:仓库中路径是根据它的坐标生成的。
      • 坐标信息如下:
          <groupId>com.atguigu .maven</groupId>
          <artifactId>pro01-maven-java</artifactId>
          <version>1.0-SNAPSHOT</version>
      
      • 在Maven仓库中生成的路径如下;
          D:maven-rep1026\com\atguigu\maven\pro01-maven-java\1.0-SNAPSHOT\pro01-maven-java-1.0-1SNAPSHOT.jar
      
      • 另外,安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地合库。所以我们在 Maven 的本地合库中想看一个iar 包原始的 pom.xml 文件时,查看对应 XXXpom 文件即可,它们是名字发生了改变,本质上是同-一个文件。

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

  • 说明
    • 使用 mvn archetype:generate 命令生成Web工程时,需要使用一个专门的archetype。这个专门生成 Web工程骨架的archetype可以参照官网看到它的用法:
    • Maven Webapp Archetype
      • maven-archetype-webapp is an archetype which generates a sample Maven webapp project
    project
    |-- pom.xml
    |-- src
        |-- main
            |-- webapp
                |-- WEB-INF
                    |--web.xml
                |-- index.jsp
    
    • Usage
      • To generate a new project from this archetype, type.
      • mn archetype:generate -DarchetypeGroupld-org-apachemaen.archetypes -DarchetypeArtifactId-maven-archetype-webapp -Darchetypeversion-1.4
  • 操作
    • 注意: 如果在上一个工程的目录下执行 mvn archetype:generate 命令,那么 Maven 会报错:不能在一个非 pom的工程下再创建其他工程。所以不要再刚才创建的工程里再创建新的工程,请回到工作空向根目录来操作。
  • 生成的pom.xml
    • 确认打包的方式是war包形式
      <packaging>war</packaging>
    
  • 生成的Web工程的目录结构
|-- pro02-maven-web
    |-- pom.xml
    |-- src
        |-- main
            |-- webapp
                |--index.jsp
                |-- WEB-INF
                    |-- web.xml
  • 创建Servlet
    • 在main目录下创建java目录
    • 在java目录下创建Servlet类所在的包的目录
    • 在包下创建Servlet类
      packagecom.atquigu.maven;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpservletRequest ;
      import javax.servlet.http.HttpServletResponse ;
      import javax.servlet.ServletException;
      import java.io.IOException;
      
      public class HelloServlet etextends HttpServlet{
          protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException,IOException {
              response.getWriter() .write("hello maver web") ;
          }
      }
    
    • 在web.xml中注册Servlet
      <web-app>
          <display-name>Archetype Created Web Application</display-name>
          <servlet>
              <servlet-name>helloServlet</servlet-name>
              <servlet-class>com,atquiqu,maven,HelloServlet</servlet-class>
          </servlet>
          <servlet-mapping>
              <servlet-name>helloservlet</servlet-name>
              <url-pattern>/helloServlet</url-pattern>
          </servlet-mapping>
      </web-app>
    
  • 在index.jsp页面编写超链接
    • JSP全称是Java Server Page,和 Thymeleaf一样,是服务器端页面染技术,这里我们不必关心JSP 语法细节编写一个超链接标签即可。
    <html>
        <body>
            <h2>He1lo world!</h2>
            <a href="hlloServlet">Access Servlet</a>
        </body>
    </html>
  • 编译
    • 此时直接执行mvn compile命令出错
    • 缺少servlet-api.jar
  • 配置
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
  • 将Web工程打包为war包
    • 运行mvn package命令,生成war包的位置在target下
  • 将war包部署到Tomcat上运行
    • 将war包复制Tomcat/webapps目录下

实验五:让Web工程依赖Jave工程

  • 观念
    • 明确一个意识:从来只有Web工程依赖Java工程,没有反过来Java工程依赖Web工程。本质上来说,Web工程依赖的]avax工程其实就是Web工程里导入的jar包。最终Java工程会变成jar包,放在Web工程的WEB-INF/lib目录下。
  • 操作
    • 在 pro02-maven-web工程的pom.xml中,找到dependencies标签,在 dependencies标签中做如下配置:
      <!-- 配器对Java工程pro01-maven-java的依赖 -->
      <!-- 具体的配置方式: 在dependency标签内使用坐标实现依赖
      <dependency>
          <groupId>com.atguigu.maven</groupId>
          <artifactId>pro01-maven-java</artifactId>
          <version>1.0-SNAPSHOT</version>
      </dependency>
    
  • 在Web工程中,编写测试代码
    • 补充创建目录
      • pro02-maven-web\src\test\java\com\atguigu\maven
    • 确认Web工程依赖了junit
    • 创建测试类
  • 执行Maven命令
    • 测试命令
      • mvn test
      • 说明:测试操作会提前自动执行编译操作,测试成功就说明编译也是成功的。
    • 打包命令
      • mvn package
      • 通过查看war包内的结构,我们看到Web工程依赖的Jave工程确实会变成Web工程的WEB-INF/lib目录下的jar包
    • 查看当前Web工程所依赖的jar包的列表
      • mvn dependency:list
      • 这样的格式虽然和我们XML配置文件中坐标的格式不同,但是本质上还是坐标信息,大家需要能够认识这样的格式,将来从Maven命令的日志或错误信息中看到这样格式的信息,就能够识别出来这是坐标。进而根据坐标到Maven仓库找到对应的iar包,用这样的方式解决我们遇到的报错的情况
    • 以树形结构查看当前 Web 工程的依赖信息
      • mvn dependency:tree
      • 我们在pomxml中并没有依赖hamcrest-core,但是它却被加入了我们依赖的列表。原因是: junit 依赖了hamcrest-core,然后基于依赖的传递性,hamcrest-core被传递到我们的工程了

实验六:测试依赖的范围

  • 依赖范围
    • 标签的位置: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包产生冲突,同时减轻服务器的负担,说白了就是“服务器上已经有了,你就别带了”

实验七:依赖传递性

  • 依赖的传递性
    • 概念
      • A 依赖 B,B 依赖 C,那么在A没有配置对 C 的依的情况下,A里面能不能直接使用 C?
    • 传递的原则
      • 在 A 依赖 B,B 依赖 C 的前提下,C 是否够传递到 A,取决于 B 依赖 C 时使用的依赖范围。
        • B 依赖 C时使用 copile 范国: 可以传递
        • B 依赖 C 时使用 test 或 provided 范国: 不能传递,所以需要这样的 ar 包时,就必须在需要的地方明确和依赖才可以。
  • 使用compile范围依赖spring-core

实验八:依赖的排除

  • 概念
    • 当A依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A不想要,需要在 A 里面把C 排除掉。而往往这种情况都是为了避免Jar 包之间的冲奕。
    • 所以配置依赖的排除其实就是阻止某些 ar 包的传递,因为这样的 ar 包传递过来会和其他 jar 包冲突
  • 配置方式
<dependency>
    <groupId>com.atguigu.maven</groupId>
    <artifactId>pro01-maven-java</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>compiTe</scope>
    <!-- 使用excludes标签配置依赖的排除 -->
    <exclusions>
    <!-- 在exclude标签中配置一个具体的排除 -->
        <exclusion>
        <!-- 指定要排除的依赖的坐标不需要写version -->
            <groupId>commons-logging</groupId>
            <artifactid>commons-logging</artifactid>
        </exclusion>
    </exclusions>
</dependency>
  • 测试
    • 测试方式,在pro02-maven-web工程中配置对commons-logging的排除

实验九:继承

概述

  • 概念
    • Maven工程之间,A工程继承B工程
      • B 工程: 父工程
      • A 工程: 子工程
    • 本质上是 A工程的 pom.xml 中的配置继承了 B 工程中 pom.xml 的配置
  • 作用
    • 在父工程中统一管理项目中的依赖信息,具体来说是管理依赖信息的版本
    • 它的背景是:
      • 对一个比较大型的项目进行了模块拆分
      • 一个 project下面,创建了很多个 module。
      • 每一个module 都需要配置自己的依赖信息
    • 它背后的需求是:
      • 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理
      • 使用同一个框架内的不同jar 包,它们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
      • 使用框架时所需要的jar包组合(或者说依赖信息组合) 需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的项目中重新摸索
    • 通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的jar包:又能够将以往的经验沉淀下来,节约时间和精力。
  • 举例
    • 在一个工程中依赖多个Spring的jar包
    • 使用 Spring 时要求所有 Spring 自己的 ar 包版本必须一致。为了能够对这些 jar 包的版本进行统一管理,我们使用继承这个机制,将所有版本信息统一在父工程中进行管理。

创建父子工程

  • 操作
    • 创建父工程
      • 创建的过程和前面创建 pro01-maven-java 一样.
      • 工程名称: pro03-maven-parent
      • 工程创建好之后,要修改它的打包方式
      <groupId>com.atquigu.maven</groupId>
      <artifactId>pro03-maven-parent</artifactId>
      <version>1.0-SNAPSHOT</version>
      <!-- 当前工程作为父工程,它要去管理子工程,所以打包方式必须是pom -->
      <packaging>pom</packaging>
      
      • 只有打包方式为 pom 的 Maven 工程能够管理其他 Maven 工程。打包方式为 pom 的 Maven 工程中不写业务代码,它是专门管理其他Maven工程的工程
    • 创建模块工程
      • 模块工程类似于IDEA中的module,所以需要进入pro03-maven-parent工程的根目录,然后运行mvn archetype:generate 命令来创建模块工程
      • 假设,我们创建三个模块工程
        • pro04-maven-module
        • pro05-maven-module
        • pro06-maven-module
    • 查看被添加新内容的父工程pom.xml
      • 下面modules和module标签是聚合功能的配置
      <!-- 聚合配置 -->
      <modules>
          <module>pro04-maven-module</module>
          <module>pro05-maven-module</module>
          <module>pro06-maven-moduTe</module>
      </modules>
      
      <parent>
          <!-- 通过指定父工程的坐标找到父工程-->
          <groupId>com.atguigu.maven</groupId>
          <artifactId>pro03-maven-parent</artifactId>
          <version>1.0-SNAPSHOT</version>
      </parent>
      <!-- 子工程的groupId如果和父工程一样,则可以省略 -->
      <!-- 子工程的version如果和父工程一样,则可以省略 -->
      

在父工程管理依赖

<!-- 在父工程中统一管理依赖信息 -->
<!-- 注意:即使在父工程配置了对依赖的管理,子工程需要具体哪一个还需要明确配置 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springf amework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.spr ingframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>orgspringframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>

实验十:聚合

  • 聚合本身的含义
    • 部分组成整体
  • Maven中的聚合
    • 便用一个"总工程"将各个“模块工程”汇集起来,作为一个整体对应完整的项目
      • 项目: 整体
      • 模块: 部分
    • 概念的对应关系:
      • 从继承关系角度来看:
        • 父工程
        • 子工程
      • 从聚合关系角度来看:
        • 总工程
        • 模块工程
  • 好处
    • 一键执行 Maven 命令: 很多构建命令都可以在”总工程”中一键执行
    • 以 mvn install 命令为例: Maven 要求有父工程时先安装父工程,有依赖的工程时,先安装被依赖的工程,我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行
    • 配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然
  • 聚合的配置
    • 在总工程中配置modules即可
      <modules>
          <module>pro04-maven-module</module>
          <module>pro05-maven-module</module>
          <module>pro06-maven-moduTe</module>
      </modules>
    
  • 依赖循环问题
    • 如果 A 工程依赖 B 工程,B 工程依 C 工程,C 工程又反过来依赖A 工程,那么在执行构建操作时会报下面的错误:
      • (ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
        • 这个错误的含义是: 循环引用

IDEA

其他核心概念

声明周期

  • 作用
    • 为了让构建过程自动化完成,Maven 设定了三个生命周期,生命周期中的每一个环节对应构建过程中的一个操作。
  • 三个生命周期
生命周期名称作用各个环节
Clean清理操作pre-clean
clean
post-clean
Site生成站点相关pre-site
site
post-site
deploy-site
Default主要构建过程validate
generate-sources
process-sources
generate-resources
process-resources 复制并处理资源文件,至目标目录,准备打包。
compile 编译项目的源代码。
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources 复制并处理资源文件,至目标测试目录。
test-commpile 编译测试源代码。
process-test-classes
test 使用合适的单元测试框架运行测试。这些测试代码不会被打包或部苦prepare-package
package 接受编译好的代码,打包成可发布的格式,如AR。
pre-integration-test
integration-test
post-integration-test
verify
install将包安装至本地仓库,以让其它项目依赖。
deploy将最终的包复制到远程的仓库,以让其它开发人员与项目共享或部署到服务器上运行。(需要借助插件,例如:cargo)

插件和目标

  • 生命周期定义的是一个抽象的标准,从逻辑上定义我们要干什么事,插件是具体实现,插件的目标是一个具体的功能
  • 插件
    • Maven的核心程序仅仅负责宏观调度,不做具体工作。具体工作都是由Maven插件完成的。例如: 编译就是由maven-compiler-plugin-3.1.jar 插件来执行的。
  • 目标
    • 个插件可以对应多个目标,而每一个目标都和生命周期中的某一个环节对应。
    • Default 生命周期中有compile 和 test-compile 两个和编译相关的环节,这两个环节对应 compile 和 testcompile 两个目标,而这两个目标都是由 maven-compiler-plugin-3.1.jar 插件来执行的

仓库

  • 本地仓库: 在当前电脑上,为电脑上所有 Maven 工程服务
  • 远程仓库: 需要联网
    • 局域网: 我们自己搭建的 Maven 私服,例如使用 Nexus 技术
    • Internet
      • 中仓库
      • 镜像仓库:内容和中央仓库保持一致,但是能够分担中央仓库的负载,同时让用户能够就近访问提高下药速度,例如: Nexus aliyun
  • 建议: 不要中央仓库和阿里云镜像混用,否则 jar 包来源不纯,彼此冲突。
  • 专门搜索 Maven 依赖信息的网站: mvnrepository.com/

POM深入

Maven是一款项目管理工具-重新认识Maven

  • Maven 的完整功能
    • 在入门的时候我们介绍说 Maven 是一款[构建管理] 和[依赖管理]的工具。但事实上这只是 Maven 的一部分功能。Maven 本身的产品定位是一款[项目管理工具]。
  • 项目管理功能的具体体现
    • 下面是 spring-boot-starter的 POM 文件,可以看到: 除了我们熟悉的坐标标签、dependencies 标签,还有description、url,organization、licenses,developers、scm、issueManagement 等这些描计项目信息的标签
  • 所以从[项目管理]的角度来看,Maven 提供了如下这些功能:
    • 项目对象模型(POM): 将整个项目本身抽象、封装为应用程序中的一个对象,以便于管理和操作。
    • 全局性构建逻辑重用: Maven 对整个构建过程进行封装之后,程序员只需要指定配置信息即可完成构建。让构建过程从 Ant 的[编程式] 升级到了 Maven 的[声明式]。
    • 构件的标准集合: 在 Maven 提供的标准框架体系内,所有的构件都可以按照统一的规范生成和使用。
    • 构件关系定义: Maven 定义了构件之间的三种基本关系,让大型应用系统可以使用 Maven 来进行管理
      • 继承关系:通过从上到下的继承关系,将各个子构件中的重复信息提取到父构件中统一管理
      • 聚合关系:将多个构件聚合为一个整体,使于统一操作
      • 依赖关系:Maven 定义了依赖的范用、依赖的传递、依赖的排除、版本仲裁机制等一系列规范和标准让大型项目可以有序容纳数百甚至更多依赖
    • 插件目标系统: Maven 核心程序定义抽象的生命周期,然后将插件的目标绑定到生命周期中的特定阶段,实现了标准和具体实现解耦合,让 Maven 程序极具扩展性
    • 项目描述信息的维护: 我们不仅可以在 POM 中声明项目描述信息,更可以将整个项目相关信息收集起来生成HTML 页面组成的一个可以直接访问的站点。这些项目描述信息包括:
      • 公司或组织信息
      • 项许可证
      • 开发成员信息
      • issue 管理信息
      • SCM 信息

POM的四个层次

  • 超级POM
    • 经过我们前面的学习,我们看到 Maven 在构建过程中有很多默认的设定。例如: 源文件存放的目录、测试源文件存放的目录、构建输出的目录....等等。但是其实这些要素都是被 Maven 定义过的,定义的位置就是,超级POM。
  • 关于超级 POM,Maven 官网是这样介绍的:
    • The Super POM is Maven's default POM. All POMs extend the Super POM unless explicitly set, meaningthe configuration specified in the Super POM is inherited by the POMs you created for your projects.
    • 译文: Super POM 是Maven 的默认 POM,除非明确设置,否则所有 POM 都广展 Super POM,这意味亏Super POM 中指定的配置由您为项目创建的 POM 继承
    • 所以我们自己的 POM 即便没有明确指定一个父工程(父 POM),其实也默认继承了超级 POM。就好比一个lava 类默认继承了 Object 类
  • 父POM
    • 和Java 类一样,POM 之间其实也是单继承的。
  • 有效POM
    • 概念
      • 有效 POM 英文翻译为 effective POM,它的概念是这样的--在 POM 的继承关系中,子POM 可以夏益父 POM中的配置;如果子 POM 没有覆盖,那么父 POM 中的配置将会被继承。按照这个规则,继承关系中的所有 POM叠加到一起,就得到了一个最终生效的 POM,显然 Maven 实际运行过程中,执行构建操作就是按照这个最终生效的 POM 来运行的。这个最终生效的 POM 就是有效 POM,英文叫effective POM。
    • 查看有效POM
      • mvn help:effective-pom
  • 小结
    • 综上所述,平时我们使用和配置的 POM 其实大致是由四个层次组成的
      • 超级 POM: 所有 POM 默认继承,只是有直接和间接之分
      • 父 POM: 这一层可能没有,可能有一层,也可能有很多层
      • 当前 pom.xml配置的 POM: 我们最多关注和最多使用的一层
      • 有效 POM: 隐含的一层,但是实际上真正生效的一层。

属性的声明与医用

  • help 插件的各个目标
目标说明
help:acvite-profiles列出当前已激活的profile
help:all-profiles列出当前工程所有可用profile
help:describe描述一个插件和/或Mojo的属性
help:effective-pom以XML格式展示有效POM
help:effective-settings为当前工程以XML格式展示计算得到的settings配置
help:evaluate计算用户在交互模式下给出的Maven表达式
help:system显示平台详细信息列表,如系统属性和环境变量
  • 使用help:evaluate查看属性值
    • 定义属性
      <properties>
          <com.atguigu.hello></com.atguigu.hello>
      </properties>
    
    • 运行命令
      • mvn help:evaluate
  • 通过Maven访问系统属性
    • java系统属性一览
      • java代码
      Properties properties = System.getProperties();
      Set<Object> propNameSet = properties.keySet ();
      for(object propName : propNameset) {
          string propValue = properties.getproperty((string) propName);
          System.out.printIn(propName + " = " + propValue);
      }
      
    • 使用Maven访问系统属性
    • 访问列表标签
      • ${project.标签名[下标]}
  • 访问系统环境变量
  • 访问project属性
  • 访问seetings全局配置
  • 用途
    • 在当前pom.xml文件中引用
    • 资源过滤功能:在非Maven配置文件中引用属性,由Maven在处理资源时将引用属性的表达式替换为属性值

build标签详解

  • 一睹真容
    • 在实际使用 Maven 的过程中,我们会发现 build 标签有时候有,有时候没,这是怎么回事呢? 其实通过有效POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置build 标签覆盖默认值或补充配置,这一点我们可以通过打印有效 POM 来看到
    • 所以本质上来说:我们配置的 build 标签都是对超级 POM 配置的叠加。那我们又为什么要在默认配置的基础上叠加呢?很简单,在默认配置无法满足需求的时候定制构建过程。
  • build标签组成
    • 从完整示例中,build标签的字标签大致包含三个主体部分

      • 定义约束的目录结构
      目录名作用
      sourceDirectory主体源程序存放目录
      scriptSourceDirectory脚本源程序存放目录
      testSourceDirectory测试源程序存放目录
      outputDirectory主体源程序编译结果输出目录
      testOutputDirectory测试源程序编译结果输出目录
      resources主体资源文件存放目录
      testResources测试资源文件存放目录
      directory构建结果输出目录
      • 备用插件管理
        • pluginManagement 标签存放着几个极少用到的插件:
          • maven-antrun-plugin
          • maven-assembly-plugin
          • maven-dependency-plugin
          • maven-release-plugin
        • 通过 pluginManagement 标签管理起来的靠件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。情看下面例子:
          • 被 spring-boot-dependencies 管理的插件信息:
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                  <version>2.6.2</version>
              </plugin>
          
          • 子工程使用的插件信息:
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
          
        • 生命周期插件
          • plugins标签存放的是默认生命周期中实际会用到的插件,这些插件想必大家都不陌生,所以抛开插件本身不谈,我们来看看 plugin 标签的结构:
          <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.1</version>
              <executions>
                  <execution>
                      <id>default-compile</id>
                      <phase>compile</phase>
                      <goals>
                          <goal>compile</goal>
                      </goals>
                  </execution>
                  <execution>
                      <id>default-testCompile</id>
                      <phase>test-compile</phase>
                      <goals>
                          <goal>testCompile</goal>
                      </goals>
                  </execution>
              </executions>
          </plugin>
          
          • 坐标部分
            • artifactId和version标签定义了插件的坐标,作为Maven的自带插件这里省略了groupId
          • 执行部分
            • executions标签内可以配置多个execution标签,execution标签内:
              • id:指定唯一标识
              • phase:关联的生命周期阶段
              • goals/goal:关联指定生命周期的目标
                • goals标签中可以配置多个goal标签,表示一个生命周期环节可以对应当前插件的多个目标
          • 另外,插件目标的执行过程可以进行配置,例如 maven-site-plugin 插件的 site 目标:
          <execution>
              <id>default-site</id>
              <phase>site</phase>
              <goals>
                  <goa1>site</goa1>
              </goals>
              <configuration>
                  <outputDirectory>D: idea2019workspace atguigu-maven-test-prepare\target\site</outputDirectory>
                  <reportplugins>
                      <reportpTugin>
                          <groupId>org.apache.maven.plugins</groupId>
                          <artifactId>maven-project-info-reports-plugin</artifactId>
                      </reportplugin>
                  </reportplugins>
              </confiquration>
          </execution>
          
          • configuration 标签内进行配置时使用的标签是插件本身定义的。就以 maven-site-plugin 插件为例,它的核心类是 org.apache.maven.plugins.site.render.SiteMojo,在这个类中我们看到了 outputDirectory 属性
  • 典型应用:指定JDK版本
    • 提出问题
      • 前面我们在 settingsxml中配置了JDK 版本,那么将来把 Maven 工程部署都服务器上,脱离了 settingsxml配置,如何保证程序正常运行呢? 思路就是我们直接把 DK 版本信息告诉负责编译操作的 maven-compiler-plugir插件,让它在构建过程中,按照我们指定的信息工作。
    • 暂时取消settings.xml配置
      • 为了测试对maven-compiler-plugin插件进行配置的效果,我们暂时取消 settingsxml 中的 profile 配置,
    • 编写源代码
      • 很明显这里用到了Lambda表达式,这是JDK1.8才支持的语法
          package com.atguigu .maven;
          public class Hello {
              public void hello() {
                  new Thread(()->{
                      System.out.printIn("thread ...");
                  }).start() ;
              }
          }
      
    • 执行编译命令
    • 配置构建过程
    <!--bui1d 标签: 意思是告诉 Maven,你的构建行为,我要开始定制了!-->
    <build>
        <!-- plugins 标签: Maven 你给我听好了,你给我构建的时候要用到这些插件!-->
        <plugins>
            <!-- plugin 标签: 这是我要指定的一个具体的插件 -->
            <plugin>
                <!-- 插件的坐标。此处引用的 maven-compiler-plugin 插件不是第三方的,是一个 Maven 自带的插件。-->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <!-- configuration 标签: 配置 maven-compiler-plugin 插件 -->
                <configuration>
                    <!-- 具体配置信息会因为插件不同、需求不同而有所差异 -->
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
    • 再次执行编译命令
    • 两种配置方式的比较
      • settings.xml中配置
        • 仅在本地生效,如果脱离当前settings.xml能够覆盖的范围,则无法生效
      • 在当前Maven工程pom.xml中配置
        • 无论在哪个环境执行编译等构建操作都有效
    • 补充说明
      • source标签含义
        The -source argument for the Java compiler.
        NOTE: Since 3.8.0 the default value has changed from 1.5 to 1.6
        Default value is 1 .6 .
        User property is: maven.compiler.source
        
        • 调用Java 编译器命令时传入的 -source 参数。【提供与指定发行版的源兼容性】
          • 我们写代码是按JDK1.8 写的一一这就是[源兼容性]里的[源] 。
          • 指定发行版就是我们指定的JDK1.8。
          • [兼容性]是谁和谁兼容呢? 现在源代码是既定的,所以就是要求编译器使用指定的JDK 版本来兼容我们的源代码。
        • 这个功能还可以通过在 properties 标签中配置 maven.compiler.source 属性来实现。所以我们也经常会看到类似这样的配置:
        <properties>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
        
      • target标签含义
        The -target argument for the Java compiler.
        NOTE: Since 3.8.0 the default value has changed from 1.5 to 1.6
        Default value is: 1 . 6 .
        User property is: maven . compiler .target
        
        • 调用Java 编译器命令时传入的 -target 参数。【生成特定 VM 版本的类文件】
          • VM指JVM
          • 类文件指*.class 字节码文件
          • 整体意思就是源文件编译后,生成的 ".class 字节码文件要符合指定的VM 版本
  • 典型应用: SpringBoot 定制化打包
    • 需求
      • 很显然 spring-boot-maven-plugin 并不是 Maven 自带的插件,而是 SpringBoot 提供的,用来改变 Maven 默认的构建行为。具体来说是改变打包的行为。默认情况下 Maven 调用 maven-jar-plugin 插件的jar 目标,生成普通的jar包。
      • 普通 jar 包没法使用java -jar xxx.jar这样的命令来启动、运行,但是SpringBoot的设计理念就是每一个[微服务] 导出为一个jar包,这个jar包可以使用java -jar xxx.jar这样的命令直接启动运行。这样一来,打包的方式肯定要进行调整,所以SpringBoot提供了spring-boot-maven-plugin这个插件来定制包行为。
    • 示例代码
      • 所有的一切已经都被SpringBoot封装好了,所以配置非常简单,提供插件坐标即可
      <build>
          <plugins>
              <plugin>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-maven-plugin</artifactId>
                  <version>2.5.5</version>
              </plugin>
          </plugins>
      </build>
      
    • 插件的七个目标
    标题
    spring-boot:build-imagePackage an application into a OCl image using a buildpack
    spring-boot:build-infoGenerate a build-info.properties file based on the content of the currentMavenProject.
    spring-boot:helpDisplay help information on spring-boot-maven-plugin.
    Call mvn spring-boot:help -Ddetail=true -Dgoal= to display parameterdetails.
    spring-bootrepackageRepackage existing JAR and WAR archives so that they can be executed from thecommand line using java -jar. With layout=NONE can also be used simply to packagea JAR with nested dependencies (and no main class, so not executable).
    springboot:runRun an application in place.
    spring-bootstartStart a spring application. Contrary to the run goal, this does not block and allowother goals to operate on the application. This goal is typically used in integratiotest scenario where the application is started before a test suite and stopped after.
    spring-boot:stopStop an application that has been started by the 'start' goal. Typically invoked once atest suite has completed.
  • 典型应用:Mybatis逆向工程
  • 小结
    • 不知大家有没有发现,通常需要用到 build 标签的时候底层都会帮我们封装好,!需要我们配置的地方不多。即使有些地方需要我们配置,也不会真的我们自己去写,把现成的案例复制过来就行
    • 所以对 build 标签来说,我们的掌握要求就是:能大致看懂就行。

依赖配置

依赖范围

  • import
    • 管理依赖最基本的办法是继承父工程,但是和 Java 类一样,Maven 也是单继承的,如果不同体系的依赖信息封装在不同 POM 中了,没办法继承多个父工程怎么办? 这时就可以使用 import 依赖范围。
    • 典型案例当然是在项目中引入 SpringBoot、SpringCloud 依赖:
      <dependencyManagement>
          <!-- Springcloud 依赖导入 -->
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-dependencies</artifactId>
              <version>Hoxton.SR9</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
          <!-- springcloud Alibaba 依赖导入 -->
          <dependency>
              <groupId>com.alibaba.cloud</groupId>
              <artifactId>spring-cloud-alibaba-dependencies</artifactId>
              <version>2.2.6.RELEASE</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>  
          <!-- SpringBoot 依赖导入 -->
          <dependency>
              <groupId>org.springframeworkboot</groupId>
              <artifactId>spring-boot-dependencies</artifactId>
              <version>2.3.6.RELEASE</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencyManagement>
      
    • import依赖范围使用要求
      • 打包类型必须是pom
      • 必须放在dependencyManagement中
      • 官方说明如下:
        • This scope is only supported on a dependency of type pom in the <dependencyManagement> section. ltindicates the dependency is to be replaced with the effective list of dependencies in the specifiedPOM's <dependencyManagement> section. Since they are replaced, dependencies with a scope ofimport do not actually participate in limiting the transitivity of a dependency.
  • system
    • 以Windows系统环境下开发为例,假设现在 D:\tempare\atguigu-maven-test-aaa-1.0-SNAPSHOT.jar 想要引入到我们的项目中,此时我们就可以将依赖配置为system范围:
      <dependency>
          <groupId>com.atguigu .maven</groupId>
          <artifactId>atguigu-maven-test-aaa</artifactId>
          <version>1.0-SNAPSHOT</version>
          <systemPath>D:\tempare\atguigu-maven-test-aaa-1.0-SNAPSHOT.jar</systemPath>
          <scope>system</scope>
      </dependency>
      
      • 但是很明显:这样引入依赖完全不具有可移植性,所以不要使用。如果需要引入体系外jar 包我们后面会讲专门的办法。
  • runtime
    • 专门用于编译时不需要,但是运行时需要的jar包。比如: 编译时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。典型案例是:
      <!--热部署 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtoos</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
      

可选依赖

  • 配置举例
      <!--热部署 -->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtoos</artifactId>
          <scope>runtime</scope>
          <optional>true</optional>
      </dependency>
    
  • 本质含义
    • 可选其实就是[可有可无]。官网的解释是:
      • Marks a dependency optional when this project itself is a dependency. For example. imaqine aproject A that depends upon project B to compile a portion of code that may not be used at runtime, then we may have no need for project B for all project. So if project x adds project A as its own dependency, then Maven does not need to install project B at all. Symbolically, if => represents a required dependency, and --> represents optional, although A=> B may be the case when building A X=>A-->B would be the case when building x .
      • in the shortest ters. optional lets other projects know that, when you use this project, you do not require this dependency in order to work correctly.
    • 其核心含义是: Project X依赖 Project A,A中一部分X用不到的代码依赖了B,那么对X来说B就是[可有可无]的。

版本仲裁

  • 最短路径优先
  • 路径相同时先声明者优先

Maven 自定义插件

本节定位

  • 其实实际开发中几乎没有什么场景需要我们开发自定义 Maven 插件,所以本节只是通过这个角度帮助我们更好的理解插件的目标和生命周期阶段之间的关系。

插件开发

  • 创建工程
  • 设定打包方式
    <packaging>maven-plugin</packaging>
    
  • 引入依赖
    • 下面两种方式二选一
      • 将来在文档注释中使用注解
          <dependency>
              <groupId>org.apache.maven</groupId>
              <artifactId>maven-plugin-api</artifactId>
              <version>3.5.2</version>
          </dependency>
        
      • 将来直接使用注解
        <dependency>
            <groupId>org.apache.maven.p]ugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.52</version>
        </dependency>
        
      • 创建Mojo类
        • Mojo类是一个Maven插件的核心类。
        • Mojo 这个单词的意思是: Maven ld java Object,其实 mo 这个单词本身包含魔力;符咒(袋);护身符:(人的)魅力的含义,Maven 用 Moio 是因为它是对 POJO开的一个小玩笑。
          /**
           * @goal sayHello
           */
          public class MyHellop]ugin extends AbstractMojo {
              @Override
              public void execute() throws MojoExecutionException,MojoFailureException {
                  getLog().info("This is my first maven plugin.");
              }
          }
        

插件配置

  • Mojo类中的配置
    • 文档注释中用注解
      • 对应的pom.xml中的依赖:maven-plugin-api
      /**
        * @goal sayHello
        */      
      
    • 直接在类上标记注解
      • 对应pom.xml中的依赖:maven-plugin-annotations
      //name属性:指定目标名称
      @Mojo(name = "firstBool")
      
  • 安装插件
    • 要在后续使用插件,就必须至少将插件安装到本地仓库
  • 注册插件
    • 我们需要将插件坐标中的groupId部分注册到settings.xml中
        <pluginGroups>
            <!-- pluginGroup
             Specifies a further group identifier to use for plugin lookup.
            <pluginGroup>com.your .plugins</pluginGroup>
            -->
            <pluginGroup>com.atguigu.maven</pluginGroup>
        </pluginGroups>
      

使用插件

  • 识别插件前缀
    • Maven根据插件的artifactId来识别插件前缀。例如下面两种情况:
      • 前置配置
        • 匹配规则:${prefix}-maven-plugin
        • artifactId:hello-maven-plugin
        • 前缀:hello
      • 中间匹配
        • 匹配规则:maven-${prefix}-plugin
        • artifactId:maven-good-plugin
        • 前缀:good
  • 在命令行直接用
    • 命令
      • mvn hello:sayHello
  • 配置到build标签里
      <build>
          <plugins>
              <plugin>
                  <groupId>com.atguigu.maven</groupId>
                  <artifactId>hello-maven-plugin</artifactId>
                  <version>1.0-SNAPSHOT</version>
                  <executions>
                      <execution>
                          <id>hello-plugin</id>
                          <phase>validate</phase>
                          <goals>
                              <goal>sayfiello</goal>
                          </goals>
                      </execution>
                  </executions>
              </plugin>
          </plugins>
      </build>
    

profile详解

  • 简介
    • 单词释义
    • 这里我们可以对接 profile 这个单词中下侧面]这个含义: 项目的每一个运行环境,相当于是项目整体的一个侧 面
  • 项目的不同运行环境
    • 通常情况下,我们至少有三种运行环境:
      • 开发环境:供不同开发工程师的各个模块之间相互调用,访问;内部使用
      • 测试环境:供测试工程师对项目的各个模块进行功能测试;内部使用
      • 生产环境:供最终用户访问--所以这是正式的运行环境,对外提供服务
    • 而我们这里的【环境】仍然是一个笼统的说法,实际工作中一整套运行环境会包含很多种服务器
      • MySQL
      • Redis
      • elasticSearch
      • RabbitMQ
      • FastDFS
      • Nginx
      • Tomcat
      • ...
    • 就拿其中的MySQL来说,不同环境下的访问参数肯定完全不同
    • 可是代码只有一套。如果在idbc.properties 里面来回改,那就太麻烦了,而且很容易遗漏或写错,增加调试的难度和工作量。所以最好的办法就是把适用于各种不同环境的配置信息分别准备好,部署哪个环境就激活哪个配置。
    • 在 Maven 中,使用 profile 机制来管理不同环境下的配置信息。但是解决同类问题的类似机制在其他框架中也有,而且从模块划分的角度来说,持久化层的信息放在构建工具中配置也违反了了高内聚,低糖合] 的原则。
  • profile声明和使用的基本逻辑
    • 首先为每一个环境声明一个profile
      • 环境A:profile A
      • 环境B:profile B
      • 环境C:profile C
    • 然后激活某一个profile
  • 默认profile
    • 其实即使我们在pom.xm中不配置 profile 标签,也已经用到 profile了。为什么呢? 因为根标签 project 下所有标签相当于都是在设定默认的 proflle。这样一来我们也就很容易理解下面这句话: project 标签下除了modelVersion 和坐标标签之外,其它标签都可以配置到 profile 中。

配置 profile

  • 外观视角:配置文件
    • 从外部视角来看,profile 可以在下面两种配置文件中配置
      • settings.xml: 全局生效,其中我们最熟悉的就是配置JDK 1.8
      • pom.xml: 当前 POM牛效
    • 内部实现: 具体标签
      • 从内部视角来看,配置 profile 有如下语法要求:
        • profiles/profile标签
          • 由于 profile 天然代表众多可选配置中的一个所以由复数形式的 profiles 标签统一管理。
          • 由于 profile 标签覆盖了 pom.xml 中的默认配置,所以 profiles 标签通常是 pom.xml中的最后一个标签
        • id标签
          • 每个 profle 都必须有一个id 标签,指定该 profle 的唯一标识。这个id 标签的值会在命令行调用 profile 时被用到。这个命令格式是:-D。
        • 其它允许出现的标签
          • 一个 profile 可以覆盖项目的最终名称、项目依赖、插件配置等各个方面以影响构建行为.
          • build
            • defaultGoal
            • finalName
            • resources
            • testResourcs
            • plugins
          • reporting
          • moudles
          • dependencies
          • dependencyMangement
          • repositories
          • pluginRepositories
          • properties

激活profile

  • 默认配置默认被激活
    • 前面提到了,POM 中没有在 profile 标签里的就是默认的 profile,当然默认被激活
  • 基于环境信息激活
    • 环境信息包含:JDK版本、操作系统参数、文件、属性等各个方面。一个profile一旦被激活,那么定义的所有配置都会覆盖原来POM中对应层次的元素。大家可以参考下面的标签结构
          <profile>
              <id>dev</id>
              <activation>
                  <!-- 配置是否默认激活 -->
                  <activeByDefault>false</activeByDefault>
                  <jdk>1.5</jdk>
                  <os>
                      <name>windows XP</name>
                      <family>Windows</family>
                      <arch>x86</arch>
                      <version>5.1.2600</version>
                  </os>
                  <property>
                      <name>mavenversion</name>
                      <value>2.0.5</value>
                  </property>
                  <file>
                      <exists>file2.properties</exists>
                      <missing>filel.properties</missing>
                  </file>
              </activation>
          </profile>
      
    • 这里有个问题是:多个激活条件之间是什么关系?
      • Maven3.2.2之前遇到第一个满足的条件即可激活--或的关系
      • Maven3.2.2之后各条件均需满足--且的关系
    • 下面我们来看一个具体的例子。假设有如下profile配置,在JDK版本为1.6时被激活
          <profiles>
              <profile>
                  <id>JDK1.6</id>
                  <activation>
                      <!-- 指定激活条件为: JDK 1.6 -->
                      <jdk>1.6</jdk>
                  </activation>
              </profile>
          </profiles>
      
    • 这里需要指出的是:Maven会自动监测当前环境安装的JDK版本,只要JDK版本是以1.6开头都算符合条件。下面几个例子都符合:
      • 1.6.0_03
      • 1.6.0_02
  • 命令行激活
    • 列出活动的profile
      #列出所有激活的profle,以及它们在哪里定义
      mvn help:active-profiles
      
    • 指定某个具体profile
      mvn compile -P<profileId>
      

资源属性过滤

简介

  • Maven 为了能够通过 profile 实现各不同运行环境切换,提供了一种[资源属性过]的机制。通过属性换实现 不同环境使用不同的参数。
  • 操作演示
    • 配置profile
          <profiles>
              <profile>
                  <id>devJDBcProfile</id>
                  <properties>
                      <dev.jdbc.user>root</dev.jdbc.user>
                      <dev.jdbc.password>atguigu</dev.jdbc.password>
                      <dev.jdbc.password>atguigu</dev.jdbc.password>
                      <dev.jdbc.ur1>http://localhost:3306/db_good</dev.jdbc.ur1>
                      <dev.jdbc.driver>com.mysql.jdbc.Dr iver</dev.jdbc.driver>
                  </properties>
                  <build>
                      <resources>
                          <resource>
                              <!-- 表示为这里指定的目录开启资源过滤功能 -->
                              <directory>src/main/resources</directory>
                              <!-- 将资源过滤功能打开 -->
                              <filtering>true</filtering>
                          </resource>
                      </resources>
                  </build>
              </profile>
          </profiles>
      
    • 创建待处理的资源文件
      dev.user=s{dev.jdbc.user3}
      dev.passwor d=s{dev.jdbc.password}
      dev.ur1=$(dev.jdbc.url}
      dev.driver=$[dev.jdbc.driver3} 
      
    • 执行处理资源命令
      mvn clean resources:resources -PdevJDBCProfile
      
    • 找到处理得到的资源文件
    • 延伸
      • 我们时不时会在 resource 标签下看到 includes 和 excludes 标签。它们的作用是
        • includes: 指定执行 resource 阶段时要包含到目标位置的资源
        • excludes: 指定执行 resource 阶段时要排除的资源

正常实践

Nexus

Nexus安装

  • 下载
  • 上传解压
  • 启动Nexus

初始设置

对接Nexus

  • 通过Nexus下载jar包
    • 了解Nexus上各种仓库

      仓库类型说明
      proxy某个远程仓库的代理第三方
      group存放:通过Nexus获取的第三方jar包
      hosted存放:本团队其他开发人员部署到Nexus的jar包
    • 使用空的本地仓库

    • 指定Nexus服务器地址

      • 把我们原来配置阿里云仓库地址的mirror标签改成下面这样
        <mirror>
            <id>nexus-mine</id>
            <mirrorOf>central</mirrorOf>
            <name>Nexus mine</name>
            <ur1>http://192.168.198.100:8081/repository/maven-public/</ur1>
        </mirror>
        <server>
            <id>nexus-mine</id>
            <username>admin</username>
            <password>123456</password>
        </server>
        
      • 这里需要格外注意:server标签内的id标签值必须和mirror中的id值一样
  • 将jar包部署到Nexus
    • 配置Maven工程
      <distributionManagement>
          <snapshotRepository>
              <id>nexus-mine</id>
              <name>Nexus Snapsnot</name>
              <ur1>http://192.168.198.100:8081/repository/maven-snapshots/</ur1>
          </snapshotRepository>
      </distributionManagement>
      
      • 这里snapshotRepository的id标签也必须和settingsxml中指定的mirror标签的id属性一致
    • 执行部署命令
      mvn deploy
      
  • 应用别人部署的jar包
    • 提出问题
      • 默认访问的Nexus仓库:maven-public
      • 存放别人部署jar包的仓库:maven-snapshots
    • 配置Maven工程
      <repositories>
          <repository>
              <id>nexus-mine</id>
              <name>nexus-mines</name>
              <ur1>http://192.168.198.100:8081/repository/maven-snapshots/</ur1>
              <snapshots>
                  <enabled>true</enabled>
              </snapshots>
              <releases>
                  <enabled>true</enabled>
              </releases>
          </repository>
      </repositories>
      

jar包冲突

谁需要面对jar包冲突?

  • 先给结论:编订依赖列表的程序员。初次设定一组依赖,因为尚未经过验证,所以确实有可能存在各种问题,需要做有针对性的调整。那么谁来做这件事呢?我们最不希望看到的就是: 团队中每个程序员都需要自己去找依赖,即使是做同一个项目,每个模块也各加各的依赖,没有统一管理。那前人踩过的坑,后人还要再踩一遍。而且大家用的依赖有很多细节都不一样,版本更是五花八门,这就让事情变得更加复杂。
  • 所以虽然初期需要根据项目开发和实际运行情况对依赖配置不断调整,最终确定一个各方面都 OK 的版本。但是一旦确定下来,放在父工程中做依赖管理,各个子模块各取所需,这样基本上就能很好的避免问题的扩散。
  • 即使开发中遇到了新问题,也可以回到源头检查、调整 dependencyManagement 配置的列表一一而不是每个模块都要改。所以学完这一节你应该就会对前面讲过的[继承,有了更深的理解。

表现形式

  • 由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉及到大量 iar 包。数以百计的 ar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 ar 包组合在一起难免会有磕磕碰碰。最关键的是由于lr包冲突所导致的问题非常诡导,这里我们只能罗列较为典型的问题,而没法保证穷举。

  • 但是我们仍然能够指出一点:一般来说,由于我们自己编写代码、配置文件写错所导致的问题通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径,如果整个错误信息中完全没有我们负责的部分,全部是框架.第三方工具包里面的类报错,这往往就是jar包的问题所引起的。

  • 而具体的表现形式中,主要体现为找不到类或找不到方法。

  • 抛异常:找不到类

    • 此时抛出的常见的异常类型
      • java.lang.ClassNotFoundException: 编译过程中找不到类
      • java.lang.NoClassDefFoundError: 运行过程中找不到类
      • java.lang.LinkageError: 不同类加载器分别加载的多个类有相同的全限定名
    • 我们来举个例子
      <dependency>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactid>httpclient</artifactid>
          <version>4.x.x</version>
      </dependency>
      
    • httpclient 这个jar 包中有一个类: org.apache.http.conn.ssl.NoopHostnameVerifier。这个类在较低版本中没有,但在较高的版本存在,比如:
    jar包版本是否存在
    4.3.6
    4.4
    • 那当我们确实需要用到NoopHostnameVerifier这个类,我们看到Maven通过依赖传递机制引入了这个jar 包所以没有明确地显式声明对这个jar 包的依赖,可是Maven传递过来的jar包是4.3.6版本,里面没有包合我们需要的类,就会抛出异常。
    • 而[冲突]体现在:4.3.6和4.4 这两个版本的 ar 包都被框架所依赖的ar 包给传递进来了,但是假设 Maven 根据[版本仲裁]规则实际采纳的是 4.3.6。
  • 抛出异常:找不到方法

    • 程序找不到符合预期的方法。这种情况多见于通过反射调用方法,所以经常会导致:java.lang,NoSuchMethodError。比如 antr:antlr:x.x.x 这个包中有一个接口: antlr.collections.AST

      版本getLine()方法
      2.7.2
      2.7.6
  • 没有报错但结果不对

    • 发生这种情况比较典型的原因是:两个lar 包中的类分别实现了同一个接口,这本来是很正常的。但是问题在于:由于没有注意命名规范,两个不同实现类恰巧是同一个名字
    • 具体例子是有的同学在实际工作中遇到过: 项目中部分模块使用 og4 打印日志,其它模块使用 logback,编译运行都不会冲突,但是会引起日志服务降级,让你的log 配置文件失效,比如:你指定了 error 级别输出,但是冲突就会导致 info、debug 都在输出。

本质

  • 以上表现形式归根到底是两种基本情况导致的
    • 同一jar包的不同版本
    • 不同jar包中包含同名类

解决办法

  • 概述
    • 很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 lar 包。我们接下来要说的是通用方法。
    • 不管具体使用的是什么工具,基本思路无非是这么两步:
      • 第一步: 把彼此冲奕的ar 包找到
      • 第二步: 在冲突的jar包中选定一个。具体做法无非是通过exclude排除依赖,或是明确声明依赖
  • IDEA的Maven Helper插件
  • Maven的enforcer插件
    • 使用Maven的enforcer插件既可以检测同一个jar 包的不同版本,又可以检测不同jar 包中同名的类.
    • 引入netty依赖
      <dependencies>
          <dependency>
              <groupId>org.jboss.netty</groupId>
              <artifactId>netty</artifactId>
              <version>3.2.10.Final</version>
          </dependency>
          <dependency>
              <groupId>io.netty</groupId>
              <artifactId>netty</artifactId><
              version>3.9.2.Final</version>
          </dependency>
      </dependencies>
      
    • 配置enforcer插件
      <bui1d>
          <pluginManagement>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-enforcer-plugin</artifactId>
                      <version>1.4.1</version>
                      <executions>
                          <execution>
                              <id>enforce-dependencies</id>
                              <phase>validate</phase>
                              <goals>
                                  <goal>display-info</goal>
                                  <goal>enforce</goal>
                              </goals>
                          </execution>
                      </executions>
                      <dependencies>
                          <dependency>
                              <groupId>org.codehaus.mojo</groupId>
                              <artifactId>extra-enforcer-rules</artifactId>
                              <version>1.0-beta-4</version>
                          </dependency>
                      </dependencies>
                      <configuration>
                          <rules>
                              <banDuplicateClasses>
                                  <findA11Duplicates>true</findA11Duplicates>
                              </banDuplicateClasses>
                          </rules>
                      </configuration>
                  </plugin>
              </plugins>
          </pluginManagement>
      </bui1d>
      
    • 测试
      • 执行如下Maven命令
          mvn clean package enforcer:enforce
        

体系外jar包引入

  • 提出问题
    • [体系外jar包!这个名字是我起的,来源是这样一一目前来说我们在 Maven 工程中用到的jar 包都是通过Maven 本身的机制导入进来的。
    • 而实际开发中确实有可能用到一些jar 包并非是用 Maven 的方式发布入那自然也没法通过 Maven 导入此时如果我们能够拿到该 ar 包的源码那还可以自己建一个 Maven 工程,自己打包。可是如果连源码都没有呢?这方面的例子包括一些人脸识别用的 jar 包、海康视频监控 jar 包等等。
  • 解决办法
    • 准备一个体系外jar 包
      • 我们通过学Maven以前的方式创建一个Java工程,然后导出jar包即可用来测试
    • 将该jar包安装到Maven仓库
      • 这里我们使用install插件的install-file目标:

        mvn install:install-file -Dfile=[体系外 jar 包路径]-DgroupId=[给体系外 jar 包强行设定坐标]-DartifactId=[给体系外 jar 包强行设定坐标]-Dversion=1-Dpackage=jar