Merge branch 'main' into master

pull/80/head
Yang Libin 4 years ago committed by GitHub
commit 261a9c253f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,5 +13,5 @@ jobs:
with:
github_token: ${{ github.token }}
source_ref: ${{ github.ref }}
target_branch: 'master'
target_branch: 'main'
commit_message_template: '[Automated] Merged {source_ref} into {target_branch}'

@ -0,0 +1,25 @@
name: Prettier
on:
pull_request:
push:
branches:
- main
jobs:
prettier:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}
- name: Prettify code
uses: creyD/prettier_action@v3.0
with:
prettier_options: --write **/*.{html,js,md}
commit_message: 'docs: prettify code'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

@ -2,11 +2,12 @@ name: Sync
on:
push:
branches: [ master ]
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'doocs/source-code-hunter'
steps:
- name: Sync to Gitee
uses: wearerequired/git-mirror-action@master
@ -27,4 +28,5 @@ jobs:
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
gitee-password: ${{ secrets.GITEE_PASSWORD }}
# 注意替换为你的 Gitee 仓库
gitee-repo: doocs/source-code-hunter
gitee-repo: doocs/source-code-hunter
branch: main

@ -1,5 +1,6 @@
# 互联网公司常用框架源码赏析
[![license](https://badgen.net/github/license/doocs/source-code-hunter?color=green)](https://github.com/doocs/source-code-hunter/blob/master/LICENSE)
[![license](https://badgen.net/github/license/doocs/source-code-hunter?color=green)](https://github.com/doocs/source-code-hunter/blob/main/LICENSE)
[![stars](https://badgen.net/github/stars/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/stargazers)
[![contributors](https://badgen.net/github/contributors/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/graphs/contributors)
[![help-wanted](https://badgen.net/github/label-issues/doocs/source-code-hunter/help%20wanted/open)](https://github.com/doocs/source-code-hunter/labels/help%20wanted)
@ -10,52 +11,52 @@
加入我们,一起通读互联网行业主流框架及中间件源码,成为强大的 “源码猎人”,目前开放的有 **Spring 全家桶**、**Mybatis**、**Netty**、**Dubbo 框架**,及 **Redis**、**Tomcat** 中间件等,让我们一起开拓新的领地,揭开这些源码的神秘面纱。
本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件简单有趣,且有价值的事情,抽空更新中... (如果本项目对您有帮助请watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件简单有趣,且有价值的事情,抽空更新中... (如果本项目对您有帮助,请 watch、star、fork 素质三连一波,鼓励一下作者,谢谢)
* Netlify: https://schunter.netlify.app
* Gitee Pages: https://doocs.gitee.io/source-code-hunter
* GitHub Pages: https://doocs.github.io/source-code-hunter
- Netlify: https://schunter.netlify.app
- Gitee Pages: https://doocs.gitee.io/source-code-hunter
- GitHub Pages: https://doocs.github.io/source-code-hunter
## Spring 系列
### IoC 容器
* [BeanDefinition 的资源定位过程](/docs/Spring/IoC/1、BeanDefinition的资源定位过程.md)
* [将 bean 解析封装成 BeanDefinition](/docs/Spring/IoC/2、将bean解析封装成BeanDefinition.md)
* [将 BeanDefinition 注册进 IoC 容器](/docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md)
* [依赖注入(DI)](/docs/Spring/IoC/4、依赖注入(DI).md)
* [BeanPostProcessor](/docs/Spring/IoC/BeanPostProcessor.md)
* [Spring BeanFactory源码解析](/docs/Spring/clazz/Spring-beanFactory.md)
- [BeanDefinition 的资源定位过程](/docs/Spring/IoC/1、BeanDefinition的资源定位过程.md)
- [将 bean 解析封装成 BeanDefinition](/docs/Spring/IoC/2、将bean解析封装成BeanDefinition.md)
- [将 BeanDefinition 注册进 IoC 容器](/docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md)
- [依赖注入(DI)](</docs/Spring/IoC/4、依赖注入(DI).md>)
- [BeanPostProcessor](/docs/Spring/IoC/BeanPostProcessor.md)
- [Spring BeanFactory 源码解析](/docs/Spring/clazz/Spring-beanFactory.md)
### AOP
* [AOP 源码实现及分析](/docs/Spring/AOP/AOP源码实现及分析.md)
* [JDK 动态代理的实现原理解析](/docs/Spring/AOP/JDK动态代理的实现原理解析.md)
* [Spring AOP 如何生效(Spring AOP标签解析)](/docs/Spring/AOP/Spring-Aop如何生效.md)
- [AOP 源码实现及分析](/docs/Spring/AOP/AOP源码实现及分析.md)
- [JDK 动态代理的实现原理解析](/docs/Spring/AOP/JDK动态代理的实现原理解析.md)
- [Spring AOP 如何生效(Spring AOP 标签解析)](/docs/Spring/AOP/Spring-Aop如何生效.md)
### SpringMVC
* [IoC容器 在 Web环境 中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md)
* [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md)
* [SpringMVC 跨域解析](/docs/Spring/SpringMVC/SpringMVC-CROS.md)
* [Spring-MVC-HandlerMapping](/docs/Spring/mvc/Spring-MVC-HandlerMapping.md)
* [Spring-mvc-MappingRegistry](/docs/Spring/mvc/Spring-mvc-MappingRegistry.md)
- [IoC 容器 在 Web 环境 中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md)
- [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md)
- [SpringMVC 跨域解析](/docs/Spring/SpringMVC/SpringMVC-CROS.md)
- [Spring-MVC-HandlerMapping](/docs/Spring/mvc/Spring-MVC-HandlerMapping.md)
- [Spring-mvc-MappingRegistry](/docs/Spring/mvc/Spring-mvc-MappingRegistry.md)
### SpringJDBC
* 努力编写中...
- 努力编写中...
### Spring 事务
* [Spring 与事务处理](/docs/Spring/SpringTransaction/Spring与事务处理.md)
* [Spring 声明式事务处理](/docs/Spring/SpringTransaction/Spring声明式事务处理.md)
* [Spring 事务处理的设计与实现](/docs/Spring/SpringTransaction/Spring事务处理的设计与实现.md)
* [Spring 事务管理器的设计与实现](/docs/Spring/SpringTransaction/Spring事务管理器的设计与实现.md)
* [Spring 事务解析](/docs/Spring/TX/Spring-transaction.md)
- [Spring 与事务处理](/docs/Spring/SpringTransaction/Spring与事务处理.md)
- [Spring 声明式事务处理](/docs/Spring/SpringTransaction/Spring声明式事务处理.md)
- [Spring 事务处理的设计与实现](/docs/Spring/SpringTransaction/Spring事务处理的设计与实现.md)
- [Spring 事务管理器的设计与实现](/docs/Spring/SpringTransaction/Spring事务管理器的设计与实现.md)
- [Spring 事务解析](/docs/Spring/TX/Spring-transaction.md)
### Spring 源码故事(瞎编版)
* [面筋哥 IoC 容器的一天(上)](/docs/Spring/Spring源码故事瞎编版/面筋哥IoC容器的一天(上).md)
- [面筋哥 IoC 容器的一天(上)](</docs/Spring/Spring源码故事瞎编版/面筋哥IoC容器的一天(上).md>)
### Spring 类解析
@ -88,208 +89,225 @@
* [Spring-Parser](/docs/Spring/clazz/format/Spring-Parser.md)
* [Spring-Printer](/docs/Spring/clazz/format/Spring-Printer.md)
### Spring5 新特性
* [Spring5-spring.components解析](/docs/Spring/Spring5新特性/Spring-spring-components.md)
- [Spring5-spring.components 解析](/docs/Spring/Spring5新特性/Spring-spring-components.md)
### Spring RMI
* [Spring RMI](/docs/Spring/RMI/Spring-RMI.md)
- [Spring RMI](/docs/Spring/RMI/Spring-RMI.md)
### Spring Message
* [Spring EnableJMS](/docs/Spring/message/Spring-EnableJms.md)
* [Spring JmsTemplate](/docs/Spring/message/Spring-JmsTemplate.md)
* [Spring MessageConverter](/docs/Spring/message/Spring-MessageConverter.md)
- [Spring EnableJMS](/docs/Spring/message/Spring-EnableJms.md)
- [Spring JmsTemplate](/docs/Spring/message/Spring-JmsTemplate.md)
- [Spring MessageConverter](/docs/Spring/message/Spring-MessageConverter.md)
### SpringBoot
* [SpringBoot run方法解析](/docs/SpringBoot/Spring-Boot-Run.md)
* [SpringBoot 配置加载解析](/docs/SpringBoot/SpringBoot-application-load.md)
* [SpringBoot 自动装配](/docs/SpringBoot/SpringBoot-自动装配.md)
* [SpringBoot ConfigurationProperties](/docs/SpringBoot/SpringBoot-ConfigurationProperties.md)
* [SpringBoot 日志系统](/docs/SpringBoot/SpringBoot-LogSystem.md)
* [SpringBoot ConditionalOnBean](/docs/SpringBoot/SpringBoot-ConditionalOnBean.md)
- [SpringBoot run 方法解析](/docs/SpringBoot/Spring-Boot-Run.md)
- [SpringBoot 配置加载解析](/docs/SpringBoot/SpringBoot-application-load.md)
- [SpringBoot 自动装配](/docs/SpringBoot/SpringBoot-自动装配.md)
- [SpringBoot ConfigurationProperties](/docs/SpringBoot/SpringBoot-ConfigurationProperties.md)
- [SpringBoot 日志系统](/docs/SpringBoot/SpringBoot-LogSystem.md)
- [SpringBoot ConditionalOnBean](/docs/SpringBoot/SpringBoot-ConditionalOnBean.md)
## MyBatis
### 基础支持层
* [反射工具箱和 TypeHandler 系列](docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md)
* [DataSource 及 Transaction 模块](docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md)
* [binding 模块](docs/Mybatis/基础支持层/3、binding模块.md)
* [缓存模块](docs/Mybatis/基础支持层/4、缓存模块.md)
- [反射工具箱和 TypeHandler 系列](docs/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列.md)
- [DataSource 及 Transaction 模块](docs/Mybatis/基础支持层/2、DataSource及Transaction模块.md)
- [binding 模块](docs/Mybatis/基础支持层/3、binding模块.md)
- [缓存模块](docs/Mybatis/基础支持层/4、缓存模块.md)
### 核心处理层
* [MyBatis 初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md)
* [SqlNode 和 SqlSource](docs/Mybatis/核心处理层/2、SqlNode和SqlSource.md)
* [ResultSetHandler](docs/Mybatis/核心处理层/3、ResultSetHandler.md)
* [StatementHandler](docs/Mybatis/核心处理层/4、StatementHandler.md)
* [Executor 组件](docs/Mybatis/核心处理层/5、Executor组件.md)
* [SqlSession 组件](docs/Mybatis/核心处理层/6、SqlSession组件.md)
- [MyBatis 初始化](docs/Mybatis/核心处理层/1、MyBatis初始化.md)
- [SqlNode 和 SqlSource](docs/Mybatis/核心处理层/2、SqlNode和SqlSource.md)
- [ResultSetHandler](docs/Mybatis/核心处理层/3、ResultSetHandler.md)
- [StatementHandler](docs/Mybatis/核心处理层/4、StatementHandler.md)
- [Executor 组件](docs/Mybatis/核心处理层/5、Executor组件.md)
- [SqlSession 组件](docs/Mybatis/核心处理层/6、SqlSession组件.md)
### 类解析
* [Mybatis-Cache](/docs/Mybatis/基础支持层/Mybatis-Cache.md)
* [Mybatis-log](/docs/Mybatis/基础支持层/Mybatis-log.md)
* [Mybatis-Reflector](/docs/Mybatis/基础支持层/Mybatis-Reflector.md)
* [Mybatis-Alias](/docs/Mybatis/核心处理层/Mybatis-Alias.md)
* [Mybatis-Cursor](/docs/Mybatis/核心处理层/Mybatis-Cursor.md)
* [Mybatis-DataSource](/docs/Mybatis/核心处理层/Mybatis-DataSource.md)
* [Mybatis-DyanmicSqlSourcce](/docs/Mybatis/核心处理层/Mybatis-DyanmicSqlSourcce.md)
* [Mybatis-MapperMethod](/docs/Mybatis/核心处理层/Mybatis-MapperMethod.md)
* [Mybatis-MetaObject](/docs/Mybatis/核心处理层/Mybatis-MetaObject.md)
* [Mybatis-MethodSignature](/docs/Mybatis/核心处理层/Mybatis-MethodSignature.md)
* [Mybatis-ObjectWrapper](/docs/Mybatis/核心处理层/Mybatis-ObjectWrapper.md)
* [Mybatis-ParamNameResolver](/docs/Mybatis/核心处理层/Mybatis-ParamNameResolver.md)
* [Mybatis-SqlCommand](/docs/Mybatis/核心处理层/Mybatis-SqlCommand.md)
* [Mybats-GenericTokenParser](/docs/Mybatis/核心处理层/Mybats-GenericTokenParser.md)
- [Mybatis-Cache](/docs/Mybatis/基础支持层/Mybatis-Cache.md)
- [Mybatis-log](/docs/Mybatis/基础支持层/Mybatis-log.md)
- [Mybatis-Reflector](/docs/Mybatis/基础支持层/Mybatis-Reflector.md)
- [Mybatis-Alias](/docs/Mybatis/核心处理层/Mybatis-Alias.md)
- [Mybatis-Cursor](/docs/Mybatis/核心处理层/Mybatis-Cursor.md)
- [Mybatis-DataSource](/docs/Mybatis/核心处理层/Mybatis-DataSource.md)
- [Mybatis-DyanmicSqlSourcce](/docs/Mybatis/核心处理层/Mybatis-DyanmicSqlSourcce.md)
- [Mybatis-MapperMethod](/docs/Mybatis/核心处理层/Mybatis-MapperMethod.md)
- [Mybatis-MetaObject](/docs/Mybatis/核心处理层/Mybatis-MetaObject.md)
- [Mybatis-MethodSignature](/docs/Mybatis/核心处理层/Mybatis-MethodSignature.md)
- [Mybatis-ObjectWrapper](/docs/Mybatis/核心处理层/Mybatis-ObjectWrapper.md)
- [Mybatis-ParamNameResolver](/docs/Mybatis/核心处理层/Mybatis-ParamNameResolver.md)
- [Mybatis-SqlCommand](/docs/Mybatis/核心处理层/Mybatis-SqlCommand.md)
- [Mybats-GenericTokenParser](/docs/Mybatis/核心处理层/Mybats-GenericTokenParser.md)
## Netty
### 网络 IO 技术基础
* [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)
* [IO模型](docs/Netty/IOTechnologyBase/IO模型.md)
* [四种IO编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md)
### JDK1.8 NIO包 核心组件源码剖析
* [Selector、SelectionKey及Channel组件](docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md)
- [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md)
- [IO 模型](docs/Netty/IOTechnologyBase/IO模型.md)
- [四种 IO 编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md)
### JDK1.8 NIO 包 核心组件源码剖析
- [Selector、SelectionKey 及 Channel 组件](docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md)
### Netty 粘拆包及解决方案
* [TCP粘拆包问题及Netty中的解决方案](docs/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案.md)
- [TCP 粘拆包问题及 Netty 中的解决方案](docs/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案.md)
### Netty 多协议开发
* [基于HTTP协议的Netty开发](docs/Netty/Netty多协议开发/基于HTTP协议的Netty开发.md)
* [基于WebSocket协议的Netty开发](docs/Netty/Netty多协议开发/基于WebSocket协议的Netty开发.md)
* [基于自定义协议的Netty开发](docs/Netty/Netty多协议开发/基于自定义协议的Netty开发.md)
- [基于 HTTP 协议的 Netty 开发](docs/Netty/Netty多协议开发/基于HTTP协议的Netty开发.md)
- [基于 WebSocket 协议的 Netty 开发](docs/Netty/Netty多协议开发/基于WebSocket协议的Netty开发.md)
- [基于自定义协议的 Netty 开发](docs/Netty/Netty多协议开发/基于自定义协议的Netty开发.md)
### 基于 Netty 开发服务端及客户端
* [基于Netty的服务端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发.md)
* [基于Netty的客户端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发.md)
- [基于 Netty 的服务端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发.md)
- [基于 Netty 的客户端开发](docs/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发.md)
### Netty 主要组件的源码分析
* [ByteBuf组件](docs/Netty/Netty主要组件源码分析/ByteBuf组件.md)
* [Channel组件 和 Unsafe组件](docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md)
* [EventLoop 组件](docs/Netty/Netty主要组件源码分析/EventLoop组件.md)
* [ChannelPipeline 和 ChannelHandler组件](docs/Netty/Netty主要组件源码分析/ChannelPipeline和ChannelHandler组件.md)
* [Future 和 Promise组件](docs/Netty/Netty主要组件源码分析/Future和Promise组件.md)
- [ByteBuf 组件](docs/Netty/Netty主要组件源码分析/ByteBuf组件.md)
- [Channel 组件 和 Unsafe 组件](docs/Netty/Netty主要组件源码分析/Channel和Unsafe组件.md)
- [EventLoop 组件](docs/Netty/Netty主要组件源码分析/EventLoop组件.md)
- [ChannelPipeline 和 ChannelHandler 组件](docs/Netty/Netty主要组件源码分析/ChannelPipeline和ChannelHandler组件.md)
- [Future 和 Promise 组件](docs/Netty/Netty主要组件源码分析/Future和Promise组件.md)
### Netty 高级特性
* [Netty 架构设计](docs/Netty/AdvancedFeaturesOfNetty/Netty架构设计.md)
* [Netty 高性能之道](docs/Netty/AdvancedFeaturesOfNetty/Netty高性能之道.md)
- [Netty 架构设计](docs/Netty/AdvancedFeaturesOfNetty/Netty架构设计.md)
- [Netty 高性能之道](docs/Netty/AdvancedFeaturesOfNetty/Netty高性能之道.md)
### Netty 技术细节源码分析
* [FastThreadLocal源码分析](docs/Netty/Netty技术细节源码分析/FastThreadLocal源码分析.md)
* [Recycler对象池原理分析](docs/Netty/Netty技术细节源码分析/Recycler对象池原理分析.md)
* [MpscLinkedQueue队列原理分析](docs/Netty/Netty技术细节源码分析/MpscLinkedQueue队列原理分析.md)
* [HashedWheelTimer时间轮原理分析](docs/Netty/Netty技术细节源码分析/HashedWheelTimer时间轮原理分析.md)
* [ByteBuf的内存泄漏原因与检测原理](docs/Netty/Netty技术细节源码分析/ByteBuf的内存泄漏原因与检测原理.md)
- [FastThreadLocal 源码分析](docs/Netty/Netty技术细节源码分析/FastThreadLocal源码分析.md)
- [Recycler 对象池原理分析](docs/Netty/Netty技术细节源码分析/Recycler对象池原理分析.md)
- [MpscLinkedQueue 队列原理分析](docs/Netty/Netty技术细节源码分析/MpscLinkedQueue队列原理分析.md)
- [HashedWheelTimer 时间轮原理分析](docs/Netty/Netty技术细节源码分析/HashedWheelTimer时间轮原理分析.md)
- [HashedWheelTimer & schedule](docs/Netty/Netty技术细节源码分析/HashedWheelTimer&schedule.md)
- [ByteBuf 的内存泄漏原因与检测原理](docs/Netty/Netty技术细节源码分析/ByteBuf的内存泄漏原因与检测原理.md)
- [内存池之 PoolChunk 设计与实现](docs/Netty/Netty技术细节源码分析/内存池之PoolChunk设计与实现.md)
- [内存池之从内存池申请内存](docs/Netty/Netty技术细节源码分析/内存池之从内存池申请内存.md)
## Dubbo
### 架构设计
* [Dubbo整体架构](docs/Dubbo/architectureDesign/Dubbo整体架构.md)
- [Dubbo 整体架构](docs/Dubbo/architectureDesign/Dubbo整体架构.md)
### SPI机制
### SPI 机制
* [Dubbo与Java的SPI机制](docs/Dubbo/SPI/Dubbo与Java的SPI机制.md)
- [Dubbo 与 Java 的 SPI 机制](docs/Dubbo/SPI/Dubbo与Java的SPI机制.md)
### 注册中心
* [Dubbo注册中心模块简析](docs/Dubbo/registry/Dubbo注册中心模块简析.md)
* [注册中心的Zookeeper实现](docs/Dubbo/registry/注册中心的Zookeeper实现.md)
- [Dubbo 注册中心模块简析](docs/Dubbo/registry/Dubbo注册中心模块简析.md)
- [注册中心的 Zookeeper 实现](docs/Dubbo/registry/注册中心的Zookeeper实现.md)
### 远程通信
* [Dubbo远程通信模块简析](docs/Dubbo/remote/Dubbo远程通信模块简析.md)
* [Transport组件](docs/Dubbo/remote/Transport组件.md)
* [Exchange组件](docs/Dubbo/remote/Exchange组件.md)
* [Buffer组件](docs/Dubbo/remote/Buffer组件.md)
* [基于Netty实现远程通信](docs/Dubbo/remote/基于Netty实现远程通信.md)
* [基于HTTP实现远程通信](docs/Dubbo/remote/基于HTTP实现远程通信.md)
- [Dubbo 远程通信模块简析](docs/Dubbo/remote/Dubbo远程通信模块简析.md)
- [Transport 组件](docs/Dubbo/remote/Transport组件.md)
- [Exchange 组件](docs/Dubbo/remote/Exchange组件.md)
- [Buffer 组件](docs/Dubbo/remote/Buffer组件.md)
- [基于 Netty 实现远程通信](docs/Dubbo/remote/基于Netty实现远程通信.md)
- [基于 HTTP 实现远程通信](docs/Dubbo/remote/基于HTTP实现远程通信.md)
### RPC
* [RPC模块简析](docs/Dubbo/RPC/RPC模块简析.md)
* [Protocol组件](docs/Dubbo/RPC/Protocol组件.md)
* [Proxy组件](docs/Dubbo/RPC/Proxy组件.md)
* [Dubbo协议](docs/Dubbo/RPC/Dubbo协议.md)
* [Hessian协议](docs/Dubbo/RPC/Hessian协议.md)
- [RPC 模块简析](docs/Dubbo/RPC/RPC模块简析.md)
- [Protocol 组件](docs/Dubbo/RPC/Protocol组件.md)
- [Proxy 组件](docs/Dubbo/RPC/Proxy组件.md)
- [Dubbo 协议](docs/Dubbo/RPC/Dubbo协议.md)
- [Hessian 协议](docs/Dubbo/RPC/Hessian协议.md)
### 集群
* [Dubbo集群模块简析](docs/Dubbo/cluster/Dubbo集群模块简析.md)
* [负载均衡](docs/Dubbo/cluster/负载均衡.md)
* [集群容错](docs/Dubbo/cluster/集群容错.md)
* [mock与服务降级](docs/Dubbo/cluster/mock与服务降级.md)
- [Dubbo 集群模块简析](docs/Dubbo/cluster/Dubbo集群模块简析.md)
- [负载均衡](docs/Dubbo/cluster/负载均衡.md)
- [集群容错](docs/Dubbo/cluster/集群容错.md)
- [mock 与服务降级](docs/Dubbo/cluster/mock与服务降级.md)
## Tomcat
### Servlet 与 Servlet容器
### Servlet 与 Servlet 容器
* [servlet-api 源码赏析](docs/Tomcat/servlet-api源码赏析.md)
* [一个简单的Servlet容器](docs/Tomcat/一个简单的servlet容器代码设计.md)
* [Servlet容器详解](docs/Tomcat/servlet容器详解.md)
- [servlet-api 源码赏析](docs/Tomcat/servlet-api源码赏析.md)
- [一个简单的 Servlet 容器](docs/Tomcat/一个简单的servlet容器代码设计.md)
- [Servlet 容器详解](docs/Tomcat/servlet容器详解.md)
### Web 容器
* [一个简单的Web服务器](docs/Tomcat/一个简单的Web服务器代码设计.md)
- [一个简单的 Web 服务器](docs/Tomcat/一个简单的Web服务器代码设计.md)
## Redis
* [深挖 Redis 6.0 源码——SDS](docs/Redis/redis-sds.md)
- [深挖 Redis 6.0 源码——SDS](docs/Redis/redis-sds.md)
## Nacos
* [nacos 服务注册](docs/nacos/nacos-discovery.md)
- [nacos 服务注册](docs/nacos/nacos-discovery.md)
## Sentinel
- [sentinel 时间窗口实现](docs/Sentinel/Sentinel时间窗口的实现.md)
## 番外篇JDK 1.8
### 基础类库
* [String类 源码赏析](docs/JDK/basic/String.md)
* [Thread类 源码赏析](docs/JDK/basic/Thread.md)
* [ThreadLocal类 源码赏析](docs/JDK/basic/ThreadLocal.md)
- [String 类 源码赏析](docs/JDK/basic/String.md)
- [Thread 类 源码赏析](docs/JDK/basic/Thread.md)
- [ThreadLocal 类 源码赏析](docs/JDK/basic/ThreadLocal.md)
### 集合
* [HashMap类 源码赏析](docs/JDK/collection/HashMap.md)
* [ConcurrentHashMap类 源码赏析](docs/JDK/collection/ConcurrentHashMap.md)
* [LinkedHashMap类 源码赏析](docs/JDK/collection/LinkedHashMap.md)
* [ArrayList类 源码赏析](docs/JDK/collection/ArrayList.md)
* [LinkedList类 源码赏析](docs/JDK/collection/LinkedList.md)
* [HashSet类 源码赏析](docs/JDK/collection/HashSet.md)
* [TreeSet类 源码赏析](docs/JDK/collection/TreeSet.md)
- [HashMap 类 源码赏析](docs/JDK/collection/HashMap.md)
- [ConcurrentHashMap 类 源码赏析](docs/JDK/collection/ConcurrentHashMap.md)
- [LinkedHashMap 类 源码赏析](docs/JDK/collection/LinkedHashMap.md)
- [ArrayList 类 源码赏析](docs/JDK/collection/ArrayList.md)
- [LinkedList 类 源码赏析](docs/JDK/collection/LinkedList.md)
- [HashSet 类 源码赏析](docs/JDK/collection/HashSet.md)
- [TreeSet 类 源码赏析](docs/JDK/collection/TreeSet.md)
### 并发编程
* [JUC并发包UML全量类图](docs/JDK/concurrentCoding/JUC并发包UML全量类图.md)
* [Executor 线程池组件 源码赏析](docs/JDK/concurrentCoding/Executor线程池组件.md)
* [Lock 锁组件 源码赏析](docs/JDK/concurrentCoding/Lock锁组件.md)
* [详解AbstractQueuedSynchronizer抽象类](docs/JDK/concurrentCoding/详解AbstractQueuedSynchronizer.md)
* [CountdownLatch类 源码赏析](docs/JDK/concurrentCoding/CountdownLatch.md)
* [CyclicBarrier类 源码赏析](docs/JDK/concurrentCoding/CyclicBarrier.md)
* [Semaphore类 源码赏析](docs/JDK/concurrentCoding/Semaphore.md)
- [JUC 并发包 UML 全量类图](docs/JDK/concurrentCoding/JUC并发包UML全量类图.md)
- [Executor 线程池组件 源码赏析](docs/JDK/concurrentCoding/Executor线程池组件.md)
- [Lock 锁组件 源码赏析](docs/JDK/concurrentCoding/Lock锁组件.md)
- [详解 AbstractQueuedSynchronizer 抽象类](docs/JDK/concurrentCoding/详解AbstractQueuedSynchronizer.md)
- [CountdownLatch 类 源码赏析](docs/JDK/concurrentCoding/CountdownLatch.md)
- [CyclicBarrier 类 源码赏析](docs/JDK/concurrentCoding/CyclicBarrier.md)
- [Semaphore 类 源码赏析](docs/JDK/concurrentCoding/Semaphore.md)
## 学习心得
### 个人经验
* [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md)
- [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md)
### 编码规范
* [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md)
- [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md)
### 设计模式
* [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md)
* [从 Spring 及 Mybatis 框架源码中学习设计模式(行为型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md)
* [从 Spring 及 Mybatis 框架源码中学习设计模式(结构型)](docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md)
- [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)](<docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md>)
- [从 Spring 及 Mybatis 框架源码中学习设计模式(行为型)](<docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型).md>)
- [从 Spring 及 Mybatis 框架源码中学习设计模式(结构型)](<docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型).md>)
### 多线程
* [Java并发编程在各主流框架中的应用](docs/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用.md)
- [Java 并发编程在各主流框架中的应用](docs/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用.md)
---
@ -297,15 +315,15 @@
GitHub 技术社区 [Doocs](https://github.com/doocs),致力于打造一个内容完整、持续成长的互联网开发者学习生态圈!以下是 Doocs 的一些优秀项目,欢迎各位开发者朋友持续保持关注。
| # | 项目名称 | 项目描述 |
|---|---|---|
| 1 | [advanced-java](https://github.com/doocs/advanced-java) | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 |
| 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版)》、《程序员面试金典(第 6 版)》题解。 |
| 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 |
| 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 |
| 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 |
| 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 |
| 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 |
| # | 项目名称 | 项目描述 |
| --- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| 1 | [advanced-java](https://github.com/doocs/advanced-java) | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 |
| 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版)》、《程序员面试金典(第 6 版)》题解。 |
| 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 |
| 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 |
| 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 |
| 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 |
| 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 |
## 贡献者
@ -317,5 +335,6 @@ GitHub 技术社区 [Doocs](https://github.com/doocs),致力于打造一个内
<!-- ALL-CONTRIBUTORS-LIST: END -->
## 请小码农喝杯coffee吧
![在这里插入图片描述](./images/appreciateCode.JPG)
## 请小码农喝杯 coffee 吧
![在这里插入图片描述](./images/appreciateCode.JPG)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,4 +1,5 @@
# GenericTokenParser
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git)
@ -120,9 +121,9 @@ public class GenericTokenParser {
```
- 一个具体的例子`org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler`
- 具体类`org.apache.ibatis.builder.SqlSourceBuilder`
- 具体类`org.apache.ibatis.builder.SqlSourceBuilder`
```java
/**
* ? 的来源
@ -137,6 +138,7 @@ public class GenericTokenParser {
}
```
```java
/**
* sql 参数类型 返回值
@ -173,4 +175,4 @@ public class GenericTokenParser {
```
![image-20191219100446796](../../../images/mybatis/image-20191219100446796.png)
![image-20191219100446796](../../../images/mybatis/image-20191219100446796.png)

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

@ -4,9 +4,9 @@
#### 1、流的概念和作用
**流**:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。<Thinking in Java>
**流的本质**:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
**流的作用**:为数据源和目的地建立一个输送通道。
**流**:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。&lt;Thinking in Java&gt;
**流的本质**:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
**流的作用**:为数据源和目的地建立一个输送通道。
Java 中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。
每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
@ -37,7 +37,7 @@ BIO 中的阻塞,就是阻塞在 2 个地方:
![avatar](../../../images/Netty/非阻塞IO模型.png)
每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮寻。但是,轮寻对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。
每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮询。但是,轮询对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。
Java 的 NIO 就是采用这种方式,当我们 new 了一个 socket 后我们可以设置它是非阻塞的。比如:
@ -52,7 +52,7 @@ serverSocketChannel.configureBlocking(false);
上面的代码是设置 ServerSocketChannel 为非阻塞SocketChannel 也可以设置。
从图中可以看到,当设置为非阻塞后,我们的 socket.read()方法就会立即得到一个返回结果(成功 or 失败),我们可以根据返回结果执行不同的逻辑,比如在失败时,我们可以做一些其他的事情。但事实上这种方式也是低效的,因为我们不得不使用轮询方法一直问 OS“我的数据好了没啊”。
从图中可以看到,当设置为非阻塞后,我们的 socket.read()方法就会立即得到一个返回结果(成功 or 失败),我们可以根据返回结果执行不同的逻辑,比如在失败时,我们可以做一些其他的事情。但事实上这种方式也是低效的,因为我们不得不使用轮询方法一直问 OS“我的数据好了没啊”。
**NIO 不会在 recvfrom询问数据是否准备好时阻塞但还是会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方Non-Blocking 还是会阻塞的。**
@ -60,7 +60,7 @@ serverSocketChannel.configureBlocking(false);
![avatar](../../../images/Netty/IO复用模型.png)
传统情况下 client 与 server 通信需要 3 个 socket(客户端的 socket服务端的 serversocket服务端中用来和客户端通信的 socket),而在 IO 多路复用中,客户端与服务端通信需要的不是 socket而是 3 个 channel通过 channel 可以完成与 socket 同样的操作channel 的底层还是使用的 socket 进行通信,但是多个 channel 只对应一个 socket(可能不只是一个,但是 socket 的数量一定少于 channel 数量),这样仅仅通过少量的 socket 就可以完成更多的连接,提高了 client 容量。
传统情况下 client 与 server 通信需要 3 个 socket(客户端的 socket服务端的 server socket服务端中用来和客户端通信的 socket),而在 IO 多路复用中,客户端与服务端通信需要的不是 socket而是 3 个 channel通过 channel 可以完成与 socket 同样的操作channel 的底层还是使用的 socket 进行通信,但是多个 channel 只对应一个 socket(可能不只是一个,但是 socket 的数量一定少于 channel 数量),这样仅仅通过少量的 socket 就可以完成更多的连接,提高了 client 容量。
其中,不同的操作系统,对此有不同的实现:
@ -447,6 +447,6 @@ JDK1.7 升级了 NIO 类库,升级后的 NIO 类库被称为 NIO 2.0。Java
异步通道获取获取操作结果方式:
1. 使用 java.util.concurrent.Future 类表示异步操作的结果;
2. 在执行异步操作的时候传入一个 java.nio.channels操作完成后回调 CompletionHandler 接口的实现类。
2. 在执行异步操作的时候传入一个 java.nio.channels操作完成后回调 CompletionHandler 接口的实现类。
NIO 2.0 的异步套接字通道是真正的异步非阻塞 I/O对应于 UNIX 网络编程中的事件驱动 I/OAIO

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

@ -1,33 +1,38 @@
## Netty 的线程模型
Netty线程模型 的设计,也是基于 Reactor模型尽管不同的 NIO框架 对于 Reactor模式 的实现存在差异,但本质上还是遵循了 Reactor 的基础线程模型。
Netty 线程模型 的设计,也是基于 Reactor 模型,尽管不同的 NIO 框架 对于 Reactor 模式 的实现存在差异,但本质上还是遵循了 Reactor 的基础线程模型。
#### Reactor 单线程模型
Reactor单线程模型是指所有的 I/O操作 都在同一个 NIO线程 上完成。NIO线程 的职责如下。
1. 作为 NIO服务端接收客户端的 TCP连接
2. 作为 NIO客户端向服务端发起 TCP连接
Reactor 单线程模型,是指所有的 I/O 操作 都在同一个 NIO 线程 上完成。NIO 线程 的职责如下。
1. 作为 NIO 服务端,接收客户端的 TCP 连接;
2. 作为 NIO 客户端,向服务端发起 TCP 连接;
3. 读取通信对端的请求或者应答消息;
4. 向通信对端发送消息请求或者应答消息。
理论上一个 NIO线程 可以独立处理所有 I/O操作。例如通过 Acceptor类 接收客户端的 TCP连接链路建立成功后通过 Dispatch 轮询事件就绪的 Channel将事件分发到指定的 Handler 上进行事件处理。小容量应用场景下,可以使用单线程模型。但对于高负载、大并发的应用场景并不合用。
理论上一个 NIO 线程 可以独立处理所有 I/O 操作。例如,通过 Acceptor 类 接收客户端的 TCP 连接,链路建立成功后,通过 Dispatch 轮询事件就绪的 Channel将事件分发到指定的 Handler 上进行事件处理。小容量应用场景下,可以使用单线程模型。但对于高负载、大并发的应用场景并不合用。
#### Reactor 多线程模型
Rector多线程模型 与 单线程模型 最大的区别就是有一组 NIO线程 来处理 I/O操作Reactor多线程模型 的特点如下。
1. 有专门一个 NIO线程 (Acceptor线程) 用于监听服务端,接收客户端的 TCP连接请求。
2. 网络IO操作 由一个 NIO线程池 负责,由这些 NIO线程 负责消息的 读取、解码、编码、发送。
3. 一个 NIO线程 可以同时处理 N条链路但是一个链路只对应一个 NIO线程防止发生并发操作问题。
Reactor多线程模型 可以满足大部分场景的需求。但对于 百万级超高并发 或 服务端需要对客户端进行安全认证,但认证非常消耗资源。在这类场景下,单独一个 Acceptor线程 可能会处理不过来,成为系统的性能瓶颈。
Rector 多线程模型 与 单线程模型 最大的区别就是有一组 NIO 线程 来处理 I/O 操作Reactor 多线程模型 的特点如下。
1. 有专门一个 NIO 线程 (Acceptor 线程) 用于监听服务端,接收客户端的 TCP 连接请求。
2. 网络 IO 操作 由一个 NIO 线程池 负责,由这些 NIO 线程 负责消息的 读取、解码、编码、发送。
3. 一个 NIO 线程 可以同时处理 N 条链路,但是一个链路只对应一个 NIO 线程,防止发生并发操作问题。
Reactor 多线程模型 可以满足大部分场景的需求。但对于 百万级超高并发 或 服务端需要对客户端进行安全认证,但认证非常消耗资源。在这类场景下,单独一个 Acceptor 线程 可能会处理不过来,成为系统的性能瓶颈。
#### Reactor 主从多线程模型
主从Reactor多线程模型的特点是服务端用于接收客户端连接的是一个独立的 NIO线程池。**Acceptor线程 与客户端建立 TCP连接 后,将新的 SocketChannel 注册到 NIO线程池 的某个 NIO线程 上,由该 NIO线程 负责轮询 SocketChannel 上的 IO事件并进行事件处理**。
主从 Reactor 多线程模型的特点是,服务端用于接收客户端连接的是一个独立的 NIO 线程池。**Acceptor 线程 与客户端建立 TCP 连接 后,将新的 SocketChannel 注册到 NIO 线程池 的某个 NIO 线程 上,由该 NIO 线程 负责轮询 SocketChannel 上的 IO 事件,并进行事件处理**。
利用 主从多线程模型,可以解决一个服务端监听线程无法有效处理所有客户端连接的性能不足问题。在 Netty 的官方 Demo 中,也是推荐使用该线程模型。
#### Netty 多线程编程最佳实践
1. **如果业务逻辑比较简单,并且没有 数据库操作、线程阻塞的磁盘操作、网路操作等,可以直接在 NIO线程 上完成业务逻辑编排,不需要切换到用户线程;**
2. **如果业务逻辑比较复杂,不要在 NIO线程 上完成,建议将解码后的 POJO消息 封装成 Task分发到 业务线程池 中由业务线程执行,以保证 NIO线程 尽快被释放处理其他的I/O操作。**
1. **如果业务逻辑比较简单,并且没有 数据库操作、线程阻塞的磁盘操作、网路操作等,可以直接在 NIO 线程 上完成业务逻辑编排,不需要切换到用户线程;**
2. **如果业务逻辑比较复杂,不要在 NIO 线程 上完成,建议将解码后的 POJO 消息 封装成 Task分发到 业务线程池 中由业务线程执行,以保证 NIO 线程 尽快被释放,处理其他的 I/O 操作。**
3. **由于用户场景不同,对于一些复杂系统,很难根据 理论公式 计算出最优线程配置,只能是 结合公式给出一个相对合理的范围,然后对范围内的数据进行性能测试,选择相对最优配置。**
## NioEventLoop 源码解析
@ -94,7 +99,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
}
}
}
/**
* 轮询 事件就绪的channel进行 IO事件处理
*/
@ -153,4 +158,4 @@ public final class NioEventLoop extends SingleThreadEventLoop {
}
}
}
```
```

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

@ -1,9 +1,11 @@
该文所涉及的netty源码版本为4.1.6。
该文所涉及的 netty 源码版本为 4.1.6。
## Netty中的ByteBuf为什么会发生内存泄漏
在Netty中ByetBuf并不是只采用可达性分析来对ByteBuf底层的byte[]数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。
在每个ByteBuf中都维护着一个refCnt用来对ByteBuf的被引用数进行记录当ByteBuf的retain()方法被调用时将会增加refCnt的计数而其release()方法被调用时将会减少其被引用数计数。
```Java
## Netty 中的 ByteBuf 为什么会发生内存泄漏
在 Netty 中ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 byte[]数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。
在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 retain()方法被调用时,将会增加 refCnt 的计数,而其 release()方法被调用时将会减少其被引用数计数。
```java
private boolean release0(int decrement) {
for (;;) {
int refCnt = this.refCnt;
@ -20,78 +22,88 @@ private boolean release0(int decrement) {
}
}
```
当调用了ByteBuf的release()方法的时候最后在上方的release0()方法中将会为ByteBuf的引用计数减一当引用计数归于0的时候将会调用deallocate()方法对其对应的底层存储数组进行释放(在池化的ByteBuf中在deallocate()方法里会把该ByteBuf的byte[]回收到底层内存池中以确保byte[]可以重复利用)。
由于Netty中的ByteBuf并不是随着申请之后会马上使其引用计数归0而进行释放往往在这两个操作之间还有许多操作如果在这其中如果发生异常抛出导致引用没有及时释放在使用池化ByetBuffer的情况下内存泄漏的问题就会产生。
当采用了池化的ByteBuffer的时候比如PooledHeapByteBuf和PooledDirectByteBuf其deallocate()方法一共主要分为两个步骤。
```Java
@Override
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, handle, maxLength);
recycle();
}
}
```
- 将其底层的byte[]通过free()方法回收到内存池中等待下一次使用。
- 通过recycle()方法将其本身回收到对象池中等待下一次使用。
关键在第一步的内存回收到池中如果其引用计数未能在ByteBuf对象被回收之前归0将会导致其底层占用byte[]无法回收到内存池PoolArena中导致该部分无法被重复利用下一次将会申请新的内存进行操作从而产生内存泄漏。
而非池化的ByteBuffer即使引用计数没有在对象被回收的时候被归0因为其使用的是单独一块byte[]内存因此也会随着java对象被回收使得底层byte[]被释放由JDK的Cleaner来保证
## Netty进行内存泄漏检测的原理
在Netty对于ByteBuf的检测中一共包含4个级别。
```Java
if (level.ordinal() < Level.PARANOID.ordinal()) {
if (leakCheckCnt ++ % samplingInterval == 0) {
reportLeak(level);
return new DefaultResourceLeak(obj);
} else {
return null;
}
}
当调用了 ByteBuf 的 release()方法的时候,最后在上方的 release0()方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 deallocate()方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 deallocate()方法里会把该 ByteBuf 的 byte[]回收到底层内存池中,以确保 byte[]可以重复利用)。
由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。
当采用了池化的 ByteBuffer 的时候,比如 PooledHeapByteBuf 和 PooledDirectByteBuf其 deallocate()方法一共主要分为两个步骤。
```java
@Override
protected final void deallocate() {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, handle, maxLength);
recycle();
}
}
```
以默认的SIMPLE级别为例在这个级别下Netty将会根据以ByteBuf创建的序列号与113进行取模来判断是否需要进行内存泄漏的检测追踪。当取模成功的时候将会为这个ByteBuf产生一个对应的DefaultResourceLeak对象DefaultResourceLeak是一个PhantomReference虚引用的子类并有其对应的ReferenceQueue。之后通过SimpleLeakAwareByteBuf类来将被追踪的ByteBuf和DefaultResourceLeak包装起来。
```Java
@Override
public boolean release(int decrement) {
boolean deallocated = super.release(decrement);
if (deallocated) {
leak.close();
}
return deallocated;
}
- 将其底层的 byte[]通过 free()方法回收到内存池中等待下一次使用。
- 通过 recycle()方法将其本身回收到对象池中等待下一次使用。
关键在第一步的内存回收到池中,如果其引用计数未能在 ByteBuf 对象被回收之前归 0将会导致其底层占用 byte[]无法回收到内存池 PoolArena 中,导致该部分无法被重复利用,下一次将会申请新的内存进行操作,从而产生内存泄漏。
而非池化的 ByteBuffer 即使引用计数没有在对象被回收的时候被归 0因为其使用的是单独一块 byte[]内存,因此也会随着 java 对象被回收使得底层 byte[]被释放(由 JDK 的 Cleaner 来保证)。
## Netty 进行内存泄漏检测的原理
在 Netty 对于 ByteBuf 的检测中,一共包含 4 个级别。
```java
if (level.ordinal() < Level.PARANOID.ordinal()) {
if (leakCheckCnt ++ % samplingInterval == 0) {
reportLeak(level);
return new DefaultResourceLeak(obj);
} else {
return null;
}
}
```
在包装类中如果该ByteBuf成功deallocated释放掉了其持有的byte[]数组将会调用DefaultResourceLeak的close()方法来已通知当前ByteBuf已经释放了其持有的内存。
正是这个虚引用使得该DefaultResourceLeak对象被回收的时候将会被放入到与这个虚引用所对应的ReferenceQueue中。
```Java
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
ref.clear();
以默认的 SIMPLE 级别为例在这个级别下Netty 将会根据以 ByteBuf 创建的序列号与 113 进行取模来判断是否需要进行内存泄漏的检测追踪。当取模成功的时候,将会为这个 ByteBuf 产生一个对应的 DefaultResourceLeak 对象DefaultResourceLeak 是一个 PhantomReference 虚引用的子类,并有其对应的 ReferenceQueue。之后通过 SimpleLeakAwareByteBuf 类来将被追踪的 ByteBuf 和 DefaultResourceLeak 包装起来。
if (!ref.close()) {
continue;
}
```java
@Override
public boolean release(int decrement) {
boolean deallocated = super.release(decrement);
if (deallocated) {
leak.close();
}
return deallocated;
}
```
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
if (records.isEmpty()) {
logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
"Enable advanced leak reporting to find out where the leak occurred. " +
"To enable advanced leak reporting, " +
"specify the JVM option '-D{}={}' or call {}.setLevel()",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
} else {
logger.error(
"LEAK: {}.release() was not called before it's garbage-collected.{}",
resourceType, records);
}
}
在包装类中,如果该 ByteBuf 成功 deallocated 释放掉了其持有的 byte[]数组将会调用 DefaultResourceLeak 的 close()方法来已通知当前 ByteBuf 已经释放了其持有的内存。
正是这个虚引用使得该 DefaultResourceLeak 对象被回收的时候将会被放入到与这个虚引用所对应的 ReferenceQueue 中。
```java
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
ref.clear();
if (!ref.close()) {
continue;
}
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
if (records.isEmpty()) {
logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
"Enable advanced leak reporting to find out where the leak occurred. " +
"To enable advanced leak reporting, " +
"specify the JVM option '-D{}={}' or call {}.setLevel()",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
} else {
logger.error(
"LEAK: {}.release() was not called before it's garbage-collected.{}",
resourceType, records);
}
}
```
Netty会在下一次ByteBuf的采样中通过reportLeak()方法将ReferenceQueue中的DefaultResourceLeak取出并判断其对应的ByteBuf是否已经在其回收前调用过其close()方法如果没有显然在池化ByteBuf的场景下内存泄漏已经产生将会以ERROR日志的方式进行日志打印。
以上内容可以结合JVM堆外内存的资料进行阅读。
Netty 会在下一次 ByteBuf 的采样中通过 reportLeak()方法将 ReferenceQueue 中的 DefaultResourceLeak 取出并判断其对应的 ByteBuf 是否已经在其回收前调用过其 close()方法,如果没有,显然在池化 ByteBuf 的场景下内存泄漏已经产生,将会以 ERROR 日志的方式进行日志打印。
以上内容可以结合 JVM 堆外内存的资料进行阅读。

@ -0,0 +1,477 @@
# 前言
前段时间在给自己的玩具项目设计的时候就遇到了一个场景需要定时任务,于是就趁机了解了目前主流的一些定时任务方案,比如下面这些:
- Timerhalo 博客源码中用到了)
- ScheduledExecutorService
- ThreadPoolTaskScheduler基于 ScheduledExecutorService
- Netty 的 schedule用到了 PriorityQueue
- Netty 的 HashedWheelTimer时间轮
- Kafka 的 TimingWheel层级时间轮
还有一些分布式的定时任务:
- Quartz
- xxl-job我实习公司就在用这个
- ...
因为我玩具项目实现业务 ACK 的方案就打算用 HashedWheelTimer所以本节核心是分析 HashedWheelTimer另外会提下它与 schedule 的区别,其它定时任务实现原理就请自动 Google 吧。
> Netty Version4.1.42
# HashedWheelTimer 实现图示
![HashedWheelTimer实现图示.png](../../../images/Netty/image_1595752125587.png)
大致有个理解就行,关于蓝色格子中的数字,其实就是剩余时钟轮数,这里听不懂也没关系,等后面看到源码解释就懂了~~(大概)~~。
# HashedWheelTimer 简答使用例子
这里顺便列出 schedule 的使用方式,下面是某个 Handler 中的代码:
```java
@Override
public void handlerAdded(final ChannelHandlerContext ctx) {
// 定时任务
ScheduledFuture<?> hello_world = ctx.executor().schedule(() -> {
ctx.channel().write("hello world");
}, 3, TimeUnit.SECONDS);
// 构造一个 Timer 实例,同样只执行一次
Timer timer = new HashedWheelTimer();
Timeout timeout1 = timer.newTimeout(timeout -> System.out.println("5s 后执行该任务"), 5, TimeUnit.SECONDS);
// 取消任务
timeout1.cancel();
}
```
# HashedWheelTimer 源码
### 继承关系、方法
![继承关系&方法.png](../../../images/Netty/image_1595751597062.png)
### 构造函数、属性
请记住这些属性的是干啥用的,后面会频繁遇到:
`io.netty.util.HashedWheelTimer#HashedWheelTimer(java.util.concurrent.ThreadFactory, long, java.util.concurrent.TimeUnit, int, boolean, long)`
```java
public HashedWheelTimer(
ThreadFactory threadFactory,
long tickDuration, TimeUnit unit, int ticksPerWheel, boolean leakDetection,
long maxPendingTimeouts) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
if (unit == null) {
throw new NullPointerException("unit");
}
if (tickDuration <= 0) {
throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
}
if (ticksPerWheel <= 0) {
throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
}
// 初始化时间轮数组长度必须是2的幂次方便于取模
// Normalize ticksPerWheel to power of two and initialize the wheel.
wheel = createWheel(ticksPerWheel);
// 用于取模运算, tick & mask
mask = wheel.length - 1;
// Convert tickDuration to nanos.
// 毫秒转纳秒
long duration = unit.toNanos(tickDuration);
// Prevent overflow.
// 防止溢出
if (duration >= Long.MAX_VALUE / wheel.length) {
throw new IllegalArgumentException(String.format(
"tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
tickDuration, Long.MAX_VALUE / wheel.length));
}
// 时间刻度设置太小自动设置为MILLISECOND_NANOS 并弹出警告日志
if (duration < MILLISECOND_NANOS) {
logger.warn("Configured tickDuration {} smaller then {}, using 1ms.",
tickDuration, MILLISECOND_NANOS);
this.tickDuration = MILLISECOND_NANOS;
} else {
this.tickDuration = duration;
}
// 初始化工作线程,注意这里还没有启动
// 另外需要注意的是本类中的startTime是在worker第一次启动之后才初始化的
// 跟io.netty.util.concurrent.ScheduledFutureTask.START_TIME在类加载的时候初始化是不一样的
workerThread = threadFactory.newThread(worker);
// 用来跟踪内存问题的,本节忽略,主讲定时任务的实现
leak = leakDetection || !workerThread.isDaemon() ? leakDetector.track(this) : null;
// 最大允许任务等待数
this.maxPendingTimeouts = maxPendingTimeouts;
// HashedWheelTimer实例如果超过64个就会弹出警告告诉你HashedWheelTimer不是这样用的单个应用只需要一个单例即可
if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
reportTooManyInstances();
}
}
```
### 添加定时任务
添加定时任务其实就是 Timer 接口的 newTimeOut 方法:
`io.netty.util.HashedWheelTimer#newTimeout`
```java
@Override
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
if (task == null) {
throw new NullPointerException("task");
}
if (unit == null) {
throw new NullPointerException("unit");
}
// 获取当前等待任务数
long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();
// 如果超出最大等待
if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
pendingTimeouts.decrementAndGet();
throw new RejectedExecutionException("Number of pending timeouts ("
+ pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
+ "timeouts (" + maxPendingTimeouts + ")");
}
// 尝试启动workerThreadstartTime=0时会startTimeInitialized.await()最终就是调用Worker的run方法
start();
// Add the timeout to the timeout queue which will be processed on the next tick.
// During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.
// 这条算式我们可以稍微改下,更容易理解些:
// long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
// ↓
// long deadline = unit.toNanos(delay) - (System.nanoTime() - startTime)
// 我感觉这样更容易理解些,含义为: 距离任务执行的剩余时间 = 任务截止时间 - (当前时间 - 任务对象初始化时间)
long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
// Guard against overflow.
if (delay > 0 && deadline < 0) {
deadline = Long.MAX_VALUE;
}
// 构建任务对象
HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
// 将任务对象添加到mpsc队列中mpsc是多生产者单消费者的队列模型另外mpscQueue是无锁队列靠的CAS实现的。
timeouts.add(timeout);
// 返回任务对象,该对象可以用于取消任务、获取任务信息等
return timeout;
}
```
这里我们再跟进 start 方法看看:
`io.netty.util.HashedWheelTimer#start`
```java
public void start() {
switch (WORKER_STATE_UPDATER.get(this)) {
case WORKER_STATE_INIT:
if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {
// 一半会来到这里最终就是调用到Worker的run方法
workerThread.start();
}
break;
case WORKER_STATE_STARTED:
break;
case WORKER_STATE_SHUTDOWN:
throw new IllegalStateException("cannot be started once stopped");
default:
throw new Error("Invalid WorkerState");
}
// Wait until the startTime is initialized by the worker.
while (startTime == 0) {
try {
// 如果startTime异常Worker的run方法会处理这种异常重新唤醒
startTimeInitialized.await();
} catch (InterruptedException ignore) {
// Ignore - it will be ready very soon.
}
}
}
```
### 定时任务执行
定时任务的执行逻辑其实就在 Worker 的 run 方法中:
`io.netty.util.HashedWheelTimer.Worker#run`
```java
// 用于处理取消的任务
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
// 时钟指针转动的次数
private long tick;
@Override
public void run() {
// Initialize the startTime.
startTime = System.nanoTime();
if (startTime == 0) {
// We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.
startTime = 1;
}
// Notify the other threads waiting for the initialization at start().
// 之前如果startTime=0就会进入await状态这里就要唤醒它
startTimeInitialized.countDown();
do {
/*
* 等待到下一次 tick 时如果没有时间延迟返回tickDuration * (tick + 1);
* 如果延迟了则不空转,立马返回“当前时间”
* 这个“当前时间”是什么呢?比如时钟指针原本第三次 tick 是在300ms但是由于前面的任务阻塞了50ms导致进来的时候已经是350ms了
* 那么这里的返回值就会变成350ms至于返回值变成350ms会怎么样貌似也没有怎么样就是不等待马上执行罢了
*/
final long deadline = waitForNextTick();
if (deadline > 0) {
// 与运算取模,取出数组桶的坐标,相信这个没少见过了
int idx = (int) (tick & mask);
// 前面说过HashedWheelTimeout是可以取消任务的其实就是在这里取消的
processCancelledTasks();
// 在时间轮中取出“指针指向的块”
HashedWheelBucket bucket =
wheel[idx];
// 将任务填充到时间块中
transferTimeoutsToBuckets();
// 取出任务并执行
bucket.expireTimeouts(deadline);
tick++;
}
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
// Fill the unprocessedTimeouts so we can return them from stop() method.
for (HashedWheelBucket bucket: wheel) {
bucket.clearTimeouts(unprocessedTimeouts);
}
for (;;) {
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
break;
}
if (!timeout.isCancelled()) {
unprocessedTimeouts.add(timeout);
}
}
// 处理取消的任务
processCancelledTasks();
}
```
- 取消任务的逻辑这里就不展开看了,也比较简单,有兴趣自行补充即可。
看看上面的 transferTimeoutsToBuckets 方法,如果你看不懂上面图中蓝色格子数字是什么意思,那就认真看看这个方法:
`io.netty.util.HashedWheelTimer.Worker#transferTimeoutsToBuckets`
```java
private void transferTimeoutsToBuckets() {
// transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
// adds new timeouts in a loop.
for (int i = 0; i < 100000; i++) {
// 取出一个任务对象
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
// all processed
break;
}
// 如果任务被取消了,则直接过掉
if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
// Was cancelled in the meantime.
continue;
}
/*
* remainingRounds的含义就是:时钟还要完整转几回才能执行到任务
* 比如你的任务是在2500ms之后才执行的deadline = 2500ms时钟总共10个刻度而 tickDuration 为100ms当前时钟指针已经拨动三次tick=3
* 那 2500 / 100 = 25
* (25 - 3) / 10 约等于 2
* 2 就表示 时钟转完当前圈25-10=15还要再转一圈15-10在第三圈才能执行到该任务
*/
long calculated = timeout.deadline / tickDuration;
timeout.remainingRounds = (calculated - tick) / wheel.length;
final long ticks = Math.max(calculated, tick); // Ensure we don't schedule for past.
int stopIndex = (int) (ticks & mask);
// 将任务填充到“时间块”中
HashedWheelBucket bucket = wheel[stopIndex];
bucket.addTimeout(timeout);
}
}
```
继续看看上面 run 方法中的 bucket.expireTimeouts(deadline);,这里面就是拿出任务并执行的逻辑:
`io.netty.util.HashedWheelTimer.HashedWheelBucket#expireTimeouts`
```java
/**
* Expire all {@link HashedWheelTimeout}s for the given {@code deadline}.
*/
public void expireTimeouts(long deadline) {
HashedWheelTimeout timeout = head;
// process all timeouts
while (timeout != null) {
HashedWheelTimeout next = timeout.next;
// 如果剩余轮数 <=0则表示当前轮就要执行任务了
if (timeout.remainingRounds <= 0) {
next = remove(timeout);
if (timeout.deadline <= deadline) {
// 执行任务
timeout.expire();
} else {
// The timeout was placed into a wrong slot. This should never happen.
throw new IllegalStateException(String.format(
"timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
}
}
// 如果任务被取消了
else if (timeout.isCancelled()) {
next = remove(timeout);
}
// 如果任务没被取消,而且剩余轮数>0则扣除轮数让任务继续等到至后面轮数
else {
timeout.remainingRounds --;
}
timeout = next;
}
}
```
# 和 schedule 对比
关于 schedule 方法加入的定时任务什么时候被执行,你可以参考我之前写的[这篇博客](https://wenjie.store/archives/netty-nioeventloop-boot-2),在时间操作上和 HashedWheelTimer 大同小异。
schedule 方法也是 Netty 的定时任务实现之一,但是底层的数据结构和 HashedWheelTimer 不一样schedule 方法用到的数据结构其实和 ScheduledExecutorService 类似,是 PriorityQueue它是一个优先级的队列。
除此之外schedule 方法其实也用到 MpscQueue只是任务执行的时候会把任务从 PriorityQueue 转移到 MpscQueue 上。
下面来跟踪下 schedule 方法看看,由于主要是看数据结构的区别,所以一些地方在这里我就不深追了
首先来到如下代码:
`io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)`
```java
@Override
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
ObjectUtil.checkNotNull(command, "command");
ObjectUtil.checkNotNull(unit, "unit");
if (delay < 0) {
delay = 0;
}
validateScheduled0(delay, unit);
return schedule(new ScheduledFutureTask<Void>(
this, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay))));
}
```
继续跟进 schedule 方法看看:
`io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(io.netty.util.concurrent.ScheduledFutureTask<V>)`
```java
private <V> ScheduledFuture<V> schedule(final ScheduledFutureTask<V> task) {
if (inEventLoop()) {
scheduledTaskQueue().add(task.setId(nextTaskId++));
} else {
executeScheduledRunnable(new Runnable() {
@Override
public void run() {
scheduledTaskQueue().add(task.setId(nextTaskId++));
}
}, true, task.deadlineNanos());
}
return task;
}
```
继续跟进 scheduledTaskQueue()方法:
`io.netty.util.concurrent.AbstractScheduledEventExecutor#scheduledTaskQueue`
```java
PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
if (scheduledTaskQueue == null) {
scheduledTaskQueue = new DefaultPriorityQueue<ScheduledFutureTask<?>>(
SCHEDULED_FUTURE_TASK_COMPARATOR,
// Use same initial capacity as java.util.PriorityQueue
11);
}
return scheduledTaskQueue;
}
```
可以看到返回值就是 PriorityQueue它是一个最小堆实现的优先队列。
# 扩展
### 不同实现的时间复杂度
这里我就直接贴下网上大佬给出的解释:
如果使用最小堆实现的优先级队列:
![最小堆.png](../../../images/Netty/image_1595756711656.png)
- 大致意思就是你的任务如果插入到堆顶,时间复杂度为 O(log(n))。
如果使用链表(既然有说道,那就扩展下):
![链表.png](../../../images/Netty/image_1595756928493.png)
- 中间插入后的事件复杂度为 O(n)
单个时间轮:
![单个时间轮.png](../../../images/Netty/image_1595757035360.png)
- 复杂度可以降至 O(1)。
记录轮数的时间轮(其实就是文章开头的那个):
![记录轮数的时间轮.png](../../../images/Netty/image_1595757110003.png)
层级时间轮:
![层级时间轮.png](../../../images/Netty/image_1595757328715.png)
- 时间复杂度是 O(n)n 是轮子的数量,除此之外还要计算一个轮子上的 bucket。
### 单时间轮缺点
根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 bucket 数就会变得非常多从而导致内存占用持续升高CPU 空转时间还是不变的,仅仅是内存需求变高了),如下图:
![image.png](../../../images/Netty/image_1595758329809.png)
Netty 对于单个时间轮的优化方式就是记录下 remainingRounds从而减少 bucket 过多的内存占用。
### 时间轮和 PriorityQueue 对比
看完上面的时间复杂度对比,你可能会觉得:
- Q时间轮的复杂度只有 O(1)schedule 和 ScheduledExecutorService 这种都是 O(log(n)),那时间轮不是碾压吗?
- A你不要忘了如果任务是在很久之后才执行的那么时间轮就会产生很多空转这是非常浪费 CPU 性能的,这种空转消耗可以通过增大 tickDuration 来避免,但这样做又会产生降低定时任务的精度,可能导致一些任务推到很迟才执行。
- A而 ScheduledExecutorService 不会有这个问题。
另外Netty 时间轮的实现模型抽象出来是大概这个样子的:
```java
for(Tasks task : tasks) {
task.doXxx();
}
```
这个抽象是个什么意思呢?你要注意一个点,这里的任务循环执行是同步的,**这意味着你第一个任务执行很慢延迟很高,那么后面的任务全都会被堵住**,所以你加进时间轮的任务不可以是耗时任务,比如一些延迟很高的数据库查询,如果有这种耗时任务,最好再嵌入线程池处理,不要让任务阻塞在这一层。
> 原文链接https://wenjie.store/archives/netty-hashedwheeltimer-and-schedule

@ -1,8 +1,10 @@
该文所涉及的 netty 源码版本为 4.1.6。
该文所涉及的 Netty 源码版本为 4.1.6。
## Netty 时间轮 HashedWheelTimer 是什么
## HashedWheelTimer 是什么
Netty 的时间轮 HashedWheelTimer 给出了一个粗略的定时器实现,之所以称之为粗略的实现是因为该时间轮并没有严格的准时执行定时任务,而是在每隔一个时间间隔之后的时间节点执行,并执行当前时间节点之前到期的定时任务。当然具体的定时任务的时间执行精度可以通过调节 HashedWheelTimer 构造方法的时间间隔的大小来进行调节,在大多数网络应用的情况下,由于 IO 延迟的存在,并不会严格要求具体的时间执行精度,所以默认的 100ms 时间间隔可以满足大多数的情况,不需要再花精力去调节该时间精度。
Netty 的时间轮 `HashedWheelTimer` 给出了一个**粗略的定时器实现**,之所以称之为粗略的实现是**因为该时间轮并没有严格的准时执行定时任务**,而是在每隔一个时间间隔之后的时间节点执行,并执行当前时间节点之前到期的定时任务。
当然具体的定时任务的时间执行精度可以通过调节 HashedWheelTimer 构造方法的时间间隔的大小来进行调节,在大多数网络应用的情况下,由于 IO 延迟的存在,并**不会严格要求具体的时间执行精度**,所以默认的 100ms 时间间隔可以满足大多数的情况,不需要再花精力去调节该时间精度。
## HashedWheelTimer 的实现原理
@ -12,14 +14,14 @@ Netty 的时间轮 HashedWheelTimer 给出了一个粗略的定时器实现,
private final HashedWheelBucket[] wheel;
```
HashedWheelTimer 的主体数据结构 wheel 是一个由多个链表所组成的数组,默认情况下该数组的大小为 512。当定时任务准备加入到时间轮中的时候将会以其等待执行的时间为依据选择该数组上的一个具体槽位上的链表加入。
HashedWheelTimer 的主体数据结构 wheel 是一个**由多个链表所组成的数组**,默认情况下该数组的大小为 512。当定时任务准备加入到时间轮中的时候将会以其等待执行的时间为依据选择该数组上的一个具体槽位上的链表加入。
```java
private HashedWheelTimeout head;
private HashedWheelTimeout tail;
```
在这个 wheel 数组中,每一个槽位都是一条由 HashedWheelTimeout 所组成的链表,其中链表中的每一个节点都是一个等待执行的定时任务。
在这个 wheel 数组中,每一个槽位都是一条由 HashedWheelTimeout 所组成的**链表**,其中链表中的**每一个节点都是一个等待执行的定时任务**
### HashedWheelTimer 内部的线程模型
@ -63,9 +65,9 @@ public void run() {
}
```
简单看到 HashedWheelTimer 内部的 woker 线程的 run()方法,在其首先会记录启动时间作为 startTime 作为接下来调度定时任务的时间依据,而之后会通过 CountDownLatch 来通知所有外部线程当前 worker 工作线程已经初始化完毕。之后的循环体便是当时间轮持续生效的时间里的具体调度逻辑。时间刻度是时间轮的一个重要属性,其默认为 100ms此处的循环间隔便是时间轮的时间刻度默认情况下就是间隔 100ms 进行一次调度循环。工作线程会维护当前工作线程具体循环了多少轮,用于定位具体执行触发时间轮数组上的哪一个位置上的链表。当时间轮准备 shutdown 的阶段,最后的代码会对未执行的任务整理到未执行的队列中。
简单看到 HashedWheelTimer 内部的 woker 线程的 `run()`方法,在其首先会记录启动时间作为 startTime 作为接下来调度定时任务的时间依据,而之后会通过 CountDownLatch 来通知所有外部线程当前 worker 工作线程已经初始化完毕。之后的循环体便是当时间轮持续生效的时间里的具体调度逻辑。**时间刻度是时间轮的一个重要属性**,其默认为 100ms此处的循环间隔便是时间轮的时间刻度默认情况下就是间隔 100ms 进行一次调度循环。工作线程会维护当前工作线程具体循环了多少轮,用于定位具体执行触发时间轮数组上的哪一个位置上的链表。当时间轮准备 shutdown 的阶段,最后的代码会对未执行的任务整理到未执行的队列中。
由此可见worker 线程的 run()方法中基本定义了工作线程的整个生命周期,从初始的初始化到循环体中的具体调度,最后到未执行任务的具体清理。整体的调度逻辑便主要在这里执行。值得注意的是,在这里的前提下,每个 HashedWheelTimer 时间轮都会有一个工作线程进行调度,所以不需要在 netty 中在每一个连接中单独使用一个 HashedWheelTimer 来进行定时任务的调度,否则可能将对性能产生影响。
由此可见,**worker 线程的 run()方法中基本定义了工作线程的整个生命周期,从初始的初始化到循环体中的具体调度,最后到未执行任务的具体清理**。整体的调度逻辑便主要在这里执行。值得注意的是,在这里的前提下,每个 HashedWheelTimer 时间轮都会有一个工作线程进行调度,所以不需要在 netty 中在每一个连接中单独使用一个 HashedWheelTimer 来进行定时任务的调度,否则可能将对性能产生影响。
### 向 HashedWheelTimer 加入一个定时任务的流程
@ -89,7 +91,7 @@ public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
}
```
当此次是首次向该时间轮加入定时任务的时候,将会通过 start()方法开始执行上文所述的 worker 工作线程的启动与循环调度逻辑,这里暂且不提。之后计算定时任务触发时间相对于时间轮初始化时间的相对时间间隔 deadline并将其包装为一个链表节点 HashedWheelTimeout ,投入到 timeouts 队列中,等待 worker 工作线程在下一轮调度循环中将其加入到时间轮的具体链表中等待触发执行timeouts 的实现是一个 mpsc 队列,关于 mpsc 队列可以查看[此文](https://github.com/doocs/source-code-hunter/blob/master/docs/Netty/Netty%E6%8A%80%E6%9C%AF%E7%BB%86%E8%8A%82%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/MpscLinkedQueue%E9%98%9F%E5%88%97%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md),这里也符合多生产者单消费者的队列模型
当此次是首次向该时间轮加入定时任务的时候,将会通过 start()方法开始执行上文所述的 worker 工作线程的启动与循环调度逻辑,这里暂且不提。之后计算定时任务触发时间相对于时间轮初始化时间的相对时间间隔 deadline并将其包装为一个链表节点 HashedWheelTimeout ,投入到 timeouts 队列中,等待 worker 工作线程在下一轮调度循环中将其加入到时间轮的具体链表中等待触发执行timeouts 的实现是一个 mpsc 队列,关于 mpsc 队列可以查看[此文](https://mp.weixin.qq.com/s/VVoDJwrLZrN3mm-jaQJayQ),这里也符合**多生产者单消费者的队列模型**
### HashedWheelTimer 中工作线程的具体调度
@ -108,9 +110,9 @@ do {
在 HashedWheelTimer 中的工作线程 run()方法的主要循环中,主要分为三个步骤。
首先 worker 线程会通过 waitForNextTick()方法根据时间轮的时间刻度等待一轮循环的开始,在默认情况下时间轮的时间刻度是 100ms那么此处 worker 线程也将在这个方法中 sleep 相应的时间等待下一轮循环的开始。此处也决定了时间轮的定时任务时间精度。
首先 worker 线程会通过 `waitForNextTick()`方法根据时间轮的时间刻度等待一轮循环的开始,在默认情况下时间轮的时间刻度是 100ms那么此处 worker 线程也将在这个方法中 sleep 相应的时间等待下一轮循环的开始。此处也决定了时间轮的定时任务时间精度。
当 worker 线程经过相应时间间隔的 sleep 之后,也代表新的一轮调度开始。此时,会通过 transferTimeoutsToBuckets()方法将之前刚刚加入到 timeouts 队列中的定时任务放入到时间轮具体槽位上的链表中。
当 worker 线程经过相应时间间隔的 sleep 之后,也代表新的一轮调度开始。此时,会通过 `transferTimeoutsToBuckets()`方法将之前刚刚加入到 timeouts 队列中的定时任务放入到时间轮具体槽位上的链表中。
```java
for (int i = 0; i < 100000; i++) {
@ -136,6 +138,12 @@ for (int i = 0; i < 100000; i++) {
}
```
首先,在每一轮的调度中,最多只会从 timeouts 队列中定位到时间轮 100000 个定时任务,这也是为了防止在这里耗时过久导致后面触发定时任务的延迟。在这里会不断从 timeouts 队列中获取刚加入的定时任务。具体的计算流程便是将定时任务相对于时间轮初始化时间的相对间隔与时间轮的时间刻度相除得到相对于初始化时间的具体轮数,之后便在减去当前轮数得到还需要遍历几遍整个时间轮数组得到 remainingRounds最后将轮数与时间轮数组长度-1 相与,得到该定时任务到底应该存放到时间轮上哪个位置的链表。用具体的数组举个例子,该时间轮初始化时间为 12 点,时间刻度为 1 小时,时间轮数组长度为 8当前时间 13 点,当向时间轮加入一个明天 13 点执行的任务的时候,首先得到该任务相对于初始化的时间间隔是 25 小时,也就是需要 25 轮调度,而当前 13 点,当前调度轮数为 1因此还需要 24 轮调度,就需要再遍历 3 轮时间轮,因此 remainingRounds 为 3再根据 25 与 8-1 相与的结果为 1因此将该定时任务放置到时间轮数组下标为 1 的链表上等待被触发。这便是一次完整的定时任务加入到时间轮具体位置的计算。
首先,在每一轮的调度中,最多只会从 `timeouts` 队列中定位到时间轮 100000 个定时任务,这也是为了防止在这里耗时过久导致后面触发定时任务的延迟。在这里会不断从 timeouts 队列中获取刚加入的定时任务。
**具体的计算流程**便是将定时任务相对于时间轮初始化时间的相对间隔与时间轮的时间刻度相除得到相对于初始化时间的具体轮数,之后便在减去当前轮数得到还需要遍历几遍整个时间轮数组得到 remainingRounds最后将轮数与时间轮数组长度-1 相与,得到该定时任务到底应该存放到时间轮上哪个位置的链表。
用具体的数组**举个例子**,该时间轮初始化时间为 12 点,时间刻度为 1 小时,时间轮数组长度为 8当前时间 13 点,当向时间轮加入一个明天 13 点执行的任务的时候,首先得到该任务相对于初始化的时间间隔是 25 小时,也就是需要 25 轮调度,而当前 13 点,当前调度轮数为 1因此还需要 24 轮调度,就需要再遍历 3 轮时间轮,因此 remainingRounds 为 3再根据 25 与 8-1 相与的结果为 1因此将该定时任务放置到时间轮数组下标为 1 的链表上等待被触发。
这便是**一次完整的定时任务加入到时间轮具体位置的计算**。
在 worker 线程的最后,就需要来具体执行定时任务了,首先通过当前循环轮数与时间轮数组长度-1 相与的结果定位具体触发时间轮数组上哪个位置上的链表,再通过 expireTimeouts()方法依次对链表上的定时任务进行触发执行。这里的流程就相对很简单,链表上的节点如果 remainingRounds 小于等于 0那么就可以直接执行这个定时任务如果 remainingRounds 大于 0那么显然还没有到达触发的时间点则将其-1 等待下一轮的调度之后再进行执行。在继续回到上面的例子,当 14 点来临之时,此时工作线程将进行第 2 轮的调度,将会把 2 与 8-1 进行相与得到结果 2那么当前工作线程就会选择时间轮数组下标为 2 的链表依次判断是否需要触发,如果 remainingRounds 为 0 将会直接触发,否则将会将 remainingRounds-1 等待下一轮的执行。
在 worker 线程的最后,就需要来具体执行定时任务了,首先通过当前循环轮数与时间轮数组长度-1 相与的结果定位具体触发时间轮数组上哪个位置上的链表,再通过 `expireTimeouts()`方法依次对链表上的定时任务进行触发执行。这里的流程就相对很简单,链表上的节点如果 remainingRounds 小于等于 0那么就可以直接执行这个定时任务如果 remainingRounds 大于 0那么显然还没有到达触发的时间点则将其-1 等待下一轮的调度之后再进行执行。在继续回到上面的例子,当 14 点来临之时,此时工作线程将进行第 2 轮的调度,将会把 2 与 8-1 进行相与得到结果 2那么当前工作线程就会选择时间轮数组下标为 2 的链表依次判断是否需要触发,如果 remainingRounds 为 0 将会直接触发,否则将会将 remainingRounds-1 等待下一轮的执行。

@ -0,0 +1,114 @@
该文所涉及的 netty 源码版本为 4.1.16。
## 在一开始需要明确的几个概念
在 Netty 的内存池的 PoolChunk 中,先要明确以下几个概念。
- page: page 是 chunk 中所能申请到的最小内存单位。
- chunk: 一个 chunk 是一组 page 的集合
- 在 PoolChunk 中chunkSize 的大小是 `2^maxOrder * pageSize`,其中 2^maxOrder 是 PoolChunk 中的完全二叉树叶子结点的数量pageSize 则是单个 page 的大小。
综合如上所述,举一个数字上的例子,默认情况下,单个 Page 的大小为 8192也就是 8kbmaxOrder 默认情况下是 11因此在这个情况下 PoolChunk 中的二叉树的叶子节点数量是 2048chunkSize 的大小则是 2048\*8kb 为 16M。
## PoolChunk 的内部完全二叉树结构
PoolChunk 中的 page 通过一颗完全二叉树来达到快速访达及操作,而不需要通过 O(n)的时间复杂度来进行遍历,并耗费相当大的空间来记录各个 page 的使用情况。一颗完全二叉树的结构如下所示:
- 高度=0 1 个节点 (单个节点表示的大小为 chunkSize)
- 高度=1 2 个节点 (单个节点表示的大小为 chunkSize/2)
- ..
- ..
- 高度=d 2^d 个节点 (单个节点表示的大小为 chunkSize/2^d)
- ..
- 高度=maxOrder 2^maxOrder 个节点 (单个节点的大小为 chunkSize/2^maxOrder也就是 pageSize)
在这棵树的帮助下,当我们要申请 x 大小的内存的时候 ,得到比 x 最接近的 chunkSize/2^k 的大小,也就是说只要从左开始找到 k 层第一个没有被使用的节点即可开始将其子树的叶子结点的 page 进行分配。
## PoolChunk 的二叉树使用状态
单依靠上述的完全二叉树是无法达到内存池设计的目的的,因为缺少了 page 的使用情况,仍旧需要一个数据结构来辅助记录各个节点的使用情况。
PoolChunk 中还给出了一个 byte 数组 memoryMap大小为完全二叉树所有节点的个数在之前的例子中这个 byte 数组就为 4096。在初始情况下这个数组每个位置上的初始指为该位置的节点在完全二叉树中的高度。因此这个数组 memoryMap 就有了以下几种状态。
- 1. memoryMap[i] = i 节点在完全二叉树中的深度,代表当前节点下的子树都还没有被分配。
- 2. memoryMap[i] > i 节点在完全二叉树中的深度, 这个节点下的子树也就有节点被使用,但是仍有节点处于空闲状态。
- 3. memoryMap[i] = maxOrder + 1这个节点下面的子树已经完全被使用。
这个 Byte 数组,就相当于为这个完全二叉树准备了状态与索引存储,可以高效的在二叉树中选择定位所需要指定大小的子树进行分配。
## 业务逻辑展开
```java
private int allocateNode(int d) {
int id = 1;
int initial = - (1 << d); // has last d bits = 0 and rest all = 1
byte val = value(id);
if (val > d) { // unusable
return -1;
}
while (val < d || (id & initial) == 0) { // id & initial == 1 << d for all ids at depth d, for < d it is 0
id <<= 1;
val = value(id);
if (val > d) {
id ^= 1;
val = value(id);
}
}
byte value = value(id);
assert value == d && (id & initial) == 1 << d : String.format("val = %d, id & initial = %d, d = %d",
value, id & initial, d);
setValue(id, unusable); // mark as unusable
updateParentsAlloc(id);
return id;
}
```
allocateNode(int d)方法用来在完全二叉树中以从左开始的顺序获取一颗高度为 d 的没有被使用过的子树。具体顺序如下:
- 首先从根节点 1 开始,判断 memoryMap[1]的值,如果大于 d则说明当前的二叉树已经不存在能够分配的节点了。如果小于 d则可以继续往下分配。
- 如果其左节点在 memoryMap 的值小于 d则继续从左节点往下寻找。如果大于则从其右节点开始往下寻找。
- 在下一层的节点中持续进行上述的判断,直到在书中找到符合高度条件的子树。
```java
private long allocateRun(int normCapacity) {
int d = maxOrder - (log2(normCapacity) - pageShifts);
int id = allocateNode(d);
if (id < 0) {
return id;
}
freeBytes -= runLength(id);
return id;
}
```
allocateRun()方法就是在上文的 allocateNode()的前提下,根据指定的大小的内存在二叉树上分配指定大小的子树。比如说在上述 16M 大小每个 page8kb 的 chunk 中寻求 64k 的内存的时候,需要 8 个 page 叶子结点,那么就是需要一个高度为 4 的完全二叉树,那么也就是只要在 PoolChunk 中通过 allocateNode()方法从完全二叉树的第 7 层开始从左往右找到一颗可以使用的子树即可。
```java
private long allocateSubpage(int normCapacity) {
// Obtain the head of the PoolSubPage pool that is owned by the PoolArena and synchronize on it.
// This is need as we may add it back and so alter the linked-list structure.
PoolSubpage<T> head = arena.findSubpagePoolHead(normCapacity);
synchronized (head) {
int d = maxOrder; // subpages are only be allocated from pages i.e., leaves
int id = allocateNode(d);
if (id < 0) {
return id;
}
final PoolSubpage<T>[] subpages = this.subpages;
final int pageSize = this.pageSize;
freeBytes -= pageSize;
int subpageIdx = subpageIdx(id);
PoolSubpage<T> subpage = subpages[subpageIdx];
if (subpage == null) {
subpage = new PoolSubpage<T>(head, this, id, runOffset(id), pageSize, normCapacity);
subpages[subpageIdx] = subpage;
} else {
subpage.init(head, normCapacity);
}
return subpage.allocate();
}
}
```
当向 PoolChunk 申请的内存大小小于 pageSize 的时候,将直接通过 allocateSubpage()方法尝试直接在叶子结点,也就是二叉树的最后一层选择一个空的还未使用的叶子结点,在选择的叶子结点中构造一个 PoolSubPage 来返回,而不需要耗费整整一个叶子结点导致内存占用浪费。

@ -0,0 +1,24 @@
该文所涉及的 netty 源码版本为 4.1.16。
## Netty 内存池申请内存流程
在通过 PooledByteBufAllocator 中向内存池中进行内存申请的时候,最先开始的步骤便是从 PooledByteBufAllocator 中一系列 PoolArena 数组中,选择其中一个 PoolArena 进行分配。
这时将会从 PoolArena 数组中选取当前使用量最小的 PoolArena 与当前线程通过 ThreadLocal 进行绑定,之后涉及到内存申请将会直接从这个 PoolArena 进行获取,这个做法在高并发情况下频繁往内存池中进行内存申请的时候可以减少资源竞争,提升效率。
在当前线程获取与其绑定的 PoolArena 之后,接下来就是从 PoolArena 中继续申请内存。
为了适应各种大小的内存场景PoolArena 的组成也是为了其设计。
- PoolSubpage 数组 tinySubpagePools默认情况下当申请的内存小于 512b 的时候的时候将会从 tinySubpagePools 中直接选择 subPage内存池中的最小单位返回
- PoolSubpage 数组 smallSubpagePools默认情况下当申请的内存大于 512b 但是小于一个 page 的大小8kb的时候将会从 smallSubpagePools 返回一个 subPage。subPage 是由 poolChunk 中的 page 分配而来。
- PoolChunkList<T> qInit存储内存利用率 0-25%的 poolChunk
- PoolChunkList<T> q000存储内存利用率 1-50%的 poolChunk
- PoolChunkList<T> q025存储内存利用率 25-75%的 poolChunk
- PoolChunkList<T> q050存储内存利用率 50-100%的 poolChunk
- PoolChunkList<T> q075存储内存利用率 75-100%的 poolChunk
- PoolChunkList<T> q100存储内存利用率 100%的 poolChunk、
当申请的内存大于一个 page8kb但又小于一个 poolChunk2048kb总大小的时候将会从各个 PoolChunkList 中尝试获取一个 poolChunk 从中返回。PoolChunkList 是一个由 poolChunk 组成的链表。
以上几个 PoolChunkList由符合各个内存利用率的 poolChunk 组成,这几个 PoolChunkList 之间又互相首尾连接组成队列,方便 PoolChunk 在各个队列中根据自己当前的利用率进行转移到对应的位置上。
最后,当申请的内存大于一个 poolChunk 大小的时候将会直接申请一段非池化的内存返回,并不会占用内存池中的内存空间。
最后,到了从 poolChunk 中申请内存的场景,这一部分在[该文](https://github.com/doocs/source-code-hunter/blob/main/docs/Netty/Netty技术细节源码分析/内存池之PoolChunk设计与实现.md)中已经详细说明,这部分也是内存池中获取内存的最后一步。

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

@ -0,0 +1,35 @@
## 获取时间窗口的主要流程
在 Sentinel 中,主要是通过 LeapArray 类来实现滑动时间窗口的实现和选择。在 sentinel 的这个获取时间窗口并为时间窗口添加指标的过程中,主要的流程为:
- 根据当前时间选择当前时间应该定位当前时间应该属于的时间窗口 id。
- 根据时间窗口 id 获取时间窗口。这里可能会存在三种情况:
1. 时间窗口还未建立,那么将会为此次流量的进入建立一个新的时间窗口返回,并且接下来这个时间窗口内的获取请求都将返回该窗口。
2. 时间窗口已经建立的情况下,将会直接获取已经存在的符合条件的时间窗口。
3. 时间窗口可能已经存在,但是当前获取的时间窗口已经过期,需要加锁,并重置当前时间窗口。
4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。
其中的第四个情况表明sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。
```Java
protected final AtomicReferenceArray<WindowWrap<T>> array;
```
在 LeapArray 中,时间窗口的存放通过一个由 AtomicReferenceArray 实现的 array 来实现。AtomicReferenceArray 支持原子读取和写入,并支持通过 cas 来为指定位置的成员进行更新。在时间窗口的创建并放回 array 的过程中,也就是上文的第一步,就是通过 AtomicReferenceArray 的 compareAndSet()方法来实现,保证并发下的线程安全。并发情况下,通过 cas 更新失败的线程将会回到就绪态,在下一次婚欢得到已经初始化完成的时间窗口。
```Java
private final ReentrantLock updateLock = new ReentrantLock();
```
此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 yield()方法回到就绪态在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。
以上两个数据结构是 LeapArray 类实现时间窗口在高并发下准确获取时间窗口并更新的关键。
## 以秒级别的时间窗口举个例子
在 sentinel 默认的秒级别时间窗口中array 的大小为 2也就是每 500ms 为一个时间窗口的大小。
因此当一个线程试图获取一个时间窗口来记录指标数据的时候,将会根据单个时间窗口的时间跨度进行取模,来得到 array 上对应的时间窗口的下标,在这个情况下,将为 0 或者 1之后计算当前线程时间指标所属的时间窗口的起始时间以此为依据来判断如果在后面如果获取到的时间窗口是过期还是正好所需要的。
最后,将会不断循环从 array 尝试获取之前计算得到下标位置处的时间窗口,可能发生的 4 种情况如上所示。在这个情况,如果 cas 失败或事没有尝试获取到更新锁,都不会阻塞或是挂起,而是通过 yield 重新回到就绪态等待下一次循环获取。
## 时间窗口本身的线程安全指标更新
在指标集合类的实现 MetricBucket 中,通过 LongAdder 类来记录单个指标的值而不是 AtomicLongLongAdder 内部的核心思路是为各个线程分配一个专属变量进行更新,在需要总数的时候对这一系列进行累加,因此在更新值的时候相比 AtomicLong 会尽可能减少线程间的竞争,达到高效的 metric 更新。

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

@ -1,9 +1,10 @@
# Spring PlaceholderResolver
- 类全路径: `org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver`
- 类作用将占位符中的内容替换成属性值.
- 假设现有属性表: user.dir = c:\home
传入参数 user.dir 会获得 c:\home
- 假设现有属性表: user.dir = c:\home
传入参数 user.dir 会获得 c:\home
```java

@ -2,7 +2,6 @@
- 类全路径: `org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.PropertyPlaceholderConfigurerResolver`
- 这个类是从 Properties 中获取属性
```java
@ -45,7 +44,6 @@
```
```java
@Nullable
protected String resolvePlaceholder(String placeholder, Properties props) {
@ -53,7 +51,6 @@
}
```
```java
@Nullable
protected String resolveSystemProperty(String key) {
@ -72,4 +69,4 @@
}
}
```
```

@ -1,6 +1,5 @@
# Spring ServletContextPlaceholderResolver
- 类全路径: `org.springframework.web.util.ServletContextPropertyUtils.ServletContextPlaceholderResolver`
```java
@ -40,4 +39,4 @@
}
}
```
```

@ -1,8 +1,7 @@
# Spring SystemPropertyPlaceholderResolver
# Spring SystemPropertyPlaceholderResolver
- 类全路径: `org.springframework.util.SystemPropertyUtils.SystemPropertyPlaceholderResolver`
```java
private static class SystemPropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver {
@ -32,4 +31,4 @@
}
}
```
```

@ -3,14 +3,9 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 类全路径: `org.springframework.core.env.CommandLinePropertySource`
- 作用: 用来存储命令行参数
```java
public abstract class CommandLinePropertySource<T> extends EnumerablePropertySource<T> {
@ -93,10 +88,6 @@ public abstract class CommandLinePropertySource<T> extends EnumerablePropertySou
}
```
## getOptionValues
```java
@ -120,8 +111,6 @@ public abstract class CommandLinePropertySource<T> extends EnumerablePropertySou
protected abstract List<String> getOptionValues(String name);
```
阅读注释可以知道该方法可以获取命令行参数的列表.
阅读注释可以知道该方法可以获取命令行参数的列表.
- 如 `--foo`作为开头当输入命令行为 `--foo=bar --foo=baz` 在输入参数名称 `foo` 会得到数据`bar,baz`
- 如 `--foo`作为开头当输入命令行为 `--foo=bar --foo=baz` 在输入参数名称 `foo` 会得到数据`bar,baz`

@ -3,12 +3,11 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 整体代码如下.
- 下面几个调用方法会直接抛出异常
1. getSource
1. containsProperty
1. getProperty
- 下面几个调用方法会直接抛出异常
1. getSource
1. containsProperty
1. getProperty
```java
static class ComparisonPropertySource extends StubPropertySource {
@ -41,4 +40,4 @@
}
}
```
```

@ -1,11 +1,12 @@
# Spring CompositePropertySource
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 全路径: `org.springframework.core.env.CompositePropertySource`
- 整体代码如下
```java
public class CompositePropertySource extends EnumerablePropertySource<Object> {
@ -102,4 +103,4 @@ public class CompositePropertySource extends EnumerablePropertySource<Object> {
}
}
```
```

@ -9,7 +9,9 @@
```java
public abstract String[] getPropertyNames();
```
- 整体代码如下
```java
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
@ -27,7 +29,7 @@ public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
* <p>This implementation checks for the presence of the given name within the
* {@link #getPropertyNames()} array.
*
* 在属性列表中是否存在 properties
* 在属性列表中是否存在 properties
* @param name the name of the property to find
*/
@Override
@ -43,4 +45,4 @@ public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public abstract String[] getPropertyNames();
}
```
```

@ -3,11 +3,9 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 类全路径: `org.springframework.core.env.MapPropertySource`
- 内部数据结构是一个`Map<String,Object>`
这是一个对map的操作.
这是一个对 map 的操作.
- 整体代码如下.
```java
@ -40,4 +38,4 @@ public class MapPropertySource extends EnumerablePropertySource<Map<String, Obje
}
```
```

@ -1,19 +1,13 @@
# Spring MockPropertySource
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 内部 source 是 Properties 类型
## withProperty
- 设置属性名称和属性值
- 设置属性名称和属性值
```java
public MockPropertySource withProperty(String name, Object value) {
@ -22,10 +16,6 @@ public MockPropertySource withProperty(String name, Object value) {
}
```
## setProperty
```java
@ -34,14 +24,8 @@ public void setProperty(String name, Object value) {
}
```
## 完整代码
```java
public class MockPropertySource extends PropertiesPropertySource {
@ -109,4 +93,4 @@ public class MockPropertySource extends PropertiesPropertySource {
}
}
```
```

@ -1,14 +1,12 @@
# Spring PropertiesPropertySource
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 全路径: `org.springframework.core.env.PropertiesPropertySource`
- Properties 是map结构。可以做类型转换.
- getPropertyNames 就转换成了父类MapPropertySource的方法了
- Properties 是 map 结构。可以做类型转换.
- getPropertyNames 就转换成了父类 MapPropertySource 的方法了
- map.keySet()
```java
@ -32,4 +30,4 @@ public class PropertiesPropertySource extends MapPropertySource {
}
}
```
```

@ -1,17 +1,11 @@
# Spring ResourcePropertySource
# Spring ResourcePropertySource
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 全路径: `org.springframework.core.io.support.ResourcePropertySource`
- source 依然是map结构
- source 依然是 map 结构
## getNameForResource
@ -27,10 +21,6 @@ private static String getNameForResource(Resource resource) {
}
```
## withName
- 创建 ResourcePropertySource 对象, 根据 name 属性
@ -56,10 +46,6 @@ public ResourcePropertySource withName(String name) {
}
```
## 构造函数
- 通过 location 字符串读取 resource
@ -71,8 +57,6 @@ public ResourcePropertySource(String name, String location, ClassLoader classLoa
}
```
- 读取 resource 信息进行存储
```java
@ -85,8 +69,6 @@ public ResourcePropertySource(String name, EncodedResource resource) throws IOEx
}
```
## 完整代码
```java
@ -238,4 +220,4 @@ public class ResourcePropertySource extends PropertiesPropertySource {
}
}
```
```

@ -3,10 +3,8 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 类全路径: `org.springframework.web.context.support.ServletConfigPropertySource`
- 内部数据结构是 `ServletConfig`
- 整体代码如下
@ -33,4 +31,4 @@ public class ServletConfigPropertySource extends EnumerablePropertySource<Servle
}
```
```

@ -1,16 +1,12 @@
# Spring ServletContextPropertySource
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 类全路径: `org.springframework.web.context.support.ServletContextPropertySource`
- 内部数据结构是 ServletContext 接口
- 整体代码如下.
```java
public class ServletContextPropertySource extends EnumerablePropertySource<ServletContext> {
@ -34,4 +30,4 @@ public class ServletContextPropertySource extends EnumerablePropertySource<Servl
}
```
```

@ -1,15 +1,13 @@
# Spring SimpleCommandLineArgsParser
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 类全路径: `org.springframework.core.env.SimpleCommandLineArgsParser
- 类作用: 将命令行参数解析成 `org.springframework.core.env.CommandLineArgs`
- 完整代码如下.
- 完整代码如下.
```java
class SimpleCommandLineArgsParser {
@ -51,5 +49,5 @@ class SimpleCommandLineArgsParser {
```
- 处理流程
1. 循环命令行参数列表
2. 去掉 `--``=` 获取参数名称和参数值放入结果集合
1. 循环命令行参数列表
2. 去掉 `--``=` 获取参数名称和参数值放入结果集合

@ -2,19 +2,11 @@
- 全路径: `org.springframework.core.env.SimpleCommandLinePropertySource`
```java
public class SimpleCommandLinePropertySource extends CommandLinePropertySource<CommandLineArgs> {}
```
- SimpleCommandLinePropertySource 的source 类型是 CommandLineArgs 具体解释请看下面分析
- SimpleCommandLinePropertySource 的 source 类型是 CommandLineArgs 具体解释请看下面分析
## CommandLineArgs
@ -37,7 +29,7 @@ class CommandLineArgs {
### addOptionArg
添加 选项参数
添加 选项参数
```java
public void addOptionArg(String optionName, @Nullable String optionValue) {
@ -50,8 +42,6 @@ public void addOptionArg(String optionName, @Nullable String optionValue) {
}
```
### getOptionNames
- 获取选项参数列表
@ -62,14 +52,8 @@ public Set<String> getOptionNames() {
}
```
- 其他方法不具体描述了,各位可以查看下面的代码
```java
class CommandLineArgs {
@ -142,13 +126,7 @@ class CommandLineArgs {
}
```
在了解 CommandLineArgs 类后再来看 SimpleCommandLinePropertySource 会相对容易. 内部的几个方法就是调用 CommandLineArgs 所提供的方法
在了解 CommandLineArgs 类后再来看 SimpleCommandLinePropertySource 会相对容易. 内部的几个方法就是调用 CommandLineArgs 所提供的方法
```java
@Override
@ -172,4 +150,3 @@ protected List<String> getNonOptionArgs() {
return this.source.getNonOptionArgs();
}
```

@ -3,10 +3,8 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 整体代码如下.
- 通过 StubPropertySource 的 getProperty 方法永远返回null
- 整体代码如下.
- 通过 StubPropertySource 的 getProperty 方法永远返回 null
```java
public static class StubPropertySource extends PropertySource<Object> {
@ -25,4 +23,4 @@
}
}
```
```

@ -1,18 +1,17 @@
# Spring BeanDefinitionParserDelegate
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 全路径`org.springframework.beans.factory.xml.BeanDefinitionParserDelegate`
- 解析 xml 中标签的委托类
- 在这个类中定义常量如下,为后续解析提供帮助
```java
public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans";
public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";
public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";
public static final String TRUE_VALUE = "true";
@ -147,18 +146,10 @@
private static final String SINGLETON_ATTRIBUTE = "singleton";
```
## populateDefaults
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#populateDefaults`方法解析属性赋值给`DocumentDefaultsDefinition`对象
- 代码逻辑如下
1. 读取属性
2. 判断是否默认值
@ -215,8 +206,6 @@ protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable D
}
```
### DocumentDefaultsDefinition
- 全路径:`org.springframework.beans.factory.xml.DocumentDefaultsDefinition`
@ -266,12 +255,6 @@ public class DocumentDefaultsDefinition implements DefaultsDefinition {
}
```
## checkNameUniqueness
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#checkNameUniqueness`
@ -309,17 +292,13 @@ protected void checkNameUniqueness(String beanName, List<String> aliases, Elemen
}
```
## createBeanDefinition
- `org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition`
- 创建具有基本信息的**BeanDefinition**
1. parent bean name
1. parent bean name
2. bean clsss
3. bean class name
3. bean class name
```java
public static AbstractBeanDefinition createBeanDefinition(
@ -343,29 +322,15 @@ public static AbstractBeanDefinition createBeanDefinition(
}
```
## parseBeanDefinitionElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)`
- 该方法用来解析 `<bean/>` 标签信息
##
##
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)`
```java
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
@ -432,12 +397,8 @@ public AbstractBeanDefinition parseBeanDefinitionElement(
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionAttributes`
- 将 xml 标签的数据读取到内存中设置给`AbstractBeanDefinition`
```JAVA
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
@ -539,17 +500,11 @@ public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String
}
```
### parseMetaElements
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseMetaElements`
- 设置元数据.
- 设置元数据.
标签`meta`的解析
@ -578,8 +533,6 @@ public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attribu
}
```
使用案例
```xml
@ -588,8 +541,6 @@ public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attribu
</bean>
```
```java
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/beans/spring-lookup-method.xml");
@ -599,8 +550,6 @@ Object attribute = apple.getAttribute("meta-key");
System.out.println(attribute);
```
### parseLookupOverrideSubElements
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseLookupOverrideSubElements`
@ -609,8 +558,6 @@ System.out.println(attribute);
`lookup-method`
```java
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
// 获取子标签
@ -634,8 +581,6 @@ public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides over
}
```
使用案例
```xml
@ -659,10 +604,6 @@ public class LookupMain {
}
```
### parseReplacedMethodSubElements
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseReplacedMethodSubElements`
@ -710,12 +651,8 @@ public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides over
}
```
- 使用案例
```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -735,8 +672,6 @@ public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides over
</beans>
```
```java
public class MethodReplacerApple implements MethodReplacer {
@Override
@ -747,13 +682,7 @@ public class MethodReplacerApple implements MethodReplacer {
}
```
**replacer需要使用MethodReplacer实现类**
**replacer 需要使用 MethodReplacer 实现类**
### parseConstructorArgElements
@ -761,8 +690,6 @@ public class MethodReplacerApple implements MethodReplacer {
- 解析`constructor-arg`标签
```
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
// 获取
@ -777,10 +704,6 @@ public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
}
```
```java
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
// 获取 index 属性
@ -857,8 +780,6 @@ public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
}
```
### parseConstructorArgElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElement`
@ -928,10 +849,6 @@ public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable Strin
}
```
### parsePropertySubElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)`
@ -944,10 +861,6 @@ public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd)
}
```
### parsePropertySubElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition, java.lang.String)`
@ -1028,13 +941,9 @@ public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd,
}
```
#### parseIdRefElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseIdRefElement`
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseIdRefElement`
```java
@Nullable
@ -1058,14 +967,10 @@ public Object parseIdRefElement(Element ele) {
}
```
#### parseValueElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseValueElement`
```JAVA
public Object parseValueElement(Element ele, @Nullable String defaultTypeName) {
// It's a literal value.
@ -1093,10 +998,6 @@ public Object parseIdRefElement(Element ele) {
```
##### buildTypedStringValue
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#buildTypedStringValue`
@ -1105,7 +1006,7 @@ public Object parseIdRefElement(Element ele) {
```java
protected TypedStringValue buildTypedStringValue(String value, @Nullable String targetTypeName)
throws ClassNotFoundException {
// class loader
// class loader
ClassLoader classLoader = this.readerContext.getBeanClassLoader();
TypedStringValue typedValue;
if (!StringUtils.hasText(targetTypeName)) {
@ -1144,8 +1045,6 @@ public Object parseArrayElement(Element arrayEle, @Nullable BeanDefinition bd) {
}
```
#### parseListElement
```java
@ -1176,10 +1075,6 @@ public Set<Object> parseSetElement(Element collectionEle, @Nullable BeanDefiniti
}
```
##### parseCollectionElements
- `parseArrayElement`、`parseListElement`、`parseSetElement` 都围绕者下面这个方法进行数据合并
@ -1198,8 +1093,6 @@ protected void parseCollectionElements(
}
```
#### parseMapElement
```java
@ -1342,20 +1235,8 @@ public Map<Object, Object> parseMapElement(Element mapEle, @Nullable BeanDefinit
}
```
#### parsePropsElement
### parsePropertyElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyElement`
@ -1389,12 +1270,6 @@ public void parsePropertyElement(Element ele, BeanDefinition bd) {
}
```
### parseQualifierElements
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseQualifierElements`
@ -1413,10 +1288,6 @@ public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) {
}
```
### parseQualifierElement
- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseQualifierElement`
@ -1470,4 +1341,4 @@ public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) {
this.parseState.pop();
}
}
```
```

@ -1,13 +1,11 @@
# Spring BeanDefinitionReaderUtils
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
## createBeanDefinition
- `org.springframework.beans.factory.support.BeanDefinitionReaderUtils.createBeanDefinition`
- `org.springframework.beans.factory.support.BeanDefinitionReaderUtils.createBeanDefinition`
```java
public static AbstractBeanDefinition createBeanDefinition(
@ -31,8 +29,6 @@ public static AbstractBeanDefinition createBeanDefinition(
}
```
## generateBeanName
- `org.springframework.beans.factory.support.BeanDefinitionReaderUtils.generateBeanName(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry, boolean)`
@ -77,8 +73,6 @@ public static AbstractBeanDefinition createBeanDefinition(
}
```
## uniqueBeanName
```java
@ -96,10 +90,6 @@ public static String uniqueBeanName(String beanName, BeanDefinitionRegistry regi
}
```
## registerBeanDefinition
```java
@ -125,10 +115,6 @@ public static void registerBeanDefinition(
}
```
## registerWithGeneratedName
```java
@ -142,4 +128,4 @@ public static String registerWithGeneratedName(
registry.registerBeanDefinition(generatedName, definition);
return generatedName;
}
```
```

@ -1,12 +1,11 @@
# Spring BeanNameGenerator
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- `org.springframework.beans.factory.support.BeanNameGenerator`
- 方法用来生成 beanName
```java
public interface BeanNameGenerator {
@ -23,20 +22,12 @@ public interface BeanNameGenerator {
}
```
![](/images/spring/BeanNameGenerator.png)
## DefaultBeanNameGenerator
- `org.springframework.beans.factory.support.DefaultBeanNameGenerator`
- 调用工具类方法进行生成
```JAVA
@ -46,11 +37,9 @@ public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry
}
```
1. ClassName + # + 十六进制字符
2. parentName + $child + # + 十六进制字符
3. factoryBeanName +$created+# + 十六进制字符
2. parentName + \$child + # + 十六进制字符
3. factoryBeanName +\$created+# + 十六进制字符
4. beanName + # + 序号
```java
@ -93,13 +82,9 @@ public static String generateBeanName(
}
```
## AnnotationBeanNameGenerator
1. 获取注解的value作为beanName
1. 获取注解的 value 作为 beanName
2. 类名首字母小写
```java
@ -122,10 +107,6 @@ public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry
}
```
## FullyQualifiedAnnotationBeanNameGenerator
- 全类名
@ -137,4 +118,4 @@ protected String buildDefaultBeanName(BeanDefinition definition) {
Assert.state(beanClassName != null, "No bean class name set");
return beanClassName;
}
```
```

@ -1,19 +1,16 @@
# Spring MethodOverride
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- `org.springframework.beans.factory.support.MethodOverride`
- `org.springframework.beans.factory.support.LookupOverride`
- `org.springframework.beans.factory.support.ReplaceOverride`
- `org.springframework.beans.factory.support.LookupOverride`
- `org.springframework.beans.factory.support.ReplaceOverride`
- `org.springframework.beans.factory.support.MethodOverrides`
## MethodOverride
- MethodOverride 方法重载类
- MethodOverride 方法重载类
在`MethodOverride`定义了下面三个属性
@ -48,17 +45,11 @@ public abstract class MethodOverride implements BeanMetadataElement {
public abstract boolean matches(Method method);
```
类图
![MethodOverride](/images/spring/MethodOverride.png)
- 在Spring中有两种可以重写的机制(XML)
- 在 Spring 中有两种可以重写的机制(XML)
1. `lookup-method` 标签
@ -66,22 +57,14 @@ public abstract boolean matches(Method method);
<lookup-method name="" bean=""/>
```
2. `replaced-method` 标签
```xml
<replaced-method name="" replacer=""/>
```
相对应的两个类如类图所示
## LookupOverride
- `org.springframework.beans.factory.support.LookupOverride`
@ -100,17 +83,15 @@ private final String beanName;
private Method method;
```
### matches
比较方法
1. method是否直接相等
1. method 名称是否相同
2. 是否需要重载
3. 是不是 ABSTRACT 方法
4. 参数列表长度是否等于0
1. method 是否直接相等
1. method 名称是否相同
1. 是否需要重载
1. 是不是 ABSTRACT 方法
1. 参数列表长度是否等于 0
```java
@Override
@ -131,10 +112,6 @@ private Method method;
```
## ReplaceOverride
- `org.springframework.beans.factory.support.ReplaceOverride`
@ -152,8 +129,6 @@ private final String methodReplacerBeanName;
private final List<String> typeIdentifiers = new LinkedList<>();
```
- 一个例子
```XML
@ -175,18 +150,12 @@ private final List<String> typeIdentifiers = new LinkedList<>();
</beans>
```
methodReplacerBeanName 对应`org.springframework.beans.factory.support.MethodReplacer` 的实现类
typeIdentifiers 对应标签 arg-type 的属性值
typeIdentifiers 对应标签 arg-type 的属性值
构造方法
```java
public ReplaceOverride(String methodName, String methodReplacerBeanName) {
super(methodName);
@ -195,18 +164,10 @@ public ReplaceOverride(String methodName, String methodReplacerBeanName) {
}
```
methodName 通过父类进行设置
### matches
```java
@Override
public boolean matches(Method method) {
@ -237,39 +198,27 @@ public boolean matches(Method method) {
}
```
## MethodOverrides
- `org.springframework.beans.factory.support.MethodOverrides`
- 重载方法对象
- 存储所有重载的方法列表(set结构)
- 存储所有重载的方法列表(set 结构)
```java
private final Set<MethodOverride> overrides = new CopyOnWriteArraySet<>();
```
几个方法
1. 添加 MethodOverride
```java
public void addOverride(MethodOverride override) {
this.overrides.add(override);
}
public void addOverrides(@Nullable MethodOverrides other) {
if (other != null) {
this.overrides.addAll(other.overrides);
@ -290,4 +239,4 @@ public boolean matches(Method method) {
}
return match;
}
```
```

@ -1,4 +1,5 @@
# Spring MultiValueMap
# Spring MultiValueMap
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
@ -53,18 +54,12 @@ public interface MultiValueMap<K, V> extends Map<K, List<V>> {
}
```
- 但从接口定义上可以明确 value 是一个list结构
- 但从接口定义上可以明确 value 是一个 list 结构
类图
![](/images/spring/MultiValueMap.png)
## LinkedMultiValueMap
```java
@ -78,7 +73,7 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
// 获取 list 的第一个
return (values != null && !values.isEmpty() ? values.get(0) : null);
}
@Override
public void add(K key, @Nullable V value) {
// 从当前内存中获取key对应的list.
@ -86,7 +81,7 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
// 将value 插入到values中
values.add(value);
}
@Override
public void addAll(K key, List<? extends V> values) {
// 从当前内存中获取key对应的list.
@ -94,14 +89,14 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
// 将value 插入到values中
currentValues.addAll(values);
}
@Override
public void addAll(MultiValueMap<K, V> values) {
for (Entry<K, List<V>> entry : values.entrySet()) {
addAll(entry.getKey(), entry.getValue());
}
}
@Override
public void set(K key, @Nullable V value) {
// 构造list
@ -111,13 +106,13 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
// 添加
this.targetMap.put(key, values);
}
@Override
public void setAll(Map<K, V> values) {
// 循环执行 set 方法
values.forEach(this::set);
}
@Override
public Map<K, V> toSingleValueMap() {
// 返回结果定义
@ -134,4 +129,4 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V>, Serializa
}
```
- 其他实现类也基本和这个类相同, 不做具体展开
- 其他实现类也基本和这个类相同, 不做具体展开

@ -3,35 +3,24 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 相关类
- `org.springframework.beans.PropertyValues`
- `org.springframework.beans.PropertyValue`
- `org.springframework.beans.MutablePropertyValues`
- `org.springframework.beans.PropertyValues`
- `org.springframework.beans.PropertyValue`
- `org.springframework.beans.MutablePropertyValues`
- 类图如下
![images](/images/spring/PropertyValues.png)
- 在 Spring IoC 中,**非 Web 工程**,使用 xml 或者注解进行配置主要使用到的是 `PropertyValues` `PropertyValue` `MutablePropertyValues` 三个
其中 `PropertyValues` 是继承迭代器,具体实现在`MutablePropertyValues` 他们处理的对象是`PropertyValues`
- 在 Spring IoC 中,**非Web工程**,使用 xml 或者注解进行配置主要使用到的是 `PropertyValues` `PropertyValue` `MutablePropertyValues` 三个
其中 `PropertyValues` 是继承迭代器,具体实现在`MutablePropertyValues` 他们处理的对象是`PropertyValues`
关系就是这样.
关系就是这样.
- 开始类的解析了
## PropertyValue
- `org.springframework.beans.PropertyValue`
@ -45,13 +34,9 @@
1. name: 属性名称
2. value: 属性值
对应标签`<property name="age" value="30"/>`
属性值一一对应填入.
对应标签`<property name="age" value="30"/>`
属性值一一对应填入.
## MutablePropertyValues
@ -62,8 +47,6 @@
2. `processedProperties`: 已经处理的属性名称
3. `converted`: 是否转换
```java
public class MutablePropertyValues implements PropertyValues, Serializable {
/**
@ -84,8 +67,6 @@ public class MutablePropertyValues implements PropertyValues, Serializable {
}
```
### 构造器
- `MutablePropertyValues` 的一个构造器. 其他构造器的方式原理实现差不多. 核心是将构造参数转换成`PropertyValue`对象在放入`propertyValueList`中
@ -109,16 +90,8 @@ public MutablePropertyValues(@Nullable PropertyValues original) {
}
```
### PropertyValue 的构造方法
```JAVA
public PropertyValue(PropertyValue original) {
Assert.notNull(original, "Original must not be null");
@ -135,16 +108,8 @@ public MutablePropertyValues(@Nullable PropertyValues original) {
```
- 除了最后一行是一个复杂调用. 前面几行代码都是属性赋值操作.
- 最后一行代码会调用`AttributeAccessor`接口上的方法.
- 除了最后一行是一个复杂调用. 前面几行代码都是属性赋值操作.
- 最后一行代码会调用`AttributeAccessor`接口上的方法.
## AttributeAccessor
@ -194,16 +159,8 @@ public interface AttributeAccessor {
}
```
- 回到`org.springframework.core.AttributeAccessorSupport#copyAttributesFrom`方法
```java
protected void copyAttributesFrom(AttributeAccessor source) {
Assert.notNull(source, "Source must not be null");
@ -218,13 +175,9 @@ protected void copyAttributesFrom(AttributeAccessor source) {
}
```
### setAttribute
- 一个map操作
- 一个 map 操作
```java
@Override
@ -239,8 +192,6 @@ public void setAttribute(String name, @Nullable Object value) {
}
```
## addPropertyValue
- `org.springframework.beans.MutablePropertyValues#addPropertyValue(org.springframework.beans.PropertyValue)`
@ -268,17 +219,11 @@ public void setAttribute(String name, @Nullable Object value) {
```
## mergeIfRequired
- `org.springframework.beans.MutablePropertyValues#mergeIfRequired`
- 这段代码会取舍新老数据.
- 这段代码会取舍新老数据.
1. 如果是`Mergeable`类型会做合并操作
2. 直接返回新数据
@ -299,9 +244,7 @@ public void setAttribute(String name, @Nullable Object value) {
```
- 配合测试代码,跟容易看懂.
- 配合测试代码,跟容易看懂.
```java
@Test
@ -320,28 +263,12 @@ public void setAttribute(String name, @Nullable Object value) {
}
```
## Mergeable
新的接口`Mergeable`
- `org.springframework.beans.Mergeable`
```java
public interface Mergeable {
@ -358,16 +285,10 @@ public interface Mergeable {
}
```
![](/images/spring/Mergeable.png)
- 看一下 List 怎么实现`merge`
```java
@Override
@SuppressWarnings("unchecked")
@ -388,6 +309,4 @@ public List<E> merge(@Nullable Object parent) {
}
```
- 在 list 视线中就是讲两个结果合并. 事实上其他的几个都是这个操作. 这里就不贴所有的代码了
- 在 list 视线中就是讲两个结果合并. 事实上其他的几个都是这个操作. 这里就不贴所有的代码了

@ -1,21 +1,11 @@
# Spring PropertyPlaceholderHelper
# Spring PropertyPlaceholderHelper
- 类全路径: `org.springframework.util.PropertyPlaceholderHelper`
## parseStringValue
- `org.springframework.util.PropertyPlaceholderHelper#parseStringValue` 这个方法是主要方法
```java
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
@ -87,11 +77,9 @@ protected String parseStringValue(
}
```
在这里还需要关注一个接口
- 占位符解析.
- 占位符解析.
```java
@FunctionalInterface
@ -107,12 +95,8 @@ public interface PlaceholderResolver {
}
```
占位符解析请查看: [PlaceholderResolver](PlaceholderResolver)
## findPlaceholderEndIndex
- 寻找结尾占位符索引
@ -145,10 +129,3 @@ private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
return -1;
}
```

@ -3,19 +3,14 @@
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
## MutablePropertySources
- 全路径: `org.springframework.core.env.MutablePropertySources`
- 全路径: `org.springframework.core.env.MutablePropertySources`
- `MutablePropertySources`类内部存储了`List<PropertySource<?>>`对象,主要是针对`List<PropertySource<?>>` 进行的操作.换句话说就是对 list 操作的实现
- 类注解如下
```java
public class MutablePropertySources implements PropertySources {
@ -238,10 +233,6 @@ public class MutablePropertySources implements PropertySources {
}
```
## PropertySources
- 类路径: `org.springframework.core.env.PropertySources`
@ -269,7 +260,7 @@ public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* Return the property source with the given name, {@code null} if not found.
* 获取 PropertySource
* 获取 PropertySource
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
@Nullable
@ -278,22 +269,13 @@ public interface PropertySources extends Iterable<PropertySource<?>> {
}
```
## PropertySource
- 类路径: `org.springframework.core.env.PropertySource`
- 存有两个子类
1. StubPropertySource
2. ComparisonPropertySource
3. 调用`getSource`、`containsProperty`、`getProperty` 都会直接异常
2. ComparisonPropertySource 3. 调用`getSource`、`containsProperty`、`getProperty` 都会直接异常
```java
public abstract class PropertySource<T> {
@ -500,10 +482,6 @@ public abstract class PropertySource<T> {
}
```
类图
![PropertySource.png](/images/spring/PropertySource.png)
![PropertySource.png](/images/spring/PropertySource.png)

@ -1,7 +1,6 @@
# Spring SystemPropertyUtils
- spring 中获取系统属性的工具类
- spring 中获取系统属性的工具类
- 内部属性
@ -39,19 +38,13 @@ private static final PropertyPlaceholderHelper nonStrictHelper =
new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true);
```
## resolvePlaceholders
- 解析属性
![SystemPropertyUtils-resolvePlaceholders.png](/images/spring/SystemPropertyUtils-resolvePlaceholders.png)
时序图因为有递归所以看着有点长, 其核心方法最后会指向 PlaceholderResolver
时序图因为有递归所以看着有点长, 其核心方法最后会指向 PlaceholderResolver
通过 PlaceholderResolver 获取属性值
@ -87,4 +80,4 @@ private static class SystemPropertyPlaceholderResolver implements PropertyPlaceh
}
}
}
```
```

@ -4,9 +4,9 @@
- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read)
- 源码路径: `org.springframework.jms.annotation.EnableJms`
- `org.springframework.web.servlet.HandlerMapping`
- HandlerMapping 处理映射关系, 通过请求转换成对象`HandlerExecutionChain`
```java
public interface HandlerMapping {
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
@ -14,14 +14,8 @@ public interface HandlerMapping {
}
```
![image](/images/springMVC/HandlerMapping.png)
```java
@Override
@Nullable
@ -62,8 +56,6 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws
}
```
- `getHandlerInternal`方法是一个抽象方法
```JAVA
@ -75,14 +67,8 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws
![image-20200915135933146](images/image-20200915135933146.png)
- 先看`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`方法是怎么一回事.
```java
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
@ -105,13 +91,7 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws
```
## UrlPathHelper
## UrlPathHelper
- 全路径:`org.springframework.web.util.UrlPathHelper`
@ -122,28 +102,22 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws
* 是否全路径标记
*/
private boolean alwaysUseFullPath = false;
/**
* 是否需要 decode
*/
private boolean urlDecode = true;
private boolean removeSemicolonContent = true;
/**
* 默认的encoding编码格式
*/
private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
```
### getPathWithinApplication
```java
public String getPathWithinApplication(HttpServletRequest request) {
// 获取 context path
@ -163,19 +137,15 @@ public String getPathWithinApplication(HttpServletRequest request) {
1. 从 request 中获取 context-path
1. 从属性中直接获取
2. 从request中调用 getContextPath 获取
2. 从 request 中调用 getContextPath 获取
3. 判断是否是**`/`**
4. decode request string
4. decode request string
2. 从 request 中虎丘 request-uri
1. 从属性中获取
2. 从 request 中调用 getRequestURI 获取
3. decode
3. decode
3. 获取剩余路径
### getContextPath
- 获取 context-path 地址
@ -196,10 +166,6 @@ public String getContextPath(HttpServletRequest request) {
}
```
### decodeRequestString
- 判断是否需要编码, 需要编码就做编码操作,不需要就直接返回
@ -215,12 +181,6 @@ public String decodeRequestString(HttpServletRequest request, String source) {
}
```
### decodeInternal
- 编码方法
@ -245,16 +205,10 @@ private String decodeInternal(HttpServletRequest request, String source) {
}
```
### determineEncoding
- 确认编码
```java
protected String determineEncoding(HttpServletRequest request) {
// 从 request 中获取编码方式
@ -267,10 +221,6 @@ protected String determineEncoding(HttpServletRequest request) {
}
```
### getRequestUri
- 获取 uri 地址
@ -289,10 +239,6 @@ protected String determineEncoding(HttpServletRequest request) {
```
### decodeAndCleanUriString
- 编码和清理数据
@ -309,12 +255,6 @@ private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
}
```
### shouldRemoveTrailingServletPathSlash
- 是否删除 servlet path 后的斜杠
@ -359,10 +299,6 @@ private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request)
}
```
### decodeMatrixVariables
- 编码修改方法
@ -391,8 +327,6 @@ public MultiValueMap<String, String> decodeMatrixVariables(
- 与这个方法对应的还有`decodePathVariables`
### decodePathVariables
```java
@ -403,39 +337,27 @@ public Map<String, String> decodePathVariables(HttpServletRequest request, Map<S
}
else {
Map<String, String> decodedVars = new LinkedHashMap<>(vars.size());
// 虚幻 decoding
// 虚幻 decoding
vars.forEach((key, value) -> decodedVars.put(key, decodeInternal(request, value)));
return decodedVars;
}
}
```
- 回到`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`
```JAVA
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
```
- 设置属性上锁开锁就不具体展开了.
## lookupHandlerMethod
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod` 方法
- 第一部分
```java
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
@ -452,14 +374,12 @@ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletReques
// 添加匹配映射
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
//...
}
```
- 创建一个匹配list,将匹配结果放入
- 创建一个匹配 list,将匹配结果放入
```
List<Match> matches = new ArrayList<>();
@ -478,7 +398,7 @@ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletReques
}
```
urlLookup 是`MultiValueMap`接口.
urlLookup 是`MultiValueMap`接口.
key:url value:mapping
@ -511,8 +431,6 @@ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletReques
}
```
- `getMatchingMapping` 方法是一个抽象方法
```java
@ -528,16 +446,8 @@ protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletReques
}
```
- 第二部分
```java
if (!matches.isEmpty()) {
// 比较对象
@ -573,10 +483,6 @@ else {
}
```
- 一行行开始分析
```java
@ -603,8 +509,6 @@ protected abstract Comparator<T> getMappingComparator(HttpServletRequest request
- 执行完成比较方法后创建对象`MatchComparator`
- 对象创建后进行排序,排序后取出第一个元素作为后续操作的基准对象
```java
// 排序
matches.sort(comparator);
@ -612,10 +516,6 @@ matches.sort(comparator);
Match bestMatch = matches.get(0);
```
```java
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
@ -643,12 +543,8 @@ if (matches.size() > 1) {
- 取出第一个元素和第二个元素进行比较. 如果两个 match 相同, 出现异常
最后两个方法
```java
// 设置属性
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
@ -662,8 +558,6 @@ else {
}
```
- `handleMatch`
```java
@ -678,8 +572,6 @@ else {
- `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch`
```java
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
@ -723,10 +615,6 @@ protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServl
}
```
- `handleNoMatch` 也是同类型操作
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#handleNoMatch`
- `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch`
@ -783,10 +671,3 @@ protected HandlerMethod handleNoMatch(
return null;
}
```

@ -5,44 +5,42 @@
- 源码路径: `org.springframework.jms.annotation.EnableJms`
- 类全路径
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry`
- 基本属性
```java
class MappingRegistry {
/**
* key:mapping
* value: mapping registration
*/
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
/**
* key: mapping
* value: handlerMethod
*/
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
/**
* key: url
* value: list mapping
*/
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
/**
* key: name
* value: handler method
*/
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
/**
* key:handler method
* value: 跨域配置
*/
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
/**
* 读写锁
*/
@ -50,11 +48,7 @@
}
```
- 写一个简单的controller 来进行解析
- 写一个简单的 controller 来进行解析
```java
@RestController
@ -67,8 +61,6 @@ public class DemoController {
}
```
- 前置链路追踪
- `org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#registerHandlerMethod`
@ -96,10 +88,6 @@ public class DemoController {
![image-20200918130340555](/images/springMVC/clazz/image-20200918130340555.png)
## createHandlerMethod
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#createHandlerMethod`
@ -116,23 +104,15 @@ protected HandlerMethod createHandlerMethod(Object handler, Method method) {
}
```
- HandlerMethod 构造函数
```java
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method){}
public HandlerMethod(Object bean, Method method) {}
```
## HandlerMethod
## HandlerMethod
- 成员变量
@ -172,10 +152,6 @@ public class HandlerMethod {
}
```
## validateMethodMapping
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#validateMethodMapping`
@ -197,8 +173,6 @@ private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) {
}
```
## getDirectUrls
- 找到 mapping 匹配的 url
@ -219,8 +193,6 @@ private List<String> getDirectUrls(T mapping) {
}
```
## handlerMethod 和 name 绑定
```java
@ -236,8 +208,6 @@ if (getNamingStrategy() != null) {
- `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrategy#getName`
```java
@Override
public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
@ -259,16 +229,10 @@ public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
}
```
## initCorsConfiguration
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initCorsConfiguration`
```java
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
@ -301,12 +265,6 @@ protected CorsConfiguration initCorsConfiguration(Object handler, Method method,
}
```
## unregister
- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#unregister`
@ -344,4 +302,4 @@ public void unregister(T mapping) {
this.readWriteLock.writeLock().unlock();
}
}
```
```

@ -326,7 +326,6 @@ public @interface EnableConfigurationProperties {
```yml
server:
port: 9999
```
- 具体方法: `org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#bind`

@ -9,46 +9,44 @@
- 日志级别: `org.springframework.boot.logging.LogLevel`
```java
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}
```
```java
public enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF
}
```
## java 日志实现
## Java 日志实现
- `org.springframework.boot.logging.java.JavaLoggingSystem`
![image-20200323144523848](../../images/SpringBoot/image-20200323144523848.png)
```JAVA
static {
// KEY : springBoot 定义的日志级别, value: jdk 定义的日志级别
LEVELS.map(LogLevel.TRACE, Level.FINEST);
LEVELS.map(LogLevel.DEBUG, Level.FINE);
LEVELS.map(LogLevel.INFO, Level.INFO);
LEVELS.map(LogLevel.WARN, Level.WARNING);
LEVELS.map(LogLevel.ERROR, Level.SEVERE);
LEVELS.map(LogLevel.FATAL, Level.SEVERE);
LEVELS.map(LogLevel.OFF, Level.OFF);
}
```
![image-20200323144523848](../../images/SpringBoot/image-20200323144523848.png)
```java
static {
// KEY : springBoot 定义的日志级别, value: jdk 定义的日志级别
LEVELS.map(LogLevel.TRACE, Level.FINEST);
LEVELS.map(LogLevel.DEBUG, Level.FINE);
LEVELS.map(LogLevel.INFO, Level.INFO);
LEVELS.map(LogLevel.WARN, Level.WARNING);
LEVELS.map(LogLevel.ERROR, Level.SEVERE);
LEVELS.map(LogLevel.FATAL, Level.SEVERE);
LEVELS.map(LogLevel.OFF, Level.OFF);
}
```
- LEVELS 对象
```java
protected static class LogLevels<T> {
/**
* key SpringBoot 中定义的日志级别, value: 其他日志框架的日志级别
*/
private final Map<LogLevel, T> systemToNative;
/**
* key : 其他日志框架的日志级别 , value: springBoot 中定义中定义的日志级别
*/
private final Map<T, LogLevel> nativeToSystem;
}
protected static class LogLevels<T> {
/**
* key SpringBoot 中定义的日志级别, value: 其他日志框架的日志级别
*/
private final Map<LogLevel, T> systemToNative;
/**
* key : 其他日志框架的日志级别 , value: springBoot 中定义中定义的日志级别
*/
private final Map<T, LogLevel> nativeToSystem;
}
```
## LoggingSystem
@ -58,73 +56,72 @@ public enum LogLevel {
- 一个 map 对象: `SYSTEMS`
```JAVA
/**
* key: 第三方日志框架的类 value: springBoot 中的处理类
*/
private static final Map<String, String> SYSTEMS;
static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
```
```java
/**
* key: 第三方日志框架的类 value: springBoot 中的处理类
*/
private static final Map<String, String> SYSTEMS;
static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
```
- 各个抽象方法
| 方法名称 | 作用 |
| ----------------------- | ---------------------------------- |
| beforeInitialize | 初始化之前调用,目的是减少日志输出 |
| initialize | 初始化日志 |
| cleanUp | 清除日志 |
| getShutdownHandler | |
| getSupportedLogLevels | 获取支持的日志级别 |
| setLogLevel | 设置日志级别 |
| getLoggerConfigurations | 获取日志配置 |
| 方法名称 | 作用 |
| ----------------------- | ---------------------------------- |
| beforeInitialize | 初始化之前调用,目的是减少日志输出 |
| initialize | 初始化日志 |
| cleanUp | 清除日志 |
| getShutdownHandler | |
| getSupportedLogLevels | 获取支持的日志级别 |
| setLogLevel | 设置日志级别 |
| getLoggerConfigurations | 获取日志配置 |
### get
```java
public static LoggingSystem get(ClassLoader classLoader) {
// 获取系统属性
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
// 是不是NONE
if (NONE.equals(loggingSystem)) {
// 空的日志系统
return new NoOpLoggingSystem();
}
return get(classLoader, loggingSystem);
// 获取系统属性
String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
if (StringUtils.hasLength(loggingSystem)) {
// 是不是NONE
if (NONE.equals(loggingSystem)) {
// 空的日志系统
return new NoOpLoggingSystem();
}
// 循环所有日志,
return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
.map((entry) ->
// 实例化具体日志
get(classLoader, entry.getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
return get(classLoader, loggingSystem);
}
// 循环所有日志,
return SYSTEMS.entrySet().stream().filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
.map((entry) ->
// 实例化具体日志
get(classLoader, entry.getValue())).findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable logging system located"));
}
```
- 实例化日志系统
```java
private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
try {
Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
Constructor<?> constructor = systemClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);
return (LoggingSystem) constructor.newInstance(classLoader);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
try {
Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
Constructor<?> constructor = systemClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);
return (LoggingSystem) constructor.newInstance(classLoader);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
```
@ -138,28 +135,28 @@ public static LoggingSystem get(ClassLoader classLoader) {
![image-20200323154205484](../../images/SpringBoot/image-20200323154205484.png)
- 链路
1. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEvent`
2. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent`
3. `org.springframework.boot.logging.LoggingSystem#beforeInitialize`
- 因为前文中我们已知对象是:`org.springframework.boot.logging.logback.LogbackLoggingSystem` 直接看这个类的**`beforeInitialize`**方法
```JAVA
@Override
public void beforeInitialize() {
// 日志上下文
LoggerContext loggerContext = getLoggerContext();
// 是否初始化
if (isAlreadyInitialized(loggerContext)) {
return;
}
// 父类方法
super.beforeInitialize();
// 添加过滤器
loggerContext.getTurboFilterList().add(FILTER);
}
- 链路
1. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEvent`
2. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent`
3. `org.springframework.boot.logging.LoggingSystem#beforeInitialize`
- 因为前文中我们已知对象是:`org.springframework.boot.logging.logback.LogbackLoggingSystem` 直接看这个类的 `beforeInitialize` 方法
```java
@Override
public void beforeInitialize() {
// 日志上下文
LoggerContext loggerContext = getLoggerContext();
// 是否初始化
if (isAlreadyInitialized(loggerContext)) {
return;
}
// 父类方法
super.beforeInitialize();
// 添加过滤器
loggerContext.getTurboFilterList().add(FILTER);
}
```
- 初始化之前的的操作完成了初始化方法开始
@ -168,289 +165,281 @@ public static LoggingSystem get(ClassLoader classLoader) {
- `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEnvironmentPreparedEvent`
```JAVA
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
```java
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
```
- `org.springframework.boot.context.logging.LoggingApplicationListener#initializeSystem`
```JAVA
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
// 早期 的日志级别
initializeEarlyLoggingLevel(environment);
// 初始化日志系统
initializeSystem(environment, this.loggingSystem, this.logFile);
// 初始化日志级别
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
```java
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
// 早期 的日志级别
initializeEarlyLoggingLevel(environment);
// 初始化日志系统
initializeSystem(environment, this.loggingSystem, this.logFile);
// 初始化日志级别
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
```
```JAVA
private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
String logConfig = environment.getProperty(CONFIG_PROPERTY);
if (ignoreLogConfig(logConfig)) {
// 日志系统初始化
system.initialize(initializationContext, null, logFile);
```java
private void initializeSystem(ConfigurableEnvironment environment, LoggingSystem system, LogFile logFile) {
LoggingInitializationContext initializationContext = new LoggingInitializationContext(environment);
String logConfig = environment.getProperty(CONFIG_PROPERTY);
if (ignoreLogConfig(logConfig)) {
// 日志系统初始化
system.initialize(initializationContext, null, logFile);
}
else {
try {
ResourceUtils.getURL(logConfig).openStream().close();
system.initialize(initializationContext, logConfig, logFile);
}
else {
try {
ResourceUtils.getURL(logConfig).openStream().close();
system.initialize(initializationContext, logConfig, logFile);
}
catch (Exception ex) {
// NOTE: We can't use the logger here to report the problem
System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'");
ex.printStackTrace(System.err);
throw new IllegalStateException(ex);
}
catch (Exception ex) {
// NOTE: We can't use the logger here to report the problem
System.err.println("Logging system failed to initialize using configuration from '" + logConfig + "'");
ex.printStackTrace(System.err);
throw new IllegalStateException(ex);
}
}
}
```
- `org.springframework.boot.logging.logback.LogbackLoggingSystem#initialize`
```java
@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
// 日志初始化
super.initialize(initializationContext, configLocation, logFile);
loggerContext.getTurboFilterList().remove(FILTER);
markAsInitialized(loggerContext);
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
+ "' system property. Please use 'logging.config' instead.");
}
}
```java
@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
if (isAlreadyInitialized(loggerContext)) {
return;
}
// 日志初始化
super.initialize(initializationContext, configLocation, logFile);
loggerContext.getTurboFilterList().remove(FILTER);
markAsInitialized(loggerContext);
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
+ "' system property. Please use 'logging.config' instead.");
}
}
```
```
- `org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions`
```JAVA
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
String config = getSelfInitializationConfig();
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
if (config == null) {
config = getSpringInitializationConfig();
}
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
}
// 加载默认配置
loadDefaults(initializationContext, logFile);
```java
private void initializeWithConventions(LoggingInitializationContext initializationContext, LogFile logFile) {
String config = getSelfInitializationConfig();
if (config != null && logFile == null) {
// self initialization has occurred, reinitialize in case of property changes
reinitialize(initializationContext);
return;
}
if (config == null) {
config = getSpringInitializationConfig();
}
if (config != null) {
loadConfiguration(initializationContext, config, logFile);
return;
}
// 加载默认配置
loadDefaults(initializationContext, logFile);
}
```
- `org.springframework.boot.logging.logback.LogbackLoggingSystem#loadDefaults`
```JAVA
@Override
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
LoggerContext context = getLoggerContext();
stopAndReset(context);
boolean debug = Boolean.getBoolean("logback.debug");
if (debug) {
StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
}
LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context)
: new LogbackConfigurator(context);
Environment environment = initializationContext.getEnvironment();
context.putProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN,
environment.resolvePlaceholders("${logging.pattern.level:${LOG_LEVEL_PATTERN:%5p}}"));
context.putProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, environment.resolvePlaceholders(
"${logging.pattern.dateformat:${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}"));
context.putProperty(LoggingSystemProperties.ROLLING_FILE_NAME_PATTERN, environment
.resolvePlaceholders("${logging.pattern.rolling-file-name:${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}"));
new DefaultLogbackConfiguration(initializationContext, logFile).apply(configurator);
context.setPackagingDataEnabled(true);
}
```
```JAVA
@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
// 是否加载过
if (isAlreadyInitialized(loggerContext)) {
return;
}
// 日志初始化
super.initialize(initializationContext, configLocation, logFile);
// 删除 FILTER
loggerContext.getTurboFilterList().remove(FILTER);
// 初始化标记
markAsInitialized(loggerContext);
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
+ "' system property. Please use 'logging.config' instead.");
}
}
```
- `org.springframework.boot.logging.logback.LogbackLoggingSystem#loadDefaults`
标记`markAsInitialized`
```java
@Override
protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
LoggerContext context = getLoggerContext();
stopAndReset(context);
boolean debug = Boolean.getBoolean("logback.debug");
if (debug) {
StatusListenerConfigHelper.addOnConsoleListenerInstance(context, new OnConsoleStatusListener());
}
LogbackConfigurator configurator = debug ? new DebugLogbackConfigurator(context)
: new LogbackConfigurator(context);
Environment environment = initializationContext.getEnvironment();
context.putProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN,
environment.resolvePlaceholders("${logging.pattern.level:${LOG_LEVEL_PATTERN:%5p}}"));
context.putProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, environment.resolvePlaceholders(
"${logging.pattern.dateformat:${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}"));
context.putProperty(LoggingSystemProperties.ROLLING_FILE_NAME_PATTERN, environment
.resolvePlaceholders("${logging.pattern.rolling-file-name:${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}"));
new DefaultLogbackConfiguration(initializationContext, logFile).apply(configurator);
context.setPackagingDataEnabled(true);
}
```
```JAVA
private void markAsInitialized(LoggerContext loggerContext) {
loggerContext.putObject(LoggingSystem.class.getName(), new Object());
}
```java
@Override
public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
LoggerContext loggerContext = getLoggerContext();
// 是否加载过
if (isAlreadyInitialized(loggerContext)) {
return;
}
// 日志初始化
super.initialize(initializationContext, configLocation, logFile);
// 删除 FILTER
loggerContext.getTurboFilterList().remove(FILTER);
// 初始化标记
markAsInitialized(loggerContext);
if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) {
getLogger(LogbackLoggingSystem.class.getName()).warn("Ignoring '" + CONFIGURATION_FILE_PROPERTY
+ "' system property. Please use 'logging.config' instead.");
}
}
```
```
- 标记 `markAsInitialized`
此时日志初始化完成
```java
private void markAsInitialized(LoggerContext loggerContext) {
loggerContext.putObject(LoggingSystem.class.getName(), new Object());
}
```
此时日志初始化完成。
### 默认配置文件
- `getStandardConfigLocations` 这个方法定义了默认配置文件有哪些
- `getStandardConfigLocations` 这个方法定义了默认配置文件有哪些
```java
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
}
```
```java
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
}
```
- 切回`org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions`方法
- 切回 `org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions` 方法
- 添加依赖
```XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${revision}</version>
</dependency>
```XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>${revision}</version>
</dependency>
```
```
- 添加配置文件
![image-20200323161442058](../../images/SpringBoot/image-20200323161442058.png)
![image-20200323161442058](../../images/SpringBoot/image-20200323161442058.png)
![image-20200323161522570](../../images/SpringBoot/image-20200323161522570.png)
![image-20200323161522570](../../images/SpringBoot/image-20200323161522570.png)
- 此时配置文件地址出现了
```JAVA
protected String getSelfInitializationConfig() {
// 寻找配置文件
return findConfig(getStandardConfigLocations());
}
```
```JAVA
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
}
```java
protected String getSelfInitializationConfig() {
// 寻找配置文件
return findConfig(getStandardConfigLocations());
}
```
```
```java
@Override
protected String[] getStandardConfigLocations() {
return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" };
}
```JAVA
private String findConfig(String[] locations) {
for (String location : locations) {
ClassPathResource resource = new ClassPathResource(location, this.classLoader);
if (resource.exists()) {
return "classpath:" + location;
}
}
return null;
}
```
```
```java
private String findConfig(String[] locations) {
for (String location : locations) {
ClassPathResource resource = new ClassPathResource(location, this.classLoader);
if (resource.exists()) {
return "classpath:" + location;
}
}
return null;
}
```
- 此时自定义配置文件如何获取的已经明了
- 此时自定义配置文件如何获取的已经明了
#### reinitialize
```JAVA
@Override
protected void reinitialize(LoggingInitializationContext initializationContext) {
// 日志上下文重新设置
getLoggerContext().reset();
getLoggerContext().getStatusManager().clear();
// 加载配置文件
loadConfiguration(initializationContext, getSelfInitializationConfig(), null);
}
```java
@Override
protected void reinitialize(LoggingInitializationContext initializationContext) {
// 日志上下文重新设置
getLoggerContext().reset();
getLoggerContext().getStatusManager().clear();
// 加载配置文件
loadConfiguration(initializationContext, getSelfInitializationConfig(), null);
}
```
```JAVA
@Override
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location,
LogFile logFile) {
// 父类方法
super.loadConfiguration(initializationContext, location, logFile);
// 获取上下文
LoggerContext loggerContext = getLoggerContext();
// 停止并且重启
stopAndReset(loggerContext);
try {
// 配置文件加载
configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location));
}
catch (Exception ex) {
throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
}
List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList();
StringBuilder errors = new StringBuilder();
for (Status status : statuses) {
if (status.getLevel() == Status.ERROR) {
errors.append((errors.length() > 0) ? String.format("%n") : "");
errors.append(status.toString());
}
}
if (errors.length() > 0) {
throw new IllegalStateException(String.format("Logback configuration error detected: %n%s", errors));
```java
@Override
protected void loadConfiguration(LoggingInitializationContext initializationContext, String location,
LogFile logFile) {
// 父类方法
super.loadConfiguration(initializationContext, location, logFile);
// 获取上下文
LoggerContext loggerContext = getLoggerContext();
// 停止并且重启
stopAndReset(loggerContext);
try {
// 配置文件加载
configureByResourceUrl(initializationContext, loggerContext, ResourceUtils.getURL(location));
}
catch (Exception ex) {
throw new IllegalStateException("Could not initialize Logback logging from " + location, ex);
}
List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList();
StringBuilder errors = new StringBuilder();
for (Status status : statuses) {
if (status.getLevel() == Status.ERROR) {
errors.append((errors.length() > 0) ? String.format("%n") : "");
errors.append(status.toString());
}
}
if (errors.length() > 0) {
throw new IllegalStateException(String.format("Logback configuration error detected: %n%s", errors));
}
}
```
```java
private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
URL url) throws JoranException {
if (url.toString().endsWith("xml")) {
// logback 日志操作
JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
// 设置上下文
configurator.setContext(loggerContext);
// 执行配置
configurator.doConfigure(url);
}
else {
new ContextInitializer(loggerContext).configureByResource(url);
}
private void configureByResourceUrl(LoggingInitializationContext initializationContext, LoggerContext loggerContext,
URL url) throws JoranException {
if (url.toString().endsWith("xml")) {
// logback 日志操作
JoranConfigurator configurator = new SpringBootJoranConfigurator(initializationContext);
// 设置上下文
configurator.setContext(loggerContext);
// 执行配置
configurator.doConfigure(url);
}
else {
new ContextInitializer(loggerContext).configureByResource(url);
}
}
```
- 执行配置属于 logback 操作源码不在此进行分析
执行配置属于 logback 操作源码不在此进行分析

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@ -1,81 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<head>
<meta charset="UTF-8" />
<title></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="keywords" content="doc,docs,doocs,documentation,github,gitee,source-code-hunter,AmyliaY">
<meta name="description" content="读尽天下源码,心中自然无码,《源码猎人》项目维护者:云之君">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-dark-mode@0.6.1/dist/style.css" />
<link rel="icon" type="image/png" sizes="32x32" href="images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="images/favicon-16x16.png">
</head>
<body>
<nav>
<ul>
<li>
<ul>
<li><a href="#/?id=spring-系列">Spring</a></li>
<li><a href="#/?id=mybatis">Mybatis</a></li>
<li><a href="#/?id=netty">Netty</a></li>
<li><a href="#/?id=tomcat">Tomcat</a></li>
<li><a href="#/?id=番外篇jdk-18">JDK 1.8</a></li>
<li><a href="#/?id=学习心得"></a></li>
</ul>
<meta
name="keywords"
content="doc,docs,doocs,documentation,github,gitee,source-code-hunter,AmyliaY"
/>
<meta
name="description"
content="读尽天下源码,心中自然无码,《源码猎人》项目维护者:云之君"
/>
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify/lib/themes/vue.css"
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify-dark-mode@0.6.1/dist/style.css"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="images/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="images/favicon-16x16.png"
/>
</head>
<body>
<nav>
<ul>
<li>
<ul>
<li><a href="#/?id=spring-系列">Spring</a></li>
<li><a href="#/?id=mybatis">Mybatis</a></li>
<li><a href="#/?id=netty">Netty</a></li>
<li><a href="#/?id=tomcat">Tomcat</a></li>
<li><a href="#/?id=番外篇jdk-18">JDK 1.8</a></li>
<li><a href="#/?id=学习心得"></a></li>
</ul>
</li>
<li>
<ul>
<li><a href="#/README"></a></li>
<li><a href="https://github.com/doocs" target="_blank">Doocs</a></li>
<li><a href="https://github.com/AmyliaY" target="_blank">Author</a></li>
</ul>
<li>
<ul>
<li><a href="#/README"></a></li>
<li>
<a href="https://github.com/doocs" target="_blank">Doocs</a>
</li>
<li>
<a href="https://github.com/AmyliaY" target="_blank">Author</a>
</li>
</ul>
</li>
</ul>
</nav>
<div id="app"> Doocs </div>
<script>
window.$docsify = {
name: 'source-code-hunter',
</ul>
</nav>
<div id="app"> Doocs </div>
<script>
window.$docsify = {
name: "source-code-hunter",
maxLevel: 3,
auto2top: true,
search: [
'/'
],
search: ["/"],
darkMode: {
light: {
toggleBtnBg: '#42b983'
}
light: {
toggleBtnBg: "#42b983",
},
},
plugins: [
function (hook) {
var footer = [
'<hr/>',
'<footer>',
'<span>Copyright © 2018-2020 <a href="https://github.com/doocs" target="_blank">Doocs</a>. All rights reserved.',
'</footer>'
].join('')
function (hook) {
var footer = [
"<hr/>",
"<footer>",
'<span>Copyright © 2018-2020 <a href="https://github.com/doocs" target="_blank">Doocs</a>. All rights reserved.',
"</footer>",
].join("");
hook.afterEach(function (html) {
return html + footer
})
}
]
}
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-c.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-cpp.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-json.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-java.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-python.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-dark-mode@0.6.1/dist/index.js"></script>
</body>
</html>
hook.afterEach(function (html) {
return html + footer;
});
},
],
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-c.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-cpp.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-json.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-java.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs/components/prism-python.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-dark-mode@0.6.1/dist/index.js"></script>
</body>
</html>

Loading…
Cancel
Save