1.包管理工具

什么是包?

  • jvm的工作被设计的相当简单,且枯燥:
    • 执行一个类的字节码
    • 假如这个过程中碰到了新的类,加载它
  • 那么,去哪里加载这些类呢?

    答:去classpath里面找。无论是IDA,tomcat,maven,gradle。再启动的时候都是再拼接java命令行,启动jvm,并且再jvm里面跑。传入参数后,程序执行的时候需要用到哪个类,jvm就到相应的位置上,把类找到,然后加载它
    image.png
    再-classpath/-cp里面找,类的全限定名唯一确认了一个类(目录层级),包就是把许多类放在一起打的压缩包。

举例: mvn compile (第一个是可执行程序,去$Path环境变量里面找, 第二个是参数。本质上是在拼接java命令行)
image.png

  • 传递性依赖
    • 你依赖的类还依赖了别的类
  • Classpath hell
    • 全限定类名是类的唯一标识
    • 当多个同名类同时出现在Classpath中,就是噩梦的开始。所以maven出现了

maven之前是怎么进行包管理的

  • 查看项目中需要用的的jar包,去网上下载
  • 查看这些jar包依赖的jar包,去网上下载
  • 将他们放在一个目录里面,运行命令进行编译/测试

maven的包管理

  • 划时代的坐标系统
    1. 通过唯一的坐标定位包,不需要傻乎乎去网上下载了
    2. 坐标可读性好,简单方便
    3. 语义化版本,方便自动化工具处理
    4. 一旦发布不可修改,实现稳定的构建
  • 元数据系统
    1. jar包本身是i没有元信息,很难知道他依赖谁?
    2. pom存储这个jar包的传递性依赖关系

坐标 gourpId/artifactId/version
image.png

maven根据pom.xml文件,在仓库里面找到需要的包和依赖的包。下载下来,然后拼接参数到-classpath后面,启动jvm

  • 传递性依赖
    • 自动管理传递性依赖
    • 原则:绝对不允许最终classpath出现同名不同版本的jar包
  • 冲突解决
    • 解决原则
      • 最近原则

image.png
a >> b >> c.0.2 三层
d >> c.0.1 两层。 这个里的进,所以这个优先

  1. - 最先声明原则
  • 解决方法
    • 根据最先声明原则,在pom中追加依赖包,并且指定想要的版本 (0.2)

image.png

  • 使用maven的排除机制(exclusion)

image.png

依赖的scope

image.png

  • 通常的构建包含4个步骤
    • javac编译main目录
      • 【compile + provided -runtime】
    • javac编译testmul
      • 【compile + provided +test - runtime】
    • java运行所有测试
      • 【compile +test +runtime】
    • java运行生产代码
      • 【compile + runtime】
  1. compile:生产代码的依赖
  2. test:只有测试代码的依赖,例如测试框架Junit
  3. provided:编译是需要,但是运行时由容器提供,典型如Servlet Api
  4. runtime:编译时候不需要,运行时候需要,典型如mysql-connector等JDBC的包

2. Maven的自动化构建

maven的本地仓库

本地仓库的角色

  • 中央仓库的本地缓存
    • 先找本地仓库,找不到才去中央仓库去下载
  • 启动java/javac命令时的包来源
  • 跨项目/模块开发时的代码共享

自动化构建

默认的三套生命周期

  • default
  • clean
  • site

image.png
image.png

声明周期与阶段

  • 通过命令行指定要运行的阶段
  • 从头到该阶段所有的阶段都会被运行

插件

  • 插件/目标/绑定
    • 插件是maven的灵魂,实际上就是一组代码
    • 插件可以声明多个目标,简称MOJO
    • 同一个目标可以绑定到多个阶段上,多次执行
  • 默认绑定
    • 不需要手工进行,由插件声明
    • 任何的插件都会声明目标,比如forsure/flyway,详细见链接
    • surefire 链接里面,搜过目标goal
    • image.png
    • flyway
    • image.png
    • 目标可以被绑定到多个阶段上面
    • image.png

插件和目标的绑定

  1. 引入execute插件, 并且绑定到某个阶段(例子展示的是validate阶段)

image.png
image.png
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
image.png

  1. 绑定具体的目标到某个阶段上(示例是把exec绑定到validate上)

    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
    <execution>
     ...
     <goals>
       <goal>exec</goal>
     </goals>
    </execution>
    </executions>
    

    image.png
    绑定完后执行报错,报错信息指明,缺少一个参数,这个参数指向了一个可以执行的程序。
    The parameter executable is missing or invalid

  2. 绑定可执行程序(文档实例)

         <configuration>
           <executable>java</executable>
           <arguments>
             <argument>-classpath</argument>
             <classpath>
               <dependency>commons-io:commons-io</dependency>
               <dependency>commons-lang:commons-lang</dependency>
             </classpath>
             <argument>com.example.Main</argument>
             ...
           </arguments>
         </configuration>
    

    image.png

从命令行直接调用插件

  • mvn dependency: tree
  • mvn help:describe -Dplugin=surefire
  • mvn surefire:test
  • mvn flyaway:migrate

他们是从哪里来的?
image.png
其实每个plugin有个前缀来修饰他,执行mvn surefire 相当于执行了
mvn maven-surefire-plugin

插件是怎么解析的?

插件就是普通的jar包

  • groupId/artifactId/version去插件仓库寻找
    • 插件仓库和仓库是不同的配置
  • 如果gourpId不指定,则默认org.apache.maven.plugins
  • 如果version不指定,则可以从父POM继承
  • 如果groupId/artifactId不指定,可按照prefix调用

3. 自己写一个Maven插件

自定义插件GitHub
调用方代码

编写自己的插件

在编译阶段,统计代码行数

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>myPlugin</artifactId>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myPlugin Maven Mojo</name>
  <url>http://maven.apache.org</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>3.5.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugin-tools</groupId>
      <artifactId>maven-plugin-annotations</artifactId>
      <version>3.5.2</version>
    </dependency>
  </dependencies>
</project>
package org.example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * Goal which touches a timestamp file.
 *
 * @goal touch
 * 
 * @phase process-sources
 */
@Mojo(name = "countLines", defaultPhase = LifecyclePhase.COMPILE)
public class MyMojo extends AbstractMojo {

    @Parameter(defaultValue = "${project.build.sourceDirectory}", property = "sourceDir", required = true)
    private File sourceDir;

    public void execute() throws MojoExecutionException {
        try {
            Files.walkFileTree(sourceDir.toPath(), new SimpleFileVisitor<Path>(){
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    int lineCount = Files.readAllLines(file).size();
                    getLog().info("Line count of file" + file + " is " + lineCount);
                    return super.visitFile(file, attrs);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

写完之后一定要变成一个jar包才能被别人使用,使用如下命令,把jar包发布到本地仓库

mvn install

image.png
把包发布到了如下的路径下面。

[INFO] Installing D:\ITLearning\Java\myPlugin\target\myPlugin-1.0-SNAPSHOT.jar to D:\MavenRepository\org\example\myPlugin\1.0-SNAPSHOT\myPlugin-1.0-SNAPSHOT.jar

现在所有的项目都可以使用这个包了。因为已经发布到了本地仓库

现在打开另一个项目。使用plugin插件引入刚才写的Maven插件,并且把目标绑定到相应的阶段(initialize)上面

      <plugin>
        <!--这里是引入自己写的插件-->
        <groupId>org.example</groupId>
        <artifactId>myPlugin</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--这里是要把目标绑定到initialize阶段-->
        <executions>
          <execution>
            <phase>initialize</phase>
            <goals>
              <goal>countLines</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

然后terminal执行 mvn initialize
image.png

给插件传入参数

prefix 参数

package org.example;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * Goal which touches a timestamp file.
 *
 * @goal touch
 * 
 * @phase process-sources
 */
@Mojo(name = "countLines", defaultPhase = LifecyclePhase.COMPILE)
public class MyMojo extends AbstractMojo {

    @Parameter(defaultValue = "${project.build.sourceDirectory}", property = "sourceDir", required = true)
    private File sourceDir;

    public void execute() throws MojoExecutionException {
        // 插件接受参数
        String prefix = System.getProperty("prefix");
        try {
            Files.walkFileTree(sourceDir.toPath(), new SimpleFileVisitor<Path>(){
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    int lineCount = Files.readAllLines(file).size();
                    getLog().info("Line count of file" + file + " is " + lineCount + "and prefix is " + prefix);
                    return super.visitFile(file, attrs);
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

运行 mvn install更新到本地仓库里面去

// 调用方调用的时候使用-D来传入参数

mvn initialize -Dprefix=wahahahahahah

image.png

调试Maven(debug)

PS:jar包和源代码必须得是一一对应的才行。一点不能错

输入下列命令行

mvnDebug initialize -Dprefix=wahahahahahah

image.png

然后再maven插件的项目里面
image.png
打断点 点击dubug按钮
image.png
image.png

4. Maven多模块

为什么要进行多模块构建

  1. 软件工程的原则:高内聚,低耦合
  2. 模块化的系统,用户自由选择所需模块
  3. 可能带来性能的提升
  4. 多模块->独立的项目->微服务

把一个项目拆分为多模块

父项目

总项目的打包方式必须是pom,modeules里面存放的是子项目
image.png

项目结构
image.png
image.png

子项目

gourpId和父项目一致,artifactId是子项目本来的名称,version和父项目一直
image.png

模块之间依赖

也是用depency标签

  • groupId
  • artifactId
  • version

mvn test

image.png

mvn install

可以把子模块安装到maven仓库里。 首先使用mvn install将模块安装到本地仓库里面。以防止以前的构建影响现在的项目要(如果直接跑项目,可能maven会读取以前的jar包)

解决重复—-maven继承

模块的继承

  • 多个模块间大量的重复代码违反了DRY原则
  • 将共同元素抽取成为公用的pom
  • 所有的pom都隐式继承超级pom
  • 为所有的子模块定义统一的依赖版本
  • 为所有的子模块定义统一的插件版本

父pom引入依赖,子项目用parent标签来继承父pom
image.png
——注意注意——
标签里面之起到限定版本的作用。子pom仍然需要写标签来引入相应的包,只是version不需要写也行,因为控制了版本。
重要的事情说三遍
控制了版本
只控制版本
就控制版本

子pom,不用声明version,这个依赖只能依赖版本,不能依赖依赖本身,需要进行格外声明,只是不用声明版本
image.png

在父亲pom里面可以用property声明version,然后子pom来引用
image.png