Maven

作者: bowen_wu | 来源:发表于2022-08-05 10:48 被阅读0次

Maven 包管理工具

  1. JVM 世界里面只有全限定类名(Full Qualified Class Name),类的全限定类名唯一确定了一个类
  2. JVM 的工作 => 执行一个类的字节码,假如这个过程中碰到了新的类,加载它
  3. 类路径(Classpath) => JVM 在 classpath 里面寻找类
  4. 包就是把许多类放在一起打的压缩包
  5. 传递性依赖 => 你依赖的类还依赖了别的类
  6. Classpath Hell => 当多个同名类同时出现在 Classpath 中,就是噩梦的开始
  7. 包管理 => 告知 JVM 如何找到所需的第三方类库以及成功地解决其中的冲突问题

Maven 包管理

  • Maven 在包管理所做的事情 => 查看坐标,根据坐标去中央仓库中定位包,之后查看 pom 文件,找到依赖的包,之后逐个查看坐标,定位包,将所有的包都下载到本地,之后将这些依赖都放置在 classpath 中,供 JVM 去检索,之后启动 JVM
  • 传递性依赖原则 => 决不允许最终 classpath 出现同名不同版本的 jar 包
  • 自动管理传递性依赖
  • 自动解决冲突

坐标系统

  1. 通过唯一的坐标定位包 => groupId + artifactId + version
  2. 坐标可读性好、简单方便
  3. 语义化版本,方便自动化工具处理
  4. 一旦发布不可修改,实现稳定的构建

元数据系统

  1. jar 包本身是没有元信息,很难知道依赖谁
  2. pom.xml 存储这个 jar 包的传递性依赖关系
scope

pom 文件中的 <dependency> 标签中的 <scope> 标签

  • compile => 生产代码的依赖
  • provided => 编译的时候需要该依赖,运行的时候是由外部容器提供了相关依赖 e.g. Servlet API
  • test => 测试相关依赖 e.g. JUnit
  • runtime => 只有在运行的时候需要的依赖 e.g. mysql-connector等JDBC的包

构建的4个步骤,其中每个步骤都需要一个 classpath 去进行相应的工作

  1. javac 编译 main 目录 => compile + provided
  2. javac 编译 test 目录 => compile + provided + test
  3. java 运行所有测试 => compile + test + runtime
  4. java 运行生产代码 => compile + runtime

冲突解决

当看到如下异常的时候,意味着包冲突出现了

  • AbstractMethodError
  • NoClassDefFoundError
  • ClassNotFoundException
  • LinkageError

Maven 解决冲突原则

  1. 最近原则
  2. 最先声明原则

Maven 中央仓库

  • 按照一定的约定存储 pom、jar(class文件)、javadoc(HTML文件)、resources(源代码文件)
  • 依赖包的解析与检索 => https://repo1.maven.org/maven2/com/google/guava/guava/ => https://repo1.maven.org/maven2/<groupId>/<artifactId>/<version>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.0-jre</version> 
    
  • 中央仓库的声明与镜像
  • 中央仓库、快照仓库和插件仓库
    1. 中央仓库 => <repositories></repositories>
    2. 快照仓库 =>
    3. 插件仓库 => <pluginRepositories></pluginRepositories>
  • 快照版本与更新 => <version>0.1-SNAPSHOT</vesion> => 快照版本可以重复更新 => 主要解决在开发过程中多方同时引用同一个 jar 包,该 jar 包需要频繁更新 => 并不是每次运行 mvn clean verify 时该 jar 包都从仓库里面拉取最新的,默认是每天更新一次 => mvn clean verify -U 强行重新解析 SNAPSHOT

Maven 本地仓库

  • ~/.m2
  • 中央仓库的本地缓存 => 先找本地仓库,否则就去中央仓库找,找到了就下载到本地仓库
  • 启动 java/javac 命令时的包来源
  • 跨项目/模块开发时的代码共享

发布包

希望同一份代码在本地、团队、公司甚至时间范围内共享

  • 本地 => mvn install
  • 团队、公司 => mvn deploy 私服
  • 世界 => mvn deploy 中央仓库

发布的本质 => 将 pom/jar 按照仓库约定推送到服务器上

Maven 自动化构建工具

默认的三套生命周期

  • default => validate -> initialize -> generate-sources -> precess-sources -> generate-resources -> precess-resources ->
    compile -> precess-classes -> generate-test-sources -> process-test-sources -> generate-test-resources ->
    process-test-resources -> test-compile -> precess-test-classes -> test -> prepare-package -> package ->
    pre-integration-test -> integration-test -> post-integration-test -> verify -> install -> deploy
  • clean => 删除 target
  • site
Maven Lifecycle

Maven Plugin

  • 插件是 Maven 的灵魂,实际上就是一组代码
  • 插件可以声明多个目标(Goal),称为 MOJO(Maven Old Java Object)
  • 插件的 <groupId> 是可以忽略的,默认是 <groupId>org.apache.maven.plugins</groupId>
  • 同一个目标可以绑定到多个阶段上,执行多次
  • 默认绑定 => 不需要手工进行,由插件声明
  • 从命令行直接调用插件 =>
    • mvn dependency:tree
    • mvn surefire:test => https://repo1.maven.org/maven2/org/apache/maven/plugins/maven-metadata.xml => <prefix>
    • mvn flyway:migrate => https://repo1.maven.org/maven2/org/flywaydb/maven-metadata.xml => <prefix>
      prefix
    • mvn help:describe -Dplugin=org.flywaydb:flyway-maven-plugin
      1. 在 $PATH 环境变量中找 mvn 可执行程序,找到之后将后面的参数传递给 maven
      2. maven 启动 JVM,通过 System property 传递给 JVM, System property key 为 plugin, value 为 org.flywaydb:flyway-maven-plugin
      3. maven 查找 help 的 plugin,执行 help plugin 的 describe 目标
      4. help plugin 的 describe 目标获取 key 为 plugin 的 System property,之后执行相应的逻辑


        help:describe

插件解析

  • groupId + artifactId + version 去插件仓库中寻找
  • 如果 groupId 不指定,默认是 org.apache.maven.plugins
  • 如果 version 不指定,则可从父 POM 继承
  • 如果 groupId/artifactId 不指定,可按照 prefix 指定

编写插件

  • pom 文件中的打包方式是 maven-plugin
  • 最终代码会被打包成为 Maven 可以识别的 jar 包
  • 使用 archetype 生成项目结构 => mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-plugin -DarchetypeVersion=1.4
  • 目标 <=> MOJO
  • MOJO 与默认阶段
  • 注入 MOJO 运行所需的属性
  • 执行具体的业务逻辑 => public void execute() throws MojoExecutionException

Maven 私服

  • Hosted => 用于存放私有的包
  • Proxy => 用于代理其他仓库,提高速度,节约资源

nexus

artifactory

Maven 多模块

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

拆分成多模块

  1. parent pom =>
    • <packaging>pom</packaging>
    • <modules><module>module-one</module><module>module-two</module></modules>
  2. 子模块中的 artifactId 和 parent pom 中的 module 保持一致
  3. 子模块中的 version 和父模块中的 version 保持一致
  4. 模块之间的依赖关系和普通项目的依赖并无区别
  5. 可以运行 mvn install 将所有模块安装到本地

模块的继承

  • 多个模块间大量的重复代码违反了 DRY 原则
  • 将共同元素抽取成为公用的 POM
  • 所有的 POM 都隐式继承超级 POM => 超级 POM:<maven安装包>/lib/maven-model-build-<version>/org/apache/maven/model/pom-4.0.0.xml => <modelVersion>4.0.0</modelVersion>
  • <dependencyManagement> => 为所有子模块定义统一的依赖版本
  • <pluginManagement> => 为所有的子模块定义统一的插件版本
  • <profiles></profiles> => 可以控制在不同的环境下让 maven 做不同的事情 => 在 maven 命令中使用 -P 参数,e.g. => 也可以在本地 ~/.m2/setting.xml 中配置 profiles

知识点

  1. NoClassDefFoundError vs ClassNotFoundException
  2. IDEA 和 Maven 在包管理方面遵循同一组依赖模型,但是有时可能会产生不一致的情形。IDEA 就是一个普通的 JVM 进程,它加载了一个 Maven 的 jar 包,之后执行相关的命令
    • IDEA 对依赖的理解
      1. Maven -> Dependencies
      2. 运行时在控制台打印出来的命令中的 classpath
    • Maven 对依赖的理解 => mvn dependency:tree
  3. 调试
    • 调试的代码运行在哪个 JVM 里?
    • 调试的源代码在哪里?=> 调试的源代码和正在运行的 JVM 中的字节码是一一对应的
  4. mvnDebug 命令

相关文章

网友评论

      本文标题:Maven

      本文链接:https://www.haomeiwen.com/subject/tnzfwrtx.html