fix: 更新文档格式

pull/67/head
yanglbme 5 years ago
parent 53c92261d1
commit 580e9c74c1

@ -1,20 +1,25 @@
## JDK的SPI思想 ## JDK 的 SPI 思想
SPI即 Service Provider Interface。在面向对象的设计里面模块之间推荐基于接口编程而不是对实现类进行硬编码这样做也是为了模块设计的可插拔原则。 SPI即 Service Provider Interface。在面向对象的设计里面模块之间推荐基于接口编程而不是对实现类进行硬编码这样做也是为了模块设计的可插拔原则。
比较典型的应用,如 JDBCJava 定义了一套 JDBC 的接口,但是 Java 本身并不提供对 JDBC 的实现类,而是开发者根据项目实际使用的数据库来选择驱动程序 jar包比如 mysql你就将 mysql-jdbc-connector.jar 引入进来oracle你就将 oracle-jdbc-connector.jar 引入进来。在系统跑的时候,碰到你使用 jdbc 的接口,他会在底层使用你引入的那个 jar 中提供的实现类。 比较典型的应用,如 JDBCJava 定义了一套 JDBC 的接口,但是 Java 本身并不提供对 JDBC 的实现类,而是开发者根据项目实际使用的数据库来选择驱动程序 jar 包,比如 mysql你就将 mysql-jdbc-connector.jar 引入进来oracle你就将 oracle-jdbc-connector.jar 引入进来。在系统跑的时候,碰到你使用 jdbc 的接口,他会在底层使用你引入的那个 jar 中提供的实现类。
## Dubbo 的 SPI 扩展机制原理
## Dubbo的SPI扩展机制原理 dubbo 自己实现了一套 SPI 机制,并对 JDK 的 SPI 进行了改进。
dubbo自己实现了一套SPI机制并对 JDK的SPI进行了改进。
1. JDK标准的SPI只能通过遍历来查找扩展点和实例化有可能导致一次性加载所有的扩展点如果不是所有的扩展点都被用到就会导致资源的浪费。dubbo每个扩展点都有多种实现例如com.alibaba.dubbo.rpc.Protocol接口有InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol等实现如果只是用到其中一个实现可是加载了全部的实现会导致资源的浪费。
2. 对配置文件中扩展实现的格式的修改例如META-INF/dubbo/com.xxx.Protocol 里的 com.foo.XxxProtocol格式 改为了 xxx = com.foo.XxxProtocol 这种以键值对的形式这样做的目的是为了让我们更容易的定位到问题。比如由于第三方库不存在无法初始化导致无法加载扩展点“A”当用户配置使用A时dubbo就会报无法加载扩展点的错误而不是报哪些扩展点的实现加载失败以及错误原因**这是因为原来的配置格式没有记录扩展名的id导致dubbo无法抛出较为精准的异常这会加大排查问题的难度**。所以改成key-value的形式来进行配置。
3. dubbo的SPI机制增加了对IOC、AOP的支持一个扩展点可以直接通过setter注入到其他扩展点。
下面我们看一下Dubbo 的 SPI扩展机制实现的结构目录。 1. JDK 标准的 SPI 只能通过遍历来查找扩展点和实例化有可能导致一次性加载所有的扩展点如果不是所有的扩展点都被用到就会导致资源的浪费。dubbo 每个扩展点都有多种实现例如com.alibaba.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol 等实现,如果只是用到其中一个实现,可是加载了全部的实现,会导致资源的浪费。
2. 对配置文件中扩展实现的格式的修改例如META-INF/dubbo/com.xxx.Protocol 里的 com.foo.XxxProtocol 格式 改为了 xxx = com.foo.XxxProtocol 这种以键值对的形式这样做的目的是为了让我们更容易的定位到问题。比如由于第三方库不存在无法初始化导致无法加载扩展点“A”当用户配置使用 A 时dubbo 就会报无法加载扩展点的错误,而不是报哪些扩展点的实现加载失败以及错误原因,**这是因为原来的配置格式没有记录扩展名的 id导致 dubbo 无法抛出较为精准的异常,这会加大排查问题的难度**。所以改成 key-value 的形式来进行配置。
3. dubbo 的 SPI 机制增加了对 IOC、AOP 的支持,一个扩展点可以直接通过 setter 注入到其他扩展点。
下面我们看一下 Dubbo 的 SPI 扩展机制实现的结构目录。
![avatar](../../../images/Dubbo/SPI组件目录结构.png) ![avatar](../../../images/Dubbo/SPI组件目录结构.png)
### SPI 注解 ### SPI 注解
首先看一下 SPI注解。在某个接口上加上 @SPI 注解后表明该接口为可扩展接口。比如协议扩展接口Protocol如果使用者在 <dubbo:protocol />、<dubbo:service />、<dubbo:reference /> 都没有指定 protocol属性 的话,那么就默认使用 DubboProtocol 作为接口Protocol的实现因为在 Protocol 上有 @SPI("dubbo")注解。而这个 protocol属性值 或者默认值会被当作该接口的实现类中的一个keydubbo 会去 META-INF.dubbo.internal下的com.alibaba.dubbo.rpc.Protocol文件中找该key对应的value源码如下。
首先看一下 SPI 注解。在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。比如,协议扩展接口 Protocol如果使用者在 <dubbo:protocol />、<dubbo:service />、<dubbo:reference /> 都没有指定 protocol 属性 的话,那么就默认使用 DubboProtocol 作为接口 Protocol 的实现,因为在 Protocol 上有 @SPI("dubbo")注解。而这个 protocol 属性值 或者默认值会被当作该接口的实现类中的一个 keydubbo 会去 META-INF.dubbo.internal 下的 com.alibaba.dubbo.rpc.Protocol 文件中找该 key 对应的 value源码如下。
```java ```java
/** /**
* 协议接口 * 协议接口
@ -107,10 +112,13 @@ public @interface SPI {
// 配置文件 com.alibaba.dubbo.rpc.Protocol 中的内容 // 配置文件 com.alibaba.dubbo.rpc.Protocol 中的内容
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
``` ```
value 就是该 Protocol接口 的实现类 DubboProtocol这样就做到了SPI扩展。
value 就是该 Protocol 接口 的实现类 DubboProtocol这样就做到了 SPI 扩展。
### ExtensionLoader ### ExtensionLoader
ExtensionLoader 扩展加载器,这是 dubbo 实现 SPI扩展机制 的核心,几乎所有实现的逻辑都被封装在 ExtensionLoader 中,其源码如下。
ExtensionLoader 扩展加载器,这是 dubbo 实现 SPI 扩展机制 的核心,几乎所有实现的逻辑都被封装在 ExtensionLoader 中,其源码如下。
```java ```java
/** /**
* 拓展加载器Dubbo使用的扩展点获取 * 拓展加载器Dubbo使用的扩展点获取

@ -1,38 +1,51 @@
## 项目结构 ## 项目结构
首先从GitHub 上 clone下来 Dubbo项目我们根据其中各子项目的项目名也能大概猜出来各个模块的作用。
首先从 GitHub 上 clone 下来 Dubbo 项目,我们根据其中各子项目的项目名,也能大概猜出来各个模块的作用。
![avatar](../../../images/Dubbo/dubbo项目结构.png) ![avatar](../../../images/Dubbo/dubbo项目结构.png)
### dubbo-common ### dubbo-common
公共逻辑子项目,定义了各子项目中 通用的 组件 和 工具类IO、日志、配置处理等。 公共逻辑子项目,定义了各子项目中 通用的 组件 和 工具类IO、日志、配置处理等。
### dubbo-rpc ### dubbo-rpc
分布式协调服务框架的核心,该模块定义了 RPC相关的组件包括 服务发布、服务调用代理、远程调用结果、RPC调用网络协议RPC调用监听器和过滤器等等。该模块提供了默认的 基于dubbo协议的实现还提供了hessian、http、rmi、及webservice等协议的实现能够满足绝大多数项目的使用需求另外 还提供了对自定义协议的扩展。
分布式协调服务框架的核心,该模块定义了 RPC 相关的组件,包括 服务发布、服务调用代理、远程调用结果、RPC 调用网络协议RPC 调用监听器和过滤器等等。该模块提供了默认的 基于 dubbo 协议的实现,还提供了 hessian、http、rmi、及 webservice 等协议的实现,能够满足绝大多数项目的使用需求,另外 还提供了对自定义协议的扩展。
### dubbo-registry ### dubbo-registry
注册中心子项目,它是 RPC 中 consumer服务消费者 和 provider服务提供者 两个重要角色的协调者,该子项目定义了核心的 注册中心组件,提供了 mutilcast、redis 和 zookeeper 等多种方式的注册中心实现用于不同的使用场景。当然几乎所有的项目都会选择基于zookeeper的实现。
注册中心子项目,它是 RPC 中 consumer 服务消费者 和 provider 服务提供者 两个重要角色的协调者,该子项目定义了核心的 注册中心组件,提供了 mutilcast、redis 和 zookeeper 等多种方式的注册中心实现,用于不同的使用场景。当然,几乎所有的项目都会选择基于 zookeeper 的实现。
### dubbo-remoting ### dubbo-remoting
远程通讯子项目RPC 的实现基础就是远程通讯consmer 要调用 provider 的远程方法必须通过 远程通讯实现。该模块定义了远程传输器、endpoint 终端、客户端、服务端、编码解码器、数据交换、缓冲区、通讯异常定义 等核心组件。他是对于远程网络通讯的抽象,提供了诸如 netty、mina、http等 协议和技术框架的实现方式。
远程通讯子项目RPC 的实现基础就是远程通讯consmer 要调用 provider 的远程方法必须通过 远程通讯实现。该模块定义了远程传输器、endpoint 终端、客户端、服务端、编码解码器、数据交换、缓冲区、通讯异常定义 等核心组件。他是对于远程网络通讯的抽象,提供了诸如 netty、mina、http 等 协议和技术框架的实现方式。
### dubbo-monitor ### dubbo-monitor
监控子项目,该模块可以监控服务调用的各种信息,例如调用耗时、调用量、调用结果等等,监控中心在调用过程中收集调用的信息,发送到监控服务,在监控服务中可以存储这些信息,对这些数据进行统计分析 和 展示。dubbo默认提供了一个实现该实现非常简单只是作为默认的实现范例生产环境使用价值不高往往需要自行实现。
监控子项目,该模块可以监控服务调用的各种信息,例如调用耗时、调用量、调用结果等等,监控中心在调用过程中收集调用的信息,发送到监控服务,在监控服务中可以存储这些信息,对这些数据进行统计分析 和 展示。dubbo 默认提供了一个实现,该实现非常简单,只是作为默认的实现范例,生产环境使用价值不高,往往需要自行实现。
### dubbo-container ### dubbo-container
容器子项目,是一个独立的容器,以简单的 Main(类) 加载Spring启动因为服务通常不需要Tomcat/JBoss等Web容器的特性没必要用Web容器去加载服务。
容器子项目,是一个独立的容器,以简单的 Main(类) 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。
### dubbo-config ### dubbo-config
配置中心子项目,该模块通过 配置信息将dubbo组件的各个模块整合在一起给 框架的使用者 提供 可配置的、易用的 分布式服务框架。它定义了面向dubbo使用者的各种信息配置比如服务发布配置、方法发布配置、服务消费配置、应用程序配置、注册中心配置、协议配置、监控配置等等。
配置中心子项目,该模块通过 配置信息,将 dubbo 组件的各个模块整合在一起,给 框架的使用者 提供 可配置的、易用的 分布式服务框架。它定义了面向 dubbo 使用者的各种信息配置,比如服务发布配置、方法发布配置、服务消费配置、应用程序配置、注册中心配置、协议配置、监控配置等等。
### dubbo-cluster ### dubbo-cluster
集群子项目,将多个服务提供方伪装为一个提供方,包括:负载均衡、容错、路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。 集群子项目,将多个服务提供方伪装为一个提供方,包括:负载均衡、容错、路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。
### dubbo-admin ### dubbo-admin
该子项目是一个web应用可以独立部署用于管理 dubbo服务该管理应用可以连接注册中心读取和更新 注册中心中的内容。
该子项目是一个 web 应用,可以独立部署,用于管理 dubbo 服务,该管理应用可以连接注册中心,读取和更新 注册中心中的内容。
## 实现原理 ## 实现原理
### 角色类型与运行原理 ### 角色类型与运行原理
一个Dubbo项目 的角色主要分为如下五种。
一个 Dubbo 项目 的角色主要分为如下五种。
- Provider服务提供方 - Provider服务提供方
- Consumer服务消费方 - Consumer服务消费方
- Registry服务注册与发现的注册中心 - Registry服务注册与发现的注册中心
@ -44,7 +57,9 @@
![avatar](../../../images/Dubbo/Dubbo工作原理图.png) ![avatar](../../../images/Dubbo/Dubbo工作原理图.png)
### 工作原理 ### 工作原理
最后总结下其工作原理。 最后总结下其工作原理。
1. 服务导出:服务提供方 导出服务,监听服务端口; 1. 服务导出:服务提供方 导出服务,监听服务端口;
2. 服务注册:服务提供方 注册服务信息到注册中心; 2. 服务注册:服务提供方 注册服务信息到注册中心;
3. 服务订阅:服务消费方 订阅关注的服务; 3. 服务订阅:服务消费方 订阅关注的服务;

@ -1,26 +1,30 @@
### 集群模块简介 ### 集群模块简介
集群,是指同一个服务 被部署在了多个服务器上,每个服务器的任务都相同,能够以较高的性价比,提升系统的 性能、可靠性、灵活性,但同时也要面对 集群中会出现的 负载均衡、容错等问题。dubbo的集群模块主要涉及以下几部分内容。
- 负载均衡策略dubbo支持的所有负载均衡策略算法 集群,是指同一个服务 被部署在了多个服务器上,每个服务器的任务都相同,能够以较高的性价比,提升系统的 性能、可靠性、灵活性,但同时也要面对 集群中会出现的 负载均衡、容错等问题。dubbo 的集群模块,主要涉及以下几部分内容。
- 负载均衡策略dubbo 支持的所有负载均衡策略算法;
- 集群容错Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker对上层透明伪装过程包含了容错逻辑调用失败后重试另一个 - 集群容错Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker对上层透明伪装过程包含了容错逻辑调用失败后重试另一个
- 路由dubbo路由规则路由规则决定了一次dubbo服务调用的目标服务器路由规则分两种条件路由规则和脚本路由规则并且支持可拓展 - 路由dubbo 路由规则,路由规则决定了一次 dubbo 服务调用的目标服务器,路由规则分两种:条件路由规则和脚本路由规则,并且支持可拓展;
- 配置根据url上的配置规则生成配置信息 - 配置:根据 url 上的配置规则生成配置信息;
- 分组聚合:合并返回结果; - 分组聚合:合并返回结果;
- 本地伪装mock通常用于服务降级mock只在非业务异常时执行如 超时、网络异常等。 - 本地伪装mock 通常用于服务降级mock 只在非业务异常时执行,如 超时、网络异常等。
集群工作过程可分为两个阶段,第一个阶段是在消费者初始化期间,集群 Cluster 为消费者创建 ClusterInvoker 实例。第二个阶段是在消费者进行RPC时以 FailoverClusterInvoker 为例,该实例首先会调用 Directory 的 list()方法 获取 Invoker列表然后根据配置的 负载均衡策略,从 Invoker列表 中选择一个 Inovker最后将参数传给选择出的 Invoker实例 进行真正的远程调用。 集群工作过程可分为两个阶段,第一个阶段是在消费者初始化期间,集群 Cluster 为消费者创建 ClusterInvoker 实例。第二个阶段是在消费者进行 RPC 时,以 FailoverClusterInvoker 为例,该实例首先会调用 Directory 的 list()方法 获取 Invoker 列表,然后根据配置的 负载均衡策略,从 Invoker 列表 中选择一个 Inovker最后将参数传给选择出的 Invoker 实例 进行真正的远程调用。
可将上文中出现的 Invoker 简单理解为服务提供者Directory 的用途是保存 Invoker列表实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后RegistryDirectory 会动态增删 Inovker并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。 可将上文中出现的 Invoker 简单理解为服务提供者Directory 的用途是保存 Invoker 列表,实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后RegistryDirectory 会动态增删 Inovker并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。
下面我们来看一下 集群模块的项目结构图,结合上文的描述,可以对其有更加深刻的理解。 下面我们来看一下 集群模块的项目结构图,结合上文的描述,可以对其有更加深刻的理解。
![avatar](../../../images/Dubbo/dubbo-cluster模块工程结构.png) ![avatar](../../../images/Dubbo/dubbo-cluster模块工程结构.png)
### 集群模块核心API 源码解析 ### 集群模块核心 API 源码解析
从上图应该也能看出其核心API在哪个包里。
从上图应该也能看出其核心 API 在哪个包里。
![avatar](../../../images/Dubbo/com.alibaba.dubbo.rpc.cluster包目录.png) ![avatar](../../../images/Dubbo/com.alibaba.dubbo.rpc.cluster包目录.png)
各核心接口的源码如下。 各核心接口的源码如下。
```java ```java
/** /**
* 集群接口 * 集群接口

@ -1,15 +1,20 @@
## Dubbo 负载均衡简介 ## Dubbo 负载均衡简介
负载均衡,无论在常用的中间件 及 框架中,还是现实生活中,都有所体现。比如,一个团队干活,老大肯定要尽可能把任务均匀合理地分下去,让整个团队能高速运转,能力强的多分点,能力弱的少分点,绝对不能去逮着一个人 让他累到死,让其它人闲着。这样的均匀分配任务及压力的思想 放在开发领域 即是“负载均衡”。它就相当于是一个压力均衡机制,通过各种策略,为集群中的每台服务器合理地分配压力,这样 即能提升整个集群的运行效率,又能尽量避免 某个节点因为压力过大而宕机。 负载均衡,无论在常用的中间件 及 框架中,还是现实生活中,都有所体现。比如,一个团队干活,老大肯定要尽可能把任务均匀合理地分下去,让整个团队能高速运转,能力强的多分点,能力弱的少分点,绝对不能去逮着一个人 让他累到死,让其它人闲着。这样的均匀分配任务及压力的思想 放在开发领域 即是“负载均衡”。它就相当于是一个压力均衡机制,通过各种策略,为集群中的每台服务器合理地分配压力,这样 即能提升整个集群的运行效率,又能尽量避免 某个节点因为压力过大而宕机。
在 Dubbo 中也需要负载均衡机制,将消费者的请求 合理分配到服务提供者集群的各个节点上,以提升集群的整体运行效率 和 避免单个节点压力过大而宕机的问题。Dubbo 提供了4种负载均衡实现缺省为 RandomLoadBalance 加权随机调用,如下。 在 Dubbo 中也需要负载均衡机制,将消费者的请求 合理分配到服务提供者集群的各个节点上,以提升集群的整体运行效率 和 避免单个节点压力过大而宕机的问题。Dubbo 提供了 4 种负载均衡实现,缺省为 RandomLoadBalance 加权随机调用,如下。
- RandomLoadBalance加权随机算法按权重设置随机概率 - RandomLoadBalance加权随机算法按权重设置随机概率
- RoundRobinLoadBalance加权轮询算法按公约后的权重设置轮询比率 - RoundRobinLoadBalance加权轮询算法按公约后的权重设置轮询比率
- LeastActiveLoadBalance最少响应时间算法使快速响应的服务提供者 接收更多请求,慢的提供者收到更少请求; - LeastActiveLoadBalance最少响应时间算法使快速响应的服务提供者 接收更多请求,慢的提供者收到更少请求;
- ConsistentHashLoadBalance一致性hash算法相同参数的请求总是发到同一提供者。 - ConsistentHashLoadBalance一致性 hash 算法,相同参数的请求总是发到同一提供者。
## 源码赏析 ## 源码赏析
### LoadBalance接口 和 AbstractLoadBalance
AbstractLoadBalance 实现了 LoadBalance接口是负载均衡的抽象类提供了权重计算等通用功能。 ### LoadBalance 接口 和 AbstractLoadBalance
AbstractLoadBalance 实现了 LoadBalance 接口,是负载均衡的抽象类,提供了权重计算等通用功能。
```java ```java
/** /**
* LoadBalance. (SPI, Singleton, ThreadSafe) * LoadBalance. (SPI, Singleton, ThreadSafe)
@ -74,7 +79,9 @@ public abstract class AbstractLoadBalance implements LoadBalance {
``` ```
### RandomLoadBalance ### RandomLoadBalance
该类是基于权重随机算法的负载均衡实现类,我们先来讲讲原理,比如我有有一组服务器 servers = [A, B, C],他们他们对应的权重为 weights = [6, 3, 1]权重总和为10现在把这些权重值平铺在一维坐标值上分别出现三个区域A区域为[0,6)B区域为[6,9)C区域为[9,10),然后产生一个[0, 10)的随机数,看该数字落在哪个区间内,就用哪台服务器,这样权重越大的,被击中的概率就越大。
该类是基于权重随机算法的负载均衡实现类,我们先来讲讲原理,比如我有有一组服务器 servers = [A, B, C],他们他们对应的权重为 weights = [6, 3, 1],权重总和为 10现在把这些权重值平铺在一维坐标值上分别出现三个区域A 区域为[0,6)B 区域为[6,9)C 区域为[9,10),然后产生一个[0, 10)的随机数,看该数字落在哪个区间内,就用哪台服务器,这样权重越大的,被击中的概率就越大。
```java ```java
/** /**
* random load balance. * random load balance.
@ -123,7 +130,9 @@ public class RandomLoadBalance extends AbstractLoadBalance {
``` ```
### RoundRobinLoadBalance ### RoundRobinLoadBalance
该类是负载均衡基于加权轮询算法的实现,在 nginx 中也有类似的实现。当我们的服务器 性能之间存在明显差异,并希望请求均匀地落到各服务器上,就需要用到加权轮询。 该类是负载均衡基于加权轮询算法的实现,在 nginx 中也有类似的实现。当我们的服务器 性能之间存在明显差异,并希望请求均匀地落到各服务器上,就需要用到加权轮询。
```java ```java
/** /**
* Round robin load balance. * Round robin load balance.
@ -222,7 +231,9 @@ public class RoundRobinLoadBalance extends AbstractLoadBalance {
``` ```
### LeastActiveLoadBalance ### LeastActiveLoadBalance
该负载均衡策略基于最少活跃调用数算法某个服务活跃调用数越小表明该服务提供者效率越高也就表明单位时间内能够处理的请求更多。此时应该选择该类服务器。实现很简单就是每一个服务都有一个活跃数active来记录该服务的活跃值每收到一个请求该active就会加1每完成一个请求active就减1。在服务运行一段时间后性能好的服务提供者处理请求的速度更快因此活跃数下降的也越快此时这样的服务提供者能够优先获取到新的服务请求。除了最小活跃数还引入了权重值也就是当活跃数一样的时候选择利用权重法来进行选择如果权重也一样那么随机选择一个。
该负载均衡策略基于最少活跃调用数算法,某个服务活跃调用数越小,表明该服务提供者效率越高,也就表明单位时间内能够处理的请求更多。此时应该选择该类服务器。实现很简单,就是每一个服务都有一个活跃数 active 来记录该服务的活跃值,每收到一个请求,该 active 就会加 1每完成一个请求active 就减 1。在服务运行一段时间后性能好的服务提供者处理请求的速度更快因此活跃数下降的也越快此时这样的服务提供者能够优先获取到新的服务请求。除了最小活跃数还引入了权重值也就是当活跃数一样的时候选择利用权重法来进行选择如果权重也一样那么随机选择一个。
```java ```java
/** /**
* LeastActiveLoadBalance * LeastActiveLoadBalance
@ -290,26 +301,28 @@ public class LeastActiveLoadBalance extends AbstractLoadBalance {
``` ```
### ConsistentHashLoadBalance ### ConsistentHashLoadBalance
该类是负载均衡基于 hash一致性算法的实现。一致性哈希算法的工作原理如下。
1. 首先根据 ip 或其他的信息为缓存节点生成一个 hash在dubbo中使用参数进行计算hash。并将这个 hash 投射到 [0, 232 - 1] 的圆环上,当有查询或写入请求时,则生成一个 hash 值。 该类是负载均衡基于 hash 一致性算法的实现。一致性哈希算法的工作原理如下。
2. 然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。
1. 首先根据 ip 或其他的信息为缓存节点生成一个 hash在 dubbo 中使用参数进行计算 hash。并将这个 hash 投射到 [0, 232 - 1] 的圆环上,当有查询或写入请求时,则生成一个 hash 值。
2. 然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。
大致效果如下图所示引用一下官网的图。每个缓存节点在圆环上占据一个位置如果缓存项key 的 hash值小于缓存节点 hash值则到该缓存节点中存储或读取缓存项这里有两个概念不要弄混缓存节点就好比dubbo中的服务提供者会有很多的服务提供者而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中也就是调用cache-4 这个服务提供者。 大致效果如下图所示(引用一下官网的图)。每个缓存节点在圆环上占据一个位置,如果缓存项 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项,这里有两个概念不要弄混,缓存节点就好比 dubbo 中的服务提供者,会有很多的服务提供者,而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中,也就是调用 cache-4 这个服务提供者。
![avatar](../../../images/Dubbo/一致性hash算法1.png) ![avatar](../../../images/Dubbo/一致性hash算法1.png)
但 hash一致性算法 并不能够保证 负载的平衡性就拿上面的例子来看cache-3挂掉了那该节点下的所有缓存项都要存储到 cache-4 节点中这就导致hash值低的一直往高的存储会面临一个不平衡的现象见下图 但 hash 一致性算法 并不能够保证 负载的平衡性就拿上面的例子来看cache-3 挂掉了,那该节点下的所有缓存项都要存储到 cache-4 节点中,这就导致 hash 值低的一直往高的存储,会面临一个不平衡的现象,见下图:
![avatar](../../../images/Dubbo/一致性hash算法2.png) ![avatar](../../../images/Dubbo/一致性hash算法2.png)
可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入 “虚拟节点”,“虚拟节点” 是实际节点在 hash 空间的复制品,“虚拟节点” 在 hash空间 中以 hash值 排列,如下图。 可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入 “虚拟节点”,“虚拟节点” 是实际节点在 hash 空间的复制品,“虚拟节点” 在 hash 空间 中以 hash 值 排列,如下图。
![avatar](../../../images/Dubbo/一致性hash算法3.png) ![avatar](../../../images/Dubbo/一致性hash算法3.png)
可以看到各个节点都被均匀分布在圆环上,且一个服务提供者有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。 可以看到各个节点都被均匀分布在圆环上,且一个服务提供者有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。
看完原理,接下来我们来看看代码。 看完原理,接下来我们来看看代码。
```java ```java
/** /**
* ConsistentHashLoadBalance * ConsistentHashLoadBalance

@ -1,26 +1,33 @@
## 注册中心在Dubbo中的作用 ## 注册中心在 Dubbo 中的作用
服务治理框架可以大致分为 服务通信 和 服务管理 两部分服务管理可以分为服务注册、服务订阅以及服务发现服务提供者Provider 会往注册中心注册服务而消费者Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer以及Registry之间的依赖关系 如下图所示。
服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者 Provider 会往注册中心注册服务,而消费者 Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer 以及 Registry 之间的依赖关系 如下图所示。
![avatar](../../../images/Dubbo/Dubbo工作原理图.png) ![avatar](../../../images/Dubbo/Dubbo工作原理图.png)
## dubbo-registry 模块 结构分析 ## dubbo-registry 模块 结构分析
dubbo的注册中心有多种实现方案zookeeper、redis、multicast等本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api具体实现部分放到下章来讲。dubbo-registry模块 的结构如下图所示。
dubbo 的注册中心有多种实现方案zookeeper、redis、multicast 等,本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api具体实现部分放到下章来讲。dubbo-registry 模块 的结构如下图所示。
![avatar](../../../images/Dubbo/dubbo-registry模块结构图.png) ![avatar](../../../images/Dubbo/dubbo-registry模块结构图.png)
### Registry 核心组件类图 ### Registry 核心组件类图
典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。 典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。
![avatar](../../../images/Dubbo/Registry组件类图.png) ![avatar](../../../images/Dubbo/Registry组件类图.png)
既然有Registry组件那么按照很多框架的套路肯定也有一个用于获取 Registry实例的RegistryFactory其中用到了工厂方法模式不同的工厂类用于获取不同类型的实例。其类图结构如下。 既然有 Registry 组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry 实例的 RegistryFactory其中用到了工厂方法模式不同的工厂类用于获取不同类型的实例。其类图结构如下。
![avatar](../../../images/Dubbo/RegistryFactory组件类图.png) ![avatar](../../../images/Dubbo/RegistryFactory组件类图.png)
## 源码详解 ## 源码详解
根据上面的类图,我们开始从上往下 详解dubbo中对于注册中心的设计以及实现。
根据上面的类图,我们开始从上往下 详解 dubbo 中对于注册中心的设计以及实现。
### RegistryService 接口 ### RegistryService 接口
RegistryService 是注册中心模块的服务接口,定义了注册、取消注册、订阅、取消订阅以及查询符合条件的已注册数据 等方法。这里统一说明一下URLdubbo是以总线模式来时刻传递和保存配置信息的配置信息都被放在URL上进行传递随时可以取得相关配置信息而这里提到了URL有别的作用就是作为类似于节点的作用首先服务提供者Provider启动时需要提供服务就会向注册中心写下自己的URL地址。然后消费者启动时需要去订阅该服务则会订阅Provider注册的地址并且消费者也会写下自己的URL。
RegistryService 是注册中心模块的服务接口,定义了注册、取消注册、订阅、取消订阅以及查询符合条件的已注册数据 等方法。这里统一说明一下 URLdubbo 是以总线模式来时刻传递和保存配置信息的,配置信息都被放在 URL 上进行传递,随时可以取得相关配置信息,而这里提到了 URL 有别的作用就是作为类似于节点的作用首先服务提供者Provider启动时需要提供服务就会向注册中心写下自己的 URL 地址。然后消费者启动时需要去订阅该服务,则会订阅 Provider 注册的地址,并且消费者也会写下自己的 URL。
```java ```java
/** /**
* RegistryService. (SPI, Prototype, ThreadSafe) * RegistryService. (SPI, Prototype, ThreadSafe)
@ -95,7 +102,9 @@ public interface RegistryService {
``` ```
### Registry 接口 ### Registry 接口
注册中心接口把节点Node 以及注册中心服务RegistryService 的方法整合在了这个接口里面。该接口并没有自己的方法就是继承了Node和RegistryService接口。这里的Node是节点的接口里面协定了关于节点的一些操作方法源码如下。
注册中心接口,把节点 Node 以及注册中心服务 RegistryService 的方法整合在了这个接口里面。该接口并没有自己的方法,就是继承了 Node 和 RegistryService 接口。这里的 Node 是节点的接口,里面协定了关于节点的一些操作方法,源码如下。
```java ```java
/** /**
* 注册中心接口 * 注册中心接口
@ -114,7 +123,9 @@ public interface Node {
``` ```
### AbstractRegistry 抽象类 ### AbstractRegistry 抽象类
实现了Registry接口的抽象类。为了减轻注册中心的压力该抽象类把本地URL缓存到了property文件中并且实现了注册中心的注册、订阅等方法。
实现了 Registry 接口的抽象类。为了减轻注册中心的压力,该抽象类把本地 URL 缓存到了 property 文件中,并且实现了注册中心的注册、订阅等方法。
```java ```java
/** /**
* 实现了Registry接口的抽象类实现了如下方法 * 实现了Registry接口的抽象类实现了如下方法
@ -699,7 +710,9 @@ public abstract class AbstractRegistry implements Registry {
``` ```
### FailbackRegistry 抽象类 ### FailbackRegistry 抽象类
FailbackRegistry抽象类 继承了上面的 AbstractRegistryAbstractRegistry中的注册、订阅等方法实际上就是一些内存缓存的变化而真正的注册订阅的实现逻辑在FailbackRegistry实现并且FailbackRegistry提供了失败重试的机制。
FailbackRegistry 抽象类 继承了上面的 AbstractRegistryAbstractRegistry 中的注册、订阅等方法,实际上就是一些内存缓存的变化,而真正的注册订阅的实现逻辑在 FailbackRegistry 实现,并且 FailbackRegistry 提供了失败重试的机制。
```java ```java
/** /**
* 支持失败重试的 FailbackRegistry抽象类 * 支持失败重试的 FailbackRegistry抽象类
@ -1209,7 +1222,9 @@ public abstract class FailbackRegistry extends AbstractRegistry {
``` ```
### RegistryFactory 和 AbstractRegistryFactory ### RegistryFactory 和 AbstractRegistryFactory
RegistryFactory接口 是 Registry的工厂接口用来返回 Registry实例。该接口是一个可扩展接口可以看到该接口上有个@SPI注解并且默认值为dubbo也就是默认扩展的是DubboRegistryFactory。AbstractRegistryFactory 则是实现了 RegistryFactory接口 的抽象类。其源码如下。
RegistryFactory 接口 是 Registry 的工厂接口,用来返回 Registry 实例。该接口是一个可扩展接口,可以看到该接口上有个@SPI 注解,并且默认值为 dubbo也就是默认扩展的是 DubboRegistryFactory。AbstractRegistryFactory 则是实现了 RegistryFactory 接口 的抽象类。其源码如下。
```java ```java
/** /**
* 注册中心工厂 * 注册中心工厂
@ -1332,8 +1347,11 @@ public abstract class AbstractRegistryFactory implements RegistryFactory {
protected abstract Registry createRegistry(URL url); protected abstract Registry createRegistry(URL url);
} }
``` ```
### NotifyListener 和 RegistryDirectory ### NotifyListener 和 RegistryDirectory
最后我们来看一下 dubbo-registry-api 模块下的另一个比较重要的组件NotifyListener接口 和 RegistryDirectory抽象类。NotifyListener接口 只有一个notify方法通知监听器。当收到服务变更通知时触发。RegistryDirectory是注册中心服务维护着所有可用的远程Invoker或者本地的Invoker它的Invoker集合是从注册中心获取的另外它实现了NotifyListener接口。比如消费方要调用某远程服务会向注册中心订阅这个服务的所有 服务提供方,在订阅 及 服务提供方数据有变动时回调消费方的NotifyListener服务的notify方法回调接口传入所有服务提供方的url地址然后将urls转化为invokers也就是refer应用远程服务。源码如下。
最后我们来看一下 dubbo-registry-api 模块下的另一个比较重要的组件NotifyListener 接口 和 RegistryDirectory 抽象类。NotifyListener 接口 只有一个 notify 方法通知监听器。当收到服务变更通知时触发。RegistryDirectory 是注册中心服务,维护着所有可用的远程 Invoker 或者本地的 Invoker它的 Invoker 集合是从注册中心获取的,另外,它实现了 NotifyListener 接口。比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有 服务提供方,在订阅 及 服务提供方数据有变动时,回调消费方的 NotifyListener 服务的 notify 方法,回调接口传入所有服务提供方的 url 地址然后将 urls 转化为 invokers也就是 refer 应用远程服务。源码如下。
```java ```java
/** /**
* 通知监听器 * 通知监听器

@ -1,19 +1,21 @@
Dubbo的注册中心 虽然提供了多种实现,但生产上的事实标准基本上都是 基于Zookeeper实现的。这种注册中心的实现方法也是Dubbo最为推荐的。为了易于理解 Zookeeper 在 Dubbo 中的应用我们先简单看一下zookeeper。 Dubbo 的注册中心虽然提供了多种实现,但生产上的事实标准基本上都是 基于 Zookeeper 实现的。这种注册中心的实现方法也是 Dubbo 最为推荐的。为了易于理解 Zookeeper 在 Dubbo 中的应用,我们先简单看一下 zookeeper。
由于 Dubbo 是一个分布式RPC开源框架各服务之间单独部署往往会出现资源之间数据不一致的问题比如某一个服务增加或减少了几台机器某个服务提供者变更了服务地址那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo服务是如何被 Zookeeper的数据结构存储管理的呢zookeeper采用的是树形结构来组织数据节点它类似于一个标准的文件系统如下图所示。 由于 Dubbo 是一个分布式 RPC 开源框架,各服务之间单独部署,往往会出现资源之间数据不一致的问题,比如:某一个服务增加或减少了几台机器,某个服务提供者变更了服务地址,那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo 服务是如何被 Zookeeper 的数据结构存储管理的呢zookeeper 采用的是树形结构来组织数据节点,它类似于一个标准的文件系统,如下图所示。
![avatar](../../../images/Dubbo/dubbo注册中心在zookeeper中的结构.png) ![avatar](../../../images/Dubbo/dubbo注册中心在zookeeper中的结构.png)
该图展示了dubbo在zookeeper中存储的形式以及节点层级。dubbo的Root层是根目录通过<dubbo:registry group="dubbo" />的“group”来设置zookeeper的根节点缺省值是“dubbo”。Service层是服务接口的全名。Type层是分类一共有四种分类分别是providers 服务提供者列表、consumers 服务消费者列表、routes 路由规则列表、configurations 配置规则列表。URL层 根据不同的Type目录可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的Type关注的URL不同。 该图展示了 dubbo zookeeper 中存储的形式以及节点层级。dubbo Root 层是根目录,通过<dubbo:registry group="dubbo" />的“group”来设置 zookeeper 的根节点缺省值是“dubbo”。Service 层是服务接口的全名。Type 层是分类,一共有四种分类,分别是 providers 服务提供者列表、consumers 服务消费者列表、routes 路由规则列表、configurations 配置规则列表。URL 层 根据不同的 Type 目录:可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的 Type 关注的 URL 不同。
zookeeper以斜杠来分割每一层的znode节点比如第一层根节点dubbo就是“/dubbo”而第二层的Service层就是/dubbo/com.foo.Barservicezookeeper的每个节点通过路径来表示以及访问例如服务提供者启动时向/dubbo/com.foo.Barservice/providers目录下写入自己的URL地址。 zookeeper 以斜杠来分割每一层的 znode 节点,比如第一层根节点 dubbo 就是“/dubbo”而第二层的 Service 层就是/dubbo/com.foo.Barservicezookeeper 的每个节点通过路径来表示以及访问,例如服务提供者启动时,向/dubbo/com.foo.Barservice/providers 目录下写入自己的 URL 地址。
dubbo-registry-zookeeper 模块的工程结构如下图所示,里面就俩类,非常简单。 dubbo-registry-zookeeper 模块的工程结构如下图所示,里面就俩类,非常简单。
![avatar](../../../images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png) ![avatar](../../../images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png)
### ZookeeperRegistry ### ZookeeperRegistry
该类继承了FailbackRegistry抽象类针对注册中心核心的 服务注册、服务订阅、取消注册、取消订阅,查询注册列表进行展开,这里用到了 模板方法设计模式FailbackRegistry中定义了register()、subscribe()等模板方法和 doRegister()、doSubscribe()抽象方法ZookeeperRegistry基于zookeeper对这些抽象方法进行了实现。其实你会发现zookeeper虽然是最被推荐的反而它的实现逻辑相对简单因为调用了zookeeper服务组件很多的逻辑不需要在dubbo中自己去实现。
该类继承了 FailbackRegistry 抽象类,针对注册中心核心的 服务注册、服务订阅、取消注册、取消订阅,查询注册列表进行展开,这里用到了 模板方法设计模式FailbackRegistry 中定义了 register()、subscribe()等模板方法和 doRegister()、doSubscribe()抽象方法ZookeeperRegistry 基于 zookeeper 对这些抽象方法进行了实现。其实你会发现 zookeeper 虽然是最被推荐的,反而它的实现逻辑相对简单,因为调用了 zookeeper 服务组件,很多的逻辑不需要在 dubbo 中自己去实现。
```java ```java
/* /*
* Licensed to the Apache Software Foundation (ASF) under one or more * Licensed to the Apache Software Foundation (ASF) under one or more
@ -413,7 +415,9 @@ public class ZookeeperRegistry extends FailbackRegistry {
``` ```
### ZookeeperRegistryFactory ### ZookeeperRegistryFactory
ZookeeperRegistryFactory 继承了 AbstractRegistryFactory抽象类实现了其中的抽象方法 如createRegistry(),源码如下。
ZookeeperRegistryFactory 继承了 AbstractRegistryFactory 抽象类,实现了其中的抽象方法 如 createRegistry(),源码如下。
```java ```java
/** /**
* Zookeeper Registry 工厂 * Zookeeper Registry 工厂

@ -1,30 +1,35 @@
## dubbo-remoting 模块整体结构设计 ## dubbo-remoting 模块整体结构设计
服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分前面我们分析了有关注册中心的源码也就是服务管理接下来要分析的就是跟服务通信有关的源码也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能而在对NIO框架选型上dubbo交由用户选择它集成了mina、netty、grizzly等各类NIO框架来搭建NIO服务器和客户端并且利用dubbo的SPI扩展机制可以让用户自定义选择。dubbo-remoting的工程结构如下。
服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分,前面我们分析了有关注册中心的源码,也就是服务管理,接下来要分析的就是跟服务通信有关的源码,也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能,而在对 NIO 框架选型上dubbo 交由用户选择,它集成了 mina、netty、grizzly 等各类 NIO 框架来搭建 NIO 服务器和客户端,并且利用 dubbo 的 SPI 扩展机制可以让用户自定义选择。dubbo-remoting 的工程结构如下。
![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting的工程结构.png) ![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting的工程结构.png)
## dubbo-remoting-api 模块整体结构设计 ## dubbo-remoting-api 模块整体结构设计
本篇我们先来看一下 dubbo-remoting 中 dubbo-remoting-api的项目结构。
本篇我们先来看一下 dubbo-remoting 中 dubbo-remoting-api 的项目结构。
![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting-api的项目结构.png) ![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting-api的项目结构.png)
dubbo-remoting-api 定义了远程通信模块最核心的 API对于 dubbo-remoting-api 的解读会分为如下五个部分,其中第五部分会在本文介绍。 dubbo-remoting-api 定义了远程通信模块最核心的 API对于 dubbo-remoting-api 的解读会分为如下五个部分,其中第五部分会在本文介绍。
1. buffer包缓冲在 NIO框架 中是很重要的存在,各个 NIO框架 都实现了自己相应的缓存操作。这个 buffer包 下包括了缓冲区的接口以及抽象类; 1. buffer 包:缓冲在 NIO 框架 中是很重要的存在,各个 NIO 框架 都实现了自己相应的缓存操作。这个 buffer 包 下包括了缓冲区的接口以及抽象类;
2. exchange包信息交换层其中封装了请求响应模式在传输层之上重新封装了 Request-Response 语义,为了满足 RPC 的需求。这层可以认为专注在 Request 和 Response 携带的信息上。该层是 RPC调用 的通讯基础之一; 2. exchange 包:信息交换层,其中封装了请求响应模式,在传输层之上重新封装了 Request-Response 语义,为了满足 RPC 的需求。这层可以认为专注在 Request 和 Response 携带的信息上。该层是 RPC 调用 的通讯基础之一;
3. telnet包dubbo 支持通过 telnet命令 来进行服务治理,该包下就封装了这些通用指令的逻辑实现; 3. telnet dubbo 支持通过 telnet 命令 来进行服务治理,该包下就封装了这些通用指令的逻辑实现;
4. transport包网络传输层它只负责单向消息传输是对 Mina、Netty、Grizzly 的抽象,它也可以扩展 UDP 传输,该层也是 RPC调用 的通讯基础之一; 4. transport 包:网络传输层,它只负责单向消息传输,是对 Mina、Netty、Grizzly 的抽象,它也可以扩展 UDP 传输,该层也是 RPC 调用 的通讯基础之一;
5. 最外层的源码:该部分也是我们接下来要重点解析的。 5. 最外层的源码:该部分也是我们接下来要重点解析的。
结合 dubbo-remoting-api模块 的外层类和包划分,我们看看下面的官方架构图。 结合 dubbo-remoting-api 模块 的外层类和包划分,我们看看下面的官方架构图。
![在这里插入图片描述](../../../images/Dubbo/Dubbo整体架构图.png) ![在这里插入图片描述](../../../images/Dubbo/Dubbo整体架构图.png)
红框标注的部分是 dubbo整体架构中的 远程通讯架构,其中 Exchange组件 和 Transport组件 在框架设计中起到了很重要的作用,也是支撑 Remoting 的核心。 红框标注的部分是 dubbo 整体架构中的 远程通讯架构,其中 Exchange 组件 和 Transport 组件 在框架设计中起到了很重要的作用,也是支撑 Remoting 的核心。
## dubbo-remoting-api 模块最外层源码解析 ## dubbo-remoting-api 模块最外层源码解析
### Endpoint 接口 ### Endpoint 接口
dubbo 抽象出了一个端的概念也就是Endpoint接口这个端就是一个点而点与点之间可以双向传输。在端的基础上再衍生出通道、客户端以及服务端的概念也就是下面要介绍的 Channel、Client、Server 三个接口。在传输层Client 和 Server 的区别只是语义上的区别并不区分请求和应答职责而在交换层Client 和 Server 是有方向的端点所以区分了明确的请求和应答职责。两者都具备发送的能力只是客户端和服务端所关注的事情不一样而Endpoint接口抽象的方法就是它们共同拥有的方法。这也就是它们都能被抽象成端的原因。
dubbo 抽象出了一个端的概念,也就是 Endpoint 接口,这个端就是一个点,而点与点之间可以双向传输。在端的基础上再衍生出通道、客户端以及服务端的概念,也就是下面要介绍的 Channel、Client、Server 三个接口。在传输层Client 和 Server 的区别只是语义上的区别并不区分请求和应答职责而在交换层Client 和 Server 是有方向的端点,所以区分了明确的请求和应答职责。两者都具备发送的能力,只是客户端和服务端所关注的事情不一样,而 Endpoint 接口抽象的方法就是它们共同拥有的方法。这也就是它们都能被抽象成端的原因。
```java ```java
/** /**
* Endpoint. (API/SPI, Prototype, ThreadSafe) * Endpoint. (API/SPI, Prototype, ThreadSafe)
@ -92,8 +97,11 @@ public interface Endpoint {
boolean isClosed(); boolean isClosed();
} }
``` ```
### Channel 接口 ### Channel 接口
该接口是通道接口通道是信息传输的载体。Channel 可读可写并且可以异步读写。Channel 是 client 和 server 的数据传输桥梁。Channel 和 client 是一对一的,也就是一个 client 对应一个 Channel而 Channel 和 server 则是多对一,也就是一个 server 可以对应多个 Channel。 该接口是通道接口通道是信息传输的载体。Channel 可读可写并且可以异步读写。Channel 是 client 和 server 的数据传输桥梁。Channel 和 client 是一对一的,也就是一个 client 对应一个 Channel而 Channel 和 server 则是多对一,也就是一个 server 可以对应多个 Channel。
```java ```java
/** /**
* Channel. (API/SPI, Prototype, ThreadSafe) * Channel. (API/SPI, Prototype, ThreadSafe)
@ -122,7 +130,9 @@ public interface Channel extends Endpoint {
void removeAttribute(String key); void removeAttribute(String key);
} }
``` ```
### ChannelHandler 接口 ### ChannelHandler 接口
```java ```java
/** /**
* ChannelHandler. (API, Prototype, ThreadSafe) * ChannelHandler. (API, Prototype, ThreadSafe)
@ -150,6 +160,7 @@ public interface ChannelHandler {
} }
``` ```
### Client 和 Resetable 接口 ### Client 和 Resetable 接口
```java ```java
@ -180,6 +191,7 @@ public interface Resetable {
``` ```
### Server 接口 ### Server 接口
```java ```java
/** /**
* Remoting Server. (API/SPI, Prototype, ThreadSafe) * Remoting Server. (API/SPI, Prototype, ThreadSafe)
@ -205,7 +217,9 @@ public interface Server extends Endpoint, Resetable {
``` ```
### Codec2 接口 ### Codec2 接口
这两个都是编解码器 接口,在网络中进行传输的数据 都是原始的字节序列,这就需要 发送端使用编码器把 要传输的有意义的信息 序列化成字节序列,接收端再使用解码器 把字节序列再反序列化成 有效信息,而同时具备这两种功能的单一组件就叫 编解码器。在 dubbo 中 Codec 是老编解码器接口而Codec2是新编解码器接口并且 dubbo 已经用 CodecAdapter 把 Codec 适配成 Codec2 了。所以在这里就只介绍下Codec2接口。
这两个都是编解码器 接口,在网络中进行传输的数据 都是原始的字节序列,这就需要 发送端使用编码器把 要传输的有意义的信息 序列化成字节序列,接收端再使用解码器 把字节序列再反序列化成 有效信息,而同时具备这两种功能的单一组件就叫 编解码器。在 dubbo 中 Codec 是老编解码器接口,而 Codec2 是新编解码器接口,并且 dubbo 已经用 CodecAdapter 把 Codec 适配成 Codec2 了。所以在这里就只介绍下 Codec2 接口。
```java ```java
/** /**
* 编解码器接口,需要注意的是: * 编解码器接口,需要注意的是:
@ -236,6 +250,7 @@ public interface Codec2 {
``` ```
### Decodeable 接口 ### Decodeable 接口
```java ```java
/** /**
* 可解码的接口,该接口有两个作用,第一是在调用真正的 decode方法 实现的时候会有一些校验, * 可解码的接口,该接口有两个作用,第一是在调用真正的 decode方法 实现的时候会有一些校验,
@ -250,6 +265,7 @@ public interface Decodeable {
``` ```
### Dispatcher 接口 ### Dispatcher 接口
```java ```java
/** /**
* 调度器接口,不同的调度器实现,将操作转发到对应的线程池。 * 调度器接口,不同的调度器实现,将操作转发到对应的线程池。
@ -299,6 +315,7 @@ public interface Transporter {
``` ```
### Transporters 类 ### Transporters 类
```java ```java
/** /**
* 1、该类用到了设计模式的外观模式通过该类的包装隐藏了内部具体的实现细节降低了程序的复杂度 * 1、该类用到了设计模式的外观模式通过该类的包装隐藏了内部具体的实现细节降低了程序的复杂度
@ -371,7 +388,9 @@ public class Transporters {
``` ```
### 远程通信的异常类 ### 远程通信的异常类
RemotingException、ExecutionException 和 TimeoutException 是远程通信的异常类,内容比较简单,这里就简单介绍下 一笔带过咯。 RemotingException、ExecutionException 和 TimeoutException 是远程通信的异常类,内容比较简单,这里就简单介绍下 一笔带过咯。
1. RemotingException 继承了 Exception类是远程通信的基础异常
2. ExecutionException 继承了 RemotingException类ExecutionException 是远程通信的执行异常; 1. RemotingException 继承了 Exception 类,是远程通信的基础异常;
3. TimeoutException 继承了 RemotingException类TimeoutException是超时异常。 2. ExecutionException 继承了 RemotingException 类ExecutionException 是远程通信的执行异常;
3. TimeoutException 继承了 RemotingException 类TimeoutException 是超时异常。

@ -1,4 +1,5 @@
String的源码大家应该都能看懂这里就不一一分析咯重点讲一下equals()和hashcode()方法然后看一下String类常用方法的实现就当一起温习一下咯。 String 的源码大家应该都能看懂,这里就不一一分析咯,重点讲一下 equals()和 hashcode()方法,然后看一下 String 类常用方法的实现,就当一起温习一下咯。
```java ```java
public final class String public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { implements java.io.Serializable, Comparable<String>, CharSequence {

@ -1,6 +1,7 @@
本来想看 ThreadLocal 的源码的,但发现其中最重要的 get/set 方法都是操纵的 Thread类 中的 threadLocals变量 (java.lang.ThreadLocal.ThreadLocalMap),索性先来看一下 Thread 的源码吧,可以留意一下其中与 ThreadLocal 相关的属性,这样下次阅读 ThreadLocal 的核心API时就能够轻易理解其原理咯。不多BB直接上硬菜。 本来想看 ThreadLocal 的源码的,但发现其中最重要的 get/set 方法都是操纵的 Thread 类 中的 threadLocals 变量 (java.lang.ThreadLocal.ThreadLocalMap),索性先来看一下 Thread 的源码吧,可以留意一下其中与 ThreadLocal 相关的属性,这样下次阅读 ThreadLocal 的核心 API 时,就能够轻易理解其原理咯。不多 BB直接上硬菜。
实现多线程从本质上都是由 Thread 类 来完成的,其源码量很多,本次只看一些常见且重要的部分,源码和解析如下。
实现多线程从本质上都是由 Thread类 来完成的,其源码量很多,本次只看一些常见且重要的部分,源码和解析如下。
```java ```java
public class Thread implements Runnable { public class Thread implements Runnable {
/** 这里只看一些 常见的参数 */ /** 这里只看一些 常见的参数 */
@ -315,6 +316,7 @@ public class Thread implements Runnable {
public final native boolean isAlive(); public final native boolean isAlive();
} }
``` ```
之前一直对线程状态 及 状态切换的概念模糊不清,现在通过源码中对线程状态的定义,我们可以画张图来重新回顾一下,以使我们对其有更加深刻的理解。 之前一直对线程状态 及 状态切换的概念模糊不清,现在通过源码中对线程状态的定义,我们可以画张图来重新回顾一下,以使我们对其有更加深刻的理解。
![avatar](../../../images/JDK1.8/ThreadStatusChange.png) ![avatar](../../../images/JDK1.8/ThreadStatusChange.png)

@ -1,8 +1,9 @@
前面我们分析了 Thread类的源码有了前面的铺垫通过源码 理解ThreadLocal的秘密就容易多了。 前面我们分析了 Thread 类的源码,有了前面的铺垫,通过源码 理解 ThreadLocal 的秘密就容易多了。
ThreadLocal类 提供了 get/set线程局部变量的实现ThreadLocal成员变量与正常的成员变量不同每个线程都可以通过 ThreadLocal成员变量 get/set自己的专属值。ThreadLocal实例 通常是类中的私有静态变量常用于将状态与线程关联例如用户ID或事务ID。 ThreadLocal 类 提供了 get/set 线程局部变量的实现ThreadLocal 成员变量与正常的成员变量不同,每个线程都可以通过 ThreadLocal 成员变量 get/set 自己的专属值。ThreadLocal 实例 通常是类中的私有静态变量,常用于将状态与线程关联,例如:用户 ID 或事务 ID。
tips在类中定义 ThreadLocal 变量时,一般在定义时就进行实例化!
tips在类中定义ThreadLocal变量时一般在定义时就进行实例化
```java ```java
public class ThreadLocal<T> { public class ThreadLocal<T> {
@ -256,12 +257,13 @@ public class ThreadLocal<T> {
} }
} }
``` ```
简单画个图总结一下 ThreadLocal 的原理,如下。 简单画个图总结一下 ThreadLocal 的原理,如下。
![avatar](../../../images/JDK1.8/ThreadLocal原理.png) ![avatar](../../../images/JDK1.8/ThreadLocal原理.png)
最后强调一下 ThreadLocal的使用注意事项 最后强调一下 ThreadLocal 的使用注意事项:
1. ThreadLocal 不是用来解决线程安全问题的,多线程不共享,不存在竞争!其目的是使线程能够使用本地变量。 1. ThreadLocal 不是用来解决线程安全问题的,多线程不共享,不存在竞争!其目的是使线程能够使用本地变量。
2. 项目如果使用了线程池那么线程回收后ThreadLocal变量要remove掉否则线程池回收线程后变量还在内存中可能会带来意想不到的后果例如Tomcat容器的线程池可以在拦截器中处理继承 HandlerInterceptorAdapter然后复写 afterCompletion()方法remove掉变量 2. 项目如果使用了线程池,那么线程回收后 ThreadLocal 变量要 remove 掉,否则线程池回收线程后,变量还在内存中,可能会带来意想不到的后果!例如 Tomcat 容器的线程池,可以在拦截器中处理:继承 HandlerInterceptorAdapter然后复写 afterCompletion()方法remove 掉变量!!!

@ -161,5 +161,6 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements Concurre
} }
} }
``` ```
**与JDK1.7在同步机制上的区别** 总结如下:
JDK1.7 使用的是分段锁机制其内部类Segment 继承了 ReentrantLock将 容器内的数组划分成多段区域每个区域对应一把锁相比于HashTable确实提升了不少并发能力但在数据量庞大的情况下性能依然不容乐观只能通过不断的增加锁来维持并发性能。而JDK1.8则使用了 CAS乐观锁 + synchronized局部锁 处理并发问题,锁粒度更细,即使数据量很大也能保证良好的并发性。 **与 JDK1.7 在同步机制上的区别** 总结如下:
JDK1.7 使用的是分段锁机制,其内部类 Segment 继承了 ReentrantLock将 容器内的数组划分成多段区域,每个区域对应一把锁,相比于 HashTable 确实提升了不少并发能力,但在数据量庞大的情况下,性能依然不容乐观,只能通过不断的增加锁来维持并发性能。而 JDK1.8 则使用了 CAS 乐观锁 + synchronized 局部锁 处理并发问题,锁粒度更细,即使数据量很大也能保证良好的并发性。

@ -1,7 +1,9 @@
作为工作中最重要、最常用的容器之一,当然还是要自己动手写一篇 HashMap 的源码解析来加深对其的印象咯,而且它的设计与实现 也有很多值得学习的地方。 作为工作中最重要、最常用的容器之一,当然还是要自己动手写一篇 HashMap 的源码解析来加深对其的印象咯,而且它的设计与实现 也有很多值得学习的地方。
## 源码赏析 ## 源码赏析
JDK1.8 的HashMap 底层使用的是 动态数组,数组中元素存放的是 链表或红黑树。核心源码如下。
JDK1.8 的 HashMap 底层使用的是 动态数组,数组中元素存放的是 链表或红黑树。核心源码如下。
```java ```java
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,
Cloneable, Serializable { Cloneable, Serializable {
@ -312,17 +314,21 @@ public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,
} }
} }
``` ```
源码部分 结合注释还是很容易看懂的,比较复杂的是红黑树这种数据结构,以及红黑树与链表之间的相互转换。下面我们回顾下这个数据结构。 源码部分 结合注释还是很容易看懂的,比较复杂的是红黑树这种数据结构,以及红黑树与链表之间的相互转换。下面我们回顾下这个数据结构。
## 红黑树 ## 红黑树
红黑树是一种自平衡的二叉查找树,比普通的二叉查找树效率更高,它可在 O(logN) 时间内完成查找、增加、删除等操作。 红黑树是一种自平衡的二叉查找树,比普通的二叉查找树效率更高,它可在 O(logN) 时间内完成查找、增加、删除等操作。
普通的二叉查找树在极端情况下可退化成链表,导致 增、删、查 效率低下。红黑树通过定义一些性质,将任意节点的左右子树高度差控制在规定范围内,以达到平衡状态,红黑树的性质定义如下。 普通的二叉查找树在极端情况下可退化成链表,导致 增、删、查 效率低下。红黑树通过定义一些性质,将任意节点的左右子树高度差控制在规定范围内,以达到平衡状态,红黑树的性质定义如下。
1. 节点是红色或黑色。 1. 节点是红色或黑色。
2. 根是黑色。 2. 根是黑色。
3. 所有叶子都是黑色叶子是NIL节点 3. 所有叶子都是黑色(叶子是 NIL 节点)。
4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。) 4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
红黑树的操作和其他树一样,包括查找、插入、删除等,其查找过程和二叉查找树一样简单,但插入和删除操作要复杂的多,这也是其 为保持平衡性 不会退化成链表 所付出的代价。红黑树为保持平衡性 所进行的操作主要有 旋转(左旋、右旋)和变色。 红黑树的操作和其他树一样,包括查找、插入、删除等,其查找过程和二叉查找树一样简单,但插入和删除操作要复杂的多,这也是其 为保持平衡性 不会退化成链表 所付出的代价。红黑树为保持平衡性 所进行的操作主要有 旋转(左旋、右旋)和变色。
红黑树的实现 确实比较复杂,光是理解其 插入、删除 的操作原理 就蛮费劲,所以这里先挖个坑,后面单独用一篇博文来分析 HashMap的 内部类TreeNode 对红黑树数据结构的实现。 红黑树的实现 确实比较复杂,光是理解其 插入、删除 的操作原理 就蛮费劲,所以这里先挖个坑,后面单独用一篇博文来分析 HashMap 的 内部类 TreeNode 对红黑树数据结构的实现。

@ -1,10 +1,11 @@
HashSet 本身并没有什么特别的东西它提供的所有集合核心功能都是基于HashMap来实现的。如果了解HashMap源码的实现HashSet 源码看起来跟玩一样。我的博客中有专门分析HashMap源码的文章不熟悉的请自行翻阅。 HashSet 本身并没有什么特别的东西,它提供的所有集合核心功能,都是基于 HashMap 来实现的。如果了解 HashMap 源码的实现HashSet 源码看起来跟玩一样。我的博客中有专门分析 HashMap 源码的文章,不熟悉的请自行翻阅。
HashSet 的特点如下: HashSet 的特点如下:
- 内部使用HashMap的key存储元素以此来保证**元素不重复**
- HashSet是无序的因为HashMap的key是**无序**的; - 内部使用 HashMap 的 key 存储元素,以此来保证**元素不重复**
- HashSet中允许有一个null元素因为HashMap允许key为null - HashSet 是无序的,因为 HashMap 的 key 是**无序**的;
- HashSet是**非线程安全**的。 - HashSet 中允许有一个 null 元素,因为 HashMap 允许 key 为 null
- HashSet 是**非线程安全**的。
```java ```java
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {

@ -1,9 +1,10 @@
HashMap 大家都清楚,底层是 数组 + (链表 / 红黑树)**元素是无序的**,而 LinkedHashMap 则比 HashMap 多了这一个功能并且LinkedHashMap 的有序可以按两种顺序排列一种是按照插入的顺序一种是按照访问的顺序初始化LinkedHashMap对象时设置accessOrder参数为true而其内部是靠 建立一个双向链表 来维护这个顺序的,在每次插入、删除后,都会调用一个函数来进行 双向链表的维护,这也是实现 LRU Cache 功能的基础。 HashMap 大家都清楚,底层是 数组 + (链表 / 红黑树)**元素是无序的**,而 LinkedHashMap 则比 HashMap 多了这一个功能并且LinkedHashMap 的有序可以按两种顺序排列,一种是按照插入的顺序,一种是按照访问的顺序(初始化 LinkedHashMap 对象时设置 accessOrder 参数为 true而其内部是靠 建立一个双向链表 来维护这个顺序的,在每次插入、删除后,都会调用一个函数来进行 双向链表的维护,这也是实现 LRU Cache 功能的基础。
先说几个比较重要的结论,大家可以根据这些结论从后面的源码解析中 得到证据。 先说几个比较重要的结论,大家可以根据这些结论从后面的源码解析中 得到证据。
1. LinkedHashMap 继承了 HashMap所以和 HashMap 的底层数据结构是一样的,都是数组+链表+红黑树,扩容机制也一样; 1. LinkedHashMap 继承了 HashMap所以和 HashMap 的底层数据结构是一样的,都是数组+链表+红黑树,扩容机制也一样;
2. LinkedHashMap 是通过双向链表来维护数据的,与 HashMap 的拉链式存储不一样; 2. LinkedHashMap 是通过双向链表来维护数据的,与 HashMap 的拉链式存储不一样;
3. LinkedHashMap 存储顺序与添加顺序是一样得,同时可以根据 accessOrder参数 来决定是否在访问时移动元素,以实现 LRU 功能。 3. LinkedHashMap 存储顺序与添加顺序是一样得,同时可以根据 accessOrder 参数 来决定是否在访问时移动元素,以实现 LRU 功能。
```java ```java
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> { public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {

@ -1,17 +1,20 @@
## 线程池核心组件图解 ## 线程池核心组件图解
看源码之前,先了解一下该组件 最主要的几个 接口、抽象类和实现类的结构关系。 看源码之前,先了解一下该组件 最主要的几个 接口、抽象类和实现类的结构关系。
![avatar](../../../images/JDK1.8/线程池组件类图.png) ![avatar](../../../images/JDK1.8/线程池组件类图.png)
该组件中Executor 和 ExecutorService接口 定义了线程池最核心的几个方法提交任务submit 该组件中Executor 和 ExecutorService 接口 定义了线程池最核心的几个方法,提交任务 submit
()、关闭线程池shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。 ()、关闭线程池 shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor 接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。
另外还有一个常见的工具类 Executors里面为开发者封装了一些可以直接拿来用的线程池。 另外还有一个常见的工具类 Executors里面为开发者封装了一些可以直接拿来用的线程池。
## 源码赏析 ## 源码赏析
话不多说,直接上源码。(这里只看最主要的代码部分) 话不多说,直接上源码。(这里只看最主要的代码部分)
### Executor 和 ExecutorService接口 ### Executor 和 ExecutorService 接口
```java ```java
public interface Executor { public interface Executor {
@ -39,7 +42,9 @@ public interface ExecutorService extends Executor {
Future<?> submit(Runnable task); Future<?> submit(Runnable task);
} }
``` ```
### AbstractExecutorService 抽象类 ### AbstractExecutorService 抽象类
```java ```java
/** /**
* 该抽象类最主要的内容就是,实现了 ExecutorService 中的 submit()系列方法 * 该抽象类最主要的内容就是,实现了 ExecutorService 中的 submit()系列方法
@ -75,6 +80,7 @@ public abstract class AbstractExecutorService implements ExecutorService {
``` ```
### ThreadPoolExecutor ### ThreadPoolExecutor
```java ```java
public class ThreadPoolExecutor extends AbstractExecutorService { public class ThreadPoolExecutor extends AbstractExecutorService {
@ -209,12 +215,15 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
} }
} }
``` ```
ThreadPoolExecutor 中的 execute()方法 执行 Runnable任务 的流程逻辑可以用下图表示。
ThreadPoolExecutor 中的 execute()方法 执行 Runnable 任务 的流程逻辑可以用下图表示。
![avatar](../../../images/ConcurrentProgramming/线程池流程.png) ![avatar](../../../images/ConcurrentProgramming/线程池流程.png)
### 工具类 Executors ### 工具类 Executors
看类名也知道,它最主要的作用就是提供 static 的工具方法,为开发者提供各种封装好的 具有各自特性的线程池。 看类名也知道,它最主要的作用就是提供 static 的工具方法,为开发者提供各种封装好的 具有各自特性的线程池。
```java ```java
public class Executors { public class Executors {

@ -1,5 +1,5 @@
利用IDEA整理类图还是蛮不错的虽然这个功能BUG很多。下图是J.U.C并发包中所有类组成的类图源码看多了 再去整理这个图,感觉还是很爽的。 利用 IDEA 整理类图还是蛮不错的,虽然这个功能 BUG 很多。下图是 J.U.C 并发包中所有类组成的类图,源码看多了 再去整理这个图,感觉还是很爽的。
根据功能主要划分了六个部分其中比较重要的是线程池及其相关类、并发容器、AQS与锁与同步工具类、原子类。图可能整理的不够细致但看着这些类回想一下其中的源码实现感觉能侃一天。 根据功能主要划分了六个部分其中比较重要的是线程池及其相关类、并发容器、AQS 与锁与同步工具类、原子类。图可能整理的不够细致,但看着这些类,回想一下其中的源码实现,感觉能侃一天。
![avatar](../../../images/JDK1.8/JUC全量UML地图.png) ![avatar](../../../images/JDK1.8/JUC全量UML地图.png)

@ -1,5 +1,6 @@
## 类图结构 ## 类图结构
J.U.C 的锁组件中 类相对较少从JDK相应的包中也能看出来下图标记了其中最主要的几个接口和类也是本文要分析的重点。
J.U.C 的锁组件中 类相对较少,从 JDK 相应的包中也能看出来,下图标记了其中最主要的几个接口和类,也是本文要分析的重点。
![avatar](../../../images/JDK1.8/JUC的locks包.png) ![avatar](../../../images/JDK1.8/JUC的locks包.png)
@ -8,7 +9,9 @@ J.U.C 的锁组件中 类相对较少从JDK相应的包中也能看出来
![avatar](../../../images/JDK1.8/JUC锁组件类图.png) ![avatar](../../../images/JDK1.8/JUC锁组件类图.png)
## Lock 组件 ## Lock 组件
Lock 组件的结构很简单,只有一个接口和一个实现类,源码如下。 Lock 组件的结构很简单,只有一个接口和一个实现类,源码如下。
```java ```java
public interface Lock { public interface Lock {
@ -183,7 +186,9 @@ public class ReentrantLock implements Lock, java.io.Serializable {
``` ```
## ReadWriteLock 组件 ## ReadWriteLock 组件
ReadWriteLock 组件的结构也很简单,与上面的 Lock组件 不同的是,它提供了 公平的读锁写锁,以及非公平的读锁写锁。
ReadWriteLock 组件的结构也很简单,与上面的 Lock 组件 不同的是,它提供了 公平的读锁写锁,以及非公平的读锁写锁。
```java ```java
public interface ReadWriteLock { public interface ReadWriteLock {
/** /**
@ -518,7 +523,9 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab
``` ```
## AbstractQueuedSynchronizer ## AbstractQueuedSynchronizer
最后看一下抽象类 AbstractQueuedSynchronizer在同步组件的实现中AQS是核心部分同步组件的实现者通过使用 AQS 提供的模板方法实现同步组件语义AQS 则实现了对同步状态的管理以及对阻塞线程进行排队等待通知等等一些底层的实现处理。AQS 的核心包括同步队列独占式锁的获取和释放共享锁的获取和释放以及可中断锁超时等待锁获取这些特性的实现而这些实际上则是AQS提供出来的模板方法。源码如下。
最后看一下抽象类 AbstractQueuedSynchronizer在同步组件的实现中AQS 是核心部分,同步组件的实现者通过使用 AQS 提供的模板方法实现同步组件语义AQS 则实现了对同步状态的管理以及对阻塞线程进行排队等待通知等等一些底层的实现处理。AQS 的核心包括:同步队列,独占式锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现,而这些实际上则是 AQS 提供出来的模板方法。源码如下。
```java ```java
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements java.io.Serializable { implements java.io.Serializable {

@ -1,10 +1,11 @@
Semaphore 信号量可用于控制一定时间内并发执行的线程数基于AQS实现。可应用于网关限流、资源限制 (如 最大可发起连接数)。由于 release() 释放许可时,未对释放许可数做限制,所以可以通过该方法增加总的许可数量。 Semaphore 信号量,可用于控制一定时间内,并发执行的线程数,基于 AQS 实现。可应用于网关限流、资源限制 (如 最大可发起连接数)。由于 release() 释放许可时,未对释放许可数做限制,所以可以通过该方法增加总的许可数量。
**获取许可** 支持公平和非公平模式,默认非公平模式。公平模式无论是否有许可,都会先判断是否有线程在排队,如果有线程排队,则进入排队,否则尝试获取许可;非公平模式无论许可是否充足,直接尝试获取许可。 **获取许可** 支持公平和非公平模式,默认非公平模式。公平模式无论是否有许可,都会先判断是否有线程在排队,如果有线程排队,则进入排队,否则尝试获取许可;非公平模式无论许可是否充足,直接尝试获取许可。
不多废话,下面直接看源码。 不多废话,下面直接看源码。
#### 核心内部类 Sync #### 核心内部类 Sync
```java ```java
abstract static class Sync extends AbstractQueuedSynchronizer { abstract static class Sync extends AbstractQueuedSynchronizer {
@ -124,7 +125,8 @@ static final class FairSync extends Sync {
} }
``` ```
#### 主要API #### 主要 API
```java ```java
public class Semaphore implements java.io.Serializable { public class Semaphore implements java.io.Serializable {

@ -1,15 +1,21 @@
## 简介 ## 简介
AbstractQueuedSynchronizer 是Doug Lea大师创作的用来构建锁或者其他同步组件的基础框架类。J.U.C中许多锁和并发工具类的核心实现都依赖于AQSReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等。
AQS的源码中 方法很多,但主要做了三件事情: AbstractQueuedSynchronizer 是 Doug Lea 大师创作的用来构建锁或者其他同步组件的基础框架类。J.U.C 中许多锁和并发工具类的核心实现都依赖于 AQSReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等。
AQS 的源码中 方法很多,但主要做了三件事情:
1. 管理 同步状态; 1. 管理 同步状态;
2. 维护 同步队列; 2. 维护 同步队列;
3. 阻塞和唤醒 线程。 3. 阻塞和唤醒 线程。
另外,从行为上来区分就是 获取锁 和 释放锁,从模式上来区分就是 独占锁 和 共享锁。 另外,从行为上来区分就是 获取锁 和 释放锁,从模式上来区分就是 独占锁 和 共享锁。
## 实现原理 ## 实现原理
AQS内部维护了一个FIFO队列来管理锁。线程首先会尝试获取锁如果失败则将当前线程以及等待状态等信息包成一个Node节点放入同步队列阻塞起来当持有锁的线程释放锁时就会唤醒队列中的后继线程。
AQS 内部维护了一个 FIFO 队列来管理锁。线程首先会尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个 Node 节点放入同步队列阻塞起来,当持有锁的线程释放锁时,就会唤醒队列中的后继线程。
#### 获取锁的伪代码 #### 获取锁的伪代码
``` ```
while (不满足获取锁的条件) { while (不满足获取锁的条件) {
把当前线程包装成节点插入同步队列 把当前线程包装成节点插入同步队列
@ -18,7 +24,9 @@ while (不满足获取锁的条件) {
} }
将当前线程从同步队列中移除 将当前线程从同步队列中移除
``` ```
#### 释放锁的伪代码 #### 释放锁的伪代码
``` ```
修改同步状态 修改同步状态
if (修改后的状态允许其他线程获取到锁) if (修改后的状态允许其他线程获取到锁)
@ -26,7 +34,9 @@ if (修改后的状态允许其他线程获取到锁)
``` ```
## 源码解析 ## 源码解析
#### AQS的核心数据结构 Node(内部类)
#### AQS 的核心数据结构 Node(内部类)
```java ```java
/** /**
* 当共享资源被某个线程占有,其他请求该资源的线程将会阻塞,从而进入同步队列。 * 当共享资源被某个线程占有,其他请求该资源的线程将会阻塞,从而进入同步队列。
@ -104,7 +114,9 @@ static final class Node {
} }
} }
``` ```
#### 获取独占锁的实现 #### 获取独占锁的实现
```java ```java
/** /**
* 首先尝试获取一次锁,如果成功,则返回; * 首先尝试获取一次锁,如果成功,则返回;
@ -337,7 +349,9 @@ private void unparkSuccessor(Node node) {
``` ```
#### 释放独占锁的实现 #### 释放独占锁的实现
释放一个独占锁首先会调用tryRelease方法在完全释放掉独占锁后其后继线程是可以获取到独占锁的因此释放线程需要做的事情是唤醒一个队列中的后继线程让它去尝试获取独占锁。
释放一个独占锁,首先会调用 tryRelease 方法,在完全释放掉独占锁后,其后继线程是可以获取到独占锁的,因此释放线程需要做的事情是:唤醒一个队列中的后继线程,让它去尝试获取独占锁。
```java ```java
public final boolean release(int arg) { public final boolean release(int arg) {
if (tryRelease(arg)) { if (tryRelease(arg)) {
@ -370,11 +384,16 @@ public final boolean release(int arg) {
return false; return false;
} }
``` ```
整个release做的事情就是
1. 调用tryRelease 整个 release 做的事情就是:
2. 如果tryRelease返回true也就是独占锁被完全释放唤醒后继线程。
1. 调用 tryRelease
2. 如果 tryRelease 返回 true 也就是独占锁被完全释放,唤醒后继线程。
#### 获取共享锁的实现 #### 获取共享锁的实现
共享锁允许多个线程持有如果要使用AQS中的共享锁在实现 tryAcquireShared方法 时需要注意返回负数表示获取失败返回0表示成功但是后继争用线程不会成功返回正数表示获取成功并且后继争用线程也可能成功。
共享锁允许多个线程持有,如果要使用 AQS 中的共享锁,在实现 tryAcquireShared 方法 时需要注意,返回负数表示获取失败,返回 0 表示成功,但是后继争用线程不会成功,返回正数表示获取成功,并且后继争用线程也可能成功。
```java ```java
public final void acquireShared(int arg) { public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) if (tryAcquireShared(arg) < 0)
@ -465,8 +484,11 @@ private void doReleaseShared() {
} }
} }
``` ```
#### 释放共享锁的实现 #### 释放共享锁的实现
共享锁的获取和释放都会涉及到 doReleaseShared方法也就是后继线程的唤醒。
共享锁的获取和释放都会涉及到 doReleaseShared 方法,也就是后继线程的唤醒。
```java ```java
public final boolean releaseShared(int arg) { public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { if (tryReleaseShared(arg)) {

@ -1 +0,0 @@
努力编写中...

@ -1,43 +1,57 @@
Spring、Netty、Mybatis 等框架的代码中大量运用了 Java 多线程编程技巧。并发编程处理的恰当与否,将直接影响架构的性能。本章通过对 这些框架源码 的分析,结合并发编程的常用技巧,来讲解多线程编程在这些主流框架中的应用。 Spring、Netty、Mybatis 等框架的代码中大量运用了 Java 多线程编程技巧。并发编程处理的恰当与否,将直接影响架构的性能。本章通过对 这些框架源码 的分析,结合并发编程的常用技巧,来讲解多线程编程在这些主流框架中的应用。
## Java内存模型 ## Java 内存模型
JVM规范 定义了 Java内存模型 来屏蔽掉各种操作系统、虚拟机实现厂商和硬件的内存访问差异,以确保 Java 程序 在所有操作系统和平台上能够达到一致的内存访问效果。
JVM 规范 定义了 Java 内存模型 来屏蔽掉各种操作系统、虚拟机实现厂商和硬件的内存访问差异,以确保 Java 程序 在所有操作系统和平台上能够达到一致的内存访问效果。
### 工作内存和主内存 ### 工作内存和主内存
Java内存模型 规定所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,工作内存保存了 对应该线程使用的变量的主内存副本拷贝。线程对这些变量的操作都在自己的工作内存中进行,不能直接操作主内存 和 其他工作内存中存储的变量或者变量副本。线程间的变量传递需通过主内存来完成,三者的关系如下图所示。
Java 内存模型 规定所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,工作内存保存了 对应该线程使用的变量的主内存副本拷贝。线程对这些变量的操作都在自己的工作内存中进行,不能直接操作主内存 和 其他工作内存中存储的变量或者变量副本。线程间的变量传递需通过主内存来完成,三者的关系如下图所示。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221000348294.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221000348294.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70)
### Java内存操作协议
Java内存模型定义了8种操作来完成主内存和工作内存的变量访问具体如下。 ### Java 内存操作协议
Java 内存模型定义了 8 种操作来完成主内存和工作内存的变量访问,具体如下。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221001115193.png) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221001115193.png)
- read把一个变量的值从主内存传输到线程的工作内存中以便随后的 load 动作使用。 - read把一个变量的值从主内存传输到线程的工作内存中以便随后的 load 动作使用。
- load把从主内存中读取的变量值载入工作内存的变量副本中。 - load把从主内存中读取的变量值载入工作内存的变量副本中。
- use把工作内存中一个变量的值传递给 Java 虚拟机执行引擎。 - use把工作内存中一个变量的值传递给 Java 虚拟机执行引擎。
- assign把从执行引擎接收到的变量的值赋值给工作内存中的变量。 - assign把从执行引擎接收到的变量的值赋值给工作内存中的变量。
- store把工作内存中一个变量的值传送到主内存中以便随后的write操作。 - store把工作内存中一个变量的值传送到主内存中以便随后的 write 操作。
- write工作内存传递过来的变量值放入主内存中。 - write工作内存传递过来的变量值放入主内存中。
- lock把主内存的一个变量标识为某个线程独占的状态。 - lock把主内存的一个变量标识为某个线程独占的状态。
- unlock把主内存中 一个处于锁定状态的变量释放出来,被释放后的变量才可以被其他线程锁定。 - unlock把主内存中 一个处于锁定状态的变量释放出来,被释放后的变量才可以被其他线程锁定。
### 内存模型三大特性 ### 内存模型三大特性
#### 1、原子性 #### 1、原子性
这个概念与事务中的原子性大概一致,表明此操作是不可分割,不可中断的,要么全部执行,要么全部不执行。 Java内存模型直接保证的原子性操作包括read、load、use、assign、store、write、lock、unlock这八个。
这个概念与事务中的原子性大概一致,表明此操作是不可分割,不可中断的,要么全部执行,要么全部不执行。 Java 内存模型直接保证的原子性操作包括 read、load、use、assign、store、write、lock、unlock 这八个。
#### 2、可见性 #### 2、可见性
可见性是指当一个线程修改了共享变量的值其他线程能够立即得知这个修改。Java内存模型 是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是 volatile变量 都是如此,普通变量与 volatile变量 的区别是volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。除了 volatile 外synchronized 也提供了可见性synchronized 的可见性是由 “对一个变量执行 unlock操作 之前,必须先把此变量同步回主内存中(执行 store、write 操作)” 这条规则获得。
可见性是指当一个线程修改了共享变量的值其他线程能够立即得知这个修改。Java 内存模型 是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是 volatile 变量 都是如此,普通变量与 volatile 变量 的区别是volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。除了 volatile 外synchronized 也提供了可见性synchronized 的可见性是由 “对一个变量执行 unlock 操作 之前,必须先把此变量同步回主内存中(执行 store、write 操作)” 这条规则获得。
#### 3、有序性 #### 3、有序性
单线程环境下,程序会 “有序的”执行,即:线程内表现为串行语义。但是在多线程环境下,由于指令重排,并发执行的正确性会受到影响。在 Java 中使用 volatile 和 synchronized 关键字可以保证多线程执行的有序性。volatile 通过加入内存屏障指令来禁止内存的重排序。synchronized 通过加锁,保证同一时刻只有一个线程来执行同步代码。 单线程环境下,程序会 “有序的”执行,即:线程内表现为串行语义。但是在多线程环境下,由于指令重排,并发执行的正确性会受到影响。在 Java 中使用 volatile 和 synchronized 关键字可以保证多线程执行的有序性。volatile 通过加入内存屏障指令来禁止内存的重排序。synchronized 通过加锁,保证同一时刻只有一个线程来执行同步代码。
## volatile 的应用 ## volatile 的应用
打开 NioEventLoop 的代码中,有一个控制 IO操作 和 其他任务运行比例的,用 volatile 修饰的 int类型字段 ioRatio代码如下。
打开 NioEventLoop 的代码中,有一个控制 IO 操作 和 其他任务运行比例的,用 volatile 修饰的 int 类型字段 ioRatio代码如下。
```java ```java
private volatile int ioRatio = 50; private volatile int ioRatio = 50;
``` ```
这里为什么要用 volatile 修饰呢?我们首先对 volatile 关键字进行说明,然后再结合 Netty 的代码进行分析。 这里为什么要用 volatile 修饰呢?我们首先对 volatile 关键字进行说明,然后再结合 Netty 的代码进行分析。
关键字 volatile 是 Java 提供的最轻量级的同步机制Java 内存模型对 volatile 专门定义了一些特殊的访问规则。下面我们就看它的规则。当一个变量被 volatile 修饰后,它将具备以下两种特性。 关键字 volatile 是 Java 提供的最轻量级的同步机制Java 内存模型对 volatile 专门定义了一些特殊的访问规则。下面我们就看它的规则。当一个变量被 volatile 修饰后,它将具备以下两种特性。
- 线程可见性:当一个线程修改了被 volatile 修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改(什么叫立即看到最新的修改?感觉这句话太口语化且模糊,搞不太懂!),而普通变量却做不到这点。 - 线程可见性:当一个线程修改了被 volatile 修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改(什么叫立即看到最新的修改?感觉这句话太口语化且模糊,搞不太懂!),而普通变量却做不到这点。
- 禁止指令重排序优化:普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题,代码如下。 - 禁止指令重排序优化:普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题,代码如下。
```java ```java
public class ThreadStopExample { public class ThreadStopExample {
@ -63,20 +77,26 @@ public class ThreadStopExample {
} }
} }
``` ```
我们预期程序会在 3s 后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下。 我们预期程序会在 3s 后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下。
```java ```java
if (!stop) if (!stop)
While(true) While(true)
...... ......
``` ```
workThread线程 在执行重排序后的代码时,是无法发现 变量stop 被其它线程修改的,因此无法停止运行。要解决这个问题,只要将 stop 前增加 volatile 修饰符即可。volatile 解决了如下两个问题。第一,主线程对 stop 的修改在 workThread线程 中可见,也就是说 workThread线程 立即看到了其他线程对于 stop变量 的修改。第二,禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。
workThread 线程 在执行重排序后的代码时,是无法发现 变量 stop 被其它线程修改的,因此无法停止运行。要解决这个问题,只要将 stop 前增加 volatile 修饰符即可。volatile 解决了如下两个问题。第一,主线程对 stop 的修改在 workThread 线程 中可见,也就是说 workThread 线程 立即看到了其他线程对于 stop 变量 的修改。第二,禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。
一些人认为使用 volatile 可以代替传统锁提升并发性能这个认识是错误的。volatile 仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠 volatile 来完全替代传统的锁。根据经验总结volatile 最适用的场景是 “ 一个线程写,其他线程读 ”,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。下面我们继续对 Netty 的源码做分析。上面讲到了 ioRatio 被定义成 volatile下面看看代码为什么要这样定义。 一些人认为使用 volatile 可以代替传统锁提升并发性能这个认识是错误的。volatile 仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠 volatile 来完全替代传统的锁。根据经验总结volatile 最适用的场景是 “ 一个线程写,其他线程读 ”,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。下面我们继续对 Netty 的源码做分析。上面讲到了 ioRatio 被定义成 volatile下面看看代码为什么要这样定义。
```java ```java
final long ioTime = System.nanoTime() - ioStartTime; final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio); runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
``` ```
通过代码分析我们发现,在 NioEventLoop线程 中ioRatio 并没有被修改,它是只读操作。既然没有修改,为什么要定义成 volatile 呢?继续看代码,我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法。
通过代码分析我们发现,在 NioEventLoop 线程 中ioRatio 并没有被修改,它是只读操作。既然没有修改,为什么要定义成 volatile 呢?继续看代码,我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法。
```java ```java
public void setIoRatio(int ioRatio) { public void setIoRatio(int ioRatio) {
if (ioRatio <= 0 || ioRatio > 100) { if (ioRatio <= 0 || ioRatio > 100) {
@ -85,13 +105,17 @@ workThread线程 在执行重排序后的代码时,是无法发现 变量stop
this.ioRatio = ioRatio; this.ioRatio = ioRatio;
} }
``` ```
首先NioEventLoop线程 没有调用该 set方法说明调整 IO执行时间比例 是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对 volatile 的应用总结,此时可以使用 volatile 来代替传统的 synchronized关键字以提升并发访问的性能。
首先NioEventLoop 线程 没有调用该 set 方法,说明调整 IO 执行时间比例 是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对 volatile 的应用总结,此时可以使用 volatile 来代替传统的 synchronized 关键字,以提升并发访问的性能。
## ThreadLocal 的应用及源码解析 ## ThreadLocal 的应用及源码解析
ThreadLocal 又称为线程本地存储区Thread Local Storage简称为TLS每个线程都有自己的私有的本地存储区域不同线程之间彼此不能访问对方的 TLS区域。使用 ThreadLocal变量 的 set(T value)方法 可以将数据存入 该线程本地存储区,使用 get() 方法 可以获取到之前存入的值。
### ThreadLocal的常见应用 ThreadLocal 又称为线程本地存储区Thread Local Storage简称为 TLS每个线程都有自己的私有的本地存储区域不同线程之间彼此不能访问对方的 TLS 区域。使用 ThreadLocal 变量 的 set(T value)方法 可以将数据存入 该线程本地存储区,使用 get() 方法 可以获取到之前存入的值。
### ThreadLocal 的常见应用
不使用 ThreadLocal。 不使用 ThreadLocal。
```java ```java
public class SessionBean { public class SessionBean {
public static class Session { public static class Session {
@ -125,7 +149,9 @@ public class SessionBean {
} }
} }
``` ```
上述代码中session需要在方法间传递才可以修改和读取保证线程中各方法操作的是一个。下面看一下使用 ThreadLocal 的代码。
上述代码中session 需要在方法间传递才可以修改和读取,保证线程中各方法操作的是一个。下面看一下使用 ThreadLocal 的代码。
```java ```java
public class SessionBean { public class SessionBean {
//定义一个静态ThreadLocal变量session就能够保证各个线程有自己的一份并且方法可以方便获取不用传递 //定义一个静态ThreadLocal变量session就能够保证各个线程有自己的一份并且方法可以方便获取不用传递
@ -162,10 +188,13 @@ public class SessionBean {
} }
} }
``` ```
在方法的内部实现中,直接可以通过 session.get() 获取到当前线程的 session省掉了参数在方法间传递的环节。 在方法的内部实现中,直接可以通过 session.get() 获取到当前线程的 session省掉了参数在方法间传递的环节。
### ThreadLocal的实现原理 ### ThreadLocal 的实现原理
一般,类属性中的数据是多个线程共享的,但 ThreadLocal类型的数据 声明为类属性却可以为每一个使用它通过set(T value)方法)的线程存储 线程私有的数据,通过其源码我们可以发现其中的原理。
一般,类属性中的数据是多个线程共享的,但 ThreadLocal 类型的数据 声明为类属性,却可以为每一个使用它(通过 set(T value)方法)的线程存储 线程私有的数据,通过其源码我们可以发现其中的原理。
```java ```java
public class ThreadLocal<T> { public class ThreadLocal<T> {
@ -204,7 +233,9 @@ public class ThreadLocal<T> {
``` ```
### ThreadLocal 在 Spring 中的使用 ### ThreadLocal 在 Spring 中的使用
Spring事务处理的设计与实现中大量使用了 ThreadLocal类比如TransactionSynchronizationManager 维护了一系列的 ThreadLocal变量用于存储线程私有的 事务属性及资源。源码如下。
Spring 事务处理的设计与实现中大量使用了 ThreadLocal 类比如TransactionSynchronizationManager 维护了一系列的 ThreadLocal 变量,用于存储线程私有的 事务属性及资源。源码如下。
```java ```java
/** /**
* 管理每个线程的资源和事务同步的中心帮助程序。供资源管理代码使用,但不供典型应用程序代码使用。 * 管理每个线程的资源和事务同步的中心帮助程序。供资源管理代码使用,但不供典型应用程序代码使用。
@ -274,8 +305,10 @@ public abstract class TransactionSynchronizationManager {
} }
``` ```
### ThreadLocal 在 Mybatis中的使用 ### ThreadLocal 在 Mybatis 中的使用
Mybatis 的 SqlSession对象 也是各线程私有的资源,所以对其的管理也使用到了 ThreadLocal类。源码如下。
Mybatis 的 SqlSession 对象 也是各线程私有的资源,所以对其的管理也使用到了 ThreadLocal 类。源码如下。
```java ```java
public class SqlSessionManager implements SqlSessionFactory, SqlSession { public class SqlSessionManager implements SqlSessionFactory, SqlSession {
@ -395,9 +428,12 @@ public class SqlSessionManager implements SqlSessionFactory, SqlSession {
} }
``` ```
## J.U.C包的实际应用 ## J.U.C 包的实际应用
### 线程池 ThreadPoolExecutor ### 线程池 ThreadPoolExecutor
首先通过 ThreadPoolExecutor 的源码 看一下线程池的主要参数及方法。 首先通过 ThreadPoolExecutor 的源码 看一下线程池的主要参数及方法。
```java ```java
public class ThreadPoolExecutor extends AbstractExecutorService { public class ThreadPoolExecutor extends AbstractExecutorService {
@ -569,30 +605,36 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
} }
} }
``` ```
线程池执行流程,如下图所示。 线程池执行流程,如下图所示。
![avatar](images/ConcurrentProgramming/线程池流程.png) ![avatar](images/ConcurrentProgramming/线程池流程.png)
#### Executors 提供的4种线程池 #### Executors 提供的 4 种线程池
Executors类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池CachedThreadPoolFixedThreadPoolScheduledThreadPool 和 SingleThreadExecutor。其功能如下。
Executors 类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池CachedThreadPoolFixedThreadPoolScheduledThreadPool 和 SingleThreadExecutor。其功能如下。
1. CachedThreadPool用来创建一个几乎可以无限扩大的线程池最大线程数为 Integer.MAX_VALUE适用于执行大量短生命周期的异步任务。 1. CachedThreadPool用来创建一个几乎可以无限扩大的线程池最大线程数为 Integer.MAX_VALUE适用于执行大量短生命周期的异步任务。
2. FixedThreadPool创建一个固定大小的线程池保证线程数可控不会造成线程过多导致系统负载更为严重。 2. FixedThreadPool创建一个固定大小的线程池保证线程数可控不会造成线程过多导致系统负载更为严重。
3. SingleThreadExecutor创建一个单线程的线程池可以保证任务按调用顺序执行。 3. SingleThreadExecutor创建一个单线程的线程池可以保证任务按调用顺序执行。
4. ScheduledThreadPool适用于执行 延时 或者 周期性 任务。 4. ScheduledThreadPool适用于执行 延时 或者 周期性 任务。
#### 如何配置线程池 #### 如何配置线程池
- **CPU密集型任务**
尽量使用较小的线程池,一般为 CPU核心数+1。 因为 CPU密集型任务 使得 CPU使用率 很高,若开过多的线程数,会造成 CPU 过度切换。
- **IO密集型任务** - **CPU 密集型任务**
可以使用稍大的线程池,一般为 2*CPU核心数。 IO密集型任务 CPU使用率 并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU时间。 尽量使用较小的线程池,一般为 CPU 核心数+1。 因为 CPU 密集型任务 使得 CPU 使用率 很高,若开过多的线程数,会造成 CPU 过度切换。
- **IO 密集型任务**
可以使用稍大的线程池,一般为 2\*CPU 核心数。 IO 密集型任务 CPU 使用率 并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU 时间。
#### 线程池的实际应用 #### 线程池的实际应用
Tomcat 在分发 web请求 时使用了线程池来处理。
Tomcat 在分发 web 请求 时使用了线程池来处理。
### BlockingQueue ### BlockingQueue
#### 核心方法 #### 核心方法
```java ```java
public interface BlockingQueue<E> extends Queue<E> { public interface BlockingQueue<E> extends Queue<E> {
@ -632,23 +674,22 @@ public interface BlockingQueue<E> extends Queue<E> {
int drainTo(Collection<? super E> c, int maxElements); int drainTo(Collection<? super E> c, int maxElements);
} }
``` ```
#### 主要实现类 #### 主要实现类
- **ArrayBlockingQueue** - **ArrayBlockingQueue**
基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部维护了一个定长数组以便缓存队列中的数据对象这是一个常用的阻塞队列除了一个定长数组外ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。 基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部维护了一个定长数组以便缓存队列中的数据对象这是一个常用的阻塞队列除了一个定长数组外ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
ArrayBlockingQueue 在生产者放入数据 和 消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue。ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node对象。这在长时间内需要高效并发地处理大批量数据的系统中其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。 ArrayBlockingQueue 在生产者放入数据 和 消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue。ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
- **LinkedBlockingQueue** - **LinkedBlockingQueue**
基于链表的阻塞队列,同 ArrayListBlockingQueue 类似其内部也维持着一个数据缓冲队列该队列由一个链表构成当生产者往队列中放入一个数据时队列会从生产者手中获取数据并缓存在队列内部而生产者立即返回只有当队列缓冲区达到最大值缓存容量时LinkedBlockingQueue可以通过构造函数指定该值才会阻塞生产者队列直到消费者从队列中消费掉一份数据生产者线程会被唤醒反之对于消费者这端的处理也基于同样的原理。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。 基于链表的阻塞队列,同 ArrayListBlockingQueue 类似其内部也维持着一个数据缓冲队列该队列由一个链表构成当生产者往队列中放入一个数据时队列会从生产者手中获取数据并缓存在队列内部而生产者立即返回只有当队列缓冲区达到最大值缓存容量时LinkedBlockingQueue 可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
需要注意的是,如果构造一个 LinkedBlockingQueue对象而没有指定其容量大小LinkedBlockingQueue 会默认一个类似无限大小的容量Integer.MAX_VALUE这样的话如果生产者的速度一旦大于消费者的速度也许还没有等到队列满阻塞产生系统内存就有可能已被消耗殆尽了。 需要注意的是,如果构造一个 LinkedBlockingQueue 对象而没有指定其容量大小LinkedBlockingQueue 会默认一个类似无限大小的容量Integer.MAX_VALUE这样的话如果生产者的速度一旦大于消费者的速度也许还没有等到队列满阻塞产生系统内存就有可能已被消耗殆尽了。
- **PriorityBlockingQueue** - **PriorityBlockingQueue**
基于优先级的阻塞队列优先级的判断通过构造函数传入的Compator对象来决定但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者而只会在没有可消费的数据时阻塞数据的消费者。因此使用的时候要特别注意生产者生产数据的速度绝对不能快于消费者消费数据的速度否则时间一长会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时内部控制线程同步的锁采用的是公平锁。 基于优先级的阻塞队列(优先级的判断通过构造函数传入的 Compator 对象来决定),但需要注意的是 PriorityBlockingQueue 并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁。
### CAS 指令和原子类(应用比较多的就是计数器)
### CAS指令和原子类应用比较多的就是计数器
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为**阻塞同步**,它属于一种**悲观的并发策略,我们称之为悲观锁**。随着硬件和操作系统指令集的发展和优化,产生了**非阻塞同步**,被称为**乐观锁**。简单地说,就是**先进行操作,操作完成之后再判断操作是否成功,是否有并发问题,如果有则进行失败补偿,如果没有就算操作成功**,这样就从根本上避免了同步锁的弊端。 互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为**阻塞同步**,它属于一种**悲观的并发策略,我们称之为悲观锁**。随着硬件和操作系统指令集的发展和优化,产生了**非阻塞同步**,被称为**乐观锁**。简单地说,就是**先进行操作,操作完成之后再判断操作是否成功,是否有并发问题,如果有则进行失败补偿,如果没有就算操作成功**,这样就从根本上避免了同步锁的弊端。
目前在Java中应用最广泛的非阻塞同步就是 CAS。从 JDK1.5 以后,可以使用 CAS 操作,该操作由 sun.misc.Unsafe 类里的 compareAndSwapInt() 和 compareAndSwapLong() 等方法实现。通常情况下 sun.misc.Unsafe类 对于开发者是不可见的因此JDK 提供了很多 CAS包装类 简化开发者的使用,如 AtomicInteger。使用 Java 自带的 Atomic原子类可以避免同步锁带来的并发访问性能降低的问题减少犯错的机会。 目前,在 Java 中应用最广泛的非阻塞同步就是 CAS。从 JDK1.5 以后,可以使用 CAS 操作,该操作由 sun.misc.Unsafe 类里的 compareAndSwapInt() 和 compareAndSwapLong() 等方法实现。通常情况下 sun.misc.Unsafe 类 对于开发者是不可见的因此JDK 提供了很多 CAS 包装类 简化开发者的使用,如 AtomicInteger。使用 Java 自带的 Atomic 原子类,可以避免同步锁带来的并发访问性能降低的问题,减少犯错的机会。

@ -1,15 +1,19 @@
设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring系列、Mybatis及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring 系列、Mybatis JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。
本篇博文主要看一下创建型的几个设计模式,即,单例模式、各种工厂模式 及 建造者模式。 本篇博文主要看一下创建型的几个设计模式,即,单例模式、各种工厂模式 及 建造者模式。
## 单例模式 ## 单例模式
### 个人理解 ### 个人理解
确保某个类只有一个实例并提供该实例的获取方法。实际应用很多不管是框架、JDK还是实际的项目开发但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用但通过“双检锁机制”来保证单例的实现很少见。
确保某个类只有一个实例并提供该实例的获取方法。实际应用很多不管是框架、JDK 还是实际的项目开发,但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用,但通过“双检锁机制”来保证单例的实现很少见。
### 实现方式 ### 实现方式
最简单的就是 使用一个私有构造函数、一个私有静态变量以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多BB咯这里强调一下双检锁懒汉式实现的坑以及枚举方式的实现吧最后再结合spring源码 扩展一下单例bean的实现原理。
最简单的就是 使用一个私有构造函数、一个私有静态变量,以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多 BB 咯,这里强调一下双检锁懒汉式实现的坑,以及枚举方式的实现吧,最后再结合 spring 源码 扩展一下单例 bean 的实现原理。
**1. 双检锁实现的坑** **1. 双检锁实现的坑**
```java ```java
/** /**
* @author 云之君 * @author 云之君
@ -48,10 +52,12 @@ public class Singleton3 {
} }
} }
``` ```
**2. 枚举实现** **2. 枚举实现**
其它的单例模式实现往往都会面临序列化 和 反射攻击的问题比如上面的Singleton3如果实现了Serializable接口那么在每次序列化时都会创建一个新对象若要保证单例必须声明所有字段都是transient的并且提供一个readResolve()方法。反射攻击可以通过setAccessible()方法将私有的构造方法公共化,进而实例化。若要防止这种攻击,就需要在构造方法中添加 防止实例化第二个对象的代码。 其它的单例模式实现往往都会面临序列化 和 反射攻击的问题,比如上面的 Singleton3 如果实现了 Serializable 接口,那么在每次序列化时都会创建一个新对象,若要保证单例,必须声明所有字段都是 transient 的,并且提供一个 readResolve()方法。反射攻击可以通过 setAccessible()方法将私有的构造方法公共化,进而实例化。若要防止这种攻击,就需要在构造方法中添加 防止实例化第二个对象的代码。
枚举实现的单例在面对 复杂的序列化及反射攻击时依然能够保持自己的单例状态所以被认为是单例的最佳实践。比如mybatis 在定义 SQL 命令类型时就使用到了枚举。
枚举实现的单例在面对 复杂的序列化及反射攻击时依然能够保持自己的单例状态所以被认为是单例的最佳实践。比如mybatis在定义SQL命令类型时就使用到了枚举。
```java ```java
package org.apache.ibatis.mapping; package org.apache.ibatis.mapping;
@ -63,8 +69,10 @@ public enum SqlCommandType {
} }
``` ```
### JDK中的范例 ### JDK 中的范例
**1. java.lang.Runtime** **1. java.lang.Runtime**
```java ```java
/** /**
* 每个Java应用程序都有一个单例的Runtime对象通过getRuntime()方法获得 * 每个Java应用程序都有一个单例的Runtime对象通过getRuntime()方法获得
@ -86,6 +94,7 @@ public class Runtime {
``` ```
**2. java.awt.Desktop** **2. java.awt.Desktop**
```java ```java
public class Desktop { public class Desktop {
@ -120,8 +129,10 @@ public class Desktop {
} }
``` ```
### Spring的单例bean是如何实现的 ### Spring 的单例 bean 是如何实现的?
Spring实现单例bean是使用map注册表和synchronized同步机制实现的通过分析spring的 AbstractBeanFactory 中的 doGetBean 方法和DefaultSingletonBeanRegistry的getSingleton()方法,可以理解其实现原理。
Spring 实现单例 bean 是使用 map 注册表和 synchronized 同步机制实现的,通过分析 spring 的 AbstractBeanFactory 中的 doGetBean 方法和 DefaultSingletonBeanRegistry 的 getSingleton()方法,可以理解其实现原理。
```java ```java
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@ -227,9 +238,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
``` ```
## 简单工厂模式 ## 简单工厂模式
### 个人理解 ### 个人理解
把同一系列类的实例化交由一个工厂类进行集中管控。与其说它是一种设计模式,倒不如把它看成一种编程习惯,因为它不符合“开闭原则”,增加新的产品类需要修改工厂类的代码。 把同一系列类的实例化交由一个工厂类进行集中管控。与其说它是一种设计模式,倒不如把它看成一种编程习惯,因为它不符合“开闭原则”,增加新的产品类需要修改工厂类的代码。
### 简单实现 ### 简单实现
```java ```java
public interface Hero { public interface Hero {
void speak(); void speak();
@ -261,14 +276,19 @@ public class HeroFactory {
} }
} }
``` ```
这种设计方式只在我们产品的“FBM资金管理”模块有看到过其中对100+个按钮类进行了集中管控,不过其设计结构比上面这种要复杂的多。
这种设计方式只在我们产品的“FBM 资金管理”模块有看到过,其中对 100+个按钮类进行了集中管控,不过其设计结构比上面这种要复杂的多。
## 工厂方法模式 ## 工厂方法模式
### 个人理解 ### 个人理解
在顶级工厂(接口/抽象类)中定义 产品类的获取方法,由具体的子工厂实例化对应的产品,一般是一个子工厂对应一个特定的产品,实现对产品的集中管控,并且符合“开闭原则”。 在顶级工厂(接口/抽象类)中定义 产品类的获取方法,由具体的子工厂实例化对应的产品,一般是一个子工厂对应一个特定的产品,实现对产品的集中管控,并且符合“开闭原则”。
### Mybatis中的范例 ### Mybatis 中的范例
mybatis中数据源DataSource的获取使用到了该设计模式。接口DataSourceFactory定义了获取DataSource对象的方法各实现类 完成了获取对应类型的DataSource对象的实现。(mybatis的源码都是缩进两个空格难道国外的编码规范有独门派系)
mybatis 中数据源 DataSource 的获取使用到了该设计模式。接口 DataSourceFactory 定义了获取 DataSource 对象的方法,各实现类 完成了获取对应类型的 DataSource 对象的实现。(mybatis 的源码都是缩进两个空格,难道国外的编码规范有独门派系?)
```java ```java
public interface DataSourceFactory { public interface DataSourceFactory {
@ -348,16 +368,20 @@ public interface DataSource extends CommonDataSource, Wrapper {
throws SQLException; throws SQLException;
} }
``` ```
DataSource最主要的几个实现类内容都比较多代码就不贴出来咯感兴趣的同学可以到我的源码分析专题中看到详细解析。
DataSource 最主要的几个实现类内容都比较多,代码就不贴出来咯,感兴趣的同学可以到我的源码分析专题中看到详细解析。
**tips什么时候该用简单工厂模式什么时候该用工厂方法模式呢** **tips什么时候该用简单工厂模式什么时候该用工厂方法模式呢**
个人认为,工厂方法模式符合“开闭原则”,增加新的产品类不用修改代码,应当优先考虑使用这种模式。如果产品类结构简单且数量庞大时,还是使用简单工厂模式更容易维护些,如:上百个按钮类。 个人认为,工厂方法模式符合“开闭原则”,增加新的产品类不用修改代码,应当优先考虑使用这种模式。如果产品类结构简单且数量庞大时,还是使用简单工厂模式更容易维护些,如:上百个按钮类。
## 抽象工厂模式 ## 抽象工厂模式
### 个人理解 ### 个人理解
设计结构上与“工厂方法”模式很像,最主要的区别是,工厂方法模式中 一个子工厂只对应**一个**具体的产品,而抽象工厂模式中,一个子工厂对应**一组**具有相关性的产品,即,存在多个获取不同产品的方法。这种设计模式也很少见人用,倒是“工厂方法”模式见的最多。 设计结构上与“工厂方法”模式很像,最主要的区别是,工厂方法模式中 一个子工厂只对应**一个**具体的产品,而抽象工厂模式中,一个子工厂对应**一组**具有相关性的产品,即,存在多个获取不同产品的方法。这种设计模式也很少见人用,倒是“工厂方法”模式见的最多。
### 简单实现 ### 简单实现
```java ```java
public abstract class AbstractFactory { public abstract class AbstractFactory {
@ -409,8 +433,10 @@ public class Client {
} }
``` ```
### JDK中的范例 ### JDK 中的范例
JDK的javax.xml.transform.TransformerFactory组件使用了类似“抽象工厂”模式的设计抽象类TransformerFactory定义了两个抽象方法newTransformer()和newTemplates()分别用于生成Transformer对象 和 Templates对象其两个子类进行了不同的实现源码如下版本1.8)。
JDK 的 javax.xml.transform.TransformerFactory 组件使用了类似“抽象工厂”模式的设计,抽象类 TransformerFactory 定义了两个抽象方法 newTransformer()和 newTemplates()分别用于生成 Transformer 对象 和 Templates 对象,其两个子类进行了不同的实现,源码如下(版本 1.8)。
```java ```java
public abstract class TransformerFactory { public abstract class TransformerFactory {
@ -483,7 +509,9 @@ public class SmartTransformerFactoryImpl extends SAXTransformerFactory {
``` ```
## 建造者模式 ## 建造者模式
### 个人理解 ### 个人理解
该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。 该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。
![avatar](../../../images/DesignPattern/建造者模式类图.png) ![avatar](../../../images/DesignPattern/建造者模式类图.png)
@ -497,8 +525,10 @@ public class SmartTransformerFactoryImpl extends SAXTransformerFactory {
其中的导演角色不必了解产品类的内部细节,只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造,使产品对象的上层代码与产品对象的创建过程解耦。建造者模式将复杂产品的创建过程分散到不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的, 因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展,这符合“开放一封闭” 原则。 其中的导演角色不必了解产品类的内部细节,只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造,使产品对象的上层代码与产品对象的创建过程解耦。建造者模式将复杂产品的创建过程分散到不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的, 因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展,这符合“开放一封闭” 原则。
### 典型的范例 StringBuilder和StringBuffer ### 典型的范例 StringBuilder 和 StringBuffer
相信在拼SQL语句时大家一定经常用到StringBuffer和StringBuilder这两个类它们就用到了建造者设计模式源码如下版本1.8
相信在拼 SQL 语句时大家一定经常用到 StringBuffer 和 StringBuilder 这两个类,它们就用到了建造者设计模式,源码如下(版本 1.8
```java ```java
abstract class AbstractStringBuilder implements Appendable, CharSequence { abstract class AbstractStringBuilder implements Appendable, CharSequence {
@ -582,12 +612,15 @@ public final class StringBuffer extends AbstractStringBuilder
} }
} }
``` ```
### Mybatis中的范例
MyBatis 的初始化过程使用了建造者模式,抽象类 BaseBuilder 扮演了“建造者接口”的角色对一些公用方法进行了实现并定义了公共属性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等实现类扮演了“具体建造者”的角色分别用于解析mybatis-config.xml配置文件、映射配置文件 以及 SQL节点。Configuration 和 SqlSessionFactoryBuilder 则分别扮演了“产品” 和 “导演”的角色。**即SqlSessionFactoryBuilder 使用了 BaseBuilder建造者组件 对复杂对象 Configuration 进行了构建。**
BaseBuilder组件的设计与上面标准的建造者模式是有很大不同的BaseBuilder的建造者模式主要是为了将复杂对象Configuration的构建过程分解的层次更清晰将整个构建过程分解到多个“具体构造者”类中需要这些“具体构造者”共同配合才能完成Configuration的构造单个“具体构造者”不具有单独构造产品的能力这与StringBuilder及StringBuffer是不同的。 ### Mybatis 中的范例
MyBatis 的初始化过程使用了建造者模式,抽象类 BaseBuilder 扮演了“建造者接口”的角色对一些公用方法进行了实现并定义了公共属性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等实现类扮演了“具体建造者”的角色,分别用于解析 mybatis-config.xml 配置文件、映射配置文件 以及 SQL 节点。Configuration 和 SqlSessionFactoryBuilder 则分别扮演了“产品” 和 “导演”的角色。**即SqlSessionFactoryBuilder 使用了 BaseBuilder 建造者组件 对复杂对象 Configuration 进行了构建。**
BaseBuilder 组件的设计与上面标准的建造者模式是有很大不同的BaseBuilder 的建造者模式主要是为了将复杂对象 Configuration 的构建过程分解的层次更清晰,将整个构建过程分解到多个“具体构造者”类中,需要这些“具体构造者”共同配合才能完成 Configuration 的构造,单个“具体构造者”不具有单独构造产品的能力,这与 StringBuilder 及 StringBuffer 是不同的。
个人理解的构建者模式 其核心就是用来构建复杂对象的,比如 mybatis 对 Configuration 对象的构建。当然,我们也可以把 对这个对象的构建过程 写在一个类中,来满足我们的需求,但这样做的话,这个类就会变得及其臃肿,难以维护。所以把整个构建过程合理地拆分到多个类中,分别构建,整个代码就显得非常规整,且思路清晰,而且 建造者模式符合 开闭原则。其源码实现如下。 个人理解的构建者模式 其核心就是用来构建复杂对象的,比如 mybatis 对 Configuration 对象的构建。当然,我们也可以把 对这个对象的构建过程 写在一个类中,来满足我们的需求,但这样做的话,这个类就会变得及其臃肿,难以维护。所以把整个构建过程合理地拆分到多个类中,分别构建,整个代码就显得非常规整,且思路清晰,而且 建造者模式符合 开闭原则。其源码实现如下。
```java ```java
public abstract class BaseBuilder { public abstract class BaseBuilder {

@ -1,17 +1,21 @@
设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring系列、Mybatis及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring 系列、Mybatis JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。
本篇博文主要看一下结构型的几个设计模式,即,适配器模式、代理模式 及 装饰器模式。 本篇博文主要看一下结构型的几个设计模式,即,适配器模式、代理模式 及 装饰器模式。
## 适配器模式 ## 适配器模式
#### 个人理解 #### 个人理解
从名字就很好理解,主要起到一个连接适配的作用。生活中也有很多这样的例子,比如我们给笔记本充电,不能直接使用国家标准电源,都需要一个“电源适配器”来适配电源输入的电流。使用适配器模式最大的好处就是复用现有组件。应用程序需要复用现有的类,但接口不能被该应用程序兼容,则无法直接使用。这种场景下就适合使用适配器模式实现接口的适配,从而完成组件的复用。 从名字就很好理解,主要起到一个连接适配的作用。生活中也有很多这样的例子,比如我们给笔记本充电,不能直接使用国家标准电源,都需要一个“电源适配器”来适配电源输入的电流。使用适配器模式最大的好处就是复用现有组件。应用程序需要复用现有的类,但接口不能被该应用程序兼容,则无法直接使用。这种场景下就适合使用适配器模式实现接口的适配,从而完成组件的复用。
很明显,适配器模式通过提供 Adapter 的方式完成接口适配,实现了程序复用 Adaptee(被适配者) 的需求,避免了修改 Adaptee 实现接口,当有新的 Adaptee 需要被复用时,只要添加新的 Adapter 即可,这是符合“开放封闭”原则的。 很明显,适配器模式通过提供 Adapter 的方式完成接口适配,实现了程序复用 Adaptee(被适配者) 的需求,避免了修改 Adaptee 实现接口,当有新的 Adaptee 需要被复用时,只要添加新的 Adapter 即可,这是符合“开放封闭”原则的。
本模式的应用也比较广泛,因为实际的开发中也有很多适配工作要做,所以 这些都可以考虑使用适配器模式。在spring及mybatis中也使用了本模式分析如下。 本模式的应用也比较广泛,因为实际的开发中也有很多适配工作要做,所以 这些都可以考虑使用适配器模式。在 spring 及 mybatis 中也使用了本模式,分析如下。
#### Spring 中的应用
Spring 在 AOP 模块中,设计了一套 AdvisorAdapter 组件,将各种 Advice 对象适配成了相对应的 MethodInterceptor 对象。其中AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter 及 ThrowsAdviceAdapter 实现类扮演了“适配器”的角色AfterReturningAdvice、MethodBeforeAdvice 及 ThrowsAdvice 扮演了“被适配者”角色,而 AfterReturningAdviceInterceptor、MethodBeforeAdviceInterceptor 及 ThrowsAdviceInterceptor 则扮演了“适配目标”的角色。其源码实现如下。
#### Spring中的应用
Spring 在 AOP 模块中,设计了一套 AdvisorAdapter 组件,将各种 Advice 对象适配成了相对应的 MethodInterceptor 对象。其中AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter 及 ThrowsAdviceAdapter 实现类扮演了“适配器”的角色AfterReturningAdvice、MethodBeforeAdvice 及 ThrowsAdvice 扮演了“被适配者”角色而AfterReturningAdviceInterceptor、MethodBeforeAdviceInterceptor 及 ThrowsAdviceInterceptor 则扮演了“适配目标”的角色。其源码实现如下。
```java ```java
/** /**
* Advice 适配器的顶级接口 * Advice 适配器的顶级接口
@ -283,12 +287,15 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se
} }
} }
``` ```
像这样整理出来以后,其类结构及层次设计还是比较清晰明了的,比起很多书上范例的浅尝辄止,结合这些实际场景及源码去理解这些设计模式,要让人更加印象深刻。 像这样整理出来以后,其类结构及层次设计还是比较清晰明了的,比起很多书上范例的浅尝辄止,结合这些实际场景及源码去理解这些设计模式,要让人更加印象深刻。
#### Mybatis中的应用 #### Mybatis 中的应用
MyBatis 的日志模块中使用了适配器模式MyBatis 内部调用其日志模块时使用了其内部接口org.apache.ibatis.logging.Log。但是 Log4j、Slf4j 等第三方日志框架对外提供的接口各不相同MyBatis 为了集成和复用这些第三方日志框架,在其日志模块中提供了多种 Adapter 实现 如Log4jImpl、Slf4jImpl 等等,它们将这些 “第三方日志框架对外的接口方法” 适配成 “Log 接口方法”,这样 MyBatis 内部就可以统一通过该 Log 接口调用第三方日志框架的功能了。 MyBatis 的日志模块中使用了适配器模式MyBatis 内部调用其日志模块时使用了其内部接口org.apache.ibatis.logging.Log。但是 Log4j、Slf4j 等第三方日志框架对外提供的接口各不相同MyBatis 为了集成和复用这些第三方日志框架,在其日志模块中提供了多种 Adapter 实现 如Log4jImpl、Slf4jImpl 等等,它们将这些 “第三方日志框架对外的接口方法” 适配成 “Log 接口方法”,这样 MyBatis 内部就可以统一通过该 Log 接口调用第三方日志框架的功能了。
其中Log 接口定义了日志模块的功能,日志适配器 Log4jImpl、Slf4jImpl 等通过实现此接口,将对应框架中的日志类 (Logger) 里的方法 适配成Log接口中定义的方法。 其中Log 接口定义了日志模块的功能,日志适配器 Log4jImpl、Slf4jImpl 等通过实现此接口,将对应框架中的日志类 (Logger) 里的方法 适配成 Log 接口中定义的方法。
```java ```java
/** /**
* mybatis的日志接口统一了不同日志框架的 日志操作, * mybatis的日志接口统一了不同日志框架的 日志操作,
@ -426,13 +433,17 @@ public class Jdk14LoggingImpl implements Log {
``` ```
## 代理模式 ## 代理模式
#### 个人理解 #### 个人理解
代理模式的实际应用 主要体现在框架开发中,日常业务上的开发工作中很少有场景需要使用该模式。而代理模式中 动态代理尤为重要,不管是自己公司的内部框架 还是 一些知名的开源框架,很多重要的实现都用到了该模式。比如,有些 CS架构中Client端的远程方法调用 就使用了动态代理在invoke()方法中 为被代理对象调用的方法 织入远程调用处理然后将远程处理的结果返回给调用者Spring的AOP也是优先使用JDK动态代理来完成Mybatis为JDBC操作织入日志处理等等。下面我们结合源码来深入理解一下这个模式。
代理模式的实际应用 主要体现在框架开发中,日常业务上的开发工作中很少有场景需要使用该模式。而代理模式中 动态代理尤为重要,不管是自己公司的内部框架 还是 一些知名的开源框架,很多重要的实现都用到了该模式。比如,有些 CS 架构中Client 端的远程方法调用 就使用了动态代理,在 invoke()方法中 为被代理对象调用的方法 织入远程调用处理然后将远程处理的结果返回给调用者Spring 的 AOP 也是优先使用 JDK 动态代理来完成Mybatis 为 JDBC 操作织入日志处理,等等。下面我们结合源码来深入理解一下这个模式。
#### 动态代理原理 #### 动态代理原理
静态代理没什么好讲的很少见用到功能也比较薄弱本篇重点讲解动态代理。首先了解一下JDK动态代理的原理这对理解 Spring AOP 部分的源码及实现原理也很有帮助。
静态代理没什么好讲的,很少见用到,功能也比较薄弱,本篇重点讲解动态代理。首先了解一下 JDK 动态代理的原理,这对理解 Spring AOP 部分的源码及实现原理也很有帮助。
JDK 动态代理的实现原理是,动态创建代理类井通过指定类加载器加载,然后在创建代理对象时将 InvokerHandler 对象作为构造参数传入。当调用代理对象的方法时,会调用 InvokerHandler 的 invoke() 方法,并最终调用真正业务对象的相应方法。 JDK 动态代理不仅在 Spring 及 MyBatis 的多个模块中都有所涉及, 在其它很多开源框架中也能看到其身影。 JDK 动态代理的实现原理是,动态创建代理类井通过指定类加载器加载,然后在创建代理对象时将 InvokerHandler 对象作为构造参数传入。当调用代理对象的方法时,会调用 InvokerHandler 的 invoke() 方法,并最终调用真正业务对象的相应方法。 JDK 动态代理不仅在 Spring 及 MyBatis 的多个模块中都有所涉及, 在其它很多开源框架中也能看到其身影。
```java ```java
/** /**
* 一般会使用实现了 InvocationHandler 的类作为代理对象的生产工厂, * 一般会使用实现了 InvocationHandler 的类作为代理对象的生产工厂,
@ -612,7 +623,9 @@ public final class $Proxy0 extends Proxy implements MyInterface {
``` ```
#### Spring 中的应用 #### Spring 中的应用
Spring 在生成动态代理类时会优先选择使用JDK动态代理除非被代理类没有实现接口。
Spring 在生成动态代理类时,会优先选择使用 JDK 动态代理,除非被代理类没有实现接口。
```java ```java
/** /**
* 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy * 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy
@ -725,8 +738,10 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
} }
``` ```
#### Mybatis中的应用 #### Mybatis 中的应用
Mybatis 的 PooledConnection 类中封装了数据库连接的代理对象,对数据库连接的操作大都会通过该代理对象完成。 Mybatis 的 PooledConnection 类中封装了数据库连接的代理对象,对数据库连接的操作大都会通过该代理对象完成。
```java ```java
/** /**
* Mybatis 封装的数据库连接类,它实现了 InvocationHandler 接口,封装了真正的 * Mybatis 封装的数据库连接类,它实现了 InvocationHandler 接口,封装了真正的
@ -805,7 +820,9 @@ class PooledConnection implements InvocationHandler {
``` ```
## 装饰器模式 ## 装饰器模式
#### 个人理解 #### 个人理解
在实际生产中,新需求在软件的整个生命过程中总是不断出现的。当有新需求出现时,就需要为某些组件添加新的功能来满足这些需求。 添加新功能的方式有很多,我们可以直接修改已有组件的代码井添加相应的新功能,但这样会破坏己有组件的稳定性,修改完成后,整个组件需要重新进行测试才能上线使用。 这种方式显然违反了 “开放封闭” 原则。 在实际生产中,新需求在软件的整个生命过程中总是不断出现的。当有新需求出现时,就需要为某些组件添加新的功能来满足这些需求。 添加新功能的方式有很多,我们可以直接修改已有组件的代码井添加相应的新功能,但这样会破坏己有组件的稳定性,修改完成后,整个组件需要重新进行测试才能上线使用。 这种方式显然违反了 “开放封闭” 原则。
另一种方式是使用继承,我们可以创建子类并在子类中添加新功能实现扩展。 这种方法是静态的,用户不能控制增加行为的方式和时机。 而且有些情况下继承是不可行的,例如 己有组件是被 final 修饰的类。 另外,如果待添加的新功能存在多种组合,使用继承方式可能会导致大量子类的出现。 例如,有 4 个待添加的新功能,系统需要动态使用任意多个功能的组合, 则需要添加 15 个子类才能满足全部需求。 另一种方式是使用继承,我们可以创建子类并在子类中添加新功能实现扩展。 这种方法是静态的,用户不能控制增加行为的方式和时机。 而且有些情况下继承是不可行的,例如 己有组件是被 final 修饰的类。 另外,如果待添加的新功能存在多种组合,使用继承方式可能会导致大量子类的出现。 例如,有 4 个待添加的新功能,系统需要动态使用任意多个功能的组合, 则需要添加 15 个子类才能满足全部需求。
@ -819,10 +836,12 @@ class PooledConnection implements InvocationHandler {
- Decorator (装饰器):所有装饰器的父类,它是一个实现了 Component 接口的抽象类,并持有一个 Component 被装饰对象,这就实现了装饰器的嵌套组合和复用。 - Decorator (装饰器):所有装饰器的父类,它是一个实现了 Component 接口的抽象类,并持有一个 Component 被装饰对象,这就实现了装饰器的嵌套组合和复用。
- ConcreteDecorator (具体的装饰器实现类):该实现类要向被装饰对象添加某些功能,被装饰的对象只要是 Component 类型即可。 - ConcreteDecorator (具体的装饰器实现类):该实现类要向被装饰对象添加某些功能,被装饰的对象只要是 Component 类型即可。
#### Mybatis中的应用 #### Mybatis 中的应用
在 MyBatis 的缓存模块中,使用了装饰器模式的变体,其中将 Decorator 接口和 Component 接口合并为一个 Component 接口,即,去掉了 Decorator 这个中间层ConcreteDecorator 直接实现了Component 接口。
在 MyBatis 的缓存模块中,使用了装饰器模式的变体,其中将 Decorator 接口和 Component 接口合并为一个 Component 接口,即,去掉了 Decorator 这个中间层ConcreteDecorator 直接实现了 Component 接口。
MyBatis 中缓存模块相关的代码位于 cache 包下, 其中 Cache 接口是缓存模块的核心接口,它定义了所有缓存的基本行为,扮演了 Component 的角色。实现类 PerpetualCache 扮演了 ConcreteComponent 的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象的方法实现了 Cache 接口中定义的相应方法。而 cache 包下的 decorators 包中,则定义了一系列 ConcreteDecorator 的实现,如 BlockingCache、FifoCache 及 LruCache 等等,它们都持有一个 Cache 类型的对象,通过嵌套组合的方式为该 Cache 对象 装饰相应的功能。其源码实现如下。
MyBatis 中缓存模块相关的代码位于 cache 包下, 其中 Cache 接口是缓存模块的核心接口,它定义了所有缓存的基本行为,扮演了 Component 的角色。实现类 PerpetualCache 扮演了 ConcreteComponent 的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象的方法实现了 Cache 接口中定义的相应方法。而 cache 包下的 decorators 包中,则定义了一系列 ConcreteDecorator 的实现,如 BlockingCache、FifoCache 及 LruCache 等等,它们都持有一个 Cache 类型的对象,通过嵌套组合的方式为该 Cache对象 装饰相应的功能。其源码实现如下。
```java ```java
public interface Cache { public interface Cache {

@ -1,23 +1,26 @@
设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring系列、Mybatis及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 设计模式是解决问题的方案从大神的代码中学习对设计模式的使用可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码Spring 系列、Mybatis JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。
本篇博文主要看一下行为型的几个设计模式,即,策略模式、模板方法模式、迭代器模式、观察者模式 及 责任链模式。 本篇博文主要看一下行为型的几个设计模式,即,策略模式、模板方法模式、迭代器模式、观察者模式 及 责任链模式。
## 策略模式 ## 策略模式
#### 个人理解 #### 个人理解
去年看了蛮多源码,发现 框架的开发者在实际使用设计模式时,大都会根据实际情况 使用其变体,老老实实按照书上的类图及定义去设计代码的比较少。不过我们依然还是先看一下书上的定义,然后比较一下理论与实践的一些差别吧。策略模式的类图及定义如下。 去年看了蛮多源码,发现 框架的开发者在实际使用设计模式时,大都会根据实际情况 使用其变体,老老实实按照书上的类图及定义去设计代码的比较少。不过我们依然还是先看一下书上的定义,然后比较一下理论与实践的一些差别吧。策略模式的类图及定义如下。
![avatar](../../../images/DesignPattern/策略模式类图.png) ![avatar](../../../images/DesignPattern/策略模式类图.png)
定义一系列算法,封装每个算法 并使它们可以互换。该模式的主要角色如下: 定义一系列算法,封装每个算法 并使它们可以互换。该模式的主要角色如下:
- Strategy接口用于定义一个算法族它们都具有behavior()方法; - Strategy 接口:用于定义一个算法族,它们都具有 behavior()方法;
- Context使用该算法的类持有Strategy对象其中的setStrategy(Strategy stra)方法可以动态地改变strategy对象以此改变自己所使用的算法。 - Context使用该算法的类持有 Strategy 对象,其中的 setStrategy(Strategy stra)方法可以动态地改变 strategy 对象,以此改变自己所使用的算法。
很多书上都使用 Duck 和 QuackBehavior 作为示例进行说明,这里就不重复咯,主要看一下 Spring 中是如何使用该模式的。
很多书上都使用Duck和QuackBehavior作为示例进行说明这里就不重复咯主要看一下Spring中是如何使用该模式的。 #### Spring 中的实现
Spring 的 AbstractAutowireCapableBeanFactory 在进行 bean 实例化时使用了策略模式的变种,其中 InstantiationStrategy 接口 定义了实例化方法,实现类 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy 分别实现了各自的算法AbstractAutowireCapableBeanFactory 则通过持有 InstantiationStrategy 对象,对算进行使用。其源码实现如下。
#### Spring中的实现
Spring的 AbstractAutowireCapableBeanFactory 在进行bean实例化时使用了策略模式的变种其中InstantiationStrategy 接口 定义了实例化方法实现类SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy 分别实现了各自的算法AbstractAutowireCapableBeanFactory 则通过持有 InstantiationStrategy 对象,对算进行使用。其源码实现如下。
```java ```java
/** /**
* 本接口用于定义bean实例的创建通过给定的RootBeanDefinition对象 * 本接口用于定义bean实例的创建通过给定的RootBeanDefinition对象
@ -208,14 +211,17 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
} }
``` ```
与标准的策略模式的设计区别在于实现类CglibSubclassingInstantiationStrategy并不是直接实现了InstantiationStrategy接口而是继承了SimpleInstantiationStrategySimpleInstantiationStrategy直接实现了 通过JDK反射机制实例化bean的策略而CglibSubclassingInstantiationStrategy则是在自己的私有静态内部类中 完成的 通过CGLIB实例化bean的策略。
另外虽然AbstractAutowireCapableBeanFactory默认持有的是CglibSubclassingInstantiationStrategy的实例但具体使用哪个实现类中的策略则是由CglibSubclassingInstantiationStrategy的父类SimpleInstantiationStrategy中的instantiate()方法决定的。也就是说虽然持有的是CglibSubclassingInstantiationStrategy对象但实际上可能使用的是 JDK反射机制实例化bean的策略。 与标准的策略模式的设计区别在于,实现类 CglibSubclassingInstantiationStrategy 并不是直接实现了 InstantiationStrategy 接口,而是继承了 SimpleInstantiationStrategySimpleInstantiationStrategy 直接实现了 通过 JDK 反射机制实例化 bean 的策略,而 CglibSubclassingInstantiationStrategy 则是在自己的私有静态内部类中 完成的 通过 CGLIB 实例化 bean 的策略。
另外,虽然 AbstractAutowireCapableBeanFactory 默认持有的是 CglibSubclassingInstantiationStrategy 的实例,但具体使用哪个实现类中的策略,则是由 CglibSubclassingInstantiationStrategy 的父类 SimpleInstantiationStrategy 中的 instantiate()方法决定的。也就是说,虽然持有的是 CglibSubclassingInstantiationStrategy 对象,但实际上可能使用的是 JDK 反射机制实例化 bean 的策略。
设计模式的生产实践可能比 理论上的那些示例复杂的多,所以,若想确实提高自己代码的设计能力,还是要摆脱书本,多看实际应用。 设计模式的生产实践可能比 理论上的那些示例复杂的多,所以,若想确实提高自己代码的设计能力,还是要摆脱书本,多看实际应用。
#### Mybatis 中的实现 #### Mybatis 中的实现
mybatis 的 DefaultSqlSession 使用了策略模式DefaultSqlSession 扮演了 Context 的角色Executor 接口及其实现类扮演了策略接口及实现。DefaultSqlSession 持有 Executor 对象在DefaultSqlSession 实例化时通过构造方法传入具体的 Executor 对象,根据持有的 Executor 对象的不同,而使用不同的策略进行数据库操作。具体使用哪个 Executor 的实例,由 Configuration 的 newExecutor() 方法决定。
mybatis 的 DefaultSqlSession 使用了策略模式DefaultSqlSession 扮演了 Context 的角色Executor 接口及其实现类扮演了策略接口及实现。DefaultSqlSession 持有 Executor 对象,在 DefaultSqlSession 实例化时通过构造方法传入具体的 Executor 对象,根据持有的 Executor 对象的不同,而使用不同的策略进行数据库操作。具体使用哪个 Executor 的实例,由 Configuration 的 newExecutor() 方法决定。
```java ```java
public class DefaultSqlSession implements SqlSession { public class DefaultSqlSession implements SqlSession {
@ -413,13 +419,17 @@ public class Configuration {
``` ```
## 模板方法模式 ## 模板方法模式
#### 个人理解 #### 个人理解
在该模式中,一个算法可以分为多个步骤,这些步骤的执行次序在一个被称为“模板方法”的方法中定义,而算法的每个步骤都对应着一个方法,这些方法被称为 “基本方法”。 模板方法按照它定义的顺序依次调用多个基本方法,从而实现整个算法流程。在模板方法模式中,会将模板方法的实现以及那些固定不变的基本方法的实现放在父类中,而那些不固定的基 本方法在父类中只是抽象方法,其真正的实现代码会被延迟到子类中完成。 在该模式中,一个算法可以分为多个步骤,这些步骤的执行次序在一个被称为“模板方法”的方法中定义,而算法的每个步骤都对应着一个方法,这些方法被称为 “基本方法”。 模板方法按照它定义的顺序依次调用多个基本方法,从而实现整个算法流程。在模板方法模式中,会将模板方法的实现以及那些固定不变的基本方法的实现放在父类中,而那些不固定的基 本方法在父类中只是抽象方法,其真正的实现代码会被延迟到子类中完成。
我觉得这是最简单且常用的设计模式之一咯自己在实现一些功能时也会使用这种模式在抽象类中定义好流程的执行顺序通用的流程在抽象类中实现个性化的流程交给各个子类去实现。spring及mybatis中均有应用。 我觉得这是最简单且常用的设计模式之一咯自己在实现一些功能时也会使用这种模式在抽象类中定义好流程的执行顺序通用的流程在抽象类中实现个性化的流程交给各个子类去实现。spring 及 mybatis 中均有应用。
#### Spring 中的应用
#### Spring中的应用
Spring 中的 AbstractApplicationContext 和其子类 AbstractRefreshableApplicationContext、GenericApplicationContext 使用了模板方法模式。源码实现及详细注释如下。 Spring 中的 AbstractApplicationContext 和其子类 AbstractRefreshableApplicationContext、GenericApplicationContext 使用了模板方法模式。源码实现及详细注释如下。
```java ```java
public abstract class AbstractApplicationContext extends DefaultResourceLoader public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean { implements ConfigurableApplicationContext, DisposableBean {
@ -508,8 +518,10 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
} }
``` ```
#### Mybatis中的应用 #### Mybatis 中的应用
mybatis的 Executor 组件使用了该模式,其中抽象类 BaseExecutor 定义了模板方法和抽象方法,实现类 SimpleExecutor、BatchExecutor 及 ReuseExecutor 对抽象方法进行具体实现。源码如下。
mybatis 的 Executor 组件使用了该模式,其中抽象类 BaseExecutor 定义了模板方法和抽象方法,实现类 SimpleExecutor、BatchExecutor 及 ReuseExecutor 对抽象方法进行具体实现。源码如下。
```java ```java
public abstract class BaseExecutor implements Executor { public abstract class BaseExecutor implements Executor {
@ -818,16 +830,20 @@ public class ReuseExecutor extends BaseExecutor {
} }
} }
``` ```
可以看得出来模板方法就是BaseExecutor的update()、flushStatements()、queryFromDatabase() 及 queryCursor()分别使用了抽象方法doUpdate()、doFlushStatements()、doQuery() 及 doQueryCursor()。
可以看得出来,模板方法就是 BaseExecutor 的 update()、flushStatements()、queryFromDatabase() 及 queryCursor(),分别使用了抽象方法 doUpdate()、doFlushStatements()、doQuery() 及 doQueryCursor()。
## 迭代器模式 ## 迭代器模式
#### 个人理解 #### 个人理解
这个模式最经典的实现莫过于 Java的集合类咯。同样还是先简单介绍一下这个设计模式然后结合ArrayList的源码进行分析。
这个模式最经典的实现莫过于 Java 的集合类咯。同样还是先简单介绍一下这个设计模式,然后结合 ArrayList 的源码进行分析。
本设计模式用于提供一种遍历集合元素的方法,且不暴露集合对象的内部表示。其主要角色 和 简单实现如下: 本设计模式用于提供一种遍历集合元素的方法,且不暴露集合对象的内部表示。其主要角色 和 简单实现如下:
- Aggregate聚合类有一个可以获取 Iterator 对象的 iterator() 方法; - Aggregate聚合类有一个可以获取 Iterator 对象的 iterator() 方法;
- Iterator主要定义了hasNest() 和 next()方法; - Iterator主要定义了 hasNest() 和 next()方法;
```java ```java
public interface Aggregate { public interface Aggregate {
Iterator iterator(); Iterator iterator();
@ -894,6 +910,7 @@ public class Client {
``` ```
#### ArrayList 对迭代器模式的实现 #### ArrayList 对迭代器模式的实现
```java ```java
public class ArrayList<E> extends AbstractList<E> public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
@ -945,16 +962,20 @@ public class ArrayList<E> extends AbstractList<E>
``` ```
## 观察者模式 ## 观察者模式
#### 个人理解 #### 个人理解
这个模式也是平时很少使用的所以就简单介绍一下然后结合JDK中的源码加深理解。该模式用于定义对象之间的一对多依赖当一个对象状态改变时它的所有依赖都会收到通知然后自动更新。类图和主要角色如下
这个模式也是平时很少使用的,所以就简单介绍一下,然后结合 JDK 中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下:
![avatar](../../../images/DesignPattern/观察者模式类图.png) ![avatar](../../../images/DesignPattern/观察者模式类图.png)
- Subject主题具有注册、移除及通知观察者的功能主题是通过维护一个观察者列表来实现这些功能的 - Subject 主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的;
- Observer观察者其注册需要Subject的registerObserver()方法。 - Observer 观察者:其注册需要 Subject 的 registerObserver()方法。
#### JDK 中的源码实现
java.util 包中提供了 Observable 类和 Observer 接口,其中要求,被观察者需要继承 Observable 类,观察则需要实现 Observer 接口。下面看一下其源码实现。
#### JDK中的源码实现
java.util包中提供了 Observable 类和 Observer 接口其中要求被观察者需要继承Observable类观察则需要实现Observer接口。下面看一下其源码实现。
```java ```java
/** /**
* 当一个类希望获知 所观察的对象的变化时,可以通过实现本接口来完成 * 当一个类希望获知 所观察的对象的变化时,可以通过实现本接口来完成
@ -1076,24 +1097,27 @@ public class Observable {
``` ```
## 责任链模式 ## 责任链模式
一般用在消息请求的处理上,如 Netty 的 ChannelHandler组件Tomcat 对 HTTP 请求的处理。我们当然可以将 请求的处理逻辑都写在一个类中,但这个类会非常雕肿且不易于维护,不符合开发封闭原则。
在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 Handler 处理类中,这样我们就可以根据业务需求,将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler对象 都包含对下一个 Handler对象 的引用,一个 Handler对象 处理完请求消息(或不能处理该请求)时, 会把请求传给下一个 Handler对象 继续处理,依此类推,直至整条责任链结束。简单看一下责任链模式的类图。 一般用在消息请求的处理上,如 Netty 的 ChannelHandler 组件Tomcat 对 HTTP 请求的处理。我们当然可以将 请求的处理逻辑都写在一个类中,但这个类会非常雕肿且不易于维护,不符合开发封闭原则。
在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 Handler 处理类中,这样我们就可以根据业务需求,将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler 对象 都包含对下一个 Handler 对象 的引用,一个 Handler 对象 处理完请求消息(或不能处理该请求)时, 会把请求传给下一个 Handler 对象 继续处理,依此类推,直至整条责任链结束。简单看一下责任链模式的类图。
![avatar](../../../images/DesignPattern/责任链模式.png) ![avatar](../../../images/DesignPattern/责任链模式.png)
#### Netty 中的应用 #### Netty 中的应用
在 Netty 中,将 Channel 的数据管道抽象为 ChannelPipeline消息在 ChannelPipeline 中流动和传递。ChannelPipeline 是 ChannelHandler 的容器,持有 I/O事件拦截器 ChannelHandler 的链表,负责对 ChannelHandler 的管理和调度。由 ChannelHandler 对 I/O事件 进行拦截和处理,并可以通过接口方便地新增和删除 ChannelHandler 来实现不同业务逻辑的处理。下图是 ChannelPipeline源码中描绘的责任链事件处理过程。
在 Netty 中,将 Channel 的数据管道抽象为 ChannelPipeline消息在 ChannelPipeline 中流动和传递。ChannelPipeline 是 ChannelHandler 的容器,持有 I/O 事件拦截器 ChannelHandler 的链表,负责对 ChannelHandler 的管理和调度。由 ChannelHandler 对 I/O 事件 进行拦截和处理,并可以通过接口方便地新增和删除 ChannelHandler 来实现不同业务逻辑的处理。下图是 ChannelPipeline 源码中描绘的责任链事件处理过程。
![avatar](../../../images/Netty/ChannelPipeline责任链事件处理过程.png) ![avatar](../../../images/Netty/ChannelPipeline责任链事件处理过程.png)
其具体过程处理如下: 其具体过程处理如下:
1. 底层SocketChannel 的 read方法 读取 ByteBuf触发 ChannelRead事件由 I/O线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead()方法,将消息传输到 ChannelPipeline中。 1. 底层 SocketChannel 的 read 方法 读取 ByteBuf触发 ChannelRead 事件,由 I/O 线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead()方法,将消息传输到 ChannelPipeline 中。
2. 消息依次被 InboundHandler 1、InboundHandler 2 … InboundHandler N 拦截处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。 2. 消息依次被 InboundHandler 1、InboundHandler 2 … InboundHandler N 拦截处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。
3. 当调用 ChannelHandlerContext 的 write()方法 发送消息,消息从 OutbountHandler 1 开始 一直到 OutboundHandler N最终被添加到消息发送缓冲区中等待刷新和发送。 3. 当调用 ChannelHandlerContext 的 write()方法 发送消息,消息从 OutbountHandler 1 开始 一直到 OutboundHandler N最终被添加到消息发送缓冲区中等待刷新和发送。
在 Netty 中将事件根据源头的不同分为 InBound事件 和 OutBound事件。InBound事件 通常由 I/O线程 触发,例如 TCP链路 建立和关闭、读事件等等,分别会触发相应的事件方法。而 OutBound事件 则一般由用户主动发起的 网络I/O操作例如用户发起的连接操作绑定操作和消息发送操作等也会分别触发相应的事件方法。由于 netty 中提供了一个抽象类 ChannelHandlerAdapter它默认不处理拦截的事件。所以在实际编程过程中我们只需要继承 ChannelHandlerAdapter在我们的 自定义Handler 中覆盖业务关心的事件方法即可。其源码如下。 在 Netty 中将事件根据源头的不同分为 InBound 事件 和 OutBound 事件。InBound 事件 通常由 I/O 线程 触发,例如 TCP 链路 建立和关闭、读事件等等,分别会触发相应的事件方法。而 OutBound 事件 则一般由用户主动发起的 网络 I/O 操作,例如用户发起的连接操作,绑定操作和消息发送操作等,也会分别触发相应的事件方法。由于 netty 中提供了一个抽象类 ChannelHandlerAdapter它默认不处理拦截的事件。所以在实际编程过程中我们只需要继承 ChannelHandlerAdapter在我们的 自定义 Handler 中覆盖业务关心的事件方法即可。其源码如下。
```java ```java
/** /**
* 它扮演了 责任链模式中的 Client角色持有 构造 并使用 ChannelHandler责任链 * 它扮演了 责任链模式中的 Client角色持有 构造 并使用 ChannelHandler责任链

@ -9,6 +9,7 @@
下面我将分三个部分,谈一谈自己的经验。 下面我将分三个部分,谈一谈自己的经验。
### 一、工作方面(编码规范、编码能力、设计模式、英文阅读) ### 一、工作方面(编码规范、编码能力、设计模式、英文阅读)
我所从事的行业做的是 toB 的业务,产品底层平台的框架,代码累累,堆积成山,很多框架都是零几年写的,有的甚至比 Spring 还早。且最近国产化、中台、云服务等概念都在不断落地中,有框架源码的阅读经验,让我能够更从容地面对公司研发的新框架,所维护的产品适配华为高斯数据库时,也更清楚可能是 JDBC 框架中哪里做了特殊处理所导致的问题。当然,最主要的还是对个人编码规范的养成,设计模式的理解应用,英文阅读的能力提升。 我所从事的行业做的是 toB 的业务,产品底层平台的框架,代码累累,堆积成山,很多框架都是零几年写的,有的甚至比 Spring 还早。且最近国产化、中台、云服务等概念都在不断落地中,有框架源码的阅读经验,让我能够更从容地面对公司研发的新框架,所维护的产品适配华为高斯数据库时,也更清楚可能是 JDBC 框架中哪里做了特殊处理所导致的问题。当然,最主要的还是对个人编码规范的养成,设计模式的理解应用,英文阅读的能力提升。
作为一个初入职场的开发者编码规范是一个很重要的点能够让你写出的代码易于维护、阅读和理解。比如Spring 框架虽然类图体系复杂丰富,但对于类、方法、参数等的命名非常规范;注释注解也非常严谨,注重格式,不会偷懒;对于异常和日志的处理也具有很好的参考价值。比如,之前产品中有遇到一个“将业务表单中的小数从科学计数法转换成普通计数法”(数值过大的 Double 类型数字默认会以科学记数法显示,这是用户无法接受的),研读了复杂的业务代码之后,发现填充到表单前的数据都是 Object 类型的,且丢失了原本类型,无法通过 instanceof 判断应该转成 String 还是 Double这让我和我的师傅都有点头疼但 Spring 源码中有过一段以异常捕获机制处理逻辑代码的片段让我灵光乍现,于是我直接将 Object 强转成 Double 并使其不做科学记数法的处理,并将这段代码 try 住,如果没抛异常,就转换成了 Double抛了异常就在 catch 中强转成 String。 作为一个初入职场的开发者编码规范是一个很重要的点能够让你写出的代码易于维护、阅读和理解。比如Spring 框架虽然类图体系复杂丰富,但对于类、方法、参数等的命名非常规范;注释注解也非常严谨,注重格式,不会偷懒;对于异常和日志的处理也具有很好的参考价值。比如,之前产品中有遇到一个“将业务表单中的小数从科学计数法转换成普通计数法”(数值过大的 Double 类型数字默认会以科学记数法显示,这是用户无法接受的),研读了复杂的业务代码之后,发现填充到表单前的数据都是 Object 类型的,且丢失了原本类型,无法通过 instanceof 判断应该转成 String 还是 Double这让我和我的师傅都有点头疼但 Spring 源码中有过一段以异常捕获机制处理逻辑代码的片段让我灵光乍现,于是我直接将 Object 强转成 Double 并使其不做科学记数法的处理,并将这段代码 try 住,如果没抛异常,就转换成了 Double抛了异常就在 catch 中强转成 String。
@ -20,12 +21,15 @@
Spring 上很多接口和抽象类,其注解甚至比代码还多,我也经常尝试着去阅读理解这些注释,看看自己的理解与书上的差异,用这种方式来提升英文技术文档的阅读能力,往往更实在一些。 Spring 上很多接口和抽象类,其注解甚至比代码还多,我也经常尝试着去阅读理解这些注释,看看自己的理解与书上的差异,用这种方式来提升英文技术文档的阅读能力,往往更实在一些。
### 二、学习方面(学习模式的构建、学以致用) ### 二、学习方面(学习模式的构建、学以致用)
虽然是做技术的,但我也是一个很爱出去耍的人。构建好自己的学习模式能够让你更从容地面对工作和生活。不加班的情况下(所幸部门加班并不太多),我一般会在晚饭之后以及周日时间充电。不管是学技术还是其它什么东西,我认为 以视频为入口以业界公认的名书继续深入理解以社交圈的同行或网上社区为输出交流管道最后持久化到思维导图及学习文档中。Spring 源码学习是我工作之后对自己学习模式构建的一个尝试,构建起这种学习模式之后,个人的工作和生活也变得更加协调平衡,不至于在繁杂忙碌的工作中渐渐丧失学习能力。另外一个比较重要的就是,看 Spring 源码时经常能看到一些与公司框架有异曲同工之妙的编码技巧及实现比如异常的批量抛出ConcurrentHashMap 初始化其容量ThreadLocal 的使用等等,这些都是在读 Spring 源码之前很少会注意或使用的。 虽然是做技术的,但我也是一个很爱出去耍的人。构建好自己的学习模式能够让你更从容地面对工作和生活。不加班的情况下(所幸部门加班并不太多),我一般会在晚饭之后以及周日时间充电。不管是学技术还是其它什么东西,我认为 以视频为入口以业界公认的名书继续深入理解以社交圈的同行或网上社区为输出交流管道最后持久化到思维导图及学习文档中。Spring 源码学习是我工作之后对自己学习模式构建的一个尝试,构建起这种学习模式之后,个人的工作和生活也变得更加协调平衡,不至于在繁杂忙碌的工作中渐渐丧失学习能力。另外一个比较重要的就是,看 Spring 源码时经常能看到一些与公司框架有异曲同工之妙的编码技巧及实现比如异常的批量抛出ConcurrentHashMap 初始化其容量ThreadLocal 的使用等等,这些都是在读 Spring 源码之前很少会注意或使用的。
### 三、社交方面GitHub、事业部内部授课 ### 三、社交方面GitHub、事业部内部授课
对于我来说,既然辛辛苦苦搞懂了一个技术,那就一定得输出自己的理解和经验,装波逼,不然辛辛苦苦几个月,什么产出都没有,过一段时间又把学得给忘了,这和被白嫖有什么区别。而输出知识的话当然要选一些比较优质的平台,比如 GayHubDoocs 组织和其创建者就是我在 GitHub 上认识的,这些大佬之所以牛逼,能够成事,必然有其原因,加入他们的组织跟着混,准能学到更多我想要的东西(不仅仅是技术方面)。 对于我来说,既然辛辛苦苦搞懂了一个技术,那就一定得输出自己的理解和经验,装波逼,不然辛辛苦苦几个月,什么产出都没有,过一段时间又把学得给忘了,这和被白嫖有什么区别。而输出知识的话当然要选一些比较优质的平台,比如 GayHubDoocs 组织和其创建者就是我在 GitHub 上认识的,这些大佬之所以牛逼,能够成事,必然有其原因,加入他们的组织跟着混,准能学到更多我想要的东西(不仅仅是技术方面)。
另外,我所在的事业部也有一个“王者荣耀”的学习进阶活动,将自己的学习成果整理成简单、易于理解的内部授课也更容易获得同事的认可与信赖。 另外,我所在的事业部也有一个“王者荣耀”的学习进阶活动,将自己的学习成果整理成简单、易于理解的内部授课也更容易获得同事的认可与信赖。
### 个人建议 ### 个人建议
对于初级开发者学习 Spring 源码来说我建议配合阿里的《Java 开发手册》一起看因为编码能力和框架设计能力是需要很长时间的经验积累才能得到大幅提升的而编码规范则是我们最开始就能做到并做好的事情也是很多成熟公司越来越重视的东西。另外阿里的《Java 开发手册》中不少规范都是参考了 Spring 框架的,这也从侧面体现了 Spring 作为业界知名框架,其编码的规范性是深受认可的。 对于初级开发者学习 Spring 源码来说我建议配合阿里的《Java 开发手册》一起看因为编码能力和框架设计能力是需要很长时间的经验积累才能得到大幅提升的而编码规范则是我们最开始就能做到并做好的事情也是很多成熟公司越来越重视的东西。另外阿里的《Java 开发手册》中不少规范都是参考了 Spring 框架的,这也从侧面体现了 Spring 作为业界知名框架,其编码的规范性是深受认可的。

@ -1,7 +1,11 @@
在 Mybatis 的基础支持层主要看一下支撑 ORM实现 的底层代码。 在 Mybatis 的基础支持层主要看一下支撑 ORM 实现 的底层代码。
## 1 反射工具包 ## 1 反射工具包
### 1.1Reflector ### 1.1Reflector
Reflector类 主要实现了对 JavaBean 的元数据属性的封装,比如:可读属性列表,可写属性列表;及反射操作的封装,如:属性对应的 setter方法getter方法 的反射调用。源码实现如下:
Reflector 类 主要实现了对 JavaBean 的元数据属性的封装,比如:可读属性列表,可写属性列表;及反射操作的封装,如:属性对应的 setter 方法getter 方法 的反射调用。源码实现如下:
```java ```java
public class Reflector { public class Reflector {
@ -55,8 +59,11 @@ public class Reflector {
} }
} }
``` ```
### 1.2 ReflectorFactory ### 1.2 ReflectorFactory
顾名思义Reflector 的工厂模式,跟大部分工厂类一样,里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。 顾名思义Reflector 的工厂模式,跟大部分工厂类一样,里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。
```java ```java
public interface ReflectorFactory { public interface ReflectorFactory {
@ -110,8 +117,11 @@ public class CustomReflectorFactory extends DefaultReflectorFactory {
} }
``` ```
### 1.3 ObjectFactory ### 1.3 ObjectFactory
该类也是接口加一个默认实现类并且支持自定义扩展Mybatis 中有很多这样的设计方式。 该类也是接口加一个默认实现类并且支持自定义扩展Mybatis 中有很多这样的设计方式。
```java ```java
/** /**
* MyBatis uses an ObjectFactory to create all needed new Objects. * MyBatis uses an ObjectFactory to create all needed new Objects.
@ -205,10 +215,15 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable {
} }
} }
``` ```
## 2 类型转换 ## 2 类型转换
类型转换是实现 ORM 的重要一环,由于数据库中的数据类型与 Java语言 的数据类型并不对等,所以在 PrepareStatement 为 sql语句 绑定参数时,需要从 Java类型 转换成 JDBC类型而从结果集获取数据时又要将 JDBC类型 转换成 Java类型Mybatis 使用 TypeHandler 完成了上述的双向转换。
类型转换是实现 ORM 的重要一环,由于数据库中的数据类型与 Java 语言 的数据类型并不对等,所以在 PrepareStatement 为 sql 语句 绑定参数时,需要从 Java 类型 转换成 JDBC 类型,而从结果集获取数据时,又要将 JDBC 类型 转换成 Java 类型Mybatis 使用 TypeHandler 完成了上述的双向转换。
### 2.1 JdbcType ### 2.1 JdbcType
Mybatis 通过 JdbcType 这个枚举类型代表了 JDBC 中的数据类型。 Mybatis 通过 JdbcType 这个枚举类型代表了 JDBC 中的数据类型。
```java ```java
/** /**
* 该枚举类描述了 JDBC 中的数据类型 * 该枚举类描述了 JDBC 中的数据类型
@ -281,8 +296,11 @@ public enum JdbcType {
} }
``` ```
### 2.2 TypeHandler ### 2.2 TypeHandler
TypeHandler 是 Mybatis 中所有类型转换器的顶层接口,主要用于实现数据从 Java类型 到 JdbcType类型 的相互转换。
TypeHandler 是 Mybatis 中所有类型转换器的顶层接口,主要用于实现数据从 Java 类型 到 JdbcType 类型 的相互转换。
```java ```java
public interface TypeHandler<T> { public interface TypeHandler<T> {
@ -377,9 +395,13 @@ public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
} }
} }
``` ```
TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java对象可以在映射文件中定义合适的映射规则 &lt;resultMap&gt; 完成映射。
TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java 对象,可以在映射文件中定义合适的映射规则 &lt;resultMap&gt; 完成映射。
### 2.3 TypeHandlerRegistry ### 2.3 TypeHandlerRegistry
TypeHandlerRegistry 主要负责管理所有已知的 TypeHandlerMybatis 在初始化过程中会为所有已知的 TypeHandler 创建对象,并注册到 TypeHandlerRegistry。 TypeHandlerRegistry 主要负责管理所有已知的 TypeHandlerMybatis 在初始化过程中会为所有已知的 TypeHandler 创建对象,并注册到 TypeHandlerRegistry。
```java ```java
// TypeHandlerRegistry 中的核心字段如下 // TypeHandlerRegistry 中的核心字段如下
@ -395,8 +417,10 @@ TypeHandlerRegistry 主要负责管理所有已知的 TypeHandlerMybatis 在
/** keyTypeHandler 的类型value该 TypeHandler类型 对应的 TypeHandler对象 */ /** keyTypeHandler 的类型value该 TypeHandler类型 对应的 TypeHandler对象 */
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>(); private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
``` ```
**1、注册TypeHandler对象**
TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 的功能,该方法存在多种重载,但大多数 register()方法 最终都会走 register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) 的处理逻辑,该重载方法中分别指定了 TypeHandler 能够处理的 Java类型、JDBC类型、TypeHandler对象。 **1、注册 TypeHandler 对象**
TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler 对象 的功能,该方法存在多种重载,但大多数 register()方法 最终都会走 register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) 的处理逻辑,该重载方法中分别指定了 TypeHandler 能够处理的 Java 类型、JDBC 类型、TypeHandler 对象。
```java ```java
/** /**
* TypeHandlerRegistry 中对 register()方法 实现了多种重载,本 register()方法 * TypeHandlerRegistry 中对 register()方法 实现了多种重载,本 register()方法
@ -414,7 +438,9 @@ TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象
allTypeHandlersMap.put(handler.getClass(), handler); allTypeHandlersMap.put(handler.getClass(), handler);
} }
``` ```
另外TypeHandlerRegistry 还提供了扫描并注册指定包目录下 TypeHandler实现类 的 register()方法 重载。
另外TypeHandlerRegistry 还提供了扫描并注册指定包目录下 TypeHandler 实现类 的 register()方法 重载。
```java ```java
/** /**
* 从指定 包名packageName 中获取自定义的 TypeHandler实现类 * 从指定 包名packageName 中获取自定义的 TypeHandler实现类
@ -432,7 +458,9 @@ TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象
} }
} }
``` ```
最后看一下 TypeHandlerRegistry 的构造方法,其通过多种 register()方法 重载,完成了所有已知的 TypeHandler 的重载。 最后看一下 TypeHandlerRegistry 的构造方法,其通过多种 register()方法 重载,完成了所有已知的 TypeHandler 的重载。
```java ```java
/** /**
* 进行 Java 及 JDBC基本数据类型 的 TypeHandler 注册 * 进行 Java 及 JDBC基本数据类型 的 TypeHandler 注册
@ -515,8 +543,10 @@ TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象
register(JapaneseDate.class, new JapaneseDateTypeHandler()); register(JapaneseDate.class, new JapaneseDateTypeHandler());
} }
``` ```
**2、查找TypeHandler**
TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也就是为了方便获取,其对应的方法为 getTypeHandler(),该方法也存在多种重载,其中最重要的一个重载为 getTypeHandler(Type type, JdbcType jdbcType),它会根据指定的 Java类型 和 JdbcType类型 查找相应的 TypeHandler对象。 **2、查找 TypeHandler**
TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也就是为了方便获取,其对应的方法为 getTypeHandler(),该方法也存在多种重载,其中最重要的一个重载为 getTypeHandler(Type type, JdbcType jdbcType),它会根据指定的 Java 类型 和 JdbcType 类型 查找相应的 TypeHandler 对象。
```java ```java
/** /**
* 获取 TypeHandler对象 * 获取 TypeHandler对象
@ -545,4 +575,5 @@ TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也
return (TypeHandler<T>) handler; return (TypeHandler<T>) handler;
} }
``` ```
除了 Mabatis 本身自带的 TypeHandler实现我们还可以添加自定义的 TypeHandler实现类在配置文件 mybatis-config.xml 中的 &lt;typeHandler&gt; 标签下配置好 自定义TypeHandlerMybatis 就会在初始化时解析该标签内容,完成 自定义TypeHandler 的注册。
除了 Mabatis 本身自带的 TypeHandler 实现,我们还可以添加自定义的 TypeHandler 实现类,在配置文件 mybatis-config.xml 中的 &lt;typeHandler&gt; 标签下配置好 自定义 TypeHandlerMybatis 就会在初始化时解析该标签内容,完成 自定义 TypeHandler 的注册。

@ -1,8 +1,13 @@
在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件c3p0、Druid另外Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。 在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件c3p0、Druid另外Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。
## 1 DataSource ## 1 DataSource
常见的数据源都会实现 javax.sql.DataSource接口Mybatis 中提供了两个该接口的实现类分别是PooledDataSource 和 UnpooledDataSource并使用不同的工厂类分别管理这两个类的对象。
常见的数据源都会实现 javax.sql.DataSource 接口Mybatis 中提供了两个该接口的实现类分别是PooledDataSource 和 UnpooledDataSource并使用不同的工厂类分别管理这两个类的对象。
### 1.1 DataSourceFactory ### 1.1 DataSourceFactory
DataSourceFactory系列类 的设计比较简单DataSourceFactory 作为顶级接口UnpooledDataSourceFactory 实现了该接口PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。
DataSourceFactory 系列类 的设计比较简单DataSourceFactory 作为顶级接口UnpooledDataSourceFactory 实现了该接口PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。
```java ```java
public interface DataSourceFactory { public interface DataSourceFactory {
@ -70,7 +75,9 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
``` ```
### 1.2 UnpooledDataSource ### 1.2 UnpooledDataSource
本实现类实现了 DataSource接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
本实现类实现了 DataSource 接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下:
```java ```java
public class UnpooledDataSource implements DataSource { public class UnpooledDataSource implements DataSource {
@ -163,17 +170,23 @@ public class UnpooledDataSource implements DataSource {
} }
} }
``` ```
### 1.3 PooledDataSource ### 1.3 PooledDataSource
数据库建立连接是非常耗时的,且并发的连接数也非常有限。而数据库连接池可以实现数据库的重用、提高响应速度、防止数据库因连接过多而假死等。 数据库建立连接是非常耗时的,且并发的连接数也非常有限。而数据库连接池可以实现数据库的重用、提高响应速度、防止数据库因连接过多而假死等。
**数据库连接池的设计思路一般为:** **数据库连接池的设计思路一般为:**
1. **连接池初始化时创建一定数量的连接,并添加到连接池中备用;** 1. **连接池初始化时创建一定数量的连接,并添加到连接池中备用;**
2. **当程序需要使用数据库连接时,从连接池中请求,用完后会将其返还给连接池,而不是直接关闭;** 2. **当程序需要使用数据库连接时,从连接池中请求,用完后会将其返还给连接池,而不是直接关闭;**
3. **连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会短暂阻塞后重新尝试获取连接,如此循环,直到有连接可用;** 3. **连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会短暂阻塞后重新尝试获取连接,如此循环,直到有连接可用;**
4. **如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。** 4. **如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。**
PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。 PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。
#### 1.3.1 PooledConnection #### 1.3.1 PooledConnection
PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection数据库连接对象 及其代理对象JDK动态代理生成的。PooledConnection 继承了 JDK动态代理 的 InvocationHandler接口。
PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection 数据库连接对象 及其代理对象JDK 动态代理生成的。PooledConnection 继承了 JDK 动态代理 的 InvocationHandler 接口。
```java ```java
class PooledConnection implements InvocationHandler { class PooledConnection implements InvocationHandler {
@ -228,8 +241,11 @@ class PooledConnection implements InvocationHandler {
} }
} }
``` ```
#### 1.3.2 PoolState #### 1.3.2 PoolState
PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List&lt;PooledConnection&gt;集合 分别管理空闲状态的连接 和 活跃状态的连接。另外PoolState 还定义了一系列用于统计的字段。 PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List&lt;PooledConnection&gt;集合 分别管理空闲状态的连接 和 活跃状态的连接。另外PoolState 还定义了一系列用于统计的字段。
```java ```java
public class PoolState { public class PoolState {
@ -335,9 +351,11 @@ public class PoolState {
} }
} }
``` ```
#### 1.3.3 PooledDataSource #### 1.3.3 PooledDataSource
PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource对象 创建的,并由 PoolState 管理所有连接的状态。
PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection对象然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图: PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。
PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection 对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图:
![avatar](../../../images/mybatis/数据库连接池流程图.png) ![avatar](../../../images/mybatis/数据库连接池流程图.png)
@ -633,7 +651,9 @@ public class PooledDataSource implements DataSource {
} }
} }
``` ```
最后,我们来看一下 popConnection() 和 pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql语句检测连接是否正常。
最后,我们来看一下 popConnection() 和 pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid 字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql 语句,检测连接是否正常。
```java ```java
class PooledConnection implements InvocationHandler { class PooledConnection implements InvocationHandler {
/** /**
@ -703,9 +723,12 @@ public class PooledDataSource implements DataSource {
} }
} }
``` ```
## 2 Transaction ## 2 Transaction
遵循 “接口-实现类” 的设计原则Mybatis 也是先使用 Transaction接口 对数据库事务做了抽象而实现类则只提供了两个JdbcTransaction 和 ManagedTransaction。这两种对象的获取使用了两个对应的工厂类 JdbcTransactionFactory 和 ManagedTransactionFactory。
遵循 “接口-实现类” 的设计原则Mybatis 也是先使用 Transaction 接口 对数据库事务做了抽象而实现类则只提供了两个JdbcTransaction 和 ManagedTransaction。这两种对象的获取使用了两个对应的工厂类 JdbcTransactionFactory 和 ManagedTransactionFactory。
不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。 不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。
```java ```java
public interface Transaction { public interface Transaction {

@ -1,6 +1,7 @@
binding模块 主要为了解决一个历史遗留问题,原先查询一个 VO对象 时需要调用 SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法,执行指定的 sql语句第一个参数 selectXXVOById 指定了执行的 sql语句id如果我们不小心写错了参数Mybatis 是无法在初始化时发现这个错误的,只会在实际调用 queryForObject(“selectXXVOById”, primaryKey)方法 时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而 binding模块 就像 Java的泛型机制 一样,将程序的错误提前暴露出来,为开发人员省去不少排查问题的精力。 binding 模块主要为了解决一个历史遗留问题,原先查询一个 VO 对象 时需要调用 SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法,执行指定的 sql 语句,第一个参数 selectXXVOById 指定了执行的 sql 语句 id如果我们不小心写错了参数Mybatis 是无法在初始化时发现这个错误的,只会在实际调用 queryForObject(“selectXXVOById”, primaryKey)方法 时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而 binding 模块 就像 Java 的泛型机制 一样,将程序的错误提前暴露出来,为开发人员省去不少排查问题的精力。
binding 模块 的解决方案是,定义一个 Mapper 接口,在接口中定义 sql 语句 对应的 方法名 Id 及 参数,这些方法在 Mybatis 的初始化过程中,会与该 Mapper 接口 对应的映射配置文件中的 sql 语句 相关联,如果存在无法关联的 sql 语句Mybatis 就会抛出异常,帮助我们及时发现问题。示例代码如下:
binding模块 的解决方案是,定义一个 Mapper接口在接口中定义 sql语句 对应的 方法名Id 及 参数,这些方法在 Mybatis 的初始化过程中,会与该 Mapper接口 对应的映射配置文件中的 sql语句 相关联,如果存在无法关联的 sql语句Mybatis 就会抛出异常,帮助我们及时发现问题。示例代码如下:
```java ```java
public interface HeroMapper { public interface HeroMapper {
// 映射文件中会存在一个 <select>节点id 为 “selectHeroVOById” // 映射文件中会存在一个 <select>节点id 为 “selectHeroVOById”
@ -12,10 +13,13 @@ HeroMapper heroMapper = session.getMapper(HeroMapper.class);
// 直接调用 HeroMapper接口 中的方法,获取结果集 // 直接调用 HeroMapper接口 中的方法,获取结果集
HeroVO heroVO = heroMapper.selectHeroVOById("23333"); HeroVO heroVO = heroMapper.selectHeroVOById("23333");
``` ```
## 1 MapperRegistry和MapperProxyFactory
MapperRegistry 是 Mapper接口 及其对应的代理对象工厂的注册中心。Configuration 是 Mybatis 中全局性的配置对象,根据 Mybatis 的核心配置文件 mybatis-config.xml 解析而成。Configuration 通过 mapperRegistry属性 持有该对象。
Mybatis 在初始化过程中会读取映射配置文件和 Mapper接口 中的注解信息,并调用 MapperRegistry 的 addMappers()方法 填充 knownMappers集合在需要执行某 sql语句 时,会先调用 getMapper()方法 获取实现了 Mapper接口 的动态代理对象。 ## 1 MapperRegistry 和 MapperProxyFactory
MapperRegistry 是 Mapper 接口 及其对应的代理对象工厂的注册中心。Configuration 是 Mybatis 中全局性的配置对象,根据 Mybatis 的核心配置文件 mybatis-config.xml 解析而成。Configuration 通过 mapperRegistry 属性 持有该对象。
Mybatis 在初始化过程中会读取映射配置文件和 Mapper 接口 中的注解信息,并调用 MapperRegistry 的 addMappers()方法 填充 knownMappers 集合,在需要执行某 sql 语句 时,会先调用 getMapper()方法 获取实现了 Mapper 接口 的动态代理对象。
```java ```java
public class MapperRegistry { public class MapperRegistry {
@ -92,7 +96,9 @@ public class MapperRegistry {
} }
} }
``` ```
MapperProxyFactory 主要负责创建代理对象。 MapperProxyFactory 主要负责创建代理对象。
```java ```java
public class MapperProxyFactory<T> { public class MapperProxyFactory<T> {
@ -130,8 +136,11 @@ public class MapperProxyFactory<T> {
} }
} }
``` ```
## 2 MapperProxy ## 2 MapperProxy
MapperProxy 实现了 InvocationHandler接口为 Mapper接口 的方法调用织入了统一处理。
MapperProxy 实现了 InvocationHandler 接口,为 Mapper 接口 的方法调用织入了统一处理。
```java ```java
public class MapperProxy<T> implements InvocationHandler, Serializable { public class MapperProxy<T> implements InvocationHandler, Serializable {
@ -179,10 +188,13 @@ public class MapperProxy<T> implements InvocationHandler, Serializable {
} }
} }
``` ```
## 3 MapperMethod ## 3 MapperMethod
MapperMethod 中封装了 Mapper接口 中对应方法的信息,和对应 sql语句 的信息,是连接 Mapper接口 及映射配置文件中定义的 sql语句 的桥梁。
MapperMethod 中封装了 Mapper 接口 中对应方法的信息,和对应 sql 语句 的信息,是连接 Mapper 接口 及映射配置文件中定义的 sql 语句 的桥梁。
MapperMethod 中持有两个非常重要的属性,这两个属性对应的类 都是 MapperMethod 中的静态内部类。另外MapperMethod 在被实例化时就对这两个属性进行了初始化。 MapperMethod 中持有两个非常重要的属性,这两个属性对应的类 都是 MapperMethod 中的静态内部类。另外MapperMethod 在被实例化时就对这两个属性进行了初始化。
```java ```java
public class MapperMethod { public class MapperMethod {
@ -196,8 +208,11 @@ public class MapperMethod {
} }
} }
``` ```
MapperMethod 中的核心方法 execute() 就主要用到了这两个类,所以我们先看一下 SqlCommand 和 MethodSignature 的源码。 MapperMethod 中的核心方法 execute() 就主要用到了这两个类,所以我们先看一下 SqlCommand 和 MethodSignature 的源码。
### 3.1 SqlCommand ### 3.1 SqlCommand
```java ```java
public static class SqlCommand { public static class SqlCommand {
@ -266,7 +281,9 @@ MapperMethod 中的核心方法 execute() 就主要用到了这两个类,所
} }
} }
``` ```
### 3.2 MethodSignature ### 3.2 MethodSignature
```java ```java
public static class MethodSignature { public static class MethodSignature {
@ -341,8 +358,11 @@ MapperMethod 中的核心方法 execute() 就主要用到了这两个类,所
} }
} }
``` ```
### 3.3 execute()方法 ### 3.3 execute()方法
execute()方法 会根据 sql语句 的类型(CRUD)调用 SqlSession 对应的方法完成数据库操作SqlSession 是 Mybatis 的核心组件之一,后面会详细解读。
execute()方法 会根据 sql 语句 的类型(CRUD)调用 SqlSession 对应的方法完成数据库操作SqlSession 是 Mybatis 的核心组件之一,后面会详细解读。
```java ```java
public class MapperMethod { public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) { public Object execute(SqlSession sqlSession, Object[] args) {

@ -1,6 +1,9 @@
MyBatis 中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是 Cache接口 的实现。MyBatis缓存模块 的设计,使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。 MyBatis 中的缓存分为一级缓存、二级缓存,但在本质上是相同的,它们使用的都是 Cache 接口 的实现。MyBatis 缓存模块 的设计,使用了装饰器模式,这里不对此进行过多解析,以后会专门开一篇博文分析常用框架中使用到的设计模式。
## 1 Cache组件
MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache包 下,其中 Cache接口 是缓存模块中最核心的接口,它定义了所有缓存的基本行为。 ## 1 Cache 组件
MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache 包 下,其中 Cache 接口 是缓存模块中最核心的接口,它定义了所有缓存的基本行为。
```java ```java
public interface Cache { public interface Cache {
@ -45,12 +48,15 @@ public interface Cache {
} }
} }
``` ```
如下图所示Cache接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。
如下图所示Cache 接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。
![avatar](../../../images/mybatis/Cache组件.png) ![avatar](../../../images/mybatis/Cache组件.png)
### 1.1 PerpetualCache ### 1.1 PerpetualCache
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用 HashMap 记录缓存项,也是通过该 HashMap对象 的方法实现的 Cache接口 中定义的相应方法。
PerpetualCachePerpetual永恒的持续的在缓存模块中扮演着被装饰的角色其实现比较简单底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象 的方法实现的 Cache 接口 中定义的相应方法。
```java ```java
public class PerpetualCache implements Cache { public class PerpetualCache implements Cache {
@ -122,9 +128,13 @@ public class PerpetualCache implements Cache {
} }
} }
``` ```
下面来看一下 cache.decorators包 下提供的装饰器,它们都直接实现了 Cache接口扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。
下面来看一下 cache.decorators 包 下提供的装饰器,它们都直接实现了 Cache 接口,扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。
### 1.2 BlockingCache ### 1.2 BlockingCache
BlockingCache 是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。 BlockingCache 是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据。
```java ```java
public class BlockingCache implements Cache { public class BlockingCache implements Cache {
@ -142,7 +152,9 @@ public class BlockingCache implements Cache {
} }
} }
``` ```
假设 线程A 在 BlockingCache 中未查找到 keyA 对应的缓存项时线程A 会获取 keyA 对应的锁这样线程A 在后续查找 keyA 时,其它线程会被阻塞。
假设 线程 A 在 BlockingCache 中未查找到 keyA 对应的缓存项时,线程 A 会获取 keyA 对应的锁,这样,线程 A 在后续查找 keyA 时,其它线程会被阻塞。
```java ```java
// 根据 key 获取锁对象,然后上锁 // 根据 key 获取锁对象,然后上锁
private void acquireLock(Object key) { private void acquireLock(Object key) {
@ -172,7 +184,9 @@ public class BlockingCache implements Cache {
return locks.computeIfAbsent(key, k -> new ReentrantLock()); return locks.computeIfAbsent(key, k -> new ReentrantLock());
} }
``` ```
假设 线程A 从数据库中查找到 keyA 对应的结果对象后,将结果对象放入到 BlockingCache 中,此时 线程A 会释放 keyA 对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据,而不是再次访问数据库。
假设 线程 A 从数据库中查找到 keyA 对应的结果对象后,将结果对象放入到 BlockingCache 中,此时 线程 A 会释放 keyA 对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据,而不是再次访问数据库。
```java ```java
@Override @Override
public void putObject(Object key, Object value) { public void putObject(Object key, Object value) {
@ -194,8 +208,11 @@ public class BlockingCache implements Cache {
} }
} }
``` ```
### 1.3 FifoCache和LruCache
### 1.3 FifoCache 和 LruCache
在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。 在很多场景中为了控制缓存的大小系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。
```java ```java
public class FifoCache implements Cache { public class FifoCache implements Cache {
@ -264,7 +281,9 @@ public class FifoCache implements Cache {
} }
``` ```
LruCache 是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。 LruCache 是按照"近期最少使用算法"Least Recently Used, LRU进行缓存清理的装饰器在需要清理缓存时它会清除最近最少使用的缓存项。
```java ```java
public class LruCache implements Cache { public class LruCache implements Cache {
@ -350,23 +369,26 @@ public class LruCache implements Cache {
} }
``` ```
### 1.4 SoftCache和WeakCache
在分析 SoftCache 和 WeakCache 实现之前,我们再温习一下 Java 提供的4种引用类型强引用StrongReference、软引用SoftReference、弱引用WeakReference和虚引用PhantomReference。
- 强引用 ### 1.4 SoftCache 和 WeakCache
平时用的最多的,如 Object obj new Object(),新建的 Object对象 就是被强引用的。如果一个对象被强引用,即使是 JVM内存空间不足要抛出 OutOfMemoryError异常GC 也绝不会回收该对象。
在分析 SoftCache 和 WeakCache 实现之前,我们再温习一下 Java 提供的 4 种引用类型,强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference 和虚引用 PhantomReference。
- 强引用
平时用的最多的,如 Object obj new Object(),新建的 Object 对象 就是被强引用的。如果一个对象被强引用,即使是 JVM 内存空间不足,要抛出 OutOfMemoryError 异常GC 也绝不会回收该对象。
- 软引用 - 软引用
仅次于强引用的一种引用,它使用类 SoftReference 来表示。当 JVM内存不足时GC 会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的 SoftCache 就是通过软引用实现的。 仅次于强引用的一种引用,它使用类 SoftReference 来表示。当 JVM 内存不足时GC 会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的 SoftCache 就是通过软引用实现的。
另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get()方法 来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。 另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get()方法 来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null来判断被软引用的对象是否还存活。
- 弱引用 - 弱引用
弱引用使用 WeakReference表示它不会阻止所引用的对象被 GC回收。在 JVM 进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。 弱引用使用 WeakReference 表示,它不会阻止所引用的对象被 GC 回收。在 JVM 进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。
所以,只被弱引用所指向的对象,其生存周期是 两次GC之间 的这段时间,而只被软引用所指向的对象可以经历多次 GC直到出现内存紧张的情况才被回收。 所以,只被弱引用所指向的对象,其生存周期是 两次 GC 之间 的这段时间,而只被软引用所指向的对象可以经历多次 GC直到出现内存紧张的情况才被回收。
- 虚引用 - 虚引用
最弱的一种引用类型,由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。 最弱的一种引用类型,由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。
- 引用队列ReferenceQueue ) - 引用队列ReferenceQueue )
很多场景下,我们的程序需要在一个对象被 GC 时得到通知,引用队列就是用于收集这些信息的队列。在创建 SoftReference对象 时,可以为其关联一个引用队列,当 SoftReference 所引用的对象被 GC 时, JVM 就会将该 SoftReference对象 添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference对象。不仅是 SoftReference弱引用和虚引用都可以关联相应的队列。 很多场景下,我们的程序需要在一个对象被 GC 时得到通知,引用队列就是用于收集这些信息的队列。在创建 SoftReference 对象 时,可以为其关联一个引用队列,当 SoftReference 所引用的对象被 GC 时, JVM 就会将该 SoftReference 对象 添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference 对象。不仅是 SoftReference弱引用和虚引用都可以关联相应的队列。
现在来看一下 SoftCache 的具体实现。 现在来看一下 SoftCache 的具体实现。
```java ```java
public class SoftCache implements Cache { public class SoftCache implements Cache {
@ -480,17 +502,21 @@ public class SoftCache implements Cache {
} }
``` ```
WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry继承了WeakReference封装真正的 value对象其他实现完全一样。
另外,还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval字段 记录了两次缓存清理之间的时间间隔默认是一小时lastClear字段 记录了最近一次清理的时间戳。 ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。 WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry继承了 WeakReference封装真正的 value 对象,其他实现完全一样。
另外,还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval 字段 记录了两次缓存清理之间的时间间隔默认是一小时lastClear 字段 记录了最近一次清理的时间戳。 ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段 和 request 字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。
LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit字段 和 request字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。 SynchronizedCache 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类
SynchronizedCache 通过在每个方法上添加 synchronized关键字为 Cache 添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection内部类。 SerializedCache 提供了将 value 对象 序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象 进行序列化,井将序列化后的 byte[]数组 作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[]数组 反序列化成 Java 对象。不使用 SerializedCache 装饰器 进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java 原生序列化
SerializedCache 提供了将 value对象 序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java对象 进行序列化,井将序列化后的 byte[]数组 作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[]数组 反序列化成 Java对象。不使用 SerializedCache装饰器 进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java原生序列化。
## 2 CacheKey ## 2 CacheKey
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key 进行比较MyBatis 中因为涉及 动态SQL 等多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey类 来表示缓存项的 key在一个 CacheKey对象 中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey对象 是否相同。
在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key 进行比较MyBatis 中因为涉及 动态 SQL 等多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类 来表示缓存项的 key在一个 CacheKey 对象 中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象 是否相同。
```java ```java
public class CacheKey implements Cloneable, Serializable { public class CacheKey implements Cloneable, Serializable {
@ -613,6 +639,7 @@ public class CacheKey implements Cloneable, Serializable {
``` ```
## 3 小结 ## 3 小结
至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java反射机制的封装然后分析了类型转换 TypeHandler组件了解了 MyBatis 如何实现数据在 Java类型 与 JDBC类型 之间的转换。
之后分析了 MyBatis 提供的 DataSource模块 的实现和原理,深入解析了 MyBatis 自带的连接池 PooledDataSource 的详细实现;后面紧接着介绍了 Transaction模块 的功能。然后分析了 binding模块 如何将 Mapper接口 与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache接口 以及多个实现类的具体实现,它们是 Mybatis 中一级缓存和二级缓存的基础。 至此 Mybatis 的基础支持层的主要模块就分析完了。本模块首先介绍了 MyBatis 对 Java 反射机制的封装;然后分析了类型转换 TypeHandler 组件,了解了 MyBatis 如何实现数据在 Java 类型 与 JDBC 类型 之间的转换。
之后分析了 MyBatis 提供的 DataSource 模块 的实现和原理,深入解析了 MyBatis 自带的连接池 PooledDataSource 的详细实现;后面紧接着介绍了 Transaction 模块 的功能。然后分析了 binding 模块 如何将 Mapper 接口 与映射配置信息相关联,以及其中的原理。最后介绍了 MyBatis 的缓存模块,分析了 Cache 接口 以及多个实现类的具体实现,它们是 Mybatis 中一级缓存和二级缓存的基础。

@ -1,9 +1,11 @@
# mybatis 缓存 # mybatis 缓存
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis Cache 源码 - Description: 该文介绍 mybatis Cache 源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- `org.apache.ibatis.cache.Cache` - `org.apache.ibatis.cache.Cache`
```java ```java
public interface Cache { public interface Cache {
@ -52,7 +54,9 @@ public interface Cache {
- WeakCache: 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 - WeakCache: 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
## BlockingCache ## BlockingCache
- BlockingCache 内部使用了`ReentrantLock`来进行加锁开锁这个操作.在插入缓存时上锁,插入缓存后释放.请求缓存值得时候同理 - BlockingCache 内部使用了`ReentrantLock`来进行加锁开锁这个操作.在插入缓存时上锁,插入缓存后释放.请求缓存值得时候同理
```java ```java
public class BlockingCache implements Cache { public class BlockingCache implements Cache {
@ -157,7 +161,9 @@ public class BlockingCache implements Cache {
``` ```
## FifoCache ## FifoCache
- 存储结构是`java.util.LinkedList` - 存储结构是`java.util.LinkedList`
```java ```java
public class FifoCache implements Cache { public class FifoCache implements Cache {
@ -227,7 +233,9 @@ public class FifoCache implements Cache {
``` ```
## LruCache ## LruCache
- 存储结构是`java.util.LinkedHashMap` - 存储结构是`java.util.LinkedHashMap`
```java ```java
/** /**
* Lru (least recently used) cache decorator. * Lru (least recently used) cache decorator.
@ -318,4 +326,3 @@ public class LruCache implements Cache {
} }
``` ```

@ -1,4 +1,5 @@
# mybatis 反射 # mybatis 反射
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis 反射相关类的源码 - Description: 该文介绍 mybatis 反射相关类的源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
@ -6,6 +7,7 @@
## addDefaultConstructor ## addDefaultConstructor
- mybatis 的反射相关内容在`org.apache.ibatis.reflection` 下存放. 本片主要讲解`org.apache.ibatis.reflection.Reflector`类, 先看一下该类的属性 - mybatis 的反射相关内容在`org.apache.ibatis.reflection` 下存放. 本片主要讲解`org.apache.ibatis.reflection.Reflector`类, 先看一下该类的属性
```java ```java
public class Reflector { public class Reflector {
@ -51,6 +53,7 @@ public class Reflector {
``` ```
- 构造方法, 构造方法传入一个类的字节码,在构造方法中设置相关的属性值 - 构造方法, 构造方法传入一个类的字节码,在构造方法中设置相关的属性值
```java ```java
public class Reflector { public class Reflector {
@ -79,7 +82,9 @@ public Reflector(Class<?> clazz) {
} }
} }
``` ```
- `addDefaultConstructor` 方法 , 下面截图内容为JDK8 mybatis中 的内容
- `addDefaultConstructor` 方法 , 下面截图内容为 JDK8 mybatis 中 的内容
```java ```java
private void addDefaultConstructor(Class<?> clazz) { private void addDefaultConstructor(Class<?> clazz) {
@ -93,7 +98,9 @@ public Reflector(Class<?> clazz) {
}); });
} }
``` ```
- 创建一个测试类 - 创建一个测试类
```java ```java
public class People { public class People {
private String name; private String name;
@ -122,6 +129,7 @@ public class People {
} }
``` ```
```java ```java
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -151,7 +159,7 @@ class HfReflectorTest {
![1575890475839](../../../images/mybatis/1575890475839.png) ![1575890475839](../../../images/mybatis/1575890475839.png)
可以发现空参构造的`parameterTypes`长度是0.因此可以确认`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`方法获取了空参构造 可以发现空参构造的`parameterTypes`长度是 0.因此可以确认`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`方法获取了空参构造
- 继续看`org.apache.ibatis.reflection.Reflector#getDefaultConstructor`方法, 该方法是获取构造函数的方法,如果构造函数没有就抛出异常,这也是为什么我们的实体类需要把空参构造写上去的原因。 - 继续看`org.apache.ibatis.reflection.Reflector#getDefaultConstructor`方法, 该方法是获取构造函数的方法,如果构造函数没有就抛出异常,这也是为什么我们的实体类需要把空参构造写上去的原因。
@ -166,10 +174,6 @@ class HfReflectorTest {
} }
``` ```
## addGetMethods ## addGetMethods
- 该方法获取了所有`get`和`is`开头的方法 - 该方法获取了所有`get`和`is`开头的方法
@ -186,7 +190,7 @@ class HfReflectorTest {
} }
``` ```
- 该方法中依旧使用了JDK8语法通过`m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName())`来判断是否是`get`或·`is`开头的内容 - 该方法中依旧使用了 JDK8 语法通过`m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName())`来判断是否是`get`或·`is`开头的内容
- 调用`org.apache.ibatis.reflection.property.PropertyNamer` - 调用`org.apache.ibatis.reflection.property.PropertyNamer`
@ -199,8 +203,6 @@ class HfReflectorTest {
- `resolveGetterConflicts`方法后续介绍 - `resolveGetterConflicts`方法后续介绍
## getClassMethods ## getClassMethods
- `org.apache.ibatis.reflection.Reflector#getClassMethods`,该方法将传入对象的所有可见方法都获取到进行唯一标识处理成一个`Map`对象 添加方法为`org.apache.ibatis.reflection.Reflector#addUniqueMethods` - `org.apache.ibatis.reflection.Reflector#getClassMethods`,该方法将传入对象的所有可见方法都获取到进行唯一标识处理成一个`Map`对象 添加方法为`org.apache.ibatis.reflection.Reflector#addUniqueMethods`
@ -234,8 +236,6 @@ class HfReflectorTest {
``` ```
- `org.apache.ibatis.reflection.Reflector#addUniqueMethods` - `org.apache.ibatis.reflection.Reflector#addUniqueMethods`
```java ```java
@ -257,8 +257,6 @@ class HfReflectorTest {
} }
``` ```
- 唯一标识方法`org.apache.ibatis.reflection.Reflector#getSignature` - 唯一标识方法`org.apache.ibatis.reflection.Reflector#getSignature`
```java ```java
@ -283,8 +281,6 @@ class HfReflectorTest {
} }
``` ```
- 照旧我们进行 debug 当前方法为`toString`方法 - 照旧我们进行 debug 当前方法为`toString`方法
![1575891988804](../../../images/mybatis//1575891988804.png) ![1575891988804](../../../images/mybatis//1575891988804.png)
@ -301,7 +297,7 @@ class HfReflectorTest {
方法签名:方法 方法签名:方法
目前完成了一部分还有一个继承问题需要debug看一下, 编写一个`Man`继承`People` 还需要实现接口 目前完成了一部分还有一个继承问题需要 debug 看一下, 编写一个`Man`继承`People` 还需要实现接口
```java ```java
public class Man extends People implements TestManInterface { public class Man extends People implements TestManInterface {
@ -394,8 +390,6 @@ class HfReflectorTest {
} }
``` ```
## addFields ## addFields
- `org.apache.ibatis.reflection.Reflector#addFields` - `org.apache.ibatis.reflection.Reflector#addFields`
@ -425,8 +419,6 @@ class HfReflectorTest {
} }
``` ```
## 属性查看 ## 属性查看
- 下图为一个类的解析结果 - 下图为一个类的解析结果

@ -1,9 +1,11 @@
# mybatis 日志源码 # mybatis 日志源码
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis 日志相关源码 - Description: 该文介绍 mybatis 日志相关源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
## 核心类 ## 核心类
- `org.apache.ibatis.logging.Log` - `org.apache.ibatis.logging.Log`
- `org.apache.ibatis.logging.LogFactory` - `org.apache.ibatis.logging.LogFactory`
- 多个日志实现 - 多个日志实现
@ -12,7 +14,9 @@
- ... - ...
## 源码流程 ## 源码流程
- mybatis 提供了一个日志接口,内容如下. - mybatis 提供了一个日志接口,内容如下.
```java ```java
/** /**
* mybatis 的日志接口,提供日志级别 * mybatis 的日志接口,提供日志级别
@ -42,7 +46,9 @@ public interface Log {
void warn(String s); void warn(String s);
} }
``` ```
- 有了日志接口必然有实现类, mybatis 有`log4j2` 、 `slf4j` 等日志的相关实现 , 下面是`Slf4jImpl`的代码,其他代码也是一样的模式进行初始化就不再重复贴代码了. - 有了日志接口必然有实现类, mybatis 有`log4j2` 、 `slf4j` 等日志的相关实现 , 下面是`Slf4jImpl`的代码,其他代码也是一样的模式进行初始化就不再重复贴代码了.
```java ```java
public class Slf4jImpl implements Log { public class Slf4jImpl implements Log {
@ -108,7 +114,9 @@ public class Slf4jImpl implements Log {
} }
``` ```
- 通过上述方法来达到统一接口多个实现,这个在开发中也经常使用.多日志的实现方法有了还缺一个创建方法,创建方法由`org.apache.ibatis.logging.LogFactory`提供 - 通过上述方法来达到统一接口多个实现,这个在开发中也经常使用.多日志的实现方法有了还缺一个创建方法,创建方法由`org.apache.ibatis.logging.LogFactory`提供
```java ```java
/** /**
@ -239,6 +247,7 @@ public final class LogFactory {
- `LogFactory`是一个单例对象,对外公开`getLog`方法在使用时直接`private static final Log log = LogFactory.getLog(CglibProxyFactory.class);`即可 - `LogFactory`是一个单例对象,对外公开`getLog`方法在使用时直接`private static final Log log = LogFactory.getLog(CglibProxyFactory.class);`即可
- 在 `org.apache.ibatis.session.Configuration` 中可以看到下面这些注册方法 - 在 `org.apache.ibatis.session.Configuration` 中可以看到下面这些注册方法
```java ```java
// 日志实现类 // 日志实现类
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);

@ -1,6 +1,9 @@
和 Spring框架 的 IoC容器初始化 一样Mybatis 也会通过定位、解析相应的配置文件完成自己的初始化。Mybatis 的配置文件主要有 mybatis-config.xml核心配置文件 及一系列映射配置文件另外Mybatis 也会根据注解进行配置。 和 Spring 框架 的 IoC 容器初始化 一样Mybatis 也会通过定位、解析相应的配置文件完成自己的初始化。Mybatis 的配置文件主要有 mybatis-config.xml 核心配置文件 及一系列映射配置文件另外Mybatis 也会根据注解进行配置。
## 1 BaseBuilder ## 1 BaseBuilder
Mybatis初始化 的主要内容是加载并解析 mybatis-config.xml配置文件、映射配置文件以及相关的注解信息。Mybatis 的初始化入口是 SqlSessionFactoryBuilder 的 build()方法。
Mybatis 初始化 的主要内容是加载并解析 mybatis-config.xml 配置文件、映射配置文件以及相关的注解信息。Mybatis 的初始化入口是 SqlSessionFactoryBuilder 的 build()方法。
```java ```java
public class SqlSessionFactoryBuilder { public class SqlSessionFactoryBuilder {
@ -44,7 +47,9 @@ public class SqlSessionFactoryBuilder {
return new DefaultSqlSessionFactory(config); return new DefaultSqlSessionFactory(config);
} }
``` ```
BaseBuilder 中的核心字段如下: BaseBuilder 中的核心字段如下:
```java ```java
public abstract class BaseBuilder { public abstract class BaseBuilder {
@ -64,9 +69,13 @@ public abstract class BaseBuilder {
} }
} }
``` ```
BaseBuilder 中的 typeAliasRegistry 和 typeHandlerRegistry字段 均来自于 configuration通过 BaseBuilder 的构造方法可以看到详细内容。
BaseBuilder 中的 typeAliasRegistry 和 typeHandlerRegistry 字段 均来自于 configuration通过 BaseBuilder 的构造方法可以看到详细内容。
## 2 XMLConfigBuilder ## 2 XMLConfigBuilder
XMLConfigBuilder 是 BaseBuilder 的众多子类之一,主要负责解析 mybatis-config.xml配置文件。它通过调用 parseConfiguration()方法 实现整个解析过程其中mybatis-config.xml配置文件 中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法 只是依次调用了这些解析方法而已。
XMLConfigBuilder 是 BaseBuilder 的众多子类之一,主要负责解析 mybatis-config.xml 配置文件。它通过调用 parseConfiguration()方法 实现整个解析过程其中mybatis-config.xml 配置文件 中的每个节点都被封装成了一个个相应的解析方法parseConfiguration()方法 只是依次调用了这些解析方法而已。
```java ```java
public class XMLConfigBuilder extends BaseBuilder { public class XMLConfigBuilder extends BaseBuilder {
@ -115,8 +124,11 @@ public class XMLConfigBuilder extends BaseBuilder {
} }
} }
``` ```
Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。 Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑几个比较重要的标签进行分析。
### 2.1 解析&lt;typeHandlers&gt;标签 ### 2.1 解析&lt;typeHandlers&gt;标签
```java ```java
private void typeHandlerElement(XNode parent) throws Exception { private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) { if (parent != null) {
@ -153,7 +165,9 @@ Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑
} }
} }
``` ```
### 2.2 解析&lt;environments&gt;标签 ### 2.2 解析&lt;environments&gt;标签
```java ```java
/** /**
* Mybatis 可以配置多个 <environment>环境,分别用于开发、测试及生产等, * Mybatis 可以配置多个 <environment>环境,分别用于开发、测试及生产等,
@ -185,10 +199,13 @@ Mybatis 中的标签很多,所以相对应的解析方法也很多,这里挑
} }
} }
``` ```
### 2.3 解析&lt;databaseIdProvider&gt;标签 ### 2.3 解析&lt;databaseIdProvider&gt;标签
Mybatis 不像 Hibernate 那样,通过 HQL 的方式直接帮助开发人员屏蔽不同数据库产品在 sql语法 上的差异,针对不同的数据库产品, Mybatis 往往要编写不同的 sql语句。但在 mybatis-config.xml配置文件 中,可以通过 &lt;databaseIdProvider&gt; 定义所有支持的数据库产品的 databaseId然后在映射配置文件中定义 sql语句节点 时,通过 databaseId 指定该 sql语句 应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。
Mybatis 初始化时,会根据前面解析到的 DataSource 来确认当前使用的数据库产品,然后在解析映射文件时,加载不带 databaseId属性 的 sql语句 及带有 databaseId属性 的 sql语句其中带有 databaseId属性 的 sql语句 优先级更高,会被优先选中。 Mybatis 不像 Hibernate 那样,通过 HQL 的方式直接帮助开发人员屏蔽不同数据库产品在 sql 语法 上的差异,针对不同的数据库产品, Mybatis 往往要编写不同的 sql 语句。但在 mybatis-config.xml 配置文件 中,可以通过 &lt;databaseIdProvider&gt; 定义所有支持的数据库产品的 databaseId然后在映射配置文件中定义 sql 语句节点 时,通过 databaseId 指定该 sql 语句 应用的数据库产品,也可以达到类似的屏蔽数据库产品的功能。
Mybatis 初始化时,会根据前面解析到的 DataSource 来确认当前使用的数据库产品,然后在解析映射文件时,加载不带 databaseId 属性 的 sql 语句 及带有 databaseId 属性 的 sql 语句,其中,带有 databaseId 属性 的 sql 语句 优先级更高,会被优先选中。
```java ```java
/** /**
* 解析 <databaseIdProvider>节点,并创建指定的 DatabaseIdProvider对象 * 解析 <databaseIdProvider>节点,并创建指定的 DatabaseIdProvider对象
@ -217,7 +234,9 @@ Mybatis 初始化时,会根据前面解析到的 DataSource 来确认当前使
} }
} }
``` ```
Mybatis 提供了 DatabaseIdProvider接口该接口的核心方法为 getDatabaseId(DataSource dataSource),主要根据 dataSource 查找对应的 databaseId 并返回。该接口的主要实现类为 VendorDatabaseIdProvider。
Mybatis 提供了 DatabaseIdProvider 接口,该接口的核心方法为 getDatabaseId(DataSource dataSource),主要根据 dataSource 查找对应的 databaseId 并返回。该接口的主要实现类为 VendorDatabaseIdProvider。
```java ```java
public class VendorDatabaseIdProvider implements DatabaseIdProvider { public class VendorDatabaseIdProvider implements DatabaseIdProvider {
@ -279,8 +298,11 @@ public class VendorDatabaseIdProvider implements DatabaseIdProvider {
} }
} }
``` ```
### 2.4 解析&lt;mappers&gt;标签 ### 2.4 解析&lt;mappers&gt;标签
Mybatis 初始化时,除了加载 mybatis-config.xml文件还会加载全部的映射配置文件mybatis-config.xml 文件的 &lt;mapper&gt;节点 会告诉 Mybatis 去哪里查找映射配置文件,及使用了配置注解标识的接口。
Mybatis 初始化时,除了加载 mybatis-config.xml 文件还会加载全部的映射配置文件mybatis-config.xml 文件的 &lt;mapper&gt;节点 会告诉 Mybatis 去哪里查找映射配置文件,及使用了配置注解标识的接口。
```java ```java
/** /**
* 解析 <mappers>节点,本方法会创建 XMLMapperBuilder对象 加载映射文件,如果映射配置文件存在 * 解析 <mappers>节点,本方法会创建 XMLMapperBuilder对象 加载映射文件,如果映射配置文件存在
@ -327,8 +349,11 @@ Mybatis 初始化时,除了加载 mybatis-config.xml文件还会加载全
} }
} }
``` ```
## 3 XMLMapperBuilder ## 3 XMLMapperBuilder
和 XMLConfigBuilder 一样XMLMapperBuilder 也继承了 BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是 parse()另外XMLMapperBuilder 也将各个节点的解析过程拆分成了一个个小方法,然后由 configurationElement()方法 统一调用。 和 XMLConfigBuilder 一样XMLMapperBuilder 也继承了 BaseBuilder其主要负责解析映射配置文件其解析配置文件的入口方法也是 parse()另外XMLMapperBuilder 也将各个节点的解析过程拆分成了一个个小方法,然后由 configurationElement()方法 统一调用。
```java ```java
public class XMLMapperBuilder extends BaseBuilder { public class XMLMapperBuilder extends BaseBuilder {
public void parse() { public void parse() {
@ -372,13 +397,17 @@ public class XMLMapperBuilder extends BaseBuilder {
} }
} }
``` ```
XMLMapperBuilder 也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的 &lt;resultMap&gt;节点 和 &lt;sql&gt;节点 XMLMapperBuilder 也根据配置文件进行了一系列节点解析,我们着重分析一下比较重要且常见的 &lt;resultMap&gt;节点 和 &lt;sql&gt;节点
### 3.1 解析&lt;resultMap&gt;节点 ### 3.1 解析&lt;resultMap&gt;节点
select语句 查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而 Java 是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC编程 中,为了将结果集中的数据映射成 VO对象我们需要自己写代码从结果集中获取数据然后将数据封装成对应的 VO对象并设置好对象之间的关系这种 ORM 的过程中存在大量重复的代码。
Mybatis 通过 &lt;resultMap&gt;节点 定义了 ORM规则可以满足大部分的映射需求减少重复代码提高开发效率。 select 语句 查询得到的结果是一张二维表,水平方向上是一个个字段,垂直方向上是一条条记录。而 Java 是面向对象的程序设计语言对象是根据类的定义创建的类之间的引用关系可以认为是嵌套结构。JDBC 编程 中,为了将结果集中的数据映射成 VO 对象,我们需要自己写代码从结果集中获取数据,然后将数据封装成对应的 VO 对象,并设置好对象之间的关系,这种 ORM 的过程中存在大量重复的代码。
Mybatis 通过 &lt;resultMap&gt;节点 定义了 ORM 规则,可以满足大部分的映射需求,减少重复代码,提高开发效率。
在分析 &lt;resultMap&gt;节点 的解析过程之前,先看一下该过程使用的数据结构。每个 ResultMapping 对象 记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。&lt;resultMap&gt;节点 下除了 &lt;discriminator&gt;子节点 的其它子节点,都会被解析成对应的 ResultMapping 对象。
在分析 &lt;resultMap&gt;节点 的解析过程之前,先看一下该过程使用的数据结构。每个 ResultMapping对象 记录了结果集中的一列与 JavaBean 中一个属性之间的映射关系。&lt;resultMap&gt;节点 下除了 &lt;discriminator&gt;子节点 的其它子节点,都会被解析成对应的 ResultMapping对象。
```java ```java
public class ResultMapping { public class ResultMapping {
@ -411,7 +440,9 @@ public class ResultMapping {
private boolean lazy; private boolean lazy;
} }
``` ```
另一个比较重要的类是 ResultMap每个 &lt;resultMap&gt;节点 都会被解析成一个 ResultMap对象其中每个节点所定义的映射关系则使用 ResultMapping对象 表示。
另一个比较重要的类是 ResultMap每个 &lt;resultMap&gt;节点 都会被解析成一个 ResultMap 对象,其中每个节点所定义的映射关系,则使用 ResultMapping 对象 表示。
```java ```java
public class ResultMap { public class ResultMap {
private Configuration configuration; private Configuration configuration;
@ -442,7 +473,9 @@ public class ResultMap {
private Boolean autoMapping; private Boolean autoMapping;
} }
``` ```
了解了 ResultMapping 和 ResultMap 记录的信息之后,下面开始介绍 &lt;resultMap&gt;节点 的解析过程。在 XMLMapperBuilder 中通过 resultMapElements()方法 解析映射配置文件中的全部 &lt;resultMap&gt;节点,该方法会循环调用 resultMapElement()方法 处理每个 &lt;resultMap&gt; 节点。 了解了 ResultMapping 和 ResultMap 记录的信息之后,下面开始介绍 &lt;resultMap&gt;节点 的解析过程。在 XMLMapperBuilder 中通过 resultMapElements()方法 解析映射配置文件中的全部 &lt;resultMap&gt;节点,该方法会循环调用 resultMapElement()方法 处理每个 &lt;resultMap&gt; 节点。
```java ```java
private ResultMap resultMapElement(XNode resultMapNode) throws Exception { private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList()); return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
@ -498,7 +531,9 @@ public class ResultMap {
} }
} }
``` ```
从上面的代码我们可以看到Mybatis 从 &lt;resultMap&gt;节点 获取到 id属性 和 type属性值 之后,就会通过 XMLMapperBuilder 的 buildResultMappingFromContext()方法 为 &lt;result&gt;节点 创建对应的 ResultMapping对象。
从上面的代码我们可以看到Mybatis 从 &lt;resultMap&gt;节点 获取到 id 属性 和 type 属性值 之后,就会通过 XMLMapperBuilder 的 buildResultMappingFromContext()方法 为 &lt;result&gt;节点 创建对应的 ResultMapping 对象。
```java ```java
/** /**
* 根据上下文环境构建 ResultMapping * 根据上下文环境构建 ResultMapping
@ -531,7 +566,9 @@ public class ResultMap {
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
} }
``` ```
得到 ResultMapping对象集合 之后,会调用 ResultMapResolver 的 resolve()方法,该方法会调用 MapperBuilderAssistant 的 addResultMap()方法 创建 ResultMap对象并将 ResultMap对象 添加到 Configuration 的 resultMaps集合 中保存。
得到 ResultMapping 对象集合 之后,会调用 ResultMapResolver 的 resolve()方法,该方法会调用 MapperBuilderAssistant 的 addResultMap()方法 创建 ResultMap 对象,并将 ResultMap 对象 添加到 Configuration 的 resultMaps 集合 中保存。
```java ```java
public class MapperBuilderAssistant extends BaseBuilder { public class MapperBuilderAssistant extends BaseBuilder {
public ResultMap addResultMap(String id, Class<?> type, String extend, public ResultMap addResultMap(String id, Class<?> type, String extend,
@ -579,8 +616,11 @@ public class MapperBuilderAssistant extends BaseBuilder {
} }
} }
``` ```
### 3.2 解析&lt;sql&gt;节点 ### 3.2 解析&lt;sql&gt;节点
在映射配置文件中,可以使用 &lt;sql&gt;节点 定义可重用的 SQL语句片段当需要重用 &lt;sql&gt;节点 中定义的 SQL语句片段 时,只需要使用 &lt;include&gt;节点 引入相应的片段即可,这样,在编写 SQL语句 以及维护这些 SQL语句 时都会比较方便。XMLMapperBuilder 的 sqlElement()方法 负责解析映射配置文件中定义的 全部&lt;sql&gt;节点。
在映射配置文件中,可以使用 &lt;sql&gt;节点 定义可重用的 SQL 语句片段,当需要重用 &lt;sql&gt;节点 中定义的 SQL 语句片段 时,只需要使用 &lt;include&gt;节点 引入相应的片段即可,这样,在编写 SQL 语句 以及维护这些 SQL 语句 时都会比较方便。XMLMapperBuilder 的 sqlElement()方法 负责解析映射配置文件中定义的 全部&lt;sql&gt;节点。
```java ```java
private void sqlElement(List<XNode> list) throws Exception { private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) { if (configuration.getDatabaseId() != null) {
@ -604,13 +644,13 @@ public class MapperBuilderAssistant extends BaseBuilder {
} }
} }
``` ```
## 4 XMLStatementBuilder
## 4 XMLStatementBuilder
## 5 绑定 Mapper 接口
通过之前对 binding 模块 的解析可知,每个映射配置文件的命名空间可以绑定一个 Mapper 接口,并注册到 MapperRegistry 中。XMLMapperBuilder 的 bindMapperForNamespace()方法 中,完成了映射配置文件与对应 Mapper 接口 的绑定。
## 5 绑定Mapper接口
通过之前对 binding模块 的解析可知,每个映射配置文件的命名空间可以绑定一个 Mapper接口并注册到 MapperRegistry中。XMLMapperBuilder 的 bindMapperForNamespace()方法 中,完成了映射配置文件与对应 Mapper接口 的绑定。
```java ```java
public class XMLMapperBuilder extends BaseBuilder { public class XMLMapperBuilder extends BaseBuilder {
private void bindMapperForNamespace() { private void bindMapperForNamespace() {
@ -695,4 +735,5 @@ public class MapperAnnotationBuilder {
} }
} }
``` ```
另外,在 MapperAnnotationBuilder 的 parse()方法 中解析的注解,都能在映射配置文件中找到与之对应的 XML节点且两者的解析过程也非常相似。
另外,在 MapperAnnotationBuilder 的 parse()方法 中解析的注解,都能在映射配置文件中找到与之对应的 XML 节点,且两者的解析过程也非常相似。

@ -1,6 +1,7 @@
StatementHandler接口是MyBatis的核心接口之一它完成了MyBatis中最核心的工作也是Executor 接口实现的基础。 StatementHandler 接口是 MyBatis 的核心接口之一,它完成了 MyBatis 中最核心的工作,也是 Executor 接口实现的基础。
StatementHandler 接口中的功能很多,例如创建 Statement 对象,为 SQL 语句绑定实参,执行 select、insert、update、delete 等多种类型的 SQL 语句,批量执行 SQL 语句,将结果集映射成结果对象。
StatementHandler接口中的功能很多例如创建Statement对象为SQL语句绑定实参执行select、insert、update、delete等多种类型的SQL语句批量执行SQL语句将结果集映射成结果对象。
```java ```java
public interface StatementHandler { public interface StatementHandler {
@ -34,8 +35,11 @@ public interface StatementHandler {
} }
``` ```
## RoutingStatementHandler ## RoutingStatementHandler
RoutingStatementHandler使用了策略模式RoutingStatementHandler是策略类而SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler则是实现了具体算法的实现类RoutingStatementHandler对象会根据MappedStatement对象的StatementType属性值选择使用相应的策略去执行。
RoutingStatementHandler 使用了策略模式RoutingStatementHandler 是策略类,而 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler 则是实现了具体算法的实现类RoutingStatementHandler 对象会根据 MappedStatement 对象的 StatementType 属性值选择使用相应的策略去执行。
```java ```java
public class RoutingStatementHandler implements StatementHandler { public class RoutingStatementHandler implements StatementHandler {
@ -102,8 +106,11 @@ public class RoutingStatementHandler implements StatementHandler {
} }
} }
``` ```
## BaseStatementHandler ## BaseStatementHandler
看它以Base开头就可以猜到 它是一个实现了StatementHandler接口的抽象类这个类只提供了一些参数绑定相关的方法并没有实现操作数据库的方法。
看它以 Base 开头,就可以猜到 它是一个实现了 StatementHandler 接口的抽象类,这个类只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法。
```java ```java
public abstract class BaseStatementHandler implements StatementHandler { public abstract class BaseStatementHandler implements StatementHandler {
@ -225,9 +232,13 @@ public abstract class BaseStatementHandler implements StatementHandler {
} }
``` ```
BaseStatementHandler主要实现了StatementHandler接口中的prepare()方法BaseStatementHandler依赖两个重要的组件ParameterHandler和ResultSetHandler。
## ParameterHandler系列组件 BaseStatementHandler 主要实现了 StatementHandler 接口中的 prepare()方法BaseStatementHandler 依赖两个重要的组件ParameterHandler 和 ResultSetHandler。
我们要执行的SQL语句中可能包含占位符"?",而每个"?"都对应了BoundSql中parameterMappings集合中的一个元素在该ParameterMapping对象中记录了对应的参数名称以及该参数的相关属性。ParameterHandler接口定义了一个非常重要的方法setParameters()该方法主要负责调用PreparedStatement的set()系列方法为SQL语句绑定实参。MyBatis只为ParameterHandler接口提供了唯一一个实现类DefaultParameterHandler。
## ParameterHandler 系列组件
我们要执行的 SQL 语句中可能包含占位符"?",而每个"?"都对应了 BoundSql 中 parameterMappings 集合中的一个元素,在该 ParameterMapping 对象中记录了对应的参数名称以及该参数的相关属性。ParameterHandler 接口定义了一个非常重要的方法 setParameters(),该方法主要负责调用 PreparedStatement 的 set()系列方法,为 SQL 语句绑定实参。MyBatis 只为 ParameterHandler 接口提供了唯一一个实现类 DefaultParameterHandler。
```java ```java
public interface ParameterHandler { public interface ParameterHandler {
@ -317,9 +328,13 @@ public class DefaultParameterHandler implements ParameterHandler {
} }
``` ```
为SQL语句绑定完实参之后就可以调用Statement对象 相应的execute方法将SQL语句交给数据库执行了。
为 SQL 语句绑定完实参之后,就可以调用 Statement 对象 相应的 execute 方法,将 SQL 语句交给数据库执行了。
## SimpleStatementHandler ## SimpleStatementHandler
SimpleStatementHandler继承了BaseStatementHandler抽象类。其底层使用java.sql.Statement来完成数据库的相关操作所以SQL语句中不存在占位符所以SimpleStatementHandler的parameterize()方法是空实现。SimpleStatementHandler的instantiateStatement()方法直接通过JDBC Connection创建Statement对象。
SimpleStatementHandler 继承了 BaseStatementHandler 抽象类。其底层使用 java.sql.Statement 来完成数据库的相关操作,所以 SQL 语句中不存在占位符,所以 SimpleStatementHandler 的 parameterize()方法是空实现。SimpleStatementHandler 的 instantiateStatement()方法直接通过 JDBC Connection 创建 Statement 对象。
```java ```java
public class SimpleStatementHandler extends BaseStatementHandler { public class SimpleStatementHandler extends BaseStatementHandler {
@ -403,8 +418,11 @@ public class SimpleStatementHandler extends BaseStatementHandler {
} }
``` ```
## PreparedStatementHandler ## PreparedStatementHandler
PreparedStatementHandler底层依赖于java.sql.PreparedStatement来完成数据库的相关操作。其中的parameterize()方法中会调用前面介绍的ParameterHandler的setParameters()方法 完成 SQL语句的参数绑定。instantiateStatement()方法直接调用JDBC Connection的prepareStatement()方法创建PreparedStatement对象。
PreparedStatementHandler 底层依赖于 java.sql.PreparedStatement 来完成数据库的相关操作。其中的 parameterize()方法中,会调用前面介绍的 ParameterHandler 的 setParameters()方法 完成 SQL 语句的参数绑定。instantiateStatement()方法直接调用 JDBC Connection 的 prepareStatement()方法创建 PreparedStatement 对象。
```java ```java
public class PreparedStatementHandler extends BaseStatementHandler { public class PreparedStatementHandler extends BaseStatementHandler {
@ -479,6 +497,7 @@ public class PreparedStatementHandler extends BaseStatementHandler {
} }
``` ```
另外StatementHandler接口还有一个CallableStatementHandler的实现。其底层依赖于java.sql.CallableStatement调用指定的存储过程其parameterize()方法也会调用ParameterHandler的setParameters()方法完成SQL语句的参数绑定并指定输出参数的索引位置和JDBC类型。其余方法与前面介绍的ResultSetHandler实现类似唯一区别是会调用ResultSetHandler的handleOutputParameters()方法 处理输出参数。
看到这里我们可以发现StatementHandler组件依赖ParameterHandler组件 和 ResultSetHandler组件 完成了MyBatis的核心功能它控制着参数绑定、SQL语句执行、结果集映射等一系列核心流程。 另外StatementHandler 接口还有一个 CallableStatementHandler 的实现。其底层依赖于 java.sql.CallableStatement 调用指定的存储过程,其 parameterize()方法也会调用 ParameterHandler 的 setParameters()方法完成 SQL 语句的参数绑定,并指定输出参数的索引位置和 JDBC 类型。其余方法与前面介绍的 ResultSetHandler 实现类似,唯一区别是会调用 ResultSetHandler 的 handleOutputParameters()方法 处理输出参数。
看到这里,我们可以发现 StatementHandler 组件依赖 ParameterHandler 组件 和 ResultSetHandler 组件 完成了 MyBatis 的核心功能它控制着参数绑定、SQL 语句执行、结果集映射等一系列核心流程。

@ -1,4 +1,5 @@
Executor是MyBatis的核心接口之一其中定义了数据库操作的基本方法。在实际应用中经常涉及的SqISession接口的功能都是基于Executor 接口实现的。 Executor 是 MyBatis 的核心接口之一,其中定义了数据库操作的基本方法。在实际应用中经常涉及的 SqISession 接口的功能,都是基于 Executor 接口实现的。
```java ```java
public interface Executor { public interface Executor {
ResultHandler NO_RESULT_HANDLER = null; ResultHandler NO_RESULT_HANDLER = null;
@ -44,8 +45,11 @@ public interface Executor {
boolean isClosed(); boolean isClosed();
} }
``` ```
## 1 BaseExecutor ## 1 BaseExecutor
BaseExecutor是一个实现了Executor接口的抽象类它实现了Executor接口的大部分方法。BaseExecutor中主要提供了缓存管理和事务管理的基本功能继承BaseExecutor的子类只要实现四个基本方法来完成数据库的相关操作即可这四个方法分别是doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法。
BaseExecutor 是一个实现了 Executor 接口的抽象类,它实现了 Executor 接口的大部分方法。BaseExecutor 中主要提供了缓存管理和事务管理的基本功能,继承 BaseExecutor 的子类只要实现四个基本方法来完成数据库的相关操作即可这四个方法分别是doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法。
```java ```java
public abstract class BaseExecutor implements Executor { public abstract class BaseExecutor implements Executor {
@ -70,18 +74,23 @@ public abstract class BaseExecutor implements Executor {
private boolean closed; private boolean closed;
} }
``` ```
### 1.1 一级缓存简介 ### 1.1 一级缓存简介
常见的应用系统中,数据库是比较珍贵的资源,很容易成为整个系统的瓶颈。在设计和维护系统时,会进行多方面的权衡,并且利用多种优化手段,减少对数据库的直接访问。 常见的应用系统中,数据库是比较珍贵的资源,很容易成为整个系统的瓶颈。在设计和维护系统时,会进行多方面的权衡,并且利用多种优化手段,减少对数据库的直接访问。
使用缓存是一种比较有效的优化手段,使用缓存可以减少应用系统与数据库的网络交互、减少数据库访问次数、降低数据库的负担、降低重复创建和销毁对象等一系列开销,从而提高整个系统的性能。 使用缓存是一种比较有效的优化手段,使用缓存可以减少应用系统与数据库的网络交互、减少数据库访问次数、降低数据库的负担、降低重复创建和销毁对象等一系列开销,从而提高整个系统的性能。
MyBatis提供的缓存功能分别为一级缓存和二级缓存。BaseExecutor主要实现了一级缓存的相关内容。一级缓存是会话级缓存在MyBatis中每创建一个SqlSession对象就表示开启一次数据库会话。在一次会话中应用程序可能会在短时间内(一个事务内),反复执行完全相同的查询语句,如果不对数据进行缓存,那么每一次查询都会执行一次数据库查询操作,而多次完全相同的、时间间隔较短的查询语句得到的结果集极有可能完全相同,这会造成数据库资源的浪费。 MyBatis 提供的缓存功能分别为一级缓存和二级缓存。BaseExecutor 主要实现了一级缓存的相关内容。一级缓存是会话级缓存,在 MyBatis 中每创建一个 SqlSession 对象,就表示开启一次数据库会话。在一次会话中,应用程序可能会在短时间内(一个事务内),反复执行完全相同的查询语句,如果不对数据进行缓存,那么每一次查询都会执行一次数据库查询操作,而多次完全相同的、时间间隔较短的查询语句得到的结果集极有可能完全相同,这会造成数据库资源的浪费。
为了避免上述问题MyBatis 会在 Executor 对象中建立一个简单的一级缓存,将每次查询的结果集缓存起来。在执行查询操作时,会先查询一级缓存,如果存在完全一样的查询情况,则直接从一级缓存中取出相应的结果对象并返回给用户,减少数据库访问次数,从而减小了数据库的压力。
为了避免上述问题MyBatis会在Executor对象中建立一个简单的一级缓存将每次查询的结果集缓存起来。在执行查询操作时会先查询一级缓存如果存在完全一样的查询情况则直接从一级缓存中取出相应的结果对象并返回给用户减少数据库访问次数从而减小了数据库的压力。 一级缓存的生命周期与 SqlSession 相同,其实也就与 SqISession 中封装的 Executor 对象的生命周期相同。当调用 Executor 对象的 close()方法时(断开连接),该 Executor 对象对应的一级缓存就会被废弃掉。一级缓存中对象的存活时间受很多方面的影响,例如,在调用 Executor 的 update()方法时,也会先请空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置
一级缓存的生命周期与SqlSession相同其实也就与SqISession中封装的Executor 对象的生命周期相同。当调用Executor对象的close()方法时断开连接该Executor 对象对应的一级缓存就会被废弃掉。一级缓存中对象的存活时间受很多方面的影响例如在调用Executor的update()方法时,也会先请空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。
### 1.2 一级缓存的管理 ### 1.2 一级缓存的管理
BaseExecutor的query()方法会首先创建CacheKey对象并根据该CacheKey对象查找一级缓存如果缓存命中则返回缓存中记录的结果对象如果缓存未命中则查询数据库得到结果集之后将结果集映射成结果对象并保存到一级缓存中同时返回结果对象。
BaseExecutor 的 query()方法会首先创建 CacheKey 对象,并根据该 CacheKey 对象查找一级缓存,如果缓存命中则返回缓存中记录的结果对象,如果缓存未命中则查询数据库得到结果集,之后将结果集映射成结果对象并保存到一级缓存中,同时返回结果对象。
```java ```java
public abstract class BaseExecutor implements Executor { public abstract class BaseExecutor implements Executor {
@Override @Override
@ -181,9 +190,11 @@ public abstract class BaseExecutor implements Executor {
} }
} }
``` ```
从上面的代码中可以看到BaseExecutor的query()方法会根据flushCache属性和localCacheScope配置 决定是否清空一级缓存。
另外BaseExecutor的update()方法在调用doUpdate()方法之前也会清除一级缓存。update()方法负责执行insert、update、delete三类SQL 语句它是调用doUpdate()方法实现的。 从上面的代码中可以看到BaseExecutor 的 query()方法会根据 flushCache 属性和 localCacheScope 配置 决定是否清空一级缓存。
另外BaseExecutor 的 update()方法在调用 doUpdate()方法之前也会清除一级缓存。update()方法负责执行 insert、update、delete 三类 SQL 语句,它是调用 doUpdate()方法实现的。
```java ```java
@Override @Override
public int update(MappedStatement ms, Object parameter) throws SQLException { public int update(MappedStatement ms, Object parameter) throws SQLException {
@ -207,8 +218,11 @@ public abstract class BaseExecutor implements Executor {
} }
} }
``` ```
### 1.3 事务相关操作 ### 1.3 事务相关操作
在BatchExecutor实现中可以缓存多条SQL语句等待合适时机将缓存的多条SQL 语句一并发送到数据库执行。Executor的flushStatements()方法主要是针对批处理多条SQL语句的它会调用doFlushStatements()这个基本方法处理Executor中缓存的多条SQL语句。在BaseExecutor的commit()及rollback()等方法中都会首先调用flushStatements()方法,然后再执行相关事务操作。
在 BatchExecutor 实现中,可以缓存多条 SQL 语句,等待合适时机将缓存的多条 SQL 语句一并发送到数据库执行。Executor 的 flushStatements()方法主要是针对批处理多条 SQL 语句的,它会调用 doFlushStatements()这个基本方法处理 Executor 中缓存的多条 SQL 语句。在 BaseExecutor 的 commit()及 rollback()等方法中都会首先调用 flushStatements()方法,然后再执行相关事务操作。
```java ```java
@Override @Override
public void commit(boolean required) throws SQLException { public void commit(boolean required) throws SQLException {
@ -280,8 +294,11 @@ public abstract class BaseExecutor implements Executor {
} }
} }
``` ```
## 2 SimpleExecutor ## 2 SimpleExecutor
SimpleExecutor继承了BaseExecutor抽象类它是最简单的Executor接口实现。Executor组件使用了模板方法模式一级缓存等固定不变的操作都封装到了BaseExecutor中在SimpleExecutor中就不必再关心一级缓存等操作只需要专注实现4 个基本方法的实现即可。
SimpleExecutor 继承了 BaseExecutor 抽象类,它是最简单的 Executor 接口实现。Executor 组件使用了模板方法模式,一级缓存等固定不变的操作都封装到了 BaseExecutor 中,在 SimpleExecutor 中就不必再关心一级缓存等操作,只需要专注实现 4 个基本方法的实现即可。
```java ```java
public class SimpleExecutor extends BaseExecutor { public class SimpleExecutor extends BaseExecutor {
@ -351,12 +368,15 @@ public class SimpleExecutor extends BaseExecutor {
} }
``` ```
## 3 ReuseExecutor ## 3 ReuseExecutor
在传统的JDBC编程中复用Statement对象是常用的一种优化手段该优化手段可以减少SQL预编译的开销以及创建和销毁Statement对象的开销从而提高性能Reuse复用
ReuseExecutor提供了Statement复用的功能ReuseExecutor中通过statementMap 字段缓存使用过的Statement对象key是SQL语句value是SQL对应的Statement 对象。 在传统的 JDBC 编程中,复用 Statement 对象是常用的一种优化手段,该优化手段可以减少 SQL 预编译的开销以及创建和销毁 Statement 对象的开销从而提高性能Reuse复用
ReuseExecutor 提供了 Statement 复用的功能ReuseExecutor 中通过 statementMap 字段缓存使用过的 Statement 对象key 是 SQL 语句value 是 SQL 对应的 Statement 对象。
ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与 SimpleExecutor 中对应方法的实现一样,区别在于其中调用的 prepareStatement()方法SimpleExecutor 每次都会通过 JDBC 的 Connection 对象创建新的 Statement 对象,而 ReuseExecutor 则会先尝试重用 StaternentMap 中缓存的 Statement 对象。
ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与SimpleExecutor中对应方法的实现一样区别在于其中调用的prepareStatement()方法SimpleExecutor每次都会通过JDBC的Connection对象创建新的Statement对象而ReuseExecutor则会先尝试重用StaternentMap中缓存的Statement对象。
```java ```java
// 本map用于缓存使用过的Statement以提升本框架的性能 // 本map用于缓存使用过的Statement以提升本框架的性能
// key SQL语句value 该SQL语句对应的Statement // key SQL语句value 该SQL语句对应的Statement
@ -403,51 +423,57 @@ ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与SimpleEx
return Collections.emptyList(); return Collections.emptyList();
} }
``` ```
#### 拓展内容SQL预编译
#### 拓展内容SQL 预编译
**1、数据库预编译起源** **1、数据库预编译起源**
1数据库SQL语句编译特性 1数据库 SQL 语句编译特性
数据库接收到sql语句之后需要词法和语义解析以优化sql语句制定执行计划。这需要花费一些时间。但是很多情况我们的同一条sql语句可能会反复执行或者每次执行的时候只有个别的值不同比如query的where子句值不同update的set子句值不同insert的values值不同 数据库接收到 sql 语句之后,需要词法和语义解析,以优化 sql 语句,制定执行计划。这需要花费一些时间。但是很多情况,我们的同一条 sql 语句可能会反复执行或者每次执行的时候只有个别的值不同比如query where 子句值不同update set 子句值不同insert values 值不同)。
2减少编译的方法 2减少编译的方法
如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等则效率就明显不行了。为了解决上面的问题于是就有了预编译预编译语句就是将这类语句中的值用占位符替代可以视为将sql语句模板化或者说参数化。一次编译、多次运行省去了解析优化等过程。 如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。为了解决上面的问题,于是就有了预编译,预编译语句就是将这类语句中的值用占位符替代,可以视为将 sql 语句模板化或者说参数化。一次编译、多次运行,省去了解析优化等过程。
3缓存预编译 3缓存预编译
预编译语句被DB的编译器编译后的执行代码被缓存下来那么下次调用时只要是相同的预编译语句就不需要重复编译只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。 预编译语句被 DB 的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要重复编译,只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。
4 预编译的实现方法 4 预编译的实现方法
预编译是通过PreparedStatement和占位符来实现的。 预编译是通过 PreparedStatement 和占位符来实现的。
**2.预编译作用** **2.预编译作用**
1减少编译次数 提升性能 1减少编译次数 提升性能
预编译之后的 sql 多数情况下可以直接执行DBMS数据库管理系统不需要再次编译。越复杂的sql往往编译的复杂度就越大。 预编译之后的 sql 多数情况下可以直接执行DBMS数据库管理系统不需要再次编译。越复杂的 sql往往编译的复杂度就越大。
2防止 SQL 注入
2防止SQL注入 使用预编译,后面注入的参数将不会再次触发 SQL 编译。也就是说,对于后面注入的参数,系统将不会认为它会是一个 SQL 命令,而默认其是一个参数,参数中的 or 或 and 等SQL 注入常用技俩)就不是 SQL 语法保留字了。
使用预编译后面注入的参数将不会再次触发SQL编译。也就是说对于后面注入的参数系统将不会认为它会是一个SQL命令而默认其是一个参数参数中的or或and等SQL注入常用技俩就不是SQL语法保留字了。 **3.mybatis 是如何实现预编译的**
**3.mybatis是如何实现预编译的** mybatis 默认情况下,将对所有的 sql 进行预编译。mybatis 底层使用 PreparedStatement过程是先将带有占位符即”?”)的 sql 模板发送至数据库服务器,由服务器对此无参数的 sql 进行编译后,将编译结果缓存,然后直接执行带有真实参数的 sql。核心是通过 “#{ }” 实现的。在预编译之前,#{ } 被解析为一个预编译语句PreparedStatement的占位符 ?。
mybatis默认情况下将对所有的 sql 进行预编译。mybatis底层使用PreparedStatement过程是先将带有占位符即”?”的sql模板发送至数据库服务器由服务器对此无参数的sql进行编译后将编译结果缓存然后直接执行带有真实参数的sql。核心是通过 “#{ }” 实现的。在预编译之前,#{ } 被解析为一个预编译语句PreparedStatement的占位符 ?。
```sql ```sql
// sqlMap 中如下的 sql 语句 // sqlMap 中如下的 sql 语句
select * from user where name = #{name}; select * from user where name = #{name};
// 解析成为预编译语句 // 解析成为预编译语句
select * from user where name = ?; select * from user where name = ?;
``` ```
## 4 BatchExecutor ## 4 BatchExecutor
应用系统在执行一条SQL语句时会将SQL语句以及相关参数通过网络发送到数据库系统。对于频繁操作数据库的应用系统来说如果执行一条SQL语句就向数据库发送一次请求很多时间会浪费在网络通信上。使用批量处理的优化方式可以在客户端缓存多条SQL语句并在合适的时机将多条SQL语句打包发送给数据库执行从而减少网络方面的开销提升系统的性能。
需要注意的是在批量执行多条SQL 语句时每次向数据库发送的SQL语句条数 应用系统在执行一条 SQL 语句时,会将 SQL 语句以及相关参数通过网络发送到数据库系统。对于频繁操作数据库的应用系统来说,如果执行一条 SQL 语句就向数据库发送一次请求,很多时间会浪费在网络通信上。使用批量处理的优化方式可以在客户端缓存多条 SQL 语句,并在合适的时机将多条 SQL 语句打包发送给数据库执行,从而减少网络方面的开销,提升系统的性能。
是有上限的若超出上限数据库会拒绝执行这些SQL语句井抛出异常所以批量发送SQL语句的时机很重要。
需要注意的是,在批量执行多条 SQL 语句时,每次向数据库发送的 SQL 语句条数
是有上限的,若超出上限,数据库会拒绝执行这些 SQL 语句井抛出异常,所以批量发送 SQL 语句的时机很重要。
mybatis 的 BatchExecutor 实现了批处理多条 SQL 语句的功能。
mybatis的BatchExecutor实现了批处理多条SQL 语句的功能。
```java ```java
public class BatchExecutor extends BaseExecutor { public class BatchExecutor extends BaseExecutor {
@ -584,10 +610,9 @@ public class BatchExecutor extends BaseExecutor {
} }
} }
``` ```
通过了解JDBC的批处理功能 我们可以知道Statement中可以添加不同语句结构的SQL但是每添加一个新结构的SQL语句都会触发一次编译操作。而PreparedStatement中只能添加同一语句结构的SQL语句只会触发一次编译操作但是可以通过绑定多组不同的实参实现批处理。通过上面对doUpdate()方法的分析可知BatchExecutor会将连续添加的、相同语句结构的SQL语句添加到同一个Statement/PreparedStatement对象中这样可以有效地减少编译操作的次数。
BatchExecutor中doQuery()和doQueryCursor()方法的实现与前面介绍的SimpleExecutor类似主要区别就是BatchExecutor中的这两个方法在最开始都会先调用flushStatements()方法执行缓存的SQL语句以保证 从数据库中查询到的数据是最新的。
CachingExecutor中为Executor对象增加了二级缓存相关功能而mybatis的二级缓存在实际使用中往往利大于弊被redis等产品所替代所以这里不做分析 通过了解 JDBC 的批处理功能 我们可以知道Statement 中可以添加不同语句结构的 SQL但是每添加一个新结构的 SQL 语句都会触发一次编译操作。而 PreparedStatement 中只能添加同一语句结构的 SQL 语句,只会触发一次编译操作,但是可以通过绑定多组不同的实参实现批处理。通过上面对 doUpdate()方法的分析可知BatchExecutor 会将连续添加的、相同语句结构的 SQL 语句添加到同一个 Statement/PreparedStatement 对象中,这样可以有效地减少编译操作的次数。
BatchExecutor 中 doQuery()和 doQueryCursor()方法的实现与前面介绍的 SimpleExecutor 类似,主要区别就是 BatchExecutor 中的这两个方法在最开始都会先调用 flushStatements()方法,执行缓存的 SQL 语句,以保证 从数据库中查询到的数据是最新的。
CachingExecutor 中为 Executor 对象增加了二级缓存相关功能,而 mybatis 的二级缓存在实际使用中往往利大于弊,被 redis 等产品所替代,所以这里不做分析。

@ -1,6 +1,9 @@
SqlSession是MyBatis核心接口之一也是MyBatis接口层的主要组成部分对外提供MyBatis常用的API。mybatis提供了两个SqlSession接口的实现分别为DefaultSqlSession、SqlSessionManager其中最常用的是DefaultSqlSession。另外跟前面分析过的源码mybatis的源码一样mybatis也为SqlSession提供了相应的工厂接口SqlSessionFactory及实现该接口的实现DefaultSqlSessionFactorySqlSessionManager同时实现了SqlSession和SqlSessionFactory接口 SqlSession 是 MyBatis 核心接口之一,也是 MyBatis 接口层的主要组成部分,对外提供 MyBatis 常用的 API。mybatis 提供了两个 SqlSession 接口的实现,分别为 DefaultSqlSession、SqlSessionManager其中最常用的是 DefaultSqlSession。另外跟前面分析过的源码 mybatis 的源码一样mybatis 也为 SqlSession 提供了相应的工厂接口 SqlSessionFactory及实现该接口的实现 DefaultSqlSessionFactorySqlSessionManager 同时实现了 SqlSession 和 SqlSessionFactory 接口)。
## 1 SqlSession ## 1 SqlSession
在SqlSession中定义了常用的数据库操作以及事务的相关操作为了方便用户使用每种类型的操作都提供了多种重载。
在 SqlSession 中定义了常用的数据库操作以及事务的相关操作,为了方便用户使用,每种类型的操作都提供了多种重载。
```java ```java
public interface SqlSession extends Closeable { public interface SqlSession extends Closeable {
// 泛型方法参数是要执行查询的sql语句返回值为查询的结果对象 // 泛型方法参数是要执行查询的sql语句返回值为查询的结果对象
@ -78,15 +81,18 @@ public interface SqlSession extends Closeable {
Connection getConnection(); Connection getConnection();
} }
``` ```
### 1.1 DefaultSqlSession ### 1.1 DefaultSqlSession
DefaultSqlSession是单独使用MyBatis进行开发时最常用的SqISession接口实现。其实现了SqISession接口中定义的方法及各方法的重载。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之间的调用关系如下图殊途同归它们最终都会调用Executor的query()方法。
DefaultSqlSession 是单独使用 MyBatis 进行开发时,最常用的 SqISession 接口实现。其实现了 SqISession 接口中定义的方法及各方法的重载。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之间的调用关系如下图,殊途同归,它们最终都会调用 Executor 的 query()方法。
![avatar](../../../images/mybatis/DefaultSqlSession方法调用栈.png) ![avatar](../../../images/mybatis/DefaultSqlSession方法调用栈.png)
上述重载方法最终都是通过调用Executor的query(MappedStatement, Object, RowBounds,ResultHandler)方法实现数据库查询操作的但各自对结果对象进行了相应的调整例如selectOne()方法是从结果对象集合中获取了第一个元素返回selectMap()方法会将List类型的结果集 转换成Map类型集合返回select()方法是将结果集交由用户指定的ResultHandler对象处理且没有返回值selectList()方法则是直接返回结果对象集合。 上述重载方法最终都是通过调用 Executor 的 query(MappedStatement, Object, RowBounds,ResultHandler)方法实现数据库查询操作的但各自对结果对象进行了相应的调整例如selectOne()方法是从结果对象集合中获取了第一个元素返回selectMap()方法会将 List 类型的结果集 转换成 Map 类型集合返回select()方法是将结果集交由用户指定的 ResultHandler 对象处理且没有返回值selectList()方法则是直接返回结果对象集合。
DefaultSqlSession的insert()方法、update()方法、delete()方法也有多个重载它们最后都是通过调用DefaultSqlSession的update(String, Object)方法实现的该重载首先会将dirty字段置为true然后再通过Executor的update()方法完成数据库修改操作。 DefaultSqlSession 的 insert()方法、update()方法、delete()方法也有多个重载,它们最后都是通过调用 DefaultSqlSession 的 update(String, Object)方法实现的,该重载首先会将 dirty 字段置为 true然后再通过 Executor 的 update()方法完成数据库修改操作。
DefaultSqlSession的commit()方法、rollback()方法以及close()方法都会调用Executor中相应的方法其中就会涉及清空缓存的操作之后就会将dirty字段设置为false。 DefaultSqlSession 的 commit()方法、rollback()方法以及 close()方法都会调用 Executor 中相应的方法,其中就会涉及清空缓存的操作,之后就会将 dirty 字段设置为 false。
上述的dirty字段主要在isCommitOrRollbackRequired()方法中与autoCommit字段以及用户传入的force参数共同决定是否提交/回滚事务。该方法的返回值将作为Executor的commit()方法和rollback()方法的参数。 上述的 dirty 字段主要在 isCommitOrRollbackRequired()方法中,与 autoCommit 字段以及用户传入的 force 参数共同决定是否提交/回滚事务。该方法的返回值将作为 Executor 的 commit()方法和 rollback()方法的参数。
```java ```java
private boolean isCommitOrRollbackRequired(boolean force) { private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force; return (!autoCommit && dirty) || force;
@ -94,7 +100,9 @@ DefaultSqlSession的commit()方法、rollback()方法以及close()方法都会
``` ```
## 2 SqlSessionFactory ## 2 SqlSessionFactory
SqlSessionFactory负责创建SqlSession对象其中包含了多个openSession()方法的重载可以通过其参数指定事务的隔离级别、底层使用Executor的类型、以及是否自动提交事务等方面的配置。
SqlSessionFactory 负责创建 SqlSession 对象,其中包含了多个 openSession()方法的重载,可以通过其参数指定事务的隔离级别、底层使用 Executor 的类型、以及是否自动提交事务等方面的配置。
```java ```java
public interface SqlSessionFactory { public interface SqlSessionFactory {
@ -112,10 +120,13 @@ public interface SqlSessionFactory {
Configuration getConfiguration(); Configuration getConfiguration();
} }
``` ```
### 2.1 DefaultSqlSessionFactory ### 2.1 DefaultSqlSessionFactory
DefaultSqlSessionFactory是SqlSessionFactory接口的默认实现主要提供了两种创建DefaultSqlSession对象的方式一种方式是通过数据源获取数据库连接并创建Executor对象以及DefaultSqlSession对象另一种方式是用户提供数据库连接对象DefaultSqlSessionFactory根据该数据库连接对象获取autoCommit属性创建Executor对象以及DefaultSqlSession对象。
DefaultSqISessionFactory提供的所有openSession()方法重载都是基于上述两种方式创建DefaultSqlSession对象的。 DefaultSqlSessionFactory 是 SqlSessionFactory 接口的默认实现,主要提供了两种创建 DefaultSqlSession 对象的方式,一种方式是通过数据源获取数据库连接,并创建 Executor 对象以及 DefaultSqlSession 对象另一种方式是用户提供数据库连接对象DefaultSqlSessionFactory 根据该数据库连接对象获取 autoCommit 属性,创建 Executor 对象以及 DefaultSqlSession 对象。
DefaultSqISessionFactory 提供的所有 openSession()方法重载都是基于上述两种方式创建 DefaultSqlSession 对象的。
```java ```java
public class DefaultSqlSessionFactory implements SqlSessionFactory { public class DefaultSqlSessionFactory implements SqlSessionFactory {
@ -233,15 +244,18 @@ public class DefaultSqlSessionFactory implements SqlSessionFactory {
} }
} }
``` ```
### 2.2 SqlSessionManager ### 2.2 SqlSessionManager
SqlSessionManager同时实现了SqlSession接口和SqlSessionFactory接口所以同时提供了SqlSessionFactory创建SqlSession对象以及SqlSession操纵数据库的功能。
SqlSessionManager与DefaultSqlSessionFactory的主要不同点SqlSessionManager 提供了两种模式第一种模式与DefaultSqlSessionFactory的行为相同同一线程每次通过SqlSessionManager对象访问数据库时都会创建新的SqlSession对象完成数据库操作。第二种模式是SqlSessionManager通过localSqlSession这ThreadLocal 变量记录与当前线程绑定的SqlSession对象供当前线程循环使用从而避免在同一线程多次创建SqlSession对象带来的性能损失。 SqlSessionManager 同时实现了 SqlSession 接口和 SqlSessionFactory 接口,所以同时提供了 SqlSessionFactory 创建 SqlSession 对象,以及 SqlSession 操纵数据库的功能。
SqlSessionManager 与 DefaultSqlSessionFactory 的主要不同点 SqlSessionManager 提供了两种模式,第一种模式与 DefaultSqlSessionFactory 的行为相同,同一线程每次通过 SqlSessionManager 对象访问数据库时,都会创建新的 SqlSession 对象完成数据库操作。第二种模式是 SqlSessionManager 通过 localSqlSession 这 ThreadLocal 变量,记录与当前线程绑定的 SqlSession 对象,供当前线程循环使用,从而避免在同一线程多次创建 SqlSession 对象带来的性能损失。
SqlSessionManager的构造方法是唯一且私有的如果要创建SqlSessionManager对象需要调用其newInstance()方法但需要注意的是这不是单例模式因为每次调用newInstance()方法都返回了一个新的对象)。 SqlSessionManager 的构造方法是唯一且私有的,如果要创建 SqlSessionManager 对象,需要调用其 newInstance()方法(但需要注意的是,这不是单例模式,因为每次调用 newInstance()方法都返回了一个新的对象)。
SqlSessionManager 的 openSession()系列方法,都是通过直接调用其持有的
DefaultSqlSessionFactory 实例来实现的。
SqlSessionManager的openSession()系列方法,都是通过直接调用其持有的
DefaultSqlSessionFactory实例来实现的。
```java ```java
public class SqlSessionManager implements SqlSessionFactory, SqlSession { public class SqlSessionManager implements SqlSessionFactory, SqlSession {
@ -340,7 +354,9 @@ public class SqlSessionManager implements SqlSessionFactory, SqlSession {
} }
} }
``` ```
SqlSessionManager中实现的SqlSession接口方法例如select ()系列方法、update()系列方法等都是直接调用sqlSessionProxy代理对象对应的方法实现的。在创建该代理对象时使用的InvocationHandler对象是SqlSessionlnterceptor它是SqISessionManager的内部类。
SqlSessionManager 中实现的 SqlSession 接口方法,例如 select ()系列方法、update()系列方法等,都是直接调用 sqlSessionProxy 代理对象对应的方法实现的。在创建该代理对象时使用的 InvocationHandler 对象是 SqlSessionlnterceptor它是 SqISessionManager 的内部类。
```java ```java
private class SqlSessionInterceptor implements InvocationHandler { private class SqlSessionInterceptor implements InvocationHandler {
@ -380,7 +396,9 @@ SqlSessionManager中实现的SqlSession接口方法例如select ()系列方
} }
} }
``` ```
通过对SqlSessionlnterceptor的分析可知第一种模式中新建的SqlSession在使用完成后会立即关闭。在第二种模式中与当前线程绑定的SqISession对象需要先通过SqlSessionManager的startManagedSession()方法进行设置,此方法也存在多种重载,但都彼此相似 且简单。
通过对 SqlSessionlnterceptor 的分析可知,第一种模式中新建的 SqlSession 在使用完成后会立即关闭。在第二种模式中,与当前线程绑定的 SqISession 对象需要先通过 SqlSessionManager 的 startManagedSession()方法进行设置,此方法也存在多种重载,但都彼此相似 且简单。
```java ```java
public void startManagedSession() { public void startManagedSession() {
this.localSqlSession.set(openSession()); this.localSqlSession.set(openSession());
@ -418,7 +436,9 @@ SqlSessionManager中实现的SqlSession接口方法例如select ()系列方
return this.localSqlSession.get() != null; return this.localSqlSession.get() != null;
} }
``` ```
当需要提交/回滚事务或关闭IocalSqlSession中记录的SqlSession对象时需要通过SqlSessionManager的commit()、rollback()以及close()方法完成其中会先检测当前线程是否绑定了SqlSession对象如果未绑定则抛出异常如果绑定了则调用该SqlSession对象的相应方法。
当需要提交/回滚事务,或关闭 IocalSqlSession 中记录的 SqlSession 对象时,需要通过 SqlSessionManager 的 commit()、rollback()以及 close()方法完成,其中会先检测当前线程是否绑定了 SqlSession 对象,如果未绑定则抛出异常,如果绑定了则调用该 SqlSession 对象的相应方法。
```java ```java
@Override @Override
public void clearCache() { public void clearCache() {

@ -1,11 +1,11 @@
# Mybatis Alias # Mybatis Alias
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis Alias 源码 - Description: 该文介绍 mybatis Alias 源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- 源码位置 :`org.apache.ibatis.type.Alias` - 源码位置 :`org.apache.ibatis.type.Alias`
- 与 Alias 相关的一个方法`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`(别名注册) - 与 Alias 相关的一个方法`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`(别名注册)
```java ```java
/** /**
* 别名注册, * 别名注册,
@ -29,6 +29,7 @@
``` ```
- registerAlias 操作的对象是一个`map`对象 - registerAlias 操作的对象是一个`map`对象
```java ```java
/** /**
@ -37,8 +38,11 @@
*/ */
private final Map<String, Class<?>> typeAliases = new HashMap<>(); private final Map<String, Class<?>> typeAliases = new HashMap<>();
``` ```
不难看出这个对象存放的内容是 别名 -> clazz. 不难看出这个对象存放的内容是 别名 -> clazz.
- 相关注解`Alias` - 相关注解`Alias`
```java ```java
@Documented @Documented
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -52,7 +56,9 @@ public @interface Alias {
String value(); String value();
} }
``` ```
- 看一下实现方式 - 看一下实现方式
```java ```java
/** /**
* 加载{@link Alias} 注解的内容 * 加载{@link Alias} 注解的内容
@ -70,8 +76,10 @@ public @interface Alias {
registerAlias(alias, type); registerAlias(alias, type);
} }
``` ```
最后回到了`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`方法 最后回到了`org.apache.ibatis.type.TypeAliasRegistry.registerAlias(java.lang.String, java.lang.Class<?>)`方法
我们可以简单编写一个测试类 我们可以简单编写一个测试类
```java ```java
@Alias(value = "hc") @Alias(value = "hc")
public class Hc { public class Hc {
@ -88,4 +96,5 @@ public class Hc {
} }
``` ```
其他与`Alias`相关的测试类位于: `org.apache.ibatis.type.TypeAliasRegistryTest` 其他与`Alias`相关的测试类位于: `org.apache.ibatis.type.TypeAliasRegistryTest`

@ -1,10 +1,14 @@
# Mybatis Cursor # Mybatis Cursor
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis Cursor 源码 - Description: 该文介绍 mybatis Cursor 源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
## Cursor ## Cursor
- 源码位置:`org.apache.ibatis.cursor.Cursor` - 源码位置:`org.apache.ibatis.cursor.Cursor`
- 继承`Iterable`说明是一个迭代器,继承`Closeable`说明有一个东西需要关闭 - 继承`Iterable`说明是一个迭代器,继承`Closeable`说明有一个东西需要关闭
```java ```java
public interface Cursor<T> extends Closeable, Iterable<T> { public interface Cursor<T> extends Closeable, Iterable<T> {
@ -31,7 +35,9 @@ public interface Cursor<T> extends Closeable, Iterable<T> {
int getCurrentIndex(); int getCurrentIndex();
} }
``` ```
## DefaultCursor ## DefaultCursor
```java ```java
public class DefaultCursor<T> implements Cursor<T> { public class DefaultCursor<T> implements Cursor<T> {

@ -1,9 +1,11 @@
# Mybatis DataSource # Mybatis DataSource
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis DataSource 源码 - Description: 该文介绍 mybatis DataSource 源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- `org.apache.ibatis.datasource.DataSourceFactory` - `org.apache.ibatis.datasource.DataSourceFactory`
```java ```java
/** /**
* 数据源工厂 * 数据源工厂
@ -42,15 +44,8 @@ public interface DataSourceFactory {
</dataSource> </dataSource>
``` ```
- 在`org.apache.ibatis.session.Configuration`中有配置下面三个信息 - 在`org.apache.ibatis.session.Configuration`中有配置下面三个信息
```java ```java
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
@ -58,14 +53,6 @@ public interface DataSourceFactory {
``` ```
## JndiDataSourceFactory ## JndiDataSourceFactory
```java ```java
@ -156,8 +143,6 @@ public class JndiDataSourceFactory implements DataSourceFactory {
protected int poolPingConnectionsNotUsedFor; protected int poolPingConnectionsNotUsedFor;
``` ```
## PooledDataSourceFactory ## PooledDataSourceFactory
```java ```java
@ -176,8 +161,6 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
} }
``` ```
## UnpooledDataSourceFactory ## UnpooledDataSourceFactory
```java ```java
@ -208,8 +191,6 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
``` ```
## UnpooledDataSource ## UnpooledDataSource
- `org.apache.ibatis.datasource.unpooled.UnpooledDataSource`主要定义数据库连接相关的一些属性,以及与数据库的链接对象创建 - `org.apache.ibatis.datasource.unpooled.UnpooledDataSource`主要定义数据库连接相关的一些属性,以及与数据库的链接对象创建
@ -227,8 +208,6 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
private Integer defaultNetworkTimeout; private Integer defaultNetworkTimeout;
``` ```
- 初始化连接对象 - 初始化连接对象
```java ```java
@ -258,8 +237,6 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
``` ```
- 设置连接对象的属性 - 设置连接对象的属性
```java ```java
@ -282,8 +259,6 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
``` ```
- 获取连接对象 - 获取连接对象
```java ```java
@ -310,15 +285,9 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
``` ```
## 解析流程 ## 解析流程
- 在xml解析的过程中会执行`DataSourceFactory`相关内容 - 在 xml 解析的过程中会执行`DataSourceFactory`相关内容
```java ```java
/** /**
@ -357,4 +326,3 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
方法直接走完 方法直接走完
![image-20191223083732972](../../../images/mybatis/image-20191223083732972.png) ![image-20191223083732972](../../../images/mybatis/image-20191223083732972.png)

@ -1,12 +1,11 @@
# Mybatis DyanmicSqlSourcce # Mybatis DyanmicSqlSourcce
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- `org.apache.ibatis.scripting.xmltags.DynamicSqlSource` - `org.apache.ibatis.scripting.xmltags.DynamicSqlSource`
- `org.apache.ibatis.scripting.xmltags.DynamicContext.DynamicContext` - `org.apache.ibatis.scripting.xmltags.DynamicContext.DynamicContext`
```XML ```XML
<select id="list" resultType="com.huifer.mybatis.entity.HsSell"> <select id="list" resultType="com.huifer.mybatis.entity.HsSell">
select * from hs_sell select * from hs_sell
@ -20,8 +19,6 @@
``` ```
![image-20191219151247240](../../../images/mybatis/image-20191219151247240.png) ![image-20191219151247240](../../../images/mybatis/image-20191219151247240.png)
![image-20191219151408597](../../../images/mybatis/image-20191219151408597.png) ![image-20191219151408597](../../../images/mybatis/image-20191219151408597.png)
@ -43,9 +40,7 @@ public class MixedSqlNode implements SqlNode {
} }
``` ```
- 根据 mapper.xml 文件中的代码流程 需要走
- 根据mapper.xml文件中的代码流程 需要走
`org.apache.ibatis.scripting.xmltags.StaticTextSqlNode#apply` `org.apache.ibatis.scripting.xmltags.StaticTextSqlNode#apply`
@ -105,8 +100,6 @@ public class StaticTextSqlNode implements SqlNode {
} }
``` ```
![image-20191219152655746](../../../images/mybatis/image-20191219152655746.png) ![image-20191219152655746](../../../images/mybatis/image-20191219152655746.png)
```JAVA ```JAVA
@ -165,7 +158,7 @@ public class StaticTextSqlNode implements SqlNode {
存在返回`true` 存在返回`true`
执行完成就得到了一个sql 执行完成就得到了一个 sql
![image-20191219153553127](../../../images/mybatis/image-20191219153553127.png) ![image-20191219153553127](../../../images/mybatis/image-20191219153553127.png)
@ -173,9 +166,7 @@ public class StaticTextSqlNode implements SqlNode {
![image-20191219155129772](../../../images/mybatis/image-20191219155129772.png) ![image-20191219155129772](../../../images/mybatis/image-20191219155129772.png)
- 发送sql`org.apache.ibatis.executor.SimpleExecutor#doQuery` - 发送 sql`org.apache.ibatis.executor.SimpleExecutor#doQuery`
- 调用链路如下 - 调用链路如下
@ -281,8 +272,6 @@ public class StaticTextSqlNode implements SqlNode {
- `org.apache.ibatis.executor.statement.BaseStatementHandler#prepare` - `org.apache.ibatis.executor.statement.BaseStatementHandler#prepare`
- `org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement` - `org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement`
```java ```java
@Override @Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
@ -326,8 +315,6 @@ public class StaticTextSqlNode implements SqlNode {
- 这个方法都去了`java.sql.Connection#prepareStatement(java.lang.String, java.lang.String[])` - 这个方法都去了`java.sql.Connection#prepareStatement(java.lang.String, java.lang.String[])`
- 接下来需要考虑的问题是如何将`?`换成我们的参数`2` - 接下来需要考虑的问题是如何将`?`换成我们的参数`2`
![image-20191219161555793](../../../images/mybatis/image-20191219161555793.png) ![image-20191219161555793](../../../images/mybatis/image-20191219161555793.png)
@ -339,10 +326,6 @@ public class StaticTextSqlNode implements SqlNode {
- `org.apache.ibatis.executor.parameter.ParameterHandler` - `org.apache.ibatis.executor.parameter.ParameterHandler`
- `org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters` - `org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters`
![image-20191219162258040](../../../images/mybatis/image-20191219162258040.png) ![image-20191219162258040](../../../images/mybatis/image-20191219162258040.png)
这样就拿到了`value`的值 这样就拿到了`value`的值
@ -374,8 +357,6 @@ public class StaticTextSqlNode implements SqlNode {
- `org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets` - `org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets`
- `org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets` - `org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets`
![image-20191219163628214](../../../images/mybatis/image-20191219163628214.png) ![image-20191219163628214](../../../images/mybatis/image-20191219163628214.png)
![image-20191219163640968](../../../images/mybatis/image-20191219163640968.png) ![image-20191219163640968](../../../images/mybatis/image-20191219163640968.png)
@ -431,4 +412,3 @@ public class StaticTextSqlNode implements SqlNode {
} }
``` ```

@ -1,4 +1,5 @@
# MapperMethod # MapperMethod
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis MapperMethod 源码 - Description: 该文介绍 mybatis MapperMethod 源码
- 源码地址: `org.apache.ibatis.binding.MapperMethod`,核心方法是`execute` - 源码地址: `org.apache.ibatis.binding.MapperMethod`,核心方法是`execute`
@ -91,8 +92,8 @@
``` ```
- 返回值为多个的情况 - 返回值为多个的情况
```java ```java
/** /**
* 针对多个查询结果进行 ,转换成不同的 list 或者数组 * 针对多个查询结果进行 ,转换成不同的 list 或者数组
@ -128,6 +129,7 @@
``` ```
### convertToArray ### convertToArray
```java ```java
/** /**
* 转换为数组 * 转换为数组
@ -154,7 +156,9 @@
} }
``` ```
### convertToDeclaredCollection ### convertToDeclaredCollection
```java ```java
/** /**
* 转换为不同的list对象 * 转换为不同的list对象
@ -176,28 +180,22 @@
``` ```
- 上述两个为转换的过程,其实质还是在 `org.apache.ibatis.session.SqlSession` 中做执行操作 - 上述两个为转换的过程,其实质还是在 `org.apache.ibatis.session.SqlSession` 中做执行操作
## debug
- 修改 mapper 返回数组对`org.apache.ibatis.binding.MapperMethod#convertToArray`方法进行测试
## debug
- 修改mapper返回数组对`org.apache.ibatis.binding.MapperMethod#convertToArray`方法进行测试
```java ```java
HsSell[] list(@Param("ID") Integer id); HsSell[] list(@Param("ID") Integer id);
``` ```
![image-20191219092442456](../../../images/mybatis/image-20191219092442456.png) ![image-20191219092442456](../../../images/mybatis/image-20191219092442456.png)
- 修改mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试 - 修改 mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试
```java ```java
LinkedList<HsSell> list(@Param("ID") Integer id); LinkedList<HsSell> list(@Param("ID") Integer id);
``` ```
![image-20191219093043035](../../../images/mybatis/image-20191219093043035.png) ![image-20191219093043035](../../../images/mybatis/image-20191219093043035.png)

@ -1,7 +1,9 @@
# Mybatis MetaObject # Mybatis MetaObject
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- 源码位于:`org.apache.ibatis.reflection.MetaObject` - 源码位于:`org.apache.ibatis.reflection.MetaObject`
```java ```java
/** /**
* @author Clinton Begin * @author Clinton Begin

@ -1,8 +1,10 @@
# MethodSignature # MethodSignature
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis MethodSignature 类 - Description: 该文介绍 mybatis MethodSignature 类
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- `org.apache.ibatis.binding.MapperMethod.MethodSignature` - `org.apache.ibatis.binding.MapperMethod.MethodSignature`
```java ```java
/** /**
* 方法签名 * 方法签名

@ -1,4 +1,5 @@
# Mybatis ObjectWrapper # Mybatis ObjectWrapper
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- 源码位于: `org.apache.ibatis.reflection.wrapper.ObjectWrapper` - 源码位于: `org.apache.ibatis.reflection.wrapper.ObjectWrapper`
@ -6,6 +7,7 @@
类图: 类图:
![image-20191223100956713](../../../images/mybatis/image-20191223100956713.png) ![image-20191223100956713](../../../images/mybatis/image-20191223100956713.png)
```java ```java
public interface ObjectWrapper { public interface ObjectWrapper {
@ -114,7 +116,9 @@ public interface ObjectWrapper {
} }
``` ```
## BaseWrapper ## BaseWrapper
```java ```java
/** /**
* @author Clinton Begin * @author Clinton Begin
@ -230,6 +234,7 @@ public abstract class BaseWrapper implements ObjectWrapper {
``` ```
## BeanWrapper ## BeanWrapper
```java ```java
public class BeanWrapper extends BaseWrapper { public class BeanWrapper extends BaseWrapper {
@ -448,7 +453,9 @@ public class BeanWrapper extends BaseWrapper {
} }
``` ```
## MapWrapper ## MapWrapper
```java ```java
public class MapWrapper extends BaseWrapper { public class MapWrapper extends BaseWrapper {
@ -583,7 +590,9 @@ public class MapWrapper extends BaseWrapper {
} }
``` ```
## CollectionWrapper ## CollectionWrapper
```java ```java
public class CollectionWrapper implements ObjectWrapper { public class CollectionWrapper implements ObjectWrapper {

@ -1,10 +1,13 @@
# ParamNameResolver 源码解析 # ParamNameResolver 源码解析
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis `@Param` 注解和`ParamNameResolver` - Description: 该文介绍 mybatis `@Param` 注解和`ParamNameResolver`
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
## 源码 ## 源码
- `org.apache.ibatis.reflection.ParamNameResolver` - `org.apache.ibatis.reflection.ParamNameResolver`
```java ```java
/** /**
* {@link Param} 注解的扫描工具和处理工具 * {@link Param} 注解的扫描工具和处理工具
@ -142,8 +145,11 @@ public class ParamNameResolver {
} }
``` ```
## debug 阶段 ## debug 阶段
- 测试用例为同一个 每次修改mapper方法参数来进行debug
- 测试用例为同一个 每次修改 mapper 方法参数来进行 debug
```java ```java
@Test @Test
void testXmlConfigurationLoad() throws IOException { void testXmlConfigurationLoad() throws IOException {
@ -160,13 +166,11 @@ public class ParamNameResolver {
} }
``` ```
```java ```java
List<HsSell> list( Integer id); List<HsSell> list( Integer id);
``` ```
如果不写`@Param`称则返回 如果不写`@Param`称则返回
![image-20191219083223084](assets/image-20191219083223084.png) ![image-20191219083223084](assets/image-20191219083223084.png)
@ -180,8 +184,6 @@ public class ParamNameResolver {
![image-20191219083354873](../../../images/mybatis/image-20191219083354873.png) ![image-20191219083354873](../../../images/mybatis/image-20191219083354873.png)
- `org.apache.ibatis.reflection.ParamNameResolver#getNamedParams` - `org.apache.ibatis.reflection.ParamNameResolver#getNamedParams`
```java ```java
@ -190,9 +192,6 @@ public class ParamNameResolver {
![image-20191219084455292](../../../images/mybatis/image-20191219084455292.png) ![image-20191219084455292](../../../images/mybatis/image-20191219084455292.png)
```java ```java
List<HsSell> list(@Param("ID") Integer id); List<HsSell> list(@Param("ID") Integer id);
``` ```

@ -1,9 +1,11 @@
# sqlCommand # sqlCommand
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis sqlCommand类的源码 - Description: 该文介绍 mybatis sqlCommand 类的源码
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
- `org.apache.ibatis.binding.MapperMethod.SqlCommand` - `org.apache.ibatis.binding.MapperMethod.SqlCommand`
```java ```java
/** /**
* 核心内容: sql id , Sql 类型 * 核心内容: sql id , Sql 类型
@ -92,14 +94,6 @@
``` ```
![image-20191218191512184](../../../images/mybatis/image-20191218191512184.png) ![image-20191218191512184](../../../images/mybatis/image-20191218191512184.png)
![image-20191218191550550](../../../images/mybatis/image-20191218191550550.png) ![image-20191218191550550](../../../images/mybatis/image-20191218191550550.png)

@ -1,73 +1,88 @@
本博文用于重点分析 Netty 的逻辑架构及关键的架构质量属性,希望有助于大家从 Netty 的架构设计中汲取营养,设计出高性能、高可靠 本博文用于重点分析 Netty 的逻辑架构及关键的架构质量属性,希望有助于大家从 Netty 的架构设计中汲取营养,设计出高性能、高可靠
性和可扩展的程序。 性和可扩展的程序。
## Netty的三层架构设计 ## Netty 的三层架构设计
Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。 Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。
![avatar](../../../images/Netty/Netty逻辑架构图.png) ![avatar](../../../images/Netty/Netty逻辑架构图.png)
### 通信调度层 Reactor ### 通信调度层 Reactor
它由一系列辅助类完成,包括 Reactor线程 NioEventLoop 及其父类NioSocketChannel / NioServerSocketChannel 及其父类Buffer组件Unsafe组件 等。该层的主要职责就是**监听网络的读写和连接操作**,负责**将网络层的数据读取到内存缓冲区**,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 PipeLine 中,由 PipeLine 管理的责任链来进行后续的处理。
它由一系列辅助类完成,包括 Reactor 线程 NioEventLoop 及其父类NioSocketChannel / NioServerSocketChannel 及其父类Buffer 组件Unsafe 组件 等。该层的主要职责就是**监听网络的读写和连接操作**,负责**将网络层的数据读取到内存缓冲区**,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 PipeLine 中,由 PipeLine 管理的责任链来进行后续的处理。
### 责任链层 Pipeline ### 责任链层 Pipeline
它负责上述的各种网络事件 在责任链中的有序传播,同时负责动态地编排责任链。责任链可以选择监听和处理自己关心的事件,它可以拦截处理事件,以及向前向后传播事件。不同应用的 Handler节点 的功能也不同,通常情况下,往往会开发 编解码Hanlder 用于消息的编解码,可以将外部的协议消息转换成 内部的POJO对象这样上层业务则只需要关心处理业务逻辑即可不需要感知底层的协议差异和线程模型差异实现了架构层面的分层隔离。
它负责上述的各种网络事件 在责任链中的有序传播,同时负责动态地编排责任链。责任链可以选择监听和处理自己关心的事件,它可以拦截处理事件,以及向前向后传播事件。不同应用的 Handler 节点 的功能也不同,通常情况下,往往会开发 编解码 Hanlder 用于消息的编解码,可以将外部的协议消息转换成 内部的 POJO 对象,这样上层业务则只需要关心处理业务逻辑即可,不需要感知底层的协议差异和线程模型差异,实现了架构层面的分层隔离。
### 业务逻辑编排层 Service ChannelHandler ### 业务逻辑编排层 Service ChannelHandler
业务逻辑编排层通常有两类一类是纯粹的业务逻辑编排还有一类是其他的应用层协议插件用于特定协议相关的会话和链路管理。例如CMPP协议用于管理和中国移动短信系统的对接。
架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心责任链的拦截和 业务Handler 的编排。因为应用层协议栈往往是开发一次,到处运行,所以实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需关心业务逻辑定制。这种分层的架构设计理念实现了 NIO框架 各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制。 业务逻辑编排层通常有两类一类是纯粹的业务逻辑编排还有一类是其他的应用层协议插件用于特定协议相关的会话和链路管理。例如CMPP 协议,用于管理和中国移动短信系统的对接。
架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心责任链的拦截和 业务 Handler 的编排。因为应用层协议栈往往是开发一次,到处运行,所以实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需关心业务逻辑定制。这种分层的架构设计理念实现了 NIO 框架 各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制。
正是由于 Netty 的分层架构设计非常合理,基于 Netty 的各种应用服务器和协议栈开发才能够如雨后春笋般得到快速发展。 正是由于 Netty 的分层架构设计非常合理,基于 Netty 的各种应用服务器和协议栈开发才能够如雨后春笋般得到快速发展。
## 关键的架构质量属性 ## 关键的架构质量属性
### 性能 ### 性能
影响最终产品的性能因素非常多,其中软件因素如下。 影响最终产品的性能因素非常多,其中软件因素如下。
- 架构不合理导致的性能问题; - 架构不合理导致的性能问题;
- 编码实现不合理导致的性能问题,例如,锁没用好导致的性能瓶颈。 - 编码实现不合理导致的性能问题,例如,锁没用好导致的性能瓶颈。
硬件因素如下。 硬件因素如下。
- 服务器硬件配置太低导致的性能问题; - 服务器硬件配置太低导致的性能问题;
- 带宽、磁盘的 IOPS 等限制导致的 IO操作 性能差; - 带宽、磁盘的 IOPS 等限制导致的 IO 操作 性能差;
- 测试环境被共用导致被测试的软件产品受到影响。 - 测试环境被共用导致被测试的软件产品受到影响。
尽管影响产品性能的因素非常多,但是架构的性能模型合理与否对性能的影响非常大。如果一个产品的架构设计得不好,无论开发如何努力,都很难开发出一个高性能、高可用的软件产品。 尽管影响产品性能的因素非常多,但是架构的性能模型合理与否对性能的影响非常大。如果一个产品的架构设计得不好,无论开发如何努力,都很难开发出一个高性能、高可用的软件产品。
“性能是设计出来的,而不是测试出来的”。下面我们看看 Netty 的架构设计是如何实现高性能的。 “性能是设计出来的,而不是测试出来的”。下面我们看看 Netty 的架构设计是如何实现高性能的。
1. 采用非阻塞的 NIO类库基于 Reactor 模式实现,解决了传统 同步阻塞IO模式 下一个服务端无法平滑地处理线性增长的客户端的问题。
1. 采用非阻塞的 NIO 类库,基于 Reactor 模式实现,解决了传统 同步阻塞 IO 模式 下一个服务端无法平滑地处理线性增长的客户端的问题。
2. TCP 接收和发送缓冲区**使用直接内存代替堆内存,避免了内存复制**,提升了 IO 读取和写入的性能。 2. TCP 接收和发送缓冲区**使用直接内存代替堆内存,避免了内存复制**,提升了 IO 读取和写入的性能。
3. 支持通过内存池的方式循环利用 ByteBuffer避免了频繁创建和销毁 ByteBuffer 带来的性能损耗。 3. 支持通过内存池的方式循环利用 ByteBuffer避免了频繁创建和销毁 ByteBuffer 带来的性能损耗。
4. 可配置的 IO线程数、TCP参数 等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。 4. 可配置的 IO 线程数、TCP 参数 等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。
5. 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。 5. 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。
6. 合理地使用线程安全容器、原子类等,提升系统的并发处理能力。 6. 合理地使用线程安全容器、原子类等,提升系统的并发处理能力。
7. 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的 CPU资源消耗问题。 7. 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的 CPU 资源消耗问题。
8. 通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少了频繁 GC 带来的延时和 CPU损耗。 8. 通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少了频繁 GC 带来的延时和 CPU 损耗。
### 可靠性 ### 可靠性
作为一个高性能的异步通信框架,架构的可靠性是大家选择的另一个重要依据。下面我们看一下 Netty架构 的可靠性设计。
作为一个高性能的异步通信框架,架构的可靠性是大家选择的另一个重要依据。下面我们看一下 Netty 架构 的可靠性设计。
**1、链路有效性检测** **1、链路有效性检测**
由于长连接不需要每次发送消息都创建链路,也不需要在消息交互完成时关闭链路,因此相对于短连接性能更高。对于长连接,一旦链路建立成功便一直维系双方之间的链路,直到系统退出。 由于长连接不需要每次发送消息都创建链路,也不需要在消息交互完成时关闭链路,因此相对于短连接性能更高。对于长连接,一旦链路建立成功便一直维系双方之间的链路,直到系统退出。
为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用周期性心跳的原因是:在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙 Hang住或者遭遇网络闪断、网络单通等通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时瞬间的海量业务冲击会导致消息积压无法发送给对方由于链路的重建需要时间这期间业务会大量失败 (集群或者分布式组网情况会好一些)。为了解决这个问题,需要周期性的 “心跳检测” 对链路进行有效性检查,一旦发生问题,可以及时关闭链路,重建 TCP连接。 为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用周期性心跳的原因是:在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙 Hang 住,或者遭遇网络闪断、网络单通等,通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时,瞬间的海量业务冲击会导致消息积压无法发送给对方,由于链路的重建需要时间,这期间业务会大量失败 (集群或者分布式组网情况会好一些)。为了解决这个问题,需要周期性的 “心跳检测” 对链路进行有效性检查,一旦发生问题,可以及时关闭链路,重建 TCP 连接。
当有业务消息时无须心跳检测可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。为了支持心跳机制Netty 提供了如下两种链路空闲检测机制。 当有业务消息时无须心跳检测可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。为了支持心跳机制Netty 提供了如下两种链路空闲检测机制。
- 读空闲超时机制:当经过 连续的周期 T 没有消息可读时,触发 超时Handler用户可以基于 该读空闲超时Handler 发送心跳消息,进行链路检测,如果连续 N个周期 仍然没有读取到心跳消息,可以主动关闭这条链路。
- 写空闲超时机制:当经过 连续的周期 T 没有消息要发送时,触发 超时Handler用户可以基于 该写空闲超时Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有接收到对方的心跳消息,可以主动关闭这条链路。 - 读空闲超时机制:当经过 连续的周期 T 没有消息可读时,触发 超时 Handler用户可以基于 该读空闲超时 Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有读取到心跳消息,可以主动关闭这条链路。
- 写空闲超时机制:当经过 连续的周期 T 没有消息要发送时,触发 超时 Handler用户可以基于 该写空闲超时 Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有接收到对方的心跳消息,可以主动关闭这条链路。
为了满足不同用户场景的心跳定制Netty 提供了空闲状态检测事件通知机制,用户可以订阅:空闲超时事件、读空闲超时机制、写空闲超时事件,在接收到对应的空闲事件之后,灵活地进行定制。 为了满足不同用户场景的心跳定制Netty 提供了空闲状态检测事件通知机制,用户可以订阅:空闲超时事件、读空闲超时机制、写空闲超时事件,在接收到对应的空闲事件之后,灵活地进行定制。
**2、内存保护机制** **2、内存保护机制**
Netty 提供多种机制对内存进行保护,包括以下几个方面。 Netty 提供多种机制对内存进行保护,包括以下几个方面。
- 通过对象引用计数器对 Netty 的 ByteBuffer 等内置对象进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护。 - 通过对象引用计数器对 Netty 的 ByteBuffer 等内置对象进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护。
- 通过内存池来重用 ByteBuffer节省内存。 - 通过内存池来重用 ByteBuffer节省内存。
- 可设置的内存容量上限,包括 ByteBuffer、线程池线程数等。 - 可设置的内存容量上限,包括 ByteBuffer、线程池线程数等。
### 可定制性 ### 可定制性
Netty 的可定制性主要体现在以下几点。 Netty 的可定制性主要体现在以下几点。
- 责任链模式ChannelPipeline 基于责任链模式开发,便于业务逻辑的拦截、定制和扩展。 - 责任链模式ChannelPipeline 基于责任链模式开发,便于业务逻辑的拦截、定制和扩展。
- 基于接口的开发:关键的类库都提供了接口或者抽象类,如果 Netty 自身的实现无法满足用户的需求,可以由用户自定义实现相关接口。 - 基于接口的开发:关键的类库都提供了接口或者抽象类,如果 Netty 自身的实现无法满足用户的需求,可以由用户自定义实现相关接口。
- 提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象。 - 提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象。
- 提供了大量的系统参数供用户按需设置,增强系统的场景定制性。 - 提供了大量的系统参数供用户按需设置,增强系统的场景定制性。
### 可扩展性 ### 可扩展性
基于 Netty 的 基本NIO框架可以方便地进行应用层协议定制例如HTTP协议栈、Thrift协议栈、FTP协议栈 等。这些扩展不需要修改 Netty 的源码,直接基于 Netty 的二进制类库即可实现协议的扩展和定制。目前,业界存在大量的基于 Netty框架 开发的协议,例如基于 Netty 的 HTTP协议、Dubbo协议、RocketMQ内部私有协议 等。
基于 Netty 的 基本 NIO 框架可以方便地进行应用层协议定制例如HTTP 协议栈、Thrift 协议栈、FTP 协议栈 等。这些扩展不需要修改 Netty 的源码,直接基于 Netty 的二进制类库即可实现协议的扩展和定制。目前,业界存在大量的基于 Netty 框架 开发的协议,例如基于 Netty 的 HTTP 协议、Dubbo 协议、RocketMQ 内部私有协议 等。

@ -1,71 +1,83 @@
作为一个高性能的 NIO通信框架Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO框架 之一Netty 可以完全满足不同领域对高性能通信的需求。本章我们将从架构层对 Netty 的高性能设计和关键代码实现进行剖析,看 Netty 是如何支撑高性能网络通信的。 作为一个高性能的 NIO 通信框架Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO 框架 之一Netty 可以完全满足不同领域对高性能通信的需求。本章我们将从架构层对 Netty 的高性能设计和关键代码实现进行剖析,看 Netty 是如何支撑高性能网络通信的。
## RPC 调用性能模型分析 ## RPC 调用性能模型分析
### 传统 RPC 调用性能差的原因 ### 传统 RPC 调用性能差的原因
**一、网络传输方式问题。** **一、网络传输方式问题。**
传统的 RPC框架 或者基于 RMI 等方式的 远程过程调用 采用了同步阻塞I/O当客户端的并发压力或者网络时延增大之后同步阻塞I/O 会由于频繁的 wait 导致 I/O线程 经常性的阻塞由于线程无法高效的工作I/O 处理能力自然下降。 传统的 RPC 框架 或者基于 RMI 等方式的 远程过程调用 采用了同步阻塞 I/O当客户端的并发压力或者网络时延增大之后同步阻塞 I/O 会由于频繁的 wait 导致 I/O 线程 经常性的阻塞由于线程无法高效的工作I/O 处理能力自然下降。
采用 BIO通信模型 的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,接收到客户端连接之后,为其创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的 “ 一请求,一应答 ” 模型。该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是 Java虛拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。 采用 BIO 通信模型 的服务端,通常由一个独立的 Acceptor 线程 负责监听客户端的连接,接收到客户端连接之后,为其创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的 “ 一请求,一应答 ” 模型。该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是 Java 虛拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。
**二、序列化性能差。** **二、序列化性能差。**
Java序列化 存在如下几个典型问题: Java 序列化 存在如下几个典型问题:
1. Java 序列化机制是 Java 内部的一 种对象编解码技术无法跨语言使用。例如对于异构系统之间的对接Java序列化 后的码流需要能够通过其他语言反序列化成原始对象,这很难支持。
2. 相比于其他开源的序列化框架Java序列化 后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。 1. Java 序列化机制是 Java 内部的一 种对象编解码技术无法跨语言使用。例如对于异构系统之间的对接Java 序列化 后的码流需要能够通过其他语言反序列化成原始对象,这很难支持。
2. 相比于其他开源的序列化框架Java 序列化 后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。
3. 序列化性能差,资源占用率高 ( 主要是 CPU 资源占用高 )。 3. 序列化性能差,资源占用率高 ( 主要是 CPU 资源占用高 )。
**三、线程模型问题。** **三、线程模型问题。**
由于采用 同步阻塞I/O这会导致每个 TCP连接 都占用 1 个线程,由于线程资源是 JVM虚拟机 非常宝贵的资源,当 I/O 读写阻塞导致线程无法及时释放时,会导致系统性能急剧下降,严重的甚至会导致虚拟机无法创建新的线程。 由于采用 同步阻塞 I/O这会导致每个 TCP 连接 都占用 1 个线程,由于线程资源是 JVM 虚拟机 非常宝贵的资源,当 I/O 读写阻塞导致线程无法及时释放时,会导致系统性能急剧下降,严重的甚至会导致虚拟机无法创建新的线程。
### IO 通信性能三原则 ### IO 通信性能三原则
尽管影响 I/O通信性能 的因素非常多,但是从架构层面看主要有三个要素。
1. 传输:用什么样的通道将数据发送给对方。可以选择 BIO、NIO 或者 AIOI/O模型 在很大程度上决定了通信的性能; 尽管影响 I/O 通信性能 的因素非常多,但是从架构层面看主要有三个要素。
2. 协议采用什么样的通信协议HTTP等公有协议或者内部私有协议。协议的选择不同性能也不同。相比于公有协议内部私有协议的性能通常可以被设计得更优
1. 传输:用什么样的通道将数据发送给对方。可以选择 BIO、NIO 或者 AIOI/O 模型 在很大程度上决定了通信的性能;
2. 协议采用什么样的通信协议HTTP 等公有协议或者内部私有协议。协议的选择不同,性能也不同。相比于公有协议,内部私有协议的性能通常可以被设计得更优;
3. 线程模型数据报如何读取读取之后的编解码在哪个线程进行编解码后的消息如何派发Reactor 线程模型的不同,对性能的影响也非常大。 3. 线程模型数据报如何读取读取之后的编解码在哪个线程进行编解码后的消息如何派发Reactor 线程模型的不同,对性能的影响也非常大。
## 异步非阻塞通信 ## 异步非阻塞通信
在 I/O编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O多路复用技术 进行处理。I/O多路复用技术 通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程 / 多进程模型比I/O多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
JDK1.4 提供了对非阻塞 I/O 的支持JDK1.5 使用 epoll 替代了传统的 select / poll极大地提升了 NIO通信 的性能。 在 I/O 编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O 多路复用技术 进行处理。I/O 多路复用技术 通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程 / 多进程模型比I/O 多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
JDK1.4 提供了对非阻塞 I/O 的支持JDK1.5 使用 epoll 替代了传统的 select / poll极大地提升了 NIO 通信 的性能。
与 Socket 和 ServerSocket 类相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择 同步阻塞I/O 以降低编程复杂度。但是对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。 与 Socket 和 ServerSocket 类相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择 同步阻塞 I/O 以降低编程复杂度。但是对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。
Netty 的 I/O 线程 NioEventLoop 由于聚合了 多路复用器Selector可以同时并发处理成百上千个客户端 SocketChannel。由于读写操作都是非阻塞的这就可以充分提升 I/O线程 的运行效率,避免由频繁的 I/O阻塞 导致的线程挂起。另外,由于 Netty 采用了异步通信模式,一个 I/O线程 可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统 同步阻塞I/O “ 一连接,一线程 ” 模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 Netty 的 I/O 线程 NioEventLoop 由于聚合了 多路复用器 Selector可以同时并发处理成百上千个客户端 SocketChannel。由于读写操作都是非阻塞的这就可以充分提升 I/O 线程 的运行效率,避免由频繁的 I/O 阻塞 导致的线程挂起。另外,由于 Netty 采用了异步通信模式,一个 I/O 线程 可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统 同步阻塞 I/O “ 一连接,一线程 ” 模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
## 高效的 Reactor 线程模型 ## 高效的 Reactor 线程模型
常用的 Reactor线程模型 有三种,分别如下。
1. Reactor单线程模型 常用的 Reactor 线程模型 有三种,分别如下。
1. Reactor 单线程模型;
2. Reactor 多线程模型; 2. Reactor 多线程模型;
3. 主从Reactor多线程模型。 3. 主从 Reactor 多线程模型。
Reactor 单线程模型,指的是所有的 I/O 操作 都在同一个 NIO 线程 上面完成NIO 线程 的职责如下:
Reactor单线程模型指的是所有的 I/O操作 都在同一个 NIO线程 上面完成NIO线程 的职责如下: 1. 作为 NIO 服务端,接收客户端的 TCP 连接;
1. 作为 NIO服务端接收客户端的 TCP连接 2. 作为 NIO 客户端,向服务端发起 TCP 连接;
2. 作为 NIO客户端向服务端发起 TCP连接
3. 读取通信对端的请求或者应答消息; 3. 读取通信对端的请求或者应答消息;
4. 向通信对端发送消息请求或者应答消息。 4. 向通信对端发送消息请求或者应答消息。
由于 Reactor模式 使用的是 异步非阻塞I/O所有的 I/O操作 都不会导致阻塞,理论上一个线程可以独立处理所有 I/O 相关的操作。从架构层面看,一个 NIO线程 确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP连接请求消息链路建立成功之后通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户Handler 可以通过 NIO线程 将消息发送给客户端。 由于 Reactor 模式 使用的是 异步非阻塞 I/O所有的 I/O 操作 都不会导致阻塞,理论上一个线程可以独立处理所有 I/O 相关的操作。从架构层面看,一个 NIO 线程 确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户 Handler 可以通过 NIO 线程 将消息发送给客户端。
对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下。 对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下。
1. 一个 NIO线程 同时处理成百上千的链路,性能上无法支撑。即便 NIO线程 的 CPU负荷 达到 100%,也无法满足海量消息的编码,解码、读取和发送;
2. 当 NIO线程 负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO线程 的负载最终会导致大量消息积压和处理超时NIO线程 会成为系统的性能瓶颈;
3. 可靠性问题。一旦 NIO线程 意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
为了解决这些问题,演进出了 Reactor多线程模型下面我们看一下 Reactor多线程模型。 1. 一个 NIO 线程 同时处理成百上千的链路,性能上无法支撑。即便 NIO 线程 的 CPU 负荷 达到 100%,也无法满足海量消息的编码,解码、读取和发送;
2. 当 NIO 线程 负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程 的负载最终会导致大量消息积压和处理超时NIO 线程 会成为系统的性能瓶颈;
3. 可靠性问题。一旦 NIO 线程 意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
为了解决这些问题,演进出了 Reactor 多线程模型,下面我们看一下 Reactor 多线程模型。
Rector 多线程模型 与单线程模型最大的区别就是有一组 NIO 线程 处理 I/O 操作,它的特点如下。
Rector多线程模型 与单线程模型最大的区别就是有一组 NIO线程 处理 I/O操作它的特点如下。 1. 有一个专门的 NIO 线程 —— Acceptor 线程 用于监听服务端口,接收客户端的 TCP 连接请求;
1. 有一个专门的 NIO线程 —— Acceptor线程 用于监听服务端口,接收客户端的 TCP连接请求 2. 网络 IO 操作 —— 读、写等由一个 NIO 线程池 负责,线程池可以采用标准的 JDK 线程池 实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程 负责消息的读取、解码、编码和发送;
2. 网络IO操作 —— 读、写等由一个 NIO线程池 负责,线程池可以采用标准的 JDK线程池 实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO线程 负责消息的读取、解码、编码和发送; 3. 1 个 NIO 线程 可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,以防止发生并发操作问题。
3. 1 个 NIO线程 可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO线程以防止发生并发操作问题。
在绝大多数场景下Reactor多线程模型 都可以满足性能需求,但是,在极特殊应用场景中,一个 NIO线程 负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手消息进行安全认证,认证本身非常损耗性能。在这类场景下,单独一个 Acceptor线程 可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor线程模型 —— 主从Reactor多线程模型。 在绝大多数场景下Reactor 多线程模型 都可以满足性能需求,但是,在极特殊应用场景中,一个 NIO 线程 负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手消息进行安全认证,认证本身非常损耗性能。在这类场景下,单独一个 Acceptor 线程 可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor 线程模型 —— 主从 Reactor 多线程模型。
主从Reactor线程模型 的特点是服务端用于接收客户端连接的不再是个单线程的连接处理Acceptor而是一个独立的 Acceptor线程池。Acceptor 接收到客户端 TCP连接请求 处理完成后 ( 可能包含接入认证等 ),将新创建的 SocketChannel 注册到 I/O处理线程池 的某个I/O线程 上,由它负责 SocketChannel 的读写和编解码工作。Acceptor线程池 只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到 I/O处理线程池的 I/O线程 上,每个 I/O线程 可以同时监听 N 个链路,对链路产生的 IO事件 进行相应的 消息读取、解码、编码及消息发送等操作。 主从 Reactor 线程模型 的特点是,服务端用于接收客户端连接的不再是个单线程的连接处理 Acceptor而是一个独立的 Acceptor 线程池。Acceptor 接收到客户端 TCP 连接请求 处理完成后 ( 可能包含接入认证等 ),将新创建的 SocketChannel 注册到 I/O 处理线程池 的某个 I/O 线程 上,由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池 只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到 I/O 处理线程池的 I/O 线程 上,每个 I/O 线程 可以同时监听 N 个链路,对链路产生的 IO 事件 进行相应的 消息读取、解码、编码及消息发送等操作。
利用 主从Reactor线程模型可以解决 1 个 Acceptor线程 无法有效处理所有客户端连接的性能问题。因此Netty官方 也推荐使用该线程模型。 利用 主从 Reactor 线程模型,可以解决 1 个 Acceptor 线程 无法有效处理所有客户端连接的性能问题。因此Netty 官方 也推荐使用该线程模型。
事实上Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup实例 并进行适当的参数配置,就可以支持上述三种 Reactor线程模型。可以根据业务场景的性能诉求选择不同的线程模型。 事实上Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup 实例 并进行适当的参数配置,就可以支持上述三种 Reactor 线程模型。可以根据业务场景的性能诉求,选择不同的线程模型。
Netty 单线程模型 服务端代码示例如下。 Netty 单线程模型 服务端代码示例如下。
```java ```java
EventLoopGroup reactor = new NioEventLoopGroup(1); EventLoopGroup reactor = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap(); ServerBootstrap bootstrap = new ServerBootstrap();
@ -75,6 +87,7 @@ Netty 单线程模型 服务端代码示例如下。
``` ```
Netty 多线程模型 代码示例如下。. Netty 多线程模型 代码示例如下。.
```java ```java
EventLoopGroup acceptor = new NioEventLoopGroup(1); EventLoopGroup acceptor = new NioEventLoopGroup(1);
EventLoopGroup ioGroup = new NioEventLoopGroup(); EventLoopGroup ioGroup = new NioEventLoopGroup();
@ -83,7 +96,9 @@ Netty 多线程模型 代码示例如下。.
.channel(NioServerSocketChannel.class) .channel(NioServerSocketChannel.class)
...... ......
``` ```
Netty 主从多线程模型 代码示例如下。 Netty 主从多线程模型 代码示例如下。
```java ```java
EventLoopGroup acceptorGroup = new NioEventLoopGroup(); EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup ioGroup = new NioEventLoopGroup(); EventLoopGroup ioGroup = new NioEventLoopGroup();
@ -94,48 +109,60 @@ Netty 主从多线程模型 代码示例如下。
``` ```
## 无锁化的串行设计 ## 无锁化的串行设计
在大多数场景下,并行多线程处理可以提升系统的并发性能。但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会导致性能的下降。为了尽可能地避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。 在大多数场景下,并行多线程处理可以提升系统的并发性能。但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会导致性能的下降。为了尽可能地避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。
为了尽可能提升性能Netty 对消息的处理 采用了串行无锁化设计,在 I/O线程 内部进行串行操作避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。 为了尽可能提升性能Netty 对消息的处理 采用了串行无锁化设计,在 I/O 线程 内部进行串行操作避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。
![avatar](../../../images/Netty/Netty串行化设计工作原理.png) ![avatar](../../../images/Netty/Netty串行化设计工作原理.png)
Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的Handler期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争从性能角度看是最优的。 Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的 Handler期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争从性能角度看是最优的。
## 零拷贝 ## 零拷贝
Netty的 “ 零拷贝 ” 主要体现在如下三个方面。
第一种情况。Netty 的接收和发送 ByteBuffer 采用 堆外直接内存 (DIRECT BUFFERS) 进行 Socket读写不需要进行字节缓冲区的二次拷贝。如果使用传统的 堆内存(HEAP BUFFERS) 进行 Socket读写JVM 会将 堆内存Buffer 拷贝一份到 直接内存 中,然后才写入 Socket。相比于堆外直接内存消息在发送过程中多了一次缓冲区的内存拷贝 Netty 的 “ 零拷贝 ” 主要体现在如下三个方面
下面我们继续看第二种 “ 零拷贝 ” 的实现 CompositeByteBuf它对外将多个 ByteBuf 封装成一个 ByteBuf对外提供统一封装后的 ByteBuf接口。CompositeByteBuf 实际就是个 ByteBuf 的装饰器,它将多个 ByteBuf 组合成一个集合,然后对外提供统一的 ByteBuf接口添加 ByteBuf不需要做内存拷贝。 第一种情况。Netty 的接收和发送 ByteBuffer 采用 堆外直接内存 (DIRECT BUFFERS) 进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的 堆内存(HEAP BUFFERS) 进行 Socket 读写JVM 会将 堆内存 Buffer 拷贝一份到 直接内存 中,然后才写入 Socket。相比于堆外直接内存消息在发送过程中多了一次缓冲区的内存拷贝。
下面我们继续看第二种 “ 零拷贝 ” 的实现 CompositeByteBuf它对外将多个 ByteBuf 封装成一个 ByteBuf对外提供统一封装后的 ByteBuf 接口。CompositeByteBuf 实际就是个 ByteBuf 的装饰器,它将多个 ByteBuf 组合成一个集合,然后对外提供统一的 ByteBuf 接口,添加 ByteBuf不需要做内存拷贝。
第三种 “ 零拷贝 ” 就是文件传输Netty 文件传输类 DefaultFileRegion 通过 transferTo()方法 将文件发送到目标 Channel 中。很多操作系统直接将文件缓冲区的内容发送到目标 Channel 中,而不需要通过循环拷贝的方式,这是一种更加高效的传输方式,提升了传输性能,降低了 CPU 和内存占用,实现了文件传输的 “ 零拷贝 ” 。 第三种 “ 零拷贝 ” 就是文件传输Netty 文件传输类 DefaultFileRegion 通过 transferTo()方法 将文件发送到目标 Channel 中。很多操作系统直接将文件缓冲区的内容发送到目标 Channel 中,而不需要通过循环拷贝的方式,这是一种更加高效的传输方式,提升了传输性能,降低了 CPU 和内存占用,实现了文件传输的 “ 零拷贝 ” 。
## 内存池 ## 内存池
随着 JVM虚拟机 和 JIT即时编译技术 的发展,对象的分配和回收是个非常轻量级的工作。但是对于 缓冲区Buffer情况却稍有不同特别是对于堆外直接内存的分配和回收是一件耗时的操作。为了尽量重用缓冲区Netty 提供了基于内存池的缓冲区重用机制。ByteBuf 的子类中提供了多种 PooledByteBuf 的实现,基于这些实现 Netty 提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。
随着 JVM 虚拟机 和 JIT 即时编译技术 的发展,对象的分配和回收是个非常轻量级的工作。但是对于 缓冲区 Buffer情况却稍有不同特别是对于堆外直接内存的分配和回收是一件耗时的操作。为了尽量重用缓冲区Netty 提供了基于内存池的缓冲区重用机制。ByteBuf 的子类中提供了多种 PooledByteBuf 的实现,基于这些实现 Netty 提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。
## Socket 与 SocketChannel ## Socket 与 SocketChannel
网络由下往上分为 物理层、数据链路层、网络层、传输层和应用层。IP协议 对应于网络层TCP协议 对应于传输层,而 HTTP协议 对应于应用层三者从本质上来说没有可比性Socket 则是对 TCP/IP协议 的封装和应用 (程序员层面上)。也可以说TPC/IP协议 是传输层协议,主要解决数据如何在网络中传输,而 HTTP 是应用层协议主要解决如何包装数据。Socket 是对 TCP/IP协议 的封装Socket 本身并不是协议,而是一个 调用接口(API)。 通过 Socket我们才能使用 TCP/IP协议。
网络由下往上分为 物理层、数据链路层、网络层、传输层和应用层。IP 协议 对应于网络层TCP 协议 对应于传输层,而 HTTP 协议 对应于应用层三者从本质上来说没有可比性Socket 则是对 TCP/IP 协议 的封装和应用 (程序员层面上)。也可以说TPC/IP 协议 是传输层协议,主要解决数据如何在网络中传输,而 HTTP 是应用层协议主要解决如何包装数据。Socket 是对 TCP/IP 协议 的封装Socket 本身并不是协议,而是一个 调用接口(API)。 通过 Socket我们才能使用 TCP/IP 协议。
### 一、利用 Socket 建立网络连接的步骤 ### 一、利用 Socket 建立网络连接的步骤
建立 Socket连接 至少需要一对套接字,其中一个运行于客户端,称为 clientSocket ,另一个运行于服务器端,称为 serverSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
建立 Socket 连接 至少需要一对套接字,其中一个运行于客户端,称为 clientSocket ,另一个运行于服务器端,称为 serverSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1. 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。 1. 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2. 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。 2. 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3. 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给 客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。 3. 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给 客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
### 二、HTTP连接 的特点
  HTTP协议 是 Web联网 的基础也是手机联网常用的协议之一HTTP协议 是建立在 TCP协议 之上的一种应用。HTTP连接 最显著的特点是客户端发送的每次请求 都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为 “一次连接”。 ### 二、HTTP 连接 的特点
HTTP 协议 是 Web 联网 的基础也是手机联网常用的协议之一HTTP 协议 是建立在 TCP 协议 之上的一种应用。HTTP 连接 最显著的特点是客户端发送的每次请求 都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为 “一次连接”。
### 三、TCP 和 UDP 的区别 ### 三、TCP 和 UDP 的区别
1. TCP 是面向连接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但 TCP 的三次握手在很大程度上 保证了连接的可靠性。而 UDP 不是面向连接的UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说 UDP 是无连接的、不可靠的一种数据传输协议。 1. TCP 是面向连接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但 TCP 的三次握手在很大程度上 保证了连接的可靠性。而 UDP 不是面向连接的UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说 UDP 是无连接的、不可靠的一种数据传输协议。
2. 也正由于 1 所说的特点,使得 UDP 的开销更小,数据传输速率更高,因为不必进行收发数据的确认,所以 UDP 的实时性更好。 2. 也正由于 1 所说的特点,使得 UDP 的开销更小,数据传输速率更高,因为不必进行收发数据的确认,所以 UDP 的实时性更好。
### 四、Socket 与 SocketChannel 有什么区别 ### 四、Socket 与 SocketChannel 有什么区别
Socket、SocketChannel 二者的实质都是一样的,都是为了实现客户端与服务器端的连接而存在的,但是在使用上却有很大的区别。具体如下: Socket、SocketChannel 二者的实质都是一样的,都是为了实现客户端与服务器端的连接而存在的,但是在使用上却有很大的区别。具体如下:
1. 所属包不同。Socket 在 java.net包 中,而 SocketChannel 在 java.nio包 中。
2. 异步方式不同。从包的不同我们大体可以推断出他们主要的区别Socket 是阻塞连接SocketChannel 可以设置为非阻塞连接。使用 ServerSocket 与 Socket 的搭配服务端Socket 往往要为每一个 客户端Socket 分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中。过多的线程也会影响服务器的性能。而使用 SocketChannel 与 ServerSocketChannel 的搭配可以非阻塞通信,这样使得服务器端只需要一个线程就能处理所有 客户端Socket 的请求。 1. 所属包不同。Socket 在 java.net 包 中,而 SocketChannel 在 java.nio 包 中。
2. 异步方式不同。从包的不同我们大体可以推断出他们主要的区别Socket 是阻塞连接SocketChannel 可以设置为非阻塞连接。使用 ServerSocket 与 Socket 的搭配,服务端 Socket 往往要为每一个 客户端 Socket 分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中。过多的线程也会影响服务器的性能。而使用 SocketChannel 与 ServerSocketChannel 的搭配可以非阻塞通信,这样使得服务器端只需要一个线程就能处理所有 客户端 Socket 的请求。
3. 性能不同。一般来说,高并发场景下,使用 SocketChannel 与 ServerSocketChannel 的搭配会有更好的性能。 3. 性能不同。一般来说,高并发场景下,使用 SocketChannel 与 ServerSocketChannel 的搭配会有更好的性能。
4. 使用方式不同。Socket、ServerSocket类 可以传入不同参数直接实例化对象并绑定 IP 和 端口。而 SocketChannel、ServerSocketChannel类 需要借助 Selector类。 4. 使用方式不同。Socket、ServerSocket 类 可以传入不同参数直接实例化对象并绑定 IP 和 端口。而 SocketChannel、ServerSocketChannel 类 需要借助 Selector 类。
下面是 SocketChannel方式 需要用到的几个核心类: 下面是 SocketChannel 方式 需要用到的几个核心类:
ServerSocketChannelServerSocket 的替代类, 支持阻塞通信与非阻塞通信。 ServerSocketChannelServerSocket 的替代类, 支持阻塞通信与非阻塞通信。
@ -143,7 +170,8 @@ SocketChannelSocket 的替代类, 支持阻塞通信与非阻塞通信。
Selector为 ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器读就绪和写就绪事件。 Selector为 ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器读就绪和写就绪事件。
SelectionKey代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄。当一个 SelectionKey对象 位于 Selector对象 的 selected-keys集合 中时,就表示与这个 SelectionKey对象 相关的事件发生了。在 SelectionKey类 中有如下几个静态常量: SelectionKey代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄。当一个 SelectionKey 对象 位于 Selector 对象 的 selected-keys 集合 中时,就表示与这个 SelectionKey 对象 相关的事件发生了。在 SelectionKey 类 中有如下几个静态常量:
- SelectionKey.OP_ACCEPT客户端连接就绪事件等于监听 serverSocket.accept(),返回一个 socket。 - SelectionKey.OP_ACCEPT客户端连接就绪事件等于监听 serverSocket.accept(),返回一个 socket。
- SelectionKey.OP_CONNECT准备连接服务器就绪跟上面类似只不过是对于 socket 的,相当于监听了 socket.connect()。 - SelectionKey.OP_CONNECT准备连接服务器就绪跟上面类似只不过是对于 socket 的,相当于监听了 socket.connect()。
- SelectionKey.OP_READ读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作了。 - SelectionKey.OP_READ读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作了。

@ -1,57 +1,68 @@
## Linux 网络 IO 模型简介 ## Linux 网络 IO 模型简介
Linux 的内核将所有外部设备都看做一个文件来操作对一个文件的读写操作会调用内核提供的系统命令返回一个fd (file descriptor文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。根据UNIX网络编程对 I/O模型 的分类UNIX 提供了5种 I/O模型分别如下。
#### 1、阻塞IO模型 Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个 fd (file descriptor文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。根据 UNIX 网络编程对 I/O 模型 的分类UNIX 提供了 5 种 I/O 模型,分别如下。
#### 1、阻塞 IO 模型
在内核将数据准备好之前系统调用会一直等待所有的套接字Socket传来数据默认的是阻塞方式。 在内核将数据准备好之前系统调用会一直等待所有的套接字Socket传来数据默认的是阻塞方式。
![avatar](../../../images/Netty/阻塞IO模型.png) ![avatar](../../../images/Netty/阻塞IO模型.png)
Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom方法OS 会判断来自网络的数据报是否准备好当数据报准备好了之后OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。 Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom 方法OS 会判断来自网络的数据报是否准备好当数据报准备好了之后OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。
BIO 中的阻塞,就是阻塞在 2 个地方:
BIO中的阻塞就是阻塞在2个地方
1. OS 等待数据报通过网络发送过来,如果建立连接后数据一直没过来,就会白白浪费线程的资源; 1. OS 等待数据报通过网络发送过来,如果建立连接后数据一直没过来,就会白白浪费线程的资源;
2. 将数据从内核空间拷贝到用户空间。 2. 将数据从内核空间拷贝到用户空间。
在这2个时候我们的线程会一直被阻塞啥事情都不干。 在这 2 个时候,我们的线程会一直被阻塞,啥事情都不干。
#### 2、非阻塞IO模型
#### 2、非阻塞 IO 模型
![avatar](../../../images/Netty/非阻塞IO模型.png) ![avatar](../../../images/Netty/非阻塞IO模型.png)
每次应用程序询问内核是否有数据报准备好当有数据报准备好时就进行拷贝数据报的操作从内核拷贝到用户空间和拷贝完成返回的这段时间应用进程是阻塞的。但在没有数据报准备好时并不会阻塞程序内核直接返回未准备好的信号等待应用进程的下一次询问。但是轮寻对于CPU来说是较大的浪费一般只有在特定的场景下才使用。 每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。
从图中可以看到非阻塞IO 的 recvfrom调用 会立即得到一个返回结果(数据报是否准备好)我们可以根据返回结果继续执行不同的逻辑。而阻塞IO 的recvfrom调用如果无数据报准备好一定会被阻塞住。虽然 非阻塞IO 比 阻塞IO 少了一段阻塞的过程,但事实上 非阻塞IO 这种方式也是低效的,因为我们不得不使用轮询方法区一直问 OS“我的数据好了没啊”。 从图中可以看到,非阻塞 IO 的 recvfrom 调用 会立即得到一个返回结果(数据报是否准备好),我们可以根据返回结果继续执行不同的逻辑。而阻塞 IO 的 recvfrom 调用,如果无数据报准备好,一定会被阻塞住。虽然 非阻塞 IO 比 阻塞 IO 少了一段阻塞的过程,但事实上 非阻塞 IO 这种方式也是低效的,因为我们不得不使用轮询方法区一直问 OS“我的数据好了没啊”。
**BIO 不会在 拷贝数据之前 阻塞但会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方Non-Blocking 还是会阻塞的。** **BIO 不会在 拷贝数据之前 阻塞但会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方Non-Blocking 还是会阻塞的。**
#### 3、IO复用模型
Linux 提供 select/poll进程通过将一个或多个 fd 传递给 select 或 poll系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限因此它的使用也受到了一些制约。Linux 还提供了一个 epoll系统调用epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。 #### 3、IO 复用模型
Linux 提供 select/poll进程通过将一个或多个 fd 传递给 select 或 poll 系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限因此它的使用也受到了一些制约。Linux 还提供了一个 epoll 系统调用epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。
![avatar](../../../images/Netty/IO复用模型.png) ![avatar](../../../images/Netty/IO复用模型.png)
#### 4、信号驱动IO模型 #### 4、信号驱动 IO 模型
首先开启套接口信号驱动IO功能并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO信号通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。
首先开启套接口信号驱动 IO 功能,并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。
![avatar](../../../images/Netty/信号驱动IO模型.png) ![avatar](../../../images/Netty/信号驱动IO模型.png)
#### 5、异步IO模型 #### 5、异步 IO 模型
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是信号驱动IO 由内核通知我们何时可以开始一个 IO 操作异步IO模型 由内核通知我们 IO操作何时已经完成。
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动 IO 由内核通知我们何时可以开始一个 IO 操作;异步 IO 模型 由内核通知我们 IO 操作何时已经完成。
![avatar](../../../images/Netty/异步IO模型.png) ![avatar](../../../images/Netty/异步IO模型.png)
从这五种 IO模型的结构 也可以看出阻塞程度阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO效率是由低到高的。 从这五种 IO 模型的结构 也可以看出,阻塞程度:阻塞 IO>非阻塞 IO>多路转接 IO>信号驱动 IO>异步 IO效率是由低到高的。
最后我们看一下数据从客户端到服务器再由服务器返回结果数据的整体IO流程以便我们更好地理解上述的IO模型。 最后,我们看一下数据从客户端到服务器,再由服务器返回结果数据的整体 IO 流程,以便我们更好地理解上述的 IO 模型。
![avatar](../../../images/Netty/数据在客户端及服务器之间的整体IO流程.png) ![avatar](../../../images/Netty/数据在客户端及服务器之间的整体IO流程.png)
## IO 多路复用技术 ## IO 多路复用技术
Java NIO 的核心类库中 多路复用器Selector 就是基于 epoll 的多路复用技术实现。
在 IO编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO多路复用技术 进行处理。IO多路复用技术 通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比IO多路复用 的最大优势是系统开销小系统不需要创建新的额外进程或线程也不需要维护这些进程和线程的运行降低了系统的维护工作量节省了系统资源IO多路复用 的主要应用场景如下。 Java NIO 的核心类库中 多路复用器 Selector 就是基于 epoll 的多路复用技术实现。
在 IO 编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO 多路复用技术 进行处理。IO 多路复用技术 通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比IO 多路复用 的最大优势是系统开销小系统不需要创建新的额外进程或线程也不需要维护这些进程和线程的运行降低了系统的维护工作量节省了系统资源IO 多路复用 的主要应用场景如下。
- 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字; - 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;
- 服务器需要同时处理多种网络协议的套接字。 - 服务器需要同时处理多种网络协议的套接字。
目前支持 IO多路复用 的系统调用有 select、pselect、poll、epoll在 Linux网络编程 过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点epoll 作了很多重大改进,现总结如下。 目前支持 IO 多路复用 的系统调用有 select、pselect、poll、epoll在 Linux 网络编程 过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点epoll 作了很多重大改进,现总结如下。
1. 支持一个进程打开的 socket描述符 (fd) 不受限制(仅受限于操作系统的最大文件句柄数)
2. IO效率 不会随着 FD 数目的增加而线性下降; 1. 支持一个进程打开的 socket 描述符 (fd) 不受限制(仅受限于操作系统的最大文件句柄数)
3. epoll的API更加简单。 2. IO 效率 不会随着 FD 数目的增加而线性下降;
3. epoll 的 API 更加简单。
值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。 值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。

@ -1,10 +1,13 @@
Selector、SelectionKey和Channel 这三个组件构成了Java nio包的核心也是Reactor模型在代码层面的体现。Selector能让单线程同时处理多个客户端Channel非常适用于高并发传输数据量较小的场景。要使用Selector首先要将对应的Channel及IO事件读、写、连接注册到Selector注册后会产生一个SelectionKey对象用于关联Selector和Channel及后续的IO事件处理。这三者的关系如下图所示。 Selector、SelectionKey Channel 这三个组件构成了 Java nio 包的核心,也是 Reactor 模型在代码层面的体现。Selector 能让单线程同时处理多个客户端 Channel非常适用于高并发传输数据量较小的场景。要使用 Selector首先要将对应的 Channel IO 事件(读、写、连接)注册到 Selector注册后会产生一个 SelectionKey 对象,用于关联 Selector Channel及后续的 IO 事件处理。这三者的关系如下图所示。
![在这里插入图片描述](../../../images/Netty/Selector和SelectionKey和Channel关系图.png) ![在这里插入图片描述](../../../images/Netty/Selector和SelectionKey和Channel关系图.png)
对nio编程不熟的同学可以搜索一些简单的demo跑一下下面 我们直接进入源码窥探一些nio的奥秘。 对 nio 编程不熟的同学可以搜索一些简单的 demo 跑一下,下面 我们直接进入源码,窥探一些 nio 的奥秘。
### Selector ### Selector
其实,不管是 Selector 还是 SelectionKey 的源码,其具体实现类都是依赖于底层操作系统的,这里我们只看一下抽象类 Selector 的源码,日后有事件,再找一些具体的实现类深入分析一下。 其实,不管是 Selector 还是 SelectionKey 的源码,其具体实现类都是依赖于底层操作系统的,这里我们只看一下抽象类 Selector 的源码,日后有事件,再找一些具体的实现类深入分析一下。
```java ```java
public abstract class Selector implements Closeable { public abstract class Selector implements Closeable {
@ -57,7 +60,9 @@ public abstract class Selector implements Closeable {
``` ```
### SelectionKey ### SelectionKey
表示 SelectableChannel 在 Selector 中的注册的标记 / 句柄。 表示 SelectableChannel 在 Selector 中的注册的标记 / 句柄。
```java ```java
public abstract class SelectionKey { public abstract class SelectionKey {
@ -150,12 +155,15 @@ public abstract class SelectionKey {
} }
} }
``` ```
### Channel组件
平时编码用的比较多的就是 SocketChannel 和 ServerSocketChannel而将 Channel 与 Selecor 关联到一起的核心API则定义在它们的公共父类SelectableChannel中整个Channel组件的核心类图如下所示。 ### Channel 组件
平时编码用的比较多的就是 SocketChannel 和 ServerSocketChannel而将 Channel 与 Selecor 关联到一起的核心 API 则定义在它们的公共父类 SelectableChannel 中,整个 Channel 组件的核心类图如下所示。
![在这里插入图片描述](../../../images/Netty/Channel组件.png) ![在这里插入图片描述](../../../images/Netty/Channel组件.png)
#### SelectableChannel #### SelectableChannel
```java ```java
public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel {
@ -193,7 +201,9 @@ public abstract class SelectableChannel extends AbstractInterruptibleChannel imp
``` ```
#### ServerSocketChannel #### ServerSocketChannel
相当于 BIO 中的 ServerSocket主要用于服务端与客户端建立连接通信的channel。
相当于 BIO 中的 ServerSocket主要用于服务端与客户端建立连接通信的 channel。
```java ```java
public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel { public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel {
@ -225,8 +235,11 @@ public abstract class ServerSocketChannel extends AbstractSelectableChannel impl
public abstract SocketChannel accept() throws IOException; public abstract SocketChannel accept() throws IOException;
} }
``` ```
#### SocketChannel #### SocketChannel
相当于 BIO 中的 Socket主要用于通信双方的读写操作。 相当于 BIO 中的 Socket主要用于通信双方的读写操作。
```java ```java
public abstract class SocketChannel extends AbstractSelectableChannel public abstract class SocketChannel extends AbstractSelectableChannel
implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel { implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel {

@ -1,30 +1,36 @@
## 传统的BIO编程 ## 传统的 BIO 编程
网络编程的基本模型是 Client/Server 模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket) 进行通信。
在基于传统同步阻塞模型开发中ServerSocket 负责绑定IP 地址启动监听端口Socket负责发起连接操作。连接成功之后双方通过输入和输出流进行同步阻塞式通信。 网络编程的基本模型是 Client/Server 模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的 IP 地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket) 进行通信。
### BIO通信模型 在基于传统同步阻塞模型开发中ServerSocket 负责绑定 IP 地址启动监听端口Socket 负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。
通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,它接收到客户
### BIO 通信模型
通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程 负责监听客户端的连接,它接收到客户
端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。 端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。
![avatar](../../../images/Netty/BIO通信模型.png) ![avatar](../../../images/Netty/BIO通信模型.png)
该模型最大的问题就是缺乏弹性伸缩能力当客户端并发访问量增加后服务端的线程个数和客户端并发访问数呈1: 1的正比关系由于线程是 Java虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。 该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈 1: 1 的正比关系,由于线程是 Java 虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。为了改进 一线程一连接 模型,后来又演进出了一种通过线程池或者消息队列实现 1 个或者多个线程处理 N 个客户端的模型,由于它的底层通信机制依然使用 同步阻塞 IO所以被称为 “伪异步”。
在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。为了改进 一线程一连接 模型后来又演进出了一种通过线程池或者消息队列实现1个或者多个线程处理N个客户端的模型由于它的底层通信机制依然使用 同步阻塞IO所以被称为 “伪异步”。 ## 伪异步 IO 编程
## 伪异步IO编程 为了解决 同步阻塞 IO 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成 客户端个数 M线程池最大线程数 N 的比例关系,其中 M 可以远远大于 N。通过线程池可以灵活地调配线程资源设置线程的最大值防止由于海量并发接入导致线程耗尽。
为了解决 同步阻塞IO 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成 客户端个数M线程池最大线程数N 的比例关系,其中 M 可以远远大于 N。通过线程池可以灵活地调配线程资源设置线程的最大值防止由于海量并发接入导致线程耗尽。
### 伪异步IO模型图 ### 伪异步 IO 模型图
采用线程池和任务队列可以实现一种叫做 伪异步的IO通信框架其模型图下。当有新的客户端接入时将客户端的 Socket 封装成一个 Task对象 (该类实现了java.lang.Runnable接口)投递到后端的线程池中进行处理JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
采用线程池和任务队列可以实现一种叫做 伪异步的 IO 通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task 对象 (该类实现了 java.lang.Runnable 接口)投递到后端的线程池中进行处理JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
![avatar](../../../images/Netty/伪异步IO通信模型.png) ![avatar](../../../images/Netty/伪异步IO通信模型.png)
伪异步 IO通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。 伪异步 IO 通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。
### 伪异步 IO 编程弊端分析
要对 伪异步 IO 编程 的弊端进行深入分析,首先我们看两个 Java 同步 IO 的 API 说明,随后结合代码进行详细分析。
### 伪异步IO编程弊端分析
要对 伪异步IO编程 的弊端进行深入分析,首先我们看两个 Java同步IO 的 API说明随后结合代码进行详细分析。
```java ```java
public abstract class InputStream implements Closeable { public abstract class InputStream implements Closeable {
@ -45,15 +51,17 @@ public abstract class InputStream implements Closeable {
public abstract int read() throws IOException; public abstract int read() throws IOException;
} }
``` ```
注意其中的一句话 **“This method blocks until input data is available, the end of the stream is detected, or an exception is thrown”**,当对 Socket 的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件。 注意其中的一句话 **“This method blocks until input data is available, the end of the stream is detected, or an exception is thrown”**,当对 Socket 的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件。
- 有数据可读; - 有数据可读;
- 可用数据已经读取完毕; - 可用数据已经读取完毕;
- 发生空指针或者 IO异常。 - 发生空指针或者 IO 异常。
这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞,如果对方要 60s 才能够将数据发送完成,读取一方的 IO线程 也将会被同步阻塞 60s在此期间其他接入消息只能在消息队列中排队。 这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞,如果对方要 60s 才能够将数据发送完成,读取一方的 IO 线程 也将会被同步阻塞 60s在此期间其他接入消息只能在消息队列中排队。
下面我们接着对输出流进行分析,还是看 JDK IO 类库 输出流的 API 文档,然后结合文档说明进行故障分析。
下面我们接着对输出流进行分析,还是看 JDK IO类库 输出流的 API文档然后结合文档说明进行故障分析。
```java ```java
public abstract class OutputStream implements Closeable, Flushable { public abstract class OutputStream implements Closeable, Flushable {
@ -69,71 +77,83 @@ public abstract class OutputStream implements Closeable, Flushable {
} }
} }
``` ```
当调用 OutputStream 的 write()方法 写输出流的时候,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。学习过 TCP/IP 相关知识的人都知道,当消息的接收方处理缓慢的时候,将不能及时地从 TCP缓冲区 读取数据,这将会导致发送方的 TCP window size 不断减小,直到为 0双方处于 Keep-Alive状态消息发送方将不能再向 TCP缓冲区 写入消息,这时如果采用的是 同步阻塞IOwrite操作 将会被无限期阻塞,直到 TCP window size 大于0 或者发生 IO异常。
通过对输入和输出流的 API文档 进行分析,我们了解到读和写操作都是同步阻塞的,阻塞的时间取决于对方 IO线程 的处理速度和 网络IO 的传输速度。本质上来讲,我们无法保证生产环境的网络状况和对方的应用程序能足够快,如果我们的应用程序依赖对方的处理速度,它的可靠性就非常差。也许在实验室进行的性能测试结果令人满意,但是一旦上线运行,面对恶劣的网络环境和良莠不齐的第三方系统,问题就会如火山一样喷发。 当调用 OutputStream 的 write()方法 写输出流的时候,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。学习过 TCP/IP 相关知识的人都知道,当消息的接收方处理缓慢的时候,将不能及时地从 TCP 缓冲区 读取数据,这将会导致发送方的 TCP window size 不断减小,直到为 0双方处于 Keep-Alive 状态,消息发送方将不能再向 TCP 缓冲区 写入消息,这时如果采用的是 同步阻塞 IOwrite 操作 将会被无限期阻塞,直到 TCP window size 大于 0 或者发生 IO 异常。
通过对输入和输出流的 API 文档 进行分析,我们了解到读和写操作都是同步阻塞的,阻塞的时间取决于对方 IO 线程 的处理速度和 网络 IO 的传输速度。本质上来讲,我们无法保证生产环境的网络状况和对方的应用程序能足够快,如果我们的应用程序依赖对方的处理速度,它的可靠性就非常差。也许在实验室进行的性能测试结果令人满意,但是一旦上线运行,面对恶劣的网络环境和良莠不齐的第三方系统,问题就会如火山一样喷发。
伪异步IO 实际上仅仅是对之前 IO线程模型 的一个简单优化,它无法从根本上解决 同步IO 导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。 伪异步 IO 实际上仅仅是对之前 IO 线程模型 的一个简单优化,它无法从根本上解决 同步 IO 导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。
1. 服务端处理缓慢返回应答消息耗费60s 平时只需要10ms。 1. 服务端处理缓慢,返回应答消息耗费 60s 平时只需要 10ms。
2. 采用伪异步I/O的线程正在读取故障服务节点的响应由于读取输入流是阻塞的它将会被同步阻塞60s。 2. 采用伪异步 I/O 的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞 60s。
3. 假如所有的可用线程都被故障服务器阻塞那后续所有的1/O消息都将在队列中排队。 3. 假如所有的可用线程都被故障服务器阻塞,那后续所有的 1/O 消息都将在队列中排队。
4. 由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞。 4. 由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞。
5. 由于前端只有一个Accptor线程接收客户端接入它被阻塞在线程池的同步阻塞队列之后新的客户端请求消息将被拒绝客户端会发生大量的连接超时。 5. 由于前端只有一个 Accptor 线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时。
6. 由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。 6. 由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。
## NIO编程 ## NIO 编程
与 Socket类 和 ServerSocket类 相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自
己的需要来选择合适的模式。一般来说,低负载、低并发的应用程序可以选择 同步阻塞IO以降低编程复杂度对于高负载、高并发的网络应用需要使用 NIO 的非阻塞模式进行开发。
### NIO类库简介 与 Socket 类 和 ServerSocket 类 相对应NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自
NIO类库 是在 JDK 1.4 中引入的。NIO 弥补了原来 同步阻塞IO 的不足,它在 标准Java代码 中提供了高速的、面向块的IO。下面我们简单看一下 NIO类库 及其 相关概念。 己的需要来选择合适的模式。一般来说,低负载、低并发的应用程序可以选择 同步阻塞 IO以降低编程复杂度对于高负载、高并发的网络应用需要使用 NIO 的非阻塞模式进行开发。
**1、缓冲区Buffer** ### NIO 类库简介
Buffer对象 包含了一些要写入或者要读出的数据。在 NIO类库 中加入 Buffer对象是其与 原IO类库 的一个重要区别。在面向流的 IO 中,可以将数据直接写入或者将数据直接读到 Stream对象 中。在NIO库中所有数据都是用缓冲区处理的。在读取数据时它是直接读到缓冲区中的在写入数据时写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。
缓冲区实质上是一个数组。通常它是一个字节数组ByteBuffer也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组缓冲区提供了对数据的结构化访问以及维护读写位置limit等信息。最常用的缓冲区是 ByteBuffer一个 ByteBuffer 提供了一组功能用于操作 byte数组。除了 ByteBuffer还有其他的一些缓冲区事实上每一种 Java基本类型除了 boolean都对应有一种与之对应的缓冲区CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer组件中主要类的类图如下所示。 NIO 类库 是在 JDK 1.4 中引入的。NIO 弥补了原来 同步阻塞 IO 的不足,它在 标准 Java 代码 中提供了高速的、面向块的 IO。下面我们简单看一下 NIO 类库 及其 相关概念。
**1、缓冲区 Buffer**
Buffer 对象 包含了一些要写入或者要读出的数据。在 NIO 类库 中加入 Buffer 对象,是其与 原 IO 类库 的一个重要区别。在面向流的 IO 中,可以将数据直接写入或者将数据直接读到 Stream 对象 中。在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。
缓冲区实质上是一个数组。通常它是一个字节数组ByteBuffer也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组缓冲区提供了对数据的结构化访问以及维护读写位置limit等信息。最常用的缓冲区是 ByteBuffer一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了 ByteBuffer还有其他的一些缓冲区事实上每一种 Java 基本类型(除了 boolean都对应有一种与之对应的缓冲区CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer 组件中主要类的类图如下所示。
![avatar](../../../images/Netty/Buffer组件类图.png) ![avatar](../../../images/Netty/Buffer组件类图.png)
除了ByteBuffer每一个 Buffer类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准IO操作 都使用 ByteBuffer所以它在具有一般缓冲区的操作之外还提供了一些特有的操作以方便网络读写。 除了 ByteBuffer每一个 Buffer 类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准 IO 操作 都使用 ByteBuffer所以它在具有一般缓冲区的操作之外还提供了一些特有的操作以方便网络读写。
**2、通道Channel** **2、通道 Channel**
Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX网络编程模型 中底层操作系统的通道都是全双工的同时支持读写操作。Channel组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。 Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX 网络编程模型 中底层操作系统的通道都是全双工的同时支持读写操作。Channel 组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。
![avatar](../../../images/Netty/Channel组件类图.png) ![avatar](../../../images/Netty/Channel组件类图.png)
**3、多路复用器Selector** **3、多路复用器 Selector**
多路复用器Selector 是 Java NIO编程 的基础,熟练地掌握 Selector 对于 NIO编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲Selector会不断地轮询 “注册在其上的Channel”如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO操作。 多路复用器 Selector 是 Java NIO 编程 的基础,熟练地掌握 Selector 对于 NIO 编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲Selector 会不断地轮询 “注册在其上的 Channel”如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO 操作。
一个 多路复用器Selector 可以同时轮询多个 Channel由于 JDK 使用了 epoll() 代替传统的 select 的实现,所以它并没有最大连接句柄的限制。这也就意味着,只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。下面,我们通过 NIO编程的序列图 和 源码分析来熟悉相关的概念。 一个 多路复用器 Selector 可以同时轮询多个 Channel由于 JDK 使用了 epoll() 代替传统的 select 的实现,所以它并没有最大连接句柄的限制。这也就意味着,只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。下面,我们通过 NIO 编程的序列图 和 源码分析来熟悉相关的概念。
### NIO服务端序列图 ### NIO 服务端序列图
![avatar](../../../images/Netty/NIO服务端序列图.png) ![avatar](../../../images/Netty/NIO服务端序列图.png)
下面,我们看一下 NIO服务端 的主要创建过程。 下面,我们看一下 NIO 服务端 的主要创建过程。
1、打开 ServerSocketChannel用于监听客户端的连接它是所有客户端连接的 1、打开 ServerSocketChannel用于监听客户端的连接它是所有客户端连接的
父管道,示例代码如下。 父管道,示例代码如下。
```java ```java
ServerSocketChannel acceptorSvr = ServerSocketChannel.open(); ServerSocketChannel acceptorSvr = ServerSocketChannel.open();
``` ```
2、绑定监听端口设置连接为非阻塞模式示例代码如下。 2、绑定监听端口设置连接为非阻塞模式示例代码如下。
```java ```java
acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port)); acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port));
acceptorSvr.configureBlocking(false); acceptorSvr.configureBlocking(false);
``` ```
3、创建 Reactor线程创建多路复用器并启动线程示例代码如下。
3、创建 Reactor 线程,创建多路复用器并启动线程,示例代码如下。
```java ```java
Selector selector = Selector.open(); Selector selector = Selector.open();
New Thread (new ReactorTask()).start(); New Thread (new ReactorTask()).start();
``` ```
4、将 ServerSocketChannel 注册到 Reactor线程 的 多路复用器Selector 上,监听 ACCEPT事件示例代码如下。
4、将 ServerSocketChannel 注册到 Reactor 线程 的 多路复用器 Selector 上,监听 ACCEPT 事件,示例代码如下。
```java ```java
SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ ACCEPT, ioHandler); SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ ACCEPT, ioHandler);
``` ```
5、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的Key示例代码如下。
5、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的 Key示例代码如下。
```java ```java
int num = selector.select(); int num = selector.select();
Set selectedKeys = selector.selectedKeys(); Set selectedKeys = selector.selectedKeys();
@ -142,25 +162,35 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
SelectionKey key = (SelectionKey) it.next(); SelectionKey key = (SelectionKey) it.next();
// .... deal with IO event ... // .... deal with IO event ...
``` ```
6、多路复用器Selector 监听到有新的客户端接入,处理新的接入请求,完成 TCP三次握手建立物理链路示例代码如下。
6、多路复用器 Selector 监听到有新的客户端接入,处理新的接入请求,完成 TCP 三次握手,建立物理链路,示例代码如下。
```java ```java
SocketChannel channel = svrChannel.accept(); SocketChannel channel = svrChannel.accept();
``` ```
7、设置客户端链路为非阻塞模式示例代码如下。 7、设置客户端链路为非阻塞模式示例代码如下。
```java ```java
channel.configureBlocking(false); channel.configureBlocking(false);
channel.socket().setReuseAddress(true); channel.socket().setReuseAddress(true);
...... ......
``` ```
8、将新接入的客户端连接注册到 Reactor线程 的多路复用器上,监听读操作,读取客户端发送的网络消息,示例代码如下。
8、将新接入的客户端连接注册到 Reactor 线程 的多路复用器上,监听读操作,读取客户端发送的网络消息,示例代码如下。
```java ```java
SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ, ioHandler); SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ, ioHandler);
``` ```
9、异步读取客户端请求消息到缓冲区示例代码如下。 9、异步读取客户端请求消息到缓冲区示例代码如下。
```java ```java
int readNumber = channel.read(receivedBuffer); int readNumber = channel.read(receivedBuffer);
``` ```
10、对 ByteBuffer 进行编解码,如果有半包消息指针 reset继续读取后续的报文将解码成功的消息封装成 Task投递到业务线程池中,进行业务逻辑编排,示例代码如下。 10、对 ByteBuffer 进行编解码,如果有半包消息指针 reset继续读取后续的报文将解码成功的消息封装成 Task投递到业务线程池中,进行业务逻辑编排,示例代码如下。
```java ```java
List messageList = null; List messageList = null;
while (byteBuffer.hasRemain()) { while (byteBuffer.hasRemain()) {
@ -183,32 +213,42 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
} }
} }
``` ```
11、将 POJO对象 encode 成 ByteBuffer调用 SocketChannel 的 异步write接口将消息异步发送给客户端示例代码如下。
11、将 POJO 对象 encode 成 ByteBuffer调用 SocketChannel 的 异步 write 接口,将消息异步发送给客户端,示例代码如下。
```java ```java
socketChannel.write(byteBuffer); socketChannel.write(byteBuffer);
``` ```
注意:如果发送区 TCP缓冲区满会导致写半包此时需要注册监听写操作位循环写直到整包消息写入 TCP缓冲区。对于 “半包问题” 此处暂不赘述,后续会单独写一篇详细分析 Netty 的处理策略。
注意:如果发送区 TCP 缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入 TCP 缓冲区。对于 “半包问题” 此处暂不赘述,后续会单独写一篇详细分析 Netty 的处理策略。
### NIO 客户端序列图 ### NIO 客户端序列图
![avatar](../../../images/Netty/NIO客户端序列图.png) ![avatar](../../../images/Netty/NIO客户端序列图.png)
1、打开 SocketChannel绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。 1、打开 SocketChannel绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。
```java ```java
SocketChannel clientChannel = SocketChannel.open(); SocketChannel clientChannel = SocketChannel.open();
``` ```
2、设置 SocketChannel 为非阻塞模式,同时设置客户端连接的 TCP参数示例代码如下。
2、设置 SocketChannel 为非阻塞模式,同时设置客户端连接的 TCP 参数,示例代码如下。
```java ```java
clientChannel.configureBlocking(false); clientChannel.configureBlocking(false);
socket.setReuseAddress(true); socket.setReuseAddress(true);
socket.setReceiveBufferSize(BUFFER_SIZE); socket.setReceiveBufferSize(BUFFER_SIZE);
socket.setSendBufferSize(BUFFER_SIZE); socket.setSendBufferSize(BUFFER_SIZE);
``` ```
3、异步连接服务端示例代码如下。 3、异步连接服务端示例代码如下。
```java ```java
boolean connected = clientChannel.connect( new InetSocketAddress("ip", port) ); boolean connected = clientChannel.connect( new InetSocketAddress("ip", port) );
``` ```
4、判断是否连接成功如果连接成功则直接注册读状态位到多路复用器中如果当前没有连接成功则重连 (异步连接返回false说明客户端已经发送 syne包服务端没有返回 ack包物理链路还没有建立),示例代码如下。
4、判断是否连接成功如果连接成功则直接注册读状态位到多路复用器中如果当前没有连接成功则重连 (异步连接,返回 false说明客户端已经发送 syne 包,服务端没有返回 ack 包,物理链路还没有建立),示例代码如下。
```java ```java
if (connected) { if (connected) {
clientChannel.register(selector, SelectionKey.OP_READ, ioHandler); clientChannel.register(selector, SelectionKey.OP_READ, ioHandler);
@ -216,16 +256,22 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler); clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler);
} }
``` ```
5、向 Reactor线程 的多路复用器注册 OP_CONNECT 状态位,监听服务端的 TCP ACK应答示例代码如下。
5、向 Reactor 线程 的多路复用器注册 OP_CONNECT 状态位,监听服务端的 TCP ACK 应答,示例代码如下。
```java ```java
clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler); clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler);
``` ```
6、创建 Reactor线程创建多路复用器并启动线程代码如下。
6、创建 Reactor 线程,创建多路复用器并启动线程,代码如下。
```java ```java
Selector selector = Selector.open(); Selector selector = Selector.open();
New Thread( new ReactorTask() ).start(); New Thread( new ReactorTask() ).start();
``` ```
7、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的Key代码如下。
7、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的 Key代码如下。
```java ```java
int num = selector.select(); int num = selector.select();
Set selectedKeys = selector.selectedKeys(); Set selectedKeys = selector.selectedKeys();
@ -235,27 +281,37 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
// ... deal with IO event ... // ... deal with IO event ...
} }
``` ```
8、接收 connect事件并进行处理示例代码如下。
8、接收 connect 事件,并进行处理,示例代码如下。
```java ```java
if (key.isConnectable()) { if (key.isConnectable()) {
// handlerConnect(); // handlerConnect();
} }
``` ```
9、判断连接结果如果连接成功注册读事件到多路复用器示例代码如下。 9、判断连接结果如果连接成功注册读事件到多路复用器示例代码如下。
```java ```java
if(channel.finishConnect()) { if(channel.finishConnect()) {
registerRead(); registerRead();
} }
``` ```
10、注册读事件到多路复用器示例代码如下。 10、注册读事件到多路复用器示例代码如下。
```java ```java
clientChannel.register(selector, SelectionKey.OP_READ, ioHandler); clientChannel.register(selector, SelectionKey.OP_READ, ioHandler);
``` ```
11、异步读客户端请求消息到缓冲区示例代码如下。 11、异步读客户端请求消息到缓冲区示例代码如下。
```java ```java
int readNumber = channel.read(receivedBuffer); int readNumber = channel.read(receivedBuffer);
``` ```
12、对 ByteBuffer 进行编解码,如果有半包消息接收缓冲区 Reset继续读取后续的报文将解码成功的消息封装成 Task投递到业务线程池中进行业务逻辑编排。示例代码如下。 12、对 ByteBuffer 进行编解码,如果有半包消息接收缓冲区 Reset继续读取后续的报文将解码成功的消息封装成 Task投递到业务线程池中进行业务逻辑编排。示例代码如下。
```java ```java
List messageList = null; List messageList = null;
while (byteBuffer.hasRemain()) { while (byteBuffer.hasRemain()) {
@ -278,41 +334,51 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
} }
} }
``` ```
13、将 POJO对象 encode成 ByteBuffer调用 SocketChannel 的 异步write接口将消息异步发送给客户端。示例代码如下。
13、将 POJO 对象 encode 成 ByteBuffer调用 SocketChannel 的 异步 write 接口,将消息异步发送给客户端。示例代码如下。
```java ```java
socketChannel.write(buffer); socketChannel.write(buffer);
``` ```
## AIO编程 ## AIO 编程
NIO2.0 引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取获取操作结果。 NIO2.0 引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取获取操作结果。
- 通过 java.util.concurrent.Future类 来表示异步操作的结果;
- 在执行异步操作的时候传入一个 java.nio.channels.CompletionHandler接口 的实现类作为操作完成的回调。
NIO2.0 的异步套接字通道是真正的 异步非阻塞IO对应于 UNIX网络编程 中的 事件驱动IO (AIO)。它不需要通过多路复用器 (Selector) 对注册的通道进行轮询操作即可实现异步读写,从而简化了 NIO 的编程模型。 - 通过 java.util.concurrent.Future 类 来表示异步操作的结果;
- 在执行异步操作的时候传入一个 java.nio.channels.CompletionHandler 接口 的实现类作为操作完成的回调。
NIO2.0 的异步套接字通道是真正的 异步非阻塞 IO对应于 UNIX 网络编程 中的 事件驱动 IO (AIO)。它不需要通过多路复用器 (Selector) 对注册的通道进行轮询操作即可实现异步读写,从而简化了 NIO 的编程模型。
由于在实际开发中使用较少,所以这里不对 AIO 进行详细分析。 由于在实际开发中使用较少,所以这里不对 AIO 进行详细分析。
## 四种IO编程模型的对比 ## 四种 IO 编程模型的对比
对比之前,这里再澄清一下 “伪异步IO” 的概念。伪异步IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO编程 没有流行之前,为了解决 Tomcat 通信线程同步IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO线程 和业务线程间的直接访问,这样业务线程就不会被 IO线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO线程 或者进行 IO读写这样也就不会被同步阻塞。
对比之前,这里再澄清一下 “伪异步 IO” 的概念。伪异步 IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO 编程 没有流行之前,为了解决 Tomcat 通信线程同步 IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO 线程 和业务线程间的直接访问,这样业务线程就不会被 IO 线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO 线程 或者进行 IO 读写,这样也就不会被同步阻塞。
![avatar](../../../images/Netty/四种IO模型的功能特性对比图.png) ![avatar](../../../images/Netty/四种IO模型的功能特性对比图.png)
## 选择 Netty 开发项目的理由 ## 选择 Netty 开发项目的理由
从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。
### 为什么不选择 Java原生NIO 进行开发 从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO 线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。
### 为什么不选择 Java 原生 NIO 进行开发
1. NIO 的类库和 API 使用起来非常繁杂,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。 1. NIO 的类库和 API 使用起来非常繁杂,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。
2. 需要具备其他的额外技能做铺垫,例如,熟悉 Java多线程编程。这是因为 NIO编程 涉及到 Reactor模式你必须对 多线程 和 网路编程 非常熟悉,才能编写出高质量的 NIO程序。 2. 需要具备其他的额外技能做铺垫,例如,熟悉 Java 多线程编程。这是因为 NIO 编程 涉及到 Reactor 模式,你必须对 多线程 和 网路编程 非常熟悉,才能编写出高质量的 NIO 程序。
3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临:断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理,等问题。 3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临:断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理,等问题。
4. JDK NIO 的 BUG例如臭名昭著的 epoll bug它会导致 Selector空轮询最终导致 CPU 100%。虽然官方声称修复了该问题,但是直到 JDK 1.7版本 该问题仍旧未得到彻底的解决。 4. JDK NIO 的 BUG例如臭名昭著的 epoll bug它会导致 Selector 空轮询,最终导致 CPU 100%。虽然官方声称修复了该问题,但是直到 JDK 1.7 版本 该问题仍旧未得到彻底的解决。
### 为什么选择 Netty 进行开发 ### 为什么选择 Netty 进行开发
Netty 是业界最流行的 NIO框架 之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC框架 Avro ,阿里的 RPC框架 Dubbo 就使用了 Netty 作为底层通信框架。通过对Netty的分析我们将它的优点总结如下。
- API使用简单开发门槛低 Netty 是业界最流行的 NIO 框架 之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC 框架 Avro ,阿里的 RPC 框架 Dubbo 就使用了 Netty 作为底层通信框架。通过对 Netty 的分析,我们将它的优点总结如下。
- API 使用简单,开发门槛低;
- 功能强大,预置了多种编解码功能,支持多种主流协议; - 功能强大,预置了多种编解码功能,支持多种主流协议;
- 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活地扩展; - 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活地扩展;
- 性能高,通过与其他业界主流的 NIO框架 对比Netty 的综合性能最优; - 性能高,通过与其他业界主流的 NIO 框架 对比Netty 的综合性能最优;
- 成熟、稳定Netty 修复了已经发现的所有 JDK NIO BUG业务开发人员不需要再为 NIO 的 BUG 而烦恼; - 成熟、稳定Netty 修复了已经发现的所有 JDK NIO BUG业务开发人员不需要再为 NIO 的 BUG 而烦恼;
- 社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时,更多的新功能会加入; - 社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时,更多的新功能会加入;
- 经历了大规模的商业应用考验质量得到验证。Netty 在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。 - 经历了大规模的商业应用考验质量得到验证。Netty 在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。
正是因为这些优点Netty 逐渐成为了 Java NIO编程 的首选框架。 正是因为这些优点Netty 逐渐成为了 Java NIO 编程 的首选框架。

@ -1,16 +1,18 @@
Netty 的 ChannelPipeline 和 ChannelHandler 机制类似于 Servlet 和 Filter过滤器这类拦截器实际上是职责链模式的一种变形主要是为了方便事件的拦截和用户业务逻辑的定制。 Netty 的 ChannelPipeline 和 ChannelHandler 机制类似于 Servlet 和 Filter 过滤器,这类拦截器实际上是职责链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定制。
Servlet Filter 能够以声明的方式web.xml 配置文件)插入到 HTTP请求响应的处理过程中用于拦截请求和响应以便能够查看、提取或以某种方式操作正在客户端和服务器之间交换的数据。拦截器封装了业务定制逻辑能够实现对 Web应用程序 的预处理和事后处理。 Servlet Filter 能够以声明的方式web.xml 配置文件)插入到 HTTP 请求响应的处理过程中,用于拦截请求和响应,以便能够查看、提取或以某种方式操作正在客户端和服务器之间交换的数据。拦截器封装了业务定制逻辑,能够实现对 Web 应用程序 的预处理和事后处理。
Netty 的 Channel过滤器 实现原理与 Servlet Filter机制 一致,它将 Channel的数据管道 抽象为 ChannelPipeline消息在 ChannelPipeline 中流动和传递。ChannelPipeline 持有 I/O事件拦截器 ChannelHandler链表由 ChannelHandler链表 对 IO事件 进行拦截和处理,可以通过新增和删除 ChannelHandler 来实现不同的业务逻辑定制,不需要对已有的 ChannelHandler 进行修改,能够实现对修改封闭和对扩展的支持。 Netty 的 Channel 过滤器 实现原理与 Servlet Filter 机制 一致,它将 Channel 的数据管道 抽象为 ChannelPipeline消息在 ChannelPipeline 中流动和传递。ChannelPipeline 持有 I/O 事件拦截器 ChannelHandler 链表,由 ChannelHandler 链表 对 IO 事件 进行拦截和处理,可以通过新增和删除 ChannelHandler 来实现不同的业务逻辑定制,不需要对已有的 ChannelHandler 进行修改,能够实现对修改封闭和对扩展的支持。
下面我们对 ChannelPipeline 和 ChannelHandler 的功能进行简单地介绍,然后分析下其源码设计。 下面我们对 ChannelPipeline 和 ChannelHandler 的功能进行简单地介绍,然后分析下其源码设计。
## ChannelPipeline 的功能和作用 ## ChannelPipeline 的功能和作用
ChannelPipeline 是 ChannelHandler 的容器,它负责 ChannelHandler 的管理、事件拦截与调度。 ChannelPipeline 是 ChannelHandler 的容器,它负责 ChannelHandler 的管理、事件拦截与调度。
#### ChannelPipeline 的事件处理 #### ChannelPipeline 的事件处理
下图展示了 一个消息被 ChannelPipeline 的 ChannelHandler链 拦截和处理的全过程。
下图展示了一个消息被 ChannelPipeline 的 ChannelHandler 链拦截和处理的全过程。
```java ```java
* I/O Request * I/O Request
@ -52,34 +54,40 @@ ChannelPipeline 是 ChannelHandler 的容器,它负责 ChannelHandler 的管
* | Netty Internal I/O Threads (Transport Implementation) | * | Netty Internal I/O Threads (Transport Implementation) |
* +-------------------------------------------------------------------+ * +-------------------------------------------------------------------+
``` ```
从上图可以看出 消息读取和发送处理全流程为: 从上图可以看出 消息读取和发送处理全流程为:
1. 底层的 SocketChannel.read()方法 读取 ByteBuf触发 ChannelRead事件由 IO线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead(Object msg)方法,将消息传输到 ChannelPipeline 中。
1. 底层的 SocketChannel.read()方法 读取 ByteBuf触发 ChannelRead 事件,由 IO 线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead(Object msg)方法,将消息传输到 ChannelPipeline 中。
2. 消息依次被 HeadHandler、ChannelHandler1、ChannelHandler2 … TailHandler 拦截和处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。 2. 消息依次被 HeadHandler、ChannelHandler1、ChannelHandler2 … TailHandler 拦截和处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。
3. 调用 ChannelHandlerContext 的 write方法 发送消息,消息从 TailHandler 开始途经 ChannelHandlerN … ChannelHandler1、HeadHandler最终被添加到消息发送缓冲区中等待刷新和发送在此过程中也可以中断消息的传递例如当编码失败时就需要中断流程构造异常的Future返回。 3. 调用 ChannelHandlerContext 的 write 方法 发送消息,消息从 TailHandler 开始途经 ChannelHandlerN … ChannelHandler1、HeadHandler最终被添加到消息发送缓冲区中等待刷新和发送在此过程中也可以中断消息的传递例如当编码失败时就需要中断流程构造异常的 Future 返回。
Netty 中的事件分为 Inbound 事件 和 Outbound 事件。Inbound 事件 通常由 I/O 线程 触发,例如 TCP 链路建立事件、链路关闭事件、读事件、异常通知事件等,它对应上图的左半部分。触发 Inbound 事件 的方法如下。
Netty 中的事件分为 Inbound事件 和 Outbound事件。Inbound事件 通常由 I/O线程 触发,例如 TCP链路建立事件、链路关闭事件、读事件、异常通知事件等它对应上图的左半部分。触发 Inbound事件 的方法如下。 1. ChannelHandlerContext.fireChannelRegistered()Channel 注册事件;
1. ChannelHandlerContext.fireChannelRegistered()Channel注册事件 2. ChannelHandlerContext.fireChannelActive()TCP 链路建立成功Channel 激活事件;
2. ChannelHandlerContext.fireChannelActive()TCP链路建立成功Channel激活事件
3. ChannelHandlerContext.fireChannelRead(Object):读事件; 3. ChannelHandlerContext.fireChannelRead(Object):读事件;
4. ChannelHandlerContext.fireChannelReadComplete():读操作完成通知事件; 4. ChannelHandlerContext.fireChannelReadComplete():读操作完成通知事件;
5. ChannelHandlerContext.fireExceptionCaught(Throwable):异常通知事件; 5. ChannelHandlerContext.fireExceptionCaught(Throwable):异常通知事件;
6. ChannelHandlerContext.fireUserEventTriggered(Object):用户自定义事件; 6. ChannelHandlerContext.fireUserEventTriggered(Object):用户自定义事件;
7. ChannelHandlerContext.fireChannelWritabilityChanged()Channel的可写状态变化 7. ChannelHandlerContext.fireChannelWritabilityChanged()Channel 的可写状态变化;
8. ChannelHandlerContext.fireChannellnactive()TCP连接关闭链路不可用通知事件。 8. ChannelHandlerContext.fireChannellnactive()TCP 连接关闭,链路不可用通知事件。
Outbound 事件 通常是由用户主动发起的 网络 IO 操作,例如用户发起的连接操作、绑定操作、消息发送等操作,它对应上图的右半部分。触发 Outbound 事件 的方法如下:
Outbound事件 通常是由用户主动发起的 网络IO操作例如用户发起的连接操作、绑定操作、消息发送等操作它对应上图的右半部分。触发 Outbound事件 的方法如下:
1. ChannelHandlerContext.bind(SocketAddress, ChannelPromise):绑定本地地址事件; 1. ChannelHandlerContext.bind(SocketAddress, ChannelPromise):绑定本地地址事件;
2. ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise):连接服务端事件; 2. ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise):连接服务端事件;
3. ChannelHandlerContext.write(Object, ChannelPromise):发送事件; 3. ChannelHandlerContext.write(Object, ChannelPromise):发送事件;
4. ChannelHandlerContext.flush():刷新事件; 4. ChannelHandlerContext.flush():刷新事件;
5. ChannelHandlerContext.read():读事件; 5. ChannelHandlerContext.read():读事件;
6. ChannelHandlerContext.disconnect(ChannelPromise):断开连接事件; 6. ChannelHandlerContext.disconnect(ChannelPromise):断开连接事件;
7. ChannelHandlerContext.close(ChannelPromise)关闭当前Channel事件。 7. ChannelHandlerContext.close(ChannelPromise):关闭当前 Channel 事件。
#### ChannelPipeline 自定义拦截器 #### ChannelPipeline 自定义拦截器
ChannelPipeline 通过 ChannelHandler 来实现事件的拦截和处理,由于 ChannelHandler 中的事件种类繁多,不同的 ChannelHandler 可能只需要关心其中的个别事件所以自定义的ChannelHandler 只需要继承 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter覆盖自己关心的方法即可。
下面的两个示例分别展示了:拦截 Channel Active事件打印TCP链路建立成功日志和 链路关闭的时候释放资源,代码如下。 ChannelPipeline 通过 ChannelHandler 来实现事件的拦截和处理,由于 ChannelHandler 中的事件种类繁多,不同的 ChannelHandler 可能只需要关心其中的个别事件,所以,自定义的 ChannelHandler 只需要继承 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter覆盖自己关心的方法即可。
下面的两个示例分别展示了:拦截 Channel Active 事件,打印 TCP 链路建立成功日志,和 链路关闭的时候释放资源,代码如下。
```java ```java
public class MyInboundHandler extends ChannelInboundHandlerAdapter { public class MyInboundHandler extends ChannelInboundHandlerAdapter {
@Override @Override
@ -99,7 +107,9 @@ public class MyOutboundHandler extends ChannelOutboundHandlerAdapter {
``` ```
#### 构建 pipeline #### 构建 pipeline
使用 Netty 时,用户不需要自己创建 pipeline因为使用 ServerBootstrap 或者 Bootstrap 进行配置后Netty 会为每个 Channel连接 创建一个独立的pipeline。我们只需将自定义的 ChannelHandler 加入到 pipeline 即可。相关代码如下。
使用 Netty 时,用户不需要自己创建 pipeline因为使用 ServerBootstrap 或者 Bootstrap 进行配置后Netty 会为每个 Channel 连接 创建一个独立的 pipeline。我们只需将自定义的 ChannelHandler 加入到 pipeline 即可。相关代码如下。
```java ```java
ServerBootstrap server = new ServerBootstrap(); ServerBootstrap server = new ServerBootstrap();
server.childHandler(new ChannelInitializer<SocketChannel>() { server.childHandler(new ChannelInitializer<SocketChannel>() {
@ -119,19 +129,22 @@ server.childHandler(new ChannelInitializer<SocketChannel>() {
} }
}); });
``` ```
对于类似编解码这样的 ChannelHandler它存在先后顺序例如 MessageToMessageDecoder在它之前往往需要有 ByteToMessageDecoder 将 ByteBuf 解码为对象,然后将对象做二次解码 得到最终的 POJO对象。pipeline 支持指定位置添加或者删除ChannelHandler。
对于类似编解码这样的 ChannelHandler它存在先后顺序例如 MessageToMessageDecoder在它之前往往需要有 ByteToMessageDecoder 将 ByteBuf 解码为对象,然后将对象做二次解码 得到最终的 POJO 对象。pipeline 支持指定位置添加或者删除 ChannelHandler。
#### ChannelPipeline 的主要特性 #### ChannelPipeline 的主要特性
ChannelPipeline 支持运行时动态的添加或者删除 ChannelHandler在某些场景下这个特性非常实用。例如当业务高峰期需要对系统做拥塞保护时就可以根据当前的系统时间进行判断如果处于业务高峰期则动态地将 系统拥塞保护ChannelHandler 添加到当前的ChannelPipeline 中,当高峰期过去之后,再动态删除 拥塞保护ChannelHandler。
ChannelPipeline 支持运行时动态的添加或者删除 ChannelHandler在某些场景下这个特性非常实用。例如当业务高峰期需要对系统做拥塞保护时就可以根据当前的系统时间进行判断如果处于业务高峰期则动态地将 系统拥塞保护 ChannelHandler 添加到当前的 ChannelPipeline 中,当高峰期过去之后,再动态删除 拥塞保护 ChannelHandler。
ChannelPipeline 是线程安全的,这意味着 N 个业务线程可以并发地操作 ChannelPipeline 而不存在多线程并发问题。但 ChannelHandler 不是线程安全的,这意味着 我们需要自己保证 ChannelHandler 的线程安全。 ChannelPipeline 是线程安全的,这意味着 N 个业务线程可以并发地操作 ChannelPipeline 而不存在多线程并发问题。但 ChannelHandler 不是线程安全的,这意味着 我们需要自己保证 ChannelHandler 的线程安全。
## ChannelPipeline 源码解析 ## ChannelPipeline 源码解析
ChannelPipeline 的代码比较简单,它实际上是一个 ChannelHandler容器内部维护了一个 ChannelHandler 的链表和迭代器,可以方便地进行 ChannelHandler 的 CRUD。
另外一个比较重要的部分是,当发生某个 I/O事件 时,如 链路建立、链路关闭、读写操作 等,都会产一个事件,事件在 pipeline 中传播和处理,它是事件处理的总入口。由于 网络I/O 相关的事件有限,因此 Netty 对这些事件进行了统一抽象Netty 提供的 和用户自定义的 ChannelHandler 会对感兴趣的事件进行拦截和处理。 ChannelPipeline 的代码比较简单,它实际上是一个 ChannelHandler 容器,内部维护了一个 ChannelHandler 的链表和迭代器,可以方便地进行 ChannelHandler 的 CRUD。
另外一个比较重要的部分是,当发生某个 I/O 事件 时,如 链路建立、链路关闭、读写操作 等,都会产一个事件,事件在 pipeline 中传播和处理,它是事件处理的总入口。由于 网络 I/O 相关的事件有限,因此 Netty 对这些事件进行了统一抽象Netty 提供的 和用户自定义的 ChannelHandler 会对感兴趣的事件进行拦截和处理。
pipeline 中以 fireXXX 命名的方法都是从 I/O线程 流向 用户业务Handler 的 inbound事件它们的实现因功能而异但是处理步骤类似都是 调用HeadHandler 对应的 fireXXX方法然后执行事件相关的逻辑操作。 pipeline 中以 fireXXX 命名的方法都是从 I/O 线程 流向 用户业务 Handler 的 inbound 事件,它们的实现因功能而异,但是处理步骤类似,都是 调用 HeadHandler 对应的 fireXXX 方法,然后执行事件相关的逻辑操作。
```java ```java
public interface ChannelPipeline public interface ChannelPipeline
@ -224,20 +237,24 @@ public interface ChannelPipeline
``` ```
## ChannelHandler 的功能和作用 ## ChannelHandler 的功能和作用
ChannelHandler 负责对 I/O事件 进行拦截处理,它可以选择性地 拦截处理感兴趣的事件,也可以透传和终止事件的传递。基于 ChannelHandler接口我们可以方便地进行业务逻辑定制如 打印日志、统一封装异常信息、性能统计和消息编解码等。
ChannelHandler 负责对 I/O 事件 进行拦截处理,它可以选择性地 拦截处理感兴趣的事件,也可以透传和终止事件的传递。基于 ChannelHandler 接口,我们可以方便地进行业务逻辑定制,如 打印日志、统一封装异常信息、性能统计和消息编解码等。
#### ChannelHandlerAdapter #### ChannelHandlerAdapter
大部分 ChannelHandler 都会选择性 拦截处理感兴趣的 I/O事件忽略其他事件然后交由下一个 ChannelHandler 进行拦截处理。这会导致一个问题:自定义 ChannelHandler 必须要实现 ChannelHandler 的所有接口,包括它不关心的那些事件处理接口,这会导致用户代码的冗余和臃肿,代码的可维护性也会变差。
为了解决这个问题Netty 提供了 ChannelHandlerAdapter 基类,和 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter 两个实现类,如果 自定义ChannelHandler 关心某个事件,只需要继承 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter 覆盖对应的方法即可,对于不关心的,可以直接继承使用父类的方法,这样子类的代码就会非常简洁清晰。 大部分 ChannelHandler 都会选择性 拦截处理感兴趣的 I/O 事件,忽略其他事件,然后交由下一个 ChannelHandler 进行拦截处理。这会导致一个问题:自定义 ChannelHandler 必须要实现 ChannelHandler 的所有接口,包括它不关心的那些事件处理接口,这会导致用户代码的冗余和臃肿,代码的可维护性也会变差。
为了解决这个问题Netty 提供了 ChannelHandlerAdapter 基类,和 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter 两个实现类,如果 自定义 ChannelHandler 关心某个事件,只需要继承 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter 覆盖对应的方法即可,对于不关心的,可以直接继承使用父类的方法,这样子类的代码就会非常简洁清晰。
## ChannelHandler 组件 的类结构
## ChannelHandler组件 的类结构
相对于 ByteBuf 和 ChannelChannelHandler 的类继承关系稍微简单些,但是它的子类非常多,功能各异,主要可以分为如下四类。 相对于 ByteBuf 和 ChannelChannelHandler 的类继承关系稍微简单些,但是它的子类非常多,功能各异,主要可以分为如下四类。
1. ChannelPipeline 的系统 ChannelHandler用于 I/O操作 和对事件进行预处理,对用户不可见,这类 ChannelHandler 主要包括 HeadHandler 和 TailHandler
2. 编解码ChannelHandler如 MessageToMessageEncoder、MessageToMessageDecoder、MessageToMessageCodec 1. ChannelPipeline 的系统 ChannelHandler用于 I/O 操作 和对事件进行预处理,对用户不可见,这类 ChannelHandler 主要包括 HeadHandler 和 TailHandler
3. 其他系统功能性 ChannelHandler如 流量整型Handler、读写超时Handler、日志Handler等 2. 编解码 ChannelHandler如 MessageToMessageEncoder、MessageToMessageDecoder、MessageToMessageCodec
3. 其他系统功能性 ChannelHandler如 流量整型 Handler、读写超时 Handler、日志 Handler 等;
4. 自定义 ChannelHandler。 4. 自定义 ChannelHandler。
ChannelHandler组件 的核心类及常用类的类图如下。 ChannelHandler 组件 的核心类及常用类的类图如下。
![在这里插入图片描述](../../../images/Netty/ChannelHandler组件.png) ![在这里插入图片描述](../../../images/Netty/ChannelHandler组件.png)

@ -1,11 +1,14 @@
类似于 java.nio包 的 ChannelNetty 提供了自己的 Channel 和其子类实现,用于异步 I/O操作 等。Unsafe 是 Channel 的内部接口,聚合在 Channel 中协助进行网络读写相关的操作,因为它的设计初衷就是 Channel 的内部辅助类,不应该被 Netty框架 的上层使用者调用,所以被命名为 Unsafe。 类似于 java.nio 包 的 ChannelNetty 提供了自己的 Channel 和其子类实现,用于异步 I/O 操作 等。Unsafe 是 Channel 的内部接口,聚合在 Channel 中协助进行网络读写相关的操作,因为它的设计初衷就是 Channel 的内部辅助类,不应该被 Netty 框架 的上层使用者调用,所以被命名为 Unsafe。
## Channel 组件 ## Channel 组件
Netty 的 **Channel组件 是 Netty 对网络操作的封装****如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外Netty 并没有直接使用 java.nio包 的 SocketChannel和ServerSocketChannel而是**使用 NioSocketChannel和NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel接口 的API开始分析然后看一下其重要子类的源码实现。
Netty 的 **Channel 组件 是 Netty 对网络操作的封装****如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty 框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外Netty 并没有直接使用 java.nio 包 的 SocketChannel 和 ServerSocketChannel而是**使用 NioSocketChannel 和 NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel 接口 的 API 开始分析,然后看一下其重要子类的源码实现。
为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。 为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。
![在这里插入图片描述](../../../images/Netty/Netty的Channel组件.png) ![在这里插入图片描述](../../../images/Netty/Netty的Channel组件.png)
#### Channel 接口 #### Channel 接口
```java ```java
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> { public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
@ -94,6 +97,7 @@ public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparabl
``` ```
#### AbstractChannel #### AbstractChannel
```java ```java
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
@ -279,6 +283,7 @@ public abstract class AbstractNioChannel extends AbstractChannel {
``` ```
#### NioServerSocketChannel #### NioServerSocketChannel
```java ```java
public class NioServerSocketChannel extends AbstractNioMessageChannel public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel { implements io.netty.channel.socket.ServerSocketChannel {
@ -343,8 +348,8 @@ public class NioServerSocketChannel extends AbstractNioMessageChannel
} }
``` ```
#### NioSocketChannel #### NioSocketChannel
```java ```java
public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel { public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {
@ -514,7 +519,8 @@ public class NioSocketChannel extends AbstractNioByteChannel implements io.netty
``` ```
## Unsafe 功能简介 ## Unsafe 功能简介
Unsafe接口 实际上是 **Channel接口 的辅助接口**,它不应该被用户代码直接调用。**实际的 IO读写操作 都是由 Unsafe接口 负责完成的**。
Unsafe 接口 实际上是 **Channel 接口 的辅助接口**,它不应该被用户代码直接调用。**实际的 IO 读写操作 都是由 Unsafe 接口 负责完成的**。
```java ```java
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> { public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
@ -575,6 +581,7 @@ public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparabl
``` ```
#### AbstractUnsafe #### AbstractUnsafe
```java ```java
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
@ -862,7 +869,9 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha
``` ```
#### AbstractNioUnsafe #### AbstractNioUnsafe
AbstractNioUnsafe 是 AbstractUnsafe类 的 NIO实现它主要实现了 connect 、finishConnect 等方法。
AbstractNioUnsafe 是 AbstractUnsafe 类 的 NIO 实现,它主要实现了 connect 、finishConnect 等方法。
```java ```java
public abstract class AbstractNioChannel extends AbstractChannel { public abstract class AbstractNioChannel extends AbstractChannel {

@ -1,47 +1,59 @@
## TCP粘包/拆包 ## TCP 粘包/拆包
熟悉 TCP编程 的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP底层 的 粘包/拆包机制。TCP粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。
### TCP粘包/拆包问题说明 熟悉 TCP 编程 的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP 底层 的 粘包/拆包机制。TCP 粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP 粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。
TCP 是个 “流” 协议所谓流就是没有界限的一串数据。TCP底层 并不了解上层(如 HTTP协议业务数据的具体含义它会根据 TCP缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP粘包和拆包问题。我们可以通过下面的示例图对 TCP粘包和拆包问题 进行说明。
### TCP 粘包/拆包问题说明
TCP 是个 “流” 协议所谓流就是没有界限的一串数据。TCP 底层 并不了解上层(如 HTTP 协议)业务数据的具体含义,它会根据 TCP 缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。我们可以通过下面的示例图,对 TCP 粘包和拆包问题 进行说明。
![avatar](../../../images/Netty/TCP粘包拆包问题.png) ![avatar](../../../images/Netty/TCP粘包拆包问题.png)
假设客户端依次发送了两个数据包 DI 和 D2 给服务端由于服务端一次读取到的字节数是不确定的故可能存在以下4种情况。 假设客户端依次发送了两个数据包 DI 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。
1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2没有粘包和拆包 1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2没有粘包和拆包
2. 服务端一次接收到了两个数据包DI 和 D2 粘合在一起,被称为 TCP粘包 2. 服务端一次接收到了两个数据包DI 和 D2 粘合在一起,被称为 TCP 粘包;
3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 DI包 和 D2包的部分内容第二次读取到了 D2包 的剩余内容,这被称为 TCP拆包 3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 DI 包 和 D2 包的部分内容,第二次读取到了 D2 包 的剩余内容,这被称为 TCP 拆包;
4. 服务端分两次读取到了两个数据包,第一次读取到了 D1包的部分内容第二次读取到了 D1包的剩余内容 和 D2包的整包。 4. 服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部分内容,第二次读取到了 D1 包的剩余内容 和 D2 包的整包。
如果此时服务端 TCP 接收滑窗非常小,而 数据包DI 和 D2 比较大很有可能会发生第5种可能即服务端分多次才能将 D1 和 D2包 接收完全,期间发生多次拆包。 如果此时服务端 TCP 接收滑窗非常小,而 数据包 DI 和 D2 比较大,很有可能会发生第 5 种可能,即服务端分多次才能将 D1 和 D2 包 接收完全,期间发生多次拆包。
### TCP 粘包/拆包发生的原因
### TCP粘包/拆包发生的原因
问题产生的原因有三个,分别如下。 问题产生的原因有三个,分别如下。
1. **应用程序 write写入的字节大小 超出了 套接口发送缓冲区大小;**
2. 进行 MSS 大小的 TCP分段 1. **应用程序 write 写入的字节大小 超出了 套接口发送缓冲区大小;**
3. 以太网帧的 payload 大于 MTU 进行 IP分片。 2. 进行 MSS 大小的 TCP 分段;
3. 以太网帧的 payload 大于 MTU 进行 IP 分片。
### 粘拆包问题的解决策略 ### 粘拆包问题的解决策略
由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。 由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。
1. 固定消息长度,例如,每个报文的大小为 固定长度200字节如果不够空位补空格
2. 在包尾使用 “回车换行符” 等特殊字符作为消息结束的标志例如FTP协议这种方式在文本协议中应用比较广泛 1. 固定消息长度,例如,每个报文的大小为 固定长度 200 字节,如果不够,空位补空格;
3. 将消息分为消息头和消息体,在消息头中定义一个 长度字段Len 来标识消息的总长度; 2. 在包尾使用 “回车换行符” 等特殊字符,作为消息结束的标志,例如 FTP 协议,这种方式在文本协议中应用比较广泛;
3. 将消息分为消息头和消息体,在消息头中定义一个 长度字段 Len 来标识消息的总长度;
4. 更复杂的应用层协议。 4. 更复杂的应用层协议。
介绍完了 TCP粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP粘包/拆包问题的。 介绍完了 TCP 粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP 粘包/拆包问题的。
## 利用 Netty 的解码器 解决 TCP 粘拆包问题
## 利用 Netty的解码器 解决 TCP粘拆包问题 根据上面的 粘拆包问题解决策略Netty 提供了相应的解码器实现。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑 TCP 的粘包和拆包。
根据上面的 粘拆包问题解决策略Netty 提供了相应的解码器实现。有了这些解码器用户不需要自己对读取的报文进行人工解码也不需要考虑TCP的粘包和拆包。
### LineBasedFrameDecoder 和 StringDecoder 的原理分析 ### LineBasedFrameDecoder 和 StringDecoder 的原理分析
为了解决 TCP粘包 / 拆包 导致的 半包读写问题Netty 默认提供了多种编解码器用于处理半包只要能熟练掌握这些类库的使用TCP粘拆包问题 从此会变得非常容易,你甚至不需要关心它们,这也是其他 NIO框架 和 JDK原生的 NIO API 所无法匹敌的。对于使用者来说,只要将支持半包解码的 Handler 添加到 ChannelPipeline对象 中即可,不需要写额外的代码,使用起来非常简单。
为了解决 TCP 粘包 / 拆包 导致的 半包读写问题Netty 默认提供了多种编解码器用于处理半包只要能熟练掌握这些类库的使用TCP 粘拆包问题 从此会变得非常容易,你甚至不需要关心它们,这也是其他 NIO 框架 和 JDK 原生的 NIO API 所无法匹敌的。对于使用者来说,只要将支持半包解码的 Handler 添加到 ChannelPipeline 对象 中即可,不需要写额外的代码,使用起来非常简单。
```java ```java
// 示例代码,其中 socketChannel 是一个 SocketChannel对象 // 示例代码,其中 socketChannel 是一个 SocketChannel对象
socketChannel.pipeline().addLast( new LineBasedFrameDecoder(1024) ); socketChannel.pipeline().addLast( new LineBasedFrameDecoder(1024) );
socketChannel.pipeline().addLast( new StringDecoder() ); socketChannel.pipeline().addLast( new StringDecoder() );
``` ```
LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断看是否有 “\n” 或者 “\r\n”如果有就以此位置为结束位置从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器支持携带结束符或者不携带结束符两种解码方式同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符就会抛出异常同时忽略掉之前读到的异常码流。 LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断看是否有 “\n” 或者 “\r\n”如果有就以此位置为结束位置从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器支持携带结束符或者不携带结束符两种解码方式同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符就会抛出异常同时忽略掉之前读到的异常码流。
StringDecoder 的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的 Handler。LineBasedFrameDecoder + StringDecoder组合 就是按行切换的文本解码器,它被设计用来支持 TCP 的粘包和拆包。 StringDecoder 的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的 Handler。LineBasedFrameDecoder + StringDecoder 组合 就是按行切换的文本解码器,它被设计用来支持 TCP 的粘包和拆包。
### 其它解码器 ### 其它解码器
除了 LineBasedFrameDecoder 以外,还有两个常用的解码器 DelimiterBasedFrameDecoder 和 FixedLengthFrameDecoder前者能自动对 “以分隔符做结束标志的消息” 进行解码,后者可以自动完成对定长消息的解码。使用方法也和前面的示例代码相同,结合 字符串解码器StringDecoder轻松完成对很多消息的自动解码。
除了 LineBasedFrameDecoder 以外,还有两个常用的解码器 DelimiterBasedFrameDecoder 和 FixedLengthFrameDecoder前者能自动对 “以分隔符做结束标志的消息” 进行解码,后者可以自动完成对定长消息的解码。使用方法也和前面的示例代码相同,结合 字符串解码器 StringDecoder轻松完成对很多消息的自动解码。

@ -1,30 +1,35 @@
相对于服务端Netty客户端 的创建更加复杂,除了要考虑线程模型、异步连接、客户端连接超时等因素外,还需要对连接过程中的各种异常进行考虑。本章将对 Netty客户端 创建的关键流程和源码进行分析,以期读者能够了解客户端创建的细节。 相对于服务端Netty 客户端 的创建更加复杂,除了要考虑线程模型、异步连接、客户端连接超时等因素外,还需要对连接过程中的各种异常进行考虑。本章将对 Netty 客户端 创建的关键流程和源码进行分析,以期读者能够了解客户端创建的细节。
## 基于 Netty 创建客户端的流程分析 ## 基于 Netty 创建客户端的流程分析
Netty 为了向使用者屏蔽 NIO通信 的底层细节在和用户交互的边界做了封装目的就是为了减少用户开发工作量降低开发难度。Bootstrap 是 Socket 客户端创建工具类,用户通过 Bootstrap 可以方便地创建 Netty 的客户端并发起 异步TCP连接操作。
Netty 为了向使用者屏蔽 NIO 通信 的底层细节在和用户交互的边界做了封装目的就是为了减少用户开发工作量降低开发难度。Bootstrap 是 Socket 客户端创建工具类,用户通过 Bootstrap 可以方便地创建 Netty 的客户端并发起 异步 TCP 连接操作。
### 基于 Netty 创建客户端 时序图 ### 基于 Netty 创建客户端 时序图
![avatar](../../../images/Netty/基于Netty创建客户端时序图.png) ![avatar](../../../images/Netty/基于Netty创建客户端时序图.png)
### Netty 创建客户端 流程分析 ### Netty 创建客户端 流程分析
1. 用户线程创建 Bootstrap实例通过 API 设置客户端相关的参数,异步发起客户端连接;
2. 创建处理客户端连接、I/O 读写的 Reactor线程组 NioEventLoopGroup。可以通过构造函数指定 IO线程 的个数,默认为 CPU 内核数的 2 倍; 1. 用户线程创建 Bootstrap 实例,通过 API 设置客户端相关的参数,异步发起客户端连接;
3. 通过 Bootstrap 的 ChannelFactory 和用户指定的 Channel类型 创建用于客户端连接的 NioSocketChannel它的功能类似于 JDK NIO类库 提供的 SocketChannel 2. 创建处理客户端连接、I/O 读写的 Reactor 线程组 NioEventLoopGroup。可以通过构造函数指定 IO 线程 的个数,默认为 CPU 内核数的 2 倍;
3. 通过 Bootstrap 的 ChannelFactory 和用户指定的 Channel 类型 创建用于客户端连接的 NioSocketChannel它的功能类似于 JDK NIO 类库 提供的 SocketChannel
4. 创建默认的 Channel、Handler、Pipeline用于调度和执行网络事件 4. 创建默认的 Channel、Handler、Pipeline用于调度和执行网络事件
5. 异步发起 TCP连接判断连接是否成功。如果成功则直接将 NioSocketChannel 注册到多路复用器上,监听读操作位,用于数据报读取和消息发送;如果没有立即连接成功,则注册连接监听位到多路复用器,等待连接结果; 5. 异步发起 TCP 连接,判断连接是否成功。如果成功,则直接将 NioSocketChannel 注册到多路复用器上,监听读操作位,用于数据报读取和消息发送;如果没有立即连接成功,则注册连接监听位到多路复用器,等待连接结果;
6. 注册对应的网络监听状态位到多路复用器; 6. 注册对应的网络监听状态位到多路复用器;
7. 由多路复用器在 IO 现场中轮询各 Channel处理连接结果 7. 由多路复用器在 IO 现场中轮询各 Channel处理连接结果
8. 如果连接成功,设置 Future结果发送连接成功事件触发 ChanneIPipeline 执行; 8. 如果连接成功,设置 Future 结果,发送连接成功事件,触发 ChanneIPipeline 执行;
9. 由 ChannelPipeline 调度执行系统和用户的 ChannelHandler执行业务逻辑。 9. 由 ChannelPipeline 调度执行系统和用户的 ChannelHandler执行业务逻辑。
## Netty 客户端创建源码分析 ## Netty 客户端创建源码分析
Netty客户端 的创建流程比较繁琐,下面我们针对关键步骤和代码进行分析,通过梳理关键流程来掌握客户端创建的原理。
Netty 客户端 的创建流程比较繁琐,下面我们针对关键步骤和代码进行分析,通过梳理关键流程来掌握客户端创建的原理。
### 客户端连接辅助类 BootStrap ### 客户端连接辅助类 BootStrap
Bootstrap 是 Netty 提供的客户端连接工具类,主要用于简化客户端的创建,下面我们对它的 主要API 进行讲解。
设置 lO线程组NIO的特点就是一个多路复用器可以同时处理上干条链路这就意味着NIO模式中 一个线程可以处理多个 TCP连接。考虑到 lO线程 的处理性能,大多数 NIO框架 都采用线程池的方式处理 IO读写Netty 也不例外。客户端相对于服务端,只需要一个处理 IO读写 的线程组即可,因为 Bootstrap 提供了 设置IO线程组 的接口,代码如下。 Bootstrap 是 Netty 提供的客户端连接工具类,主要用于简化客户端的创建,下面我们对它的 主要 API 进行讲解。
设置 lO 线程组NIO 的特点就是一个多路复用器可以同时处理上干条链路这就意味着NIO 模式中 一个线程可以处理多个 TCP 连接。考虑到 lO 线程 的处理性能,大多数 NIO 框架 都采用线程池的方式处理 IO 读写Netty 也不例外。客户端相对于服务端,只需要一个处理 IO 读写 的线程组即可,因为 Bootstrap 提供了 设置 IO 线程组 的接口,代码如下。
```java ```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable { public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
@ -42,11 +47,13 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
} }
} }
``` ```
由于 Netty 的 NIO线程组 默认采用 EventLoopGroup接口因此线程组参数使用 EventLoopGroup。
TCP参数设置接口无论是 NIO还是 BIO创建客户端套接字的时候通常都会设置连接参数例如接收和发送缓冲区大小、连接超时时间等。Bootstrap 也提供了客户端 TCP参数设置接口代码如下。 由于 Netty 的 NIO 线程组 默认采用 EventLoopGroup 接口,因此线程组参数使用 EventLoopGroup。
TCP 参数设置接口:无论是 NIO还是 BIO创建客户端套接字的时候通常都会设置连接参数例如接收和发送缓冲区大小、连接超时时间等。Bootstrap 也提供了客户端 TCP 参数设置接口,代码如下。
```java ```java
public <T> B option(ChannelOption<T> option, T value) { public <T> B option(ChannelOption<T> option, T value) {
if (option == null) { if (option == null) {
throw new NullPointerException("option"); throw new NullPointerException("option");
} else { } else {
@ -62,29 +69,33 @@ TCP参数设置接口无论是 NIO还是 BIO创建客户端套接字的
return this; return this;
} }
} }
``` ```
Netty 提供的 主要TCP参数 如下。
1、SO_TIMEOUT控制读取操作将阻塞多少毫秒。如果返回值为0计时器就被禁止了该线程将无限期阻塞 Netty 提供的 主要 TCP 参数 如下。
1、SO_TIMEOUT控制读取操作将阻塞多少毫秒。如果返回值为 0计时器就被禁止了该线程将无限期阻塞
2、SO_SNDBUF套接字使用的发送缓冲区大小 2、SO_SNDBUF套接字使用的发送缓冲区大小
3、SO_RCVBUF套接字使用的接收缓冲区大小 3、SO_RCVBUF套接字使用的接收缓冲区大小
4、SO_REUSEADDR用于决定 如果网络上仍然有数据向旧的 ServerSocket 传输数据,是否允许新的 ServerSocket 绑定到与旧的 ServerSocket 同样的端口上。SO_REUSEADDR选项 的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口; 4、SO_REUSEADDR用于决定 如果网络上仍然有数据向旧的 ServerSocket 传输数据,是否允许新的 ServerSocket 绑定到与旧的 ServerSocket 同样的端口上。SO_REUSEADDR 选项 的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口;
5、CONNECT_TIMEOUT_MILLIS客户端连接超时时间由于 NIO原生的客户端 并不提供设置连接超时的接口因此Netty 采用的是自定义连接超时定时器负责检测和超时控制; 5、CONNECT_TIMEOUT_MILLIS客户端连接超时时间由于 NIO 原生的客户端 并不提供设置连接超时的接口因此Netty 采用的是自定义连接超时定时器负责检测和超时控制;
Channel 接口:用于指定客户端使用的 Channel 接口,对于 TCP 客户端连接,默认使用 NioSocketChannel代码如下。
Channel接口用于指定客户端使用的 Channel接口对于 TCP客户端连接默认使用 NioSocketChannel代码如下。
```java ```java
public B channel(Class<? extends C> channelClass) { public B channel(Class<? extends C> channelClass) {
if (channelClass == null) { if (channelClass == null) {
throw new NullPointerException("channelClass"); throw new NullPointerException("channelClass");
} else { } else {
return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass))); return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
} }
} }
``` ```
BootstrapChannelFactory 利用 参数channelClass通过反射机制创建 NioSocketChannel对象。
设置 Handler接口Bootstrap 为了简化 Handler 的编排,提供了 Channellnitializer它继承了 ChannelHandlerAdapter当 TCP链路 注册成功之后,调用 initChannel 接口,用于设置用户 ChanneIHandler。它的代码如下。 BootstrapChannelFactory 利用 参数 channelClass通过反射机制创建 NioSocketChannel 对象。
设置 Handler 接口Bootstrap 为了简化 Handler 的编排,提供了 Channellnitializer它继承了 ChannelHandlerAdapter当 TCP 链路 注册成功之后,调用 initChannel 接口,用于设置用户 ChanneIHandler。它的代码如下。
```java ```java
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter { public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
@ -97,13 +108,17 @@ public abstract class ChannelInitializer<C extends Channel> extends ChannelInbou
} }
} }
``` ```
最后一个比较重要的接口就是发起客户端连接,代码如下。 最后一个比较重要的接口就是发起客户端连接,代码如下。
```java ```java
ChannelFuture f = b.connect(host, port).sync(); ChannelFuture f = b.connect(host, port).sync();
``` ```
### 客户端连接操作 ### 客户端连接操作
首先要创建和初始化 NioSocketChannel代码如下。 首先要创建和初始化 NioSocketChannel代码如下。
```java ```java
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> { public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
@ -197,51 +212,62 @@ public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel> {
} }
} }
``` ```
需要注意的是SocketChannel 执行 connect() 操作后有以下三种结果。 需要注意的是SocketChannel 执行 connect() 操作后有以下三种结果。
1. 连接成功返回True
2. 暂时没有连接上,服务端没有返回 ACK应答连接结果不确定返回False
3. 连接失败,直接抛出 IO异常。
如果是第二种结果,需要将 NioSocketChannel 中的 selectionKey 设置为 OP_CONNECT监听连接结果。异步连接返回之后需要判断连接结果如果连接成功则触发 ChannelActive 事件。ChannelActive事件 最终会将 NioSocketChannel 中的 selectionKey 设置为 SelectionKey.OP_READ用于监听网络读操作。如果没有立即连接上服务端则注册 SelectionKey.OP_CONNECT 到多路复用器。如果连接过程发生异常,则关闭链路,进入连接失败处理流程。 1. 连接成功,返回 True
2. 暂时没有连接上,服务端没有返回 ACK 应答,连接结果不确定,返回 False
3. 连接失败,直接抛出 IO 异常。
如果是第二种结果,需要将 NioSocketChannel 中的 selectionKey 设置为 OP_CONNECT监听连接结果。异步连接返回之后需要判断连接结果如果连接成功则触发 ChannelActive 事件。ChannelActive 事件 最终会将 NioSocketChannel 中的 selectionKey 设置为 SelectionKey.OP_READ用于监听网络读操作。如果没有立即连接上服务端则注册 SelectionKey.OP_CONNECT 到多路复用器。如果连接过程发生异常,则关闭链路,进入连接失败处理流程。
### 异步连接结果通知 ### 异步连接结果通知
NioEventLoop 的 Selector 轮询 客户端连接Channel当服务端返回握手应答之后对连接结果进行判断代码如下。
NioEventLoop 的 Selector 轮询 客户端连接 Channel当服务端返回握手应答之后对连接结果进行判断代码如下。
```java ```java
if ((readyOps & 8) != 0) { if ((readyOps & 8) != 0) {
int ops = k.interestOps(); int ops = k.interestOps();
ops &= -9; ops &= -9;
k.interestOps(ops); k.interestOps(ops);
unsafe.finishConnect(); unsafe.finishConnect();
} }
``` ```
下面对 finishConnect()方法 进行分析,代码如下。 下面对 finishConnect()方法 进行分析,代码如下。
```java ```java
try { try {
boolean wasActive = AbstractNioChannel.this.isActive(); boolean wasActive = AbstractNioChannel.this.isActive();
AbstractNioChannel.this.doFinishConnect(); AbstractNioChannel.this.doFinishConnect();
this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, wasActive); this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, wasActive);
} catch (Throwable var5) { } catch (Throwable var5) {
...... ......
} }
``` ```
doFinishConnect()方法 用于判断 JDK 的 SocketChannel 的连接结果,如果未出错 表示连接成功,其他值或者发生异常表示连接失败。 doFinishConnect()方法 用于判断 JDK 的 SocketChannel 的连接结果,如果未出错 表示连接成功,其他值或者发生异常表示连接失败。
```java ```java
protected void doFinishConnect() throws Exception { protected void doFinishConnect() throws Exception {
if (!this.javaChannel().finishConnect()) { if (!this.javaChannel().finishConnect()) {
throw new Error(); throw new Error();
} }
} }
``` ```
连接成功之后,调用 fufillConectPromise()方法,触发链路激活事件,该事件由 ChannelPipeline 进行传播。 连接成功之后,调用 fufillConectPromise()方法,触发链路激活事件,该事件由 ChannelPipeline 进行传播。
### 客户端连接超时机制 ### 客户端连接超时机制
对于SocketChannel接口JDK并没有提供连接超时机制需要NIO框架或者用户自己扩展实现。Netty利用定时器提供了客户端连接超时控制功能下面我们对该功能进行详细讲解。
首先,用户在创建Netty 客户端的时候可以通过ChannelOption.CONNECT_TIMEOUT_MILLIS配置项设置连接超时时间代码如下。 对于 SocketChannel 接口JDK 并没有提供连接超时机制,需要 NIO 框架或者用户自己扩展实现。Netty 利用定时器提供了客户端连接超时控制功能,下面我们对该功能进行详细讲解。
首先,用户在创建 Netty 客户端的时候,可以通过 ChannelOption.CONNECT_TIMEOUT_MILLIS 配置项设置连接超时时间,代码如下。
```java ```java
Bootstrap b = new Bootstrap(); Bootstrap b = new Bootstrap();
b.group(workerGroup); b.group(workerGroup);
b.channel(NioSocketChannel.class); b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true); b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
``` ```

@ -1,18 +1,20 @@
## Netty 服务端创建源码分析 ## Netty 服务端创建源码分析
当我们直接使用 JDK 的 NIO类库 开发基于 NIO 的异步服务端时,需要用到 多路复用器Selector、ServerSocketChannel、SocketChannel、ByteBuffer、SelectionKey 等,相比于传统的 BIO开发NIO 的开发要复杂很多开发出稳定、高性能的异步通信框架一直是个难题。Netty 为了向使用者屏蔽 NIO通信 的底层细节在和用户交互的边界做了封装目的就是为了减少用户开发工作量降低开发难度。ServerBootstrap 是 Socket服务端 的启动辅助类,用户通过 ServerBootstrap 可以方便地创建 Netty 的服务端。
当我们直接使用 JDK 的 NIO 类库 开发基于 NIO 的异步服务端时,需要用到 多路复用器 Selector、ServerSocketChannel、SocketChannel、ByteBuffer、SelectionKey 等,相比于传统的 BIO 开发NIO 的开发要复杂很多开发出稳定、高性能的异步通信框架一直是个难题。Netty 为了向使用者屏蔽 NIO 通信 的底层细节在和用户交互的边界做了封装目的就是为了减少用户开发工作量降低开发难度。ServerBootstrap 是 Socket 服务端 的启动辅助类,用户通过 ServerBootstrap 可以方便地创建 Netty 的服务端。
### Netty 服务端创建时序图 ### Netty 服务端创建时序图
![avatar](../../../images/Netty/Netty服务端创建时序图.png) ![avatar](../../../images/Netty/Netty服务端创建时序图.png)
下面我们对 Netty服务端创建 的关键步骤和原理进行详细解析。 下面我们对 Netty 服务端创建 的关键步骤和原理进行详细解析。
1、**创建 ServerBootstrap实例**。ServerBootstrap 是 Netty服务端 的 启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层对各种 原生NIO 的 API 进行了封装,减少了用户与 底层API 的接触降低了开发难度。ServerBootstrap 中只有一个 public 的无参的构造函数可以给用户直接使用ServerBootstrap 只开放一个无参的构造函数 的根本原因是 它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入 Builder建造者模式。 1、**创建 ServerBootstrap 实例**。ServerBootstrap 是 Netty 服务端 的 启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层对各种 原生 NIO 的 API 进行了封装,减少了用户与 底层 API 的接触降低了开发难度。ServerBootstrap 中只有一个 public 的无参的构造函数可以给用户直接使用ServerBootstrap 只开放一个无参的构造函数 的根本原因是 它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入 Builder 建造者模式。
2、**设置并绑定 Reactor线程池**。Netty 的 Reactor线程池 是 EventLoopGroup它实际上是一个 EventLoop数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 ChannelSelector 的轮询操作由绑定的 EventLoop线程 的 run()方法 驱动在一个循环体内循环执行。值得说明的是EventLoop 的职责不仅仅是处理 网络IO事件用户自定义的Task 和 定时任务Task 也统一由 EventLoop 负责处理,这样线程模型就实现了统一。从调度层面看,也不存在从 EventLoop线程 中再启动其他类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了 IO线程 的处理和调度性能。 2、**设置并绑定 Reactor 线程池**。Netty 的 Reactor 线程池 是 EventLoopGroup它实际上是一个 EventLoop 数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 ChannelSelector 的轮询操作由绑定的 EventLoop 线程 的 run()方法 驱动在一个循环体内循环执行。值得说明的是EventLoop 的职责不仅仅是处理 网络 IO 事件,用户自定义的 Task 和 定时任务 Task 也统一由 EventLoop 负责处理,这样线程模型就实现了统一。从调度层面看,也不存在从 EventLoop 线程 中再启动其他类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了 IO 线程 的处理和调度性能。
3、**设置并绑定 服务端Channel**。作为 NIO服务端需要创建 ServerSocketChannelNetty 对 原生NIO类库 进行了封装对应的实现是NioServerSocketChannel。对于用户而言不需要关心 服务端Channel 的底层实现细节和工作原理,只需要指定具体使用哪种服务端 Channel 即可。因此Netty 中 ServerBootstrap的基类 提供了 channel()方法,用于指定 服务端Channel 的类型。Netty 通过工厂类,利用反射创建 NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用因此反射对性能的影响并不大。相关代 3、**设置并绑定 服务端 Channel**。作为 NIO 服务端,需要创建 ServerSocketChannelNetty 对 原生 NIO 类库 进行了封装,对应的实现是 NioServerSocketChannel。对于用户而言不需要关心 服务端 Channel 的底层实现细节和工作原理,只需要指定具体使用哪种服务端 Channel 即可。因此Netty 中 ServerBootstrap 的基类 提供了 channel()方法,用于指定 服务端 Channel 的类型。Netty 通过工厂类,利用反射创建 NioServerSocketChannel 对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代
码如下。 码如下。
```java ```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable { public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
@ -28,7 +30,8 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
} }
``` ```
4、**链路建立的时候创建并初始化 ChannelPipeline**。ChannelPipeline 并不是 NIO服务端 必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。网络事件以事件流的形式在 ChannelPipeline 中流转,由 ChannelPipeline 根据 ChannelHandler的执行策略 调度 ChannelHandler的执行。典型的网络事件如下。 4、**链路建立的时候创建并初始化 ChannelPipeline**。ChannelPipeline 并不是 NIO 服务端 必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。网络事件以事件流的形式在 ChannelPipeline 中流转,由 ChannelPipeline 根据 ChannelHandler 的执行策略 调度 ChannelHandler 的执行。典型的网络事件如下。
1. 链路注册; 1. 链路注册;
2. 链路激活; 2. 链路激活;
3. 链路断开; 3. 链路断开;
@ -38,7 +41,8 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
7. 链路发生异常; 7. 链路发生异常;
8. 发生用户自定义事件。 8. 发生用户自定义事件。
5、**初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler**。ChannelHandler 是 Netty 提供给用户定制和扩展的关键接口。利用 ChannelHandler 用户可以完成大多数的功能定制例如消息编解码、心跳、安全认证、TSL/SSL 认证、流量控制和流量整形等。Netty 同时也提供了大量的 系统ChannelHandler 供用户使用,比较实用的 系统ChannelHandler 总结如下。 5、**初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler**。ChannelHandler 是 Netty 提供给用户定制和扩展的关键接口。利用 ChannelHandler 用户可以完成大多数的功能定制例如消息编解码、心跳、安全认证、TSL/SSL 认证、流量控制和流量整形等。Netty 同时也提供了大量的 系统 ChannelHandler 供用户使用,比较实用的 系统 ChannelHandler 总结如下。
1. 系统编解码框架ByteToMessageCodec 1. 系统编解码框架ByteToMessageCodec
2. 基于长度的半包解码器LengthFieldBasedFrameDecoder 2. 基于长度的半包解码器LengthFieldBasedFrameDecoder
3. 码流日志打印 HandlerLoggingHandler 3. 码流日志打印 HandlerLoggingHandler
@ -46,7 +50,8 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
5. 链路空闲检测 HandlerIdleStateHandler 5. 链路空闲检测 HandlerIdleStateHandler
6. 流量整形 HandlerChannelTrafficShapingHandler 6. 流量整形 HandlerChannelTrafficShapingHandler
7. Base64 编解码Base64Decoder 和 Base64Encoder。 7. Base64 编解码Base64Decoder 和 Base64Encoder。
创建和添加 ChannelHandler 的代码示例如下。 创建和添加 ChannelHandler 的代码示例如下。
```java ```java
.childHandler( new ChannelInitializer<SocketChannel>() { .childHandler( new ChannelInitializer<SocketChannel>() {
@Override @Override
@ -58,7 +63,8 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
6、**绑定并启动监听端口**。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将 ServerSocketChannel 注册到 Selector 上监听客户端连接。 6、**绑定并启动监听端口**。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将 ServerSocketChannel 注册到 Selector 上监听客户端连接。
7、**Selector 轮询**。由 Reactor线程 NioEventLoop 负责调度和执行 Selector 轮询操作,选择准备就绪的 Channel集合相关代码如下。 7、**Selector 轮询**。由 Reactor 线程 NioEventLoop 负责调度和执行 Selector 轮询操作,选择准备就绪的 Channel 集合,相关代码如下。
```java ```java
public final class NioEventLoop extends SingleThreadEventLoop { public final class NioEventLoop extends SingleThreadEventLoop {
@ -76,11 +82,12 @@ public final class NioEventLoop extends SingleThreadEventLoop {
} }
``` ```
8、**当轮询到 准备就绪的Channel 之后,就由 Reactor线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。 8、**当轮询到 准备就绪的 Channel 之后,就由 Reactor 线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。
![avatar](../../../images/Netty/ChannelPipeline的调度相关方法.png) ![avatar](../../../images/Netty/ChannelPipeline的调度相关方法.png)
9、**执行 Netty 中 系统的ChannelHandler 和 用户添加定制的ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler相关代码如下。 9、**执行 Netty 中 系统的 ChannelHandler 和 用户添加定制的 ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler相关代码如下。
```java ```java
public class DefaultChannelPipeline implements ChannelPipeline { public class DefaultChannelPipeline implements ChannelPipeline {
@ -92,13 +99,17 @@ public class DefaultChannelPipeline implements ChannelPipeline {
} }
``` ```
### 结合 Netty源码 对服务端的创建过程进行解析 ### 结合 Netty 源码 对服务端的创建过程进行解析
首先通过构造函数创建 ServerBootstrap实例随后通常会创建两个 EventLoopGroup实例 (也可以只创建一个并共享),代码如下。
首先通过构造函数创建 ServerBootstrap 实例,随后,通常会创建两个 EventLoopGroup 实例 (也可以只创建一个并共享),代码如下。
```java ```java
EventLoopGroup acceptorGroup = new NioEventLoopGroup(); EventLoopGroup acceptorGroup = new NioEventLoopGroup();
EventLoopGroup iOGroup = new NioEventLoopGroup(); EventLoopGroup iOGroup = new NioEventLoopGroup();
``` ```
NioEventLoopGroup 实际就是一个 Reactor线程池负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过 ServerBootstrap 的 group()方法 将两个 EventLoopGroup实例 传入,代码如下。
NioEventLoopGroup 实际就是一个 Reactor 线程池,负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过 ServerBootstrap 的 group()方法 将两个 EventLoopGroup 实例 传入,代码如下。
```java ```java
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> { public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
@ -120,7 +131,9 @@ public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerCh
} }
} }
``` ```
其中 parentGroup对象 被设置进了 ServerBootstrap 的父类 AbstractBootstrap 中,代码如下。
其中 parentGroup 对象 被设置进了 ServerBootstrap 的父类 AbstractBootstrap 中,代码如下。
```java ```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable { public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
@ -142,7 +155,9 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
} }
} }
``` ```
该方法会被客户端和服务端重用,用于设置 工作IO线程执行和调度网络事件的读写。线程组和线程类型设置完成后需要设置 服务端Channel 用于端口监听和客户端链路接入。Netty 通过 Channel工厂类 来创建不同类型的 Channel对于服务端需要创建 NioServerSocketChannel。所以通过指定 Channel类型 的方式创建 Channel工厂。ReflectiveChannelFactory 可以根据 Channel的类型 通过反射创建 Channel的实例服务端需要创建的是 NioServerSocketChannel实例代码如下。
该方法会被客户端和服务端重用,用于设置 工作 IO 线程,执行和调度网络事件的读写。线程组和线程类型设置完成后,需要设置 服务端 Channel 用于端口监听和客户端链路接入。Netty 通过 Channel 工厂类 来创建不同类型的 Channel对于服务端需要创建 NioServerSocketChannel。所以通过指定 Channel 类型 的方式创建 Channel 工厂。ReflectiveChannelFactory 可以根据 Channel 的类型 通过反射创建 Channel 的实例,服务端需要创建的是 NioServerSocketChannel 实例,代码如下。
```java ```java
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> { public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
@ -168,17 +183,19 @@ public class ReflectiveChannelFactory<T extends Channel> implements ChannelFacto
} }
} }
``` ```
指定 NioServerSocketChannel 后,需要设置 TCP 的一些参数,作为服务端,主要是设置 TCP 的 backlog参数。
backlog 指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列 和 已连接队列,根据 TCP三次握手 的 三个子过程来分隔这两个队列。服务器处于 listen状态 时,收到客户端 syn过程(connect) 时在未完成队列中创建一个新的条目,然后用三次握手的第二个过程,即服务器的 syn响应客户端此条目在第三个过程到达前 (客户端对服务器 syn 的 ack) 一直保留在未完成连接队列中,如果三次握手完成,该条目将从未完成连接队列搬到已完成连接队列尾部。当进程调用 accept 时从已完成队列中的头部取出一个条目给进程当已完成队列为空时进程将睡眠直到有条目在已完成连接队列中才唤醒。backlog 被规定为两个队列总和的最大值,大多数实现默认值为 5但在高并发 Web服务器 中此值显然不够。 需要设置此值更大一些的原因是,未完成连接队列的长度可能因为客户端 syn 的到达及等待三次握手的第三个过程延时 而增大。Netty 默认的 backlog 为 100当然用户可以修改默认值这需要根据实际场景和网络状况进行灵活设置 指定 NioServerSocketChannel 后,需要设置 TCP 的一些参数,作为服务端,主要是设置 TCP 的 backlog 参数
TCP参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。 backlog 指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列 和 已连接队列,根据 TCP 三次握手 的 三个子过程来分隔这两个队列。服务器处于 listen 状态 时,收到客户端 syn 过程(connect) 时在未完成队列中创建一个新的条目,然后用三次握手的第二个过程,即服务器的 syn 响应客户端,此条目在第三个过程到达前 (客户端对服务器 syn 的 ack) 一直保留在未完成连接队列中,如果三次握手完成,该条目将从未完成连接队列搬到已完成连接队列尾部。当进程调用 accept 时从已完成队列中的头部取出一个条目给进程当已完成队列为空时进程将睡眠直到有条目在已完成连接队列中才唤醒。backlog 被规定为两个队列总和的最大值,大多数实现默认值为 5但在高并发 Web 服务器 中此值显然不够。 需要设置此值更大一些的原因是,未完成连接队列的长度可能因为客户端 syn 的到达及等待三次握手的第三个过程延时 而增大。Netty 默认的 backlog 为 100当然用户可以修改默认值这需要根据实际场景和网络状况进行灵活设置。
TCP 参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。
![avatar](../../../images/Netty/ServerBootstrap的Handler模型.png) ![avatar](../../../images/Netty/ServerBootstrap的Handler模型.png)
本质区别就是ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的所有连接该监听端口的客户端都会执行它父类AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。 本质区别就是ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有连接该监听端口的客户端都会执行它;父类 AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。
服务端启动的最后一步,就是绑定本地端口,启动服务,下面我们来分析下这部分代码。 服务端启动的最后一步,就是绑定本地端口,启动服务,下面我们来分析下这部分代码。
```java ```java
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable { public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
@ -219,7 +236,9 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
} }
} }
``` ```
先看下上述代码调用的 initAndRegister()方法。它首先实例化了一个 NioServerSocketChannel类型 的 Channel对象。相关代码如下。
先看下上述代码调用的 initAndRegister()方法。它首先实例化了一个 NioServerSocketChannel 类型 的 Channel 对象。相关代码如下。
```java ```java
final ChannelFuture initAndRegister() { final ChannelFuture initAndRegister() {
Channel channel = null; Channel channel = null;
@ -248,7 +267,9 @@ public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C ext
return regFuture; return regFuture;
} }
``` ```
NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作主要有以下三点。 NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作主要有以下三点。
```java ```java
@Override @Override
void init(Channel channel) throws Exception { void init(Channel channel) throws Exception {
@ -303,9 +324,11 @@ NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作
}); });
} }
``` ```
到此Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。
到此Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor 线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。
![avatar](../../../images/Netty/NioServerSocketChannel的ChannelPipeline.png) ![avatar](../../../images/Netty/NioServerSocketChannel的ChannelPipeline.png)
最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor线程 的多路复用器上监听新客户端的接入,代码如下。 最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor 线程 的多路复用器上监听新客户端的接入,代码如下。
```java ```java
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
@ -395,10 +418,13 @@ public abstract class AbstractNioChannel extends AbstractChannel {
} }
} }
``` ```
到此,服务端监听启动部分源码已经分析完成。 到此,服务端监听启动部分源码已经分析完成。
## 结合 Netty源码 对客户端接入过程进行解析 ## 结合 Netty 源码 对客户端接入过程进行解析
负责处理网络读写、连接和客户端请求接入的 Reactor线程 就是 NioEventLoop下面我们看下 NioEventLoop 是如何处理新的客户端连接接入的。当 多路复用器 检测到新的准备就绪的 Channel 时,默认执行 processSelectedKeysOptimized()方法,代码如下。
负责处理网络读写、连接和客户端请求接入的 Reactor 线程 就是 NioEventLoop下面我们看下 NioEventLoop 是如何处理新的客户端连接接入的。当 多路复用器 检测到新的准备就绪的 Channel 时,默认执行 processSelectedKeysOptimized()方法,代码如下。
```java ```java
public final class NioEventLoop extends SingleThreadEventLoop { public final class NioEventLoop extends SingleThreadEventLoop {
@ -590,11 +616,13 @@ public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerCh
} }
} }
``` ```
下面我们展开看下 NioSocketChannel 的 register()方法。NioSocketChannel 的注册方法与 ServerSocketChannel 的一致, 也是将 Channel 注册到 Reactor线程 的多路复用器上。由于注册的操作位是 0所以此时 NioSocketChannel 还不能读取客户端发送的消息,下面我们看看 是什么时候修改监听操作位为 OP_READ 的。
执行完注册操作之后,紧接着会触发 ChannelReadComplete 事件。我们继续分析 ChannelReadComplete 在 ChannelPipeline 中的处理流程Netty 的 Header 和 Tail 本身不关注 ChannelReadComplete事件 就直接透传,执行完 ChannelReadComplete 后,接着执行 PipeLine 的 read()方法,最终执行 HeadHandler 的 read()方法。 下面我们展开看下 NioSocketChannel 的 register()方法。NioSocketChannel 的注册方法与 ServerSocketChannel 的一致, 也是将 Channel 注册到 Reactor 线程 的多路复用器上。由于注册的操作位是 0所以此时 NioSocketChannel 还不能读取客户端发送的消息,下面我们看看 是什么时候修改监听操作位为 OP_READ 的。
执行完注册操作之后,紧接着会触发 ChannelReadComplete 事件。我们继续分析 ChannelReadComplete 在 ChannelPipeline 中的处理流程Netty 的 Header 和 Tail 本身不关注 ChannelReadComplete 事件 就直接透传,执行完 ChannelReadComplete 后,接着执行 PipeLine 的 read()方法,最终执行 HeadHandler 的 read()方法。
HeadHandler 的 read()方法用来将网络操作位修改为读操作。创建 NioSocketChannel 的时候已经将 AbstractNioChannel 的 readInterestOp 设置为 OP\_ READ这样执行 selectionKey. interestOps(interestOps | readInterestOp)操作 时就会把操作位设置为 OP_READ。代码如下。
HeadHandler 的 read()方法用来将网络操作位修改为读操作。创建 NioSocketChannel 的时候已经将 AbstractNioChannel 的 readInterestOp 设置为 OP_ READ这样执行 selectionKey. interestOps(interestOps | readInterestOp)操作 时就会把操作位设置为 OP_READ。代码如下。
```java ```java
public abstract class AbstractNioByteChannel extends AbstractNioChannel { public abstract class AbstractNioByteChannel extends AbstractNioChannel {
@ -603,4 +631,5 @@ public abstract class AbstractNioByteChannel extends AbstractNioChannel {
} }
} }
``` ```
到此,新接入的客户端连接处理完成,可以进行网络读写等 IO操作。
到此,新接入的客户端连接处理完成,可以进行网络读写等 IO 操作。

@ -1,7 +1,9 @@
理论性的文字,我觉得就没必要再扯一遍咯,大道理讲这么多,越听越迷糊。不如直接看源码加注释来的明白痛快。所以话不多说,直接上源码。 理论性的文字,我觉得就没必要再扯一遍咯,大道理讲这么多,越听越迷糊。不如直接看源码加注释来的明白痛快。所以话不多说,直接上源码。
## 1 主要的接口 ## 1 主要的接口
### 1.1 Advice 通知 ### 1.1 Advice 通知
本接口定义了切面的增强方式,如:前置增强 BeforeAdvice后置增强 AfterAdvice异常增强 ThrowsAdvice 等。下面看两个主要的子接口的源码。 本接口定义了切面的增强方式,如:前置增强 BeforeAdvice后置增强 AfterAdvice异常增强 ThrowsAdvice 等。下面看两个主要的子接口的源码。
```java ```java
@ -21,7 +23,9 @@ public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable; void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
} }
``` ```
### 1.2 Pointcut 方法的横切面 ### 1.2 Pointcut 方法的横切面
本接口用来定义需要增强的目标方法的集合一般使用正则表达式去匹配筛选指定范围内的所有满足条件的目标方法。Pointcut 接口有很多实现,我们主要看一下 JdkRegexpMethodPointcut 和 NameMatchMethodPointcut 的实现原理,前者主要通过正则表达式对方法名进行匹配,后者则通过匹配方法名进行匹配。 本接口用来定义需要增强的目标方法的集合一般使用正则表达式去匹配筛选指定范围内的所有满足条件的目标方法。Pointcut 接口有很多实现,我们主要看一下 JdkRegexpMethodPointcut 和 NameMatchMethodPointcut 的实现原理,前者主要通过正则表达式对方法名进行匹配,后者则通过匹配方法名进行匹配。
```java ```java
@ -45,7 +49,9 @@ public interface AfterReturningAdvice extends AfterAdvice {
return false; return false;
} }
``` ```
### 1.3 Advisor 通知器 ### 1.3 Advisor 通知器
将 Pointcut 和 Advice 有效地结合在一起。它定义了在哪些方法Pointcut上执行哪些动作Advice。下面看一下 DefaultPointcutAdvisor 的源码实现,它通过持有 Pointcut 和 Advice 属性来将两者有效地结合在一起。 将 Pointcut 和 Advice 有效地结合在一起。它定义了在哪些方法Pointcut上执行哪些动作Advice。下面看一下 DefaultPointcutAdvisor 的源码实现,它通过持有 Pointcut 和 Advice 属性来将两者有效地结合在一起。
```java ```java
@ -88,10 +94,13 @@ public abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdv
} }
} }
``` ```
## 2 Spring AOP 的设计与实现 ## 2 Spring AOP 的设计与实现
AOP 的实现代码中,主要使用了 JDK 动态代理,在特定场景下(被代理对象没有 implements 的接口)也用到了 CGLIB 生成代理对象。通过 AOP 的源码设计可以看到,其先为目标对象建立了代理对象,这个代理对象的生成可以使用 JDK 动态代理或 CGLIB 完成。然后启动为代理对象配置的拦截器,对横切面(目标方法集合)进行相应的增强,将 AOP 的横切面设计和 Proxy 模式有机地结合起来,实现了在 AOP 中定义好的各种织入方式。 AOP 的实现代码中,主要使用了 JDK 动态代理,在特定场景下(被代理对象没有 implements 的接口)也用到了 CGLIB 生成代理对象。通过 AOP 的源码设计可以看到,其先为目标对象建立了代理对象,这个代理对象的生成可以使用 JDK 动态代理或 CGLIB 完成。然后启动为代理对象配置的拦截器,对横切面(目标方法集合)进行相应的增强,将 AOP 的横切面设计和 Proxy 模式有机地结合起来,实现了在 AOP 中定义好的各种织入方式。
### 2.1 ProxyFactoryBean ### 2.1 ProxyFactoryBean
这里我们主要以 ProxyFactoryBean 的实现为例,对 AOP 的实现原理进行分析。ProxyFactoryBean 主要持有目标对象 target 的代理对象 aopProxy和 Advisor 通知器,而 Advisor 持有 Advice 和 Pointcut这样就可以判断 aopProxy 中的方法 是否是某个指定的切面 Pointcut然后根据其配置的织入方向前置增强/后置增强),通过反射为其织入相应的增强行为 Advice。先看一下 ProxyFactoryBean 的配置和使用。 这里我们主要以 ProxyFactoryBean 的实现为例,对 AOP 的实现原理进行分析。ProxyFactoryBean 主要持有目标对象 target 的代理对象 aopProxy和 Advisor 通知器,而 Advisor 持有 Advice 和 Pointcut这样就可以判断 aopProxy 中的方法 是否是某个指定的切面 Pointcut然后根据其配置的织入方向前置增强/后置增强),通过反射为其织入相应的增强行为 Advice。先看一下 ProxyFactoryBean 的配置和使用。
```xml ```xml
@ -111,7 +120,9 @@ AOP 的实现代码中,主要使用了 JDK 动态代理,在特定场景下
</property> </property>
</bean> </bean>
``` ```
### 2.2 为配置的 target 生成 AopProxy 代理对象 ### 2.2 为配置的 target 生成 AopProxy 代理对象
ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化,然后根据被代理对象类型的不同,生成代理对象。 ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化,然后根据被代理对象类型的不同,生成代理对象。
```java ```java
@ -135,6 +146,7 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化,
} }
} }
``` ```
### 2.3 初始化 Advisor 链 ### 2.3 初始化 Advisor 链
```java ```java
@ -193,9 +205,11 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化,
this.advisorChainInitialized = true; this.advisorChainInitialized = true;
} }
``` ```
生成 singleton 的代理对象在 getSingletonInstance 方法中完成,这是 ProxyFactoryBean 生成 AopProxy 代理对象的调用入口。代理对象会封装对 target 对象的调用,针对 target 对象的方法调用会被这里生成的代理对象所拦截。 生成 singleton 的代理对象在 getSingletonInstance 方法中完成,这是 ProxyFactoryBean 生成 AopProxy 代理对象的调用入口。代理对象会封装对 target 对象的调用,针对 target 对象的方法调用会被这里生成的代理对象所拦截。
### 2.4 生成单例代理对象 ### 2.4 生成单例代理对象
```java ```java
/** /**
* 返回此类代理对象的单例实例,如果尚未创建该实例,则单例地创建它 * 返回此类代理对象的单例实例,如果尚未创建该实例,则单例地创建它
@ -288,9 +302,10 @@ public class ProxyCreatorSupport extends AdvisedSupport {
} }
``` ```
可以看到其根据目标对象是否实现了接口,而决定是使用 JDK动态代理 还是 CGLIB 去生成代理对象,而 AopProxy 接口的实现类也只有 JdkDynamicAopProxy 和 CglibAopProxy 这两个。 可以看到其根据目标对象是否实现了接口,而决定是使用 JDK 动态代理 还是 CGLIB 去生成代理对象,而 AopProxy 接口的实现类也只有 JdkDynamicAopProxy 和 CglibAopProxy 这两个。
### 2.5 JDK 动态代理 生成 AopProxy 代理对象
### 2.5 JDK动态代理 生成 AopProxy代理对象
```java ```java
/** /**
* 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy * 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy
@ -330,9 +345,10 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
} }
``` ```
通过 JdkDynamicAopProxy 的源码可以非常清楚地看到,其使用了 JDK动态代理 的方式生成了 代理对象。JdkDynamicAopProxy 实现了 InvocationHandler 接口,并通过 java.lang.reflect.Proxy 的 newProxyInstance()静态方法 生成代理对象并返回。 通过 JdkDynamicAopProxy 的源码可以非常清楚地看到,其使用了 JDK 动态代理 的方式生成了 代理对象。JdkDynamicAopProxy 实现了 InvocationHandler 接口,并通过 java.lang.reflect.Proxy 的 newProxyInstance()静态方法 生成代理对象并返回。
### 2.6 CGLIB 生成 AopProxy 代理对象
### 2.6 CGLIB 生成 AopProxy代理对象
```java ```java
final class CglibAopProxy implements AopProxy, Serializable { final class CglibAopProxy implements AopProxy, Serializable {
@ -413,13 +429,17 @@ final class CglibAopProxy implements AopProxy, Serializable {
} }
} }
``` ```
为 目标对象target 生成 代理对象 之后,在调用 代理对象 的目标方法时,目标方法会进行 invoke()回调JDK动态代理 或 callbacks()回调CGLIB然后就可以在回调方法中对目标对象的目标方法进行拦截和增强处理了。
为 目标对象 target 生成 代理对象 之后,在调用 代理对象 的目标方法时,目标方法会进行 invoke()回调JDK 动态代理) 或 callbacks()回调CGLIB然后就可以在回调方法中对目标对象的目标方法进行拦截和增强处理了。
## 3 Spring AOP 拦截器调用的实现 ## 3 Spring AOP 拦截器调用的实现
在 Spring AOP 通过 JDK 的 Proxy类 生成代理对象时,相关的拦截器已经配置到了代理对象持有的 InvocationHandler(即ProxyBeanFactory) 的 invoke() 方法中,拦截器最后起作用,是通过调用代理对象的目标方法时,在代理类中触发了 InvocationHandler 的 invoke() 回调。通过 CGLIB 实现的 AOP原理与此相似。
在 Spring AOP 通过 JDK 的 Proxy 类 生成代理对象时,相关的拦截器已经配置到了代理对象持有的 InvocationHandler(即ProxyBeanFactory) 的 invoke() 方法中,拦截器最后起作用,是通过调用代理对象的目标方法时,在代理类中触发了 InvocationHandler 的 invoke() 回调。通过 CGLIB 实现的 AOP原理与此相似。
### 3.1 JdkDynamicAopProxy 的 invoke() 拦截 ### 3.1 JdkDynamicAopProxy 的 invoke() 拦截
前面已经通过两种不同的方式生成了 AopProxy 代理对象,下面我们先看一下 JdkDynamicAopProxy 中的 invoke()回调方法 中对拦截器调用的实现。 前面已经通过两种不同的方式生成了 AopProxy 代理对象,下面我们先看一下 JdkDynamicAopProxy 中的 invoke()回调方法 中对拦截器调用的实现。
```java ```java
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
@ -503,7 +523,9 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
} }
} }
``` ```
### 3.2 CglibAopProxy 的 intercept() 拦截 ### 3.2 CglibAopProxy 的 intercept() 拦截
CglibAopProxy 的 intercept() 回调方法实现和 JdkDynamicAopProxy 的 invoke() 非常相似,只是在 CglibAopProxy 中构造 CglibMethodInvocation 对象来完成拦截器链的调用,而在 JdkDynamicAopProxy 中则是通过构造 ReflectiveMethodInvocation 对象来完成的。 CglibAopProxy 的 intercept() 回调方法实现和 JdkDynamicAopProxy 的 invoke() 非常相似,只是在 CglibAopProxy 中构造 CglibMethodInvocation 对象来完成拦截器链的调用,而在 JdkDynamicAopProxy 中则是通过构造 ReflectiveMethodInvocation 对象来完成的。
```java ```java
@ -552,7 +574,9 @@ final class CglibAopProxy implements AopProxy, Serializable {
} }
} }
``` ```
### 3.3 目标对象中目标方法的调用 ### 3.3 目标对象中目标方法的调用
对目标对象中目标方法的调用,是在 AopUtils 工具类中利用反射机制完成的,具体代码如下。 对目标对象中目标方法的调用,是在 AopUtils 工具类中利用反射机制完成的,具体代码如下。
```java ```java
@ -583,7 +607,9 @@ public abstract class AopUtils {
} }
} }
``` ```
### 3.4 AOP 拦截器链的调用 ### 3.4 AOP 拦截器链的调用
JdkDynamicAopProxy 和 CglibAopProxy 虽然使用了不同的代理对象,但对 AOP 拦截的处理却是相同的,都是通过 ReflectiveMethodInvocation 的 proceed() 方法实现的。 JdkDynamicAopProxy 和 CglibAopProxy 虽然使用了不同的代理对象,但对 AOP 拦截的处理却是相同的,都是通过 ReflectiveMethodInvocation 的 proceed() 方法实现的。
```java ```java
@ -651,8 +677,11 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea
} }
} }
``` ```
### 3.5 配置通知器 ### 3.5 配置通知器
AdvisedSupport 中实现了获取拦截器链的方法,并使用了缓存。 AdvisedSupport 中实现了获取拦截器链的方法,并使用了缓存。
```java ```java
public class AdvisedSupport extends ProxyConfig implements Advised { public class AdvisedSupport extends ProxyConfig implements Advised {
@ -845,7 +874,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
} }
``` ```
注意Advisor 本身就被配置为 bean所以它的获取也是通过 IoC容器 获得的。 注意Advisor 本身就被配置为 bean所以它的获取也是通过 IoC 容器 获得的。
### 3.6 Advice 通知的实现 ### 3.6 Advice 通知的实现
@ -989,7 +1018,7 @@ class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {
} }
``` ```
可以看到,其中的 getInterceptor()方法 把 Advice 从 Advisor 中取出来,然后创建了一个 MethodBeforeAdviceInterceptor对象并返回这个对象中持有对 Advice 的引用。下面我们看一下 MethodBeforeAdviceInterceptor 拦截器的源码实现。 可以看到,其中的 getInterceptor()方法 把 Advice 从 Advisor 中取出来,然后创建了一个 MethodBeforeAdviceInterceptor 对象,并返回,这个对象中持有对 Advice 的引用。下面我们看一下 MethodBeforeAdviceInterceptor 拦截器的源码实现。
```java ```java
public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable {
@ -1018,7 +1047,7 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Seriali
可以看到MethodBeforeAdviceInterceptor 的 invoke()方法 先是触发了 advice 的 before()方法,然后才是 MethodInvocation 的 proceed()方法调用。 可以看到MethodBeforeAdviceInterceptor 的 invoke()方法 先是触发了 advice 的 before()方法,然后才是 MethodInvocation 的 proceed()方法调用。
回顾一下之前的代码,在 AopProxy代理对象 触发的 ReflectiveMethodInvocation 的 proceed() 中,在取得 拦截器interceptor 后调用了其 invoke()方法。按照 AOP 的配置规则ReflectiveMethodInvocation 触发的拦截器 invoke()回调,最终会根据 Advice 类型的不同,触发 Spring 对不同的 Advice 的拦截器封装,比如 MethodBeforeAdvice 最终会触发 MethodBeforeAdviceInterceptor 的 invoke()回调,其它两个以此类推,这里就不逐一分析咯。 回顾一下之前的代码,在 AopProxy 代理对象 触发的 ReflectiveMethodInvocation 的 proceed() 中,在取得 拦截器 interceptor 后调用了其 invoke()方法。按照 AOP 的配置规则ReflectiveMethodInvocation 触发的拦截器 invoke()回调,最终会根据 Advice 类型的不同,触发 Spring 对不同的 Advice 的拦截器封装,比如 MethodBeforeAdvice 最终会触发 MethodBeforeAdviceInterceptor 的 invoke()回调,其它两个以此类推,这里就不逐一分析咯。
另外,可以结合我 GitHub 上对 Spring框架源码 的阅读及个人理解一起看,会更有助于各位开发大佬理解,如果本内容对你们有帮助的,还望各位同学 watchstarfork素质三连一波地址 另外,可以结合我 GitHub 上对 Spring 框架源码 的阅读及个人理解一起看,会更有助于各位开发大佬理解,如果本内容对你们有帮助的,还望各位同学 watchstarfork素质三连一波地址
https://github.com/AmyliaY/spring-aop-reading https://github.com/AmyliaY/spring-aop-reading

@ -1,4 +1,5 @@
最近在看 Spring AOP 部分的源码所以对JDK动态代理具体是如何实现的这件事产生了很高的兴趣而且能从源码上了解这个原理的话也有助于对 spring-aop 模块的理解。话不多说,上代码。 最近在看 Spring AOP 部分的源码,所以对 JDK 动态代理具体是如何实现的这件事产生了很高的兴趣,而且能从源码上了解这个原理的话,也有助于对 spring-aop 模块的理解。话不多说,上代码。
```java ```java
/** /**
* 一般会使用实现了 InvocationHandler接口 的类作为代理对象的生产工厂, * 一般会使用实现了 InvocationHandler接口 的类作为代理对象的生产工厂,

@ -1,12 +1,16 @@
# Spring AOP 如何生效 # Spring AOP 如何生效
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 解析 ## 解析
- 在使用 Spring AOP 技术的时候会有下面这段代码在xml配置文件中出现,来达到 Spring 支持 AOP
- 在使用 Spring AOP 技术的时候会有下面这段代码在 xml 配置文件中出现,来达到 Spring 支持 AOP
```xml ```xml
<aop:aspectj-autoproxy/> <aop:aspectj-autoproxy/>
``` ```
- 源码阅读目标找到了,那么怎么去找入口或者对这句话的标签解析方法呢?项目中使用搜索 - 源码阅读目标找到了,那么怎么去找入口或者对这句话的标签解析方法呢?项目中使用搜索
![image-20200115083744268](../../../images/spring/image-20200115083744268.png) ![image-20200115083744268](../../../images/spring/image-20200115083744268.png)
@ -18,6 +22,7 @@
- 类图 - 类图
![image-20200115084031725](../../../images/spring/image-20200115084031725.png) ![image-20200115084031725](../../../images/spring/image-20200115084031725.png)
```java ```java
@Override @Override
@Nullable @Nullable
@ -50,7 +55,9 @@
} }
``` ```
- `org.springframework.aop.config.AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)` - `org.springframework.aop.config.AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)`
```java ```java
@Nullable @Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
@ -61,7 +68,9 @@
} }
``` ```
- `org.springframework.aop.config.AopConfigUtils.registerOrEscalateApcAsRequired` - `org.springframework.aop.config.AopConfigUtils.registerOrEscalateApcAsRequired`
```java ```java
/** /**
* 注册或者升级 bean * 注册或者升级 bean
@ -103,7 +112,9 @@
} }
``` ```
### org.springframework.aop.config.AopNamespaceUtils.useClassProxyingIfNecessary ### org.springframework.aop.config.AopNamespaceUtils.useClassProxyingIfNecessary
```java ```java
/** /**
* proxy-target-class 和 expose-proxy 标签处理 * proxy-target-class 和 expose-proxy 标签处理
@ -124,7 +135,9 @@
} }
``` ```
- `org.springframework.aop.config.AopConfigUtils.forceAutoProxyCreatorToUseClassProxying` - `org.springframework.aop.config.AopConfigUtils.forceAutoProxyCreatorToUseClassProxying`
```java ```java
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
@ -134,8 +147,9 @@
} }
``` ```
- `forceAutoProxyCreatorToExposeProxy`方法就不贴出代码了,操作和`forceAutoProxyCreatorToUseClassProxying`一样都是将读取到的数据放入bean对象作为一个属性存储
- `forceAutoProxyCreatorToExposeProxy`方法就不贴出代码了,操作和`forceAutoProxyCreatorToUseClassProxying`一样都是将读取到的数据放入 bean 对象作为一个属性存储
## 总结 ## 总结
- 实现`org.springframework.beans.factory.xml.BeanDefinitionParser`接口的类,多用于对xml标签的解析,并且入口为`parse`方法,如果是一个bean对象通常会和Spring监听器一起出现
- 实现`org.springframework.beans.factory.xml.BeanDefinitionParser`接口的类,多用于对 xml 标签的解析,并且入口为`parse`方法,如果是一个 bean 对象通常会和 Spring 监听器一起出现

@ -1,7 +1,8 @@
## 前言 ## 前言
之前一直想系统的拜读一下 spring 的源码,看看它到底是如何吸引身边的大神们对它的设计赞不绝口,虽然每天工作很忙,每天下班后总感觉脑子内存溢出,想去放松一下,但总是以此为借口,恐怕会一直拖下去。所以每天下班虽然有些疲惫,但还是按住自己啃下这块硬骨头。 之前一直想系统的拜读一下 spring 的源码,看看它到底是如何吸引身边的大神们对它的设计赞不绝口,虽然每天工作很忙,每天下班后总感觉脑子内存溢出,想去放松一下,但总是以此为借口,恐怕会一直拖下去。所以每天下班虽然有些疲惫,但还是按住自己啃下这块硬骨头。
spring 源码这种东西真的是一回生二回熟第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向不知道看到的这些方法调用的是哪个父类的实现IoC相关的类图实在太复杂咯继承体系又深又广但当你耐下心来多走几遍会发现越看越熟练每次都能 get 到新的点。 spring 源码这种东西真的是一回生二回熟第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向不知道看到的这些方法调用的是哪个父类的实现IoC 相关的类图实在太复杂咯,继承体系又深又广),但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。
另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解(写博文或部门内部授课)加强印象。 另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解(写博文或部门内部授课)加强印象。
@ -13,7 +14,9 @@ spring-context https://github.com/AmyliaY/spring-context-reading
## 正文 ## 正文
当我们传入一个 Spring 配置文件去实例化 FileSystemXmlApplicationContext 时,可以看一下它的构造方法都做了什么。 当我们传入一个 Spring 配置文件去实例化 FileSystemXmlApplicationContext 时,可以看一下它的构造方法都做了什么。
```java ```java
/** /**
* 下面这 4 个构造方法都调用了第 5 个构造方法 * 下面这 4 个构造方法都调用了第 5 个构造方法
@ -69,7 +72,9 @@ protected Resource getResourceByPath(String path) {
return new FileSystemResource(path); return new FileSystemResource(path);
} }
``` ```
看看其父类 AbstractApplicationContext 实现的 refresh() 方法,该方法就是 IoC 容器初始化的入口类 看看其父类 AbstractApplicationContext 实现的 refresh() 方法,该方法就是 IoC 容器初始化的入口类
```java ```java
/** /**
* 容器初始化的过程BeanDefinition 的 Resource 定位、BeanDefinition 的载入、BeanDefinition 的注册。 * 容器初始化的过程BeanDefinition 的 Resource 定位、BeanDefinition 的载入、BeanDefinition 的注册。
@ -133,7 +138,9 @@ public void refresh() throws BeansException, IllegalStateException {
} }
} }
``` ```
看看 obtainFreshBeanFactory() 方法,该方法告诉了子类去刷新内部的 beanFactory 看看 obtainFreshBeanFactory() 方法,该方法告诉了子类去刷新内部的 beanFactory
```java ```java
/** /**
* Tell the subclass to refresh the internal bean factory. * Tell the subclass to refresh the internal bean factory.
@ -152,7 +159,9 @@ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
return beanFactory; return beanFactory;
} }
``` ```
下面看一下 AbstractRefreshableApplicationContext 中对 refreshBeanFactory() 方法的实现。FileSystemXmlApplicationContext 从上层体系的各抽象类中继承了大量的方法实现,抽象类中抽取大量公共行为进行具体实现,留下 abstract 的个性化方法交给具体的子类实现,这是一个很好的 OOP 编程设计,我们在自己编码时也可以尝试这样设计自己的类图。理清 FileSystemXmlApplicationContext 的上层体系设计,就不易被各种设计模式搞晕咯。 下面看一下 AbstractRefreshableApplicationContext 中对 refreshBeanFactory() 方法的实现。FileSystemXmlApplicationContext 从上层体系的各抽象类中继承了大量的方法实现,抽象类中抽取大量公共行为进行具体实现,留下 abstract 的个性化方法交给具体的子类实现,这是一个很好的 OOP 编程设计,我们在自己编码时也可以尝试这样设计自己的类图。理清 FileSystemXmlApplicationContext 的上层体系设计,就不易被各种设计模式搞晕咯。
```java ```java
/** /**
* 在这里完成了容器的初始化,并赋值给自己私有的 beanFactory 属性,为下一步调用做准备 * 在这里完成了容器的初始化,并赋值给自己私有的 beanFactory 属性,为下一步调用做准备
@ -183,7 +192,9 @@ protected final void refreshBeanFactory() throws BeansException {
} }
} }
``` ```
AbstractXmlApplicationContext 中对 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的实现。 AbstractXmlApplicationContext 中对 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的实现。
```java ```java
/* /*
* 实现了基类 AbstractRefreshableApplicationContext 的抽象方法 * 实现了基类 AbstractRefreshableApplicationContext 的抽象方法
@ -210,7 +221,9 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw
loadBeanDefinitions(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader);
} }
``` ```
继续看 AbstractXmlApplicationContext 中 loadBeanDefinitions() 的重载方法。 继续看 AbstractXmlApplicationContext 中 loadBeanDefinitions() 的重载方法。
```java ```java
/** /**
* 用传进来的 XmlBeanDefinitionReader 读取器加载 Xml 文件中的 BeanDefinition * 用传进来的 XmlBeanDefinitionReader 读取器加载 Xml 文件中的 BeanDefinition
@ -239,7 +252,9 @@ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansE
} }
} }
``` ```
AbstractBeanDefinitionReader 中对 loadBeanDefinitions 方法的各种重载及调用。 AbstractBeanDefinitionReader 中对 loadBeanDefinitions 方法的各种重载及调用。
```java ```java
/** /**
* loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String) * loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String)
@ -318,7 +333,9 @@ public int loadBeanDefinitions(String location, Set<Resource> actualResources) t
} }
} }
``` ```
resourceLoader 的 getResource() 方法有多种实现,看清 FileSystemXmlApplicationContext 的继承体系就可以明确,其走的是 DefaultResourceLoader 中的实现。 resourceLoader 的 getResource() 方法有多种实现,看清 FileSystemXmlApplicationContext 的继承体系就可以明确,其走的是 DefaultResourceLoader 中的实现。
```java ```java
/** /**
* 获取 Resource 的具体实现方法 * 获取 Resource 的具体实现方法
@ -345,7 +362,9 @@ public Resource getResource(String location) {
} }
} }
``` ```
其中的 getResourceByPath(location) 方法的实现则是在 FileSystemXmlApplicationContext 中完成的。 其中的 getResourceByPath(location) 方法的实现则是在 FileSystemXmlApplicationContext 中完成的。
```java ```java
/** /**
* 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作 * 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作
@ -359,4 +378,5 @@ protected Resource getResourceByPath(String path) {
return new FileSystemResource(path); return new FileSystemResource(path);
} }
``` ```
至此我们可以看到FileSystemXmlApplicationContext 的 getResourceByPath() 方法返回了一个 FileSystemResource 对象,接下来 spring 就可以对这个对象进行相关的 I/O 操作,进行 BeanDefinition 的读取和载入了。 至此我们可以看到FileSystemXmlApplicationContext 的 getResourceByPath() 方法返回了一个 FileSystemResource 对象,接下来 spring 就可以对这个对象进行相关的 I/O 操作,进行 BeanDefinition 的读取和载入了。

@ -1,4 +1,5 @@
## 前言 ## 前言
接着上一篇的 BeanDefinition 资源定位开始讲。Spring IoC 容器 BeanDefinition 解析过程就是把用户在配置文件中配置的 bean解析并封装成 IoC 容器可以装载的 BeanDefinition 对象BeanDefinition 是 Spring 定义的基本数据结构,其中的属性与配置文件中 bean 的属性相对应。 接着上一篇的 BeanDefinition 资源定位开始讲。Spring IoC 容器 BeanDefinition 解析过程就是把用户在配置文件中配置的 bean解析并封装成 IoC 容器可以装载的 BeanDefinition 对象BeanDefinition 是 Spring 定义的基本数据结构,其中的属性与配置文件中 bean 的属性相对应。
PS可以结合我 GitHub 上对 Spring 框架源码的阅读及个人理解一起看,会更有助于各位开发大佬理解。地址如下。 PS可以结合我 GitHub 上对 Spring 框架源码的阅读及个人理解一起看,会更有助于各位开发大佬理解。地址如下。
@ -6,6 +7,7 @@ spring-beans https://github.com/AmyliaY/spring-beans-reading
spring-context https://github.com/AmyliaY/spring-context-reading spring-context https://github.com/AmyliaY/spring-context-reading
## 正文 ## 正文
首先看一下 AbstractRefreshableApplicationContext 的 refreshBeanFactory() 方法,这是一个模板方法,其中调用的 loadBeanDefinitions() 方法是一个抽象方法,交由子类实现。 首先看一下 AbstractRefreshableApplicationContext 的 refreshBeanFactory() 方法,这是一个模板方法,其中调用的 loadBeanDefinitions() 方法是一个抽象方法,交由子类实现。
```java ```java
@ -38,7 +40,9 @@ protected final void refreshBeanFactory() throws BeansException {
} }
} }
``` ```
下面看一下 AbstractRefreshableApplicationContext 的子类 AbstractXmlApplicationContext 对 loadBeanDefinitions() 方法的实现。 下面看一下 AbstractRefreshableApplicationContext 的子类 AbstractXmlApplicationContext 对 loadBeanDefinitions() 方法的实现。
```java ```java
@Override @Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
@ -62,7 +66,9 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw
loadBeanDefinitions(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader);
} }
``` ```
接着看一下上面最后一个调用的方法 loadBeanDefinitions(XmlBeanDefinitionReader reader)。 接着看一下上面最后一个调用的方法 loadBeanDefinitions(XmlBeanDefinitionReader reader)。
```java ```java
/** /**
* 读取并解析 .xml 文件中配置的 bean然后封装成 BeanDefinition 对象 * 读取并解析 .xml 文件中配置的 bean然后封装成 BeanDefinition 对象
@ -91,7 +97,9 @@ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansE
} }
} }
``` ```
AbstractBeanDefinitionReader 对 loadBeanDefinitions() 方法的三重重载。 AbstractBeanDefinitionReader 对 loadBeanDefinitions() 方法的三重重载。
```java ```java
/** /**
* loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String location) * loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String location)
@ -170,7 +178,9 @@ public int loadBeanDefinitions(String location, Set<Resource> actualResources) t
} }
} }
``` ```
XmlBeanDefinitionReader 读取器中的方法执行流,按代码的先后顺序。 XmlBeanDefinitionReader 读取器中的方法执行流,按代码的先后顺序。
```java ```java
/** /**
* XmlBeanDefinitionReader 加载资源的入口方法 * XmlBeanDefinitionReader 加载资源的入口方法
@ -283,7 +293,9 @@ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanD
return getRegistry().getBeanDefinitionCount() - countBefore; return getRegistry().getBeanDefinitionCount() - countBefore;
} }
``` ```
文档解析器 DefaultBeanDefinitionDocumentReader 对配置文件中元素的解析。 文档解析器 DefaultBeanDefinitionDocumentReader 对配置文件中元素的解析。
```java ```java
// 根据 Spring 对 Bean 的定义规则进行解析 // 根据 Spring 对 Bean 的定义规则进行解析
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
@ -516,7 +528,9 @@ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate d
} }
} }
``` ```
看一下 BeanDefinitionParserDelegate 中对 bean 元素的详细解析过程。 看一下 BeanDefinitionParserDelegate 中对 bean 元素的详细解析过程。
```java ```java
/** /**
* 解析 <bean> 元素的入口 * 解析 <bean> 元素的入口
@ -666,7 +680,9 @@ public AbstractBeanDefinition parseBeanDefinitionElement(
return null; return null;
} }
``` ```
对 bean 的部分子元素进行解析的具体实现。 对 bean 的部分子元素进行解析的具体实现。
```java ```java
/** /**
* 解析 <bean> 元素中所有的 <property> 子元素 * 解析 <bean> 元素中所有的 <property> 子元素
@ -917,4 +933,5 @@ protected void parseCollectionElements(NodeList elementNodes, Collection<Object>
} }
} }
``` ```
经过这样逐层地解析,我们在配置文件中定义的 bean 就被整个解析成了 IoC 容器能够装载和使用的 BeanDefinition对象这种数据结构可以让 IoC 容器执行索引、查询等操作。经过上述解析,接下来我们就可以将得到的 BeanDefinition对象 注册到 IoC 容器中咯。
经过这样逐层地解析,我们在配置文件中定义的 bean 就被整个解析成了 IoC 容器能够装载和使用的 BeanDefinition 对象,这种数据结构可以让 IoC 容器执行索引、查询等操作。经过上述解析,接下来我们就可以将得到的 BeanDefinition 对象 注册到 IoC 容器中咯。

@ -1,5 +1,6 @@
## 前言 ## 前言
这篇文章分享一下 spring IoC 容器初始化第三部分的代码,也就是将前面解析出来的 BeanDefinition对象 注册进 IoC 容器,其实就是存入一个 ConcurrentHashMap<String, BeanDefinition> 中。
这篇文章分享一下 spring IoC 容器初始化第三部分的代码,也就是将前面解析出来的 BeanDefinition 对象 注册进 IoC 容器,其实就是存入一个 ConcurrentHashMap<String, BeanDefinition> 中。
PS可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学理解,地址: PS可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学理解,地址:
spring-beans https://github.com/AmyliaY/spring-beans-reading spring-beans https://github.com/AmyliaY/spring-beans-reading
@ -7,7 +8,9 @@ spring-context https://github.com/AmyliaY/spring-context-reading
## 正文 ## 正文
回过头看一下前面在 DefaultBeanDefinitionDocumentReader 中实现的 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法。 回过头看一下前面在 DefaultBeanDefinitionDocumentReader 中实现的 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法。
```java ```java
/** /**
* 将 .xml 文件中的元素解析成 BeanDefinition对象并注册到 IoC容器 中 * 将 .xml 文件中的元素解析成 BeanDefinition对象并注册到 IoC容器 中
@ -38,7 +41,9 @@ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate d
} }
} }
``` ```
接着看一下 BeanDefinitionReaderUtils 的 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法。 接着看一下 BeanDefinitionReaderUtils 的 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法。
```java ```java
/** /**
* 将解析到的 BeanDefinition对象 注册到 IoC容器 * 将解析到的 BeanDefinition对象 注册到 IoC容器
@ -65,7 +70,9 @@ public static void registerBeanDefinition(
} }
} }
``` ```
BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法在 DefaultListableBeanFactory 实现类中的具体实现。 BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法在 DefaultListableBeanFactory 实现类中的具体实现。
```java ```java
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {

@ -1,17 +1,19 @@
## 前言 ## 前言
前面我们主要分析了 FileSystemXmlApplicationContext 这个具体的 IoC容器实现类 的初始化源码,在 IoC容器 中建立了 beanName 到 BeanDefinition 的数据映射,通过一个 ConcurrentHashMap。现在我们来看一下 Spring 是如何将 IoC 容器中存在依赖关系的 bean 根据配置联系在一起的。
Spring 中触发 IoC容器“依赖注入” 的方式有两种,一个是应用程序通过 getBean()方法 向容器索要 bean实例 时触发依赖注入;另一个是提前给 bean 配置了 lazy-init 属性为 falseSpring 在 IoC容器 初始化会自动调用此 bean 的 getBean() 方法,提前完成依赖注入。总的来说,想提高运行时获取 bean 的效率,可以考虑配置此属性 前面我们主要分析了 FileSystemXmlApplicationContext 这个具体的 IoC 容器实现类 的初始化源码,在 IoC 容器 中建立了 beanName 到 BeanDefinition 的数据映射,通过一个 ConcurrentHashMap。现在我们来看一下 Spring 是如何将 IoC 容器中存在依赖关系的 bean 根据配置联系在一起的
下面我将分别解读这两种依赖注入的触发方式,先看 getBean() 的,因为 lazy-init 最后也是通过调用 getBean() 完成的依赖注入 Spring 中触发 IoC 容器“依赖注入” 的方式有两种,一个是应用程序通过 getBean()方法 向容器索要 bean 实例 时触发依赖注入;另一个是提前给 bean 配置了 lazy-init 属性为 falseSpring 在 IoC 容器 初始化会自动调用此 bean 的 getBean() 方法,提前完成依赖注入。总的来说,想提高运行时获取 bean 的效率,可以考虑配置此属性
下面我将分别解读这两种依赖注入的触发方式,先看 getBean() 的,因为 lazy-init 最后也是通过调用 getBean() 完成的依赖注入。
PS可以结合我 GitHub 上对 Spring 框架源码的阅读及个人理解一起看,会更有助于各位开发姥爷理解,地址: PS可以结合我 GitHub 上对 Spring 框架源码的阅读及个人理解一起看,会更有助于各位开发姥爷理解,地址:
spring-beans https://github.com/AmyliaY/spring-beans-reading spring-beans https://github.com/AmyliaY/spring-beans-reading
spring-context https://github.com/AmyliaY/spring-context-reading spring-context https://github.com/AmyliaY/spring-context-reading
## 正文 ## 正文
首先看一下 AbstractBeanFactory 中的 getBean() 系列方法及 doGetBean() 具体实现。 首先看一下 AbstractBeanFactory 中的 getBean() 系列方法及 doGetBean() 具体实现。
```java ```java
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@ -211,7 +213,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
} }
} }
``` ```
总的来说getBean() 方法是依赖注入的起点,之后会调用 createBean(),根据之前解析生成的 BeanDefinition对象 生成 bean 对象,下面我们看看 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中对 createBean() 的具体实现。
总的来说getBean() 方法是依赖注入的起点,之后会调用 createBean(),根据之前解析生成的 BeanDefinition 对象 生成 bean 对象,下面我们看看 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中对 createBean() 的具体实现。
```java ```java
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory { implements AutowireCapableBeanFactory {
@ -375,7 +379,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
} }
} }
``` ```
从源码中可以看到 createBeanInstance() 和 populateBean() 这两个方法与依赖注入的实现非常密切createBeanInstance() 方法中生成了 bean 所包含的 Java 对象populateBean() 方法对这些生成的 bean 对象之间的依赖关系进行了处理。下面我们先看一下 createBeanInstance() 方法的实现。 从源码中可以看到 createBeanInstance() 和 populateBean() 这两个方法与依赖注入的实现非常密切createBeanInstance() 方法中生成了 bean 所包含的 Java 对象populateBean() 方法对这些生成的 bean 对象之间的依赖关系进行了处理。下面我们先看一下 createBeanInstance() 方法的实现。
```java ```java
/** /**
* 创建 bean 的实例对象 * 创建 bean 的实例对象
@ -459,7 +465,9 @@ protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefin
} }
} }
``` ```
从源码中我们可以看到其调用了 SimpleInstantiationStrategy 实现类来生成 bean 对象,这个类是 Spring 用来生成 bean对象 的默认类,它提供了两种策略来实例化 bean对象一种是利用 Java 的反射机制,另一种是直接使用 CGLIB。
从源码中我们可以看到其调用了 SimpleInstantiationStrategy 实现类来生成 bean 对象,这个类是 Spring 用来生成 bean 对象 的默认类,它提供了两种策略来实例化 bean 对象,一种是利用 Java 的反射机制,另一种是直接使用 CGLIB。
```java ```java
public class SimpleInstantiationStrategy implements InstantiationStrategy { public class SimpleInstantiationStrategy implements InstantiationStrategy {
@ -514,7 +522,9 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
} }
} }
``` ```
在 SimpleInstantiationStrategy 的子类 CglibSubclassingInstantiationStrategy 中可以看到使用 CGLIB 进行实例化的源码实现。 在 SimpleInstantiationStrategy 的子类 CglibSubclassingInstantiationStrategy 中可以看到使用 CGLIB 进行实例化的源码实现。
```java ```java
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy { public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
@ -571,7 +581,9 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
} }
} }
``` ```
至此,完成了 bean对象 的实例化,然后就可以根据解析得到的 BeanDefinition对象 完成对各个属性的赋值处理,也就是依赖注入。这个实现方法就是前面 AbstractAutowireCapableBeanFactory 类中的 populateBean() 方法。
至此,完成了 bean 对象 的实例化,然后就可以根据解析得到的 BeanDefinition 对象 完成对各个属性的赋值处理,也就是依赖注入。这个实现方法就是前面 AbstractAutowireCapableBeanFactory 类中的 populateBean() 方法。
```java ```java
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory { implements AutowireCapableBeanFactory {
@ -787,7 +799,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
} }
} }
``` ```
BeanDefinitionValueResolver 中解析属性值,对注入类型进行转换的具体实现。 BeanDefinitionValueResolver 中解析属性值,对注入类型进行转换的具体实现。
```java ```java
class BeanDefinitionValueResolver { class BeanDefinitionValueResolver {
@ -1005,7 +1019,9 @@ class BeanDefinitionValueResolver {
} }
} }
``` ```
至此,已经为依赖注入做好了准备,下面就该将 bean对象 设置到它所依赖的另一个 bean 的属性中去。AbstractPropertyAccessor 和其子类 BeanWrapperImpl 完成了依赖注入的详细过程。先看一下 AbstractPropertyAccessor 中的实现。
至此,已经为依赖注入做好了准备,下面就该将 bean 对象 设置到它所依赖的另一个 bean 的属性中去。AbstractPropertyAccessor 和其子类 BeanWrapperImpl 完成了依赖注入的详细过程。先看一下 AbstractPropertyAccessor 中的实现。
```java ```java
public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor { public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor {
@ -1068,7 +1084,9 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
} }
} }
``` ```
最后看一下 BeanWrapperImpl 中的实现。 最后看一下 BeanWrapperImpl 中的实现。
```java ```java
public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper { public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper {
@ -1386,11 +1404,14 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
} }
} }
``` ```
至此,完成了对 bean 的各种属性的依赖注入,在 bean 的实例化和依赖注入的过程中,需要依据 BeanDefinition 中的信息来递归地完成依赖注入。另外,在此过程中存在许多递归调用,一个递归是在上下文体系中查找 当前bean依赖的bean 和创建 当前bean依赖的bean 的递归调用;另一个是在依赖注入时,通过递归调用容器的 getBean() 方法,得到当前 bean 的依赖 bean同时也触发对依赖 bean 的创建和注入;在对 bean 的属性进行依赖注入时解析的过程也是递归的。这样根据依赖关系从最末层的依赖bean开始一层一层地完成 bean 的创建和注入,直到最后完成当前 bean 的创建。
至此,完成了对 bean 的各种属性的依赖注入,在 bean 的实例化和依赖注入的过程中,需要依据 BeanDefinition 中的信息来递归地完成依赖注入。另外,在此过程中存在许多递归调用,一个递归是在上下文体系中查找 当前 bean 依赖的 bean 和创建 当前 bean 依赖的 bean 的递归调用;另一个是在依赖注入时,通过递归调用容器的 getBean() 方法,得到当前 bean 的依赖 bean同时也触发对依赖 bean 的创建和注入;在对 bean 的属性进行依赖注入时,解析的过程也是递归的。这样,根据依赖关系,从最末层的依赖 bean 开始,一层一层地完成 bean 的创建和注入,直到最后完成当前 bean 的创建。
## lazy-init 属性触发的依赖注入 ## lazy-init 属性触发的依赖注入
最后看一下 lazy-init 触发的预实例化和依赖注入,发生在 IoC 容器完成对 BeanDefinition 的定位、载入、解析和注册之后。通过牺牲 IoC 容器初始化的性能,来有效提升应用第一次获取该 bean 的效率。 最后看一下 lazy-init 触发的预实例化和依赖注入,发生在 IoC 容器完成对 BeanDefinition 的定位、载入、解析和注册之后。通过牺牲 IoC 容器初始化的性能,来有效提升应用第一次获取该 bean 的效率。
lazy-init 实现的入口方法在我们前面解读过的 AbstractApplicationContext 的 refresh() 中,它是 IoC 容器正式启动的标志。 lazy-init 实现的入口方法在我们前面解读过的 AbstractApplicationContext 的 refresh() 中,它是 IoC 容器正式启动的标志。
```java ```java
public abstract class AbstractApplicationContext extends DefaultResourceLoader public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean { implements ConfigurableApplicationContext, DisposableBean {

@ -1,4 +1,4 @@
BeanPostProcessor接口 也叫 Bean后置处理器作用是在Bean对象实例化和依赖注入完成后在显示调用bean的init-method(初始化方法)的前后添加我们自己的处理逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的接口的源码如下。 BeanPostProcessor 接口 也叫 Bean 后置处理器,作用是在 Bean 对象实例化和依赖注入完成后,在显示调用 bean init-method(初始化方法)的前后添加我们自己的处理逻辑。注意是 Bean 实例化完毕后及依赖注入完成后触发的,接口的源码如下。
```java ```java
public interface BeanPostProcessor { public interface BeanPostProcessor {
@ -15,4 +15,4 @@ public interface BeanPostProcessor {
} }
``` ```
使用方法也很简单,实现 BeanPostProcessor接口然后将实现类注入IoC容器即可。 使用方法也很简单,实现 BeanPostProcessor 接口,然后将实现类注入 IoC 容器即可。

@ -1,9 +1,8 @@
# Spring JDBC # Spring JDBC
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 环境搭建 ## 环境搭建
- 依赖 - 依赖
@ -14,7 +13,7 @@
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.47' compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.47'
``` ```
- db配置 - db 配置
```properties ```properties
jdbc.url= jdbc.url=
@ -49,8 +48,6 @@
} }
``` ```
- DAO - DAO
```JAVA ```JAVA
@ -62,8 +59,6 @@
``` ```
- 实现类 - 实现类
```JAVA ```JAVA
@ -101,8 +96,6 @@
``` ```
- xml - xml
```xml ```xml
@ -177,12 +170,6 @@
``` ```
## 链接对象构造 ## 链接对象构造
`Connection con = DataSourceUtils.getConnection(obtainDataSource());` `Connection con = DataSourceUtils.getConnection(obtainDataSource());`
@ -258,10 +245,6 @@
``` ```
## 释放资源 ## 释放资源
`releaseConnection(con, dataSource);` `releaseConnection(con, dataSource);`
@ -302,10 +285,6 @@ public static void doReleaseConnection(@Nullable Connection con, @Nullable DataS
} }
``` ```
### org.springframework.transaction.support.ResourceHolderSupport ### org.springframework.transaction.support.ResourceHolderSupport
链接数 链接数
@ -328,12 +307,6 @@ public static void doReleaseConnection(@Nullable Connection con, @Nullable DataS
} }
``` ```
## 查询解析 ## 查询解析
### org.springframework.jdbc.core.JdbcTemplate ### org.springframework.jdbc.core.JdbcTemplate
@ -344,16 +317,17 @@ public static void doReleaseConnection(@Nullable Connection con, @Nullable DataS
</bean> </bean>
``` ```
- 从配置中可以知道 JdbcTemplate 需要 dataSource 属性, 就从这里开始讲起 - 从配置中可以知道 JdbcTemplate 需要 dataSource 属性, 就从这里开始讲起
- `org.springframework.jdbc.support.JdbcAccessor.setDataSource`, 这段代码就只做了赋值操作(依赖注入) - `org.springframework.jdbc.support.JdbcAccessor.setDataSource`, 这段代码就只做了赋值操作(依赖注入)
```java ```java
public void setDataSource(@Nullable DataSource dataSource) { public void setDataSource(@Nullable DataSource dataSource) {
this.dataSource = dataSource; this.dataSource = dataSource;
} }
``` ```
- 下面`hsLogDao`也是依赖注入本篇不做详细讲述。
- 下面`hsLogDao`也是依赖注入本篇不做详细讲述。
### org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.RowMapper<T>) ### org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.RowMapper<T>)
@ -466,12 +440,6 @@ public void setDataSource(@Nullable DataSource dataSource) {
} }
``` ```
## 插入解析 ## 插入解析
```java ```java
@ -515,8 +483,3 @@ public void setDataSource(@Nullable DataSource dataSource) {
} }
``` ```

@ -1,18 +1,25 @@
# Spring RMI # Spring RMI
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
- Spring 远程服务调用 - Spring 远程服务调用
## DEMO ## DEMO
### 服务提供方 ### 服务提供方
- 服务提供方需要准备**接口**、**接口实现泪** - 服务提供方需要准备**接口**、**接口实现泪**
- 接口 - 接口
```java ```java
public interface IDemoRmiService { public interface IDemoRmiService {
int add(int a, int b); int add(int a, int b);
} }
``` ```
- 接口实现 - 接口实现
```java ```java
public class IDemoRmiServiceImpl implements IDemoRmiService { public class IDemoRmiServiceImpl implements IDemoRmiService {
@Override @Override
@ -21,7 +28,9 @@ public class IDemoRmiServiceImpl implements IDemoRmiService {
} }
} }
``` ```
- xml配置文件
- xml 配置文件
```xml ```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -41,7 +50,9 @@ public class IDemoRmiServiceImpl implements IDemoRmiService {
</bean> </bean>
</beans> </beans>
``` ```
- 运行 - 运行
```java ```java
public class RMIServerSourceCode { public class RMIServerSourceCode {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
@ -50,8 +61,11 @@ public class RMIServerSourceCode {
} }
``` ```
### 服务调用方 ### 服务调用方
- xml 配置 - xml 配置
```java ```java
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" <beans xmlns="http://www.springframework.org/schema/beans"
@ -66,8 +80,10 @@ public class RMIServerSourceCode {
</beans> </beans>
``` ```
**注意:rmi使用小写**
**注意:rmi 使用小写**
大写报错信息: 大写报错信息:
```text ```text
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'rmiClient' defined in class path resource [rmi/RMIClientSourceCode.xml]: Invocation of init method failed; nested exception is org.springframework.remoting.RemoteLookupFailureException: Service URL [RMI://localhost:9999/springRmi] is invalid; nested exception is java.net.MalformedURLException: invalid URL scheme: RMI://localhost:9999/springRmi Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'rmiClient' defined in class path resource [rmi/RMIClientSourceCode.xml]: Invocation of init method failed; nested exception is org.springframework.remoting.RemoteLookupFailureException: Service URL [RMI://localhost:9999/springRmi] is invalid; nested exception is java.net.MalformedURLException: invalid URL scheme: RMI://localhost:9999/springRmi
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1795) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1795)
@ -99,7 +115,9 @@ Caused by: java.net.MalformedURLException: invalid URL scheme: RMI://localhost:9
... 17 more ... 17 more
``` ```
- 运行 - 运行
```java ```java
public class RMIClientSourceCode { public class RMIClientSourceCode {
public static void main(String[] args) { public static void main(String[] args) {
@ -112,10 +130,15 @@ public class RMIClientSourceCode {
``` ```
--- ---
## 服务端初始化 ## 服务端初始化
- `org.springframework.remoting.rmi.RmiServiceExporter`,该类定义了spring的远程服务信息
- `org.springframework.remoting.rmi.RmiServiceExporter`,该类定义了 spring 的远程服务信息
### RmiServiceExporter ### RmiServiceExporter
- 基础属性如下 - 基础属性如下
```java ```java
public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean { public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean {
/** /**
@ -173,9 +196,10 @@ public class RMIClientSourceCode {
private boolean createdRegistry = false; private boolean createdRegistry = false;
} }
``` ```
- 属性设置不说了,看接口实现了那些 - 属性设置不说了,看接口实现了那些
1. InitializingBean: 初始化bean调用`afterPropertiesSet`方法 1. InitializingBean: 初始化 bean 调用`afterPropertiesSet`方法
2. DisposableBean: 摧毁bean调用`destroy`方法 2. DisposableBean: 摧毁 bean 调用`destroy`方法
```java ```java
@ -268,7 +292,9 @@ public class RMIClientSourceCode {
``` ```
#### checkService #### checkService
- 校验service 是否为空
- 校验 service 是否为空
```java ```java
/** /**
* Check whether the service reference has been set. * Check whether the service reference has been set.
@ -282,8 +308,10 @@ public class RMIClientSourceCode {
``` ```
#### getRegistry #### getRegistry
- 获取 Registry 实例 - 获取 Registry 实例
- 简单说就是通过host和port创建socket - 简单说就是通过 host 和 port 创建 socket
```java ```java
protected Registry getRegistry(String registryHost, int registryPort, protected Registry getRegistry(String registryHost, int registryPort,
@Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory) @Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory)
@ -305,8 +333,11 @@ public class RMIClientSourceCode {
} }
} }
``` ```
#### getObjectToExport #### getObjectToExport
- 初始化并且获取缓存的object
- 初始化并且获取缓存的 object
```java ```java
protected Remote getObjectToExport() { protected Remote getObjectToExport() {
// determine remote object // determine remote object
@ -331,8 +362,11 @@ public class RMIClientSourceCode {
} }
``` ```
#### getProxyForService #### getProxyForService
- 获取代理服务 - 获取代理服务
```java ```java
protected Object getProxyForService() { protected Object getProxyForService() {
// service 校验 // service 校验
@ -365,13 +399,15 @@ public class RMIClientSourceCode {
} }
``` ```
- 这里要注意,切片方法的`invoke`调用 - 这里要注意,切片方法的`invoke`调用
- `RmiInvocationWrapper`的`invoke`方法会在调用方调用接口时候触发 - `RmiInvocationWrapper`的`invoke`方法会在调用方调用接口时候触发
#### rebind 和 bind #### rebind 和 bind
- 绑定和重新绑定 - 绑定和重新绑定
- 这部分源码在: `sun.rmi.registry.RegistryImpl` - 这部分源码在: `sun.rmi.registry.RegistryImpl`
```java ```java
public void rebind(String var1, Remote var2) throws RemoteException, AccessException { public void rebind(String var1, Remote var2) throws RemoteException, AccessException {
checkAccess("Registry.rebind"); checkAccess("Registry.rebind");
@ -380,6 +416,7 @@ public class RMIClientSourceCode {
``` ```
```java ```java
public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException { public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException {
checkAccess("Registry.bind"); checkAccess("Registry.bind");
@ -393,10 +430,13 @@ public class RMIClientSourceCode {
} }
} }
``` ```
- 共同维护` private Hashtable<String, Remote> bindings = new Hashtable(101);`这个对象 - 共同维护` private Hashtable<String, Remote> bindings = new Hashtable(101);`这个对象
#### unexportObjectSilently #### unexportObjectSilently
- 出现异常时候调用方法 - 出现异常时候调用方法
```java ```java
private void unexportObjectSilently() { private void unexportObjectSilently() {
@ -411,15 +451,15 @@ public class RMIClientSourceCode {
} }
``` ```
--- ---
## 客户端初始化 ## 客户端初始化
### RmiProxyFactoryBean ### RmiProxyFactoryBean
![image-20200225104850528](../../../images/spring/image-20200226082614312.png) ![image-20200225104850528](../../../images/spring/image-20200226082614312.png)
- 该类实现了`InitializingBean`接口直接看`afterPropertiesSet`方法 - 该类实现了`InitializingBean`接口直接看`afterPropertiesSet`方法
- `org.springframework.remoting.rmi.RmiProxyFactoryBean` - `org.springframework.remoting.rmi.RmiProxyFactoryBean`
@ -456,15 +496,10 @@ public class RMIClientSourceCode {
``` ```
### org.springframework.remoting.support.UrlBasedRemoteAccessor#afterPropertiesSet ### org.springframework.remoting.support.UrlBasedRemoteAccessor#afterPropertiesSet
- 该方法对 `serviceUrl`进行空判断,如果是空的抛出异常 - 该方法对 `serviceUrl`进行空判断,如果是空的抛出异常
```java ```java
/** /**
* 判断服务地址是否为空 * 判断服务地址是否为空
@ -477,12 +512,8 @@ public class RMIClientSourceCode {
} }
``` ```
### org.springframework.remoting.rmi.RmiClientInterceptor#afterPropertiesSet ### org.springframework.remoting.rmi.RmiClientInterceptor#afterPropertiesSet
```java ```java
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
@ -494,8 +525,6 @@ public class RMIClientSourceCode {
1. 调用父类的`afterPropertiesSet`方法判断`serviceUrl`是否为空 1. 调用父类的`afterPropertiesSet`方法判断`serviceUrl`是否为空
2. 执行`prepare()`方法 2. 执行`prepare()`方法
```JAVA ```JAVA
public void prepare() throws RemoteLookupFailureException { public void prepare() throws RemoteLookupFailureException {
// Cache RMI stub on initialization? // Cache RMI stub on initialization?
@ -576,8 +605,6 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
} }
``` ```
### org.springframework.remoting.rmi.RmiProxyFactoryBean#afterPropertiesSet ### org.springframework.remoting.rmi.RmiProxyFactoryBean#afterPropertiesSet
```java ```java
@ -594,9 +621,6 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
``` ```
### 增强调用 ### 增强调用
- 通过类图我们可以知道`RmiProxyFactoryBean`实现了`MethodInterceptor`,具体实现方法在`org.springframework.remoting.rmi.RmiClientInterceptor#invoke` - 通过类图我们可以知道`RmiProxyFactoryBean`实现了`MethodInterceptor`,具体实现方法在`org.springframework.remoting.rmi.RmiClientInterceptor#invoke`
@ -625,10 +649,6 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
``` ```
```JAVA ```JAVA
protected Remote getStub() throws RemoteLookupFailureException { protected Remote getStub() throws RemoteLookupFailureException {
if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) { if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) {
@ -646,12 +666,8 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
} }
``` ```
- `org.springframework.remoting.rmi.RmiClientInterceptor#doInvoke(org.aopalliance.intercept.MethodInvocation, org.springframework.remoting.rmi.RmiInvocationHandler)` - `org.springframework.remoting.rmi.RmiClientInterceptor#doInvoke(org.aopalliance.intercept.MethodInvocation, org.springframework.remoting.rmi.RmiInvocationHandler)`
```JAVA ```JAVA
@Nullable @Nullable
protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler) protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
@ -673,8 +689,6 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
![image-20200226082614312](../../../images/spring/image-20200226082614312.png) ![image-20200226082614312](../../../images/spring/image-20200226082614312.png)
最后的`invoke`方法 最后的`invoke`方法
- `org.springframework.remoting.rmi.RmiInvocationWrapper#invoke` - `org.springframework.remoting.rmi.RmiInvocationWrapper#invoke`
@ -741,16 +755,12 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
``` ```
- 关键语句**`return getRemoteInvocationExecutor().invoke(invocation, targetObject);`** - 关键语句**`return getRemoteInvocationExecutor().invoke(invocation, targetObject);`**
类图 类图
![image-20200226083247784](../../../images/spring/image-20200226083247784.png) ![image-20200226083247784](../../../images/spring/image-20200226083247784.png)
```JAVA ```JAVA
public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor { public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor {
@ -777,21 +787,15 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
- 这部分流程相对清晰,从对象中获取函数,调用这个函数 - 这部分流程相对清晰,从对象中获取函数,调用这个函数
--- ---
## 服务端 debug
## 服务端debug
- `org.springframework.remoting.rmi.RmiServiceExporter#afterPropertiesSet`打上断点 - `org.springframework.remoting.rmi.RmiServiceExporter#afterPropertiesSet`打上断点
![image-20200226084056993](../../../images/spring/image-20200226084056993.png) ![image-20200226084056993](../../../images/spring/image-20200226084056993.png)
可以看到此时的数据字段和我们的xml配置中一致 可以看到此时的数据字段和我们的 xml 配置中一致
- `org.springframework.remoting.rmi.RmiServiceExporter#prepare`断点 - `org.springframework.remoting.rmi.RmiServiceExporter#prepare`断点
@ -801,7 +805,7 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
![image-20200226084400939](../../../images/spring/image-20200226084400939.png) ![image-20200226084400939](../../../images/spring/image-20200226084400939.png)
这一行是jdk的就不进去看了 这一行是 jdk 的就不进去看了
执行完成就创建出了 `Registry` 执行完成就创建出了 `Registry`
@ -813,9 +817,7 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
![image-20200226084640683](../../../images/spring/image-20200226084640683.png) ![image-20200226084640683](../../../images/spring/image-20200226084640683.png)
- 执行 bind
- 执行bind
![image-20200226084923783](../../../images/spring/image-20200226084923783.png) ![image-20200226084923783](../../../images/spring/image-20200226084923783.png)
@ -823,14 +825,12 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
- 此时服务端信息已经成功记录并且启动 - 此时服务端信息已经成功记录并且启动
## 客户端debug ## 客户端 debug
![image-20200226085433130](../../../images/spring/image-20200226085433130.png) ![image-20200226085433130](../../../images/spring/image-20200226085433130.png)
![image-20200226085440865](../../../images/spring/image-20200226085440865.png) ![image-20200226085440865](../../../images/spring/image-20200226085440865.png)
remote 对象 remote 对象
![image-20200226085727426](../../../images/spring/image-20200226085727426.png) ![image-20200226085727426](../../../images/spring/image-20200226085727426.png)
@ -839,16 +839,12 @@ remote 对象
![image-20200226085839496](../../../images/spring/image-20200226085839496.png) ![image-20200226085839496](../../../images/spring/image-20200226085839496.png)
- serviceProxy - serviceProxy
![image-20200226090042946](../../../images/spring/image-20200226090042946.png) ![image-20200226090042946](../../../images/spring/image-20200226090042946.png)
- 方法调用 - 方法调用
- 使用的是AOP技术进行的AOP相关技术不在此处展开 - 使用的是 AOP 技术进行的AOP 相关技术不在此处展开
![image-20200226090315865](../../../images/spring/image-20200226090315865.png) ![image-20200226090315865](../../../images/spring/image-20200226090315865.png)
@ -856,10 +852,6 @@ stub 对象
![image-20200226090432052](../../../images/spring/image-20200226090432052.png) ![image-20200226090432052](../../../images/spring/image-20200226090432052.png)
![image-20200226090650154](../../../images/spring/image-20200226090650154.png) ![image-20200226090650154](../../../images/spring/image-20200226090650154.png)
- `invocation` - `invocation`
@ -870,10 +862,8 @@ stub 对象
![image-20200226090827849](../../../images/spring/image-20200226090827849.png) ![image-20200226090827849](../../../images/spring/image-20200226090827849.png)
- 反射执行`method`结束整个调用 - 反射执行`method`结束整个调用
![image-20200226090945418](../../../images/spring/image-20200226090945418.png) ![image-20200226090945418](../../../images/spring/image-20200226090945418.png)
此时得到结果RMI调用结束 此时得到结果 RMI 调用结束

@ -1,14 +1,18 @@
# Spring5 新特性 - spring.components # Spring5 新特性 - spring.components
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 解析 ## 解析
- 相关类: `org.springframework.context.index.CandidateComponentsIndexLoader` - 相关类: `org.springframework.context.index.CandidateComponentsIndexLoader`
- 测试用例: `org.springframework.context.annotation.ClassPathScanningCandidateComponentProviderTests.defaultsWithIndex`,`org.springframework.context.index.CandidateComponentsIndexLoaderTests` - 测试用例: `org.springframework.context.annotation.ClassPathScanningCandidateComponentProviderTests.defaultsWithIndex`,`org.springframework.context.index.CandidateComponentsIndexLoaderTests`
- `CandidateComponentsIndexLoader`是怎么找出来的,全文搜索`spring.components` - `CandidateComponentsIndexLoader`是怎么找出来的,全文搜索`spring.components`
### 使用介绍 ### 使用介绍
- 下面是从`resources/example/scannable/spring.components`测试用例中复制过来的,从中可以发现等号左侧放的是我们写的组件,等号右边是属于什么组件 - 下面是从`resources/example/scannable/spring.components`测试用例中复制过来的,从中可以发现等号左侧放的是我们写的组件,等号右边是属于什么组件
``` ```
example.scannable.AutowiredQualifierFooService=example.scannable.FooService example.scannable.AutowiredQualifierFooService=example.scannable.FooService
example.scannable.DefaultNamedComponent=org.springframework.stereotype.Component example.scannable.DefaultNamedComponent=org.springframework.stereotype.Component
@ -23,7 +27,9 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component
``` ```
### debug ### debug
- 入口 `org.springframework.context.index.CandidateComponentsIndexLoader.loadIndex` - 入口 `org.springframework.context.index.CandidateComponentsIndexLoader.loadIndex`
```java ```java
@Nullable @Nullable
public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) { public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) {
@ -35,6 +41,7 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component
} }
``` ```
```java ```java
/** /**
* 解析 META-INF/spring.components 文件 * 解析 META-INF/spring.components 文件
@ -99,11 +106,8 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component
} }
``` ```
![image-20200115105941265](../../../images/spring/image-20200115105941265.png) ![image-20200115105941265](../../../images/spring/image-20200115105941265.png)
- 该类给`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents`提供了帮助 - 该类给`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents`提供了帮助
```java ```java

@ -1,9 +1,11 @@
## 1 Web环境中的SpringMVC ## 1 Web 环境中的 SpringMVC
在 Web环境 中SpringMVC 是建立在 IoC容器 基础上的。了解 SpringMVC首先要了解 Spring 的 IoC容器 是如何在 Web环境 中被载入并起作用的。
Spring 的 IoC 是一个独立模块,它并不直接在 Web容器 中发挥作用,如果要在 Web环境 中使用 IoC容器需要 Spring 为 IoC 设计一个启动过程,把 IoC容器 导入,并在 Web容器 中建立起来。具体说来,这个启动过程是和 Web容器 的启动过程集成在一起的。在这个过程中,一方面处理 Web容器 的启动,另一方面通过设计特定的 Web容器拦截器将 IoC容器 载人到 Web环境 中来并将其初始化。在这个过程建立完成以后IoC容器 才能正常工作,而 SpringMVC 是建立在 IoC容器 的基础上的,这样才能建立起 MVC框架 的运行机制,从而响应从 Web容器 传递的 HTTP请求。 在 Web 环境 中SpringMVC 是建立在 IoC 容器 基础上的。了解 SpringMVC首先要了解 Spring 的 IoC 容器 是如何在 Web 环境 中被载入并起作用的。
Spring 的 IoC 是一个独立模块,它并不直接在 Web 容器 中发挥作用,如果要在 Web 环境 中使用 IoC 容器,需要 Spring 为 IoC 设计一个启动过程,把 IoC 容器 导入,并在 Web 容器 中建立起来。具体说来,这个启动过程是和 Web 容器 的启动过程集成在一起的。在这个过程中,一方面处理 Web 容器 的启动,另一方面通过设计特定的 Web 容器拦截器,将 IoC 容器 载人到 Web 环境 中来并将其初始化。在这个过程建立完成以后IoC 容器 才能正常工作,而 SpringMVC 是建立在 IoC 容器 的基础上的,这样才能建立起 MVC 框架 的运行机制,从而响应从 Web 容器 传递的 HTTP 请求。
下面以 Tomcat 作为 Web 容器 的例子进行分析。在 Tomcat 中web.xml 是应用的部署描述文件。在 web.xml 中常常经常能看到与 Spring 相关的部署描述。
下面以 Tomcat 作为 Web容器 的例子进行分析。在 Tomcat 中web.xml 是应用的部署描述文件。在 web.xml 中常常经常能看到与 Spring 相关的部署描述。
```xml ```xml
<servlet> <servlet>
<servlet-name>sample</servlet-name> <servlet-name>sample</servlet-name>
@ -22,34 +24,38 @@ Spring 的 IoC 是一个独立模块,它并不直接在 Web容器 中发挥作
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> </listener>
``` ```
web.xml 是 SpringMVC 与 Tomcat 的接口部分。这个部署描述文件中,首先定义了一个 Servlet对象它是 SpringMVC 的 DispatcherServlet。这个 DispatcherServlet 是 MVC 中很重要的一个类,起着分发请求的作用。
同时,在部署描述中,还为这个 DispatcherServlet 定义了对应的 URL映射以指定这个 Servlet 需要处理的 HTTP请求范围。context-param参数 用来指定 IoC容器 读取 Bean 的 XML文件 的路径,在这里,这个配置文件被定义为 WEB-INF/applicationContext.xml。其中可以看到 Spring应用 的 Bean配置。 web.xml 是 SpringMVC 与 Tomcat 的接口部分。这个部署描述文件中,首先定义了一个 Servlet 对象,它是 SpringMVC 的 DispatcherServlet。这个 DispatcherServlet 是 MVC 中很重要的一个类,起着分发请求的作用。
同时,在部署描述中,还为这个 DispatcherServlet 定义了对应的 URL 映射,以指定这个 Servlet 需要处理的 HTTP 请求范围。context-param 参数 用来指定 IoC 容器 读取 Bean 的 XML 文件 的路径,在这里,这个配置文件被定义为 WEB-INF/applicationContext.xml。其中可以看到 Spring 应用 的 Bean 配置。
最后,作为 Spring MVC 的启动类ContextLoaderListener 被定义为一个监听器,这个监听器是与 Web 服务器 的生命周期相关联的,由 ContextLoaderListener 监听器 负责完成 IoC 容器 在 Web 环境 中的启动工作。
最后,作为 Spring MVC 的启动类ContextLoaderListener 被定义为一个监听器,这个监听器是与 Web服务器 的生命周期相关联的,由 ContextLoaderListener监听器 负责完成 IoC容器 在 Web环境 中的启动工作。 DispatchServlet 和 ContextLoaderListener 提供了在 Web 容器 中对 Spring 的接口,也就是说,这些接口与 Web 容器 耦合是通过 ServletContext 来实现的ServletContext 是容器和应用沟通的桥梁,从一定程度上讲 ServletContext 就是 servlet 规范 的体现)。这个 ServletContext 为 Spring 的 IoC 容器 提供了一个宿主环境在宿主环境中Spring MVC 建立起一个 IoC 容器 的体系。这个 IoC 容器体系 是通过 ContextLoaderListener 的初始化来建立的,在建立 IoC 容器体系 后,把 DispatchServlet 作为 Spring MVC 处理 Web 请求 的转发器建立起来,从而完成响应 HTTP 请求 的准备。有了这些基本配置,建立在 IoC 容器 基础上的 SpringMVC 就可以正常地发挥作用了。下面我们看一下 IoC 容器 在 Web 容器 中的启动代码实现
DispatchServlet 和 ContextLoaderListener 提供了在 Web容器 中对 Spring 的接口,也就是说,这些接口与 Web容器 耦合是通过 ServletContext 来实现的ServletContext 是容器和应用沟通的桥梁,从一定程度上讲 ServletContext 就是 servlet规范 的体现)。这个 ServletContext 为 Spring 的 IoC容器 提供了一个宿主环境在宿主环境中Spring MVC 建立起一个 IoC容器 的体系。这个 IoC容器体系 是通过 ContextLoaderListener 的初始化来建立的,在建立 IoC容器体系 后,把 DispatchServlet 作为 Spring MVC 处理 Web请求 的转发器建立起来,从而完成响应 HTTP请求 的准备。有了这些基本配置,建立在 IoC容器 基础上的 SpringMVC 就可以正常地发挥作用了。下面我们看一下 IoC容器 在 Web容器 中的启动代码实现。 ## 2 IoC 容器启动的基本过程
## 2 IoC容器启动的基本过程 IoC 容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC 容器 在 Web 应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC 对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在 Web 容器 中启动 Spring 应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML 时序图 如下图所示。
IoC容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC容器 在 Web应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC对象作为根上下文的子上下文构成一个层次化的上下文体系。在 Web容器 中启动 Spring应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML时序图 如下图所示。
![avatar](../../../images/springMVC/Web容器启动spring应用程序过程图.png) ![avatar](../../../images/springMVC/Web容器启动spring应用程序过程图.png)
在 web.xml 中,已经配置了 ContextLoaderListener它是 Spring 提供的类,是为在 Web容器 中建立 IoC容器 服务的,它实现了 ServletContextListener接口这个接口是在 Servlet API 中定义的,提供了与 Servlet生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 和 上下文销毁 contextDestroyed()方法。而在 Web容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外ContextLoaderListener 还继承了 ContextLoader具体的载入 IoC容器 的过程是由 ContextLoader 来完成的。 在 web.xml 中,已经配置了 ContextLoaderListener它是 Spring 提供的类,是为在 Web 容器 中建立 IoC 容器 服务的,它实现了 ServletContextListener 接口,这个接口是在 Servlet API 中定义的,提供了与 Servlet 生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 和 上下文销毁 contextDestroyed()方法。而在 Web 容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外ContextLoaderListener 还继承了 ContextLoader具体的载入 IoC 容器 的过程是由 ContextLoader 来完成的。
在 ContextLoader 中,完成了两个 IoC容器 建立的基本过程,一个是在 Web容器 中建立起 双亲IoC容器另一个是生成相应的 WebApplicationContext 并将其初始化。 在 ContextLoader 中,完成了两个 IoC 容器 建立的基本过程,一个是在 Web 容器 中建立起 双亲 IoC 容器,另一个是生成相应的 WebApplicationContext 并将其初始化。
## 3 Web容器中的上下文设计 ## 3 Web 容器中的上下文设计
先从 Web容器 中的上下文入手,看看 Web环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web环境 中使用 IoC容器
Spring 为 Web应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。 先从 Web 容器 中的上下文入手,看看 Web 环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web 环境 中使用 IoC 容器,
Spring 为 Web 应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。
![avatar](../../../images/springMVC/WebApplicationContext接口的类继承关系.png) ![avatar](../../../images/springMVC/WebApplicationContext接口的类继承关系.png)
在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex接口 与 BeanFactory接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。 在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex 接口 与 BeanFactory 接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。
同样,在源代码中,也可以分析出类似的继承关系,在 WebApplicationContext 中可以看到相关的常量设计,比如 ROOT* WEB* APPLICATION_CONTEXT_ATTRIBUTE 等,这个常量是用来索引在 ServletContext 中存储的根上下文的。这个接口类定义的接口方法比较简单,在这个接口中,定义了一
个 getServletContext()方法,通过这个方法可以得到当前 Web 容器 的 Servlet 上下文环境,通过
这个方法,相当于提供了一个 Web 容器级别的 全局环境。
同样,在源代码中,也可以分析出类似的继承关系,在 WebApplicationContext 中可以看到相关的常量设计,比如 ROOT_ WEB_ APPLICATION_CONTEXT_ATTRIBUTE 等,这个常量是用来索引在 ServletContext 中存储的根上下文的。这个接口类定义的接口方法比较简单,在这个接口中,定义了一
个 getServletContext()方法,通过这个方法可以得到当前 Web容器 的 Servlet上下文环境通过
这个方法,相当于提供了一个 Web容器级别的 全局环境。
```java ```java
public interface WebApplicationContext extends ApplicationContext { public interface WebApplicationContext extends ApplicationContext {
@ -64,7 +70,9 @@ public interface WebApplicationContext extends ApplicationContext {
ServletContext getServletContext(); ServletContext getServletContext();
} }
``` ```
在启动过程中Spring 会使用一个默认的 WebApplicationContext 实现作为 IoC容器这个默认使用的 IoC容器 就是 XmlWebApplicationContext它继承了 ApplicationContext在 ApplicationContext 的基础上,增加了对 Web环境 和 XML配置定义 的处理。在 XmlWebApplicationContext 的初始化过程中Web容器 中的 IoC容器 被建立起来,从而在 Web容器 中建立起整个 Spring应用。与前面博文中分析的 IoC容器 的初始化一样,这个过程也有 loadBeanDefinition()方法 对 BeanDefinition 的载入。在 Web环境 中,对定位 BeanDefinition 的 Resource 有特别的要求,这个要求的处理体现在对 getDefaultConfigLocations()方法 的处理中。这里使用了默认的 BeanDefinition 的配置路径,这个路径在 XmlWebApplicationContext 中作为一个常量定义好了,即 /WEB-INF/applicationContext.xml。
在启动过程中Spring 会使用一个默认的 WebApplicationContext 实现作为 IoC 容器,这个默认使用的 IoC 容器 就是 XmlWebApplicationContext它继承了 ApplicationContext在 ApplicationContext 的基础上,增加了对 Web 环境 和 XML 配置定义 的处理。在 XmlWebApplicationContext 的初始化过程中Web 容器 中的 IoC 容器 被建立起来,从而在 Web 容器 中建立起整个 Spring 应用。与前面博文中分析的 IoC 容器 的初始化一样,这个过程也有 loadBeanDefinition()方法 对 BeanDefinition 的载入。在 Web 环境 中,对定位 BeanDefinition 的 Resource 有特别的要求,这个要求的处理体现在对 getDefaultConfigLocations()方法 的处理中。这里使用了默认的 BeanDefinition 的配置路径,这个路径在 XmlWebApplicationContext 中作为一个常量定义好了,即 /WEB-INF/applicationContext.xml。
```java ```java
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
@ -127,13 +135,17 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC
} }
} }
``` ```
从上面的代码中可以看到,在 XmlWebApplicationContext 中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取 BeanDefinition信息在这里就转化为如何在 Web容器环境 中获得 BeanDefinition信息。在获得 BeanDefinition信息 之后,后面的过程基本上就和前面分析的 XmlFileSystemBeanFactory 一样,是通过 XmlBeanDefinitionReader 来载入 BeanDefinition信息 的,最终完成整个上下文的初始化过程。
## 4 ContextLoader的设计与实现
对于 Spring 承载的 Web应用 而言,可以指定在 Web应用程序 启动时载入 IoC容器或者称为WebApplicationContext。这个功能是由 ContextLoaderListener 来完成的,它是在 Web容器 中配置的监听器,会监听 Web容器 的启动,然后载入 IoC容器。这个 ContextLoaderListener 通过使用 ContextLoader 来完成实际的 WebApplicationContext也就是 IoC容器 的初始化工作。这个 ContextLoader 就像 Spring应用程序 在 Web容器 中的启动器。这个启动过程是在 Web容器 中发生的,所以需要根据 Web容器 部署的要求来定义 ContextLoader相关的配置在概述中已经看到了这里就不重复了。
为了了解 IoC容器 在 Web容器 中的启动原理,这里对 启动器ContextLoaderListener 的实现进行分析。**这个监听器是启动 根IoC容器 并把它载入到 Web容器 的主要功能模块,也是整个 Spring Web应用 加载 IoC 的第一个地方**。从加载过程可以看到,首先从 Servlet事件 中得到 ServletContext然后可以读取配置在 web.xml 中的各个相关的属性值,接着 ContextLoader 会实例化 WebApplicationContext并完成其载人和初始化过程。这个被初始化的第一个上下文作为根上下文而存在这个根上下文载入后被绑定到 Web应用程序 的 ServletContext 上。任何需要访问根上下文的应用程序代码都可以从 WebApplicationContextUtils类 的静态方法中得到。 从上面的代码中可以看到,在 XmlWebApplicationContext 中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取 BeanDefinition 信息,在这里,就转化为如何在 Web 容器环境 中获得 BeanDefinition 信息。在获得 BeanDefinition 信息 之后,后面的过程基本上就和前面分析的 XmlFileSystemBeanFactory 一样,是通过 XmlBeanDefinitionReader 来载入 BeanDefinition 信息 的,最终完成整个上下文的初始化过程。
## 4 ContextLoader 的设计与实现
对于 Spring 承载的 Web 应用 而言,可以指定在 Web 应用程序 启动时载入 IoC 容器(或者称为 WebApplicationContext。这个功能是由 ContextLoaderListener 来完成的,它是在 Web 容器 中配置的监听器,会监听 Web 容器 的启动,然后载入 IoC 容器。这个 ContextLoaderListener 通过使用 ContextLoader 来完成实际的 WebApplicationContext也就是 IoC 容器 的初始化工作。这个 ContextLoader 就像 Spring 应用程序 在 Web 容器 中的启动器。这个启动过程是在 Web 容器 中发生的,所以需要根据 Web 容器 部署的要求来定义 ContextLoader相关的配置在概述中已经看到了这里就不重复了。
为了了解 IoC 容器 在 Web 容器 中的启动原理,这里对 启动器 ContextLoaderListener 的实现进行分析。**这个监听器是启动 根 IoC 容器 并把它载入到 Web 容器 的主要功能模块,也是整个 Spring Web 应用 加载 IoC 的第一个地方**。从加载过程可以看到,首先从 Servlet 事件 中得到 ServletContext然后可以读取配置在 web.xml 中的各个相关的属性值,接着 ContextLoader 会实例化 WebApplicationContext并完成其载人和初始化过程。这个被初始化的第一个上下文作为根上下文而存在这个根上下文载入后被绑定到 Web 应用程序 的 ServletContext 上。任何需要访问根上下文的应用程序代码都可以从 WebApplicationContextUtils 类 的静态方法中得到。
下面分析具体的根上下文的载人过程。在 ContextLoaderListener 中,实现的是 **ServletContextListener 接口,这个接口里的函数会结合 Web 容器 的生命周期被调用**。因为 ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于 ServletContext 的变化而触发的监听器的响应具体包括在服务器启动时ServletContext 被创建的时候服务器关闭时ServletContext 将被销毁的时候等。对应这些事件及 Web 容器状态 的变化在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时ServletContextListener 的 contextInitialized()方法 被调用服务器将要关闭时ServletContextListener 的 contextDestroyed()方法 被调用。了解了 Web 容器 中监听器的工作原理,下面看看服务器启动时 ContextLoaderListener 的调用完成了什么。在这个初始化回调中,创建了 ContextLoader同时会利用创建出来的 ContextLoader 来完成 IoC 容器 的初始化。
下面分析具体的根上下文的载人过程。在 ContextLoaderListener 中,实现的是 **ServletContextListener接口这个接口里的函数会结合 Web容器 的生命周期被调用**。因为 ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于 ServletContext 的变化而触发的监听器的响应具体包括在服务器启动时ServletContext 被创建的时候服务器关闭时ServletContext 将被销毁的时候等。对应这些事件及 Web容器状态 的变化在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时ServletContextListener 的 contextInitialized()方法 被调用服务器将要关闭时ServletContextListener 的 contextDestroyed()方法 被调用。了解了 Web容器 中监听器的工作原理,下面看看服务器启动时 ContextLoaderListener 的调用完成了什么。在这个初始化回调中,创建了 ContextLoader同时会利用创建出来的 ContextLoader 来完成 IoC容器 的初始化。
```java ```java
public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
@ -336,4 +348,5 @@ public class ContextLoader {
} }
} }
``` ```
这就是 IoC容器 在 Web容器 中的启动过程,与应用中启动 IoC容器 的方式相类似,所不同的是这里需要考虑 Web容器 的环境特点比如各种参数的设置IoC容器 与 Web容器 ServletContext 的结合等。在初始化这个上下文以后,该上下文会被存储到 SevletContext 中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动 SpringMVC 时,我们还会看到这个上下文被以后的 DispatcherServlet 在进行自己持有的上下文的初始化时,设置为 DispatcherServlet 自带的上下文的双亲上下文。
这就是 IoC 容器 在 Web 容器 中的启动过程,与应用中启动 IoC 容器 的方式相类似,所不同的是这里需要考虑 Web 容器 的环境特点比如各种参数的设置IoC 容器 与 Web 容器 ServletContext 的结合等。在初始化这个上下文以后,该上下文会被存储到 SevletContext 中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动 SpringMVC 时,我们还会看到这个上下文被以后的 DispatcherServlet 在进行自己持有的上下文的初始化时,设置为 DispatcherServlet 自带的上下文的双亲上下文。

@ -1,8 +1,6 @@
# Spring-MVC 跨域 # Spring-MVC 跨域
## CrossOrigin 注解
## CrossOrigin注解
- 通过注解设置跨域 demo 如下 - 通过注解设置跨域 demo 如下
@ -28,10 +26,6 @@ public class JSONController {
``` ```
- 切入点: - 切入点:
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod` - `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod`
@ -123,16 +117,12 @@ public class JSONController {
} }
``` ```
信息截图: 信息截图:
![image-20200123085741347](../../../images/springMVC/clazz/image-20200123085741347.png) ![image-20200123085741347](../../../images/springMVC/clazz/image-20200123085741347.png)
![image-20200123085756168](../../../images/springMVC/clazz/image-20200123085756168.png) ![image-20200123085756168](../../../images/springMVC/clazz/image-20200123085756168.png)
### updateCorsConfig ### updateCorsConfig
- 该方法对原有的配置信息做补充 - 该方法对原有的配置信息做补充
@ -174,16 +164,10 @@ public class JSONController {
``` ```
最终解析结果 最终解析结果
![image-20200123085946476](../../../images/springMVC/clazz/image-20200123085946476.png) ![image-20200123085946476](../../../images/springMVC/clazz/image-20200123085946476.png)
- 解析完成后放入 `corsLookup`对象中 类:**`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping`** - 解析完成后放入 `corsLookup`对象中 类:**`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping`**
```java ```java
@ -193,12 +177,6 @@ public class JSONController {
``` ```
## xml 配置方式 ## xml 配置方式
```xml ```xml
@ -215,7 +193,7 @@ public class JSONController {
</mvc:cors> </mvc:cors>
``` ```
- `mvc`标签解析类: `org.springframework.web.servlet.config.MvcNamespaceHandler`这个类对Spring配置文件中的`<mvc:xxx>`标签做了解析设定,如这次我们的关注点**`CORS`** - `mvc`标签解析类: `org.springframework.web.servlet.config.MvcNamespaceHandler`,这个类对 Spring 配置文件中的`<mvc:xxx>`标签做了解析设定,如这次我们的关注点**`CORS`**
```java ```java
public class MvcNamespaceHandler extends NamespaceHandlerSupport { public class MvcNamespaceHandler extends NamespaceHandlerSupport {
@ -255,10 +233,6 @@ public class JSONController {
``` ```
### CorsBeanDefinitionParser ### CorsBeanDefinitionParser
#### 类图 #### 类图
@ -268,7 +242,7 @@ public class JSONController {
#### 解析 #### 解析
- 实现**BeanDefinitionParser** 接口的都有一个**parse**方法直接看方法. - 实现**BeanDefinitionParser** 接口的都有一个**parse**方法直接看方法.
- 通过查看我们可以知道最终目的获取xml标签中的属性,对 **CorsConfiguration**进行初始化最后Spring中注册 - 通过查看我们可以知道最终目的获取 xml 标签中的属性,对 **CorsConfiguration**进行初始化,最后 Spring 中注册
```JAVA ```JAVA
public class CorsBeanDefinitionParser implements BeanDefinitionParser { public class CorsBeanDefinitionParser implements BeanDefinitionParser {
@ -333,8 +307,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
- 可以看出这个是我们的第一个跨域配置的信息 - 可以看出这个是我们的第一个跨域配置的信息
- 注册方法 - 注册方法
```java ```java
@ -365,10 +337,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
- ![image-20200123091445694](../../../images/springMVC/clazz/image-20200123091445694.png) - ![image-20200123091445694](../../../images/springMVC/clazz/image-20200123091445694.png)
## CorsConfiguration ## CorsConfiguration
- 跨域信息 - 跨域信息
@ -417,10 +385,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
private Long maxAge; private Long maxAge;
``` ```
## 处理请求 ## 处理请求
- 请求处理的一部分,前置后置都还有其他处理,这里只对跨域请求进行说明 - 请求处理的一部分,前置后置都还有其他处理,这里只对跨域请求进行说明
@ -491,8 +455,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
``` ```
### 跨域拦截器创建 ### 跨域拦截器创建
```java ```java
@ -512,10 +474,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
``` ```
### 跨域拦截器 ### 跨域拦截器
```java ```java
@ -547,18 +505,12 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
``` ```
### DefaultCorsProcessor ### DefaultCorsProcessor
- 经过跨域拦截器 **`CorsInterceptor`**之后会调用 - 经过跨域拦截器 **`CorsInterceptor`**之后会调用
![image-20200123093733129](../../../images/springMVC/clazz/image-20200123093733129.png) ![image-20200123093733129](../../../images/springMVC/clazz/image-20200123093733129.png)
```JAVA ```JAVA
@Override @Override
@SuppressWarnings("resource") @SuppressWarnings("resource")
@ -599,8 +551,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
``` ```
### 模拟请求 ### 模拟请求
``` ```
@ -610,6 +560,4 @@ Origin: localhost
变量截图 变量截图
![image-20200123093032179](../../../images/springMVC/clazz/image-20200123093032179.png) ![image-20200123093032179](../../../images/springMVC/clazz/image-20200123093032179.png)

@ -1,27 +1,31 @@
## 1 SpringMVC应用场景 ## 1 SpringMVC 应用场景
在使用 SpringMVC 时,除了要在 web.xml 中配置 ContextLoaderListener 外,还要对 DispatcherServlet 进行配置。作为一个 Servlet这个 DispatcherServlet 实现的是 Sun 的 J2EE核心模式 中的 前端控制器模式(Front Controller) 作为一个前端控制器,所有的 Web请求 都需要通过它来进行转发、匹配、数据处理,然后转由页面进行展现,因此这个 DispatcerServlet 可以看成是 SpringMVC实现 中最为核心的部分。
SpringMVC 中,对于不同的 Web请求 的映射需求SpringMVC 提供了不同的 HandlerMapping 的实现可以让应用开发选取不同的映射策略。DispatcherSevlet 默认了 BeanNameUrlHandlerMapping 作为映射策略实现。除了映射策略可以定制外SpringMVC 还提供了各种 Controller 的实现来供应用扩展和使用,以应对不同的控制器使用场景,这些 Controller控制器 需要实现 handleRequest()接口方法,并返回 ModelAndView对象。SpringMVC 还提供了各种视图实现,比如常用的 JSP视图。除此之外SpringMVC 还提供了拦截器供应用使用,允许应用对 Web请求 进行拦截,以及前置处理和后置处理 使用 SpringMVC 时,除了要在 web.xml 中配置 ContextLoaderListener 外,还要对 DispatcherServlet 进行配置。作为一个 Servlet这个 DispatcherServlet 实现的是 Sun 的 J2EE 核心模式 中的 前端控制器模式(Front Controller) 作为一个前端控制器,所有的 Web 请求 都需要通过它来进行转发、匹配、数据处理,然后转由页面进行展现,因此这个 DispatcerServlet 可以看成是 SpringMVC 实现 中最为核心的部分
## 2 SpringMVC设计概览 在 SpringMVC 中,对于不同的 Web 请求 的映射需求SpringMVC 提供了不同的 HandlerMapping 的实现可以让应用开发选取不同的映射策略。DispatcherSevlet 默认了 BeanNameUrlHandlerMapping 作为映射策略实现。除了映射策略可以定制外SpringMVC 还提供了各种 Controller 的实现来供应用扩展和使用,以应对不同的控制器使用场景,这些 Controller 控制器 需要实现 handleRequest()接口方法,并返回 ModelAndView 对象。SpringMVC 还提供了各种视图实现,比如常用的 JSP 视图。除此之外SpringMVC 还提供了拦截器供应用使用,允许应用对 Web 请求 进行拦截,以及前置处理和后置处理。
在完成对 ContextLoaderListener 的初始化以后Web容器 开始初始化 DispatcherServlet这个初始化的启动与在 web.xml 中对载入次序的定义有关。DispatcherServlet 会建立自己的上下文来持有SpringMVC 的 Bean对象在建立这个自己持有的 IoC容器 时,会**从 ServletContext 中得到根上下文**作为 DispatcherServlet 持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到 ServletContext 中,供以后检索和使用。
为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet类 的继承关系。 ## 2 SpringMVC 设计概览
在完成对 ContextLoaderListener 的初始化以后Web 容器 开始初始化 DispatcherServlet这个初始化的启动与在 web.xml 中对载入次序的定义有关。DispatcherServlet 会建立自己的上下文来持有 SpringMVC 的 Bean 对象,在建立这个自己持有的 IoC 容器 时,会**从 ServletContext 中得到根上下文**作为 DispatcherServlet 持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到 ServletContext 中,供以后检索和使用。
为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet 类 的继承关系。
![avatar](../../../images/springMVC/DispatcherServlet的继承关系.png) ![avatar](../../../images/springMVC/DispatcherServlet的继承关系.png)
DispatcherServlet 通过继承 FrameworkServlet 和 HttpServletBean 而继承了 HttpServlet通过使用Servlet API 来对 HTTP请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC模块 与 Web容器 集成的处理前端。 DispatcherServlet 通过继承 FrameworkServlet 和 HttpServletBean 而继承了 HttpServlet通过使用 Servlet API 来对 HTTP 请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC 模块 与 Web 容器 集成的处理前端。
DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法在这个方法里DispatcherServlet 对 MVC模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP请求 进行响应,作为一个 ServletWeb容器 会调用 Servlet 的doGet() 和 doPost()方法,在经过 FrameworkServlet 的 processRequest() 简单处理后,会调用 DispatcherServlet 的 doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。 DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法在这个方法里DispatcherServlet 对 MVC 模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP 请求 进行响应,作为一个 ServletWeb 容器 会调用 Servlet 的 doGet() 和 doPost()方法,在经过 FrameworkServlet 的 processRequest() 简单处理后,会调用 DispatcherServlet 的 doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC 模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。
![avatar](../../../images/springMVC/DispatcherServlet的处理过程.png) ![avatar](../../../images/springMVC/DispatcherServlet的处理过程.png)
## 3 DispatcherServlet的启动和初始化 ## 3 DispatcherServlet 的启动和初始化
前面大致描述了 SpringMVC 的工作流程,下面看一下 DispatcherServlet 的启动和初始化的代码设计及实现。 前面大致描述了 SpringMVC 的工作流程,下面看一下 DispatcherServlet 的启动和初始化的代码设计及实现。
作为 ServletDispatcherServlet 的启动与 Servlet 的启动过程是相联系的。在 Servlet 的初始化过程中Servlet 的 init()方法 会被调用以进行初始化DispatcherServlet 的基类 HttpServletBean 实现了该方法。在初始化开始时,需要读取配置在 ServletContext 中的 Bean属性参数这些属性参数设置在 web.xml 的 Web容器初始化参数 中。使用编程式的方式来设置这些 Bean属性在这里可以看到对 PropertyValues 和 BeanWrapper 的使用。对于这些和依赖注人相关的类的使用,在分析 IoC容器 的初始化时,尤其是在依赖注入实现分析时,有过“亲密接触”。只是这里的依赖注人是与 Web容器 初始化相关的。 作为 ServletDispatcherServlet 的启动与 Servlet 的启动过程是相联系的。在 Servlet 的初始化过程中Servlet 的 init()方法 会被调用以进行初始化DispatcherServlet 的基类 HttpServletBean 实现了该方法。在初始化开始时,需要读取配置在 ServletContext 中的 Bean 属性参数,这些属性参数设置在 web.xml 的 Web 容器初始化参数 中。使用编程式的方式来设置这些 Bean 属性,在这里可以看到对 PropertyValues 和 BeanWrapper 的使用。对于这些和依赖注人相关的类的使用,在分析 IoC 容器 的初始化时,尤其是在依赖注入实现分析时,有过“亲密接触”。只是这里的依赖注人是与 Web 容器 初始化相关的。
接着会执行 DispatcherServlet 持有的 IoC 容器 的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个 DispatcherServlet 持有的上下文被设置为根上下文的子上下文。一个 Web 应用 中可以容纳多个 Servlet 存在;与此相对应,对于应用在 Web 容器 中的上下体系,一个根上下文可以作为许多 Servlet 上下文 的双亲上下文。了解 IoC 工作原理的读者知道,在向 IoC 容器 getBean() 时IoC 容器 会首先向其双亲上下文去 getBean(),也就是说,在根上下文中定义的 Bean 是可以被各个 Servlet 持有的上下文得到和共享的。DispatcherServlet 持有的 上下文被建立起来以后,也需要和其他 IoC 容器 一样完成初始化,这个初始化也是通过 refresh()方法 来完成的。最后DispatcherServlet 给这个自己持有的上下文命名,并把它设置到 Web 容器 的上下文中,这个名称和在 web.xml 中设置的 DispatcherServlet 的 Servlet 名称 有关,从而保证了这个上下文在 Web 环境上下文体系 中的唯一性。
接着会执行 DispatcherServlet 持有的 IoC容器 的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个 DispatcherServlet 持有的上下文被设置为根上下文的子上下文。一个 Web应用 中可以容纳多个 Servlet 存在;与此相对应,对于应用在 Web容器 中的上下体系,一个根上下文可以作为许多 Servlet上下文 的双亲上下文。了解 IoC 工作原理的读者知道,在向 IoC容器 getBean() 时IoC容器 会首先向其双亲上下文去 getBean(),也就是说,在根上下文中定义的 Bean 是可以被各个 Servlet 持有的上下文得到和共享的。DispatcherServlet 持有的 上下文被建立起来以后,也需要和其他 IoC容器 一样完成初始化,这个初始化也是通过 refresh()方法 来完成的。最后DispatcherServlet 给这个自己持有的上下文命名,并把它设置到 Web容器 的上下文中,这个名称和在 web.xml 中设置的 DispatcherServlet 的 Servlet名称 有关,从而保证了这个上下文在 Web环境上下文体系 中的唯一性。
```java ```java
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
@ -158,7 +162,9 @@ public abstract class FrameworkServlet extends HttpServletBean {
} }
} }
``` ```
至此,这个 MVC 的上下文就建立起来了,具体取得根上下文的过程在 WebApplicationContextUtils 中实现。这个根上下文是 ContextLoader 设置到 ServletContext 中去的,使用的属性是 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEContextLoader 还对这个 IoC容器 的 Bean 配置文件进行了设置,默认的位置是在 /WEB-INF/applicationContext.xml文件 中。由于这个根上下文是 DispatcherServlet 建立的上下文的 双亲上下文,所以根上下文中管理的 Bean 也可以被 DispatcherServlet 的上下文使用。通过 getBean() 向 IoC容器 获取 Bean 时,容器会先到它的 双亲IoC容器 中获取。
至此,这个 MVC 的上下文就建立起来了,具体取得根上下文的过程在 WebApplicationContextUtils 中实现。这个根上下文是 ContextLoader 设置到 ServletContext 中去的,使用的属性是 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTEContextLoader 还对这个 IoC 容器 的 Bean 配置文件进行了设置,默认的位置是在 /WEB-INF/applicationContext.xml 文件 中。由于这个根上下文是 DispatcherServlet 建立的上下文的 双亲上下文,所以根上下文中管理的 Bean 也可以被 DispatcherServlet 的上下文使用。通过 getBean() 向 IoC 容器 获取 Bean 时,容器会先到它的 双亲 IoC 容器 中获取。
```java ```java
/** /**
* 这是一个封装了很多静态方法的抽象工具类,所以只能调用其静态方法, * 这是一个封装了很多静态方法的抽象工具类,所以只能调用其静态方法,
@ -199,7 +205,9 @@ public abstract class WebApplicationContextUtils {
} }
) )
``` ```
回到 FrameworkServlet 的实现中来看一下DispatcherServlet 的上下文是怎样建立的,这个建立过程与前面建立根上下文的过程非常类似。建立 DispatcherServlet 的上下文,需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象,并为它设置参数。根据默认的配置,这个上下文对象也是 XmlWebApplicationContext对象这个类型是在 DEFAULT_CONTEXT_CLASS参数 中设置好并允许 BeanUtilis 使用的。在实例化结束后需要为这个上下文对象设置好一些基本的配置这些配置包括它的双亲上下文、Bean配置文件 的位置等。完成这些配置以后,最后通过调用 IoC容器 的 refresh()方法 来完成 IoC容器 的最终初始化,这和前面我们对 IoC容器实现原理 的分析中所看到的 IoC容器初始化 的过程是一致的。
回到 FrameworkServlet 的实现中来看一下DispatcherServlet 的上下文是怎样建立的,这个建立过程与前面建立根上下文的过程非常类似。建立 DispatcherServlet 的上下文,需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象,并为它设置参数。根据默认的配置,这个上下文对象也是 XmlWebApplicationContext 对象,这个类型是在 DEFAULT_CONTEXT_CLASS 参数 中设置好并允许 BeanUtilis 使用的。在实例化结束后需要为这个上下文对象设置好一些基本的配置这些配置包括它的双亲上下文、Bean 配置文件 的位置等。完成这些配置以后,最后通过调用 IoC 容器 的 refresh()方法 来完成 IoC 容器 的最终初始化,这和前面我们对 IoC 容器实现原理 的分析中所看到的 IoC 容器初始化 的过程是一致的。
```java ```java
public abstract class FrameworkServlet extends HttpServletBean { public abstract class FrameworkServlet extends HttpServletBean {
@ -290,11 +298,13 @@ public abstract class FrameworkServlet extends HttpServletBean {
} }
} }
``` ```
这时候 DispatcherServlet 中的 IoC容器 已经建立起来了,这个 IoC容器 是 根上下文 的子容器。如果要查找一个由 DispatcherServlet 所持有的 IoC容器 来管理的 Bean系统会首先到 根上下文 中去查找。如果查找不到,才会到 DispatcherServlet 所管理的 IoC容器 去进行查找,这是由 IoC容器 的 getBean() 的实现来决定的。通过一系列在 Web容器 中执行的动作在这个上下文体系建立和初始化完毕的基础上SpringMVC 就可以发挥其作用了。下面来分析一下 SpringMVC 的具体实现。
在前面分析 DispatchServlet 的初始化过程中可以看到DispatchServlet 持有一个以自己的 Servlet名称 命名的 IoC容器。这个 IoC容器 是一个 WebApplicationContext对象这个 IoC容器 建立起来以后,意味着 DispatcherServlet 拥有自己的 Bean定义空间这为使用各个独立的 XML文件 来配置 MVC 中各个 Bean 创造了条件。由于在初始化结束以后,与 Web容器 相关的加载过程实际上已经完成了SpringMVC 的具体实现和普通的 Spring应用程序 的实现并没有太大的差别。 这时候 DispatcherServlet 中的 IoC 容器 已经建立起来了,这个 IoC 容器 是 根上下文 的子容器。如果要查找一个由 DispatcherServlet 所持有的 IoC 容器 来管理的 Bean系统会首先到 根上下文 中去查找。如果查找不到,才会到 DispatcherServlet 所管理的 IoC 容器 去进行查找,这是由 IoC 容器 的 getBean() 的实现来决定的。通过一系列在 Web 容器 中执行的动作在这个上下文体系建立和初始化完毕的基础上SpringMVC 就可以发挥其作用了。下面来分析一下 SpringMVC 的具体实现。
在前面分析 DispatchServlet 的初始化过程中可以看到DispatchServlet 持有一个以自己的 Servlet 名称 命名的 IoC 容器。这个 IoC 容器 是一个 WebApplicationContext 对象,这个 IoC 容器 建立起来以后,意味着 DispatcherServlet 拥有自己的 Bean 定义空间,这为使用各个独立的 XML 文件 来配置 MVC 中各个 Bean 创造了条件。由于在初始化结束以后,与 Web 容器 相关的加载过程实际上已经完成了SpringMVC 的具体实现和普通的 Spring 应用程序 的实现并没有太大的差别。
在 DispatcherServlet 的初始化过程中,以对 HandlerMapping 的初始化调用作为触发点,了解 SpringMVC 模块 初始化的方法调用关系。这个调用关系最初是由 HttpServletBean 的 init()方法 触发的,这个 HttpServletBean 是 HttpServlet 的子类。接着会在 HttpServletBean 的子类 FrameworkServlet 中对 IoC 容器 完成初始化,在这个初始化方法中,会调用 DispatcherServlet 的 initStrategies()方法,该方法包括对各种 MVC 框架 的实现元素,比如支持国际化的 LocalResolver、支持 request 映射的 HandlerMappings以及视图生成的 ViewResolver 等。由该方法启动整个 SpringMVC 框架 的初始化。
在 DispatcherServlet 的初始化过程中,以对 HandlerMapping 的初始化调用作为触发点,了解 SpringMVC模块 初始化的方法调用关系。这个调用关系最初是由 HttpServletBean 的 init()方法 触发的,这个 HttpServletBean 是 HttpServlet 的子类。接着会在 HttpServletBean 的子类 FrameworkServlet 中对 IoC容器 完成初始化,在这个初始化方法中,会调用 DispatcherServlet 的 initStrategies()方法,该方法包括对各种 MVC框架 的实现元素,比如支持国际化的 LocalResolver、支持 request 映射的 HandlerMappings以及视图生成的 ViewResolver 等。由该方法启动整个 SpringMVC框架 的初始化。
```java ```java
public class DispatcherServlet extends FrameworkServlet { public class DispatcherServlet extends FrameworkServlet {
/** /**
@ -323,9 +333,11 @@ public class DispatcherServlet extends FrameworkServlet {
} }
} }
``` ```
对于具体的初始化过程,根据上面的方法名称,很容易理解。以 HandlerMapping 为例来说明这个 initHandlerMappings()过程。这里的 Mapping关系 的作用是,为 HTTP请求 找到相应的 Controller控制器从而利用这些 控制器Controller 去完成设计好的数据处理工作。
HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 Web 这个特定的应用环境中,这些控制器是与具体的 HTTP请求 相对应的。在 HandlerMapping初始化 的过程中,把在 Bean配置文件 中配置好的 HandlerMapping 从 IoC容器 中取得。 对于具体的初始化过程,根据上面的方法名称,很容易理解。以 HandlerMapping 为例来说明这个 initHandlerMappings()过程。这里的 Mapping 关系 的作用是,为 HTTP 请求 找到相应的 Controller 控制器,从而利用这些 控制器 Controller 去完成设计好的数据处理工作。
HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 Web 这个特定的应用环境中,这些控制器是与具体的 HTTP 请求 相对应的。在 HandlerMapping 初始化 的过程中,把在 Bean 配置文件 中配置好的 HandlerMapping 从 IoC 容器 中取得。
```java ```java
/** /**
* 初始化此类使用的 HandlerMappings。 * 初始化此类使用的 HandlerMappings。
@ -368,18 +380,22 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W
} }
} }
``` ```
经过以上读取过程handlerMappings变量 就已经获取了在 Bean 中配置好的映射关系。其他的初始化过程和 handlerMappings 比较类似,都是直接从 IoC容器 中读入配置,所以这里的 MVC初始化过程 是建立在 IoC容器 已经初始化完成的基础上的。
## 4 SpringMVC处理分发HTTP请求 经过以上读取过程handlerMappings 变量 就已经获取了在 Bean 中配置好的映射关系。其他的初始化过程和 handlerMappings 比较类似,都是直接从 IoC 容器 中读入配置,所以这里的 MVC 初始化过程 是建立在 IoC 容器 已经初始化完成的基础上的。
### 4.1 HandlerMapping的配置和设计原理
前面分析了 DispatcherServlet 对 SpringMVC框架 的初始化过程,在此基础上,我们再进一步分析 HandlerMapping 的实现原理,看看这个 MVC框架 中比较关键的控制部分是如何实现的。
在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。 ## 4 SpringMVC 处理分发 HTTP 请求
### 4.1 HandlerMapping 的配置和设计原理
前面分析了 DispatcherServlet 对 SpringMVC 框架 的初始化过程,在此基础上,我们再进一步分析 HandlerMapping 的实现原理,看看这个 MVC 框架 中比较关键的控制部分是如何实现的。
在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP 请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL 请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。
![avatar](../../../images/springMVC/HandlerMapping组件.png) ![avatar](../../../images/springMVC/HandlerMapping组件.png)
以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL请求 和控制器的对应关系,使 SpringMVC 以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL 请求 和控制器的对应关系,使 SpringMVC
应用 可以根据 HTTP请求 确定一个对应的 Controller。具体来说这些映射关系是通过 HandlerMapping接口 来封装的,在 HandlerMapping接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP请求 对应的 HandlerExecutionChain在这个 HandlerExecutionChain 中,封装了具体的 Controller对象。 应用 可以根据 HTTP 请求 确定一个对应的 Controller。具体来说这些映射关系是通过 HandlerMapping 接口 来封装的,在 HandlerMapping 接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP 请求 对应的 HandlerExecutionChain在这个 HandlerExecutionChain 中,封装了具体的 Controller 对象。
```java ```java
public interface HandlerMapping { public interface HandlerMapping {
@ -402,7 +418,9 @@ public interface HandlerMapping {
} }
``` ```
这个 HandlerExecutionChain 的实现看起来比较简洁,它持有一个 拦截器链(HandlerInterceptor对象列表) 和一个 handler对象这个 handler对象 实际上就是 HTTP请求 对应的 Controller在持有这个 handler对象 的同时,还在 HandlerExecutionChain 中设置了一个拦截器链,通过这个拦截器链中的拦截器,可以为 handler对象 提供功能的增强。要完成这些工作,需要对拦截器链和 handler 都进行配置,这些配置都是在 HandlerExecutionChain 的初始化函数中完成的。为了维护这个拦截器链和 handlerHandlerExecutionChain 还提供了一系列与拦截器链维护相关的操作,比如,为拦截器链增加拦截器的 addInterceptor()方法。
这个 HandlerExecutionChain 的实现看起来比较简洁,它持有一个 拦截器链(HandlerInterceptor 对象列表) 和一个 handler 对象,这个 handler 对象 实际上就是 HTTP 请求 对应的 Controller在持有这个 handler 对象 的同时,还在 HandlerExecutionChain 中设置了一个拦截器链,通过这个拦截器链中的拦截器,可以为 handler 对象 提供功能的增强。要完成这些工作,需要对拦截器链和 handler 都进行配置,这些配置都是在 HandlerExecutionChain 的初始化函数中完成的。为了维护这个拦截器链和 handlerHandlerExecutionChain 还提供了一系列与拦截器链维护相关的操作,比如,为拦截器链增加拦截器的 addInterceptor()方法。
```java ```java
public class HandlerExecutionChain { public class HandlerExecutionChain {
@ -494,7 +512,8 @@ public class HandlerExecutionChain {
} }
} }
``` ```
HandlerExecutionChain 中定义的 Handler 和 HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping要做的就是根据 URL映射 的方式,注册 Handler 和 HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping 是 ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间映射关系的 handlerMap 的建立。
HandlerExecutionChain 中定义的 Handler 和 HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping要做的就是根据 URL 映射 的方式,注册 Handler 和 HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP 请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping 是 ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间映射关系的 handlerMap 的建立。
![avatar](../../../images/springMVC/SimpleUrlHandlerMapping的继承关系.png) ![avatar](../../../images/springMVC/SimpleUrlHandlerMapping的继承关系.png)
@ -534,7 +553,9 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
} }
} }
``` ```
这个 SimpleUrlHandlerMapping 注册过程的完成,很大一部分需要它的基类来配合,这个基类就是 AbstractUrlHandlerMapping。在 AbstractUrlHandlerMapping 的处理过程中,如果使用 Bean 的名称作为映射,那么直接从容器中获取这个 HTTP映射 对应的 Bean然后还要对不同的 URL配置 进行解析处理,比如在 HTTP请求 中配置成 “/” 和 通配符“/*” 的 URL以及正常的 URL请求完成这个解析处理过程以后会把 URL 和 handler 作为键值对放到一个 handlerMap 中去。
这个 SimpleUrlHandlerMapping 注册过程的完成,很大一部分需要它的基类来配合,这个基类就是 AbstractUrlHandlerMapping。在 AbstractUrlHandlerMapping 的处理过程中,如果使用 Bean 的名称作为映射,那么直接从容器中获取这个 HTTP 映射 对应的 Bean然后还要对不同的 URL 配置 进行解析处理,比如在 HTTP 请求 中配置成 “/” 和 通配符“/\*” 的 URL以及正常的 URL 请求,完成这个解析处理过程以后,会把 URL 和 handler 作为键值对放到一个 handlerMap 中去。
```java ```java
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
@ -621,10 +642,13 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
} }
} }
``` ```
这里的 handlerMap 是一个 HashMap其中保存了 “URL请求” --> “Controller对象” 的映射关系,这个 handlerMap 是在 AbstractUrlHandlerMapping 中定义的( Map<String, object> handlerMap = new LinkedHashMap<String, object>() ),这个配置好 URL请求 和 handler映射数据 的 handlerMap为 SpringMVC 响应 HTTP请求 准备好了基本的映射数据,根据这个 handlerMap 以及设置于其中的映射数据,可以方便地由 URL请求 得到它所对应的 handler。有了这些准备工作SpringMVC 就可以等待 HTTP请求 的到来了。
### 4.2 使用HandlerMapping完成请求的映射处理 这里的 handlerMap 是一个 HashMap其中保存了 “URL 请求” --> “Controller 对象” 的映射关系,这个 handlerMap 是在 AbstractUrlHandlerMapping 中定义的( Map<String, object> handlerMap = new LinkedHashMap<String, object>() ),这个配置好 URL 请求 和 handler 映射数据 的 handlerMap为 SpringMVC 响应 HTTP 请求 准备好了基本的映射数据,根据这个 handlerMap 以及设置于其中的映射数据,可以方便地由 URL 请求 得到它所对应的 handler。有了这些准备工作SpringMVC 就可以等待 HTTP 请求 的到来了。
继续通过 SimpleUrlHandlerMapping的实现 来分析 HandlerMapping 的 接口方法getHandler(),该方法会根据初始化时得到的映射关系来生成 DispatcherServlet 需要的 HandlerExecutionChain也就是说这个 getHandler()方法 是实际使用 HandlerMapping 完成请求的映射处理的地方。在前面的 HandlerExecutionChain 的执行过程中,首先在 AbstractHandlerMapping 中启动 getHandler() 的调用。
### 4.2 使用 HandlerMapping 完成请求的映射处理
继续通过 SimpleUrlHandlerMapping 的实现 来分析 HandlerMapping 的 接口方法 getHandler(),该方法会根据初始化时得到的映射关系来生成 DispatcherServlet 需要的 HandlerExecutionChain也就是说这个 getHandler()方法 是实际使用 HandlerMapping 完成请求的映射处理的地方。在前面的 HandlerExecutionChain 的执行过程中,首先在 AbstractHandlerMapping 中启动 getHandler() 的调用。
```java ```java
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {
/** /**
@ -653,7 +677,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
} }
} }
``` ```
取得 handler 的具体过程在 getHandlerInternal()方法 中实现,这个方法接受 HTTP请求 作为参数,它的实现在 AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping 中,这个实现过程包括从 HTTP请求 中得到 URL并根据 URL 到 urlMapping 中获得 handler。
取得 handler 的具体过程在 getHandlerInternal()方法 中实现,这个方法接受 HTTP 请求 作为参数,它的实现在 AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping 中,这个实现过程包括从 HTTP 请求 中得到 URL并根据 URL 到 urlMapping 中获得 handler。
```java ```java
public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/** /**
@ -757,10 +783,13 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
} }
} }
``` ```
经过这一系列对 HTTP请求 进行解析和匹配 handler 的过程,得到了与请求对应的 handler处理器。在返回的 handler 中,已经完成了在 HandlerExecutionChain 中进行封装的工作,为 handler 对 HTTP请求 的响应做好了准备。
### 4.3 DispatcherServlet对HTTP请求的分发处理 经过这一系列对 HTTP 请求 进行解析和匹配 handler 的过程,得到了与请求对应的 handler 处理器。在返回的 handler 中,已经完成了在 HandlerExecutionChain 中进行封装的工作,为 handler 对 HTTP 请求 的响应做好了准备。
DispatcherServlet 是 SpringMVC框架 中非常重要的一个类,不但建立了自己持有的 IoC容器还肩负着请求分发处理的重任对 HTTP请求 的处理是在 doService()方法 中完成的。DispatcherServlet 是 HttpServlet 的子类 ,与其他 HttpServlet 一样,可以通过 doService() 来响应 HTTP的请求。然而依照 SpringMVC 的使用,业务逻辑的调用入口是在 handler 的 handler()方法 中实现的,这是连接 SpringMVC 和应用业务逻辑实现的地方。
### 4.3 DispatcherServlet 对 HTTP 请求的分发处理
DispatcherServlet 是 SpringMVC 框架 中非常重要的一个类,不但建立了自己持有的 IoC 容器,还肩负着请求分发处理的重任,对 HTTP 请求 的处理是在 doService()方法 中完成的。DispatcherServlet 是 HttpServlet 的子类 ,与其他 HttpServlet 一样,可以通过 doService() 来响应 HTTP 的请求。然而,依照 SpringMVC 的使用,业务逻辑的调用入口是在 handler 的 handler()方法 中实现的,这是连接 SpringMVC 和应用业务逻辑实现的地方。
```java ```java
public class DispatcherServlet extends FrameworkServlet { public class DispatcherServlet extends FrameworkServlet {
@ -984,7 +1013,9 @@ public class DispatcherServlet extends FrameworkServlet {
} }
} }
``` ```
通过判断,可以知道这个 handler 是不是 Controller接口 的实现,比如可以通过具体 HandlerAdapter 的实现来了解这个适配过程。以 SimpleControllerHandlerAdapter的实现 为例来了解这个判断是怎样起作用的。
通过判断,可以知道这个 handler 是不是 Controller 接口 的实现,比如可以通过具体 HandlerAdapter 的实现来了解这个适配过程。以 SimpleControllerHandlerAdapter 的实现 为例来了解这个判断是怎样起作用的。
```java ```java
public class SimpleControllerHandlerAdapter implements HandlerAdapter { public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@ -1008,4 +1039,5 @@ public class SimpleControllerHandlerAdapter implements HandlerAdapter {
} }
``` ```
经过上面一系列的处理,得到了 handler对象接着就可以开始调用 handler对象 中的 HTTP响应动作了。在 handler 中封装了应用业务逻辑,由这些逻辑对 HTTP请求 进行相应的处理,生成需要的数据,并把这些数据封装到 ModelAndView对象 中去,这个 ModelAndView 的数据封装是 SpringMVC框架 的要求。对 handler 来说, 这些都是通过调用 handler()方法 中的 handleRequest()方法 来触发完成的。在得到 ModelAndView对象 以后,这个 ModelAndView对象 会被交给 MVC模式 中的视图类,由视图类对 ModelAndView对象 中的数据进行呈现。
经过上面一系列的处理,得到了 handler 对象,接着就可以开始调用 handler 对象 中的 HTTP 响应动作了。在 handler 中封装了应用业务逻辑,由这些逻辑对 HTTP 请求 进行相应的处理,生成需要的数据,并把这些数据封装到 ModelAndView 对象 中去,这个 ModelAndView 的数据封装是 SpringMVC 框架 的要求。对 handler 来说, 这些都是通过调用 handler()方法 中的 handleRequest()方法 来触发完成的。在得到 ModelAndView 对象 以后,这个 ModelAndView 对象 会被交给 MVC 模式 中的视图类,由视图类对 ModelAndView 对象 中的数据进行呈现。

@ -1,18 +1,21 @@
JavaEE应用 中的事务处理是一个重要并且涉及范围很广的领域。事务管理的实现往往涉及并发和数据一致性方面的问题。作为应用平台的 Spring具有在多种环境中配置和使用事务处理的能力也就是说通过使用 Spring 的事务组件,可以把事务处理的工作统一起来,并为事务处理提供通用的支持。 JavaEE 应用中的事务处理是一个重要并且涉及范围很广的领域。事务管理的实现往往涉及并发和数据一致性方面的问题。作为应用平台的 Spring具有在多种环境中配置和使用事务处理的能力也就是说通过使用 Spring 的事务组件,可以把事务处理的工作统一起来,并为事务处理提供通用的支持。
在涉及单个数据库局部事务的事务处理中,事务的最终实现和数据库的支持是紧密相关的。对局部数据库事务来说,一个事务处理的操作单元往往对应着一系列的数据库操作。数据库产品对这些数据库的 SQL操作 已经提供了原子性的支持,对 SQL操作 而言,它的操作结果有两种: 一种是提交成功,数据库操作成功;另一种是回滚,数据库操作不成功,恢复到操作以前的状态。 在涉及单个数据库局部事务的事务处理中,事务的最终实现和数据库的支持是紧密相关的。对局部数据库事务来说,一个事务处理的操作单元往往对应着一系列的数据库操作。数据库产品对这些数据库的 SQL 操作 已经提供了原子性的支持,对 SQL 操作 而言,它的操作结果有两种: 一种是提交成功,数据库操作成功;另一种是回滚,数据库操作不成功,恢复到操作以前的状态。
在事务处理中事务处理单元的设计与相应的业务逻辑设计有很紧密的联系。在很多情况下一个业务逻辑处理不会只有一个单独的数据库操作而是有一组数据库操作。在这个处理过程中首先涉及的是事务处理单元划分的问题Spring 借助 IoC容器 的强大配置能力,为应用提供了声明式的事务划分方式,这种声明式的事务处理,为 Spring应用 使用事务管理提供了统一的方式。有了 Spring事务管理 的支持,只需要通过一些简单的配置,应用就能完成复杂的事务处理工作,从而为用户使用事务处理提供很大的方便。 在事务处理中事务处理单元的设计与相应的业务逻辑设计有很紧密的联系。在很多情况下一个业务逻辑处理不会只有一个单独的数据库操作而是有一组数据库操作。在这个处理过程中首先涉及的是事务处理单元划分的问题Spring 借助 IoC 容器 的强大配置能力,为应用提供了声明式的事务划分方式,这种声明式的事务处理,为 Spring 应用 使用事务管理提供了统一的方式。有了 Spring 事务管理 的支持,只需要通过一些简单的配置,应用就能完成复杂的事务处理工作,从而为用户使用事务处理提供很大的方便。
## 1 Spring事务处理 的设计概览
Spring事务处理模块 的类层次结构如下图所示。 ## 1 Spring 事务处理的设计概览
Spring 事务处理模块的类层次结构如下图所示。
![avatar](../../../images/springTransaction/Spring事务处理模块类层次结构.png) ![avatar](../../../images/springTransaction/Spring事务处理模块类层次结构.png)
从上图可以看到Spring事务处理模块 是通过 AOP功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP功能通过这个 TransactionProxyFactoryBean 可以生成 Proxy代理对象在这个代理对象中通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。 从上图可以看到Spring 事务处理模块 是通过 AOP 功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring 事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP 功能,通过这个 TransactionProxyFactoryBean 可以生成 Proxy 代理对象,在这个代理对象中,通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。
对于具体的事务处理实现,比如事务的生成、提交、回滚、挂起等,由于不同的底层数据库有不同的支持方式,因此,在 Spring 事务处理中,对主要的事务实现做了一个抽象和适配。适配的具体事务处理器包括:对 DataSource 数据源 的事务处理支持,对 Hibernate 数据源 的事务处理支持,对 JDO 数据源 的事务处理支持,对 JPA 和 JTA 等数据源的事务处理支持等。这一系列的事务处理支持,都是通过设计 PlatformTransactionManager、AbstractPlatformTransactionManager 以及一系列具体事务处理器来实现的,而 PlatformTransactionManager 又实现了 TransactionInterceptor 接口,通过这样一个接口实现设计,就把这一系列的事务处理的实现与前面提到的 TransactionProxyFactoryBean 结合起来,从而形成了一个 Spring 声明式事务处理 的设计体系。
对于具体的事务处理实现,比如事务的生成、提交、回滚、挂起等,由于不同的底层数据库有不同的支持方式,因此,在 Spring事务处理中对主要的事务实现做了一个抽象和适配。适配的具体事务处理器包括对 DataSource数据源 的事务处理支持,对 Hibernate数据源 的事务处理支持,对 JDO数据源 的事务处理支持,对 JPA 和 JTA 等数据源的事务处理支持等。这一系列的事务处理支持,都是通过设计 PlatformTransactionManager、AbstractPlatformTransactionManager 以及一系列具体事务处理器来实现的,而 PlatformTransactionManager 又实现了 TransactionInterceptor接口通过这样一个接口实现设计就把这一系列的事务处理的实现与前面提到的 TransactionProxyFactoryBean 结合起来,从而形成了一个 Spring声明式事务处理 的设计体系。 ## 2 Spring 事务处理 的应用场景
## 2 Spring事务处理 的应用场景 Spring 作为应用平台或框架的设计出发点是支持 POJO 的开发,这点在实现事务处理的时候也不例外。在 Spring 中,它既支持编程式事务管理方式,又支持声明式事务处理方式,在使用 Spring 处理事务的时候,声明式事务处理通常比编程式事务管理更方便些。
Spring 作为应用平台或框架的设计出发点是支持 POJO的开发这点在实现事务处理的时候也不例外。在 Spring 中,它既支持编程式事务管理方式,又支持声明式事务处理方式,在使用 Spring 处理事务的时候,声明式事务处理通常比编程式事务管理更方便些。
Spring 对应用的支持,一方面,通过声明式事务处理,将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过 AOP 的方式来完成的。显然Spring 已经把那些通用的事务处理过程抽象出来,并通过 AOP 的方式进行封装,然后用声明式的使用方式交付给客户使用。这样,应用程序可以更简单地管理事务,并且只需要关注事务的处理策略。另一方面,应用在选择数据源时可能会采取不同的方案,当以 Spring 作为平台时Spring 在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring 为常用的数据源的事务处理支持提供了一系列的 TransactionManager。这些 Spring 封装好的 TransactionManager 为应用提供了很大的方便,因为在这些具体事务处理过程中,已经根据底层的实现,封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程,这样应用在使用不同的数据源时,可以做到事务处理的即开即用。这样的另一个好处是,如果应用有其他的数据源事务处理需要, Spring 也提供了一种一致的方式。这种 有机的事务过程抽象 和 具体的事务处理 相结合的设计,是我们在日常的开发中非常需要模仿学习的。 Spring 对应用的支持,一方面,通过声明式事务处理,将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过 AOP 的方式来完成的。显然Spring 已经把那些通用的事务处理过程抽象出来,并通过 AOP 的方式进行封装,然后用声明式的使用方式交付给客户使用。这样,应用程序可以更简单地管理事务,并且只需要关注事务的处理策略。另一方面,应用在选择数据源时可能会采取不同的方案,当以 Spring 作为平台时Spring 在应用和具体的数据源之间搭建一个中间平台通过这个中间平台解耦应用和具体数据源之间的绑定并且Spring 为常用的数据源的事务处理支持提供了一系列的 TransactionManager。这些 Spring 封装好的 TransactionManager 为应用提供了很大的方便,因为在这些具体事务处理过程中,已经根据底层的实现,封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程,这样应用在使用不同的数据源时,可以做到事务处理的即开即用。这样的另一个好处是,如果应用有其他的数据源事务处理需要, Spring 也提供了一种一致的方式。这种 有机的事务过程抽象 和 具体的事务处理 相结合的设计,是我们在日常的开发中非常需要模仿学习的。

@ -1,4 +1,5 @@
## 1 事务处理的编程式使用 ## 1 事务处理的编程式使用
```java ```java
TransactionDefinition td = new DefaultTransactionDefinition(); TransactionDefinition td = new DefaultTransactionDefinition();
// transactionManager 是某一个具体的 PlatformTransactionManager实现类 的对象 // transactionManager 是某一个具体的 PlatformTransactionManager实现类 的对象
@ -12,16 +13,18 @@
} }
transactionManager.commit(ts); transactionManager.commit(ts);
``` ```
在使用编程式事务处理的过程中,利用 DefaultTransactionDefinition对象 来持有事务处理属性。同时,在创建事务的过程中得到一个 TransactionStatus对象然后通过直接调用 transactionManager对象 的 commit() 和 rollback()方法 来完成事务处理。在这个编程式使用事务管理的过程中,没有看到框架特性的使用,非常简单和直接,很好地说明了事务管理的基本实现过程,以及在 Spring事务处理实现 中涉及一些主要的类,比如 TransationStatus、TransactionManager 等,对这些类的使用与声明式事务处理的最终实现是一样的。
与编程式使用事务管理不同,在使用声明式事务处理的时候,因为涉及 Spring框架 对事务处理的统一管理,以及对并发事务和事务属性的处理,所以采用的是一个比较复杂的处理过程,但复杂归复杂,这个过程对使用声明式事务处理的应用来说,基本上是不可见的,而是由 Spring框架 来完成的。有了这些背景铺垫和前面对 AOP封装事务处理 的了解,下面来看看 Spring 是如何提供声明式事务处理的Spring 在这个相对较为复杂的过程中封装了什么。这层封装包括在事务处理中事务的创建、提交和回滚等比较核心的操作。 在使用编程式事务处理的过程中,利用 DefaultTransactionDefinition 对象 来持有事务处理属性。同时,在创建事务的过程中得到一个 TransactionStatus 对象,然后通过直接调用 transactionManager 对象 的 commit() 和 rollback()方法 来完成事务处理。在这个编程式使用事务管理的过程中,没有看到框架特性的使用,非常简单和直接,很好地说明了事务管理的基本实现过程,以及在 Spring 事务处理实现 中涉及一些主要的类,比如 TransationStatus、TransactionManager 等,对这些类的使用与声明式事务处理的最终实现是一样的。
与编程式使用事务管理不同,在使用声明式事务处理的时候,因为涉及 Spring 框架 对事务处理的统一管理,以及对并发事务和事务属性的处理,所以采用的是一个比较复杂的处理过程,但复杂归复杂,这个过程对使用声明式事务处理的应用来说,基本上是不可见的,而是由 Spring 框架 来完成的。有了这些背景铺垫和前面对 AOP 封装事务处理 的了解,下面来看看 Spring 是如何提供声明式事务处理的Spring 在这个相对较为复杂的过程中封装了什么。这层封装包括在事务处理中事务的创建、提交和回滚等比较核心的操作。
## 2 事务的创建 ## 2 事务的创建
作为声明式事务处理实现的起始点,需要注意 TransactionInterceptor拦截器 的 invoke()回调 中使用的 createTransactionIfNecessary()方法,这个方法是在 TransactionInterceptor 的基类 TransactionAspectSupport 中实现的。为了了解这个方法的实现,先分析一下 TransactionInterceptor 的基类实现 TransactionAspectSupport并以这个方法的实现为入口了解 Spring 是如何根据当前的事务状态和事务属性配置完成事务创建的。
这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction事务对象 的过程,在 AbstractTransactionManager实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。 作为声明式事务处理实现的起始点,需要注意 TransactionInterceptor 拦截器 的 invoke()回调 中使用的 createTransactionIfNecessary()方法,这个方法是在 TransactionInterceptor 的基类 TransactionAspectSupport 中实现的。为了了解这个方法的实现,先分析一下 TransactionInterceptor 的基类实现 TransactionAspectSupport并以这个方法的实现为入口了解 Spring 是如何根据当前的事务状态和事务属性配置完成事务创建的。
这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction 事务对象 的过程,在 AbstractTransactionManager 实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。
![avatar](images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png) ![avatar](<images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png>)
```java ```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
@ -102,9 +105,11 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
} }
} }
``` ```
在以上的处理过程之后,可以看到,具体的事务创建可以交给事务处理器来完成。在事务的创建过程中,已经为事务的管理做好了准备,包括记录事务处理状态,以及绑定事务信息和线程等。下面到事务处理器中去了解一下更底层的事务创建过程。 在以上的处理过程之后,可以看到,具体的事务创建可以交给事务处理器来完成。在事务的创建过程中,已经为事务的管理做好了准备,包括记录事务处理状态,以及绑定事务信息和线程等。下面到事务处理器中去了解一下更底层的事务创建过程。
createTransactionIfNecessary()方法 通过调用 PlatformTransactionManager 的 getTransaction()方法,生成一个 TransactionStatus对象封装了底层事务对象的创建。可以看到AbstractPlatformTransactionManager 提供了创建事务的模板这个模板会被具体的事务处理器所使用。从下面的代码中可以看到AbstractPlatformTransactionManager 会根据事务属性配置和当前进程绑定的事务信息,对事务是否需要创建,怎样创建 进行一些通用的处理,然后把事务创建的底层工作交给具体的事务处理器完成。尽管具体的事务处理器完成事务创建的过程各不相同,但是不同的事务处理器对事务属性和当前进程事务信息的处理都是相同的,在 **AbstractPlatformTransactionManager** 中完成了该实现,这个实现过程是 Spring 提供统一事务处理的一个重要部分。 createTransactionIfNecessary()方法 通过调用 PlatformTransactionManager 的 getTransaction()方法,生成一个 TransactionStatus 对象封装了底层事务对象的创建。可以看到AbstractPlatformTransactionManager 提供了创建事务的模板这个模板会被具体的事务处理器所使用。从下面的代码中可以看到AbstractPlatformTransactionManager 会根据事务属性配置和当前进程绑定的事务信息,对事务是否需要创建,怎样创建 进行一些通用的处理,然后把事务创建的底层工作交给具体的事务处理器完成。尽管具体的事务处理器完成事务创建的过程各不相同,但是不同的事务处理器对事务属性和当前进程事务信息的处理都是相同的,在 **AbstractPlatformTransactionManager** 中完成了该实现,这个实现过程是 Spring 提供统一事务处理的一个重要部分。
```java ```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@ -177,9 +182,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
} }
``` ```
从上面的代码中可以看到AbstractTransactionManager 提供的创建事务的实现模板,在这个模板的基础上,具体的事务处理器需要定义自己的实现来完成底层的事务创建工作,比如需要实现 isExistingTransaction() 和 doBegin()方法。关于这些由具体事务处理器实现的方法会在下面结合具体的事务处理器实现DataSourceTransactionManager、HibernateTransactionManager进行分析。
事务创建的结果是生成一个 TransactionStatus对象 通过这个对象来保存事务处理需要的基本信息,这个对象与前面提到过的 TransactionInfo对象 联系在一起, TransactionStatus 是 TransactionInfo 的一个属性,然后会把 TransactionInfo 保存在 ThreadLocal对象 里,这样当前线程可以通过 ThreadLocal对象 取得 TransactionInfo以及与这个事务对应的 TransactionStatus对象从而把事务的处理信息与调用事务方法的当前线程绑定起来。在 AbstractPlatformTransactionManager 创建事务的过程中,可以看到 TransactionStatus 的创建过程。 从上面的代码中可以看到AbstractTransactionManager 提供的创建事务的实现模板,在这个模板的基础上,具体的事务处理器需要定义自己的实现来完成底层的事务创建工作,比如需要实现 isExistingTransaction() 和 doBegin()方法。关于这些由具体事务处理器实现的方法会在下面结合具体的事务处理器实现DataSourceTransactionManager、HibernateTransactionManager 进行分析。
事务创建的结果是生成一个 TransactionStatus 对象, 通过这个对象来保存事务处理需要的基本信息,这个对象与前面提到过的 TransactionInfo 对象 联系在一起, TransactionStatus 是 TransactionInfo 的一个属性,然后会把 TransactionInfo 保存在 ThreadLocal 对象 里,这样当前线程可以通过 ThreadLocal 对象 取得 TransactionInfo以及与这个事务对应的 TransactionStatus 对象,从而把事务的处理信息与调用事务方法的当前线程绑定起来。在 AbstractPlatformTransactionManager 创建事务的过程中,可以看到 TransactionStatus 的创建过程。
```java ```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@ -202,9 +209,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
} }
``` ```
新事务的创建是比较好理解的,这里需要根据事务属性配置进行创建。所谓创建,首先是把创建工作交给具体的事务处理器来完成,比如 DataSourceTransactionManager把创建的事务对象在 TransactionStatus 中保存下来,然后将其他的事务属性和 线程ThreadLocal变量 进行绑定。
相对于创建全新事务的另一种情况是:在创建当前事务时,线程中已经有事务存在了。这种情况同样需要处理,在声明式事务处理中,在当前线程调用事务方法的时候,就会考虑事务的创建处理,这个处理在方法 handleExistingTransaction() 中完成。这里对现有事务的处理,会涉及事务传播属性的具体处理,比如 PROPAGATION_NOT_SUPPORTED、PROPAGATION_ REQUIRES_ NEW等。 新事务的创建是比较好理解的,这里需要根据事务属性配置进行创建。所谓创建,首先是把创建工作交给具体的事务处理器来完成,比如 DataSourceTransactionManager把创建的事务对象在 TransactionStatus 中保存下来,然后将其他的事务属性和 线程 ThreadLocal 变量 进行绑定。
相对于创建全新事务的另一种情况是:在创建当前事务时,线程中已经有事务存在了。这种情况同样需要处理,在声明式事务处理中,在当前线程调用事务方法的时候,就会考虑事务的创建处理,这个处理在方法 handleExistingTransaction() 中完成。这里对现有事务的处理,会涉及事务传播属性的具体处理,比如 PROPAGATION*NOT_SUPPORTED、PROPAGATION* REQUIRES\_ NEW 等。
```java ```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@ -321,7 +330,9 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
``` ```
## 3 事务的挂起 ## 3 事务的挂起
事务的挂起牵涉线程与事务处理信息的保存,下面看一下事务挂起的实现。 事务的挂起牵涉线程与事务处理信息的保存,下面看一下事务挂起的实现。
```java ```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@ -371,10 +382,13 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
} }
``` ```
基于以上内容,就可以完成声明式事务处理的创建了。声明式事务处理能使事务处理应用的开发变得简单,但是简单的背后,蕴含着平台付出的许多努力。 基于以上内容,就可以完成声明式事务处理的创建了。声明式事务处理能使事务处理应用的开发变得简单,但是简单的背后,蕴含着平台付出的许多努力。
## 4 事务的提交 ## 4 事务的提交
下面来看看事务提交是如何实现的。有了前面的对事务创建的分析,下面来分析一下在 Spring 中,声明式事务处理的事务提交是如何完成的。事务提交的调用入口是 TransactionInteceptor 的 invoke()方法,事务提交的具体实现则在其基类 TransactionAspectSupport 的 commitTransactionAfterReturning(TransactionInfo txInfo)方法 中,其中的参数 txInfo 是创建事务时生成的。同时Spring 的事务管理框架生成的 TransactionStatus对象 就包含在 TransactionInfo对象 中。这个 commitTransactionAfterReturning()方法 在 TransactionInteceptor 的实现部分是比较简单的,它通过直接调用事务处理器来完成事务提交。
下面来看看事务提交是如何实现的。有了前面的对事务创建的分析,下面来分析一下在 Spring 中,声明式事务处理的事务提交是如何完成的。事务提交的调用入口是 TransactionInteceptor 的 invoke()方法,事务提交的具体实现则在其基类 TransactionAspectSupport 的 commitTransactionAfterReturning(TransactionInfo txInfo)方法 中,其中的参数 txInfo 是创建事务时生成的。同时Spring 的事务管理框架生成的 TransactionStatus 对象 就包含在 TransactionInfo 对象 中。这个 commitTransactionAfterReturning()方法 在 TransactionInteceptor 的实现部分是比较简单的,它通过直接调用事务处理器来完成事务提交。
```java ```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
@ -391,7 +405,9 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
} }
} }
``` ```
与前面分析事务的创建过程一样,我们需要到事务管理器中去看看事务是如何提交的。同样,在 AbstractPlatformTransactionManager 中也有一个模板方法支持具体的事务管理器对事务提交的实现,这个模板方法的实现与前面我们看到的 getTransaction() 很像。 与前面分析事务的创建过程一样,我们需要到事务管理器中去看看事务是如何提交的。同样,在 AbstractPlatformTransactionManager 中也有一个模板方法支持具体的事务管理器对事务提交的实现,这个模板方法的实现与前面我们看到的 getTransaction() 很像。
```java ```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@ -479,9 +495,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
} }
``` ```
可以看到,事务提交的准备都是由具体的事务处理器来实现的。当然,对这些事务提交的处理,需要通过对 TransactionStatus 保存的事务处理的相关状态进行判断。提交过程涉及 AbstractPlatformTransactionManager 中的 doCommit() 和 prepareForCommit()方法,它们都是抽象方法,都在具体的事务处理器中完成实现,在下面对具体事务处理器的实现原理的分析中,可以看到对这些实现方法的具体分析。 可以看到,事务提交的准备都是由具体的事务处理器来实现的。当然,对这些事务提交的处理,需要通过对 TransactionStatus 保存的事务处理的相关状态进行判断。提交过程涉及 AbstractPlatformTransactionManager 中的 doCommit() 和 prepareForCommit()方法,它们都是抽象方法,都在具体的事务处理器中完成实现,在下面对具体事务处理器的实现原理的分析中,可以看到对这些实现方法的具体分析。
## 5 事务的回滚 ## 5 事务的回滚
```java ```java
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@ -541,6 +559,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran
} }
} }
``` ```
以上对事务的创建、提交和回滚的实现原理进行了分析,这些过程的实现都比较复杂,一方面 这些处理会涉及很多事务属性的处理;另一方面 会涉及事务处理过程中状态的设置,同时在事务处理的过程中,有许多处理也需要根据相应的状态来完成。这样看来,在实现事务处理的基本过程中就会产生许多事务处理的操作分支。 以上对事务的创建、提交和回滚的实现原理进行了分析,这些过程的实现都比较复杂,一方面 这些处理会涉及很多事务属性的处理;另一方面 会涉及事务处理过程中状态的设置,同时在事务处理的过程中,有许多处理也需要根据相应的状态来完成。这样看来,在实现事务处理的基本过程中就会产生许多事务处理的操作分支。
但总的来说,在事务执行的实现过程中,作为执行控制的 TransactionInfo对象 和 TransactionStatus对象 特别值得我们注意,比如它们如何与线程进行绑定,如何记录事务的执行情况等。如果大家在配置事务属性时有什么疑惑,不妨直接看看这些事务属性的处理过程,通过对这些实现原理的了解,可以极大地提高对这些事务处理属性使用的理解程度。 但总的来说,在事务执行的实现过程中,作为执行控制的 TransactionInfo 对象 和 TransactionStatus 对象 特别值得我们注意,比如它们如何与线程进行绑定,如何记录事务的执行情况等。如果大家在配置事务属性时有什么疑惑,不妨直接看看这些事务属性的处理过程,通过对这些实现原理的了解,可以极大地提高对这些事务处理属性使用的理解程度。

@ -1,16 +1,18 @@
## 1 Spring事务处理的应用场景 ## 1 Spring 事务处理的应用场景
下面,我们以 DataSourceTransactionManager事务管理器 为例看一下在具体的事务管理器中如何实现事务创建、提交和回滚这些底层的事务处理操作。DataSourceTransationManager 和其他事务管理器一样,如 JtaTransactionManagerJpaTransactionManager 和 JdoTransactionManager都继承自 AbstractPlatformManager作为一个基类AbstractPlatfromManager 封装了 Spring事务处理 中通用的处理部分,比如事务的创建、提交、回滚,事务状态和信息的处理,与线程的绑定等,有了这些通用处理的支持,对于具体的事务管理器而言,它们只需要处理和具体数据源相关的组件设置就可以了,比如在 HibernateTransactionManager 中,就只需要配置好和 Hibnernate事务处理 相关的接口以及相关的设置。所以,从 PlatformTransactionManager组件 的设计关系上我们也可以看到Spring事务处理 的主要过程是分两个部分完成的,通用的事务处理框架是在 AbstractPlatformManager 中完成,而 Spring 的事务接口与数据源实现的接口,多半是由具体的事务管理器来完成,它们都是作为 AbstractPlatformManager 的子类来是使用的。
可以看到,在 PlatformTransactionManager组件 的设计中 ,通过 PlatformTransactionManager接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。 下面,我们以 DataSourceTransactionManager 事务管理器 为例看一下在具体的事务管理器中如何实现事务创建、提交和回滚这些底层的事务处理操作。DataSourceTransationManager 和其他事务管理器一样,如 JtaTransactionManagerJpaTransactionManager 和 JdoTransactionManager都继承自 AbstractPlatformManager作为一个基类AbstractPlatfromManager 封装了 Spring 事务处理 中通用的处理部分,比如事务的创建、提交、回滚,事务状态和信息的处理,与线程的绑定等,有了这些通用处理的支持,对于具体的事务管理器而言,它们只需要处理和具体数据源相关的组件设置就可以了,比如在 HibernateTransactionManager 中,就只需要配置好和 Hibnernate 事务处理 相关的接口以及相关的设置。所以,从 PlatformTransactionManager 组件 的设计关系上我们也可以看到Spring 事务处理 的主要过程是分两个部分完成的,通用的事务处理框架是在 AbstractPlatformManager 中完成,而 Spring 的事务接口与数据源实现的接口,多半是由具体的事务管理器来完成,它们都是作为 AbstractPlatformManager 的子类来是使用的。
可以看到,在 PlatformTransactionManager 组件 的设计中 ,通过 PlatformTransactionManager 接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager 抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。
![avatar](../../../images/springTransaction/PlatformTransactionManager组件的设计.png) ![avatar](../../../images/springTransaction/PlatformTransactionManager组件的设计.png)
## 2 DataSourceTransactionManager的实现 ## 2 DataSourceTransactionManager 的实现
我们先看一下 DataSourceTransactionManager在这个事务管理器中它的实现直接与事务处理的底层实现相关。在事务开始的时候会调用 doBegin()方法,首先会得到相对应的 Connection然后可以根据事务设置的需要对 Connection 的相关属性进行配置,比如将 Connection 的 autoCommit功能 关闭,并对像 TimeoutInSeconds 这样的事务处理参数进行设置,最后通过 TransactionSynchronizationManager 来对资源进行绑定。
我们先看一下 DataSourceTransactionManager在这个事务管理器中它的实现直接与事务处理的底层实现相关。在事务开始的时候会调用 doBegin()方法,首先会得到相对应的 Connection然后可以根据事务设置的需要对 Connection 的相关属性进行配置,比如将 Connection 的 autoCommit 功能 关闭,并对像 TimeoutInSeconds 这样的事务处理参数进行设置,最后通过 TransactionSynchronizationManager 来对资源进行绑定。
从下面的代码中可以看到DataSourceTransactionManager 作为 AbstractPlatformTransactionManager 的子类,在 AbstractPlatformTransactionManager 中已经为事务实现设计好了一系列的模板方法,比如 事务的提交、回滚处理等。在 DataSourceTransactionManager 中, 可以看到对模板方法中一些抽象方法的具体实现。例如,由 DataSourceTransactionManager 的 doBegin()方法 实现负责事务的创建工作。具体来说,如果使用 DataSource 创建事务,最终通过设置 Connection 的 autoCommit属性 来对事务处理进行配置。在实现过程中,需要把数据库的 Connection 和当前的线程进行绑定。对于事务的提交和回滚,都是通过直接调用 Connection 的提交和回滚来完成的,在这个实现过程中,如何取得事务处理场景中的 Connection对象也是一个值得注意的地方。 从下面的代码中可以看到DataSourceTransactionManager 作为 AbstractPlatformTransactionManager 的子类,在 AbstractPlatformTransactionManager 中已经为事务实现设计好了一系列的模板方法,比如 事务的提交、回滚处理等。在 DataSourceTransactionManager 中, 可以看到对模板方法中一些抽象方法的具体实现。例如,由 DataSourceTransactionManager 的 doBegin()方法 实现负责事务的创建工作。具体来说,如果使用 DataSource 创建事务,最终通过设置 Connection 的 autoCommit 属性 来对事务处理进行配置。在实现过程中,需要把数据库的 Connection 和当前的线程进行绑定。对于事务的提交和回滚,都是通过直接调用 Connection 的提交和回滚来完成的,在这个实现过程中,如何取得事务处理场景中的 Connection 对象,也是一个值得注意的地方。
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit 属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。
![avatar](../../../images/springTransaction/实现DataSourceTransactionManager的时序图.png) ![avatar](../../../images/springTransaction/实现DataSourceTransactionManager的时序图.png)
@ -137,22 +139,25 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
} }
} }
``` ```
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性调用 Connection 的 commit() 和 rollback()方法 来完成的。看到这里,大家一定会觉得非常的熟悉。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit 属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。看到这里,大家一定会觉得非常的熟悉。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。
## 3 小结 ## 3 小结
总体来说,从声明式事务的整个实现中我们看到,声明式事务处理完全可以看成是一个具体的 Spring AOP应用。从这个角度来看Spring事务处理 的实现本身就为应用开发者提供了一个非常优秀的 AOP应用 参考实例。在 Spring 的声明式事务处理中,采用了 IoC容器 的 Bean配置 为事务方法调用提供事务属性设置,从而为应用对事务处理的使用提供方便。
有了声明式的使用方式,可以把对事务处理的实现与应用代码分离出来。从 Spring实现 的角度来看,声明式事务处理的大致实现过程是这样的:在为事务处理配置好 AOP 的基础设施(比如,对应的 Proxy代理对象 和 事务处理Interceptor拦截器对象)之后,首先需要完成对这些事务属性配置的读取,这些属性的读取处理是在 TransactionInterceptor 中实现的在完成这些事务处理属性的读取之后Spring 为事务处理的具体实现做好了准备。可以看到Spring声明式事务处理 的过程同时也是一个整合事务处理实现到 Spring AOP 和 IoC容器 中去的过程。我们在整个过程中可以看到下面一些要点,在这些要点中,体现了对 Spring框架 的基本特性的灵活使用。 总体来说,从声明式事务的整个实现中我们看到,声明式事务处理完全可以看成是一个具体的 Spring AOP 应用。从这个角度来看Spring 事务处理 的实现本身就为应用开发者提供了一个非常优秀的 AOP 应用 参考实例。在 Spring 的声明式事务处理中,采用了 IoC 容器 的 Bean 配置 为事务方法调用提供事务属性设置,从而为应用对事务处理的使用提供方便。
- 如何封装各种不同事务处理环境下的事务处理,具体来说,作为应用平台的 Spring它没法对应用使用什么样的事务处理环境做出限制这样对应用户使用的不同的事务处理器Spring事务处理平台 都需要为用户提供服务。这些事务处理实现包括在应用中常见的 DataSource 的 Connection、Hibermate 的 Transaction等Spring事务处理 通过一种统一的方式把它们封装起来,从而实现一个通用的事务处理过程,实现这部分事务处理对应用透明,使应用即开即用。
有了声明式的使用方式,可以把对事务处理的实现与应用代码分离出来。从 Spring 实现 的角度来看,声明式事务处理的大致实现过程是这样的:在为事务处理配置好 AOP 的基础设施(比如,对应的 Proxy 代理对象 和 事务处理 Interceptor 拦截器对象)之后,首先需要完成对这些事务属性配置的读取,这些属性的读取处理是在 TransactionInterceptor 中实现的在完成这些事务处理属性的读取之后Spring 为事务处理的具体实现做好了准备。可以看到Spring 声明式事务处理 的过程同时也是一个整合事务处理实现到 Spring AOP 和 IoC 容器 中去的过程。我们在整个过程中可以看到下面一些要点,在这些要点中,体现了对 Spring 框架 的基本特性的灵活使用。
- 如何封装各种不同事务处理环境下的事务处理,具体来说,作为应用平台的 Spring它没法对应用使用什么样的事务处理环境做出限制这样对应用户使用的不同的事务处理器Spring 事务处理平台 都需要为用户提供服务。这些事务处理实现包括在应用中常见的 DataSource 的 Connection、Hibermate 的 Transaction 等Spring 事务处理 通过一种统一的方式把它们封装起来,从而实现一个通用的事务处理过程,实现这部分事务处理对应用透明,使应用即开即用。
- 如何读取事务处理属性值,在事务处理属性正确读取的基础上,结合事务处理代码,从而完成在既定的事务处理配置下,事务处理方法的实现。 - 如何读取事务处理属性值,在事务处理属性正确读取的基础上,结合事务处理代码,从而完成在既定的事务处理配置下,事务处理方法的实现。
- 如何灵活地使用 Spring AOP框架对事务处理进行封装提供给应用即开即用的声明式事务处理功能。 - 如何灵活地使用 Spring AOP 框架,对事务处理进行封装,提供给应用即开即用的声明式事务处理功能。
在这个过程中,有几个 Spring事务处理 的核心类是我们需要关注的。其中包括 **TransactionInterceptor**,它是使用 AOP 实现声明式事务处理的拦截器,封装了 Spring 对声明式事务处理实现的基本过程;还包括 TransactionAttributeSource 和 **TransactionAttribute** 这两个类,它们封装了对声明式事务处理属性的识别,以及信息的读入和配置。我们看到的 TransactionAttribute对象可以视为对事务处理属性的数据抽象如果在使用声明式事务处理的时候应用没有配置这些属性Spring 将为用户提供 DefaultTransactionAttribute对象该对象提供了默认的事务处理属性设置。 在这个过程中,有几个 Spring 事务处理 的核心类是我们需要关注的。其中包括 **TransactionInterceptor**,它是使用 AOP 实现声明式事务处理的拦截器,封装了 Spring 对声明式事务处理实现的基本过程;还包括 TransactionAttributeSource 和 **TransactionAttribute** 这两个类,它们封装了对声明式事务处理属性的识别,以及信息的读入和配置。我们看到的 TransactionAttribute 对象可以视为对事务处理属性的数据抽象如果在使用声明式事务处理的时候应用没有配置这些属性Spring 将为用户提供 DefaultTransactionAttribute 对象,该对象提供了默认的事务处理属性设置。
在事务处理过程中,可以看到 **TransactionInfo****TransactionStatus** 这两个对象它们是存放事务处理信息的主要数据对象它们通过与线程的绑定来实现事务的隔离性。具体来说TransactionInfo对象 本身就像是一个栈,对应着每一次事务方法的调用,它会保存每一次事务方法调用的事务处理信息。值得注意的是,在 TransactionInfo对象 中,它持有 TransactionStatus对象这个 TransactionStatus 是非常重要的。由这个 TransactionStatus 来掌管事务执行的详细信息,包括具体的事务对象、事务执行状态、事务设置状态等。 在事务处理过程中,可以看到 **TransactionInfo****TransactionStatus** 这两个对象它们是存放事务处理信息的主要数据对象它们通过与线程的绑定来实现事务的隔离性。具体来说TransactionInfo 对象 本身就像是一个栈,对应着每一次事务方法的调用,它会保存每一次事务方法调用的事务处理信息。值得注意的是,在 TransactionInfo 对象 中,它持有 TransactionStatus 对象,这个 TransactionStatus 是非常重要的。由这个 TransactionStatus 来掌管事务执行的详细信息,包括具体的事务对象、事务执行状态、事务设置状态等。
在事务的创建、启动、提交和回滚的过程中,都需要与这个 TransactionStatus对象 中的数据打交道。在准备完这些与事务管理有关的数据之后,具体的事务处理是由 事务处理器TransactionManager 来完成的。在事务处理器完成事务处理的过程中,与具体事务处理器无关的操作都被封装到 AbstractPlatformTransactionManager 中实现了。这个抽象的事务处理器为不同的具体事务处理器提供了通用的事务处理模板,它封装了在事务处理过程中,与具体事务处理器无关的公共的事务处理部分。我们在具体的事务处理器(比如 DataSourceTransactionManager 和 HibernateTransactionManager)的实现中可以看到,最为底层的事务创建、挂起、提交、回滚操作。 在事务的创建、启动、提交和回滚的过程中,都需要与这个 TransactionStatus 对象 中的数据打交道。在准备完这些与事务管理有关的数据之后,具体的事务处理是由 事务处理器 TransactionManager 来完成的。在事务处理器完成事务处理的过程中,与具体事务处理器无关的操作都被封装到 AbstractPlatformTransactionManager 中实现了。这个抽象的事务处理器为不同的具体事务处理器提供了通用的事务处理模板,它封装了在事务处理过程中,与具体事务处理器无关的公共的事务处理部分。我们在具体的事务处理器(比如 DataSourceTransactionManager 和 HibernateTransactionManager)的实现中可以看到,最为底层的事务创建、挂起、提交、回滚操作。
在 Spring 中,也可以通过编程式的方法来使用事务处理器,以帮助我们处理事务。在编程式的事务处理使用中, TransactionDefinition 是定义事务处理属性的类。对于事务处理属性Spring 还提供了一个默认的事务属性 DefaultTransactionDefinition 来供用户使用。这种事务处理方式在实现上看起来比声明式事务处理要简单,但编程式实现事务处理却会造成事务处理与业务代码的紧密耦合,因而不经常被使用。在这里,我们之所以举编程式使用事务处理的例子,是因为通过了解编程式事务处理的使用,可以清楚地了解 Spring 统一实现事务处理的大致过程。 在 Spring 中,也可以通过编程式的方法来使用事务处理器,以帮助我们处理事务。在编程式的事务处理使用中, TransactionDefinition 是定义事务处理属性的类。对于事务处理属性Spring 还提供了一个默认的事务属性 DefaultTransactionDefinition 来供用户使用。这种事务处理方式在实现上看起来比声明式事务处理要简单,但编程式实现事务处理却会造成事务处理与业务代码的紧密耦合,因而不经常被使用。在这里,我们之所以举编程式使用事务处理的例子,是因为通过了解编程式事务处理的使用,可以清楚地了解 Spring 统一实现事务处理的大致过程。
有了这个背景,结合对声明式事务处理实现原理的详细分析,比如在声明式事务处理中,使用 AOP 对事务处理进行封装,对事务属性配置进行的处理,与线程绑定从而处理事务并发,并结合事务处理器的使用等,能够在很大程度上提高我们对整个 Spring事务处理实现 的理解。 有了这个背景,结合对声明式事务处理实现原理的详细分析,比如在声明式事务处理中,使用 AOP 对事务处理进行封装,对事务属性配置进行的处理,与线程绑定从而处理事务并发,并结合事务处理器的使用等,能够在很大程度上提高我们对整个 Spring 事务处理实现 的理解。

@ -1,15 +1,19 @@
## 1 设计原理与基本过程 ## 1 设计原理与基本过程
在使用 Spring声明式事务处理 的时候,一种常用的方法是结合 IoC容器 和 Spring 已有的 TransactionProxyFactoryBean 对事务管理进行配置,比如,可以在这个 TransactionProxyFactoryBean 中为事务方法配置传播行为、并发事务隔离级别等事务处理属性,从而对声明式事务的处理提供指导。具体来说,在对声明式事务处理的原理分析中,声明式事务处理的实现大致可以分为以下几个部分:
- 读取和处理在 IoC容器 中配置的事务处理属性,并转化为 Spring事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor从名字可以看出它是一个 AOP通知器Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事物处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。 在使用 Spring 声明式事务处理 的时候,一种常用的方法是结合 IoC 容器 和 Spring 已有的 TransactionProxyFactoryBean 对事务管理进行配置,比如,可以在这个 TransactionProxyFactoryBean 中为事务方法配置传播行为、并发事务隔离级别等事务处理属性,从而对声明式事务的处理提供指导。具体来说,在对声明式事务处理的原理分析中,声明式事务处理的实现大致可以分为以下几个部分:
- Spring事务处理模块 实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性以及与线程绑定完成事务处理的过程Spring 通过 TransactionInfo 和 TransactionStatus 这两个数据对象,在事务处理过程中记录和传递相关执行场景。
- 底层的事务处理实现。对于底层的事务操作Spring 委托给具体的事务处理器来完成,这些具体的事务处理器,就是在 IoC容器 中配置声明式事务处理时,配置的 PlatformTransactionManager 的具体实现,比如 DataSourceTransactionManager 和 HibernateTransactionManager 等。 - 读取和处理在 IoC 容器 中配置的事务处理属性,并转化为 Spring 事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor从名字可以看出它是一个 AOP 通知器Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC 容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事物处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。
- Spring 事务处理模块 实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性以及与线程绑定完成事务处理的过程Spring 通过 TransactionInfo 和 TransactionStatus 这两个数据对象,在事务处理过程中记录和传递相关执行场景。
- 底层的事务处理实现。对于底层的事务操作Spring 委托给具体的事务处理器来完成,这些具体的事务处理器,就是在 IoC 容器 中配置声明式事务处理时,配置的 PlatformTransactionManager 的具体实现,比如 DataSourceTransactionManager 和 HibernateTransactionManager 等。
## 2 实现分析 ## 2 实现分析
### 2.1 事务处理拦截器的配置 ### 2.1 事务处理拦截器的配置
和前面的思路一样,从声明式事务处理的基本用法入手,来了解它的基本实现原理。在使用声明式事务处理的时候,需要在 IoC容器 中配置 TransactionProxyFactoryBean见名知义这是一个 FactoryBean有一个 getObject()方法。在 IoC容器 进行注入的时候,会创建 TransactionInterceptor对象而这个对象会创建一个 TransactionAttributePointcut为读取 TransactionAttribute 做准备。在容器初始化的过程中,由于实现了 InitializingBean接口因此 AbstractSingletonProxyFactoryBean 会实现 afterPropertiesSet()方法,正是在这个方法实例化了一个 ProxyFactory建立起 Spring AOP 的应用,在这里,会为这个 ProxyFactory 设置通知、目标对象,并最终返回 Proxy代理对象。在 Proxy代理对象 建立起来以后,在调用其代理方法的时候,会调用相应的 TransactionInterceptor拦截器在这个调用中会根据 TransactionAttribute 配置的事务属性进行配置,从而为事务处理做好准备。
从 TransactionProxyFactoryBean 入手,通过代码实现来了解 Spring 是如何通过 AOP功能 来完成事务管理配置的Spring 为声明式事务处理的实现所做的一些准备工作:包括为 AOP 配置基础设施,这些基础设施包括设置 拦截器TransactionInterceptor、通知器DefaultPointcutAdvisor 或 TransactionAttributeSourceAdvisor。同时在 TransactionProxyFactoryBean 的实现中, 还可以看到注人进来的 PlatformTransactionManager 和 事务处理属性TransactionAttribute 等。 和前面的思路一样,从声明式事务处理的基本用法入手,来了解它的基本实现原理。在使用声明式事务处理的时候,需要在 IoC 容器 中配置 TransactionProxyFactoryBean见名知义这是一个 FactoryBean有一个 getObject()方法。在 IoC 容器 进行注入的时候,会创建 TransactionInterceptor 对象,而这个对象会创建一个 TransactionAttributePointcut为读取 TransactionAttribute 做准备。在容器初始化的过程中,由于实现了 InitializingBean 接口,因此 AbstractSingletonProxyFactoryBean 会实现 afterPropertiesSet()方法,正是在这个方法实例化了一个 ProxyFactory建立起 Spring AOP 的应用,在这里,会为这个 ProxyFactory 设置通知、目标对象,并最终返回 Proxy 代理对象。在 Proxy 代理对象 建立起来以后,在调用其代理方法的时候,会调用相应的 TransactionInterceptor 拦截器,在这个调用中,会根据 TransactionAttribute 配置的事务属性进行配置,从而为事务处理做好准备。
从 TransactionProxyFactoryBean 入手,通过代码实现来了解 Spring 是如何通过 AOP 功能 来完成事务管理配置的Spring 为声明式事务处理的实现所做的一些准备工作:包括为 AOP 配置基础设施,这些基础设施包括设置 拦截器 TransactionInterceptor、通知器 DefaultPointcutAdvisor 或 TransactionAttributeSourceAdvisor。同时在 TransactionProxyFactoryBean 的实现中, 还可以看到注人进来的 PlatformTransactionManager 和 事务处理属性 TransactionAttribute 等。
```java ```java
/** /**
* 代理工厂bean 用于简化声明式事务处理,这是标准 AOP 的一个方便的替代方案 * 代理工厂bean 用于简化声明式事务处理,这是标准 AOP 的一个方便的替代方案
@ -69,11 +73,13 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe
} }
} }
``` ```
以上代码完成了 AOP配置对于用户来说一个值得关心的问题是Spring 的 TransactionInterceptor配置 是在什么时候被启动并成为 Advisor通知器 的一部分的呢?从对 createMainInterceptor()方法 的调用分析中可以看到,这个 createMainInterceptor()方法 在 IoC容器 完成 Bean的依赖注入时通过 initializeBean()方法 被调用,具体的调用过程如下图所示。
![avatar](../../../images/springTransaction/createMainInterceptor()方法的调用链.png) 以上代码完成了 AOP 配置对于用户来说一个值得关心的问题是Spring 的 TransactionInterceptor 配置 是在什么时候被启动并成为 Advisor 通知器 的一部分的呢?从对 createMainInterceptor()方法 的调用分析中可以看到,这个 createMainInterceptor()方法 在 IoC 容器 完成 Bean 的依赖注入时,通过 initializeBean()方法 被调用,具体的调用过程如下图所示。
![avatar](<../../../images/springTransaction/createMainInterceptor()方法的调用链.png>)
在 TransactionProxyFactoryBean 的父类 AbstractSingletonProxyFactoryBean 中的 afterPropertiesSet()方法,是 Spring 事务处理 完成 AOP 配置 的地方,在建立 TransactionProxyFactoryBean 的事务处理拦截器的时候,首先需要对 ProxyFactoryBean 的 目标 Bean 设置进行检查,如果这个 目标 Bean 的设置是正确的,就会创建一个 ProxyFactory 对象,从而实现 AOP 的使用。在 afterPropertiesSet() 的方法实现中,可以看到为 ProxyFactory 生成代理对象、配置通知器、设置代理接口方法等。
在 TransactionProxyFactoryBean 的父类 AbstractSingletonProxyFactoryBean 中的 afterPropertiesSet()方法,是 Spring事务处理 完成 AOP配置 的地方,在建立 TransactionProxyFactoryBean 的事务处理拦截器的时候,首先需要对 ProxyFactoryBean 的 目标Bean 设置进行检查,如果这个 目标Bean 的设置是正确的,就会创建一个 ProxyFactory对象从而实现 AOP 的使用。在 afterPropertiesSet() 的方法实现中,可以看到为 ProxyFactory 生成代理对象、配置通知器、设置代理接口方法等。
```java ```java
public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanClassLoaderAware, InitializingBean { implements FactoryBean<Object>, BeanClassLoaderAware, InitializingBean {
@ -154,10 +160,13 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
} }
} }
``` ```
DefaultAopProxyFactory 创建 AOP Proxy 的过程在前面分析 AOP的实现原理 时已分析过这里就不再重复了。可以看到通过以上的一系列步骤Spring 为实现事务处理而设计的 拦截器TransctionInterceptor 已经设置到 ProxyFactory 生成的 AOP代理对象 中去了,这里的 TransactionInterceptor 是作为 AOP Advice 的拦截器来实现它的功能的。在 IoC容器 中,配置其他与事务处理有关的属性,比如,比较熟悉的 transactionManager 和事务处理的属性,也同样会被设置到已经定义好的 TransactionInterceptor 中去。这些属性配置在 TransactionInterceptor对事务方法进行拦截时会起作用。在 AOP配置 完成以后,可以看到,在 Spring声明式事务处理实现 中的一些重要的类已经悄然登场,比如 TransactionAttributeSourceAdvisor 和 TransactionInterceptor正是这些类通过 AOP 封装了 Spring 对事务处理的基本实现。
DefaultAopProxyFactory 创建 AOP Proxy 的过程在前面分析 AOP 的实现原理 时已分析过这里就不再重复了。可以看到通过以上的一系列步骤Spring 为实现事务处理而设计的 拦截器 TransctionInterceptor 已经设置到 ProxyFactory 生成的 AOP 代理对象 中去了,这里的 TransactionInterceptor 是作为 AOP Advice 的拦截器来实现它的功能的。在 IoC 容器 中,配置其他与事务处理有关的属性,比如,比较熟悉的 transactionManager 和事务处理的属性,也同样会被设置到已经定义好的 TransactionInterceptor 中去。这些属性配置在 TransactionInterceptor对事务方法进行拦截时会起作用。在 AOP 配置 完成以后,可以看到,在 Spring 声明式事务处理实现 中的一些重要的类已经悄然登场,比如 TransactionAttributeSourceAdvisor 和 TransactionInterceptor正是这些类通过 AOP 封装了 Spring 对事务处理的基本实现。
### 2.2 事务处理配置的读入 ### 2.2 事务处理配置的读入
在 AOP配置 完成的基础上,以 TransactionAttributeSourceAdvisor的实现 为入口,了解具体的事务属性配置是如何读入的。
在 AOP 配置 完成的基础上,以 TransactionAttributeSourceAdvisor 的实现 为入口,了解具体的事务属性配置是如何读入的。
```java ```java
public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor { public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor {
@ -181,7 +190,9 @@ public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor {
}; };
} }
``` ```
在声明式事务处理中,通过对目标对象的方法调用进行拦截来实现事务处理的织入,这个拦截通过 AOP 发挥作用。在 AOP 中,对于拦截的启动,首先需要对方法调用是否需要拦截进行判断,而判断的依据是那些在 TransactionProxyFactoryBean 中为目标对象设置的事务属性。也就是说,需要判断当前的目标方法调用是不是一个配置好的并且需要进行事务处理的方法调用。具体来说,这个匹配判断在 TransactionAttributeSourcePointcut 的 matches()方法 中完成,该方法实现 首先把事务方法的属性配置读取到 TransactionAttributeSource对象 中,有了这些事务处理的配置以后,根据当前方法调用的 Method对象 和 目标对象,对是否需要启动事务处理拦截器进行判断。
在声明式事务处理中,通过对目标对象的方法调用进行拦截来实现事务处理的织入,这个拦截通过 AOP 发挥作用。在 AOP 中,对于拦截的启动,首先需要对方法调用是否需要拦截进行判断,而判断的依据是那些在 TransactionProxyFactoryBean 中为目标对象设置的事务属性。也就是说,需要判断当前的目标方法调用是不是一个配置好的并且需要进行事务处理的方法调用。具体来说,这个匹配判断在 TransactionAttributeSourcePointcut 的 matches()方法 中完成,该方法实现 首先把事务方法的属性配置读取到 TransactionAttributeSource 对象 中,有了这些事务处理的配置以后,根据当前方法调用的 Method 对象 和 目标对象,对是否需要启动事务处理拦截器进行判断。
```java ```java
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
@ -191,7 +202,9 @@ abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPoi
} }
} }
``` ```
在 Pointcut 的 matches()判断过程 中,会用到 TransactionAttributeSource对象这个 TransactionAttributeSource对象 是在对 TransactionInterceptor 进行依赖注入时就配置好的。它的设置是在 TransactionInterceptor 的基类 TransactionAspectSupport 中完成的,配置的是一个 NameMatchTransactionAttributeSource对象。
在 Pointcut 的 matches()判断过程 中,会用到 TransactionAttributeSource 对象,这个 TransactionAttributeSource 对象 是在对 TransactionInterceptor 进行依赖注入时就配置好的。它的设置是在 TransactionInterceptor 的基类 TransactionAspectSupport 中完成的,配置的是一个 NameMatchTransactionAttributeSource 对象。
```java ```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
/** /**
@ -206,9 +219,11 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
} }
} }
``` ```
在以上的代码实现中可以看到NameMatchTransactionAttributeSource 作为 TransactionAttributeSource 的具体实现,是实际完成事务处理属性读入和匹配的地方。在对 事务属性TransactionAttributes 进行设置时,会从事务处理属性配置中读取事务方法名和配置属性,在得到配置的事务方法名和属性以后,会把它们作为键值对加入到一个 nameMap 中。
在应用调用目标方法的时候,因为这个目标方法已经被 TransactionProxyFactoryBean 代理,所以 TransactionProxyFactoryBean 需要判断这个调用方法是否是事务方法。这个判断的实现,是通过在 NameMatchTransactionAttributeSource 中能否为这个调用方法返回事务属性来完成的。具体的实现过程是这样的:首先,以调用方法名为索引在 nameMap 中查找相应的事务处理属性值,如果能够找到,那么就说明该调用方法和事务方法是直接对应的,如果找不到,那么就会遍历整个 nameMap对保存在 nameMap 中的每一个方法名,使用 PatternMatchUtils的simpleMatch()方法 进行命名模式上的匹配。这里使用 PatternMatchUtils 进行匹配的原因是,在设置事务方法的时候,可以不需要为事务方法设置一个完整的方法名,而可以通过设置方法名的命名模式来完成,比如可以通过对 通配符* 的使用等。所以,如果直接通过方法名没能够匹配上,而通过方法名的命名模式能够匹配上,这个方法也是需要进行事务处理的,相对应地,它所配置的事务处理属性也会从 nameMap 中取出来,从而触发事务处理拦截器的拦截。 在以上的代码实现中可以看到NameMatchTransactionAttributeSource 作为 TransactionAttributeSource 的具体实现,是实际完成事务处理属性读入和匹配的地方。在对 事务属性 TransactionAttributes 进行设置时,会从事务处理属性配置中读取事务方法名和配置属性,在得到配置的事务方法名和属性以后,会把它们作为键值对加入到一个 nameMap 中。
在应用调用目标方法的时候,因为这个目标方法已经被 TransactionProxyFactoryBean 代理,所以 TransactionProxyFactoryBean 需要判断这个调用方法是否是事务方法。这个判断的实现,是通过在 NameMatchTransactionAttributeSource 中能否为这个调用方法返回事务属性来完成的。具体的实现过程是这样的:首先,以调用方法名为索引在 nameMap 中查找相应的事务处理属性值,如果能够找到,那么就说明该调用方法和事务方法是直接对应的,如果找不到,那么就会遍历整个 nameMap对保存在 nameMap 中的每一个方法名,使用 PatternMatchUtils 的 simpleMatch()方法 进行命名模式上的匹配。这里使用 PatternMatchUtils 进行匹配的原因是,在设置事务方法的时候,可以不需要为事务方法设置一个完整的方法名,而可以通过设置方法名的命名模式来完成,比如可以通过对 通配符\* 的使用等。所以,如果直接通过方法名没能够匹配上,而通过方法名的命名模式能够匹配上,这个方法也是需要进行事务处理的,相对应地,它所配置的事务处理属性也会从 nameMap 中取出来,从而触发事务处理拦截器的拦截。
```java ```java
public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable {
@ -272,10 +287,13 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute
} }
} }
``` ```
通过以上过程可以得到与目标对象调用方法相关的 TransactionAttribute对象在这个对象中封装了事务处理的配置。具体来说在前面的匹配过程中如果匹配返回的结果是 null那么说明当前的调用方法不是一个事务方法不需要纳入 Spring 统一的事务管理中,因为它并没有配置在 TransactionProxyFactoryBean 的事务处理设置中。如果返回的 TransactionAttribute对象 不是 null,那么这个返回的 TransactionAttribute对象 就已经包含了对事务方法的配置信息,对应这个事务方法的具体事务配置也已经读入到 TransactionAttribute对象 中了,为 TransactionInterceptor 做好了对调用的目标方法添加事务处理的准备。
通过以上过程可以得到与目标对象调用方法相关的 TransactionAttribute 对象,在这个对象中,封装了事务处理的配置。具体来说,在前面的匹配过程中,如果匹配返回的结果是 null那么说明当前的调用方法不是一个事务方法不需要纳入 Spring 统一的事务管理中,因为它并没有配置在 TransactionProxyFactoryBean 的事务处理设置中。如果返回的 TransactionAttribute 对象 不是 null,那么这个返回的 TransactionAttribute 对象 就已经包含了对事务方法的配置信息,对应这个事务方法的具体事务配置也已经读入到 TransactionAttribute 对象 中了,为 TransactionInterceptor 做好了对调用的目标方法添加事务处理的准备。
### 2.3 事务处理拦截器的设计与实现 ### 2.3 事务处理拦截器的设计与实现
在完成以上的准备工作后,经过 TransactionProxyFactoryBean 的 AOP包装 此时如果对目标对象进行方法调用,起作用的对象实际上是一个 Proxy代理对象对目标对象方法的调用不会直接作用在 TransactionProxyFactoryBean 设置的目标对象上,而会被设置的事务处理拦截器拦截。而在 TransactionProxyFactoryBean 的 AOP实现 中,获取 Proxy对象 的过程并不复杂TransactionProxyFactoryBean 作为一个 FactoryBean对这个 Bean 的对象的引用是通过调用其父类 AbstractSingletonProxyFactoryBean 的 getObject()方法 来得到的。
在完成以上的准备工作后,经过 TransactionProxyFactoryBean 的 AOP 包装, 此时如果对目标对象进行方法调用,起作用的对象实际上是一个 Proxy 代理对象,对目标对象方法的调用,不会直接作用在 TransactionProxyFactoryBean 设置的目标对象上,而会被设置的事务处理拦截器拦截。而在 TransactionProxyFactoryBean 的 AOP 实现 中,获取 Proxy 对象 的过程并不复杂TransactionProxyFactoryBean 作为一个 FactoryBean对这个 Bean 的对象的引用是通过调用其父类 AbstractSingletonProxyFactoryBean 的 getObject()方法 来得到的。
```java ```java
public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanClassLoaderAware, InitializingBean { implements FactoryBean<Object>, BeanClassLoaderAware, InitializingBean {
@ -291,7 +309,9 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig
} }
} }
``` ```
InvocationHandler 的实现类中有一个非常重要的方法 invoke(),该方法是 proxy代理对象 的回调方法,在调用 proxy对象 的代理方法时触发这个回调。事务处理拦截器TransactionInterceptor 中实现了 InvocationHandler 的 invoke()方法,其过程是,首先获得调用方法的事务处理配置;在得到事务处理配置以后,会取得配置的 PlatformTransactionManager由这个事务处理器来实现事务的创建、提交、回滚操作。
InvocationHandler 的实现类中有一个非常重要的方法 invoke(),该方法是 proxy 代理对象 的回调方法,在调用 proxy 对象 的代理方法时触发这个回调。事务处理拦截器 TransactionInterceptor 中实现了 InvocationHandler 的 invoke()方法,其过程是,首先获得调用方法的事务处理配置;在得到事务处理配置以后,会取得配置的 PlatformTransactionManager由这个事务处理器来实现事务的创建、提交、回滚操作。
```java ```java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable { public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
@ -416,9 +436,10 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
} }
} }
``` ```
以事务提交为例,简要的说明下该过程。在调用代理的事务方法时,因为前面已经完成了一系列 AOP配置对事务方法的调用最终启动
TransactionInterceptor拦截器 的 invoke()方法。在这个方法中,首先会读取该事务方法的事务属性配置,然后根据事务属性配置以及具体事务处理器的配置来决定采用哪一个事务处理器,这个事务处理器实际上是一个 PlatformTransactionManager。在确定好具体的事务处理器之后会根据事务的运行情况和事务配置来决定是不是需要创建新的事务。
对于 Spring 而言,事务的管理实际上是通过一个 TransactionInfo对象 来完成的,在该对象中,封装了事务对象和事务处理的状态信息,这是事务处理的抽象。在这一步完成以后,会对拦截器链进行处理,因为有可能在该事务对象中还配置了除事务处理 AOP 之外的其他拦截器。在结束对拦截器链处理之后,会对 TransactionInfo 中的信息进行更新,以反映最近的事务处理情况,在这个时候,也就完成了事务提交的准备,通过调用 事务处理器PlatformTransactionManager 的 commitTransactionAfterReturning()方法 来完成事务的提交。这个提交的处理过程已经封装在 PlatformTransactionManager 的事务处理器中了,而与具体数据源相关的处理过程,最终委托给相关的具体事务处理器来完成,比如 DataSourceTransactionManager、Hibermate'TransactionManager 等。 以事务提交为例,简要的说明下该过程。在调用代理的事务方法时,因为前面已经完成了一系列 AOP 配置,对事务方法的调用,最终启动
TransactionInterceptor 拦截器 的 invoke()方法。在这个方法中,首先会读取该事务方法的事务属性配置,然后根据事务属性配置以及具体事务处理器的配置来决定采用哪一个事务处理器,这个事务处理器实际上是一个 PlatformTransactionManager。在确定好具体的事务处理器之后会根据事务的运行情况和事务配置来决定是不是需要创建新的事务。
对于 Spring 而言,事务的管理实际上是通过一个 TransactionInfo 对象 来完成的,在该对象中,封装了事务对象和事务处理的状态信息,这是事务处理的抽象。在这一步完成以后,会对拦截器链进行处理,因为有可能在该事务对象中还配置了除事务处理 AOP 之外的其他拦截器。在结束对拦截器链处理之后,会对 TransactionInfo 中的信息进行更新,以反映最近的事务处理情况,在这个时候,也就完成了事务提交的准备,通过调用 事务处理器 PlatformTransactionManager 的 commitTransactionAfterReturning()方法 来完成事务的提交。这个提交的处理过程已经封装在 PlatformTransactionManager 的事务处理器中了,而与具体数据源相关的处理过程,最终委托给相关的具体事务处理器来完成,比如 DataSourceTransactionManager、Hibermate'TransactionManager 等。
在这个 invoke()方法 的实现中,可以看到整个事务处理在 AOP拦截器 中实现的全过程。同时,它也是 Spring 采用 AOP 封装事务处理和实现声明式事务处理的核心部分。这部分实现是一个桥梁,它胶合了具体的事务处理和 Spring AOP框架可以看成是一个 Spring AOP应用在这个桥梁搭建完成以后Spring事务处理 的实现就开始了。 在这个 invoke()方法 的实现中,可以看到整个事务处理在 AOP 拦截器 中实现的全过程。同时,它也是 Spring 采用 AOP 封装事务处理和实现声明式事务处理的核心部分。这部分实现是一个桥梁,它胶合了具体的事务处理和 Spring AOP 框架,可以看成是一个 Spring AOP 应用在这个桥梁搭建完成以后Spring 事务处理 的实现就开始了。

@ -5,6 +5,7 @@
前几天出摊卖烤面筋时,灵感大作,即兴唱了一首“我的烤面筋”,被网友拍下来传到某站上 成了网红现在我要趁势而上把自己祖传的烤面筋工艺宣传出去让我那个臭弟弟“ClassPathXmlApplicationContext”知道谁才是 IoC 容器的正统传人! 前几天出摊卖烤面筋时,灵感大作,即兴唱了一首“我的烤面筋”,被网友拍下来传到某站上 成了网红现在我要趁势而上把自己祖传的烤面筋工艺宣传出去让我那个臭弟弟“ClassPathXmlApplicationContext”知道谁才是 IoC 容器的正统传人!
## 第一阶段BeanDefinition 资源定位ReaderbeanDefinitionReaderdocumentReader ## 第一阶段BeanDefinition 资源定位ReaderbeanDefinitionReaderdocumentReader
新的一天从 new 开始,但我却还躺在床上各种伸懒腰,毕竟我现在也是个小老板了,很多杂七杂八的活雇几个小弟干就行咯。我拿起我的 iBanana11 看了看商业街董事(某程序员)发的“精选优质面筋批发市场地址”,然后深吸一口气 refresh(),闭上眼 obtainFreshBeanFactory(),气沉丹田 refreshBeanFactory(),大喊一声: 新的一天从 new 开始,但我却还躺在床上各种伸懒腰,毕竟我现在也是个小老板了,很多杂七杂八的活雇几个小弟干就行咯。我拿起我的 iBanana11 看了看商业街董事(某程序员)发的“精选优质面筋批发市场地址”,然后深吸一口气 refresh(),闭上眼 obtainFreshBeanFactory(),气沉丹田 refreshBeanFactory(),大喊一声:
“loadBeanDefinitions()!” “loadBeanDefinitions()!”
我虎背熊腰的小弟“beanDefinitionReader” 破门而入,尖声细语地问道: 我虎背熊腰的小弟“beanDefinitionReader” 破门而入,尖声细语地问道:
@ -16,11 +17,13 @@ Reader 家有一对兄妹,哥哥 beanDefinitionReader 虎背熊腰大老粗,
妹妹会打开 Document 取出其中最大的几个箱子(&lt;beans>、&lt;import>、&lt;alias> 等一级标签),分别进行处理。其中 beans 箱最为重要,里面放满了夜市的主角,烤面筋的核心材料。 妹妹会打开 Document 取出其中最大的几个箱子(&lt;beans>、&lt;import>、&lt;alias> 等一级标签),分别进行处理。其中 beans 箱最为重要,里面放满了夜市的主角,烤面筋的核心材料。
## 第二阶段:将 bean 解析封装成 BeanDefinitionHolderBeanDefinitionParserDelegate ## 第二阶段:将 bean 解析封装成 BeanDefinitionHolderBeanDefinitionParserDelegate
之后妹妹会拿起我们 IoC 家族祖传的面筋处理神器 BeanDefinitionParserDelegate从 beans 箱里面一个一个取出形态各异的面筋 bean 分别进行加工处理。刚拿出来的面筋 bean 是不会直接烤了卖的,我们会将 bean 用神器 ParserDelegate 进行九九八十一道细致处理,所以我们家烤出来的面筋才会如此劲道美味,世世代代延绵不断。 之后妹妹会拿起我们 IoC 家族祖传的面筋处理神器 BeanDefinitionParserDelegate从 beans 箱里面一个一个取出形态各异的面筋 bean 分别进行加工处理。刚拿出来的面筋 bean 是不会直接烤了卖的,我们会将 bean 用神器 ParserDelegate 进行九九八十一道细致处理,所以我们家烤出来的面筋才会如此劲道美味,世世代代延绵不断。
不过处理程序再怎么细致复杂,也不过就是分为两大部分:第一,处理 bean 的属性信息,如 idclassscope 等;第二,处理 bean 的子元素,主要是 <property> 标签,而 <property> 标签又有属性和子元素,且子元素类型更加丰富复杂,可能是&lt;map>&lt;set>&lt;list>&lt;array> 等。所以如果你们想学我家的祖传秘方,开个同样的摊子干倒我,也不是这么容易的哦。 不过处理程序再怎么细致复杂,也不过就是分为两大部分:第一,处理 bean 的属性信息,如 idclassscope 等;第二,处理 bean 的子元素,主要是 <property> 标签,而 <property> 标签又有属性和子元素,且子元素类型更加丰富复杂,可能是&lt;map>&lt;set>&lt;list>&lt;array> 等。所以如果你们想学我家的祖传秘方,开个同样的摊子干倒我,也不是这么容易的哦。
经过上面的步骤,一个配置文件中的面筋 bean 就被处理包装成了半成品 BeanDefinitionHolder。 经过上面的步骤,一个配置文件中的面筋 bean 就被处理包装成了半成品 BeanDefinitionHolder。
## 第三阶段:将 BeanDefinition 注册进 IoC 容器BeanDefinitionReaderUtils ## 第三阶段:将 BeanDefinition 注册进 IoC 容器BeanDefinitionReaderUtils
妹妹在用神器 BeanDefinitionParserDelegate 经过一顿疯狂操作之后,将包装好的半成品 BeanDefinitionHolder 扔进传输机 BeanDefinitionReaderUtils并且输入哥哥给她的神秘人地址就继续处理下一个面筋 bean 咯。 妹妹在用神器 BeanDefinitionParserDelegate 经过一顿疯狂操作之后,将包装好的半成品 BeanDefinitionHolder 扔进传输机 BeanDefinitionReaderUtils并且输入哥哥给她的神秘人地址就继续处理下一个面筋 bean 咯。
之后,传输机将 BeanDefinitionHolder 的包装打开,分别取出 beanName面筋的唯一标识和 BeanDefinition面筋本筋传输的目的地是 BeanDefinitionRegistry 的工作室(这就是我前面给哥哥 beanDefinitionReader 的地址)。 之后,传输机将 BeanDefinitionHolder 的包装打开,分别取出 beanName面筋的唯一标识和 BeanDefinition面筋本筋传输的目的地是 BeanDefinitionRegistry 的工作室(这就是我前面给哥哥 beanDefinitionReader 的地址)。
这家工作室的 BeanDefinitionRegistry 其实就是我的影分身之一,因为我的祖先实现了这个接口。影分身 Registry 检查一下传输过来的 beanName面筋的唯一标识和 BeanDefinition面筋本筋如果没什么问题就把它们用根绳子系在一起扔进我的“王之面筋宝库”一个 ConcurrentHashMap<String, BeanDefinition>(64)也有人把我的“面筋宝库”称作“IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。 这家工作室的 BeanDefinitionRegistry 其实就是我的影分身之一,因为我的祖先实现了这个接口。影分身 Registry 检查一下传输过来的 beanName面筋的唯一标识和 BeanDefinition面筋本筋如果没什么问题就把它们用根绳子系在一起扔进我的“王之面筋宝库”一个 ConcurrentHashMap<String, BeanDefinition>(64)也有人把我的“面筋宝库”称作“IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。

@ -1,11 +1,10 @@
# Spring 事务 # Spring 事务
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 声明式事务 ## 声明式事务
### Propagation ### Propagation
- 事务传播 - 事务传播
@ -71,8 +70,6 @@ public enum Propagation {
- 事务级别 - 事务级别
```java ```java
public enum Isolation { public enum Isolation {
@ -124,12 +121,6 @@ public enum Isolation {
} }
``` ```
### EnableTransactionManagement ### EnableTransactionManagement
- 下面代码是一个注解方式的事务配置使用 `EnableTransactionManagement`来开启事务支持 - 下面代码是一个注解方式的事务配置使用 `EnableTransactionManagement`来开启事务支持
@ -180,8 +171,6 @@ public @interface EnableTransactionManagement {
} }
``` ```
```java ```java
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
@ -209,12 +198,8 @@ public class TransactionManagementConfigurationSelector extends AdviceModeImport
} }
``` ```
### ProxyTransactionManagementConfiguration ### ProxyTransactionManagementConfiguration
```java ```java
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@ -270,16 +255,10 @@ public class ProxyTransactionManagementConfiguration extends AbstractTransaction
} }
``` ```
### TransactionInterceptor ### TransactionInterceptor
![image-20200729144622440](/images/spring/image-20200729144622440.png) ![image-20200729144622440](/images/spring/image-20200729144622440.png)
- 实现了`org.aopalliance.intercept.MethodInterceptor`接口的方法 - 实现了`org.aopalliance.intercept.MethodInterceptor`接口的方法
```java ```java
@ -331,11 +310,9 @@ public class DeclarativeTransactionTest {
} }
``` ```
![image-20200729145518089](/images/spring/image-20200729145518089.png) ![image-20200729145518089](/images/spring/image-20200729145518089.png)
断点开始进行查阅. 再断点后执行一步会直接进入cglib代理对象 断点开始进行查阅. 再断点后执行一步会直接进入 cglib 代理对象
`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept` 具体不展开,继续往下执行 `org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept` 具体不展开,继续往下执行
@ -343,18 +320,10 @@ public class DeclarativeTransactionTest {
走到`invoke`方法了 走到`invoke`方法了
入参对象查看 入参对象查看
![image-20200729145835608](/images/spring/image-20200729145835608.png) ![image-20200729145835608](/images/spring/image-20200729145835608.png)
- 获取事务属性 - 获取事务属性
```java ```java
@ -408,16 +377,10 @@ public class DeclarativeTransactionTest {
``` ```
![image-20200729162023837](/images/spring/image-20200729162023837.png) ![image-20200729162023837](/images/spring/image-20200729162023837.png)
- 此处方法已经获取到了这个方法就是后面的一个切面 - 此处方法已经获取到了这个方法就是后面的一个切面
- 确定事务管理器 - 确定事务管理器
```java ```java
@ -460,14 +423,8 @@ public class DeclarativeTransactionTest {
} }
``` ```
![image-20200729160650401](/images/spring/image-20200729160650401.png) ![image-20200729160650401](/images/spring/image-20200729160650401.png)
- 类型转换 - 类型转换
```java ```java
@ -485,8 +442,6 @@ public class DeclarativeTransactionTest {
} }
``` ```
- 获取方法切面 - 获取方法切面
```java ```java
@ -507,14 +462,8 @@ public class DeclarativeTransactionTest {
} }
``` ```
![image-20200729161647214](/images/spring/image-20200729161647214.png) ![image-20200729161647214](/images/spring/image-20200729161647214.png)
- 创建一个新的事务根据事务传播性 - 创建一个新的事务根据事务传播性
```java ```java
@ -552,16 +501,8 @@ public class DeclarativeTransactionTest {
``` ```
![image-20200729163303000](/images/spring/image-20200729163303000.png) ![image-20200729163303000](/images/spring/image-20200729163303000.png)
- `tm.getTransaction` - `tm.getTransaction`
```JAVA ```JAVA
@ -699,12 +640,6 @@ public class DeclarativeTransactionTest {
} }
``` ```
- `prepareTransactionInfo`简单的`new`对象并且绑定线程 - `prepareTransactionInfo`简单的`new`对象并且绑定线程
```JAVA ```JAVA
@ -740,13 +675,9 @@ public class DeclarativeTransactionTest {
} }
``` ```
- `retVal = invocation.proceedWithInvocation();` - `retVal = invocation.proceedWithInvocation();`
- 这里走的是CGLIB的方法直接会执行结果将结果返回具体方法在 - 这里走的是 CGLIB 的方法直接会执行结果将结果返回具体方法在
`org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation#proceed` `org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation#proceed`
@ -772,8 +703,6 @@ public class DeclarativeTransactionTest {
``` ```
- 如果没有异常就直接处理完成返回了 - 如果没有异常就直接处理完成返回了
- 我们现在是有异常的 - 我们现在是有异常的
@ -795,8 +724,6 @@ public class DeclarativeTransactionTest {
} }
``` ```
- `completeTransactionAfterThrowing`回滚异常的处理方法 - `completeTransactionAfterThrowing`回滚异常的处理方法
```java ```java
@ -848,9 +775,7 @@ public class DeclarativeTransactionTest {
`txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())` `txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())`
- **注意: 这里的异常如果是exception不会走回滚** - **注意: 这里的异常如果是 exception 不会走回滚**
- 判断是否需要回滚 - 判断是否需要回滚
@ -910,8 +835,6 @@ public class DeclarativeTransactionTest {
- 这就是我们的异常判断是否需要回滚 - 这就是我们的异常判断是否需要回滚
- `cleanupTransactionInfo` - `cleanupTransactionInfo`
数据清理 数据清理
@ -932,20 +855,8 @@ public class DeclarativeTransactionTest {
} }
``` ```
## 编程式事务 ## 编程式事务
### DefaultTransactionDefinition ### DefaultTransactionDefinition
- 默认的事务定义 - 默认的事务定义
@ -954,14 +865,8 @@ public class DeclarativeTransactionTest {
2. readOnly 2. readOnly
3. .... 3. ....
### PlatformTransactionManager ### PlatformTransactionManager
```java ```java
// 获取事务 // 获取事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException; TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;
@ -973,8 +878,6 @@ void rollback(TransactionStatus status) throws TransactionException;
- 贴出一部分 - 贴出一部分
![image-20200728105926218](/images/spring/image-20200728105926218.png) ![image-20200728105926218](/images/spring/image-20200728105926218.png)
- AbstractPlatformTransactionManager 定义了一些基础属性 以及一些需要子类实现的方法 - AbstractPlatformTransactionManager 定义了一些基础属性 以及一些需要子类实现的方法
@ -1004,15 +907,9 @@ doCleanupAfterCompletion
``` ```
### DataSourceTransactionManager ### DataSourceTransactionManager
- xml配置如下 - xml 配置如下
```xml ```xml
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
@ -1031,7 +928,7 @@ doCleanupAfterCompletion
</bean> </bean>
``` ```
- 两个属性通常我们会配置datasource - 两个属性,通常我们会配置 datasource
```java ```java
@Nullable @Nullable
@ -1056,11 +953,7 @@ doCleanupAfterCompletion
``` ```
- 如果`dataSource`为空会抛出异常 - 如果`dataSource`为空会抛出异常
- 默认单例会注册到ioc容器中.后续注册流程不具体描述 - 默认单例会注册到 ioc 容器中.后续注册流程不具体描述
- 方法注释 - 方法注释
@ -1353,20 +1246,10 @@ doCleanupAfterCompletion
} }
``` ```
### AbstractPlatformTransactionManager ### AbstractPlatformTransactionManager
- abstract 修饰具体定义的方法不具体展开。主要关注实现`org.springframework.transaction.PlatformTransactionManager`的几个方法 - abstract 修饰具体定义的方法不具体展开。主要关注实现`org.springframework.transaction.PlatformTransactionManager`的几个方法
#### commit 方法 #### commit 方法
```java ```java
@ -1403,12 +1286,6 @@ public final void commit(TransactionStatus status) throws TransactionException {
} }
``` ```
```java ```java
private void processCommit(DefaultTransactionStatus status) throws TransactionException { private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try { try {
@ -1484,12 +1361,8 @@ private void processCommit(DefaultTransactionStatus status) throws TransactionEx
} }
``` ```
#### rollback 方法 #### rollback 方法
```java ```java
@Override @Override
public final void rollback(TransactionStatus status) throws TransactionException { public final void rollback(TransactionStatus status) throws TransactionException {
@ -1505,8 +1378,6 @@ public final void rollback(TransactionStatus status) throws TransactionException
} }
``` ```
```java ```java
private void processRollback(DefaultTransactionStatus status, boolean unexpected) { private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try { try {
@ -1574,10 +1445,6 @@ private void processRollback(DefaultTransactionStatus status, boolean unexpected
} }
``` ```
### TransactionSynchronizationManager ### TransactionSynchronizationManager
- 事务同步管理器 - 事务同步管理器
@ -1621,8 +1488,6 @@ private void processRollback(DefaultTransactionStatus status, boolean unexpected
new NamedThreadLocal<>("Actual transaction active"); new NamedThreadLocal<>("Actual transaction active");
``` ```
#### 资源方法 #### 资源方法
##### 获取资源 ##### 获取资源
@ -1704,16 +1569,6 @@ public static boolean hasResource(Object key) {
} }
``` ```
##### 资源绑定 ##### 资源绑定
```java ```java
@ -1746,9 +1601,7 @@ public static void bindResource(Object key, Object value) throws IllegalStateExc
} }
``` ```
- debug 使用的是 druid 的数据源
- debug 使用的是druid的数据源
![image-20200729090322058](/images/spring/image-20200729090322058.png) ![image-20200729090322058](/images/spring/image-20200729090322058.png)
@ -1816,12 +1669,8 @@ static Object unwrapResourceIfNecessary(Object resource) {
- `com.alibaba.druid.pool.DruidDataSource`不是`ScopedObject` 直接返回 - `com.alibaba.druid.pool.DruidDataSource`不是`ScopedObject` 直接返回
后续就是一个`map`的`put`方法不具体展开 后续就是一个`map`的`put`方法不具体展开
##### 解除资源绑定 ##### 解除资源绑定
```java ```java
@ -1863,17 +1712,14 @@ public static Object unbindResource(Object key) throws IllegalStateException {
``` ```
map 对象的remove操作 map 对象的 remove 操作
#### 其他 #### 其他
- 其他几个都是使用`ThreadLocal`进行数据设置操作即可. - 其他几个都是使用`ThreadLocal`进行数据设置操作即可.
--- ---
### TransactionTemplate ### TransactionTemplate
- 属性 - 属性
@ -1893,14 +1739,10 @@ map 对象的remove操作
</bean> </bean>
``` ```
- 事务操作模板类图 - 事务操作模板类图
![image-20200728094658684](/images/spring/image-20200728094658684.png) ![image-20200728094658684](/images/spring/image-20200728094658684.png)
- `org.springframework.beans.factory.InitializingBean`接口的实现 - `org.springframework.beans.factory.InitializingBean`接口的实现
```java ```java
@ -1912,10 +1754,6 @@ map 对象的remove操作
} }
``` ```
#### execute #### execute
```java ```java
@ -1955,4 +1793,3 @@ map 对象的remove操作
} }
} }
``` ```

@ -1,4 +1,5 @@
# Spring AnnotationUtils # Spring AnnotationUtils
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
- `org.springframework.core.annotation.AnnotationUtils`提供了注解相关的方法 - `org.springframework.core.annotation.AnnotationUtils`提供了注解相关的方法
@ -7,22 +8,25 @@
1. getValue: 获取属性值 1. getValue: 获取属性值
1. getDefaultValue: 获取默认值 1. getDefaultValue: 获取默认值
## getAnnotation ## getAnnotation
- 测试用例如下 - 测试用例如下
```java ```java
@Test @Test
public void findMethodAnnotationOnLeaf() throws Exception { public void findMethodAnnotationOnLeaf() throws Exception {
Method m = Leaf.class.getMethod("annotatedOnLeaf"); Method m = Leaf.class.getMethod("annotatedOnLeaf");
assertNotNull(m.getAnnotation(Order.class)); assertNotNull(m.getAnnotation(Order.class));
assertNotNull(getAnnotation(m, Order.class)); assertNotNull(getAnnotation(m, Order.class));
assertNotNull(findAnnotation(m, Order.class)); assertNotNull(findAnnotation(m, Order.class));
} }
``` ```
- `org.springframework.core.annotation.AnnotationUtils.getAnnotation(java.lang.reflect.Method, java.lang.Class<A>)` - `org.springframework.core.annotation.AnnotationUtils.getAnnotation(java.lang.reflect.Method, java.lang.Class<A>)`
```java ```java
/** /**
* Get a single {@link Annotation} of {@code annotationType} from the * Get a single {@link Annotation} of {@code annotationType} from the
* supplied {@link Method}, where the annotation is either <em>present</em> * supplied {@link Method}, where the annotation is either <em>present</em>
* or <em>meta-present</em> on the method. * or <em>meta-present</em> on the method.
@ -39,26 +43,27 @@
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @see #getAnnotation(AnnotatedElement, Class) * @see #getAnnotation(AnnotatedElement, Class)
*/ */
@Nullable @Nullable
public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) { public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
// 函数 // 函数
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
// 强制转换 // 强制转换
return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
} }
``` ```
- method - method
![image-20200116085344737](../../../images/spring/image-20200116085344737.png) ![image-20200116085344737](../../../images/spring/image-20200116085344737.png)
- annotationType - annotationType
![image-20200116085423073](../../../images/spring/image-20200116085423073.png) ![image-20200116085423073](../../../images/spring/image-20200116085423073.png)
```java ```java
@Nullable @Nullable
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) { public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
try { try {
// 获取注解 // 获取注解
A annotation = annotatedElement.getAnnotation(annotationType); A annotation = annotatedElement.getAnnotation(annotationType);
@ -75,20 +80,23 @@
handleIntrospectionFailure(annotatedElement, ex); handleIntrospectionFailure(annotatedElement, ex);
return null; return null;
} }
} }
``` ```
- `org.springframework.core.annotation.AnnotationUtils.synthesizeAnnotation(A, java.lang.reflect.AnnotatedElement)` - `org.springframework.core.annotation.AnnotationUtils.synthesizeAnnotation(A, java.lang.reflect.AnnotatedElement)`
```java ```java
public static <A extends Annotation> A synthesizeAnnotation( public static <A extends Annotation> A synthesizeAnnotation(
A annotation, @Nullable AnnotatedElement annotatedElement) { A annotation, @Nullable AnnotatedElement annotatedElement) {
return synthesizeAnnotation(annotation, (Object) annotatedElement); return synthesizeAnnotation(annotation, (Object) annotatedElement);
} }
``` ```
```java ```java
/** /**
* 注解是否存在别名,没有直接返回 * 注解是否存在别名,没有直接返回
* *
* @param annotation 注解 * @param annotation 注解
@ -96,8 +104,8 @@
* @param <A> * @param <A>
* @return * @return
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <A extends Annotation> A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) { static <A extends Annotation> A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) {
if (annotation instanceof SynthesizedAnnotation || hasPlainJavaAnnotationsOnly(annotatedElement)) { if (annotation instanceof SynthesizedAnnotation || hasPlainJavaAnnotationsOnly(annotatedElement)) {
return annotation; return annotation;
} }
@ -115,13 +123,15 @@
// synthesizable annotation before (which needs to declare @AliasFor from the same package) // synthesizable annotation before (which needs to declare @AliasFor from the same package)
Class<?>[] exposedInterfaces = new Class<?>[]{annotationType, SynthesizedAnnotation.class}; Class<?>[] exposedInterfaces = new Class<?>[]{annotationType, SynthesizedAnnotation.class};
return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler); return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);
} }
``` ```
-`org.springframework.core.annotation.AnnotationUtils.isSynthesizable` -`org.springframework.core.annotation.AnnotationUtils.isSynthesizable`
```java ```java
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) { private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
if (hasPlainJavaAnnotationsOnly(annotationType)) { if (hasPlainJavaAnnotationsOnly(annotationType)) {
return false; return false;
} }
@ -159,14 +169,14 @@
synthesizableCache.put(annotationType, synthesizable); synthesizableCache.put(annotationType, synthesizable);
return synthesizable; return synthesizable;
} }
``` ```
- `org.springframework.core.annotation.AnnotationUtils#getAttributeMethods` - `org.springframework.core.annotation.AnnotationUtils#getAttributeMethods`
```java ```java
static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) { static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) {
List<Method> methods = attributeMethodsCache.get(annotationType); List<Method> methods = attributeMethodsCache.get(annotationType);
if (methods != null) { if (methods != null) {
return methods; return methods;
@ -185,14 +195,14 @@
attributeMethodsCache.put(annotationType, methods); attributeMethodsCache.put(annotationType, methods);
// 函数列表 // 函数列表
return methods; return methods;
} }
``` ```
- `org.springframework.core.annotation.AnnotationUtils#isAttributeMethod` - `org.springframework.core.annotation.AnnotationUtils#isAttributeMethod`
```java ```java
/** /**
* Determine if the supplied {@code method} is an annotation attribute method. * Determine if the supplied {@code method} is an annotation attribute method.
* <p> * <p>
* 做3个判断 * 做3个判断
@ -206,9 +216,9 @@
* @return {@code true} if the method is an attribute method * @return {@code true} if the method is an attribute method
* @since 4.2 * @since 4.2
*/ */
static boolean isAttributeMethod(@Nullable Method method) { static boolean isAttributeMethod(@Nullable Method method) {
return (method != null && method.getParameterCount() == 0 && method.getReturnType() != void.class); return (method != null && method.getParameterCount() == 0 && method.getReturnType() !=void.class);
} }
``` ```
@ -216,7 +226,7 @@
```java ```java
@SuppressWarnings("deprecation") // on JDK 9 @SuppressWarnings("deprecation") // on JDK 9
public static void makeAccessible(Method method) { public static void makeAccessible(Method method) {
// 1. 方法修饰符是不是public // 1. 方法修饰符是不是public
// 2. 注解是不是public // 2. 注解是不是public
// 3. 是否重写 // 3. 是否重写
@ -224,7 +234,7 @@
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) {
method.setAccessible(true); method.setAccessible(true);
} }
} }
``` ```
处理结果 处理结果
@ -233,7 +243,7 @@
![image-20200116085737632](../../../images/spring/image-20200116085737632.png) ![image-20200116085737632](../../../images/spring/image-20200116085737632.png)
处理结果和Order定义相同 处理结果和 Order 定义相同
```java ```java
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -253,28 +263,18 @@ public @interface Order {
} }
``` ```
最终返回 最终返回
![image-20200116085927359](../../../images/spring/image-20200116085927359.png) ![image-20200116085927359](../../../images/spring/image-20200116085927359.png)
## findAnnotation ## findAnnotation
- `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.Method, java.lang.Class<A>)` - `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.Method, java.lang.Class<A>)`
```java ```java
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType) { public static <A extends Annotation> A findAnnotation(Method method, @Nullable Class<A> annotationType) {
Assert.notNull(method, "Method must not be null"); Assert.notNull(method, "Method must not be null");
if (annotationType == null) { if (annotationType == null) {
return null; return null;
@ -324,16 +324,14 @@ public @interface Order {
} }
// 返回 // 返回
return result; return result;
} }
```
```
- `org.springframework.core.annotation.AnnotationUtils.AnnotationCacheKey` - `org.springframework.core.annotation.AnnotationUtils.AnnotationCacheKey`
```java ```java
private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> { private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> {
/** /**
* 带有注解的函数或者类 * 带有注解的函数或者类
@ -350,16 +348,14 @@ public @interface Order {
this.annotationType = annotationType; this.annotationType = annotationType;
} }
} }
``` ```
- `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class<A>)` - `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class<A>)`
```java ```java
@Nullable @Nullable
public static <A extends Annotation> A findAnnotation( public static <A extends Annotation> A findAnnotation(
AnnotatedElement annotatedElement, @Nullable Class<A> annotationType) { AnnotatedElement annotatedElement, @Nullable Class<A> annotationType) {
// 注解类型不为空 // 注解类型不为空
if (annotationType == null) { if (annotationType == null) {
@ -371,19 +367,15 @@ public @interface Order {
// 寻找注解 // 寻找注解
A ann = findAnnotation(annotatedElement, annotationType, new HashSet<>()); A ann = findAnnotation(annotatedElement, annotationType, new HashSet<>());
return (ann != null ? synthesizeAnnotation(ann, annotatedElement) : null); return (ann != null ? synthesizeAnnotation(ann, annotatedElement) : null);
} }
```
```
- `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class<A>, java.util.Set<java.lang.annotation.Annotation>)` - `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class<A>, java.util.Set<java.lang.annotation.Annotation>)`
```java ```java
@Nullable @Nullable
private static <A extends Annotation> A findAnnotation( private static <A extends Annotation> A findAnnotation(
AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) { AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
try { try {
// 直接获取注解 // 直接获取注解
@ -406,44 +398,34 @@ public @interface Order {
handleIntrospectionFailure(annotatedElement, ex); handleIntrospectionFailure(annotatedElement, ex);
} }
return null; return null;
} }
```
```
![image-20200116092259944](../../../images/spring/image-20200116092259944.png) ![image-20200116092259944](../../../images/spring/image-20200116092259944.png)
- `synthesizeAnnotation`方法就不再重复一遍了可以看上文 - `synthesizeAnnotation`方法就不再重复一遍了可以看上文
## getValue ## getValue
- 测试用例 - 测试用例
```java ```java
@Test @Test
public void getValueFromAnnotation() throws Exception { public void getValueFromAnnotation() throws Exception {
Method method = SimpleFoo.class.getMethod("something", Object.class); Method method = SimpleFoo.class.getMethod("something", Object.class);
Order order = findAnnotation(method, Order.class); Order order = findAnnotation(method, Order.class);
assertEquals(1, getValue(order, VALUE)); assertEquals(1, getValue(order, VALUE));
assertEquals(1, getValue(order)); assertEquals(1, getValue(order));
} }
``` ```
- `org.springframework.core.annotation.AnnotationUtils#getValue(java.lang.annotation.Annotation, java.lang.String)` - `org.springframework.core.annotation.AnnotationUtils#getValue(java.lang.annotation.Annotation, java.lang.String)`
```java ```java
@Nullable @Nullable
public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) { public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) {
if (annotation == null || !StringUtils.hasText(attributeName)) { if (annotation == null || !StringUtils.hasText(attributeName)) {
return null; return null;
} }
@ -463,43 +445,33 @@ public @interface Order {
handleIntrospectionFailure(annotation.getClass(), ex); handleIntrospectionFailure(annotation.getClass(), ex);
return null; return null;
} }
} }
``` ```
```java ```java
@Nullable @Nullable
public static Object getValue(Annotation annotation) { public static Object getValue(Annotation annotation) {
return getValue(annotation, VALUE); return getValue(annotation, VALUE);
} }
``` ```
## getDefaultValue ## getDefaultValue
- `org.springframework.core.annotation.AnnotationUtils#getDefaultValue(java.lang.annotation.Annotation)` - `org.springframework.core.annotation.AnnotationUtils#getDefaultValue(java.lang.annotation.Annotation)`
```java ```java
@Nullable @Nullable
public static Object getDefaultValue(Annotation annotation) { public static Object getDefaultValue(Annotation annotation) {
return getDefaultValue(annotation, VALUE); return getDefaultValue(annotation, VALUE);
} }
``` ```
```java ```java
@Nullable @Nullable
public static Object getDefaultValue( public static Object getDefaultValue(
@Nullable Class<? extends Annotation> annotationType, @Nullable String attributeName) { @Nullable Class<? extends Annotation> annotationType, @Nullable String attributeName) {
if (annotationType == null || !StringUtils.hasText(attributeName)) { if (annotationType == null || !StringUtils.hasText(attributeName)) {
@ -512,7 +484,6 @@ public @interface Order {
handleIntrospectionFailure(annotationType, ex); handleIntrospectionFailure(annotationType, ex);
return null; return null;
} }
} }
``` ```

@ -1,9 +1,8 @@
# Spring initApplicationEventMulticaster # Spring initApplicationEventMulticaster
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## demo ## demo
```java ```java
@ -21,8 +20,6 @@ public class DemoApplicationListener implements ApplicationListener {
``` ```
```XML ```XML
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -33,8 +30,6 @@ public class DemoApplicationListener implements ApplicationListener {
</beans> </beans>
``` ```
```JAVA ```JAVA
public class ListenerSourceCode { public class ListenerSourceCode {
public static void main(String[] args) { public static void main(String[] args) {
@ -43,12 +38,9 @@ public class ListenerSourceCode {
} }
``` ```
## 初始化入口 ## 初始化入口
- `org.springframework.context.support.AbstractApplicationContext.refresh`中的`initApplicationEventMulticaster`方法
- `org.springframework.context.support.AbstractApplicationContext.refresh`中的`initApplicationEventMulticaster`方法
```java ```java
protected void initApplicationEventMulticaster() { protected void initApplicationEventMulticaster() {
@ -74,8 +66,6 @@ public class ListenerSourceCode {
``` ```
## 注册 ## 注册
```JAVA ```JAVA
@ -114,8 +104,6 @@ public class ListenerSourceCode {
![image-20200119163638222](../../../images/spring/image-20200119163638222.png) ![image-20200119163638222](../../../images/spring/image-20200119163638222.png)
## finishRefresh 发布 ## finishRefresh 发布
```java ```java
@ -142,10 +130,6 @@ public class ListenerSourceCode {
- `org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)` - `org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)`
- `org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)` - `org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)`
```java ```java
protected void publishEvent(Object event, @Nullable ResolvableType eventType) { protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null"); Assert.notNull(event, "Event must not be null");
@ -186,14 +170,10 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
} }
``` ```
- 执行监听方法 - 执行监听方法
![image-20200119164149650](../../../images/spring/image-20200119164149650.png) ![image-20200119164149650](../../../images/spring/image-20200119164149650.png)
```java ```java
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler(); ErrorHandler errorHandler = getErrorHandler();
@ -212,8 +192,6 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
``` ```
```java ```java
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
@ -239,8 +217,6 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
``` ```
![image-20200119164402137](../../../images/spring/image-20200119164402137.png) ![image-20200119164402137](../../../images/spring/image-20200119164402137.png)
![image-20200119164410301](../../../images/spring/image-20200119164410301.png) ![image-20200119164410301](../../../images/spring/image-20200119164410301.png)

@ -1,4 +1,5 @@
# Spring-BeanDefinitionParserDelegate # Spring-BeanDefinitionParserDelegate
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码路径: `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate` - 源码路径: `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate`
- 注意: 贴出的代码为当前类(`BeanDefinitionParserDelegate`)没有将其他的调用代码贴出,详细请看[huifer-srping](https://github.com/huifer/spring-framework). - 注意: 贴出的代码为当前类(`BeanDefinitionParserDelegate`)没有将其他的调用代码贴出,详细请看[huifer-srping](https://github.com/huifer/spring-framework).
@ -78,8 +79,6 @@
该类大量的`parseXXX`开头的代码,这些方法都是对标签进行解析转换成具体的实体 该类大量的`parseXXX`开头的代码,这些方法都是对标签进行解析转换成具体的实体
### 测试用例 ### 测试用例
```xml ```xml
@ -90,8 +89,6 @@
``` ```
## parseBeanDefinitionAttributes ## parseBeanDefinitionAttributes
- 解析属性`scope`,`abstract`,`lazy-init` - 解析属性`scope`,`abstract`,`lazy-init`
@ -174,7 +171,6 @@ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String
} }
``` ```
![image-20191231162505748](/image/spring/image-20191231162505748.png) ![image-20191231162505748](/image/spring/image-20191231162505748.png)
其他的标签也同样的方式**`getAttribute`**获取 其他的标签也同样的方式**`getAttribute`**获取
@ -185,9 +181,6 @@ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String
1. 有直接获取,设置值 1. 有直接获取,设置值
2. 没有跳过 2. 没有跳过
## parseMetaElements ## parseMetaElements
```java ```java
@ -217,10 +210,6 @@ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String
![image-20191231164622063](/image/spring/image-20191231164622063.png) ![image-20191231164622063](/image/spring/image-20191231164622063.png)
## parseLookupOverrideSubElements ## parseLookupOverrideSubElements
```java ```java
@ -261,12 +250,6 @@ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String
![image-20191231165638975](/image/spring/image-20191231165638975.png) ![image-20191231165638975](/image/spring/image-20191231165638975.png)
## parseReplacedMethodSubElements ## parseReplacedMethodSubElements
```java ```java
@ -304,7 +287,7 @@ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String
### 测试用例 ### 测试用例
编写方法`dis` 编写方法`dis`
```java ```java
public class Person { public class Person {
@ -340,10 +323,6 @@ xml 配置
![image-20200101093742238](/image/spring/image-20200101093742238.png) ![image-20200101093742238](/image/spring/image-20200101093742238.png)
## parseConstructorArgElements ## parseConstructorArgElements
```java ```java
@ -499,8 +478,6 @@ xml 配置
``` ```
### 测试用例 ### 测试用例
- 编辑构造函数 - 编辑构造函数
@ -527,12 +504,8 @@ xml 配置
``` ```
![image-20200101100906778](/image/spring/image-20200101100906778.png) ![image-20200101100906778](/image/spring/image-20200101100906778.png)
## parsePropertyElements ## parsePropertyElements
```java ```java
@ -588,12 +561,8 @@ xml
</bean> </bean>
``` ```
![image-20200101111755022](/image/spring/image-20200101111755022.png) ![image-20200101111755022](/image/spring/image-20200101111755022.png)
## parseQualifierElements ## parseQualifierElements
```java ```java
@ -661,8 +630,6 @@ public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
``` ```
### 测试用例 ### 测试用例
- 编写一个注解 - 编写一个注解
@ -788,18 +755,10 @@ public class QualifierSourceCode {
![image-20200101155539501](/image/spring/image-20200101155539501.png) ![image-20200101155539501](/image/spring/image-20200101155539501.png)
### Bean 解析结果
### Bean解析结果
![image-20200102083512005](/image/spring/image-20200102083512005.png) ![image-20200102083512005](/image/spring/image-20200102083512005.png)
## importBeanDefinitionResource ## importBeanDefinitionResource
```JAVA ```JAVA
@ -895,11 +854,6 @@ public class QualifierSourceCode {
``` ```
![image-20200102085031641](/image/spring/image-20200102085031641.png) ![image-20200102085031641](/image/spring/image-20200102085031641.png)
![image-20200102091421516](/image/spring/image-20200102091421516.png) ![image-20200102091421516](/image/spring/image-20200102091421516.png)

@ -1,10 +1,11 @@
# Spring BeanFactoryPostProcessor # Spring BeanFactoryPostProcessor
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
- 作用: 定制或修改`BeanDefinition`的属性 - 作用: 定制或修改`BeanDefinition`的属性
## Demo ## Demo
```java ```java
public class ChangeAttrBeanPostProcessor implements BeanFactoryPostProcessor { public class ChangeAttrBeanPostProcessor implements BeanFactoryPostProcessor {
private Set<String> attr; private Set<String> attr;
@ -75,8 +76,6 @@ public class BeanFactoryPostProcessorSourceCode {
</beans> </beans>
``` ```
## 初始化 ## 初始化
- `org.springframework.context.support.AbstractApplicationContext#refresh` - `org.springframework.context.support.AbstractApplicationContext#refresh`
@ -249,16 +248,10 @@ public class BeanFactoryPostProcessorSourceCode {
} }
``` ```
![image-20200119085346675](../../../images/spring/image-20200119085346675.png) ![image-20200119085346675](../../../images/spring/image-20200119085346675.png)
![image-20200119085655734](../../../images/spring/image-20200119085655734.png) ![image-20200119085655734](../../../images/spring/image-20200119085655734.png)
## InstantiationAwareBeanPostProcessor ## InstantiationAwareBeanPostProcessor
```java ```java
@ -267,8 +260,6 @@ public class BeanFactoryPostProcessorSourceCode {
} }
``` ```
```java ```java
public static void registerBeanPostProcessors( public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
@ -356,9 +347,7 @@ public class BeanFactoryPostProcessorSourceCode {
``` ```
- 测试用 Bean
- 测试用Bean
```java ```java
public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
@ -370,13 +359,9 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa
} }
``` ```
- 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序 Bean
![image-20200119101026726](../../../images/spring/image-20200119101026726.png)
- 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序Bean
![image-20200119101026726](../../../images/spring/image-20200119101026726.png)
![image-20200119101017989](../../../images/spring/image-20200119101017989.png) ![image-20200119101017989](../../../images/spring/image-20200119101017989.png)
@ -384,10 +369,6 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa
![image-20200119101107820](../../../images/spring/image-20200119101107820.png) ![image-20200119101107820](../../../images/spring/image-20200119101107820.png)
### 使用阶段(调用阶段) ### 使用阶段(调用阶段)
在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])`中有如下代码 在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])`中有如下代码

@ -1,9 +1,8 @@
# Spring Conditional # Spring Conditional
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
## Conditional ## Conditional
```java ```java
@ -20,8 +19,6 @@ public @interface Conditional {
} }
``` ```
## Condition ## Condition
``` ```
@ -36,8 +33,6 @@ public interface Condition {
} }
``` ```
- ConditionContext 上下文 - ConditionContext 上下文
- AnnotatedTypeMetadata 注解信息 - AnnotatedTypeMetadata 注解信息
@ -78,8 +73,6 @@ public interface ConditionContext {
- 唯一实现 : `org.springframework.context.annotation.ConditionEvaluator.ConditionContextImpl` - 唯一实现 : `org.springframework.context.annotation.ConditionEvaluator.ConditionContextImpl`
- 构造方法 - 构造方法
```java ```java
@ -96,12 +89,6 @@ public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
- 在构造方法中加载了一些变量, 这些变量是根据一定规则转换后得到. 具体规则不展开. - 在构造方法中加载了一些变量, 这些变量是根据一定规则转换后得到. 具体规则不展开.
### AnnotatedTypeMetadata ### AnnotatedTypeMetadata
- 元数据接口 - 元数据接口
@ -132,10 +119,6 @@ public interface AnnotatedTypeMetadata {
} }
``` ```
## 源码分析 ## 源码分析
- 对应测试类`org.springframework.context.annotation.ConfigurationClassWithConditionTests` - 对应测试类`org.springframework.context.annotation.ConfigurationClassWithConditionTests`
@ -183,26 +166,10 @@ public void conditionalOnMissingBeanMatch() throws Exception {
``` ```
- `org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean` - `org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean`
### shouldSkip ### shouldSkip
```java ```java
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
@ -246,10 +213,6 @@ public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable Co
} }
``` ```
- 读取注解信息, 并且执行注解对应的类的方法 - 读取注解信息, 并且执行注解对应的类的方法
用例如下. 实例化`BeanTwoConfiguration`对象的时候会去执行`NoBeanOneCondition`方法 用例如下. 实例化`BeanTwoConfiguration`对象的时候会去执行`NoBeanOneCondition`方法
@ -275,13 +238,7 @@ public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable Co
} }
``` ```
在开发中可以自定义matches规则 在开发中可以自定义 matches 规则
这也是在注册的时候第一个方法 这也是在注册的时候第一个方法

@ -1,8 +1,10 @@
# Spring 自定义属性解析器 # Spring 自定义属性解析器
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 用例 ## 用例
```xml ```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -68,7 +70,8 @@ public class DatePropertyEditor extends PropertyEditorSupport {
} }
``` ```
## PropertyEditorRegistrar解析 ## PropertyEditorRegistrar 解析
- 直接在`DatePropertyRegister`打上断点进行查看注册流程 - 直接在`DatePropertyRegister`打上断点进行查看注册流程
![image-20200117104710142](../../../images/spring/image-20200117104710142.png) ![image-20200117104710142](../../../images/spring/image-20200117104710142.png)
@ -83,8 +86,6 @@ public class DatePropertyEditor extends PropertyEditorSupport {
``` ```
```java ```java
@Override @Override
public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) { public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) {
@ -168,8 +169,6 @@ public class DatePropertyEditor extends PropertyEditorSupport {
![image-20200117110115741](../../../images/spring/image-20200117110115741.png) ![image-20200117110115741](../../../images/spring/image-20200117110115741.png)
- 为什么最后结果变成`com.huifer.source.spring.bean.DatePropertyEditor` - 为什么最后结果变成`com.huifer.source.spring.bean.DatePropertyEditor`
看配置文件 看配置文件
@ -184,7 +183,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
``` ```
- 对应的set方法 - 对应的 set 方法
```java ```java
public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) { public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {
@ -194,20 +193,10 @@ public class DatePropertyEditor extends PropertyEditorSupport {
![image-20200117110846256](../../../images/spring/image-20200117110846256.png) ![image-20200117110846256](../../../images/spring/image-20200117110846256.png)
## applyPropertyValues ## applyPropertyValues
- 应用属性值 - 应用属性值
```java ```java
protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
if (pvs.isEmpty()) { if (pvs.isEmpty()) {
@ -316,24 +305,12 @@ public class DatePropertyEditor extends PropertyEditorSupport {
``` ```
![image-20200117133325461](../../../images/spring/image-20200117133325461.png) ![image-20200117133325461](../../../images/spring/image-20200117133325461.png)
![image-20200117141309038](../../../images/spring/image-20200117141309038.png) ![image-20200117141309038](../../../images/spring/image-20200117141309038.png)
![image-20200117141519123](../../../images/spring/image-20200117141519123.png) ![image-20200117141519123](../../../images/spring/image-20200117141519123.png)
- 属性值解析 - 属性值解析
![image-20200117142800671](../../../images/spring/image-20200117142800671.png) ![image-20200117142800671](../../../images/spring/image-20200117142800671.png)
@ -355,8 +332,6 @@ public class DatePropertyEditor extends PropertyEditorSupport {
``` ```
```JAVA ```JAVA
private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) { private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
try { try {
@ -375,8 +350,6 @@ public class DatePropertyEditor extends PropertyEditorSupport {
``` ```
- 调用用例编写的方法 - 调用用例编写的方法
```JAVA ```JAVA
@ -395,9 +368,6 @@ public class DatePropertyEditor extends PropertyEditorSupport {
``` ```
![image-20200117143022827](../../../images/spring/image-20200117143022827.png) ![image-20200117143022827](../../../images/spring/image-20200117143022827.png)
该值也是这个方法的返回`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)` 该值也是这个方法的返回`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)`

@ -1,15 +1,16 @@
# Spring 自定义标签解析 # Spring 自定义标签解析
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
- 与自定义标签解析相关的类 - 与自定义标签解析相关的类
1. `org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser` 1. `org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser`
2. `org.springframework.beans.factory.xml.NamespaceHandlerSupport` 2. `org.springframework.beans.factory.xml.NamespaceHandlerSupport`
- 开始源码之前先搭建一个环境 - 开始源码之前先搭建一个环境
## 环境搭建 ## 环境搭建
- 创建对象 - 创建对象
```java ```java
public class UserXtd { public class UserXtd {
private String userName; private String userName;
@ -32,7 +33,9 @@ public class UserXtd {
} }
} }
``` ```
- 创建 xsd 文件 - 创建 xsd 文件
```xml ```xml
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" <schema xmlns="http://www.w3.org/2001/XMLSchema"
@ -48,7 +51,9 @@ public class UserXtd {
</element> </element>
</schema> </schema>
``` ```
- 创建 namespaceHandler - 创建 namespaceHandler
```java ```java
public class UserNamespaceHandler extends NamespaceHandlerSupport { public class UserNamespaceHandler extends NamespaceHandlerSupport {
@Override @Override
@ -57,7 +62,9 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
} }
} }
``` ```
- 创建 beanDefinitionParser - 创建 beanDefinitionParser
```java ```java
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
/** /**
@ -88,15 +95,21 @@ public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser
} }
``` ```
- 创建 resource/META-INF/spring.handlers - 创建 resource/META-INF/spring.handlers
```text ```text
http\://www.huifer.com/schema/user=com.huifer.source.spring.parser.UserNamespaceHandler http\://www.huifer.com/schema/user=com.huifer.source.spring.parser.UserNamespaceHandler
``` ```
- 创建 resource/META-INF/spring.schemas - 创建 resource/META-INF/spring.schemas
```text ```text
http\://www.huifer.com/schema/user.xsd=META-INF/spring-test.xsd http\://www.huifer.com/schema/user.xsd=META-INF/spring-test.xsd
``` ```
- 创建测试用例xml
- 创建测试用例 xml
```xml ```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" <beans xmlns="http://www.springframework.org/schema/beans"
@ -110,7 +123,9 @@ http\://www.huifer.com/schema/user.xsd=META-INF/spring-test.xsd
</beans> </beans>
``` ```
- 创建 Java 运行方法 - 创建 Java 运行方法
```java ```java
/** /**
* 自定义标签测试用例 * 自定义标签测试用例
@ -123,10 +138,13 @@ public class XSDDemo {
} }
} }
``` ```
- 这里我们希望输出结果是`huifer97@163.com`,运行后结果也确实是`huifer97@163.com` - 这里我们希望输出结果是`huifer97@163.com`,运行后结果也确实是`huifer97@163.com`
## 解析 DefaultNamespaceHandlerResolver ## 解析 DefaultNamespaceHandlerResolver
- 入口方法`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions` - 入口方法`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions`
```java ```java
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) { if (delegate.isDefaultNamespace(root)) {
@ -152,6 +170,7 @@ public class XSDDemo {
} }
``` ```
- 调用链路 - 调用链路
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element)` - `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element)`
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)` - `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)`
@ -187,7 +206,7 @@ public class XSDDemo {
![image-20200109084131415](../../../images/spring/image-20200109084131415.png) ![image-20200109084131415](../../../images/spring/image-20200109084131415.png)
- `http://www.huifer.com/schema/user`和我们定义的xsd文件中的url相同如何找到对应的NamespaceHandler,在`META-INF/spring.handlers`中有定义, - `http://www.huifer.com/schema/user`和我们定义的 xsd 文件中的 url 相同,如何找到对应的 NamespaceHandler,在`META-INF/spring.handlers`中有定义,
`http\://www.huifer.com/schema/user=com.huifer.source.spring.parser.UserNamespaceHandler` `http\://www.huifer.com/schema/user=com.huifer.source.spring.parser.UserNamespaceHandler`
@ -195,8 +214,6 @@ public class XSDDemo {
- 处理方法`org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.resolve` - 处理方法`org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.resolve`
```java ```java
/** /**
* Locate the {@link NamespaceHandler} for the supplied namespace URI * Locate the {@link NamespaceHandler} for the supplied namespace URI
@ -252,8 +269,6 @@ public class XSDDemo {
``` ```
- `org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#getHandlerMappings`跟踪这个方法 - `org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#getHandlerMappings`跟踪这个方法
```JAVA ```JAVA
@ -267,15 +282,13 @@ public class XSDDemo {
} }
``` ```
![image-20200109085606240](../../../images/spring/image-20200109085606240.png) ![image-20200109085606240](../../../images/spring/image-20200109085606240.png)
- 这里直接存在数据了,他是从什么时候加载的? - 这里直接存在数据了,他是从什么时候加载的?
- `org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions` - `org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions`
这个方法在注册bean定义的时候调用 这个方法在注册 bean 定义的时候调用
```java ```java
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
@ -328,8 +341,6 @@ public class XSDDemo {
} }
``` ```
- 继续跟踪`DefaultNamespaceHandlerResolver` - 继续跟踪`DefaultNamespaceHandlerResolver`
`org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver` `org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver`
@ -371,8 +382,6 @@ public class XSDDemo {
``` ```
```JAVA ```JAVA
/** /**
* Load the specified NamespaceHandler mappings lazily. * Load the specified NamespaceHandler mappings lazily.
@ -415,12 +424,6 @@ public class XSDDemo {
![image-20200109094032421](../../../images/spring/image-20200109094032421.png) ![image-20200109094032421](../../../images/spring/image-20200109094032421.png)
## org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve ## org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve
```java ```java
@ -495,8 +498,6 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
``` ```
- 方法走完,回到开始的方法 - 方法走完,回到开始的方法
```java ```java
@ -529,14 +530,8 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
``` ```
![image-20200109092801572](../../../images/spring/image-20200109092801572.png) ![image-20200109092801572](../../../images/spring/image-20200109092801572.png)
## org.springframework.beans.factory.xml.NamespaceHandler#parse ## org.springframework.beans.factory.xml.NamespaceHandler#parse
- `org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse` - `org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse`
@ -631,8 +626,6 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
![image-20200109094654409](../../../images/spring/image-20200109094654409.png) ![image-20200109094654409](../../../images/spring/image-20200109094654409.png)
执行`com.huifer.source.spring.parser.UserBeanDefinitionParser#doParse` 执行`com.huifer.source.spring.parser.UserBeanDefinitionParser#doParse`
```JAVA ```JAVA
@ -651,8 +644,3 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
} }
} }
``` ```

@ -1,4 +1,5 @@
# DefaultSingletonBeanRegistry # DefaultSingletonBeanRegistry
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
- 源码路径: `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry` - 源码路径: `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry`
@ -6,12 +7,12 @@
类图 类图
![image-20200110093044672](../../../images/spring/image-20200110093044672.png) ![image-20200110093044672](../../../images/spring/image-20200110093044672.png)
## 注册方法解析 ## 注册方法解析
- 从名字可以看出这是一个单例对象的注册类 - 从名字可以看出这是一个单例对象的注册类
- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.registerSingleton` - `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.registerSingleton`
- 测试用例出发 - 测试用例出发
```java ```java
@ -46,8 +47,6 @@
``` ```
- 第一个关注的方法`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerSingleton` 注册单例对象 - 第一个关注的方法`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerSingleton` 注册单例对象
```java ```java
@ -76,6 +75,7 @@
} }
``` ```
```java ```java
/** /**
* Add the given singleton object to the singleton cache of this factory. * Add the given singleton object to the singleton cache of this factory.
@ -95,7 +95,9 @@
} }
} }
``` ```
- 这些变量是什么 - 这些变量是什么
```java ```java
/** /**
* 单例对象的缓存: beanName -> Object * 单例对象的缓存: beanName -> Object
@ -153,7 +155,9 @@
- 注册方法至此结束 - 注册方法至此结束
## 获取方法解析 ## 获取方法解析
- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(java.lang.String)` - `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(java.lang.String)`
```java ```java
@Override @Override
@Nullable @Nullable
@ -190,9 +194,10 @@
``` ```
- 获取单例对象的本质就是从map中获取 ObjectFactory 进而执行 getObject() - 获取单例对象的本质就是从 map 中获取 ObjectFactory 进而执行 getObject()
`ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);` `ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);`
- 测试方法 - 测试方法
```java ```java
TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", new ObjectFactory<Object>() { TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", new ObjectFactory<Object>() {

@ -1,10 +1,13 @@
# EntityResolver # EntityResolver
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework) - 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework)
- 源码路径: `org.xml.sax.EntityResolver`,非Spring类 - 源码路径: `org.xml.sax.EntityResolver`,非 Spring
## DelegatingEntityResolver#resolveEntity ## DelegatingEntityResolver#resolveEntity
- org.springframework.beans.factory.xml.DelegatingEntityResolver.resolveEntity - org.springframework.beans.factory.xml.DelegatingEntityResolver.resolveEntity
```java ```java
@Override @Override
@Nullable @Nullable
@ -25,16 +28,21 @@
} }
``` ```
- 上述这段代码是针对xml进行校验
- 上述这段代码是针对 xml 进行校验
```xml ```xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans" xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
``` ```
- 如上所示以`.xsd`结尾,应该执行` return this.schemaResolver.resolveEntity(publicId, systemId);`方法 - 如上所示以`.xsd`结尾,应该执行` return this.schemaResolver.resolveEntity(publicId, systemId);`方法
`http://www.springframework.org/schema/beans/spring-beans.xsd` `http://www.springframework.org/schema/beans/spring-beans.xsd`
- `org.springframework.beans.factory.xml.PluggableSchemaResolver.resolveEntity` - `org.springframework.beans.factory.xml.PluggableSchemaResolver.resolveEntity`
## PluggableSchemaResolver#resolveEntity ## PluggableSchemaResolver#resolveEntity
```java ```java
@Override @Override
@Nullable @Nullable
@ -87,7 +95,7 @@
## BeansDtdResolver#resolveEntity ## BeansDtdResolver#resolveEntity
创建一个Dtd的约束文件 创建一个 Dtd 的约束文件
```xml ```xml
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
@ -141,17 +149,13 @@
``` ```
- systemId `https://www.springframework.org/dtd/spring-beans-2.0.dtd` - systemId `https://www.springframework.org/dtd/spring-beans-2.0.dtd`
![image-20200108082335031](../../../images/spring//image-20200108082335031.png) ![image-20200108082335031](../../../images/spring//image-20200108082335031.png)
## 总结 ## 总结
- DelegatingEntityResolver#resolveEntity,是对xml文档的校验前置步骤,根据`dtd`和`xsd`加载本地资源文件 - DelegatingEntityResolver#resolveEntity,是对 xml 文档的校验前置步骤,根据`dtd`和`xsd`加载本地资源文件
`spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.dtd` `spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.dtd`
`spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.xsd` `spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.xsd`
- `PluggableSchemaResolver`负责加载`xsd`文件 - `PluggableSchemaResolver`负责加载`xsd`文件

@ -1,9 +1,12 @@
# Spring Import # Spring Import
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
## 分析 ## 分析
- org.springframework.context.annotation.Import - org.springframework.context.annotation.Import
```java ```java
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -19,9 +22,12 @@ public @interface Import {
Class<?>[] value(); Class<?>[] value();
} }
``` ```
### ImportBeanDefinitionRegistrar ### ImportBeanDefinitionRegistrar
- 注册Import Bean
- 注册 Import Bean
- `org.springframework.context.annotation.ImportBeanDefinitionRegistrar` - `org.springframework.context.annotation.ImportBeanDefinitionRegistrar`
```java ```java
public interface ImportBeanDefinitionRegistrar { public interface ImportBeanDefinitionRegistrar {
@ -40,11 +46,13 @@ public interface ImportBeanDefinitionRegistrar {
} }
``` ```
- 两个实现类 - 两个实现类
1. `org.springframework.context.annotation.AutoProxyRegistrar` 1. `org.springframework.context.annotation.AutoProxyRegistrar`
2. `org.springframework.context.annotation.AspectJAutoProxyRegistrar` 2. `org.springframework.context.annotation.AspectJAutoProxyRegistrar`
#### AutoProxyRegistrar #### AutoProxyRegistrar
```java ```java
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

@ -1,11 +1,11 @@
# Spring MessageSource # Spring MessageSource
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 初始化入口 ## 初始化入口
- `org.springframework.context.support.AbstractApplicationContext.refresh`方法有`initMessageSource()`方法进行了`MessageSource`初始化
- `org.springframework.context.support.AbstractApplicationContext.refresh`方法有`initMessageSource()`方法进行了`MessageSource`初始化
```java ```java
protected void initMessageSource() { protected void initMessageSource() {
@ -43,20 +43,10 @@
``` ```
读取 xml 配置文件
读取xml配置文件
![image-20200119141937915](../../../images/spring/image-20200119141937915.png) ![image-20200119141937915](../../../images/spring/image-20200119141937915.png)
## getMessage ## getMessage
- `org.springframework.context.support.AbstractApplicationContext#getMessage(java.lang.String, java.lang.Object[], java.util.Locale)` - `org.springframework.context.support.AbstractApplicationContext#getMessage(java.lang.String, java.lang.Object[], java.util.Locale)`
@ -105,7 +95,7 @@
``` ```
- 返回code本身或者`null` - 返回 code 本身或者`null`
2. `org.springframework.context.support.AbstractMessageSource#getMessageInternal` 2. `org.springframework.context.support.AbstractMessageSource#getMessageInternal`
@ -161,11 +151,9 @@
``` ```
- `org.springframework.context.support.ResourceBundleMessageSource#resolveCodeWithoutArguments` - `org.springframework.context.support.ResourceBundleMessageSource#resolveCodeWithoutArguments`
```JAVA ```java
@Override @Override
protected String resolveCodeWithoutArguments(String code, Locale locale) { protected String resolveCodeWithoutArguments(String code, Locale locale) {
Set<String> basenames = getBasenameSet(); Set<String> basenames = getBasenameSet();
@ -185,26 +173,14 @@
``` ```
![image-20200119143046066](../../../images/spring/image-20200119143046066.png) ![image-20200119143046066](../../../images/spring/image-20200119143046066.png)
- 加载后截图 - 加载后截图
获取方法`String result = getStringOrNull(bundle, code);`就是map获取 获取方法`String result = getStringOrNull(bundle, code);`就是 map 获取
![image-20200119144019171](../../../images/spring/image-20200119144019171.png) ![image-20200119144019171](../../../images/spring/image-20200119144019171.png)
- 没有配置文件的情况 - 没有配置文件的情况
![image-20200119145138205](../../../images/spring/image-20200119145138205.png) ![image-20200119145138205](../../../images/spring/image-20200119145138205.png)

@ -3,12 +3,8 @@
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## ClassMetadata ## ClassMetadata
```java ```java
public interface ClassMetadata { public interface ClassMetadata {
@ -89,20 +85,10 @@ public interface ClassMetadata {
} }
``` ```
![image-20200824094154847](/images/spring/image-20200824094154847.png) ![image-20200824094154847](/images/spring/image-20200824094154847.png)
## AnnotatedTypeMetadata ## AnnotatedTypeMetadata
```java ```java
public interface AnnotatedTypeMetadata { public interface AnnotatedTypeMetadata {
@ -130,9 +116,6 @@ public interface AnnotatedTypeMetadata {
} }
``` ```
## AnnotationMetadata ## AnnotationMetadata
```java ```java
@ -199,12 +182,6 @@ public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata
} }
``` ```
## MethodMetadata ## MethodMetadata
```java ```java
@ -248,12 +225,6 @@ public interface MethodMetadata extends AnnotatedTypeMetadata {
} }
``` ```
## MetadataReader ## MetadataReader
```java ```java
@ -283,10 +254,6 @@ public interface MetadataReader {
} }
``` ```
## MetadataReaderFactory ## MetadataReaderFactory
- 用来创建 MetadataReader - 用来创建 MetadataReader
@ -313,23 +280,13 @@ public interface MetadataReaderFactory {
} }
``` ```
- 接口解释的差不多了.接下来看一些实现 - 接口解释的差不多了.接下来看一些实现
## StandardClassMetadata ## StandardClassMetadata
- 通过JAVA 反射来获取类的元信息 - 通过 JAVA 反射来获取类的元信息
- 这个类采用的方式是Java class的方法,通过构造方法来填写一个Class对象. 之后使用这个对象的方法
- 这个类采用的方式是 Java class 的方法,通过构造方法来填写一个 Class 对象. 之后使用这个对象的方法
```java ```java
public class StandardClassMetadata implements ClassMetadata { public class StandardClassMetadata implements ClassMetadata {
@ -418,18 +375,12 @@ public class StandardClassMetadata implements ClassMetadata {
} }
``` ```
## StandardMethodMetadata ## StandardMethodMetadata
- 通过java反射获取方法的元信息 - 通过 java 反射获取方法的元信息
- 构造方法传递一个method - 构造方法传递一个 method
- 如果这个方法有注解,会进行注解的解析 - 如果这个方法有注解,会进行注解的解析
```java ```java
public class StandardMethodMetadata implements MethodMetadata { public class StandardMethodMetadata implements MethodMetadata {
@ -532,17 +483,13 @@ public class StandardMethodMetadata implements MethodMetadata {
} }
``` ```
## StandardAnnotationMetadata ## StandardAnnotationMetadata
- StandardAnnotationMetadata是StandardClassMetadata的子类 - StandardAnnotationMetadata 是 StandardClassMetadata 的子类
- 还是一个基于JAVA 反射做的一个类
- 还是一个基于 JAVA 反射做的一个类
- 获取注解属性 map
- 获取注解属性map
```java ```java
@Override @Override
@ -560,8 +507,6 @@ public Map<String, Object> getAnnotationAttributes(String annotationName, boolea
- `org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes` - `org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes`
- `org.springframework.core.annotation.MergedAnnotation#asAnnotationAttributes` - `org.springframework.core.annotation.MergedAnnotation#asAnnotationAttributes`
```java ```java
@Nullable @Nullable
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
@ -573,21 +518,14 @@ public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElemen
} }
``` ```
- 查看这个方法的源码借助测试类`org.springframework.core.annotation.AnnotatedElementUtilsTests#getMergedAnnotationAttributesOnClassWithLocalAnnotation` - 查看这个方法的源码借助测试类`org.springframework.core.annotation.AnnotatedElementUtilsTests#getMergedAnnotationAttributesOnClassWithLocalAnnotation`
- getAnnotations() 方法 - getAnnotations() 方法
### getAnnotations ### getAnnotations
- `org.springframework.core.annotation.MergedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers)` - `org.springframework.core.annotation.MergedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers)`
- `org.springframework.core.annotation.TypeMappedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers, org.springframework.core.annotation.AnnotationFilter)`
- `org.springframework.core.annotation.TypeMappedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers, org.springframework.core.annotation.AnnotationFilter)`
- 最终我们找到的代码如下 - 最终我们找到的代码如下
@ -602,20 +540,10 @@ static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStr
} }
``` ```
- 判断是否为空. 为空返回 None不会为空构造出一个对象 org.springframework.core.annotation.TypeMappedAnnotations
- 判断是否为空. 为空返回None不会为空构造出一个对象org.springframework.core.annotation.TypeMappedAnnotations
### MergedAnnotations ### MergedAnnotations
```java ```java
public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>> { public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>> {
@ -639,12 +567,6 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
将多个注解合并 将多个注解合并
### SearchStrategy ### SearchStrategy
```java ```java
@ -700,10 +622,6 @@ enum SearchStrategy {
} }
``` ```
- `org.springframework.core.annotation.TypeMappedAnnotations#get(java.lang.String, java.util.function.Predicate<? super org.springframework.core.annotation.MergedAnnotation<A>>, org.springframework.core.annotation.MergedAnnotationSelector<A>)` - `org.springframework.core.annotation.TypeMappedAnnotations#get(java.lang.String, java.util.function.Predicate<? super org.springframework.core.annotation.MergedAnnotation<A>>, org.springframework.core.annotation.MergedAnnotationSelector<A>)`
```java ```java
@ -721,10 +639,6 @@ public <A extends Annotation> MergedAnnotation<A> get(String annotationType,
} }
``` ```
#### Scan #### Scan
`org.springframework.core.annotation.AnnotationsScanner#scan(C, java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.AnnotationsProcessor<C,R>, java.util.function.BiPredicate<C,java.lang.Class<?>>)` `org.springframework.core.annotation.AnnotationsScanner#scan(C, java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.AnnotationsProcessor<C,R>, java.util.function.BiPredicate<C,java.lang.Class<?>>)`
@ -739,8 +653,6 @@ static <C, R> R scan(C context, AnnotatedElement source, SearchStrategy searchSt
} }
``` ```
在这个里面重点关注`PROCESS`方法 在这个里面重点关注`PROCESS`方法
```java ```java
@ -759,8 +671,6 @@ private static <C, R> R process(C context, AnnotatedElement source,
} }
``` ```
测试类 测试类
```java ```java
@ -771,8 +681,6 @@ private static <C, R> R process(C context, AnnotatedElement source,
显然这是一个类他会走`processClass`方法 显然这是一个类他会走`processClass`方法
- 根据扫描方式进行扫描 - 根据扫描方式进行扫描
```java ```java
@ -797,28 +705,16 @@ private static <C, R> R processClass(C context, Class<?> source,
} }
``` ```
- 扫描的形式就不贴出完整代码了 - 扫描的形式就不贴出完整代码了
`finish`就包装一下返回. `finish`就包装一下返回.
- 此时`org.springframework.core.annotation.AnnotatedElementUtils#getMergedAnnotationAttributes(java.lang.reflect.AnnotatedElement, java.lang.String, boolean, boolean)`这个方法走到了最后一步`org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes` - 此时`org.springframework.core.annotation.AnnotatedElementUtils#getMergedAnnotationAttributes(java.lang.reflect.AnnotatedElement, java.lang.String, boolean, boolean)`这个方法走到了最后一步`org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes`
- 最后的组装 map 方法
- 最后的组装map方法
`org.springframework.core.annotation.TypeMappedAnnotation#asMap(java.util.function.Function<org.springframework.core.annotation.MergedAnnotation<?>,T>, org.springframework.core.annotation.MergedAnnotation.Adapt...)` `org.springframework.core.annotation.TypeMappedAnnotation#asMap(java.util.function.Function<org.springframework.core.annotation.MergedAnnotation<?>,T>, org.springframework.core.annotation.MergedAnnotation.Adapt...)`
```java ```java
@Override @Override
public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) { public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) {
@ -826,8 +722,6 @@ public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) {
} }
``` ```
```java ```java
@Override @Override
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations) { public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations) {
@ -847,7 +741,7 @@ public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T>
} }
``` ```
- 获取属性列表,循环, 放入map 返回. - 获取属性列表,循环, 放入 map 返回.
map map
@ -855,8 +749,6 @@ public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T>
value: 函数对应的值 value: 函数对应的值
```java ```java
@Transactional("TxConfig") @Transactional("TxConfig")
``` ```
@ -885,16 +777,6 @@ readOnlay:false
![image-20200824104529315](/images/spring/image-20200824104529315.png) ![image-20200824104529315](/images/spring/image-20200824104529315.png)
## SimpleMetadataReader ## SimpleMetadataReader
- 构造方法传递三个参数直接使用 - 构造方法传递三个参数直接使用
@ -948,19 +830,13 @@ readOnlay:false
} }
``` ```
## SimpleMetadataReaderFactory ## SimpleMetadataReaderFactory
- 关注点为如何获取`MetadataReader` - 关注点为如何获取`MetadataReader`
1. 通过资源直接new出来 1. 通过资源直接 new 出来
2. 通过className转换成资源地址, 2. 通过 className 转换成资源地址,
3. 将资源地址转换成`Resource`对象 3. 将资源地址转换成`Resource`对象
4. new出来 4. new 出来
```java ```java
@Override @Override

@ -1,8 +1,8 @@
# Spring OrderComparator # Spring OrderComparator
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
```java ```java
private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) { private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
boolean p1 = (o1 instanceof PriorityOrdered); boolean p1 = (o1 instanceof PriorityOrdered);
@ -50,6 +50,7 @@
``` ```
- 测试用例 - 测试用例
```java ```java
@Test @Test
public void compareWithSourceProviderArray() { public void compareWithSourceProviderArray() {
@ -62,8 +63,6 @@
![image-20200116141838601](../../../images/spring/image-20200116141838601.png) ![image-20200116141838601](../../../images/spring/image-20200116141838601.png)
```java ```java
@Nullable @Nullable
protected Integer findOrder(Object obj) { protected Integer findOrder(Object obj) {
@ -93,6 +92,4 @@
![image-20200116141932486](../../../images/spring/image-20200116141932486.png) ![image-20200116141932486](../../../images/spring/image-20200116141932486.png)
最终`Integer.compare(i1, i2)`比较返回 OK ! 最终`Integer.compare(i1, i2)`比较返回 OK !

@ -1,10 +1,10 @@
# Spring OrderUtils # Spring OrderUtils
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
- `org.springframework.core.annotation.OrderUtils`主要方法如下 - `org.springframework.core.annotation.OrderUtils`主要方法如下
1. getOrder 1. getOrder
1. getPriority 1. getPriority
- 测试类`org.springframework.core.annotation.OrderUtilsTests` - 测试类`org.springframework.core.annotation.OrderUtilsTests`
```java ```java
@ -34,7 +34,6 @@
``` ```
```java ```java
@Nullable @Nullable
public static Integer getPriority(Class<?> type) { public static Integer getPriority(Class<?> type) {

@ -1,9 +1,12 @@
# Spring 定时任务 # Spring 定时任务
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
## EnableScheduling ## EnableScheduling
- 首先关注的类为启动定时任务的注解`@EnableScheduling` - 首先关注的类为启动定时任务的注解`@EnableScheduling`
```java ```java
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -15,7 +18,9 @@ public @interface EnableScheduling {
``` ```
## SchedulingConfiguration ## SchedulingConfiguration
- 注册定时任务相关信息 - 注册定时任务相关信息
```java ```java
@Configuration @Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ -36,7 +41,9 @@ public class SchedulingConfiguration {
``` ```
## ScheduledAnnotationBeanPostProcessor ## ScheduledAnnotationBeanPostProcessor
- 关注application事件,以及spring生命周期相关的接口实现
- 关注 application 事件,以及 spring 生命周期相关的接口实现
```java ```java
/** /**
* application 事件 * application 事件
@ -96,6 +103,7 @@ public class SchedulingConfiguration {
``` ```
- 处理定时任务注解 - 处理定时任务注解
```java ```java
protected void processScheduled(Scheduled scheduled, Method method, Object bean) { protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
try { try {
@ -241,18 +249,21 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
} }
``` ```
## 定时任务 ## 定时任务
- CronTask - CronTask
- cron定时任务 - cron 定时任务
- FixedDelayTask - FixedDelayTask
- 间隔时间的定时任务 - 间隔时间的定时任务
- FixedRateTask - FixedRateTask
- 调用频率的定时任务 - 调用频率的定时任务
- ScheduledTask - ScheduledTask
- 定时任务对象 - 定时任务对象
### cron 表达式解析 ### cron 表达式解析
- `org.springframework.scheduling.support.CronSequenceGenerator.doParse` - `org.springframework.scheduling.support.CronSequenceGenerator.doParse`
```java ```java
private void doParse(String[] fields) { private void doParse(String[] fields) {
setNumberHits(this.seconds, fields[0], 0, 60); setNumberHits(this.seconds, fields[0], 0, 60);
@ -272,8 +283,10 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
``` ```
### 执行定时任务 ### 执行定时任务
- 这里以 CronTask 任务进行分析,其他定时任务同理 - 这里以 CronTask 任务进行分析,其他定时任务同理
- `org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleCronTask` - `org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleCronTask`
```java ```java
@Nullable @Nullable
public ScheduledTask scheduleCronTask(CronTask task) { public ScheduledTask scheduleCronTask(CronTask task) {

@ -1,9 +1,12 @@
# Spring-SimpleAliasRegistry # Spring-SimpleAliasRegistry
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework) - 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework)
## AliasRegistry ## AliasRegistry
- `SimpleAliasRegistry`继承`org.springframework.core.AliasRegistry` - `SimpleAliasRegistry`继承`org.springframework.core.AliasRegistry`
```java ```java
public interface AliasRegistry { public interface AliasRegistry {
@ -50,7 +53,9 @@ public interface AliasRegistry {
} }
``` ```
## SimpleAliasRegistry ## SimpleAliasRegistry
```java ```java
/** /**
* Simple implementation of the {@link AliasRegistry} interface. * Simple implementation of the {@link AliasRegistry} interface.

@ -1,12 +1,11 @@
# SpringFactoriesLoader # SpringFactoriesLoader
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read)
- 全路径 : `org.springframework.core.io.support.SpringFactoriesLoader` - 全路径 : `org.springframework.core.io.support.SpringFactoriesLoader`
- 测试类 : `org.springframework.core.io.support.SpringFactoriesLoaderTests` - 测试类 : `org.springframework.core.io.support.SpringFactoriesLoaderTests`
## loadFactories ## loadFactories
- **加载并实例化工厂** - **加载并实例化工厂**
@ -34,10 +33,6 @@ public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoa
} }
``` ```
## loadSpringFactories ## loadSpringFactories
- 获取接口的实现类名 - 获取接口的实现类名
@ -98,8 +93,6 @@ public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoa
``` ```
- `Enumeration<URL> urls ` 变量存放的是 扫描到的`META-INF/spring.factories` 路径 - `Enumeration<URL> urls ` 变量存放的是 扫描到的`META-INF/spring.factories` 路径
- while 代码简单描述 - while 代码简单描述
@ -109,8 +102,6 @@ public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoa
4. 放入返回结果 4. 放入返回结果
5. 放入缓存 5. 放入缓存
## instantiateFactory ## instantiateFactory
```java ```java
@ -134,6 +125,3 @@ private static <T> T instantiateFactory(String factoryImplementationName, Class<
``` ```
- 反射创建 - 反射创建

@ -1,9 +1,12 @@
# Spring StopWatch # Spring StopWatch
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 全路径: `org.springframework.util.StopWatch` - 全路径: `org.springframework.util.StopWatch`
## 属性 ## 属性
- taskList: 任务信息列表 - taskList: 任务信息列表
- keepTaskList: 是否保留任务信息列表 - keepTaskList: 是否保留任务信息列表
- startTimeMillis: 任务开始的时间 - startTimeMillis: 任务开始的时间
@ -13,7 +16,9 @@
- totalTimeMillis: 总共花费的时间 - totalTimeMillis: 总共花费的时间
## 方法 ## 方法
- `org.springframework.util.StopWatch.start(java.lang.String)` - `org.springframework.util.StopWatch.start(java.lang.String)`
```java ```java
public void start(String taskName) throws IllegalStateException { public void start(String taskName) throws IllegalStateException {
if (this.currentTaskName != null) { if (this.currentTaskName != null) {
@ -23,7 +28,9 @@
this.startTimeMillis = System.currentTimeMillis(); this.startTimeMillis = System.currentTimeMillis();
} }
``` ```
- `org.springframework.util.StopWatch.stop` - `org.springframework.util.StopWatch.stop`
```java ```java
public void stop() throws IllegalStateException { public void stop() throws IllegalStateException {
if (this.currentTaskName == null) { if (this.currentTaskName == null) {

File diff suppressed because it is too large Load Diff

@ -1,15 +1,19 @@
# Spring scan # Spring scan
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 解析 ## 解析
- Spring 注解形式使用有下面两种方式 - Spring 注解形式使用有下面两种方式
1. 通过`AnnotationConfigApplicationContext`参数:扫描包 1. 通过`AnnotationConfigApplicationContext`参数:扫描包
2. 通过xml配置`context:component-scan`属性`base-package` 2. 通过 xml 配置`context:component-scan`属性`base-package`
```java ```java
AnnotationConfigApplicationContext aac = AnnotationConfigApplicationContext aac =
new AnnotationConfigApplicationContext("com.huifer.source.spring.ann"); new AnnotationConfigApplicationContext("com.huifer.source.spring.ann");
``` ```
```xml ```xml
<context:component-scan base-package="com.huifer.source.spring.ann"> <context:component-scan base-package="com.huifer.source.spring.ann">
</context:component-scan> </context:component-scan>
@ -17,6 +21,7 @@
- 目标明确开始找入口方法 - 目标明确开始找入口方法
- `AnnotationConfigApplicationContext`直接点进去看就找到了 - `AnnotationConfigApplicationContext`直接点进去看就找到了
```java ```java
public AnnotationConfigApplicationContext(String... basePackages) { public AnnotationConfigApplicationContext(String... basePackages) {
this(); this();
@ -25,7 +30,9 @@ public AnnotationConfigApplicationContext(String... basePackages) {
refresh(); refresh();
} }
``` ```
- `context:component-scan`寻找方式:冒号`:`钱+NamespaceHandler 或者全文搜索`component-scan`,最终找到`org.springframework.context.config.ContextNamespaceHandler` - `context:component-scan`寻找方式:冒号`:`钱+NamespaceHandler 或者全文搜索`component-scan`,最终找到`org.springframework.context.config.ContextNamespaceHandler`
```java ```java
public class ContextNamespaceHandler extends NamespaceHandlerSupport { public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@ -49,6 +56,7 @@ public class ContextNamespaceHandler extends NamespaceHandlerSupport {
![image-20200115093602651](../../../images/spring/image-20200115093602651.png) ![image-20200115093602651](../../../images/spring/image-20200115093602651.png)
- 实现`BeanDefinitionParser`直接看`parse`方法 - 实现`BeanDefinitionParser`直接看`parse`方法
```java ```java
@Override @Override
@Nullable @Nullable
@ -75,7 +83,9 @@ public class ContextNamespaceHandler extends NamespaceHandlerSupport {
``` ```
- 回过头看`AnnotationConfigApplicationContext` - 回过头看`AnnotationConfigApplicationContext`
### org.springframework.context.annotation.AnnotationConfigApplicationContext ### org.springframework.context.annotation.AnnotationConfigApplicationContext
```java ```java
public AnnotationConfigApplicationContext(String... basePackages) { public AnnotationConfigApplicationContext(String... basePackages) {
this(); this();
@ -84,6 +94,7 @@ public AnnotationConfigApplicationContext(String... basePackages) {
refresh(); refresh();
} }
``` ```
```java ```java
private final ClassPathBeanDefinitionScanner scanner; private final ClassPathBeanDefinitionScanner scanner;
@ -94,7 +105,9 @@ public AnnotationConfigApplicationContext(String... basePackages) {
} }
``` ```
- `org.springframework.context.annotation.ClassPathBeanDefinitionScanner.scan` - `org.springframework.context.annotation.ClassPathBeanDefinitionScanner.scan`
```java ```java
public int scan(String... basePackages) { public int scan(String... basePackages) {
@ -112,12 +125,10 @@ public int scan(String... basePackages) {
} }
``` ```
- 这个地方`doScan`似曾相识,他就是`org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse`中的`doScan`,下一步解析doScan - 这个地方`doScan`似曾相识,他就是`org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse`中的`doScan`,下一步解析 doScan
### org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan ### org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan
```java ```java
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified"); Assert.notEmpty(basePackages, "At least one base package must be specified");
@ -159,8 +170,6 @@ public int scan(String... basePackages) {
``` ```
#### org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents #### org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
```java ```java
@ -176,8 +185,6 @@ public int scan(String... basePackages) {
``` ```
```java ```java
/** /**
* 扫描当前包路径下的资源 * 扫描当前包路径下的资源
@ -247,12 +254,8 @@ public int scan(String... basePackages) {
``` ```
#### org.springframework.context.annotation.ScopeMetadataResolver#resolveScopeMetadata #### org.springframework.context.annotation.ScopeMetadataResolver#resolveScopeMetadata
```java ```java
/** /**
* 生命周期设置 * 生命周期设置
@ -303,9 +306,7 @@ public int scan(String... basePackages) {
#### org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName #### org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName
- 创建beanName `org.springframework.context.annotation.AnnotationBeanNameGenerator#generateBeanName` - 创建 beanName `org.springframework.context.annotation.AnnotationBeanNameGenerator#generateBeanName`
```java ```java
@Override @Override
@ -362,17 +363,11 @@ public class DemoService {
} }
``` ```
![image-20200115143315633](../../../images/spring/image-20200115143315633.png) ![image-20200115143315633](../../../images/spring/image-20200115143315633.png)
- `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)` - `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)`
- `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition)` - `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition)`
```JAVA ```JAVA
protected String buildDefaultBeanName(BeanDefinition definition) { protected String buildDefaultBeanName(BeanDefinition definition) {
// 获取bean class name // 获取bean class name
@ -400,13 +395,9 @@ public class BeanConfig {
![image-20200115143456554](../../../images/spring/image-20200115143456554.png) ![image-20200115143456554](../../../images/spring/image-20200115143456554.png)
#### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition #### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition
- 这个方法没什么难点直接是set方法 - 这个方法没什么难点,直接是 set 方法
```java ```java
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) { protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
@ -472,16 +463,10 @@ static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, Anno
} }
``` ```
- 方法思路: - 方法思路:
1. 获取注解的属性值 1. 获取注解的属性值
2. 设置注解属性 2. 设置注解属性
#### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate #### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate
- 重复检查 - 重复检查
@ -509,14 +494,8 @@ static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, Anno
``` ```
#### org.springframework.context.annotation.AnnotationConfigUtils#applyScopedProxyMode #### org.springframework.context.annotation.AnnotationConfigUtils#applyScopedProxyMode
```JAVA ```JAVA
static BeanDefinitionHolder applyScopedProxyMode( static BeanDefinitionHolder applyScopedProxyMode(
ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
@ -531,4 +510,3 @@ static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, Anno
} }
``` ```

@ -1,9 +1,11 @@
# Spring EnableJms 注解 # Spring EnableJms 注解
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 源码路径: `org.springframework.jms.annotation.EnableJms` - 源码路径: `org.springframework.jms.annotation.EnableJms`
## 源码分析 ## 源码分析
```java ```java
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@ -15,7 +17,6 @@ public @interface EnableJms {
- 该类的切入点在`@Import(JmsBootstrapConfiguration.class)` , 直接看`JmsBootstrapConfiguration`就可以了 - 该类的切入点在`@Import(JmsBootstrapConfiguration.class)` , 直接看`JmsBootstrapConfiguration`就可以了
```java ```java
@Configuration @Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ -50,14 +51,14 @@ public class JmsBootstrapConfiguration {
![image-20200304085303580](../../../images/springmessage/image-20200304085303580.png) ![image-20200304085303580](../../../images/springmessage/image-20200304085303580.png)
- 主要关注 - 主要关注
1. **afterSingletonsInstantiated** 1. **afterSingletonsInstantiated**
2. **postProcessAfterInitialization** 2. **postProcessAfterInitialization**
#### afterSingletonsInstantiated #### afterSingletonsInstantiated
```JAVA ```JAVA
@Override @Override
public void afterSingletonsInstantiated() { public void afterSingletonsInstantiated() {
@ -128,10 +129,6 @@ public class JmsBootstrapConfiguration {
- 注册监听在下面分析会讲详见下文 - 注册监听在下面分析会讲详见下文
#### postProcessAfterInitialization #### postProcessAfterInitialization
```JAVA ```JAVA
@ -172,8 +169,6 @@ public class JmsBootstrapConfiguration {
``` ```
```JAVA ```JAVA
protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) { protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) {
Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass()); Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass());
@ -243,8 +238,6 @@ public class JmsBootstrapConfiguration {
``` ```
- `org.springframework.jms.config.JmsListenerEndpointRegistry#registerListenerContainer(org.springframework.jms.config.JmsListenerEndpoint, org.springframework.jms.config.JmsListenerContainerFactory<?>, boolean)` - `org.springframework.jms.config.JmsListenerEndpointRegistry#registerListenerContainer(org.springframework.jms.config.JmsListenerEndpoint, org.springframework.jms.config.JmsListenerContainerFactory<?>, boolean)`
```java ```java
@ -271,8 +264,6 @@ public class JmsBootstrapConfiguration {
} }
``` ```
- `org.springframework.jms.config.JmsListenerEndpointRegistry#createListenerContainer` - `org.springframework.jms.config.JmsListenerEndpointRegistry#createListenerContainer`
```java ```java
@ -309,10 +300,6 @@ public class JmsBootstrapConfiguration {
``` ```
- 关键接口`JmsListenerContainerFactory<C extends MessageListenerContainer>` - 关键接口`JmsListenerContainerFactory<C extends MessageListenerContainer>`
```JAVA ```JAVA
@ -331,8 +318,6 @@ public class JmsBootstrapConfiguration {
![image-20200304092154712](../../../images/springmessage/image-20200304092154712.png) ![image-20200304092154712](../../../images/springmessage/image-20200304092154712.png)
- 注册完成后是否立即启动 - 注册完成后是否立即启动
```java ```java
@ -354,12 +339,6 @@ public class JmsBootstrapConfiguration {
- 执行完`start`方法就结束了`processJmsListener`的调用链路, `postProcessAfterInitialization` 也结束了 - 执行完`start`方法就结束了`processJmsListener`的调用链路, `postProcessAfterInitialization` 也结束了
### JmsListenerEndpointRegistry ### JmsListenerEndpointRegistry
- 这个类辅助**JmsListenerAnnotationBeanPostProcessor** 处理 - 这个类辅助**JmsListenerAnnotationBeanPostProcessor** 处理
@ -408,4 +387,3 @@ public class JmsBootstrapConfiguration {
} }
``` ```

@ -1,10 +1,11 @@
# Spring JmsTemplate # Spring JmsTemplate
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 源码路径: `org.springframework.jms.core.JmsTemplate` - 源码路径: `org.springframework.jms.core.JmsTemplate`
## 源码分析 ## 源码分析
### send 发送消息 ### send 发送消息
```java ```java
@ -59,6 +60,7 @@
``` ```
- 最后`action.doInJms(sessionToUse)`的操作 - 最后`action.doInJms(sessionToUse)`的操作
```java ```java
Destination destination = resolveDestinationName(session, destinationName); Destination destination = resolveDestinationName(session, destinationName);
doSend(session, destination, messageCreator); doSend(session, destination, messageCreator);
@ -66,6 +68,7 @@
``` ```
- `doSend`真正做的发送方法 - `doSend`真正做的发送方法
```java ```java
protected void doSend(Session session, Destination destination, MessageCreator messageCreator) protected void doSend(Session session, Destination destination, MessageCreator messageCreator)
throws JMSException { throws JMSException {
@ -94,8 +97,10 @@
} }
``` ```
1. `createProducer`中通过`javax.jms.Session.createProducer`创建`MessageProducer`,第三方消息中间件独立实现 1. `createProducer`中通过`javax.jms.Session.createProducer`创建`MessageProducer`,第三方消息中间件独立实现
2. `createMessage` 2. `createMessage`
```java ```java
@Override @Override
public javax.jms.Message createMessage(Session session) throws JMSException { public javax.jms.Message createMessage(Session session) throws JMSException {
@ -107,8 +112,11 @@ public javax.jms.Message createMessage(Session session) throws JMSException {
} }
} }
``` ```
- 消息转换后续在更新 - 消息转换后续在更新
3. `doSend` 这里也是第三方消息中间件实现 3. `doSend` 这里也是第三方消息中间件实现
```java ```java
protected void doSend(MessageProducer producer, Message message) throws JMSException { protected void doSend(MessageProducer producer, Message message) throws JMSException {
if (this.deliveryDelay >= 0) { if (this.deliveryDelay >= 0) {
@ -122,7 +130,9 @@ protected void doSend(MessageProducer producer, Message message) throws JMSExcep
} }
} }
``` ```
4. `closeMessageProducer` 这个方法特别,直接关闭 4. `closeMessageProducer` 这个方法特别,直接关闭
```java ```java
public static void closeMessageProducer(@Nullable MessageProducer producer) { public static void closeMessageProducer(@Nullable MessageProducer producer) {
if (producer != null) { if (producer != null) {
@ -140,6 +150,7 @@ public static void closeMessageProducer(@Nullable MessageProducer producer) {
``` ```
### receive 接收消息 ### receive 接收消息
```java ```java
@Override @Override
@Nullable @Nullable

@ -1,36 +1,35 @@
# Spring MessageConverter # Spring MessageConverter
- Author: [HuiFer](https://github.com/huifer) - Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 源码路径: `org.springframework.messaging.converter.MessageConverter` - 源码路径: `org.springframework.messaging.converter.MessageConverter`
## MessageConverter ## MessageConverter
- 消息转换接口 - 消息转换接口
- 类图如下 - 类图如下
![image-20200305085013723](../../../images/springmessage/image-20200305085013723.png) ![image-20200305085013723](../../../images/springmessage/image-20200305085013723.png)
- 两个方法 - 两个方法
1. fromMessage: 从消息转换到Object
1. fromMessage: 从消息转换到 Object
```java ```java
Object fromMessage(Message<?> message, Class<?> targetClass); Object fromMessage(Message<?> message, Class<?> targetClass);
``` ```
2. toMessage: 从Object转换到消息
2. toMessage: 从 Object 转换到消息
```java ```java
Message<?> toMessage(Object payload, @Nullable MessageHeaders headers); Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);
``` ```
| 序号 | class | 作用 | | 序号 | class | 作用 |
| ---- | ------------------------------- | -------------------- | | ---- | ------------------------------- | --------------------- |
| 1 | ByteArrayMessageConverter | byte数组消息转换器 | | 1 | ByteArrayMessageConverter | byte 数组消息转换器 |
| 2 | MappingJackson2MessageConverter | jackson2的消息转换器 | | 2 | MappingJackson2MessageConverter | jackson2 的消息转换器 |
| 3 | MarshallingMessageConverter | xml的消息转换器 | | 3 | MarshallingMessageConverter | xml 的消息转换器 |
| 4 | StringMessageConverter | 字符串消息转换器 | | 4 | StringMessageConverter | 字符串消息转换器 |
## AbstractMessageConverter ## AbstractMessageConverter
类图: 类图:
@ -78,8 +77,6 @@
``` ```
### toMessage ### toMessage
```JAVA ```JAVA
@ -91,8 +88,6 @@
``` ```
```JAVA ```JAVA
@Override @Override
@Nullable @Nullable
@ -150,8 +145,6 @@
``` ```
- 创建**Message**对象 - 创建**Message**对象
```JAVA ```JAVA
@ -169,8 +162,6 @@
``` ```
```JAVA ```JAVA
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Message<T> build() { public Message<T> build() {
@ -188,8 +179,6 @@
``` ```
- 两种创建方式基本相同,如果出现异常组装异常消息对象`ErrorMessage`,成功创建`GenericMessage` - 两种创建方式基本相同,如果出现异常组装异常消息对象`ErrorMessage`,成功创建`GenericMessage`
![image-20200305090846313](../../../images/springmessage/image-20200305090846313.png) ![image-20200305090846313](../../../images/springmessage/image-20200305090846313.png)

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save