技术架构以及如何快速开发技巧

image-20230804170555177

首先来一个简单的架构。

比如一个庞大的项目,比如说类似京东这种庞大的项目,肯定是用的微服务。

像这种大的项目就需要用gateway来进行限流,权限认证。

gateway也叫网关

网关

一、API Gateway网关?是什么呢?

API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可在网关中提供额外的功能。

分布式服务架构、微服务架构与 API 网关:

在微服务架构里,服务的粒度被进一步细分,各个业务服务可以被独立的设计、开发、测试、部署和管理。这时,各个独立部署单元可以用不同的开发测试团队维护,可以使用不同的编程语言和技术平台进行设计,这就要求必须使用一种语言和平 台无关的服务协议作为各个单元间的通讯方式。

总结:网关就是所有项目的一个统一入口。

img

二、网关组成

网关 = 路由转发 + 过滤器(编写额外功能)

1. 路由转发

接收外界请求,通过网关的路由转发,转发到后端的服务上。

如果只有这个一个功能看起来和之前学习的Nginx反向代理服务器很像,外界访问nginx,由nginx做负载均衡,后把请求转发到对应服务器上。

2.过滤器

网关非常重要的功能就是过滤器。

过滤器中默认提供了25内置功能还自持额外的自定义功能。

对于我们来说比较常用的功能有网关的容错、限流以及请求及相应的额外处理。

三、 Gateway是什么?伙伴们你们清楚吗?

1.Spring Cloud Gateway介绍

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

Spring Cloud Gateway是Spring Cloud 的二级子项目,提供了微服务网关功能,包含:权限安全、监控/指标等功能。

2.介绍一下我们要学习Gateway中的名词,以及所包含的意思!

2.1 Route

Route中文称为路由,Gateway里面的Route是主要学习内容,一个Gateway项目可以包含多个Route。

一个路由包含ID、URI、Predicate集合、Filter集合。

在Route中ID是自定的,URI就是一个地址。剩下的Predicate和Filter学习明白了,Route就学习清楚了。

2.2 Predicate

中文:谓词。

谓词时学习Gateway比较重要的一点,简单点理解谓词就是一些附加条件和内容。

2.3Filter

所有生效的Filter都是GatewayFilter的实例。在Gateway运行过程中Filter负责在代理服务“之前”或“之后”去做一些事情。

2.4 流程

img

流程文字解释

网关客户端访问Gateway网关Gateway中Handler Mapping对请求URL进行处理。处理完成后交换Web Handler,Web Handler 会被Filter进行过滤。Filter中前半部分代码是处理请求的代码。处理完成后调用真实被代理的服务。被代理服务响应结果,结果会被Filter中后半部分代码进行操作,操作完成后把结果返回给Web Hanlder,在返回给Handler Mapping,最终响应给客户端。

四、为什么用Gateway

Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。

Spring Cloud Gateway 里明确的区分了 Router 和 Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。

比如内置了 10 种 Router,使得我们可以直接配置一下就可以随心所欲的根据 Header、或者 Path、或者 Host、或者 Query 来做路由。

比如区分了一般的 Filter 和全局 Filter,内置了 20 种 Filter 和 9 种全局 Filter,也都可以直接用。当然自定义 Filter 也非常方便。

Gateway最重要的几个概念

img

了解完网关之后,我们继续来进行看。

image-20230804171232826

这就是一个整体的技术架构。这里来详细介绍一下聚合应用层是什么

聚合应用层

网关是微服务架构不可或缺的一部分,作为微服务架构的唯一入口,将所有请求转发到后端对应的微服务上去,同时又可以将各个微服务中的通用功能集中到网关去做,而不是在每个微服务都实现一遍,比如权限校验,限流,熔断和监控等。

img

如图所示,这是个典型的前后端分离的微服务架构,但这个架构在的问题是,一个接口无法同时满足不同场景的业务。如移动端APP,可能与Web端、OpenAPI 的需求不一样,导致接口存在差异, 这时微服务就要对接不同的API需求,产生了各种各样的问题。

有没有更优雅的解决方式呢?那就是引入BFF,这个词是 2015 年 11 月 Sam Newman 在他的一篇博客中提出的。BFF 是 Backends for Frontends 的简写,为了前端的后端。Sam Newman 的博客还有一个副标题:Single-purpose Edge Services for UIs and external parties,为了用户界面或外部方的单一目的的边缘服务。用户界面比如我们常见的网页,或 App,外部方比如第三方 App,客户 App,企业微信,小程序等。其实这中模式更早一点就出现了,淘宝在更早一点的时候就设立中途岛项目,其主要内容就是 BFF。

BFF也称聚合层或者适配层,它主要承接一个适配角色:将内部复杂的微服务,适配成对各种不同用户体验(无线/Web/H5/第三方等)友好和统一的API。聚合裁剪适配是BFF的主要职责。

改造后的微服务架构如下图:

img

引入BFF后,有如下好处:

  1. 聚合, 将后端多个请求合并成一个请求,以减少网络传输时间。这些请求可能来自一个服务,也可能来自多个服务。
  2. 适配, 因为遵守的接口规范不同,多个微服务和外部服务的接口可能有很多不同,在 BFF 层可以做一些适配,给前端代码提供同一个的数据格式和接口格式。
  3. 裁剪, 同样的信息在不同的客户端有着不同的展现,比如手机屏幕尺寸较小,内存较小,CPU 性能较差,只能展示部分非常重要的信息,如果和电脑上使用同一个接口,势必导致手机上页面渲染变慢,还会浪费手机电量和网络流量。

我们在看到 BFF 带来的各种好处的同时,也要注意到它所带来的代码重复和工作量增加方面的问题。另外因为增加了一层调用链,也会对响应时间稍有影响。

之后我们来看我们如何快速开发

快速开发流程

这个是我们最终要进行去学习的。

这里我们要进行service层的代码生成器,来进行快速的生成。这里的pom文件,要考虑的就是兼容的问题,这个是有一定的要求的。如果兼容不好,这个会是一个大的问题,下面来介绍一下如何进行一个快速的版版本兼容。在了解了如何进行快速的版本兼容后。我们来介绍脚手架。

1
2
3
4
5
6
7
8
9
MVN archetype:generate 
-DgroupId=cn.felord.demo
-DartifactId=demo-project
-Dversion=1.0.0
-Dpackage=cn.felord.demo
-DarchetypeGroupId=cn.felord
-DarchetypeArtifactId=template-archetype
-DarchetypeVersion=1.0
-DinteractiveMode=false

大概就是一个这样的,之后我们创建一个这样的项目就可以了。

我们来对这个代码进行一个简单的介绍。

此代码使用 Maven 构建工具基于现有原型生成新项目。以下是每行含义的细分:

  • mvn archetype:generate:这会告诉 Maven 运行目标,该目标用于从原型创建新项目。archetype:generate
  • -DgroupId=cn.felord.demo:这会将新项目的组 ID 设置为“cn.felord.demo”。组 ID 是项目的唯一标识符,用于将其与同一包中的其他项目区分开来。
  • -DartifactId=demo-project:这会将新项目的工件 ID 设置为“演示项目”。项目 ID 是组中项目的唯一标识符,通常是项目的名称。
  • -Dversion=1.0.0:这会将新项目的版本号设置为“1.0.0”。如果需要,可以在以后更改。
  • -Dpackage=cn.felord.demo:这会将新项目的包名称设置为“cn.felord.demo”。这将确定项目的源文件将转到何处。
  • -DarchetypeGroupId=cn.felord:这将设置我们要用于创建新项目的原型的组 ID。在本例中,我们使用“cn.felord”原型。
  • -DarchetypeArtifactId=template-archetype:这将为我们要用于创建新项目的原型设置工件 ID。在本例中,我们使用“模板原型”工件。
  • -DarchetypeVersion=1.0:这将设置我们要用于创建新项目的原型的版本号。在本例中,我们使用的是原型版本“1.0”。
  • -DinteractiveMode=false:这告诉 Maven 在生成过程中不要提示用户输入。相反,它将仅使用指定的设置并创建项目,而无需请求确认。

因此,将它们放在一起,此代码生成一个名为“demo-project”的新Maven项目,版本为“1.0.0”,并将其打包在“cn.felord.demo”下。它使用“cn.felord”原型及其“模板原型”工件,也是版本“1.0”。生成过程会自动运行,而不会提示用户输入。

当然,这个是可以自己去自定义,如果自己嫌弃麻烦,最简单的idea也有自带的。

下面来介绍代码生成器

代码生成器

首先来看mybatis这种简单的代码生成器的一个原理。

image-20230804175342091

主要就是通过这俩个命令。

“Show table status”是一个 SQL 命令,用于显示有关数据库中特定表的各种详细信息。此命令的语法通常是 ,其中 是我们要显示其状态的表的名称。SHOW TABLE STATUS LIKE 'table_name';``table_name

此命令的输出包括几列,这些列提供有关表的信息,例如:

  1. Name:表的名称。
  2. Engine:表使用的存储引擎。常见的值包括InnoDB,MyISAM和Memory。
  3. Version:表格格式的版本号。
  4. Row_format:表中行的格式(例如,动态、固定或紧凑)。
  5. Rows:表中行数的估计值。
  6. Avg_row_length:行的平均长度(以字节为单位)。
  7. Data_length:表中所有数据的总长度(以字节为单位)。
  8. Max_data_length:单行的最大长度(以字节为单位)。
  9. Index_length:表上所有索引的总长度(以字节为单位)。
  10. Data_free:表中可用的可用空间量。

通过检查这些值,我们可以深入了解表的大小和结构,这对于优化查询、排查性能问题或规划架构的未来更改非常有用。

SHOW FULL COLUMNS FROM是一个 MySQL 命令,用于显示有关给定表中所有列的详细信息。它类似于语句,但它提供有关每列的更全面的信息,包括列的数据类型、排序规则、字符集和其他属性。DESCRIBE

以下是命令的每个部分含义的细分:

  • SHOW:此关键字表示我们要显示有关表的信息。
  • FULL:此关键字指定我们希望查看表中的所有列,包括隐藏的或具有默认值的列。
  • COLUMNS:这个关键字告诉MySQL我们希望看到有关表中列的信息。
  • FROM:此关键字指定我们要为其显示列的表。

例如,如果我们想查看名为 的表中的所有列,我们将使用以下命令:employees

1
SHOW FULL COLUMNS FROM employees;

这将显示表中所有列的列表,以及它们的数据类型、排序规则、字符集和其他属性。employees

请务必注意,某些列可能具有某些属性的值,具体取决于特定列和您使用的 MySQL 版本。此外,某些列可能指定了默认值,该值将显示在列名称后面的括号中。

通过这个就可以进行一个快速的生成了。

因为这个视频里面主要讲的就是原理,没有放出来代码生成器,所以我这里也只是说原理。在我的一点搜索后发现代码生成器使用手册 (altitude.xin)这里的代码生成器是不错的,有兴趣的可以去查看这个。这里在补充一个Vo的东西

Vo

随着编程工业化水平的不断加深,各种编程模型层出不穷(比如MVC,MVP等等),伴随着这些编程模型,又有一大批新的概念蜂拥而至,什么VO,BO,PO,DO,DTO之类的,这些新的概念一直以来都是云里雾里,网上虽然也有不少文章来区分这些概念,但看下来基本都是几篇相同的文章转载来转载去,这些文章本身也说的不明,有些还互相矛盾,再加上有些文章在简化系统里面来使用这些概念,让人越看越迷糊

什么原因造成了这种混乱的状态,就不深究了,感觉也很难究出所以然来
因此让我们立足这些概念本身,达成对概念理解的一致性,就足够了,这也是这边文章的主要目的
鉴于专业术语的解释互联网上太多,一搜一大把,我就不重复一遍术语了,而且说实话,术语太抽象,不利于理解,看完其实没解决啥疑惑,我会尽量用大白话(人话)来做解释,争取让大家都能看明白

废话不多说,先来看张图
看完图估计大部分人就已经有了一个直观的感受了

img

面对这个图,让我们先从承上启下的DTO开始入手

DTO(Data Transfer Object)数据传输对象

这个传输通常指的前后端之间的传输

DTO是一个比较特殊的对象,他有两种存在形式:

在后端,他的存在形式是java对象,也就是在controller里面定义的那个东东,通常在后端不需要关心怎么从json转成java对象的,这个都是由一些成熟的框架帮你完成啦,比如spring框架

在前端,他的存在形式通常是js里面的对象(也可以简单理解成json),也就是通过ajax请求的那个数据体

这也是为什么把他画成横跨两层的原因

这里可能会遇到个问题,现在微服务盛行,服务和服务之间调用的传输对象能叫DTO吗?
我的理解是看情况
DTO本身的一个隐含的意义是要能够完整的表达一个业务模块的输出
如果服务和服务之间相对独立,那就可以叫DTO
如果服务和服务之间不独立,每个都不是一个完整的业务模块,拆开可能仅仅是因为计算复杂度或者性能的问题,那这就不能够叫做DTO,只能是BO

VO(Value Object)值对象
VO就是展示用的数据,不管展示方式是网页,还是客户端,还是APP,只要是这个东西是让人看到的,这就叫VO
VO主要的存在形式就是js里面的对象(也可以简单理解成json)

VO和DTO的区别
主要有两个区别
一个是字段不一样,VO根据需要会删减一些字段
另一个是值不一样,VO会根据需要对DTO中的值进行展示业务的解释
举个简单的例子
DTO可能是这样的

1
2
3
4
{
"gender":"男",
"age":35
}

对于业务一来说只需要性别,而且因为是一个古风聊天室,也不能直接展示男,因此经过业务解释业务一的VO是

1
2
3
{ 
"gender":"公子"
}

对于业务二来说只需要年龄,而且不需要精确的年龄,因此经过业务解释业务二的VO是

1
2
3
{ 
"age":"30~39"
}

PO(Persistant Object)持久对象
PO比较好理解
简单说PO就是数据库中的记录,一个PO的数据结构对应着库中表的结构,表中的一条记录就是一个PO对象
通常PO里面除了get,set之外没有别的方法
对于PO来说,数量是相对固定的,一定不会超过数据库表的数量
等同于Entity,这俩概念是一致的

BO(Business Object)业务对象
BO就是PO的组合
简单的例子比如说PO是一条交易记录,BO是一个人全部的交易记录集合对象
复杂点儿的例子PO1是交易记录,PO2是登录记录,PO3是商品浏览记录,PO4是添加购物车记录,PO5是搜索记录,BO是个人网站行为对象
BO是一个业务对象,一类业务就会对应一个BO,数量上没有限制,而且BO会有很多业务操作,也就是说除了get,set方法以外,BO会有很多针对自身数据进行计算的方法
为什么BO也画成横跨两层呢?原因是现在很多持久层框架自身就提供了数据组合的功能,因此BO有可能是在业务层由业务来拼装PO而成,也有可能是在数据库访问层由框架直接生成
很多情况下为了追求查询的效率,框架跳过PO直接生成BO的情况非常普遍,PO只是用来增删改使用

BO和DTO的区别
这两个的区别主要是就是字段的删减
BO对内,为了进行业务计算需要辅助数据,或者是一个业务有多个对外的接口,BO可能会含有很多接口对外所不需要的数据,因此DTO需要在BO的基础上,只要自己需要的数据,然后对外提供
在这个关系上,通常不会有数据内容的变化,内容变化要么在BO内部业务计算的时候完成,要么在解释VO的时候完成

OK,到这里这些关系基本就理清楚了

等等,DO是什么
DO呢,标题不是还有个DO么?
上面这些概念基本上已经涵盖了全部的流程,DO只是跟其中一个概念相同
但是跟哪个概念相同呢?
现在主要有两个版本
一个是阿里巴巴的开发手册中的定义
DO( Data Object)这个等同于上面的PO
另一个是在DDD(Domain-Driven Design)领域驱动设计中
DO(Domain Object)这个等同于上面的BO

最后,让我们再说说实际应用
这几个概念很完整,我们在用的时候是必须按这个来做吗?
当然不是的,系统和系统的复杂度不同,协作水平不同,完全没有必要教条主义,这些概念全上
上哪些概念,省哪些,我给一些实际建议
1,PO这个没法省,不管叫PO还是Entity,怎么着都得有
2,一些工具类的系统和一些业务不是很复杂的系统DTO是可以和BO合并成一个,当业务扩展的时候注意拆分就行
3,VO是可以第一个优化掉的,展示业务不复杂的可以压根儿不要,直接用DTO
4,这也是最重要的一条,概念是给人用的,多人协作的时候一定要保证大家的概念一致,赶紧把这篇文章转发给跟你协作的人吧

版本兼容

知识背景

Jar包冲突在软件开发过程中是不可避免的,因此,如何快速定位冲突源,理解冲突导致的过程及底层原理,是每个程序员的必修课。也是提升工作效率、应对面试、在团队中脱颖而出的机会。

实践中能够直观感受到的Jar包冲突表现往往有这几种:

  • 程序抛出java.lang.ClassNotFoundException异常;
  • 程序抛出java.lang.NoSuchMethodError异常;
  • 程序抛出java.lang.NoClassDefFoundError异常;
  • 程序抛出java.lang.LinkageError异常等;

这是能够直观呈现的,当然还有隐性的异常,比如程序执行结果与预期不符等。下面,我们就分析一下Maven项目中Jar包的处理机制及引起冲突的原因。

Maven Jar包管理机制

在Maven项目中,想要了解Jar冲突必须得先了解一下Maven是如何管理的Jar包的。这涉及到Maven的一些特性,比如依赖传递、最短路径优先原则、最先声明原则等。

依赖传递原则

当在Maven项目中引入A的依赖,A的依赖通常又会引入B的jar包,B可能还会引入C的jar包。这样,当你在pom.xml文件中添加了A的依赖,Maven会自动的帮你把所有相关的依赖都添加进来。

比如,在Spring Boot项中,当引入了spring-boot-starter-web:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

此时Maven的依赖结构可能是这样的:

img

上面这种关系,我们就可以理解为依赖的传递性。即当一个依赖需要另外一个依赖支撑时,Maven会帮我们把相应的依赖依次添加到项目当中。

这样的好处是,使用起来就非常方便,不用自己挨个去找依赖Jar包了。坏处是会引起Jar包冲突,我们后面会讲到。

最短路径优先原则

依赖链路一:主要根据依赖的路径长短来决定引入哪个依赖(两个冲突的依赖)。

举例说明:

1
2
依赖链路一:A -> X -> Y -> Z(21.0)
依赖链路二:B -> Q -> Z(20.0)

项目中同时引入了A和B两个依赖,它们间接都引入了Z依赖,但由于B的依赖链路比较短,因此最终生效的是Z(20.0)版本。这就是最短路径优先原则。

此时如果Z的21.0版本和20.0版本区别较大,那么就会发生Jar包冲突的表现。

最先声明优先原则

如果两个依赖的路径一样,最短路径优先原则是无法进行判断的,此时需要使用最先声明优先原则,也就是说,谁的声明在前则优先选择。

举例说明:

1
2
依赖链路一:A -> X -> Z(21.0)
依赖链路二:B -> Q -> Z(20.0)

A和B最终都依赖Z,此时A的声明(pom中引入的顺序)优先于B,则针对冲突的Z会优先引入Z(21.0)。

如果Z(21.0)向下兼容Z(20.0),则不会出现Jar包冲突问题。但如果将B声明放前面,则有可能会发生Jar包冲突。

Jar包冲突产生的原因

上面讲了Maven维护Jar包的三个原则,其实每个原则会发生什么样的Jar包冲突,已经大概了解了。这里再来一个综合示例。

举例说明:

1
2
依赖链路一:A -> B -> C -> G21(guava 21.0)
依赖链路二:D -> F -> G20(guava 20.0)

假设项目中同时引入了A和D的依赖,按照依赖传递机制和默认依赖调节机制(第一:路径最近者优先;第二:第一声明优先),默认会引入G20版本的Jar包,而G21的Jar包不会被引用。

如果C中的方法使用了G21版本中的某个新方法(或类),由于Maven的处理,导致G21并未被引入。此时,程序在调用对应时便会抛出ClassNotFoundException异常,调用对应方法时便会抛出NoSuchMethodError异常。

排查定位Jar包冲突

在高版本的IDEA中已经自带了Maven依赖管理插件,依次执行:打开pom.xml文件,在文件内右击,选择Maven,选择Show Dependencies即可查看Maven的依赖层级结构:

img

执行之后展示的效果便是最开始的spring-boot-web那样效果,在图中可以清楚的看到都使用了哪些依赖,它们的层级,是否有冲突的jar包等。冲突部分会用红色标出,同时标出Maven默认选择了哪个版本。

如果你的IDEA版本中默认没有Maven管理插件,也可安装Maven Helper,通过这块插件来帮你分析Jar包冲突。

img

安装完插件,重启之后,打开pom.xml文件,在文件下面的Dependency Analyzer视图中便可以看到Jar包冲突的结果分析:

img

此时,关于哪些Jar包冲突了,便一目了然。同时,可以右击冲突的Jar包,执行”Exclude“进行排除,在pom.xml中便会自动添加排除jar包的属性。

对于本地环境可以利用Maven Helper等插件来解决,但在预生产或生成环境中就没那么方便了。此时可以通过mvn命令来定位突出的细节。

执行如下mvn命令:

1
mvn dependency:tree -Dverbose

注意不要省略-Dverbose,要不然不会显示被忽略的包。

执行结果如下:

img

通过这种形式,也可以清晰的看出哪些Jar包发生了冲突。

如何统一Jar包依赖

像上面截图所示,如果一个项目有N多个子项目构成,项目之间可能还有依赖关系,Jar包冲突不可避免,此时可采用父pom,统一对版本进行管理,一劳永逸。

通常做法,是在parent模块的pom文件中尽可能地声明所有相关依赖Jar包的版本,并在子pom中简单引用(不再指定版本)该构件即可。

比如在父pom.xml中定义Lombok的版本:

1
2
3
4
5
6
7
8
9
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
</dependencyManagement>

在子module中便可定义如下:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

通过这种方式,所有的子module中都采用了统一的版本。

解决Jar包冲突的方法

这里基于Maven项目介绍几种场景下解决Jar冲突的方法:

  • Maven默认处理:采用此种方法,要牢记Maven依赖调节机制的基本原则,路径最近者优先和第一声明优先;
  • 排除法:上面Maven Helper的实例中已经讲到,可以将冲突的Jar包在pom.xml中通过exclude来进行排除;
  • 版本锁定法:如果项目中依赖同一Jar包的很多版本,一个个排除非常麻烦,此时可用版本锁定法,即直接明确引入指定版本的依赖。根据前面介绍Maven处理Jar包基本原则,此种方式的优先级最高。这种方法一般采用上面我们讲到的如何统一Jar包依赖的方式。

Jar包冲突的本质

上面讲了Maven对项目中Jar包冲突的解决原则和实战层面的解决方案,但并未涉及到Jar包冲突的本质。关于Jar包冲突的本质在《从Jar包冲突搞到类加载机制,就是这么霸气》一文中已经进行详细的讲解了。这里再针对其中的几个关键点进行概述一下。

Jar包冲突的本质:Java应用程序因某种因素,加载不到正确的类而导致其行为跟预期不一致

具体分两种情况:

  • 情况一:项目依赖了同一Jar包的多个版本,并且选错了版本;
  • 情况二:同样的类在不同的Jar包中出现,导致JVM加载了错误的类;

情况一,也是本文重点讨论的场景,也就是说引入了多个Jar包版本,不同的Jar包版本有不同的类和方法。由于(不懂)Maven依赖树的仲裁机制导致Maven加载了错误的Jar包,从而导致Jar包冲突;

情况二,同一类在不同的Jar包中出现。这种情况是由于JVM的同一个类加载器对于同一个类只会加载一次,现在加载一个类之后,同全限定名的类便不会进行加载,从而出现Jar包冲突的问题。

针对第二种情况,如果不是类冲突抛出了异常,你可能根本意识不到,所以就显得更为棘手。这种情况就可以采用前文所述的通过分析不同类加载器的优先级及加载路径、文件系统的文件加载顺序等进行调整来解决。

小结

除了上述的方法,还很多小技巧来排查类冲突,比如通过IDE提供的搜索功能,直接搜索抛异常的类,看看是否存在多个,是否使用的是预期的版本等。这些技巧需要在实践的过程中不断的摸索和积累。

总之,无论项目多么庞大,依赖多么复杂,只要牢记导致冲突的原因,及解决冲突的几个方式,细心分析,总会有迹可循的。看完这篇文章,实践一下,你可能就会在团队中脱颖而出,成为Jar包冲突终结者