Maven 应用总结(持续更新)

2023-11-11

继承 & 模块化

模块化

在开发一个项目时,通常会进行模块化拆包,如下:

blog (parent)
    - blog-controller
    - blog-service
    - blog-entity
    - blog-util
    ...

根据模块的名称可以看出,业务是比较单一的。若是一个企业级的后台项目,业务模块是很多的,那么在拆分模块是又是另一种方式,以业务维度进行拆分:

project (parent)
    - business1-controller
    - business1-service
    - business2-controller
    - business2-service
    ...

无论那种方式,目的都在于解耦。根据代码体量的不同,应用不同的方式。当然也并不是说,越复杂的拆分越好,根据实际情况而定。比如仅仅是一个工具型的项目,
过度的拆分反而适得其反。

那么为什么一定要拆分为多个module,而不是在一个项目下,通过业务包名进行拆分呢?以我个人经验来说,项目会变得很臃肿、代码无法得到充分复用。

以我当前所在项目为例(泪),N个系统对应N个项目,但是操作同一个数据库。将每个系统作为一个独立的module,内部根据业务包名的方式进行划分,
一并集成在一个父项目中(实在弄不懂当初搭架子的人为什么这么做)。那么问题来了:

  1. 有些项目的业务繁多(超过50个,即50多个业务包),单个 module 包、代码量过多。
  2. 代码熟悉成本高。
  3. 当A项目想要使用B项目中的Service中的业务代码时,只能进行代码Copy。运气不好,需要将Copy代码中用到的所有依赖类都Copy一次。那如果依赖的类又依赖了其他类呢?

如果通过业务模块拆分的形式进行拆分,就可以避免这些问题。但是也不能所有模块都进行拆分这样的拆分,不然会导致modules的数量过多,业务量多的模块可以这么做。

模块继承

那么 parent & modules 中是如何进行关联的?

Parent:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>xxx.xxx</groupId>
    <artifactId>Parent</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Here -->
    <packaging>pom</packaging>
    <modules>
        <module>business1-controller</module>
        <module>business1-service</module>
        <module>business2-controller</module>
        <module>business2-service</module>
        ...
    </modules>

    <dependencyManagement>
        <dependencies>
            ...
        </dependencies>
    </dependencyManagement>
</project>

Modules:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>xxx.xxx</groupId>
    <artifactId>business1-controller</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    
    <!-- Here -->
    <parent>
        <groupId>xxx.xxx</groupId>
        <artifactId>Parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        ...
    </dependencies>
</project>

这里有个小问题需要注意:改动父工程时,先将 modules 注释,自身install一下。否则子工程依赖的父工程依旧是旧的,现象则是父工程中的改动没有生效。

聚合

如同设计模式中的合成复用原则:优先组合,其次继承。若想使用其他类中的功能时,应引用该类的实例,而非继承该类。目的在于解耦。

在 Maven 中也存在这样的概念,最常见的例子:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

在父 POM 工程中,使用 SpringBoot 框架,通过 import 的方式引入其他 POM 中定义好的一套依赖,即引入依赖中定义的 <dependencyManagement>。而非通过继承的方式引入:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.7.RELEASE</version>
</parent>

依赖范围

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
    <!-- Here -->
    <scope>compile</scope>
</dependency>

默认:compile(不指定默认为compile)

可选:test、provided、runtime、system、import

在讨论它们的具体作用前,我们应先说明一个事:Maven 项目在不同阶段引入到Classpath中的依赖是不相同的。可以分为:

  1. 编译阶段
  2. 运行阶段
  3. 测试阶段

1、test:测试阶段存在,编译、运行阶段不存在。编译和运行主代码时,无法找到该依赖。例如:

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

junit 依赖就是一个典型例子,在项目实际运行时是不需要它的,仅是在开发阶段测试代码时会用到,所以并不会被打包。
如果你是使用idea开发,可以在src/test/java目录下(被idea标识为绿色)使用 scope 为 test 的 junit 依赖中的类,是没问题的。同时在src/main/java源代码目录下
也使用 junit 依赖中的类,会提示找不到依赖中的类。也就是说,src/test/java目录下的类使用的Classpath是不同的,只有<scope>为test的依赖才会被加载到该Classpath下。

2、provided:编译、测试阶段存在,运行阶段不存在。典型例子:servlet-api。Web 运行时容器已经提供了,所以不需要被打包,但是编译 & 测试阶段时需要该依赖。

3、runtime:测试、运行阶段存在,编译阶段不存在。程序中是无法直接 import 该依赖中的类的,编译会找到不类,但是可以通过反射调用获取到。也就说,如果不使用 Maven 打包,
程序中是可以正确 import 的(前提 jar 在 classpath中)。可以看出所谓的编译阶段不存在中的编译阶段,仅是 Maven 范畴内编译阶段,而非真的没有被 javac 编译。无论什么类型的 scope 依赖,如provided,都是需要先被编译为class打成jar包后,才能使用的。

那么这么做的目的是什么呢?知乎上看到的解释:依赖倒置原则,强制开发者使用接口,即依赖抽象不依赖具体,降低类之间的耦合。不使用带有具体实现的依赖。

4、system:与 provided 相比,唯一不同点在于不会去 Maven 仓库寻找、下载依赖,而是会根据配置的本地路径去找:

<dependency>
	<groupId>xxx.xxx</groupId>
	<artifactId>JarName</artifactId>
	<version>1.0</version>
	<scope>system</scope>
	<systemPath>${locationPath}/JarName.jar</systemPath> 
</dependency>

5、import:在前面的继承 & 聚合中,我们引用 SpringBoot 定义依赖的 POM 依赖时,配合 <dependencyManagement> 标签使用。统一管理依赖版本。

6、compile:编译、测试、运行阶段都存在。最常用,也是默认的配置。

依赖传递

传递依赖是指:a->b,b中添加的依赖会被传递到a中。但是若有多个不同版本的传递依赖,该如何选择呢?下面有两个原则:

最短路径原则(路径不同):

a->b->c(1.0)

a->c(2.0)

取 c(2.0)

若 POM 中直接引入是 c,则直接取 POM 中的c,不去判断间接依赖中的c。

最先声明原则(路径相同):

a->b->c(1.0)

a->d->c(2.0)

取 c(1.0)

scope 对传递依赖的影响

并不是所有的依赖都能够被传递,scope的取值会对传递产生影响。

  • 可以被传递:compile、runtime、import
  • 不可被传递:test、provided、system

依赖范围 & 依赖传递总结

Scope 编译阶段存在 测试阶段存在 运行阶段存在 可以依赖传递
compile
test × × ×
provided × ×
system × ×
runtime ×
import × × ×

依赖排除

有些场景,我们期望使用指定版本传递的依赖,比如:a->b(guava 25.1-jre)、a->c(guava 28.2-jre)。

a 同时依赖了 b、c,且b、c 同时使用了不同版本的guava。但是同长度路径下,根据最先声明原则使用了b的低版本guava,我们期望使用高版本的guava,该如何去做?

调整一下声明顺序?可以,但就不。

<dependency>
    <groupId>xxx.xxx</groupId>
    <artifactId>b</artifactId>
    <version>1.0.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </exclusion>
    </exclusions>
</dependency> 

依赖树

当我们想要查看项目最终到底使用了哪些依赖,打包时最终都打了哪些包,即查看最终生效的POM内容,可以通过 idea 的 show effective pom 功能查看。(右键项目,找到 Maven 选项后就可以看到了)

但如果想要定位某个传递依赖具体是通过哪个引入的依赖间接传入的,这个时候就需要通过依赖树查看。

依赖树:将依赖关系树化展示,用于定位传递的依赖所在位置。

  1. mvn dependency:tree 查看完整的依赖树,或者 mvn dependency:tree -Dincludes=Keyword 查看包含关键词的依赖。
  2. idea 在右侧的maven视图,找到项目的dependencies,右键analyze dependencies,即可以在搜索框内搜索指定依赖。
    也可以点击Show as tree选项,转为依赖树,更直观的查看依赖关系。
    还可以在顶部点击show conflicts only,查看依赖冲突。

多环境配置

不同环境应用不同的配置,下面是一个关于 SpringBoot 项目配置文件拆分的例子。common.properties 为各个环境通用的配置,dev、pro.properties 分别
为测试 & 正式环境。

<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <!-- 默认使用 -->
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <profileActive>dev</profileActive>
            <commonProfileActive>common</commonProfileActive>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <activation>
            <activeByDefault>false</activeByDefault>
        </activation>
        <properties>
            <profileActive>prod</profileActive>
            <commonProfileActive>common</commonProfileActive>
        </properties>
    </profile>
</profiles>

配置文件列表:

application-common.properties
application-dev.properties
application-prod.properties
application.properties

application.properties:

spring.profiles.include=@commonProfileActive@,@profileActive@

如何应用:若配置成功,在 idea 右侧的 Maven 视图中,可以看到 Profiles 的菜单项,其中有名为 dev、prod 的单选框项。选中环境后,在对项目进行打包时,就会采用选中的环境名。
那么在 application.properties 中就可以根据环境名使用对应的配置文件。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Maven 应用总结(持续更新) 的相关文章

随机推荐

  • 【哈夫曼树】

    目录 前言 1 哈夫曼树结构 2 初始化 3 构造哈夫曼树 4 获取Huffman编码 整体代码 前言 哈夫曼树又称最优二叉树 可以对带权节点进行编码并且保证每个数据的编码都不会是其他数据的前缀 保证了编码的唯一性 因此 哈夫曼编码又称为前
  • xcode开发中 各种警告

    一 Missing file xxx 如果你在finder中删除了工程里面的文件 xcode上会出现一个警告 Missing file xxx 有个警告在那恨事不自在 上网着了下 发现了如下解决方法 1 打开terminal cd 到刚才你
  • Mysql存储json格式数据需要掌握的

    目录 一 前言 二 什么是 JSON 三 Mysql当中json函数 四 JSON值部分更新 4 1 使用 Partial Updates 的条件 4 2 如何在 binlog 中开启 Partial Updates 4 3 关于 Part
  • centos7 git安装

    开发十年 就只剩下这套Java开发体系了 gt gt gt 由于centos中的源仓库中git不是最新版本 需要进行源码安装 1 查看yum仓库git信息 root iZm5e3d4r5i5ml889vh6esZ zh yum info g
  • ESB产品UI升级总结

    一款好的产品需要不断地打磨才能变得更完整 更稳定 企业服务总线ESB产品作为数通畅联的核心产品 为了能够更好地迎合客户的需求 实现更好的视觉效果和体验感 需要不断地迭代升级 本次升级主要是针对整体页面进行优化以及对部分功能进行修复和调整 升
  • Qt应用开发——前言

    在IT 电子 通信 智能硬件等行业 都有PC端软件应用开发的人才需求 岗位名称有上位机软件 平台软件开发 客户端软件开发 测试软件开发或自动化测试软件开发等 根据公司行业差别和应用场景的不同 技术框架和岗位名称会有一定的区别 PC端软件应用
  • 一.快捷键基本操作(1)

    一 几种最常用的快捷键 1 1 Ctrl C 复制 2 Ctrl V 粘贴 3 Ctrl X 剪切 4 Ctrl A 全选 5 Ctrl Z 撤销上一步操作 6 Windows Windows键就是在键盘左下方类似于窗口的标志 D 最小化所
  • 智慧背囊小故事

    1 甲去买烟 烟29元 但他没火柴 跟店员说 顺便送一盒火柴吧 店员没给 乙去买烟 烟29元 他也没火柴 跟店员说 便宜一毛吧 最后 他用这一毛买一盒火柴 这是最简单的心理边际效应 第一种 店主认为自己在一个商品上赚钱了 另外一个没赚钱 赚
  • [运放滤波器]2_运放反馈原理

    运放滤波器 3 反相同相比例放大电路 Multisim电路仿真 运放滤波器 2 运放反馈原理 运放滤波器 1 理想运放 虚短虚断 简单介绍负反馈对放大电路的影响 几种运放的反馈电路 以及对应的判断方法 反馈原理 反馈 系统的输出送回输入回路
  • WIN32 代码测试(Control)

    include
  • 破案了,能ping通但是网络不通,是它们在搞鬼

    平时 我们想要知道 自己的机器到目的机器之间 网络通不通 一般会执行ping命令 一般对于状况良好的网络来说 你能看到它对应的loss丢包率为0 也就是所谓的能ping通 如果看到丢包率100 也就是ping不通 ping正常 ping不通
  • Javascript数组常用方法重写之map,reduce,some,every

    概要 我们在前端开发过程中 经常使用到各种数组的原生方法 为了更好的理解和使用这些原生方法 所以笔者试着重写了这些方法 并实现了相同的功能 本文主要对map reduce some every这四个原型方法进行重写 代码实现 由于新方法也要
  • 矩阵求导(转)

    在网上看到有人贴了如下求导公式 Y A X gt DY DX A Y X A gt DY DX A Y A X B gt DY DX A B Y A X B gt DY DX B A 于是把以前学过的矩阵求导部分整理一下 1 矩阵Y对标量x
  • 解决Visio封闭图案不能填充问题

    1 首先在文件选项高级里选中以开发人员模式运行 然后visio工具栏就出现了开发工具这一选项 然后选中封闭图形 找到开发工具里的操作这一选项 然后先对图形执行修剪 再连接 最后就可以填充颜色了 看看填充的结果
  • LinkedHashMap和LinkedHashSet 转来记录

    LinkedHashMap和LinkedHashSet是JDK 1 4中引入的两个新的集合类 虽然已经过去 年了 但我敢打赌并不是很多人都用过 因为我就没有用过 但这两个类在某些情况下还是非常有用的 过去没有用 现在没有用 都没有关系 但还
  • 操作系统3-覆盖技术,交换技术,虚拟存储,置换算法——lab3

    虚拟存储 在非连续存储内存分配的基础上 可以把一部分内容放到外存的做法 需求背景 增长迅速的存储需求 程序规模的增长速度远远大于存储器容量的增长速度 理想的存储器 更大 更快 更便宜 非易失性 实际张的存储器是目前很难达到的 要把硬盘的空间
  • Java 获取过去12个月日期

    业务功能要求 查询前12个月的相关数据统计 前 12个月日期 LocalDate today LocalDate now for long i 0L i lt 11L i LocalDate localDate today minusMon
  • PTA乙级1011 A+B 和 C

    给定区间 2 31 2 31 内的 3 个整数 A B 和 C 请判断 A B 是否大于 C 输入格式 输入第 1 行给出正整数 T 10 是测试用例的个数 随后给出 T 组测试用例 每组占一行 顺序给出 A B 和 C 整数间以空格分隔
  • 牛客网:坐标计算工具

    题目描述 开发一个坐标计算工具 A表示向左移动 D表示向右移动 W表示向上移动 S表示向下移动 从 0 0 点开始移动 从输入字符串里面读取一些坐标 并将最终输入结果输出到输出文件里面 输入 合法坐标为A 或者D或者W或者S 数字 两位以内
  • Maven 应用总结(持续更新)

    继承 模块化 模块化 在开发一个项目时 通常会进行模块化拆包 如下 blog parent blog controller blog service blog entity blog util 根据模块的名称可以看出 业务是比较单一的 若是