Dawn's Blogs

分享技术 记录成长

0%

Redis

通过引入 spring-boot-start-data-redis,就可以在 Spring Boot 中集成 Redis;同时也可以引入 spring-boot-starter-data-redis-reactive,支持与 Redis 的响应式异步交互。

使用

通过引入 starter,就可以注入一个自动配置的 RedisConnectionFactory、StringRedisTemplate、RedisTemplate。通过 StringRedisTemplate 就可以与 Redis 进行交互。

客户端

Spring Boot 支持两种 Redis 客户端,分别是 lettuce 和 jedis,lettuce 是默认的 Redis 客户端。

区别

lettuce 与 jedis 区别:

  • jedis 连接 Redis 服务器是直连模式,当多线程模式下使用 jedis 会存在线程安全问题,解决方案可以通过配置连接池使每个连接专用,但是会产生性能问题。
  • lettcus 基于 Netty 框架进行与 Redis 服务器连接,底层设计中采用 StatefulRedisConnection。StatefulRedisConnection 自身是线程安全的,可以保障并发访问安全问题,一个连接可以被多线程复用

MongoDB

通过引入 spring-boot-start-data-mongodb,就可以在 Spring Boot 中集成 MongoDB;同时也可以引入 spring-boot-starter-data-mongodb-reactive,支持与 MongoDB 的响应式异步交互。

可以注入 MongoTemplate 类来操作 MongoDB。

ElasticSearch

通过引入 spring-boot-start-data-elasticsearch,就可以在 Spring Boot 中集成 ES;同时也可以引入 spring-boot-starter-data-elasticsearch-reactive,支持与 ES 的响应式异步交互。

Spring Boot支持多个客户端。

  • 官方低级别的 REST 客户端(low-level REST client)
  • 官方的 Java API 客户端
  • Spring Data Elasticsearch 提供的 ReactiveElasticsearchClient

通过 ElasticsearchTemplate 来操作 ES。

日志

记录日志

在程序开发过程中,需要定义一个 Logger 类型的属性 log,用于记录日志:

1
private static final Logger log = LoggerFactory.getLogger(Xxx.class);

@Slf4j 注解

lombok 提供了 @Slf4j 注解,可以简化开发。在类上声明 @Slf4j 注解,可以自动生成一个 Logger 类型的属性 log。

日志格式

在配置文件中,使用 logging.pattern.console 设置日志格式。

输出日志

日志输出级别

日志级别分为六个等级:

  • trace:运行时的堆栈信息。
  • debug:测试的 Debug 信息。
  • info:记录运行过程中的数据。
  • warn:运行过程中的告警数据。
  • error:错误信息。
  • fatal:灾难信息,与 error 合并。

可以使用 Spring Boot 的配置文件,设置日志的输出级别。可以为根、包、日志组设置日志输出级别。

1
2
3
4
5
6
7
8
9
10
logging:
group: # 设置日志组
group1: com.group1.xxx,com.group1.yyy
level:
# 根日志级别
root: info
# 为包设置日志
com.dawnzzz: debug
# 为日志组设置级别
group1: debug

日志文件

使用配置文件中的 logging.file 属性,可以定义日志文件的配置信息。

如果使用 logback 作为日志工具,使用 logging.logback.rollingpolicy 属性,可以设置滚动滚动属性。

阅读全文 »

Spring Boot 介绍

Spring Boot 目标是减少 Spring 应用程序的配置。

自动依赖管理

parent

Spring Boot 所做的自动依赖管理,是由于在 Maven pom 配置文件中继承了 Spring Boot 的配置文件 spring-boot-parent,spring-boot-parent 有继承了 spring-boot-dependencies(本质上来说,就是在本地 pom 文件中引入了 spring-boot-dependencies,进行依赖管理避免依赖冲突)。

spring-boot-dependencies 中定义了依赖信息和版本信息。具体来说,是利用 properties 定义了版本信息,利用 dependencyManagement 定义依赖管理。

利用阿里云场景 Spring Boot 工程,使用的是引入 spring-boot-dependencies 的方式。

image-20240502163253884

image-20240502163312630

starter

starter 定义了当前项目所使用的依赖坐标,减少依赖配置。starter 在 Spring Boot 中的命名规范为 spring-boot-starter-xxx 或者 xxx-spring-boot-starter(第三方技术)。

在本地 pom 配置文件中导入 starter 依赖,就可以直接使用了。

image-20240502164630343

在引入 spring-boot-starter-web 时,运行程序会自动启动 tomcat 服务器(内嵌 tomcat 服务器),这是因为 spring-boot-starter-web 引用了 spring-boot-starter-tomcat。

内嵌 tomcat 服务器的原理就是,将 tomcat 服务器作为对象,在 Spring IOC 容器中管理起来

配置文件

Spring Boot 的默认配置文件为 application.properties,同时 Boot 支持三种配置文件格式:

  • properties
  • yml
  • yaml

优先级 properties > yml > yaml

可以在命令行参数中指定以下参数,来修改配置文件名:

  • –spring.config.name 参数,指定配置文件名(不包括扩展名)。
  • –spring.config.location 参数,指定配置文件全路径名。

配置文件位置

Spring boot 按照以下顺序读取配置文件,后面的配置会覆盖前面的配置。

  1. classpath(开发时)
    1. classpath 根路径。
    2. classpath 下的 /config 包。
  2. jar 当前目录(运行时):
    1. 当前目录下。
    2. 当前目录下的 config/ 子目录。
    3. config/ 子目录的直接子目录。

外部化配置

Spring Boot 支持外部化配置,可以使用各种外部配置源,包括Java properties 文件、YAML文件、环境变量和命令行参数。

读取配置信息

配置的属性值可以通过以下方式读取值

  • @Value 注入到 Bean 中。
  • 可以通过 Spring 的 Environment 访问(定义一个 Environment 类型的属性,使用 @Autowired 自动装配)。
  • 通过 @ConfigurationProperties 绑定对象(首先得将对象定义为 Spring Bean),或者 @EnableConfigurationProperties 注解。
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@ConfigurationProperties( prefix="myPrefix" )
class MyDataSource {
private String url;

public String getUrl() {
return url;
}

public setUrl(String url) {
this.url = url;
}
}

使用 @ConfigurationProperties 注解读取配置时,支持配置属性的宽松绑定(支持中划线、下划线、不区分大小写),前缀 prefix 命名规范仅仅支持小写字母、中划线、数字。

在 Maven 中引入 JSR303 及其实现类(如 Hibernate),使用 @Validated 注解开启数据校验功能,在字段上使用 @MAX、@MIN、@Email 等注解进行数据校验。

外部配置源顺序

Spring Boot 使用以下外部配置源的顺序,后面的配置会覆盖前面的配置:

  1. 默认属性(通过 SpringApplication.setDefaultProperties 指定)。
  2. @Configuration 类上的 @PropertySource 注解(这样的属性源直到 spring 容器启动时才会被加载,但是对于配置某些属性来说已经太晚了,如 logging 配置)。
  3. 配置文件,如 application.properties。
  4. RandomValuePropertySource
  5. 环境变量。
  6. Java System properties,使用 System.setProperties() 指定。
  7. java:comp/env 中的 JNDI 属性。
  8. ServletContext init parameters.
  9. ServletConfig init parameters.
  10. 来自 SPRING_APPLICATION_JSON 的属性(嵌入环境变量或系统属性中的内联 JSON)。
  11. 命令行参数,使用 --属性名=值 指定。
  12. 在测试中的 properties 和 args 属性, @SpringBootTest 注解指定 @SpringBootTest(properties = {"server.port=8080", ...}, args={"--server.port=8081", ...}),其中 args 属性比 properties 属性优先级高。
  13. 在测试中的 @DynamicPropertySource 注解。
  14. 测试中的 @TestPropertySource 注解.
  15. 当 devtools 处于活动状态时,$HOME/.config/spring-boot 目录下的Devtools全局设置属性。

多环境配置

Spring Boot 的多环境配置,可以在单个配置文件中指定,也可以在多个配置文件中指定,每一个环境对应一个配置文件(推荐)。

单文件配置

同一个配置文件中可以指定多个环境的配置,环境之间使用 --- 分隔,环境名称使用 spring.profiles 或者 spring.config.activate.on-profile 指定,使用的环境使用 spring.profiles.active 指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 指定环境名称
spring:
profiles:
active: dev

---
server:
port: 8080

# 旧版格式
spring:
profiles: dev

---
server:
port: 8081

# 新版格式
spring:
config:
activate:
on-profile: test

多文件配置

可以使用 application-环境名称.yml 指定多环境的配置信息。在主配置文件中,使用 spring.profiles.active 指定当前使用的环境。

可以根据功能对配置文件中的信息进行拆分,,如 application-devDB.yml、application-devRedis 等,之后在主配置文件中引入配置信息,方式有两种:

  • 使用 spring.profiles.include 引入多个配置,优先级为 spring.profiles.active 大于 spring.profiles.include
  • 使用 spring.profiles.group 指定配置组,配置组优先级大于 spring.profiles.active
1
2
3
4
5
6
spring:
profiles:
active: dev
group: # 指定配置组
"dev": devDB,devRedis,devMVC
"test": testDB,testRedis,testMVC

为了开发效率,可以将映射文件替换为注解开发。常用的 MyBatis 注解:

  • @Insert
  • @Delete
  • @Update
  • @Select
  • @Results:用于替换 ResultMapper 标签,可以在其中定义多个 @Result 集合。
    • @Result:替换了 ResultMapper 中标签,其中 one 属性配合 @One 注解代替了 association 标签的 select 属性;many 属性配合 @Many 注解代替了 collection 标签的 select 属性。
  • @Mapper:添加在 Mapper 接口中,目的是让 Spring 容器可以识别到。

多表查询

假设有两张表,分别是员工表和部门表。

多对一

查询员工以及员工所在的部门(需要在 User 实体类中定义一个 Dept 类型的属性 dept),可以有三种方法:

  • 级联方式
  • 使用 association
  • 分步查询

级联

使用级联方式,只需要在 resultMap 中定义 result 标签即可,dept 部门属性表示为 dept.xxx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<result column="did" property="dept.did"></result>
<result column="dname" property="dept.dname"></result>
</resultMap>

<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>

association

使用 association 标签,可以指明属性 dept 和属性类型 Dept,在 association 标签中定义部门的映射关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<association property="dept" javaType="Dept">
<id column="did" property="did"></id>
<result column="dname" property="dname"></result>
</association>
</resultMap>

<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>

分步查询

分步查询就是首先查询员工信息,然后根据员工信息查询部门信息。

具体就是使用 association 的 select 属性和 column 属性

  • select 属性:设置分步查询,查询某个属性的值的 sql 的标识。
  • column 属性:将第一步中的查询结果中的字段,设置为下一步查询的输入值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<resultMap id="empDeptStepMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--
select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
column:将sql以及查询结果中的某个字段设置为分步查询的条件
-->
<association property="dept"
select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column="did">
</association>
</resultMap>

<!--Emp getEmpByStep(@Param("eid") int eid);-->
<select id="getEmpByStep" resultMap="empDeptStepMap">
select * from t_emp where eid = #{eid}
</select>

<!--Dept getEmpDeptByStep(@Param("did") int did);-->
<select id="getEmpDeptByStep" resultType="Dept">
select * from t_dept where did = #{did}
</select>

一对多

查询部门下的所有员工(需要在 Dept 实体类中定义 User List),可以有以下几种方法:

  • collection
  • 分步查询

collection

使用 collection 标签可以表示一对多的关系,ofType 属性指明集合中存储的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>

<!--Dept getDeptEmpByDid(@Param("did") int did);-->
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did =
emp.did where dept.did = #{did}
</select>

分步查询

即先查询部门,再查询部门下的所有员工。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<collection property="emps" fetchType="eager"
select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column="did">
</collection>
</resultMap>

<!--Dept getDeptByStep(@Param("did") int did);-->
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
</select>

<!--List<Emp> getEmpListByDid(@Param("did") int did);-->
<select id="getEmpListByDid" resultType="Emp">
select * from t_emp where did = #{did}
</select>

延迟加载

分步查询的好处就是可以实现延迟加载

MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载。在 MyBatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false,然后通过设置 association 和 collection 中的 fetchType 属性设置当前分步查询是否延迟加载(lazy 延迟加载,eager 立即加载)。

原理

MyBatis 延迟加载实现的原理是,使用 CGLIB 创建目标对象的代理对象

当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName() ,拦截器 invoke() 方法发现 a.getB() 是 null 值,那么就会执行查询关联 B 对象的 sql 语句去查询,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。

缓存

MyBatis 有缓存的功能,将 select 语句缓存到内存中,下⼀次还是这条相同 select 语句的话,直接从缓存中取,不再查数据库。MyBatis 的缓存包括:

  • 一级缓存:将查询到的结果存储到 SqlSession 中。
  • 二级缓存:将查询到的数据存储到 SqlSessionFactory 中。

还有一些第三方缓存,如 EHCache、Memcache 等。

一级缓存

一级缓存是 SqlSession 级别的,同一个 SqlSession 下查询的数据会被缓存。一级缓存失效的原因:

  • 使用不同的 SqlSession。
  • 查询语句发生变化。
  • 查询之间有增删改操作。
  • 手动调用了 clearCache() 方法,手动清空一级缓存。

二级缓存

二级缓存是 SqlSessionFactory 级别的,二级缓存需要手动开启:

  1. 在配置文件中设置 <setting name="cacheEnabled" value="true"> 开启缓存,默认是 true。
  2. 在需要使⽤二级缓存的映射文件中添加一个标签:<catche />
  3. 使用二级缓存的实体类必须是可序列化的,也就是实现 java.io.Serializable 接口。
  4. SqlSession 对象关闭或者提交后,一级缓存中的数据才会被写入到二级缓存中。

配置

二级缓存在 <cache /> 标签中配置,可选的配置项:

  • eviction:淘汰算法。
    • LRU
    • FIFO
    • SOFT:淘汰软引用指向的对象。
    • WEEK:淘汰弱引用指向的对象。
  • flushInterval:刷新间隔,单位毫秒。默认是不刷新。
  • readOnly:只读属性。
    • true:缓存返回相同的实例,可能存在并发问题。
    • false:通过序列化返回对象的拷贝,线程安全,默认为 false。
  • size:缓存的对象数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出。

Mybatis 映射文件由 mapper 标签作为顶层标签,拥有属性 namespace。一个基本的映射文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<mapper namespace="ns">
<!-- 插入操作 -->
<insert id="insertID" parameterType="Xxx">
insert into xxx values(#{field1}, #{field2}, #{field3})
</insert>
<!-- 删除操作 -->
<delete id="deleteID", parameterType="Xxx">
delete frrom xxx where ...
</delete>
<!-- 更新操作 -->
<update id="updateID", parameterType="Xxx">
update xxx ser ... where ...
</update>
<!-- 查询操作 -->
<select id="selectID", parameterType="Xxx", resultType="Xxx">
select * from xxx where ...
</select>
</mapper>

${} 和 #{} 的区别:${} 本质上就是字符串的拼接,需要手动加上单引号;#{} 是占位符的方式。

阅读全文 »

配置文件用于配置 MyBatis,由 configuration 标签包裹。

MyBatis 配置文件中标签的顺序:

properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers

基本配置

properties

properties 标签用于引入 properties 文件。

1
<properties resource="jdbc.properties" />

typeAliases

typeAliases 用于设置多个类型的别名,可以包含 typeAlias 标签和 package 标签。

  • typeAlias 标签用于设置某个类型的别名,有两个属性 type 和 alias,用于指定类型和别名(不设置别名,则使用默认的别名,即类名且不区分大小写)。
  • package 标签,以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写。
1
2
3
4
<typeAliases>
<!--<typeAlias type="com.xxx.User"></typeAlias>-->
<package name="com.xxx"/>
</typeAliases>

MyBatis 中已经为常用类型设置了类型别名:

  • java.lang.Integer –> int|integer
  • int –> _int|_integer
  • Map –> map
  • List –> list
  • 。。。。。。

environments

environments 用于设置多个数据库连接配置,可以指定 default 环境 id。

environment 标签用于配置某个具体环境,属性 id 表示连接数据库的环境的唯一标识,不能重复。包含以下标签:

  • transactionManager 标签,设置事务管理方式。有属性 type,可选 JDBC 或者 MANAGED。
    • JDBC 表示当前环境中,执行 SQL 时,使用的是 JDBC 中原生的事务管理方式,事务的提交或回滚需要手动处理。
    • MANAGED 表示被管理,例如 Spring。
  • dataSource 标签,表示配置数据源。有属性 type 表示数据源类型,可选值为 POOLED(使用连接池)/UNPOOLED(不使用连接池)/JNDI(使用上下文中的数据源)。可以包含 property 标签用于配置数据库驱动、连接地址、用户名、密码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--设置连接数据库的驱动-->
<property name="driver" value="${jdbc.driver}"/>
<!--设置连接数据库的连接地址-->
<property name="url" value="${jdbc.url}"/>
<!--设置连接数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--设置连接数据库的密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>

mappers

mappers 用于引入映射文件,可以包含 mapper 或者 package 标签。

  • mapper 标签,可以指定 resource 属性(或者 url、class 属性),用于引入 mapper 映射文件。
  • package 标签,以包为单位引入映射文件,但是有两个要求:(1)mapper 接口所在的包要和映射文件所在的包一致(2)mapper 接口要和映射文件的名字一致。
1
2
3
4
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<package name="com.xxx.mapper"/>
</mappers>
阅读全文 »

介绍

MyBatis 是 Java 的持久层框架,内部封装了 jdbc,使开发者只需要关注 SQL 语句本身,不需要精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

MyBatis 通过 XML 或者注解的方式将要执行的各个 statement 配置,通过 Java 对象和 statement 中的动态参数进行映射,并生成最终执行的 SQL 语句。最后 MyBatis 执行 SQL 语句并将结果映射为 Java 对象并返回(ORM)。

开发流程

MyBatis 的开发流程:

  1. 编写核心配置文件。
  2. 定义实体类。
  3. 定义 Mapper 接口,定义数据库操作。
  4. 编写映射文件。

Interceptor

Interceptor 是 Spring MVC 框架中的一个组件,用于拦截和处理HTTP请求。在 Java Spring 中,Interceptor 是通过实现 HandlerInterceptor 接口来实现的。Interceptor 可以对请求进行预处理,也可以对响应进行后处理。Interceptor 的主要作用是在请求到达 Controller 之前或响应离开 Controller 之后,对请求和响应进行处理。

使用开发方法

使用方法就是:定义拦截器类,实现 HandlerInterceptor 接口,并重写其中的方法。

  • preHandle:在调用 Handler 之前执行,返回值决定了是否会调用 Handler。如果返回值为 false,不会执行 Handler,也不会执行下面的两个方法。
  • postHandle:在 Handler 执行完成之后执行。
  • afterCompletion:在 DispatcherServlet 进行视图的渲染之后执行,多用于清理资源。

和 Filter 的区别

Interceptor 和 Filter 都可以拦截和处理请求,但是有以下区别:

  1. 实现方式。Filter 是通过实现 javax.servlet.Filter 接口来实现的,而 Interceptor 是通过实现 HandlerInterceptor 接口来实现的。因此,Filter 依赖于 Servlet API,而 Interceptor 依赖于 Spring MVC 框架。
  2. 使用范围。Filter 不仅可以应用于 Web 应用程序,还可以应用于其他基于 Java 的应用程序,如 Java SE、Java EE 等。而 Interceptor 仅适用于基于 Spring MVC 框架的 Web 应用程序。
  3. 生命周期。Filter 的生命周期比 Interceptor 更加灵活。Filter 可以在请求到达 Controller 之前或响应离开 Controller 之后的任何阶段进行拦截和处理。而 Interceptor 的拦截和处理只能在 Controller 方法执行之前或之后进行。

统一异常处理

统一的异常处理方式,就是定义 ExceptionHandler。具体来说,就是使用 @ControllerAdvice 和 @ExceptionHandler 两个注解。

这种异常处理下,会给所有或者指定的 Controller 织入异常处理的逻辑(AOP)。当 Controller 方法抛出异常后,由被 @ExceptionHandler 修饰的方法进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

@ExceptionHandler(BaseException.class)
public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
//......
}

@ExceptionHandler(value = ResourceNotFoundException.class)
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
//......
}
}

原理

ExceptionHandlerMethodResolver 中 getMappedMethod 方法决定了异常具体被哪个被 @ExceptionHandler 注解修饰的方法处理异常。

具体来说,就是首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(匹配程度最高的那一个)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
//找到可以处理的所有异常信息。mappedMethods 中存放了异常和处理异常的方法的对应关系
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 不为空说明有方法处理异常
if (!matches.isEmpty()) {
// 按照匹配程度从小到大排序
matches.sort(new ExceptionDepthComparator(exceptionType));
// 返回处理异常的方法
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}