图片在Typora里面无法查看,修改图片的相对路径
This commit is contained in:
parent
141ecfd770
commit
068196e57d
@ -11,7 +11,7 @@ dubbo自己实现了一套SPI机制,并对 JDK的SPI进行了改进。
|
||||
|
||||
下面我们看一下Dubbo 的 SPI扩展机制实现的结构目录。
|
||||
|
||||

|
||||

|
||||
|
||||
### SPI 注解
|
||||
首先看一下 SPI注解。在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。比如,协议扩展接口Protocol,如果使用者在 <dubbo:protocol />、<dubbo:service />、<dubbo:reference /> 都没有指定 protocol属性 的话,那么就默认使用 DubboProtocol 作为接口Protocol的实现,因为在 Protocol 上有 @SPI("dubbo")注解。而这个 protocol属性值 或者默认值会被当作该接口的实现类中的一个key,dubbo 会去 META-INF.dubbo.internal下的com.alibaba.dubbo.rpc.Protocol文件中找该key对应的value,源码如下。
|
||||
|
@ -1,7 +1,7 @@
|
||||
## 项目结构
|
||||
首先从GitHub 上 down下来Dubbo项目,我们根据里面的目录名 也能大概猜出来各个模块的作用。
|
||||
|
||||

|
||||

|
||||
|
||||
### dubbo-common
|
||||
公共逻辑模块,定义了各模块中 通用的 组件 和 工具类,如:IO、日志、配置处理等。
|
||||
@ -34,7 +34,7 @@ dubbo配置模块,该模块通过 配置信息,将dubbo组件的各个模块
|
||||
|
||||
其运行原理如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
### 工作原理
|
||||
最后总结下其工作原理。
|
||||
|
@ -13,12 +13,12 @@
|
||||
|
||||
下面我们来看一下 集群模块的项目结构图,结合上文的描述,可以对其有更加深刻的理解。
|
||||
|
||||

|
||||

|
||||
|
||||
### 集群模块核心API 源码解析
|
||||
从上图应该也能看出其核心API在哪个包里。
|
||||
|
||||

|
||||

|
||||
|
||||
各核心接口的源码如下。
|
||||
```java
|
||||
|
@ -1,21 +1,21 @@
|
||||
## 注册中心在Dubbo中的作用
|
||||
服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者Provider 会往注册中心注册服务,而消费者Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer以及Registry之间的依赖关系 如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
## dubbo-registry 模块 结构分析
|
||||
dubbo的注册中心有多种实现方案,如:zookeeper、redis、multicast等,本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api,具体实现部分放到下章来讲。dubbo-registry模块 的结构如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
### Registry 核心组件类图
|
||||
典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
既然有Registry组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry实例的RegistryFactory,其中用到了工厂方法模式,不同的工厂类用于获取不同类型的实例。其类图结构如下。
|
||||
|
||||

|
||||

|
||||
|
||||
## 源码详解
|
||||
根据上面的类图,我们开始从上往下 详解dubbo中对于注册中心的设计以及实现。
|
||||
|
@ -2,7 +2,7 @@ Dubbo的注册中心 虽然提供了多种实现,但生产上的事实标准
|
||||
|
||||
由于 Dubbo 是一个分布式RPC开源框架,各服务之间单独部署,往往会出现资源之间数据不一致的问题,比如:某一个服务增加或减少了几台机器,某个服务提供者变更了服务地址,那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo服务是如何被 Zookeeper的数据结构存储管理的呢,zookeeper采用的是树形结构来组织数据节点,它类似于一个标准的文件系统,如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
该图展示了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不同。
|
||||
|
||||
@ -10,7 +10,7 @@ zookeeper以斜杠来分割每一层的znode节点,比如第一层根节点dub
|
||||
|
||||
dubbo-registry-zookeeper 模块的工程结构如下图所示,里面就俩类,非常简单。
|
||||
|
||||

|
||||

|
||||
|
||||
### ZookeeperRegistry
|
||||
该类继承了FailbackRegistry抽象类,针对注册中心核心的 服务注册、服务订阅、取消注册、取消订阅,查询注册列表进行展开,这里用到了 模板方法设计模式,FailbackRegistry中定义了register()、subscribe()等模板方法和 doRegister()、doSubscribe()抽象方法,ZookeeperRegistry基于zookeeper对这些抽象方法进行了实现。其实你会发现zookeeper虽然是最被推荐的,反而它的实现逻辑相对简单,因为调用了zookeeper服务组件,很多的逻辑不需要在dubbo中自己去实现。
|
||||
|
@ -1,7 +1,7 @@
|
||||
## 线程池核心组件图解
|
||||
看源码之前,先了解一下该组件 最主要的几个 接口、抽象类和实现类的结构关系。
|
||||
|
||||

|
||||

|
||||
|
||||
该组件中,Executor 和 ExecutorService接口 定义了线程池最核心的几个方法,提交任务submit
|
||||
()、关闭线程池shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。
|
||||
@ -211,7 +211,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
|
||||
```
|
||||
ThreadPoolExecutor 中的 execute()方法 执行 Runnable任务 的流程逻辑可以用下图表示。
|
||||
|
||||

|
||||

|
||||
|
||||
### 工具类 Executors
|
||||
看类名也知道,它最主要的作用就是提供 static 的工具方法,为开发者提供各种封装好的 具有各自特性的线程池。
|
||||
|
@ -1,11 +1,11 @@
|
||||
## 类图结构
|
||||
J.U.C 的锁组件中 类相对较少,从JDK相应的包中也能看出来,下图标记了其中最主要的几个接口和类,也是本文要分析的重点。
|
||||
|
||||

|
||||

|
||||
|
||||
下图 将这几个接口和类 以类图的方式展现出来,其中包含了它们所声明的主要方法。
|
||||
|
||||

|
||||

|
||||
|
||||
## Lock 组件
|
||||
Lock 组件的结构很简单,只有一个接口和一个实现类,源码如下。
|
||||
|
@ -317,4 +317,4 @@ public class Thread implements Runnable {
|
||||
```
|
||||
之前一直对线程状态 及 状态切换的概念模糊不清,现在通过源码中对线程状态的定义,我们可以画张图来重新回顾一下,以使我们对其有更加深刻的理解。
|
||||
|
||||

|
||||

|
@ -256,7 +256,7 @@ public class ThreadLocal<T> {
|
||||
```
|
||||
简单画个图总结一下 ThreadLocal 的原理,如下。
|
||||
|
||||

|
||||

|
||||
|
||||
最后强调一下 ThreadLocal的使用注意事项:
|
||||
|
||||
|
@ -28,6 +28,7 @@ public class Singleton3 {
|
||||
* 使用volatile修饰instance变量 可以 避免上述的指令重排
|
||||
* tips:不太理解的是 第一个线程在执行第2步之前就已经释放了锁吗?导致其它线程进入synchronized代码块
|
||||
* 执行 instance == null 的判断?
|
||||
* 回答:第一个线程在执行第2步之前就已经释放了锁吗?(没有)。如果不使用volatile修饰instance变量,那么其他线程进来的时候,看到的instance就有可能不是null的,因为已经执行了第3步,那么此时这个线程(执行 return instance;)使用的instance是一个没有初始化的instance,就会有问题。
|
||||
*/
|
||||
private volatile static Singleton3 instance;
|
||||
|
||||
@ -485,7 +486,7 @@ public class SmartTransformerFactoryImpl extends SAXTransformerFactory {
|
||||
### 个人理解
|
||||
该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。
|
||||
|
||||

|
||||

|
||||
|
||||
该模式的主要角色如下:
|
||||
|
||||
|
@ -812,7 +812,7 @@ class PooledConnection implements InvocationHandler {
|
||||
|
||||
装饰器模式能够帮助我们解决上述问题,装饰器可以动态地为对象添加功能,它是基于组合的方式实现该功能的。在实践中,我们应该尽量使用组合的方式来扩展系统的功能,而非使用继承的方式。通过装饰器模式的介绍,可以帮助读者更好地理解设计模式中常见的一句话:组合优于继承。下面先来看一下装饰器模式的类图,及其核心角色。
|
||||
|
||||

|
||||

|
||||
|
||||
- Component (组件):组件接口定义了全部 “组件实现类” 以及所有 “装饰器实现” 的行为。
|
||||
- ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。
|
||||
|
@ -7,7 +7,7 @@
|
||||
#### 个人理解
|
||||
去年看了蛮多源码,发现 框架的开发者在实际使用设计模式时,大都会根据实际情况 使用其变体,老老实实按照书上的类图及定义去设计代码的比较少。不过我们依然还是先看一下书上的定义,然后比较一下理论与实践的一些差别吧。策略模式的类图及定义如下。
|
||||
|
||||

|
||||

|
||||
|
||||
定义一系列算法,封装每个算法 并使它们可以互换。该模式的主要角色如下:
|
||||
|
||||
@ -948,7 +948,7 @@ public class ArrayList<E> extends AbstractList<E>
|
||||
#### 个人理解
|
||||
这个模式也是平时很少使用的,所以就简单介绍一下,然后结合JDK中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下:
|
||||
|
||||

|
||||

|
||||
|
||||
- Subject主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的;
|
||||
- Observer观察者:其注册需要Subject的registerObserver()方法。
|
||||
@ -1080,11 +1080,11 @@ public class Observable {
|
||||
|
||||
在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 Handler 处理类中,这样我们就可以根据业务需求,将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler对象 都包含对下一个 Handler对象 的引用,一个 Handler对象 处理完请求消息(或不能处理该请求)时, 会把请求传给下一个 Handler对象 继续处理,依此类推,直至整条责任链结束。简单看一下责任链模式的类图。
|
||||
|
||||

|
||||

|
||||
|
||||
#### Netty 中的应用
|
||||
在 Netty 中,将 Channel 的数据管道抽象为 ChannelPipeline,消息在 ChannelPipeline 中流动和传递。ChannelPipeline 是 ChannelHandler 的容器,持有 I/O事件拦截器 ChannelHandler 的链表,负责对 ChannelHandler 的管理和调度。由 ChannelHandler 对 I/O事件 进行拦截和处理,并可以通过接口方便地新增和删除 ChannelHandler 来实现不同业务逻辑的处理。下图是 ChannelPipeline源码中描绘的责任链事件处理过程。
|
||||

|
||||

|
||||
其具体过程处理如下:
|
||||
|
||||
1. 底层SocketChannel 的 read方法 读取 ByteBuf,触发 ChannelRead事件,由 I/O线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead()方法,将消息传输到 ChannelPipeline中。
|
||||
|
@ -339,7 +339,7 @@ public class PoolState {
|
||||
PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource对象 创建的,并由 PoolState 管理所有连接的状态。
|
||||
PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图:
|
||||
|
||||

|
||||

|
||||
|
||||
```java
|
||||
public class PooledDataSource implements DataSource {
|
||||
|
@ -47,7 +47,7 @@ public interface Cache {
|
||||
```
|
||||
如下图所示,Cache接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。
|
||||
|
||||

|
||||

|
||||
|
||||
### 1.1 PerpetualCache
|
||||
PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap对象 的方法实现的 Cache接口 中定义的相应方法。
|
||||
|
@ -143,13 +143,13 @@ class HfReflectorTest {
|
||||
|
||||
- 准备工作完成了开始进行 debug , 在`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`这个方法上打上断点
|
||||
|
||||

|
||||

|
||||
|
||||
观察`constructors`属性存在两个方法,这两个方法就是我在`People`类中的构造方法.
|
||||
|
||||
根据语法内容我们应该对`parameterTypes`属性进行查看
|
||||
|
||||

|
||||

|
||||
|
||||
可以发现空参构造的`parameterTypes`长度是0.因此可以确认`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`方法获取了空参构造
|
||||
|
||||
@ -287,17 +287,17 @@ class HfReflectorTest {
|
||||
|
||||
- 照旧我们进行 debug 当前方法为`toString`方法
|
||||
|
||||

|
||||

|
||||
|
||||
从返回结果可以看到`sb.toString`返回的是: `返回值类型#方法名`
|
||||
|
||||

|
||||

|
||||
|
||||
上图返回结果为`void#setName:java.lang.String` 命名规则:`返回值类型#方法名称:参数列表`
|
||||
|
||||
回过头看看`uniqueMethods`里面是什么
|
||||
|
||||

|
||||

|
||||
|
||||
方法签名:方法
|
||||
|
||||
@ -325,11 +325,11 @@ class HfReflectorTest {
|
||||
|
||||
目标明确了就直接在
|
||||
|
||||

|
||||

|
||||
|
||||
这里打断点了
|
||||
|
||||

|
||||

|
||||
|
||||
在进入循环之前回率先加载本类的所有可见方法
|
||||
|
||||
@ -342,15 +342,15 @@ class HfReflectorTest {
|
||||
|
||||
接下来断点继续往下走
|
||||
|
||||

|
||||

|
||||
|
||||
走到这一步我们来看看`currentClass.getSuperclass()`是不是上一级的类
|
||||
|
||||

|
||||

|
||||
|
||||
通过断点可见这个`currentClass`现在是`People`类,根据之前所说的最终`uniqueMethods`应该存在父类的方法
|
||||
|
||||

|
||||

|
||||
|
||||
可以看到父类的方法也都存在了
|
||||
|
||||
@ -431,4 +431,4 @@ class HfReflectorTest {
|
||||
|
||||
- 下图为一个类的解析结果
|
||||
|
||||

|
||||

|
@ -81,7 +81,7 @@ public interface SqlSession extends Closeable {
|
||||
### 1.1 DefaultSqlSession
|
||||
DefaultSqlSession是单独使用MyBatis进行开发时,最常用的SqISession接口实现。其实现了SqISession接口中定义的方法,及各方法的重载。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之间的调用关系如下图,殊途同归,它们最终都会调用Executor的query()方法。
|
||||
|
||||

|
||||

|
||||
|
||||
上述重载方法最终都是通过调用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()方法完成数据库修改操作。
|
||||
|
@ -29,7 +29,7 @@ public interface DataSourceFactory {
|
||||
|
||||
类图如下
|
||||
|
||||

|
||||

|
||||
|
||||
- `setProperties`会将下列标签放入`datasource`中
|
||||
|
||||
@ -352,9 +352,9 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
|
||||
|
||||
从类图上或者代码中我们可以发现`PooledDataSourceFactory`是继承`UnpooledDataSourceFactory`那么方法应该也是`UnpooledDataSourceFactory`的。看看设置属性方法
|
||||
|
||||

|
||||

|
||||
|
||||
方法直接走完
|
||||
|
||||

|
||||

|
||||
|
||||
|
@ -187,7 +187,7 @@
|
||||
HsSell[] list(@Param("ID") Integer id);
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
- 修改mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试
|
||||
|
||||
@ -197,7 +197,7 @@
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
类图:
|
||||
|
||||

|
||||

|
||||
```java
|
||||
public interface ObjectWrapper {
|
||||
|
||||
|
@ -176,9 +176,9 @@ public class ParamNameResolver {
|
||||
|
||||
- 写`@Param`返回
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -188,7 +188,7 @@ public class ParamNameResolver {
|
||||
List<HsSell> list( Integer id);
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -199,6 +199,6 @@ public class ParamNameResolver {
|
||||
|
||||
写上`@Param`
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
@ -93,11 +93,11 @@
|
||||
```
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -173,4 +173,4 @@ public class GenericTokenParser {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
@ -4,7 +4,7 @@
|
||||
## Netty的三层架构设计
|
||||
Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。
|
||||
|
||||

|
||||

|
||||
|
||||
### 通信调度层 Reactor
|
||||
它由一系列辅助类完成,包括 Reactor线程 NioEventLoop 及其父类,NioSocketChannel / NioServerSocketChannel 及其父类,Buffer组件,Unsafe组件 等。该层的主要职责就是**监听网络的读写和连接操作**,负责**将网络层的数据读取到内存缓冲区**,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 PipeLine 中,由 PipeLine 管理的责任链来进行后续的处理。
|
||||
|
@ -98,7 +98,7 @@ Netty 主从多线程模型 代码示例如下。
|
||||
|
||||
为了尽可能提升性能,Netty 对消息的处理 采用了串行无锁化设计,在 I/O线程 内部进行串行操作,避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。
|
||||
|
||||
|
@ -4,7 +4,7 @@ Linux 的内核将所有外部设备都看做一个文件来操作,对一个
|
||||
#### 1、阻塞IO模型
|
||||
在内核将数据准备好之前,系统调用会一直等待所有的套接字(Socket)传来数据,默认的是阻塞方式。
|
||||
|
||||

|
||||

|
||||
|
||||
Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom方法,OS 会判断来自网络的数据报是否准备好,当数据报准备好了之后,OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。
|
||||
|
||||
@ -15,7 +15,7 @@ BIO中的阻塞,就是阻塞在2个地方:
|
||||
在这2个时候,我们的线程会一直被阻塞,啥事情都不干。
|
||||
#### 2、非阻塞IO模型
|
||||
|
||||

|
||||

|
||||
|
||||
每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于CPU来说是较大的浪费,一般只有在特定的场景下才使用。
|
||||
|
||||
@ -25,22 +25,22 @@ BIO中的阻塞,就是阻塞在2个地方:
|
||||
#### 3、IO复用模型
|
||||
Linux 提供 select/poll,进程通过将一个或多个 fd 传递给 select 或 poll系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限,因此它的使用也受到了一些制约。Linux 还提供了一个 epoll系统调用,epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。
|
||||
|
||||

|
||||

|
||||
|
||||
#### 4、信号驱动IO模型
|
||||
首先开启套接口信号驱动IO功能,并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。
|
||||
|
||||

|
||||

|
||||
|
||||
#### 5、异步IO模型
|
||||
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动IO 由内核通知我们何时可以开始一个 IO 操作;异步IO模型 由内核通知我们 IO操作何时已经完成。
|
||||
|
||||

|
||||

|
||||
|
||||
从这五种 IO模型的结构 也可以看出,阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。
|
||||
|
||||
最后,我们看一下数据从客户端到服务器,再由服务器返回结果数据的整体IO流程,以便我们更好地理解上述的IO模型。
|
||||

|
||||

|
||||
|
||||
## IO 多路复用技术
|
||||
Java NIO 的核心类库中 多路复用器Selector 就是基于 epoll 的多路复用技术实现。
|
||||
|
@ -7,7 +7,7 @@
|
||||
通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,它接收到客户
|
||||
端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。
|
||||
|
||||

|
||||

|
||||
|
||||
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1: 1的正比关系,由于线程是 Java虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
### 伪异步IO模型图
|
||||
采用线程池和任务队列可以实现一种叫做 伪异步的IO通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task对象 (该类实现了java.lang.Runnable接口),投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
|
||||
|
||||

|
||||

|
||||
|
||||
伪异步 IO通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。
|
||||
|
||||
@ -94,14 +94,14 @@ Buffer对象 包含了一些要写入或者要读出的数据。在 NIO类库
|
||||
|
||||
缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte数组。除了 ByteBuffer,还有其他的一些缓冲区,事实上,每一种 Java基本类型(除了 boolean)都对应有一种与之对应的缓冲区,如:CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer组件中主要类的类图如下所示。
|
||||
|
||||

|
||||

|
||||
|
||||
除了ByteBuffer,每一个 Buffer类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准IO操作 都使用 ByteBuffer,所以它在具有一般缓冲区的操作之外还提供了一些特有的操作,以方便网络读写。
|
||||
|
||||
**2、通道Channel**
|
||||
Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream,要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX网络编程模型 中,底层操作系统的通道都是全双工的,同时支持读写操作。Channel组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。
|
||||
|
||||

|
||||

|
||||
|
||||
**3、多路复用器Selector**
|
||||
多路复用器Selector 是 Java NIO编程 的基础,熟练地掌握 Selector 对于 NIO编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询 “注册在其上的Channel”,如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO操作。
|
||||
@ -110,7 +110,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
|
||||
|
||||
### NIO服务端序列图
|
||||
|
||||

|
||||

|
||||
|
||||
下面,我们看一下 NIO服务端 的主要创建过程。
|
||||
|
||||
@ -191,7 +191,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
|
||||
|
||||
### NIO 客户端序列图
|
||||
|
||||

|
||||

|
||||
|
||||
1、打开 SocketChannel,绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。
|
||||
```java
|
||||
@ -295,7 +295,7 @@ NIO2.0 的异步套接字通道是真正的 异步非阻塞IO,对应于 UNIX
|
||||
## 四种IO编程模型的对比
|
||||
对比之前,这里再澄清一下 “伪异步IO” 的概念。伪异步IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO编程 没有流行之前,为了解决 Tomcat 通信线程同步IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO线程 和业务线程间的直接访问,这样业务线程就不会被 IO线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO线程 或者进行 IO读写,这样也就不会被同步阻塞。
|
||||
|
||||

|
||||

|
||||
|
||||
## 选择 Netty 开发项目的理由
|
||||
从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。
|
||||
|
@ -15,7 +15,7 @@ Java中将输入输出抽象称为流,就好像水管,将两个容器连接
|
||||
##### 2.1 阻塞IO(Blocking I/O)
|
||||
在内核将数据准备好之前,系统调用会一直等待所有的套接字(Socket),默认的是阻塞方式。
|
||||
|
||||

|
||||

|
||||
|
||||
Java中的socket.read()会调用native read(),而Java中的native方法会调用操作系统底层的dll,而dll是C/C++编写的,图中的recvfrom其实是C语言socket编程中的一个方法。所以其实我们在Java中调用socket.read()最后也会调用到图中的recvfrom方法。
|
||||
|
||||
@ -28,7 +28,7 @@ BIO中的阻塞,就是阻塞在2个地方:
|
||||
在这2个时候,我们的BIO程序就是占着茅坑不拉屎,啥事情都不干。
|
||||
##### 2.2 非阻塞IO(Noblocking I/O)
|
||||
|
||||

|
||||

|
||||
|
||||
每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮寻。但是,轮寻对于CPU来说是较大的浪费,一般只有在特定的场景下才使用。
|
||||
|
||||
@ -48,7 +48,7 @@ serverSocketChannel.configureBlocking(false);
|
||||
**BIO 不会在recvfrom(询问数据是否准备好)时阻塞,但还是会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方,Non-Blocking还是会阻塞的。**
|
||||
##### 2.3 IO多路复用(I/O Multiplexing)
|
||||
|
||||

|
||||

|
||||
|
||||
传统情况下client与server通信需要一个3个socket(客户端的socket,服务端的serversocket,服务端中用来和客户端通信的socket),而在IO多路复用中,客户端与服务端通信需要的不是socket,而是3个channel,通过channel可以完成与socket同样的操作,channel的底层还是使用的socket进行通信,但是多个channel只对应一个socket(可能不只是一个,但是socket的数量一定少于channel数量),这样仅仅通过少量的socket就可以完成更多的连接,提高了client容量。
|
||||
|
||||
@ -60,12 +60,12 @@ Mac:kqueue
|
||||
**selector,epoll,kqueue都属于Reactor IO设计。**
|
||||
##### 2.4 信号驱动(Signal driven IO)
|
||||
|
||||

|
||||

|
||||
|
||||
信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对SIGIO信号进行捕捉,并且调用我的信号处理函数来获取数据报。
|
||||
##### 2.5 异步IO(Asynchronous I/O)
|
||||
|
||||

|
||||

|
||||
|
||||
Asynchronous IO调用中是真正的无阻塞,其他IO model中多少会有点阻塞。程序发起read操作之后,立刻就可以开始去做其它的事。而在内核角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
### TCP粘包/拆包问题说明
|
||||
TCP 是个 “流” 协议,所谓流,就是没有界限的一串数据。TCP底层 并不了解上层(如 HTTP协议)业务数据的具体含义,它会根据 TCP缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP粘包和拆包问题。我们可以通过下面的示例图,对 TCP粘包和拆包问题 进行说明。
|
||||
|
||||

|
||||

|
||||
|
||||
假设客户端依次发送了两个数据包 DI 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。
|
||||
1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包;
|
||||
|
@ -5,7 +5,7 @@ Netty 为了向使用者屏蔽 NIO通信 的底层细节,在和用户交互的
|
||||
|
||||
### 基于 Netty 创建客户端 时序图
|
||||
|
||||

|
||||

|
||||
|
||||
### Netty 创建客户端 流程分析
|
||||
1. 用户线程创建 Bootstrap实例,通过 API 设置客户端相关的参数,异步发起客户端连接;
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
### Netty 服务端创建时序图
|
||||
|
||||

|
||||

|
||||
|
||||
下面我们对 Netty服务端创建 的关键步骤和原理进行详细解析。
|
||||
|
||||
@ -78,7 +78,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
|
||||
|
||||
8、**当轮询到 准备就绪的Channel 之后,就由 Reactor线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
9、**执行 Netty 中 系统的ChannelHandler 和 用户添加定制的ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler,相关代码如下。
|
||||
```java
|
||||
@ -174,7 +174,7 @@ backlog 指定了内核为此套接口排队的最大连接个数,对于给定
|
||||
|
||||
TCP参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler;父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。
|
||||
|
||||

|
||||

|
||||
|
||||
本质区别就是:ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有连接该监听端口的客户端都会执行它;父类AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。
|
||||
|
||||
@ -304,7 +304,7 @@ NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作
|
||||
}
|
||||
```
|
||||
到此,Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。
|
||||

|
||||

|
||||
最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor线程 的多路复用器上监听新客户端的接入,代码如下。
|
||||
```java
|
||||
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
|
||||
|
@ -9,7 +9,7 @@
|
||||
```
|
||||
- 源码阅读目标找到了,那么怎么去找入口或者对这句话的标签解析方法呢?项目中使用搜索
|
||||
|
||||

|
||||

|
||||
|
||||
这样就找到了具体解析方法了
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
- 类图
|
||||
|
||||

|
||||

|
||||
```java
|
||||
@Override
|
||||
@Nullable
|
||||
|
@ -453,7 +453,7 @@ public void setDataSource(@Nullable DataSource dataSource) {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
这样就可以获取到了
|
||||
|
||||
|
@ -416,7 +416,7 @@ public class RMIClientSourceCode {
|
||||
|
||||
### RmiProxyFactoryBean
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -671,7 +671,7 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
|
||||
|
||||
- `RmiInvocationHandler`类图
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -747,7 +747,7 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
|
||||
|
||||
类图
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -787,7 +787,7 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
|
||||
|
||||
- `org.springframework.remoting.rmi.RmiServiceExporter#afterPropertiesSet`打上断点
|
||||
|
||||

|
||||

|
||||
|
||||
可以看到此时的数据字段和我们的xml配置中一致
|
||||
|
||||
@ -795,85 +795,85 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
|
||||
|
||||
- `org.springframework.remoting.rmi.RmiServiceExporter#prepare`断点
|
||||
|
||||

|
||||

|
||||
|
||||
往下一直走
|
||||
|
||||

|
||||

|
||||
|
||||
这一行是jdk的就不进去看了
|
||||
|
||||
执行完成就创建出了 `Registry`
|
||||
|
||||

|
||||

|
||||
|
||||
- `org.springframework.remoting.rmi.RmiBasedExporter#getObjectToExport`
|
||||
|
||||
直接看结果对象
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- 执行bind
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
- 此时服务端信息已经成功记录并且启动
|
||||
|
||||
## 客户端debug
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
remote 对象
|
||||
|
||||

|
||||

|
||||
|
||||
- 服务提供接口
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- serviceProxy
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- 方法调用
|
||||
- 使用的是AOP技术进行的,AOP相关技术不在此处展开
|
||||
|
||||

|
||||

|
||||
|
||||
stub 对象
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
- `invocation`
|
||||
|
||||

|
||||

|
||||
|
||||
- `targetObject`
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- 反射执行`method`结束整个调用
|
||||
|
||||

|
||||

|
||||
|
||||
此时得到结果RMI调用结束
|
@ -103,7 +103,7 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
- 该类给`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents`提供了帮助
|
||||
|
||||
```java
|
||||
|
@ -33,7 +33,7 @@ DispatchServlet 和 ContextLoaderListener 提供了在 Web容器 中对 Spring
|
||||
## 2 IoC容器启动的基本过程
|
||||
IoC容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC容器 在 Web应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在 Web容器 中启动 Spring应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML时序图 如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
在 web.xml 中,已经配置了 ContextLoaderListener,它是 Spring 提供的类,是为在 Web容器 中建立 IoC容器 服务的,它实现了 ServletContextListener接口,这个接口是在 Servlet API 中定义的,提供了与 Servlet生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 和 上下文销毁 contextDestroyed()方法。而在 Web容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外,ContextLoaderListener 还继承了 ContextLoader,具体的载入 IoC容器 的过程是由 ContextLoader 来完成的。
|
||||
|
||||
@ -43,7 +43,7 @@ IoC容器 的启动过程就是建立上下文的过程,该上下文是与 Ser
|
||||
先从 Web容器 中的上下文入手,看看 Web环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web环境 中使用 IoC容器,
|
||||
Spring 为 Web应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex接口 与 BeanFactory接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。
|
||||
|
||||
|
@ -127,9 +127,9 @@ public class JSONController {
|
||||
|
||||
信息截图:
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -180,7 +180,7 @@ public class JSONController {
|
||||
|
||||
最终解析结果
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -263,7 +263,7 @@ public class JSONController {
|
||||
|
||||
#### 类图
|
||||
|
||||

|
||||

|
||||
|
||||
#### 解析
|
||||
|
||||
@ -329,7 +329,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
- 属性截图
|
||||
|
||||

|
||||

|
||||
|
||||
- 可以看出这个是我们的第一个跨域配置的信息
|
||||
|
||||
@ -363,7 +363,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
```
|
||||
|
||||
- 
|
||||
- 
|
||||
|
||||
|
||||
|
||||
@ -555,7 +555,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
- 经过跨域拦截器 **`CorsInterceptor`**之后会调用
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -612,4 +612,4 @@ Origin: localhost
|
||||
|
||||
|
||||
|
||||

|
||||

|
@ -8,13 +8,13 @@
|
||||
|
||||
为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet类 的继承关系。
|
||||
|
||||

|
||||

|
||||
|
||||
DispatcherServlet 通过继承 FrameworkServlet 和 HttpServletBean 而继承了 HttpServlet,通过使用Servlet API 来对 HTTP请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC模块 与 Web容器 集成的处理前端。
|
||||
|
||||
DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法,在这个方法里,DispatcherServlet 对 MVC模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP请求 进行响应,作为一个 Servlet,Web容器 会调用 Servlet 的doGet() 和 doPost()方法,在经过 FrameworkServlet 的 processRequest() 简单处理后,会调用 DispatcherServlet 的 doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。
|
||||
|
||||

|
||||

|
||||
|
||||
## 3 DispatcherServlet的启动和初始化
|
||||
前面大致描述了 SpringMVC 的工作流程,下面看一下 DispatcherServlet 的启动和初始化的代码设计及实现。
|
||||
@ -376,7 +376,7 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W
|
||||
|
||||
在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。
|
||||
|
||||

|
||||

|
||||
|
||||
以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL请求 和控制器的对应关系,使 SpringMVC
|
||||
应用 可以根据 HTTP请求 确定一个对应的 Controller。具体来说,这些映射关系是通过 HandlerMapping接口 来封装的,在 HandlerMapping接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP请求 对应的 HandlerExecutionChain,在这个 HandlerExecutionChain 中,封装了具体的 Controller对象。
|
||||
@ -496,7 +496,7 @@ public class HandlerExecutionChain {
|
||||
```
|
||||
HandlerExecutionChain 中定义的 Handler 和 HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping,要做的就是根据 URL映射 的方式,注册 Handler 和 HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping 是 ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间映射关系的 handlerMap 的建立。
|
||||
|
||||

|
||||

|
||||
|
||||
```java
|
||||
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
|
||||
|
@ -6,7 +6,7 @@ JavaEE应用 中的事务处理是一个重要并且涉及范围很广的领域
|
||||
## 1 Spring事务处理 的设计概览
|
||||
Spring事务处理模块 的类层次结构如下图所示。
|
||||
|
||||

|
||||

|
||||
|
||||
从上图可以看到,Spring事务处理模块 是通过 AOP功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP功能,通过这个 TransactionProxyFactoryBean 可以生成 Proxy代理对象,在这个代理对象中,通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
可以看到,在 PlatformTransactionManager组件 的设计中 ,通过 PlatformTransactionManager接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。
|
||||
|
||||

|
||||

|
||||
|
||||
## 2 DataSourceTransactionManager的实现
|
||||
我们先看一下 DataSourceTransactionManager,在这个事务管理器中,它的实现直接与事务处理的底层实现相关。在事务开始的时候,会调用 doBegin()方法,首先会得到相对应的 Connection,然后可以根据事务设置的需要,对 Connection 的相关属性进行配置,比如将 Connection 的 autoCommit功能 关闭,并对像 TimeoutInSeconds 这样的事务处理参数进行设置,最后通过 TransactionSynchronizationManager 来对资源进行绑定。
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。
|
||||
|
||||

|
||||

|
||||
|
||||
```java
|
||||
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
|
||||
|
@ -71,7 +71,7 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe
|
||||
```
|
||||
以上代码完成了 AOP配置,对于用户来说,一个值得关心的问题是,Spring 的 TransactionInterceptor配置 是在什么时候被启动并成为 Advisor通知器 的一部分的呢?从对 createMainInterceptor()方法 的调用分析中可以看到,这个 createMainInterceptor()方法 在 IoC容器 完成 Bean的依赖注入时,通过 initializeBean()方法 被调用,具体的调用过程如下图所示。
|
||||
|
||||
方法的调用链.png)
|
||||
方法的调用链.png)
|
||||
|
||||
在 TransactionProxyFactoryBean 的父类 AbstractSingletonProxyFactoryBean 中的 afterPropertiesSet()方法,是 Spring事务处理 完成 AOP配置 的地方,在建立 TransactionProxyFactoryBean 的事务处理拦截器的时候,首先需要对 ProxyFactoryBean 的 目标Bean 设置进行检查,如果这个 目标Bean 的设置是正确的,就会创建一个 ProxyFactory对象,从而实现 AOP 的使用。在 afterPropertiesSet() 的方法实现中,可以看到为 ProxyFactory 生成代理对象、配置通知器、设置代理接口方法等。
|
||||
```java
|
||||
|
@ -50,11 +50,11 @@
|
||||
```
|
||||
- method
|
||||
|
||||

|
||||

|
||||
|
||||
- annotationType
|
||||
|
||||

|
||||

|
||||
|
||||
```java
|
||||
@Nullable
|
||||
@ -229,9 +229,9 @@
|
||||
|
||||
处理结果
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
处理结果和Order定义相同
|
||||
|
||||
@ -257,7 +257,7 @@ public @interface Order {
|
||||
|
||||
最终返回
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -412,7 +412,7 @@ public @interface Order {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
- `synthesizeAnnotation`方法就不再重复一遍了可以看上文
|
||||
|
||||
|
@ -112,7 +112,7 @@ public class ListenerSourceCode {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -190,7 +190,7 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
|
||||
|
||||
- 执行监听方法
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -241,6 +241,6 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
@ -251,9 +251,9 @@ public class BeanFactoryPostProcessorSourceCode {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -374,15 +374,15 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa
|
||||
|
||||
- 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序Bean
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
- 注册方法信息截图
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -444,4 +444,4 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa
|
||||
|
||||
这个地方已经可以看到`InstantiationAwareBeanPostProcessor`出现了,并且调用了方法`postProcessBeforeInstantiation`,此处就可以调用我们的自定义方法了
|
||||
|
||||

|
||||

|
@ -71,7 +71,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
## PropertyEditorRegistrar解析
|
||||
- 直接在`DatePropertyRegister`打上断点进行查看注册流程
|
||||
|
||||

|
||||

|
||||
|
||||
直接看调用堆栈获取调用层次
|
||||
|
||||
@ -111,7 +111,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
|
||||
- `PropertyEditorRegistrySupport`
|
||||
|
||||

|
||||

|
||||
|
||||
此处对象是通过`DatePropertyRegister`传递的
|
||||
|
||||
@ -166,7 +166,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
|
||||
- 在`AbstractBeanFactory`中查看变量
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -192,7 +192,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -318,17 +318,17 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -336,7 +336,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
|
||||
- 属性值解析
|
||||
|
||||

|
||||

|
||||
|
||||
```JAVA
|
||||
@Nullable
|
||||
@ -397,7 +397,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
该值也是这个方法的返回`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)`
|
||||
|
||||
|
@ -185,7 +185,7 @@ public class XSDDemo {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
- `http://www.huifer.com/schema/user`和我们定义的xsd文件中的url相同,如何找到对应的NamespaceHandler,在`META-INF/spring.handlers`中有定义,
|
||||
|
||||
@ -269,7 +269,7 @@ public class XSDDemo {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
- 这里直接存在数据了,他是从什么时候加载的?
|
||||
|
||||
@ -345,7 +345,7 @@ public class XSDDemo {
|
||||
|
||||
断点
|
||||
|
||||

|
||||

|
||||
|
||||
```JAVA
|
||||
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
|
||||
@ -355,13 +355,13 @@ public class XSDDemo {
|
||||
|
||||
`public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";`
|
||||
|
||||

|
||||

|
||||
|
||||
此时还是空
|
||||
|
||||
走完
|
||||
|
||||

|
||||

|
||||
|
||||
```java
|
||||
@Override
|
||||
@ -413,7 +413,7 @@ public class XSDDemo {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -531,7 +531,7 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -571,7 +571,7 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
### org.springframework.beans.factory.xml.BeanDefinitionParser#parse
|
||||
|
||||
@ -629,7 +629,7 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
- 官方提供的测试类: `org.springframework.beans.factory.support.DefaultSingletonBeanRegistryTests`
|
||||
|
||||
类图
|
||||

|
||||

|
||||
## 注册方法解析
|
||||
- 从名字可以看出这是一个单例对象的注册类
|
||||
- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.registerSingleton`
|
||||
|
@ -79,9 +79,9 @@
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
得到本地路径,后续直接返回读取资源
|
||||
|
||||
@ -146,7 +146,7 @@
|
||||
|
||||
- systemId `https://www.springframework.org/dtd/spring-beans-2.0.dtd`
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 总结
|
||||
|
@ -49,7 +49,7 @@
|
||||
|
||||
读取xml配置文件
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -191,7 +191,7 @@
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -199,7 +199,7 @@
|
||||
|
||||
获取方法`String result = getStringOrNull(bundle, code);`就是map获取
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -207,4 +207,4 @@
|
||||
|
||||
- 没有配置文件的情况
|
||||
|
||||

|
||||

|
@ -60,7 +60,7 @@
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -46,7 +46,7 @@ public class ContextNamespaceHandler extends NamespaceHandlerSupport {
|
||||
|
||||
### org.springframework.context.annotation.ComponentScanBeanDefinitionParser
|
||||
|
||||

|
||||

|
||||
|
||||
- 实现`BeanDefinitionParser`直接看`parse`方法
|
||||
```java
|
||||
@ -299,7 +299,7 @@ public int scan(String... basePackages) {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
#### org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName
|
||||
|
||||
@ -364,7 +364,7 @@ public class DemoService {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -398,7 +398,7 @@ public class BeanConfig {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -48,7 +48,7 @@ public class JmsBootstrapConfiguration {
|
||||
|
||||
类图
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -329,7 +329,7 @@ public class JmsBootstrapConfiguration {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
## MessageConverter
|
||||
- 消息转换接口
|
||||
- 类图如下
|
||||

|
||||

|
||||
- 两个方法
|
||||
1. fromMessage: 从消息转换到Object
|
||||
```java
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
类图:
|
||||
|
||||

|
||||

|
||||
|
||||
### fromMessage
|
||||
|
||||
@ -192,6 +192,6 @@
|
||||
|
||||
- 两种创建方式基本相同,如果出现异常组装异常消息对象`ErrorMessage`,成功创建`GenericMessage`
|
||||
|
||||

|
||||

|
||||
|
||||
从类图上看`ErrorMessage`是`GenericMessage`的子类
|
@ -137,11 +137,11 @@ public class Application {
|
||||
- `SpringFactoriesLoader.loadFactoryNames(type, classLoader)` 是spring提供的方法,主要目的是读取`spring.factories`文件
|
||||
- 读取需要创建的内容
|
||||
|
||||

|
||||

|
||||
|
||||
- 创建完成
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -151,21 +151,21 @@ public class Application {
|
||||
|
||||
`SharedMetadataReaderFactoryContextInitializer`
|
||||
|
||||

|
||||

|
||||
|
||||
- 同样的再找一个`DelegatingApplicationContextInitializer`
|
||||
|
||||

|
||||

|
||||
|
||||
- 下图中的所有类都有Order数值返回
|
||||
|
||||
排序前:
|
||||
|
||||

|
||||

|
||||
|
||||
排序后:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -378,7 +378,7 @@ public class Application {
|
||||
|
||||
### exceptionReporters
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -465,9 +465,9 @@ public class Application {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -504,7 +504,7 @@ public class Application {
|
||||
|
||||
- 数据结果
|
||||
|
||||

|
||||

|
||||
|
||||
- 子类的具体实现不展开了
|
||||
|
||||
@ -534,7 +534,7 @@ public class Application {
|
||||
|
||||
- `primarySources` 就是我们的项目启动类,在`SpringApplication`的构造器中有`this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources))`
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -608,7 +608,7 @@ private int load(Object source) {
|
||||
|
||||
- 通过前文我们已经知道 `source`就是一个class
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -36,7 +36,7 @@ public @interface ConfigurationPropertiesScan {}
|
||||
|
||||
## ConfigurationPropertiesScanRegistrar
|
||||
|
||||

|
||||

|
||||
|
||||
- debug没有抓到后续补充
|
||||
|
||||
@ -156,13 +156,13 @@ public @interface EnableConfigurationProperties {
|
||||
|
||||
- 先看输入参数 **metadata**
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- getTypes结果
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -231,7 +231,7 @@ public @interface EnableConfigurationProperties {
|
||||
|
||||
## ConfigurationPropertiesBindingPostProcessor
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -356,15 +356,15 @@ public @interface EnableConfigurationProperties {
|
||||
|
||||
- `annotation`
|
||||
|
||||

|
||||

|
||||
|
||||
- `bindType`
|
||||
|
||||

|
||||

|
||||
|
||||
- 返回对象
|
||||
|
||||

|
||||

|
||||
|
||||
- 此时数据还没有进去
|
||||
|
||||
@ -378,7 +378,7 @@ public @interface EnableConfigurationProperties {
|
||||
|
||||
直接看结果
|
||||
|
||||

|
||||

|
||||
|
||||
- 上述配置和我在配置文件中写的配置一致
|
||||
|
||||
@ -425,7 +425,7 @@ BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -501,15 +501,15 @@ BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -546,7 +546,7 @@ private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperti
|
||||
|
||||
- 最终获取得到的处理器
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -586,7 +586,7 @@ private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperti
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ public enum LogLevel {
|
||||
|
||||
- `org.springframework.boot.logging.java.JavaLoggingSystem`
|
||||
|
||||

|
||||

|
||||
|
||||
```JAVA
|
||||
static {
|
||||
@ -141,7 +141,7 @@ public static LoggingSystem get(ClassLoader classLoader) {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -151,7 +151,7 @@ public static LoggingSystem get(ClassLoader classLoader) {
|
||||
|
||||
- 初始化之前
|
||||
|
||||

|
||||

|
||||
|
||||
- 链路
|
||||
1. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEvent`
|
||||
@ -395,13 +395,13 @@ public static LoggingSystem get(ClassLoader classLoader) {
|
||||
|
||||
- 添加配置文件
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
- 此时配置文件地址出现了
|
||||
|
||||
|
@ -12,11 +12,11 @@
|
||||
|
||||
2. 全局搜索yml
|
||||
|
||||

|
||||

|
||||
|
||||
3. 换成`properties`搜索
|
||||
|
||||

|
||||

|
||||
|
||||
4. 我们以`yml`为例打上断点开始源码追踪
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
看到调用堆栈
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -40,9 +40,9 @@
|
||||
|
||||
### 调用过程
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -91,15 +91,15 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL
|
||||
|
||||
- 搜索目标: `org.springframework.boot.env.PropertySourceLoader`
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
观察发现里面有一个`YamlPropertySourceLoader`和我们之前找yml字符串的时候找到的类是一样的。说明搜索方式没有什么问题。
|
||||
|
||||

|
||||

|
||||
|
||||
初始化完成,后续进行解析了
|
||||
|
||||
@ -139,7 +139,7 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL
|
||||
### initializeProfiles
|
||||
|
||||
- 初始化`private Deque<Profile> profiles;` 属性
|
||||
- 
|
||||
- 
|
||||
|
||||
|
||||
|
||||
@ -170,7 +170,7 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document
|
||||
|
||||
- 资源路径可能性
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -237,7 +237,7 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
@ -52,7 +52,7 @@ public @interface EnableAutoConfiguration {
|
||||
|
||||
- 类图
|
||||
|
||||

|
||||

|
||||
|
||||
## getAutoConfigurationMetadata()
|
||||
|
||||
@ -106,7 +106,7 @@ public @interface EnableAutoConfiguration {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -138,11 +138,11 @@ public @interface EnableAutoConfiguration {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
同样找一下redis
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -236,13 +236,13 @@ public class RedisProperties {
|
||||
|
||||
- `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process`
|
||||
|
||||

|
||||

|
||||
|
||||
再此之前我们看过了`getAutoConfigurationMetadata()`的相关操作
|
||||
|
||||
关注 `AnnotationMetadata annotationMetadata` 存储了一些什么
|
||||
|
||||

|
||||

|
||||
|
||||
这里简单理解
|
||||
|
||||
@ -304,7 +304,7 @@ public class RedisProperties {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -324,7 +324,7 @@ public class RedisProperties {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
- 第一个是我自己写的一个测试用
|
||||
|
||||
@ -390,7 +390,7 @@ public class RedisProperties {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -401,7 +401,7 @@ public class RedisProperties {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
### checkExcludedClasses
|
||||
|
||||
@ -477,7 +477,7 @@ public class RedisProperties {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -493,7 +493,7 @@ public class RedisProperties {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -521,13 +521,13 @@ public class RedisProperties {
|
||||
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
- `AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);`
|
||||
|
||||

|
||||

|
||||
|
||||
- `org.springframework.boot.autoconfigure.AutoConfigurationImportListener#onAutoConfigurationImportEvent` 在执行自动配置时触发 , 实现类只有 **`ConditionEvaluationReportAutoConfigurationImportListener`**
|
||||
|
||||
@ -547,7 +547,7 @@ public class RedisProperties {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@ -565,7 +565,7 @@ public class RedisProperties {
|
||||
|
||||
|
||||
|
||||

|
||||

|
||||
|
||||
- 后续的一些行为相对简单,直接放个源码了.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user