diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 8c2f118c..00000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.html linguist-language=java \ No newline at end of file diff --git a/.github/workflows/compress.yml b/.github/workflows/compress.yml new file mode 100644 index 00000000..e4dfe145 --- /dev/null +++ b/.github/workflows/compress.yml @@ -0,0 +1,36 @@ +name: Compress + +on: + schedule: + - cron: "0 0 * * 3" + workflow_dispatch: + +jobs: + compress: + runs-on: ubuntu-latest + if: github.repository == 'doocs/source-code-hunter' + steps: + - name: Checkout Branch + uses: actions/checkout@v4 + + - name: Compress Images + id: calibre + uses: calibreapp/image-actions@main + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + compressOnly: true + + - name: Commit Files + if: | + steps.calibre.outputs.markdown != '' + run: | + git config --local user.email "szuyanglb@outlook.com" + git config --local user.name "yanglbme" + git commit -m "chore: auto compress images" -a + + - name: Push Changes + if: | + steps.calibre.outputs.markdown != '' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..ae732091 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,51 @@ +name: Build and deploy + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build with VitePress + run: pnpm run docs:build + + - name: Generate CNAME + run: echo "schunter.doocs.org" > docs/.vitepress/dist/CNAME + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github_pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml deleted file mode 100644 index 11e58257..00000000 --- a/.github/workflows/sync.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Sync - -on: - push: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Sync to Gitee - uses: wearerequired/git-mirror-action@master - env: - # 注意在 Settings->Secrets 配置 GITEE_RSA_PRIVATE_KEY - SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} - with: - # 注意替换为你的 GitHub 源仓库地址 - source-repo: "git@github.com:doocs/source-code-hunter.git" - # 注意替换为你的 Gitee 目标仓库地址 - destination-repo: "git@gitee.com:Doocs/source-code-hunter.git" - - - name: Build Gitee Pages - uses: yanglbme/gitee-pages-action@master - with: - # 注意替换为你的 Gitee 用户名 - gitee-username: yanglbme - # 注意在 Settings->Secrets 配置 GITEE_PASSWORD - gitee-password: ${{ secrets.GITEE_PASSWORD }} - # 注意替换为你的 Gitee 仓库 - gitee-repo: doocs/source-code-hunter \ No newline at end of file diff --git a/.gitignore b/.gitignore index a1034339..8f014948 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,8 @@ local.properties .DS_Store gradle.properties *.gh -Redis源码分析的内容暂时现放在这个模块下,等中间件的内容多了,再将项目分成框架和中间件两大模块 -.vscode \ No newline at end of file +.vscode +/node_modules + +docs/.vitepress/dist +docs/.vitepress/cache \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..cf040424 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..92d1fea3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +.idea/ +.github/ +node_modules/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..a031e0cb --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "avoid" +} diff --git a/Main.java b/Main.java new file mode 100644 index 00000000..c1c8cb94 --- /dev/null +++ b/Main.java @@ -0,0 +1,8 @@ +/** + * @author yanglbme + */ +public class Main { + public static void main(String[] args) { + System.out.println("互联网公司常用框架源码赏析"); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 4230b053..5dae38c9 100644 --- a/README.md +++ b/README.md @@ -1,271 +1,362 @@ # 互联网公司常用框架源码赏析 -[![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) [![issues](https://badgen.net/github/open-issues/doocs/source-code-hunter)](https://github.com/doocs/source-code-hunter/issues) [![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com) -“技术深度” 与 “技术广度”是对开发者来说最为重要的两个维度,本项目致力于从源码层面,剖析和挖掘互联网行业主流技术的底层实现原理,**为广大开发者“提升技术深度”提供便利**。 +“技术深度” 与 “技术广度”是对开发者来说最为重要的两个维度,本项目致力于从源码层面,剖析和挖掘互联网行业主流技术的底层实现原理,**为广大开发者 “提升技术深度” 提供便利**。 + +加入我们,一起通读互联网行业主流框架及中间件源码,成为强大的 “源码猎人”,目前开放的有 **Spring 全家桶**、**Mybatis**、**Netty**、**Dubbo 框架**,及 **Redis**、**Tomcat** 中间件等,让我们一起开拓新的领地,揭开这些源码的神秘面纱。 -加入我们,一起通读互联网行业主流框架及中间件源码,成为强大的“源码猎人”,目前开放的有 **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 +## 网站 + +[https://schunter.doocs.org](https://schunter.doocs.org) ## 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) +- [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)]() +- [BeanFactoryPostProcessor](/docs/Spring/IoC/BeanFactoryPostProcessor.md) +- [BeanPostProcessor](/docs/Spring/IoC/BeanPostProcessor.md) +- [Spring BeanFactory 源码解析](/docs/Spring/clazz/Spring-beanFactory.md) +- [循环依赖](/docs/Spring/IoC/循环依赖.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 -* [温习一下 servlet](/docs/Spring/SpringMVC/温习一下servlet.md) -* [IoC容器 在 Web环境 中的启动](/docs/Spring/SpringMVC/IoC容器在Web环境中的启动.md) -* [SpringMVC 的设计与实现](/docs/Spring/SpringMVC/SpringMVC的设计与实现.md) -* [SpringMVC 跨域解析](/docs/Spring/SpringMVC/SpringMVC-CROS.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/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 容器的一天(上)]() -### Spring 类解析 +### Spring 整体脉络 -* [Spring 自定义标签解析](/docs/Spring/clazz/Spring-Custom-label-resolution.md) -* [Spring Scan 包扫描](/docs/Spring/clazz/Spring-scan.md) -* [Spring 注解工具类](/docs/Spring/clazz/Spring-AnnotationUtils.md) -* [Spring 别名注册](/docs/Spring/clazz/Spring-SimpleAliasRegistry.md) -* [Spring 标签解析类](/docs/Spring/clazz/Spring-BeanDefinitionParserDelegate.md) -* [Spring ApplicationListener](/docs/Spring/clazz/Spring-ApplicationListener.md) -* [Spring messageSource](/docs/Spring/clazz/Spring-MessageSource.md) -* [Spring 自定义属性解析器](/docs/Spring/clazz/Spring-Custom-attribute-resolver.md) -* [Spring 排序工具](/docs/Spring/clazz/Spring-OrderUtils.md) +- [16 张图解锁 Spring 的整体脉络](/docs/Spring/Spring整体脉络/16张图解锁Spring的整体脉络.md) -* [Spring-import注解](/docs/Spring/clazz/Spring-Import.md) -* [Spring-定时任务](/docs/Spring/clazz/Spring-Scheduling.md) -* [Spring StopWatch](/docs/Spring/clazz/Spring-StopWatch.md) +### Spring 类解析 + +- [Spring 自定义标签解析](/docs/Spring/clazz/Spring-Custom-label-resolution.md) +- [Spring Scan 包扫描](/docs/Spring/clazz/Spring-scan.md) +- [Spring 注解工具类](/docs/Spring/clazz/Spring-AnnotationUtils.md) +- [Spring 别名注册](/docs/Spring/clazz/Spring-SimpleAliasRegistry.md) +- [Spring 标签解析类](/docs/Spring/clazz/Spring-BeanDefinitionParserDelegate.md) +- [Spring ApplicationListener](/docs/Spring/clazz/Spring-ApplicationListener.md) +- [Spring messageSource](/docs/Spring/clazz/Spring-MessageSource.md) +- [Spring 自定义属性解析器](/docs/Spring/clazz/Spring-Custom-attribute-resolver.md) +- [Spring 排序工具](/docs/Spring/clazz/Spring-OrderUtils.md) + +- [Spring-import 注解](/docs/Spring/clazz/Spring-Import.md) +- [Spring-定时任务](/docs/Spring/clazz/Spring-Scheduling.md) +- [Spring StopWatch](/docs/Spring/clazz/Spring-StopWatch.md) +- [Spring 元数据](/docs/Spring/clazz/Spring-Metadata.md) +- [Spring 条件接口](/docs/Spring/clazz/Spring-Conditional.md) + +- [Spring MultiValueMap](/docs/Spring/clazz/Spring-MultiValueMap.md) +- [Spring MethodOverride](/docs/Spring/clazz/Spring-MethodOverride.md) +- [Spring BeanDefinitionReaderUtils](/docs/Spring/clazz/Spring-BeanDefinitionReaderUtils.md) +- [Spring PropertyPlaceholderHelper](/docs/Spring/clazz/Spring-PropertyPlaceholderHelper.md) + +- [Spring PropertySource](/docs/Spring/clazz/PropertySource) +- [Spring PlaceholderResolver](/docs/Spring/clazz/PlaceholderResolver) + +- [Spring-AnnotationFormatterFactory](/docs/Spring/clazz/format/Spring-AnnotationFormatterFactory.md) +- [Spring-Formatter](/docs/Spring/clazz/format/Spring-Formatter.md) +- [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 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) + +### Spring Cloud + +- [Spring Cloud Commons 源码](docs/SpringCloud/spring-cloud-commons-source-note.md) +- [Spring Cloud OpenFeign 源码](docs/SpringCloud/spring-cloud-openfeign-source-note.md) +- [Spring Cloud Gateway 源码](docs/SpringCloud/spring-cloud-gateway-source-note.md) + +### SpringSecurity + +- [SpringSecurity 请求全过程解析](/docs/SpringSecurity/SpringSecurity请求全过程解析.md) +- [SpringSecurity 自定义用户认证](/docs/SpringSecurity/SpringSecurity自定义用户认证.md) +- [SpringSecurity 流程补充](/docs/SpringSecurity/SpringSecurity流程补充.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-DynamicSqlSource](/docs/Mybatis/核心处理层/Mybatis-DynamicSqlSource.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) +- [Mybatis-GenericTokenParser](/docs/Mybatis/核心处理层/Mybatis-GenericTokenParser.md) ## Netty ### 网络 IO 技术基础 -* [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md) -* [IO模型](docs/Netty/IOTechnologyBase/IO模型.md) -* [详解selector、poll和epoll]() -* [四种IO编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md) +- [把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍](docs/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍.md) +- [IO 模型](docs/Netty/IOTechnologyBase/IO模型.md) +- [四种 IO 编程及对比](docs/Netty/IOTechnologyBase/四种IO编程及对比.md) -### Netty 粘拆包解决方案 +### JDK1.8 NIO 包 核心组件源码剖析 -* [TCP粘拆包问题及Netty中的解决方案](docs/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案.md) +- [Selector、SelectionKey 及 Channel 组件](docs/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件.md) -### Netty 编解码 +### Netty 粘拆包及解决方案 -* [Java序列化缺点与主流编解码框架](docs/Netty/Netty编解码/Java序列化缺点与主流编解码框架.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) -* [ChannelPipeline 和 ChannelHandler组件](docs/Netty/Netty主要组件源码分析/ChannelPipeline和ChannelHandler组件.md) -* [EventLoop 和 EventLoopGroup组件](docs/Netty/Netty主要组件源码分析/EventLoop和EventLoopGroup组件.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 高性能之道](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) +- [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) -* [多协议支持](docs/Dubbo/RPC/多协议支持.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) + +## Nacos + +- [nacos 服务注册](docs/nacos/nacos-discovery.md) + +## Sentinel + +- [sentinel 时间窗口实现](docs/Sentinel/Sentinel时间窗口的实现.md) +- [Sentinel 底层 LongAdder 的计数实现](docs/Sentinel/Sentinel底层LongAdder的计数实现.md) +- [Sentinel 限流算法的实现](docs/Sentinel/Sentinel限流算法的实现.md) + +## RocketMQ + +- [RocketMQ NameServer 与 Broker 的通信](docs/rocketmq/rocketmq-nameserver-broker.md) +- [RocketMQ 生产者启动流程](docs/rocketmq/rocketmq-producer-start.md) +- [RocketMQ 消息发送流程](docs/rocketmq/rocketmq-send-message.md) +- [RocketMQ 消息发送存储流程](docs/rocketmq/rocketmq-send-store.md) +- [RocketMQ MappedFile 内存映射文件详解](docs/rocketmq/rocketmq-mappedfile-detail.md) +- [RocketMQ ConsumeQueue 详解](docs/rocketmq/rocketmq-consumequeue.md) +- [RocketMQ CommitLog 详解](docs/rocketmq/rocketmq-commitlog.md) +- [RocketMQ IndexFile 详解](docs/rocketmq/rocketmq-indexfile.md) +- [RocketMQ 消费者启动流程](docs/rocketmq/rocketmq-consumer-start.md) +- [RocketMQ 消息拉取流程](docs/rocketmq/rocketmq-pullmessage.md) +- [RocketMQ Broker 处理拉取消息请求流程](docs/rocketmq/rocketmq-pullmessage-processor.md) +- [RocketMQ 消息消费流程](docs/rocketmq/rocketmq-consume-message-process.md) ## 番外篇(JDK 1.8) -* [Executor 线程池组件 源码赏析](docs/JDK/Executor线程池组件.md) -* [Lock 锁组件 源码赏析](docs/JDK/Lock锁组件.md) -* [Thread类 源码赏析](docs/JDK/Thread.md) -* [ThreadLocal类 源码赏析](docs/JDK/ThreadLocal.md) -* [HashMap类 源码赏析](docs/JDK/HashMap.md) -* [ConcurrentHashMap类 源码赏析](docs/JDK/ConcurrentHashMap.md) -* [String类 源码赏析](docs/JDK/String.md) +### 基础类库 -## 学习心得 +- [String 类 源码赏析](docs/JDK/basic/String.md) +- [Thread 类 源码赏析](docs/JDK/basic/Thread.md) +- [ThreadLocal 类 源码赏析](docs/JDK/basic/ThreadLocal.md) -### 个人经验 +### 集合 -* [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.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) +- [Semaphore 类 源码赏析](docs/JDK/concurrentCoding/Semaphore.md) + +## 学习心得 -### 编码规范 +### 个人经验 -* [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md) +- [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.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) -* [从框架源码中学习设计模式的感悟](docs/LearningExperience/DesignPattern/从框架源码中学习设计模式的感悟.md) +- [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)]() +- [从 Spring 及 Mybatis 框架源码中学习设计模式(行为型)]() +- [从 Spring 及 Mybatis 框架源码中学习设计模式(结构型)]() ### 多线程 -* [Java并发编程在各主流框架中的应用](docs/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用.md) +- [Java 并发编程在各主流框架中的应用](docs/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用.md) --- +## Stars 趋势 + +[![Star History Chart](https://api.star-history.com/svg?repos=doocs/source-code-hunter&type=Date)](https://star-history.com/#doocs/source-code-hunter&Date) + ## Doocs 社区优质项目 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 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 | ![](https://badgen.net/github/stars/doocs/advanced-java)
![](https://badgen.net/github/forks/doocs/advanced-java) | +| 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。 | ![](https://badgen.net/github/stars/doocs/leetcode)
![](https://badgen.net/github/forks/doocs/leetcode) | +| 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 | ![](https://badgen.net/github/stars/doocs/source-code-hunter)
![](https://badgen.net/github/forks/doocs/source-code-hunter) | +| 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 | ![](https://badgen.net/github/stars/doocs/jvm)
![](https://badgen.net/github/forks/doocs/jvm) | +| 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 | ![](https://badgen.net/github/stars/doocs/coding-interview)
![](https://badgen.net/github/forks/doocs/coding-interview) | +| 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 | ![](https://badgen.net/github/stars/doocs/md)
![](https://badgen.net/github/forks/doocs/md) | +| 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 | ![](https://badgen.net/github/stars/doocs/technical-books)
![](https://badgen.net/github/forks/doocs/technical-books) | ## 贡献者 @@ -276,3 +367,22 @@ GitHub 技术社区 [Doocs](https://github.com/doocs),致力于打造一个内 + +## 公众号 + +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 + + + + + + +
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取 [互联网 Java 工程师进阶知识完全扫盲](https://github.com/doocs/advanced-java) 项目离线 PDF 文档(283 页精华),学习更加方便! + +![](./images/pdf.png) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 00000000..40a4982b --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,519 @@ +import { defineConfig } from 'vitepress' + +export default defineConfig({ + title: "Source Code Hunter", + description: "读尽天下源码,心中自然无码——源码猎人", + head: [ + ['meta', { name: 'keywords', content: 'doc,docs,doocs,documentation,github,gitee,source-code-hunter' }], + ['meta', { name: 'description', content: '读尽天下源码,心中自然无码——源码猎人' }], + ['link', { rel: 'icon', type: 'image/png', href: 'https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/favicon-32x32.png' }] + ], + ignoreDeadLinks: true, + themeConfig: { + search: { + provider: 'local' + }, + footer: { + message: 'Released under the CC-BY-SA-4.0 license.', + copyright: `Copyright © 2018-${new Date().getFullYear()} Doocs` + }, + logo: 'https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/favicon-32x32.png', + docFooter: { + prev: '上一篇', + next: '下一篇' + }, + editLink: { + pattern: 'https://github.com/doocs/source-code-hunter/edit/main/docs/:path', + text: '在 GitHub 编辑' + }, + nav: [ + { text: '首页', link: '/' }, + { text: 'Spring系列', link: '/Spring/IoC/1、BeanDefinition的资源定位过程' }, + { text: 'Mybatis', link: '/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列' }, + { text: 'Netty', link: '/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍' }, + { text: 'Dubbo', link: '/Dubbo/architectureDesign/Dubbo整体架构' }, + { text: 'Tomcat', link: '/Tomcat/servlet-api源码赏析' }, + { text: 'Redis', link: '/Redis/redis-sds' }, + { text: 'JDK 1.8', link: '/JDK/basic/String' }, + { text: '学习心得', link: '/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么' } + ], + sidebar: [ + { + text: 'Spring 系列', + collapsed: true, + items: [ + { + text: 'IoC 容器', + collapsed: true, + items: [ + { text: 'BeanDefinition 的资源定位过程', link: '/Spring/IoC/1、BeanDefinition的资源定位过程' }, + { text: '将 bean 解析封装成 BeanDefinition', link: '/Spring/IoC/2、将bean解析封装成BeanDefinition' }, + { text: '将 BeanDefinition 注册进 IoC 容器', link: '/Spring/IoC/3、将BeanDefinition注册进IoC容器' }, + { text: '依赖注入(DI)', link: '/Spring/IoC/4、依赖注入(DI)' }, + { text: 'BeanFactoryPostProcessor', link: '/Spring/IoC/BeanFactoryPostProcessor' }, + { text: 'BeanPostProcessor', link: '/Spring/IoC/BeanPostProcessor' }, + { text: 'Spring BeanFactory 源码解析', link: '/Spring/clazz/Spring-beanFactory' }, + { text: '循环依赖', link: '/Spring/IoC/循环依赖' }, + ], + }, + { + text: 'AOP', + collapsed: true, + items: [ + { text: 'AOP 源码实现及分析', link: '/Spring/AOP/AOP源码实现及分析' }, + { text: 'JDK 动态代理的实现原理解析', link: '/Spring/AOP/JDK动态代理的实现原理解析' }, + { text: 'Spring AOP 如何生效', link: '/Spring/AOP/Spring-Aop如何生效' }, + ], + }, + { + text: 'SpringMVC', + collapsed: true, + items: [ + { text: 'IoC 容器在 Web 环境中的启动', link: '/Spring/SpringMVC/IoC容器在Web环境中的启动' }, + { text: 'SpringMVC 的设计与实现', link: '/Spring/SpringMVC/SpringMVC的设计与实现' }, + { text: 'SpringMVC 跨域解析', link: '/Spring/SpringMVC/SpringMVC-CROS' }, + { text: 'Spring-MVC-HandlerMapping', link: '/Spring/mvc/Spring-MVC-HandlerMapping' }, + { text: 'Spring-mvc-MappingRegistry', link: '/Spring/mvc/Spring-mvc-MappingRegistry' }, + ], + }, + { + text: 'SpringJDBC', + collapsed: true, + items: [ + { text: '努力编写中...', link: '' }, + ], + }, + { + text: 'Spring 事务', + collapsed: true, + items: [ + { text: 'Spring 与事务处理', link: '/Spring/SpringTransaction/Spring与事务处理' }, + { text: 'Spring 声明式事务处理', link: '/Spring/SpringTransaction/Spring声明式事务处理' }, + { text: 'Spring 事务处理的设计与实现', link: '/Spring/SpringTransaction/Spring事务处理的设计与实现' }, + { text: 'Spring 事务管理器的设计与实现', link: '/Spring/SpringTransaction/Spring事务管理器的设计与实现' }, + { text: 'Spring 事务解析', link: '/Spring/TX/Spring-transaction' }, + ], + }, + { + text: 'Spring 源码故事(瞎编版)', + collapsed: true, + items: [ + { text: '面筋哥 IoC 容器的一天(上)', link: '/Spring/Spring源码故事(瞎编版)/面筋哥IoC容器的一天(上)' }, + ], + }, + { + text: 'Spring 整体脉络', + collapsed: true, + items: [ + { text: '16 张图解锁 Spring 的整体脉络', link: '/Spring/Spring整体脉络/16张图解锁Spring的整体脉络' }, + ], + }, + { + text: 'Spring 类解析', + collapsed: true, + items: [ + { text: 'Spring 自定义标签解析', link: '/Spring/clazz/Spring-Custom-label-resolution' }, + { text: 'Spring Scan 包扫描', link: '/Spring/clazz/Spring-scan' }, + { text: 'Spring 注解工具类', link: '/Spring/clazz/Spring-AnnotationUtils' }, + { text: 'Spring 别名注册', link: '/Spring/clazz/Spring-SimpleAliasRegistry' }, + { text: 'Spring 标签解析类', link: '/Spring/clazz/Spring-BeanDefinitionParserDelegate' }, + { text: 'Spring ApplicationListener', link: '/Spring/clazz/Spring-ApplicationListener' }, + { text: 'Spring messageSource', link: '/Spring/clazz/Spring-MessageSource' }, + { text: 'Spring 自定义属性解析器', link: '/Spring/clazz/Spring-Custom-attribute-resolver' }, + { text: 'Spring 排序工具', link: '/Spring/clazz/Spring-OrderUtils' }, + { text: 'Spring-import 注解', link: '/Spring/clazz/Spring-Import' }, + { text: 'Spring-定时任务', link: '/Spring/clazz/Spring-Scheduling' }, + { text: 'Spring StopWatch', link: '/Spring/clazz/Spring-StopWatch' }, + { text: 'Spring 元数据', link: '/Spring/clazz/Spring-Metadata' }, + { text: 'Spring 条件接口', link: '/Spring/clazz/Spring-Conditional' }, + { text: 'Spring MultiValueMap', link: '/Spring/clazz/Spring-MultiValueMap' }, + { text: 'Spring MethodOverride', link: '/Spring/clazz/Spring-MethodOverride' }, + { text: 'Spring BeanDefinitionReaderUtils', link: '/Spring/clazz/Spring-BeanDefinitionReaderUtils' }, + { text: 'Spring PropertyPlaceholderHelper', link: '/Spring/clazz/Spring-PropertyPlaceholderHelper' }, + { text: 'Spring PropertySources', link: '/Spring/clazz/Spring-PropertySources' }, + { text: 'Spring-AnnotationFormatterFactory', link: '/Spring/clazz/format/Spring-AnnotationFormatterFactory' }, + { text: 'Spring-Formatter', link: '/Spring/clazz/format/Spring-Formatter' }, + { text: 'Spring-Parser', link: '/Spring/clazz/format/Spring-Parser' }, + { text: 'Spring-Printer', link: '/Spring/clazz/format/Spring-Printer' }, + ], + }, + { + text: 'Spring5 新特性', + collapsed: true, + items: [ + { text: 'Spring5-spring.components 解析', link: '/Spring/Spring5新特性/Spring-spring-components' }, + ], + }, + { + text: 'Spring RMI', + collapsed: true, + items: [ + { text: 'Spring RMI', link: '/Spring/RMI/Spring-RMI' }, + ], + }, + { + text: 'Spring Message', + collapsed: true, + items: [ + { text: 'Spring EnableJMS', link: '/Spring/message/Spring-EnableJms' }, + { text: 'Spring JmsTemplate', link: '/Spring/message/Spring-JmsTemplate' }, + { text: 'Spring MessageConverter', link: '/Spring/message/Spring-MessageConverter' }, + ], + }, + { + text: 'SpringBoot', + collapsed: true, + items: [ + { text: 'SpringBoot run 方法解析', link: '/SpringBoot/Spring-Boot-Run' }, + { text: 'SpringBoot 配置加载解析', link: '/SpringBoot/SpringBoot-application-load' }, + { text: 'SpringBoot 自动装配', link: '/SpringBoot/SpringBoot-自动装配' }, + { text: 'SpringBoot ConfigurationProperties', link: '/SpringBoot/SpringBoot-ConfigurationProperties' }, + { text: 'SpringBoot 日志系统', link: '/SpringBoot/SpringBoot-LogSystem' }, + { text: 'SpringBoot ConditionalOnBean', link: '/SpringBoot/SpringBoot-ConditionalOnBean' }, + ], + }, + { + text: 'Spring Cloud', + collapsed: true, + items: [ + { text: 'Spring Cloud Commons 源码', link: '/SpringCloud/spring-cloud-commons-source-note' }, + { text: 'Spring Cloud OpenFeign 源码', link: '/SpringCloud/spring-cloud-openfeign-source-note' }, + { text: 'Spring Cloud Gateway 源码', link: '/SpringCloud/spring-cloud-gateway-source-note' }, + ], + }, + { + text: 'SpringSecurity', + collapsed: true, + items: [ + { text: 'SpringSecurity 请求全过程解析', link: '/SpringSecurity/SpringSecurity请求全过程解析' }, + // { text: 'SpringSecurity 自定义用户认证', link: '/SpringSecurity/SpringSecurity自定义用户认证' }, + // { text: 'SpringSecurity 流程补充', link: '/SpringSecurity/SpringSecurity流程补充' }, + ], + }, + ], + }, + { + text: 'MyBatis', + collapsed: true, + items: [ + { + text: '基础支持层', + collapsed: true, + items: [ + { text: '反射工具箱和 TypeHandler 系列', link: '/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列' }, + { text: 'DataSource 及 Transaction 模块', link: '/Mybatis/基础支持层/2、DataSource及Transaction模块' }, + { text: 'binding 模块', link: '/Mybatis/基础支持层/3、binding模块' }, + { text: '缓存模块', link: '/Mybatis/基础支持层/4、缓存模块' }, + ], + }, + { + text: '核心处理层', + collapsed: true, + items: [ + { text: 'MyBatis 初始化', link: '/Mybatis/核心处理层/1、MyBatis初始化' }, + { text: 'SqlNode 和 SqlSource', link: '/Mybatis/核心处理层/2、SqlNode和SqlSource' }, + { text: 'ResultSetHandler', link: '/Mybatis/核心处理层/3、ResultSetHandler' }, + { text: 'StatementHandler', link: '/Mybatis/核心处理层/4、StatementHandler' }, + { text: 'Executor 组件', link: '/Mybatis/核心处理层/5、Executor组件' }, + { text: 'SqlSession 组件', link: '/Mybatis/核心处理层/6、SqlSession组件' }, + ], + }, + { + text: '类解析', + collapsed: true, + items: [ + { text: 'Mybatis-Cache', link: '/Mybatis/基础支持层/Mybatis-Cache' }, + { text: 'Mybatis-log', link: '/Mybatis/基础支持层/Mybatis-log' }, + { text: 'Mybatis-Reflector', link: '/Mybatis/基础支持层/Mybatis-Reflector' }, + { text: 'Mybatis-Alias', link: '/Mybatis/核心处理层/Mybatis-Alias' }, + { text: 'Mybatis-Cursor', link: '/Mybatis/核心处理层/Mybatis-Cursor' }, + { text: 'Mybatis-DataSource', link: '/Mybatis/核心处理层/Mybatis-DataSource' }, + { text: 'Mybatis-DynamicSqlSource', link: '/Mybatis/核心处理层/Mybatis-DynamicSqlSource' }, + { text: 'Mybatis-MapperMethod', link: '/Mybatis/核心处理层/Mybatis-MapperMethod' }, + { text: 'Mybatis-MetaObject', link: '/Mybatis/核心处理层/Mybatis-MetaObject' }, + { text: 'Mybatis-MethodSignature', link: '/Mybatis/核心处理层/Mybatis-MethodSignature' }, + { text: 'Mybatis-ObjectWrapper', link: '/Mybatis/核心处理层/Mybatis-ObjectWrapper' }, + { text: 'Mybatis-ParamNameResolver', link: '/Mybatis/核心处理层/Mybatis-ParamNameResolver' }, + { text: 'Mybatis-SqlCommand', link: '/Mybatis/核心处理层/Mybatis-SqlCommand' }, + { text: 'Mybatis-GenericTokenParser', link: '/Mybatis/核心处理层/Mybatis-GenericTokenParser' }, + ], + }, + ], + }, + { + text: 'Netty', + collapsed: true, + items: [ + { + text: '网络 IO 技术基础', + collapsed: true, + items: [ + { text: '把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍', link: '/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍' }, + { text: 'IO 模型', link: '/Netty/IOTechnologyBase/IO模型' }, + { text: '四种 IO 编程及对比', link: '/Netty/IOTechnologyBase/四种IO编程及对比' }, + ], + }, + { + text: 'JDK1.8 NIO 包 核心组件源码剖析', + collapsed: true, + items: [ + { text: 'Selector、SelectionKey 及 Channel 组件', link: '/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件' }, + ], + }, + { + text: 'Netty 粘拆包及解决方案', + collapsed: true, + items: [ + { text: 'TCP 粘拆包问题及 Netty 中的解决方案', link: '/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案' }, + ], + }, + { + text: 'Netty 多协议开发', + collapsed: true, + items: [ + { text: '基于 HTTP 协议的 Netty 开发', link: '/Netty/Netty多协议开发/基于HTTP协议的Netty开发' }, + { text: '基于 WebSocket 协议的 Netty 开发', link: '/Netty/Netty多协议开发/基于WebSocket协议的Netty开发' }, + { text: '基于自定义协议的 Netty 开发', link: '/Netty/Netty多协议开发/基于自定义协议的Netty开发' }, + ], + }, + { + text: '基于 Netty 开发服务端及客户端', + collapsed: true, + items: [ + { text: '基于 Netty 的服务端开发', link: '/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发' }, + { text: '基于 Netty 的客户端开发', link: '/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发' }, + ], + }, + { + text: 'Netty 主要组件的源码分析', + collapsed: true, + items: [ + { text: 'ByteBuf 组件', link: '/Netty/Netty主要组件源码分析/ByteBuf组件' }, + { text: 'Channel 组件 和 Unsafe 组件', link: '/Netty/Netty主要组件源码分析/Channel和Unsafe组件' }, + { text: 'EventLoop 组件', link: '/Netty/Netty主要组件源码分析/EventLoop组件' }, + { text: 'ChannelPipeline 和 ChannelHandler 组件', link: '/Netty/Netty主要组件源码分析/ChannelPipeline和ChannelHandler组件' }, + { text: 'Future 和 Promise 组件', link: '/Netty/Netty主要组件源码分析/Future和Promise组件' }, + ], + }, + { + text: 'Netty 高级特性', + collapsed: true, + items: [ + { text: 'Netty 架构设计', link: '/Netty/AdvancedFeaturesOfNetty/Netty架构设计' }, + { text: 'Netty 高性能之道', link: '/Netty/AdvancedFeaturesOfNetty/Netty高性能之道' }, + ], + }, + { + text: 'Netty 技术细节源码分析', + collapsed: true, + items: [ + { text: 'FastThreadLocal 源码分析', link: '/Netty/Netty技术细节源码分析/FastThreadLocal源码分析' }, + { text: 'Recycler 对象池原理分析', link: '/Netty/Netty技术细节源码分析/Recycler对象池原理分析' }, + { text: 'MpscLinkedQueue 队列原理分析', link: '/Netty/Netty技术细节源码分析/MpscLinkedQueue队列原理分析' }, + { text: 'HashedWheelTimer 时间轮原理分析', link: '/Netty/Netty技术细节源码分析/HashedWheelTimer时间轮原理分析' }, + { text: 'HashedWheelTimer & schedule', link: '/Netty/Netty技术细节源码分析/HashedWheelTimer&schedule' }, + { text: 'ByteBuf 的内存泄漏原因与检测原理', link: '/Netty/Netty技术细节源码分析/ByteBuf的内存泄漏原因与检测原理' }, + { text: '内存池之 PoolChunk 设计与实现', link: '/Netty/Netty技术细节源码分析/内存池之PoolChunk设计与实现' }, + { text: '内存池之从内存池申请内存', link: '/Netty/Netty技术细节源码分析/内存池之从内存池申请内存' }, + ], + }, + ], + }, + { + text: 'Dubbo', + collapsed: true, + items: [ + { + text: '架构设计', + collapsed: true, + items: [ + { text: 'Dubbo 整体架构', link: '/Dubbo/architectureDesign/Dubbo整体架构' }, + ], + }, + { + text: 'SPI 机制', + collapsed: true, + items: [ + { text: 'Dubbo 与 Java 的 SPI 机制', link: '/Dubbo/SPI/Dubbo与Java的SPI机制' }, + ], + }, + { + text: '注册中心', + collapsed: true, + items: [ + { text: 'Dubbo 注册中心模块简析', link: '/Dubbo/registry/Dubbo注册中心模块简析' }, + { text: '注册中心的 Zookeeper 实现', link: '/Dubbo/registry/注册中心的Zookeeper实现' }, + ], + }, + { + text: '远程通信', + collapsed: true, + items: [ + { text: 'Dubbo 远程通信模块简析', link: '/Dubbo/remote/Dubbo远程通信模块简析' }, + { text: 'Transport 组件', link: '/Dubbo/remote/Transport组件' }, + { text: 'Exchange 组件', link: '/Dubbo/remote/Exchange组件' }, + { text: 'Buffer 组件', link: '/Dubbo/remote/Buffer组件' }, + { text: '基于 Netty 实现远程通信', link: '/Dubbo/remote/基于Netty实现远程通信' }, + { text: '基于 HTTP 实现远程通信', link: '/Dubbo/remote/基于HTTP实现远程通信' }, + ], + }, + { + text: 'RPC', + collapsed: true, + items: [ + { text: 'RPC 模块简析', link: '/Dubbo/RPC/RPC模块简析' }, + { text: 'Protocol 组件', link: '/Dubbo/RPC/Protocol组件' }, + { text: 'Proxy 组件', link: '/Dubbo/RPC/Proxy组件' }, + { text: 'Dubbo 协议', link: '/Dubbo/RPC/Dubbo协议' }, + { text: 'Hessian 协议', link: '/Dubbo/RPC/Hessian协议' }, + ], + }, + { + text: '集群', + collapsed: true, + items: [ + { text: 'Dubbo 集群模块简析', link: '/Dubbo/cluster/Dubbo集群模块简析' }, + { text: '负载均衡', link: '/Dubbo/cluster/负载均衡' }, + { text: '集群容错', link: '/Dubbo/cluster/集群容错' }, + { text: 'mock 与服务降级', link: '/Dubbo/cluster/mock与服务降级' }, + ], + }, + ], + }, + { + text: 'Tomcat', + collapsed: true, + items: [ + { + text: 'Servlet 与 Servlet 容器', + collapsed: true, + items: [ + { text: 'servlet-api 源码赏析', link: '/Tomcat/servlet-api源码赏析' }, + { text: '一个简单的 Servlet 容器', link: '/Tomcat/一个简单的servlet容器代码设计' }, + { text: 'Servlet 容器详解', link: '/Tomcat/servlet容器详解' }, + ], + }, + { + text: 'Web 容器', + collapsed: true, + items: [ + { text: '一个简单的 Web 服务器', link: '/Tomcat/一个简单的Web服务器代码设计' }, + ], + }, + ], + }, + { + text: 'Redis', + collapsed: true, + items: [ + { text: '深挖 Redis 6.0 源码——SDS', link: '/Redis/redis-sds' }, + ], + }, + { + text: 'Nacos', + collapsed: true, + items: [ + { text: 'nacos 服务注册', link: '/nacos/nacos-discovery' }, + ], + }, + { + text: 'Sentinel', + collapsed: true, + items: [ + { text: 'sentinel 时间窗口实现', link: '/Sentinel/Sentinel时间窗口的实现' }, + { text: 'Sentinel 底层 LongAdder 的计数实现', link: '/Sentinel/Sentinel底层LongAdder的计数实现' }, + { text: 'Sentinel 限流算法的实现', link: '/Sentinel/Sentinel限流算法的实现' }, + ], + }, + { + text: 'RocketMQ', + collapsed: true, + items: [ + { text: 'RocketMQ NameServer 与 Broker 的通信', link: '/rocketmq/rocketmq-nameserver-broker' }, + { text: 'RocketMQ 生产者启动流程', link: '/rocketmq/rocketmq-producer-start' }, + { text: 'RocketMQ 消息发送流程', link: '/rocketmq/rocketmq-send-message' }, + { text: 'RocketMQ 消息发送存储流程', link: '/rocketmq/rocketmq-send-store' }, + { text: 'RocketMQ MappedFile 内存映射文件详解', link: '/rocketmq/rocketmq-mappedfile-detail' }, + { text: 'RocketMQ ConsumeQueue 详解', link: '/rocketmq/rocketmq-consumequeue' }, + { text: 'RocketMQ CommitLog 详解', link: '/rocketmq/rocketmq-commitlog' }, + { text: 'RocketMQ IndexFile 详解', link: '/rocketmq/rocketmq-indexfile' }, + { text: 'RocketMQ 消费者启动流程', link: '/rocketmq/rocketmq-consumer-start' }, + { text: 'RocketMQ 消息拉取流程', link: '/rocketmq/rocketmq-pullmessage' }, + { text: 'RocketMQ Broker 处理拉取消息请求流程', link: '/rocketmq/rocketmq-pullmessage-processor' }, + { text: 'RocketMQ 消息消费流程', link: '/rocketmq/rocketmq-consume-message-process' }, + ], + }, + { + text: '番外篇(JDK 1.8)', + collapsed: true, + items: [ + { + text: '基础类库', + collapsed: true, + items: [ + { text: 'String 类 源码赏析', link: '/JDK/basic/String' }, + { text: 'Thread 类 源码赏析', link: '/JDK/basic/Thread' }, + { text: 'ThreadLocal 类 源码赏析', link: '/JDK/basic/ThreadLocal' }, + ], + }, + { + text: '集合', + collapsed: true, + items: [ + { text: 'HashMap 类 源码赏析', link: '/JDK/collection/HashMap' }, + { text: 'ConcurrentHashMap 类 源码赏析', link: '/JDK/collection/ConcurrentHashMap' }, + { text: 'LinkedHashMap 类 源码赏析', link: '/JDK/collection/LinkedHashMap' }, + { text: 'ArrayList 类 源码赏析', link: '/JDK/collection/ArrayList' }, + { text: 'LinkedList 类 源码赏析', link: '/JDK/collection/LinkedList' }, + { text: 'HashSet 类 源码赏析', link: '/JDK/collection/HashSet' }, + { text: 'TreeSet 类 源码赏析', link: '/JDK/collection/TreeSet' }, + ], + }, + { + text: '并发编程', + collapsed: true, + items: [ + { text: 'JUC 并发包 UML 全量类图', link: '/JDK/concurrentCoding/JUC并发包UML全量类图' }, + { text: 'Executor 线程池组件 源码赏析', link: '/JDK/concurrentCoding/Executor线程池组件' }, + { text: 'Lock 锁组件 源码赏析', link: '/JDK/concurrentCoding/Lock锁组件' }, + { text: '详解 AbstractQueuedSynchronizer 抽象类', link: '/JDK/concurrentCoding/详解AbstractQueuedSynchronizer' }, + { text: 'Semaphore 类 源码赏析', link: '/JDK/concurrentCoding/Semaphore' }, + ], + }, + ], + }, + { + text: '学习心得', + collapsed: true, + items: [ + { + text: '个人经验', + collapsed: true, + items: [ + { text: '初级开发者应该从 Spring 源码中学什么', link: '/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么' }, + ], + }, + { + text: '设计模式', + collapsed: true, + items: [ + { text: '从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)', link: '/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型)' }, + { text: '从 Spring 及 Mybatis 框架源码中学习设计模式(行为型)', link: '/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型)' }, + { text: '从 Spring 及 Mybatis 框架源码中学习设计模式(结构型)', link: '/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型)' }, + ], + }, + { + text: '多线程', + collapsed: true, + items: [ + { text: 'Java 并发编程在各主流框架中的应用', link: '/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用' }, + ], + }, + ], + }, + ], + socialLinks: [ + { icon: 'github', link: 'https://github.com/doocs/source-code-hunter' } + ], + } +}) diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue new file mode 100644 index 00000000..87df4f7c --- /dev/null +++ b/docs/.vitepress/theme/Layout.vue @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js new file mode 100644 index 00000000..d6b5382f --- /dev/null +++ b/docs/.vitepress/theme/index.js @@ -0,0 +1,41 @@ +import DefaultTheme from "vitepress/theme"; +import giscusTalk from "vitepress-plugin-comment-with-giscus"; +import { useData, useRoute } from "vitepress"; +import { toRefs } from "vue"; +import Layout from "./Layout.vue"; + +export default { + extends: DefaultTheme, + Layout: Layout, + enhanceApp(ctx) { + DefaultTheme.enhanceApp(ctx); + }, + setup() { + const { frontmatter } = toRefs(useData()); + const route = useRoute(); + + giscusTalk( + { + repo: "doocs/source-code-hunter", + category: "Announcements", + categoryId: "DIC_kwDODP2NQc4CpG9T", + repoId: "MDEwOlJlcG9zaXRvcnkyMTc5NDMzNjE=", + mapping: "pathname", + inputPosition: "top", + lang: "zh-CN", + homePageShowComment: true, + term: "9", + strict: "0", + reactionsEnabled: "1", + loading: "lazy", + lightTheme: "light", + darkTheme: "transparent_dark", + }, + { + frontmatter, + route, + }, + true + ); + }, +}; \ No newline at end of file diff --git "a/docs/Dubbo/RPC/Dubbo\345\215\217\350\256\256.md" "b/docs/Dubbo/RPC/Dubbo\345\215\217\350\256\256.md" new file mode 100644 index 00000000..b3787c53 --- /dev/null +++ "b/docs/Dubbo/RPC/Dubbo\345\215\217\350\256\256.md" @@ -0,0 +1 @@ +努力编写中... diff --git "a/docs/Dubbo/RPC/Hessian\345\215\217\350\256\256.md" "b/docs/Dubbo/RPC/Hessian\345\215\217\350\256\256.md" new file mode 100644 index 00000000..b3787c53 --- /dev/null +++ "b/docs/Dubbo/RPC/Hessian\345\215\217\350\256\256.md" @@ -0,0 +1 @@ +努力编写中... diff --git "a/docs/Dubbo/RPC/Protocol\347\273\204\344\273\266.md" "b/docs/Dubbo/RPC/Protocol\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/RPC/Protocol\347\273\204\344\273\266.md" +++ "b/docs/Dubbo/RPC/Protocol\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/RPC/Proxy\347\273\204\344\273\266.md" "b/docs/Dubbo/RPC/Proxy\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/RPC/Proxy\347\273\204\344\273\266.md" +++ "b/docs/Dubbo/RPC/Proxy\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/RPC/RPC\346\250\241\345\235\227\347\256\200\346\236\220.md" "b/docs/Dubbo/RPC/RPC\346\250\241\345\235\227\347\256\200\346\236\220.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/RPC/RPC\346\250\241\345\235\227\347\256\200\346\236\220.md" +++ "b/docs/Dubbo/RPC/RPC\346\250\241\345\235\227\347\256\200\346\236\220.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/RPC/\345\244\232\345\215\217\350\256\256\346\224\257\346\214\201.md" "b/docs/Dubbo/RPC/\345\244\232\345\215\217\350\256\256\346\224\257\346\214\201.md" deleted file mode 100644 index fcb5dbe4..00000000 --- "a/docs/Dubbo/RPC/\345\244\232\345\215\217\350\256\256\346\224\257\346\214\201.md" +++ /dev/null @@ -1 +0,0 @@ -努力编写中... \ No newline at end of file diff --git "a/docs/Dubbo/SPI/Dubbo\344\270\216Java\347\232\204SPI\346\234\272\345\210\266.md" "b/docs/Dubbo/SPI/Dubbo\344\270\216Java\347\232\204SPI\346\234\272\345\210\266.md" index f701ced4..3c58476b 100644 --- "a/docs/Dubbo/SPI/Dubbo\344\270\216Java\347\232\204SPI\346\234\272\345\210\266.md" +++ "b/docs/Dubbo/SPI/Dubbo\344\270\216Java\347\232\204SPI\346\234\272\345\210\266.md" @@ -1,20 +1,25 @@ -## JDK的SPI思想 +## JDK 的 SPI 思想 + SPI,即 Service Provider Interface。在面向对象的设计里面,模块之间推荐基于接口编程,而不是对实现类进行硬编码,这样做也是为了模块设计的可插拔原则。 -比较典型的应用,如 JDBC,Java 定义了一套 JDBC 的接口,但是 Java 本身并不提供对 JDBC 的实现类,而是开发者根据项目实际使用的数据库来选择驱动程序 jar包,比如 mysql,你就将 mysql-jdbc-connector.jar 引入进来;oracle,你就将 oracle-jdbc-connector.jar 引入进来。在系统跑的时候,碰到你使用 jdbc 的接口,他会在底层使用你引入的那个 jar 中提供的实现类。 +比较典型的应用,如 JDBC,Java 定义了一套 JDBC 的接口,但是 Java 本身并不提供对 JDBC 的实现类,而是开发者根据项目实际使用的数据库来选择驱动程序 jar 包,比如 mysql,你就将 mysql-jdbc-connector.jar 引入进来;oracle,你就将 oracle-jdbc-connector.jar 引入进来。在系统跑的时候,碰到你使用 jdbc 的接口,他会在底层使用你引入的那个 jar 中提供的实现类。 + +## Dubbo 的 SPI 扩展机制原理 + +dubbo 自己实现了一套 SPI 机制,并对 JDK 的 SPI 进行了改进。 -## Dubbo的SPI扩展机制原理 -dubbo自己实现了一套SPI机制,并对 JDK的SPI进行了改进。 -1. JDK标准的SPI只能通过遍历来查找扩展点和实例化,有可能导致一次性加载所有的扩展点,如果不是所有的扩展点都被用到,就会导致资源的浪费。dubbo每个扩展点都有多种实现,例如:com.alibaba.dubbo.rpc.Protocol接口有InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol等实现,如果只是用到其中一个实现,可是加载了全部的实现,会导致资源的浪费。 -2. 对配置文件中扩展实现的格式的修改,例如,META-INF/dubbo/com.xxx.Protocol 里的 com.foo.XxxProtocol格式 改为了 xxx = com.foo.XxxProtocol 这种以键值对的形式,这样做的目的是为了让我们更容易的定位到问题。比如,由于第三方库不存在,无法初始化,导致无法加载扩展点(“A”),当用户配置使用A时,dubbo就会报无法加载扩展点的错误,而不是报哪些扩展点的实现加载失败以及错误原因,**这是因为原来的配置格式没有记录扩展名的id,导致dubbo无法抛出较为精准的异常,这会加大排查问题的难度**。所以改成key-value的形式来进行配置。 -3. dubbo的SPI机制增加了对IOC、AOP的支持,一个扩展点可以直接通过setter注入到其他扩展点。 +1. JDK 标准的 SPI 只能通过遍历来查找扩展点和实例化,有可能导致一次性加载所有的扩展点,如果不是所有的扩展点都被用到,就会导致资源的浪费。dubbo 每个扩展点都有多种实现,例如:com.alibaba.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol 等实现,如果只是用到其中一个实现,可是加载了全部的实现,会导致资源的浪费。 +2. 对配置文件中扩展实现的格式的修改,例如,META-INF/dubbo/com.xxx.Protocol 里的 com.foo.XxxProtocol 格式 改为了 xxx = com.foo.XxxProtocol 这种以键值对的形式,这样做的目的是为了让我们更容易的定位到问题。比如,由于第三方库不存在,无法初始化,导致无法加载扩展点(“A”),当用户配置使用 A 时,dubbo 就会报无法加载扩展点的错误,而不是报哪些扩展点的实现加载失败以及错误原因,**这是因为原来的配置格式没有记录扩展名的 id,导致 dubbo 无法抛出较为精准的异常,这会加大排查问题的难度**。所以改成 key-value 的形式来进行配置。 +3. dubbo 的 SPI 机制增加了对 IOC、AOP 的支持,一个扩展点可以直接通过 setter 注入到其他扩展点。 -下面我们看一下Dubbo 的 SPI扩展机制实现的结构目录。 +下面我们看一下 Dubbo 的 SPI 扩展机制实现的结构目录。 -![avatar](../../../images/Dubbo/SPI组件目录结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/SPI组件目录结构.png) ### SPI 注解 -首先看一下 SPI注解。在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。比如,协议扩展接口Protocol,如果使用者在 <dubbo:protocol />、<dubbo:service />、<dubbo:reference /> 都没有指定 protocol属性 的话,那么就默认使用 DubboProtocol 作为接口Protocol的实现,因为在 Protocol 上有 @SPI("dubbo")注解。而这个 protocol属性值 或者默认值会被当作该接口的实现类中的一个key,dubbo 会去 META-INF.dubbo.internal下的com.alibaba.dubbo.rpc.Protocol文件中找该key对应的value,源码如下。 + +首先看一下 SPI 注解。在某个接口上加上 @SPI 注解后,表明该接口为可扩展接口。比如,协议扩展接口 Protocol,如果使用者在 <dubbo:protocol />、<dubbo:service />、<dubbo:reference /> 都没有指定 protocol 属性 的话,那么就默认使用 DubboProtocol 作为接口 Protocol 的实现,因为在 Protocol 上有 @SPI("dubbo")注解。而这个 protocol 属性值 或者默认值会被当作该接口的实现类中的一个 key,dubbo 会去 META-INF.dubbo.internal 下的 com.alibaba.dubbo.rpc.Protocol 文件中找该 key 对应的 value,源码如下。 + ```java /** * 协议接口 @@ -41,7 +46,7 @@ public interface Protocol { */ @Adaptive Exporter export(Invoker invoker) throws RpcException; - + /** * 引用远程服务:
* 1. 当用户调用 refer() 所返回的 Invoker 对象的 invoke() 方法时,协议需相应执行同 URL 远端 export() 传入的 Invoker 对象的 invoke() 方法。
@@ -56,7 +61,7 @@ public interface Protocol { */ @Adaptive Invoker refer(Class type, URL url) throws RpcException; - + /** * 释放协议:
* 1. 取消该协议所有已经暴露和引用的服务。
@@ -107,10 +112,13 @@ public @interface SPI { // 配置文件 com.alibaba.dubbo.rpc.Protocol 中的内容 dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol ``` -value 就是该 Protocol接口 的实现类 DubboProtocol,这样就做到了SPI扩展。 + +value 就是该 Protocol 接口 的实现类 DubboProtocol,这样就做到了 SPI 扩展。 ### ExtensionLoader -ExtensionLoader 扩展加载器,这是 dubbo 实现 SPI扩展机制 的核心,几乎所有实现的逻辑都被封装在 ExtensionLoader 中,其源码如下。 + +ExtensionLoader 扩展加载器,这是 dubbo 实现 SPI 扩展机制 的核心,几乎所有实现的逻辑都被封装在 ExtensionLoader 中,其源码如下。 + ```java /** * 拓展加载器,Dubbo使用的扩展点获取 @@ -496,7 +504,7 @@ public class ExtensionLoader { getExtensionClasses(); // 如果为 true ,不能继续调用 `#getExtension(true)` 方法,会形成死循环。 if (null == cachedDefaultName || cachedDefaultName.length() == 0 - || "true".equals(cachedDefaultName)) { + || "true".equals(cachedDefaultName)) { return null; } return getExtension(cachedDefaultName); @@ -1237,4 +1245,4 @@ public class ExtensionLoader { return this.getClass().getName() + "[" + type.getName() + "]"; } } -``` \ No newline at end of file +``` diff --git "a/docs/Dubbo/architectureDesign/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204.md" "b/docs/Dubbo/architectureDesign/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204.md" index 13520ef9..25f16d52 100644 --- "a/docs/Dubbo/architectureDesign/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204.md" +++ "b/docs/Dubbo/architectureDesign/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204.md" @@ -1,38 +1,51 @@ ## 项目结构 -首先从GitHub 上 clone下来 Dubbo项目,我们根据其中各子项目的项目名,也能大概猜出来各个模块的作用。 -![avatar](../../../images/Dubbo/dubbo项目结构.png) +首先从 GitHub 上 clone 下来 Dubbo 项目,我们根据其中各子项目的项目名,也能大概猜出来各个模块的作用。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo项目结构.png) ### dubbo-common + 公共逻辑子项目,定义了各子项目中 通用的 组件 和 工具类,如:IO、日志、配置处理等。 ### dubbo-rpc -分布式协调服务框架的核心,该模块定义了 RPC相关的组件,包括 服务发布、服务调用代理、远程调用结果、RPC调用网络协议,RPC调用监听器和过滤器等等。该模块提供了默认的 基于dubbo协议的实现,还提供了hessian、http、rmi、及webservice等协议的实现,能够满足绝大多数项目的使用需求,另外 还提供了对自定义协议的扩展。 + +分布式协调服务框架的核心,该模块定义了 RPC 相关的组件,包括 服务发布、服务调用代理、远程调用结果、RPC 调用网络协议,RPC 调用监听器和过滤器等等。该模块提供了默认的 基于 dubbo 协议的实现,还提供了 hessian、http、rmi、及 webservice 等协议的实现,能够满足绝大多数项目的使用需求,另外 还提供了对自定义协议的扩展。 ### dubbo-registry -注册中心子项目,它是 RPC 中 consumer服务消费者 和 provider服务提供者 两个重要角色的协调者,该子项目定义了核心的 注册中心组件,提供了 mutilcast、redis 和 zookeeper 等多种方式的注册中心实现,用于不同的使用场景。当然,几乎所有的项目都会选择基于zookeeper的实现。 + +注册中心子项目,它是 RPC 中 consumer 服务消费者 和 provider 服务提供者 两个重要角色的协调者,该子项目定义了核心的 注册中心组件,提供了 mutilcast、redis 和 zookeeper 等多种方式的注册中心实现,用于不同的使用场景。当然,几乎所有的项目都会选择基于 zookeeper 的实现。 ### dubbo-remoting -远程通讯子项目,RPC 的实现基础就是远程通讯,consmer 要调用 provider 的远程方法必须通过 远程通讯实现。该模块定义了远程传输器、endpoint 终端、客户端、服务端、编码解码器、数据交换、缓冲区、通讯异常定义 等核心组件。他是对于远程网络通讯的抽象,提供了诸如 netty、mina、http等 协议和技术框架的实现方式。 + +远程通讯子项目,RPC 的实现基础就是远程通讯,consmer 要调用 provider 的远程方法必须通过 远程通讯实现。该模块定义了远程传输器、endpoint 终端、客户端、服务端、编码解码器、数据交换、缓冲区、通讯异常定义 等核心组件。他是对于远程网络通讯的抽象,提供了诸如 netty、mina、http 等 协议和技术框架的实现方式。 ### dubbo-monitor -监控子项目,该模块可以监控服务调用的各种信息,例如调用耗时、调用量、调用结果等等,监控中心在调用过程中收集调用的信息,发送到监控服务,在监控服务中可以存储这些信息,对这些数据进行统计分析 和 展示。dubbo默认提供了一个实现,该实现非常简单,只是作为默认的实现范例,生产环境使用价值不高,往往需要自行实现。 + +监控子项目,该模块可以监控服务调用的各种信息,例如调用耗时、调用量、调用结果等等,监控中心在调用过程中收集调用的信息,发送到监控服务,在监控服务中可以存储这些信息,对这些数据进行统计分析 和 展示。dubbo 默认提供了一个实现,该实现非常简单,只是作为默认的实现范例,生产环境使用价值不高,往往需要自行实现。 ### dubbo-container -容器子项目,是一个独立的容器,以简单的 Main(类) 加载Spring启动,因为服务通常不需要Tomcat/JBoss等Web容器的特性,没必要用Web容器去加载服务。 + +容器子项目,是一个独立的容器,以简单的 Main(类) 加载 Spring 启动,因为服务通常不需要 Tomcat/JBoss 等 Web 容器的特性,没必要用 Web 容器去加载服务。 ### dubbo-config -配置中心子项目,该模块通过 配置信息,将dubbo组件的各个模块整合在一起,给 框架的使用者 提供 可配置的、易用的 分布式服务框架。它定义了面向dubbo使用者的各种信息配置,比如服务发布配置、方法发布配置、服务消费配置、应用程序配置、注册中心配置、协议配置、监控配置等等。 -### dubbo-cluster +配置中心子项目,该模块通过 配置信息,将 dubbo 组件的各个模块整合在一起,给 框架的使用者 提供 可配置的、易用的 分布式服务框架。它定义了面向 dubbo 使用者的各种信息配置,比如服务发布配置、方法发布配置、服务消费配置、应用程序配置、注册中心配置、协议配置、监控配置等等。 + +### dubbo-cluster + 集群子项目,将多个服务提供方伪装为一个提供方,包括:负载均衡、容错、路由等,集群的地址列表可以是静态配置的,也可以是由注册中心下发。 ### dubbo-admin -该子项目是一个web应用,可以独立部署,用于管理 dubbo服务,该管理应用可以连接注册中心,读取和更新 注册中心中的内容。 + +该子项目是一个 web 应用,可以独立部署,用于管理 dubbo 服务,该管理应用可以连接注册中心,读取和更新 注册中心中的内容。 ## 实现原理 + ### 角色类型与运行原理 -一个Dubbo项目 的角色主要分为如下五种。 + +一个 Dubbo 项目 的角色主要分为如下五种。 + - Provider:服务提供方; - Consumer:服务消费方; - Registry:服务注册与发现的注册中心; @@ -41,13 +54,15 @@ 其运行原理如下图所示。 -![avatar](../../../images/Dubbo/Dubbo工作原理图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Dubbo工作原理图.png) ### 工作原理 + 最后总结下其工作原理。 + 1. 服务导出:服务提供方 导出服务,监听服务端口; 2. 服务注册:服务提供方 注册服务信息到注册中心; 3. 服务订阅:服务消费方 订阅关注的服务; 4. 服务发现:当服务地址发生变更时,注册中心通知服务消费端; 5. 远程服务调用 :根据负载均衡策略 选择服务地址,直接调用; -6. 监控:监控器 收集和展示 服务提供方、服务消费方之间 的服务调用统计信息 。 \ No newline at end of file +6. 监控:监控器 收集和展示 服务提供方、服务消费方之间 的服务调用统计信息 。 diff --git "a/docs/Dubbo/cluster/Dubbo\351\233\206\347\276\244\346\250\241\345\235\227\347\256\200\346\236\220.md" "b/docs/Dubbo/cluster/Dubbo\351\233\206\347\276\244\346\250\241\345\235\227\347\256\200\346\236\220.md" index 51c6411f..cbe1c5a1 100644 --- "a/docs/Dubbo/cluster/Dubbo\351\233\206\347\276\244\346\250\241\345\235\227\347\256\200\346\236\220.md" +++ "b/docs/Dubbo/cluster/Dubbo\351\233\206\347\276\244\346\250\241\345\235\227\347\256\200\346\236\220.md" @@ -1,26 +1,30 @@ ### 集群模块简介 -集群,是指同一个服务 被部署在了多个服务器上,每个服务器的任务都相同,能够以较高的性价比,提升系统的 性能、可靠性、灵活性,但同时也要面对 集群中会出现的 负载均衡、容错等问题。dubbo的集群模块,主要涉及以下几部分内容。 -- 负载均衡策略:dubbo支持的所有负载均衡策略算法; + +集群,是指同一个服务 被部署在了多个服务器上,每个服务器的任务都相同,能够以较高的性价比,提升系统的 性能、可靠性、灵活性,但同时也要面对 集群中会出现的 负载均衡、容错等问题。dubbo 的集群模块,主要涉及以下几部分内容。 + +- 负载均衡策略:dubbo 支持的所有负载均衡策略算法; - 集群容错:Cluster 将 Directory 中的多个 Invoker 伪装成一个 Invoker,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个; -- 路由:dubbo路由规则,路由规则决定了一次dubbo服务调用的目标服务器,路由规则分两种:条件路由规则和脚本路由规则,并且支持可拓展; -- 配置:根据url上的配置规则生成配置信息; +- 路由:dubbo 路由规则,路由规则决定了一次 dubbo 服务调用的目标服务器,路由规则分两种:条件路由规则和脚本路由规则,并且支持可拓展; +- 配置:根据 url 上的配置规则生成配置信息; - 分组聚合:合并返回结果; -- 本地伪装:mock通常用于服务降级,mock只在非业务异常时执行,如 超时、网络异常等。 +- 本地伪装:mock 通常用于服务降级,mock 只在非业务异常时执行,如 超时、网络异常等。 -集群工作过程可分为两个阶段,第一个阶段是在消费者初始化期间,集群 Cluster 为消费者创建 ClusterInvoker 实例。第二个阶段是在消费者进行RPC时,以 FailoverClusterInvoker 为例,该实例首先会调用 Directory 的 list()方法 获取 Invoker列表,然后根据配置的 负载均衡策略,从 Invoker列表 中选择一个 Inovker,最后将参数传给选择出的 Invoker实例 进行真正的远程调用。 +集群工作过程可分为两个阶段,第一个阶段是在消费者初始化期间,集群 Cluster 为消费者创建 ClusterInvoker 实例。第二个阶段是在消费者进行 RPC 时,以 FailoverClusterInvoker 为例,该实例首先会调用 Directory 的 list()方法 获取 Invoker 列表,然后根据配置的 负载均衡策略,从 Invoker 列表 中选择一个 Inovker,最后将参数传给选择出的 Invoker 实例 进行真正的远程调用。 -可将上文中出现的 Invoker 简单理解为服务提供者,Directory 的用途是保存 Invoker列表,实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Inovker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。 +可将上文中出现的 Invoker 简单理解为服务提供者,Directory 的用途是保存 Invoker 列表,实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Inovker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Inovker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。 下面我们来看一下 集群模块的项目结构图,结合上文的描述,可以对其有更加深刻的理解。 -![avatar](../../../images/Dubbo/dubbo-cluster模块工程结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-cluster模块工程结构.png) -### 集群模块核心API 源码解析 -从上图应该也能看出其核心API在哪个包里。 +### 集群模块核心 API 源码解析 -![avatar](../../../images/Dubbo/com.alibaba.dubbo.rpc.cluster包目录.png) +从上图应该也能看出其核心 API 在哪个包里。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/com.alibaba.dubbo.rpc.cluster包目录.png) 各核心接口的源码如下。 + ```java /** * 集群接口 @@ -135,4 +139,4 @@ public interface RouterFactory { @Adaptive("protocol") Router getRouter(URL url); } -``` \ No newline at end of file +``` diff --git "a/docs/Dubbo/cluster/mock\344\270\216\346\234\215\345\212\241\351\231\215\347\272\247.md" "b/docs/Dubbo/cluster/mock\344\270\216\346\234\215\345\212\241\351\231\215\347\272\247.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/cluster/mock\344\270\216\346\234\215\345\212\241\351\231\215\347\272\247.md" +++ "b/docs/Dubbo/cluster/mock\344\270\216\346\234\215\345\212\241\351\231\215\347\272\247.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/cluster/\350\264\237\350\275\275\345\235\207\350\241\241.md" "b/docs/Dubbo/cluster/\350\264\237\350\275\275\345\235\207\350\241\241.md" index 48c9676c..1988e7ff 100644 --- "a/docs/Dubbo/cluster/\350\264\237\350\275\275\345\235\207\350\241\241.md" +++ "b/docs/Dubbo/cluster/\350\264\237\350\275\275\345\235\207\350\241\241.md" @@ -1,15 +1,20 @@ ## Dubbo 负载均衡简介 + 负载均衡,无论在常用的中间件 及 框架中,还是现实生活中,都有所体现。比如,一个团队干活,老大肯定要尽可能把任务均匀合理地分下去,让整个团队能高速运转,能力强的多分点,能力弱的少分点,绝对不能去逮着一个人 让他累到死,让其它人闲着。这样的均匀分配任务及压力的思想 放在开发领域 即是“负载均衡”。它就相当于是一个压力均衡机制,通过各种策略,为集群中的每台服务器合理地分配压力,这样 即能提升整个集群的运行效率,又能尽量避免 某个节点因为压力过大而宕机。 -在 Dubbo 中也需要负载均衡机制,将消费者的请求 合理分配到服务提供者集群的各个节点上,以提升集群的整体运行效率 和 避免单个节点压力过大而宕机的问题。Dubbo 提供了4种负载均衡实现,缺省为 RandomLoadBalance 加权随机调用,如下。 +在 Dubbo 中也需要负载均衡机制,将消费者的请求 合理分配到服务提供者集群的各个节点上,以提升集群的整体运行效率 和 避免单个节点压力过大而宕机的问题。Dubbo 提供了 4 种负载均衡实现,缺省为 RandomLoadBalance 加权随机调用,如下。 + - RandomLoadBalance:加权随机算法,按权重设置随机概率; - RoundRobinLoadBalance:加权轮询算法,按公约后的权重设置轮询比率; - LeastActiveLoadBalance:最少响应时间算法,使快速响应的服务提供者 接收更多请求,慢的提供者收到更少请求; -- ConsistentHashLoadBalance:一致性hash算法,相同参数的请求总是发到同一提供者。 +- ConsistentHashLoadBalance:一致性 hash 算法,相同参数的请求总是发到同一提供者。 ## 源码赏析 -### LoadBalance接口 和 AbstractLoadBalance -AbstractLoadBalance 实现了 LoadBalance接口,是负载均衡的抽象类,提供了权重计算等通用功能。 + +### LoadBalance 接口 和 AbstractLoadBalance + +AbstractLoadBalance 实现了 LoadBalance 接口,是负载均衡的抽象类,提供了权重计算等通用功能。 + ```java /** * LoadBalance. (SPI, Singleton, ThreadSafe) @@ -74,7 +79,9 @@ public abstract class AbstractLoadBalance implements LoadBalance { ``` ### RandomLoadBalance -该类是基于权重随机算法的负载均衡实现类,我们先来讲讲原理,比如我有有一组服务器 servers = [A, B, C],他们他们对应的权重为 weights = [6, 3, 1],权重总和为10,现在把这些权重值平铺在一维坐标值上,分别出现三个区域,A区域为[0,6),B区域为[6,9),C区域为[9,10),然后产生一个[0, 10)的随机数,看该数字落在哪个区间内,就用哪台服务器,这样权重越大的,被击中的概率就越大。 + +该类是基于权重随机算法的负载均衡实现类,我们先来讲讲原理,比如我有有一组服务器 servers = [A, B, C],他们他们对应的权重为 weights = [6, 3, 1],权重总和为 10,现在把这些权重值平铺在一维坐标值上,分别出现三个区域,A 区域为[0,6),B 区域为[6,9),C 区域为[9,10),然后产生一个[0, 10)的随机数,看该数字落在哪个区间内,就用哪台服务器,这样权重越大的,被击中的概率就越大。 + ```java /** * random load balance. @@ -123,7 +130,9 @@ public class RandomLoadBalance extends AbstractLoadBalance { ``` ### RoundRobinLoadBalance + 该类是负载均衡基于加权轮询算法的实现,在 nginx 中也有类似的实现。当我们的服务器 性能之间存在明显差异,并希望请求均匀地落到各服务器上,就需要用到加权轮询。 + ```java /** * Round robin load balance. @@ -222,11 +231,13 @@ public class RoundRobinLoadBalance extends AbstractLoadBalance { ``` ### LeastActiveLoadBalance -该负载均衡策略基于最少活跃调用数算法,某个服务活跃调用数越小,表明该服务提供者效率越高,也就表明单位时间内能够处理的请求更多。此时应该选择该类服务器。实现很简单,就是每一个服务都有一个活跃数active来记录该服务的活跃值,每收到一个请求,该active就会加1,每完成一个请求,active就减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求。除了最小活跃数,还引入了权重值,也就是当活跃数一样的时候,选择利用权重法来进行选择,如果权重也一样,那么随机选择一个。 + +该负载均衡策略基于最少活跃调用数算法,某个服务活跃调用数越小,表明该服务提供者效率越高,也就表明单位时间内能够处理的请求更多。此时应该选择该类服务器。实现很简单,就是每一个服务都有一个活跃数 active 来记录该服务的活跃值,每收到一个请求,该 active 就会加 1,每完成一个请求,active 就减 1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求。除了最小活跃数,还引入了权重值,也就是当活跃数一样的时候,选择利用权重法来进行选择,如果权重也一样,那么随机选择一个。 + ```java /** * LeastActiveLoadBalance - * + * * 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 * 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 */ @@ -290,26 +301,28 @@ public class LeastActiveLoadBalance extends AbstractLoadBalance { ``` ### ConsistentHashLoadBalance -该类是负载均衡基于 hash一致性算法的实现。一致性哈希算法的工作原理如下。 - 1. 首先根据 ip 或其他的信息为缓存节点生成一个 hash,在dubbo中使用参数进行计算hash。并将这个 hash 投射到 [0, 232 - 1] 的圆环上,当有查询或写入请求时,则生成一个 hash 值。 - 2. 然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。 - - 大致效果如下图所示(引用一下官网的图)。每个缓存节点在圆环上占据一个位置,如果缓存项key 的 hash值小于缓存节点 hash值,则到该缓存节点中存储或读取缓存项,这里有两个概念不要弄混,缓存节点就好比dubbo中的服务提供者,会有很多的服务提供者,而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中,也就是调用cache-4 这个服务提供者。 +该类是负载均衡基于 hash 一致性算法的实现。一致性哈希算法的工作原理如下。 -![avatar](../../../images/Dubbo/一致性hash算法1.png) +1. 首先根据 ip 或其他的信息为缓存节点生成一个 hash,在 dubbo 中使用参数进行计算 hash。并将这个 hash 投射到 [0, 232 - 1] 的圆环上,当有查询或写入请求时,则生成一个 hash 值。 +2. 然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。 -但 hash一致性算法 并不能够保证 负载的平衡性,就拿上面的例子来看,cache-3挂掉了,那该节点下的所有缓存项都要存储到 cache-4 节点中,这就导致hash值低的一直往高的存储,会面临一个不平衡的现象,见下图: +大致效果如下图所示(引用一下官网的图)。每个缓存节点在圆环上占据一个位置,如果缓存项 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项,这里有两个概念不要弄混,缓存节点就好比 dubbo 中的服务提供者,会有很多的服务提供者,而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中,也就是调用 cache-4 这个服务提供者。 -![avatar](../../../images/Dubbo/一致性hash算法2.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/一致性hash算法1.png) -可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入 “虚拟节点”,“虚拟节点” 是实际节点在 hash 空间的复制品,“虚拟节点” 在 hash空间 中以 hash值 排列,如下图。 +但 hash 一致性算法 并不能够保证 负载的平衡性,就拿上面的例子来看,cache-3 挂掉了,那该节点下的所有缓存项都要存储到 cache-4 节点中,这就导致 hash 值低的一直往高的存储,会面临一个不平衡的现象,见下图: -![avatar](../../../images/Dubbo/一致性hash算法3.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/一致性hash算法2.png) + +可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入 “虚拟节点”,“虚拟节点” 是实际节点在 hash 空间的复制品,“虚拟节点” 在 hash 空间 中以 hash 值 排列,如下图。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/一致性hash算法3.png) 可以看到各个节点都被均匀分布在圆环上,且一个服务提供者有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。 看完原理,接下来我们来看看代码。 + ```java /** * ConsistentHashLoadBalance @@ -448,4 +461,4 @@ public class ConsistentHashLoadBalance extends AbstractLoadBalance { } } } -``` \ No newline at end of file +``` diff --git "a/docs/Dubbo/cluster/\351\233\206\347\276\244\345\256\271\351\224\231.md" "b/docs/Dubbo/cluster/\351\233\206\347\276\244\345\256\271\351\224\231.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/cluster/\351\233\206\347\276\244\345\256\271\351\224\231.md" +++ "b/docs/Dubbo/cluster/\351\233\206\347\276\244\345\256\271\351\224\231.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/registry/Dubbo\346\263\250\345\206\214\344\270\255\345\277\203\346\250\241\345\235\227\347\256\200\346\236\220.md" "b/docs/Dubbo/registry/Dubbo\346\263\250\345\206\214\344\270\255\345\277\203\346\250\241\345\235\227\347\256\200\346\236\220.md" index adb301f0..a8e4676b 100644 --- "a/docs/Dubbo/registry/Dubbo\346\263\250\345\206\214\344\270\255\345\277\203\346\250\241\345\235\227\347\256\200\346\236\220.md" +++ "b/docs/Dubbo/registry/Dubbo\346\263\250\345\206\214\344\270\255\345\277\203\346\250\241\345\235\227\347\256\200\346\236\220.md" @@ -1,26 +1,33 @@ -## 注册中心在Dubbo中的作用 -服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者Provider 会往注册中心注册服务,而消费者Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer以及Registry之间的依赖关系 如下图所示。 +## 注册中心在 Dubbo 中的作用 -![avatar](../../../images/Dubbo/Dubbo工作原理图.png) +服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者 Provider 会往注册中心注册服务,而消费者 Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer 以及 Registry 之间的依赖关系 如下图所示。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Dubbo工作原理图.png) ## dubbo-registry 模块 结构分析 -dubbo的注册中心有多种实现方案,如:zookeeper、redis、multicast等,本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api,具体实现部分放到下章来讲。dubbo-registry模块 的结构如下图所示。 -![avatar](../../../images/Dubbo/dubbo-registry模块结构图.png) +dubbo 的注册中心有多种实现方案,如:zookeeper、redis、multicast 等,本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api,具体实现部分放到下章来讲。dubbo-registry 模块 的结构如下图所示。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-registry模块结构图.png) ### Registry 核心组件类图 + 典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。 -![avatar](../../../images/Dubbo/Registry组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Registry组件类图.png) -既然有Registry组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry实例的RegistryFactory,其中用到了工厂方法模式,不同的工厂类用于获取不同类型的实例。其类图结构如下。 +既然有 Registry 组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry 实例的 RegistryFactory,其中用到了工厂方法模式,不同的工厂类用于获取不同类型的实例。其类图结构如下。 -![avatar](../../../images/Dubbo/RegistryFactory组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/RegistryFactory组件类图.png) ## 源码详解 -根据上面的类图,我们开始从上往下 详解dubbo中对于注册中心的设计以及实现。 + +根据上面的类图,我们开始从上往下 详解 dubbo 中对于注册中心的设计以及实现。 + ### RegistryService 接口 -RegistryService 是注册中心模块的服务接口,定义了注册、取消注册、订阅、取消订阅以及查询符合条件的已注册数据 等方法。这里统一说明一下URL,dubbo是以总线模式来时刻传递和保存配置信息的,配置信息都被放在URL上进行传递,随时可以取得相关配置信息,而这里提到了URL有别的作用,就是作为类似于节点的作用,首先服务提供者(Provider)启动时需要提供服务,就会向注册中心写下自己的URL地址。然后消费者启动时需要去订阅该服务,则会订阅Provider注册的地址,并且消费者也会写下自己的URL。 + +RegistryService 是注册中心模块的服务接口,定义了注册、取消注册、订阅、取消订阅以及查询符合条件的已注册数据 等方法。这里统一说明一下 URL,dubbo 是以总线模式来时刻传递和保存配置信息的,配置信息都被放在 URL 上进行传递,随时可以取得相关配置信息,而这里提到了 URL 有别的作用,就是作为类似于节点的作用,首先服务提供者(Provider)启动时需要提供服务,就会向注册中心写下自己的 URL 地址。然后消费者启动时需要去订阅该服务,则会订阅 Provider 注册的地址,并且消费者也会写下自己的 URL。 + ```java /** * RegistryService. (SPI, Prototype, ThreadSafe) @@ -95,7 +102,9 @@ public interface RegistryService { ``` ### Registry 接口 -注册中心接口,把节点Node 以及注册中心服务RegistryService 的方法整合在了这个接口里面。该接口并没有自己的方法,就是继承了Node和RegistryService接口。这里的Node是节点的接口,里面协定了关于节点的一些操作方法,源码如下。 + +注册中心接口,把节点 Node 以及注册中心服务 RegistryService 的方法整合在了这个接口里面。该接口并没有自己的方法,就是继承了 Node 和 RegistryService 接口。这里的 Node 是节点的接口,里面协定了关于节点的一些操作方法,源码如下。 + ```java /** * 注册中心接口 @@ -114,7 +123,9 @@ public interface Node { ``` ### AbstractRegistry 抽象类 -实现了Registry接口的抽象类。为了减轻注册中心的压力,该抽象类把本地URL缓存到了property文件中,并且实现了注册中心的注册、订阅等方法。 + +实现了 Registry 接口的抽象类。为了减轻注册中心的压力,该抽象类把本地 URL 缓存到了 property 文件中,并且实现了注册中心的注册、订阅等方法。 + ```java /** * 实现了Registry接口的抽象类,实现了如下方法: @@ -699,7 +710,9 @@ public abstract class AbstractRegistry implements Registry { ``` ### FailbackRegistry 抽象类 -FailbackRegistry抽象类 继承了上面的 AbstractRegistry,AbstractRegistry中的注册、订阅等方法,实际上就是一些内存缓存的变化,而真正的注册订阅的实现逻辑在FailbackRegistry实现,并且FailbackRegistry提供了失败重试的机制。 + +FailbackRegistry 抽象类 继承了上面的 AbstractRegistry,AbstractRegistry 中的注册、订阅等方法,实际上就是一些内存缓存的变化,而真正的注册订阅的实现逻辑在 FailbackRegistry 实现,并且 FailbackRegistry 提供了失败重试的机制。 + ```java /** * 支持失败重试的 FailbackRegistry抽象类 @@ -1209,7 +1222,9 @@ public abstract class FailbackRegistry extends AbstractRegistry { ``` ### RegistryFactory 和 AbstractRegistryFactory -RegistryFactory接口 是 Registry的工厂接口,用来返回 Registry实例。该接口是一个可扩展接口,可以看到该接口上有个@SPI注解,并且默认值为dubbo,也就是默认扩展的是DubboRegistryFactory。AbstractRegistryFactory 则是实现了 RegistryFactory接口 的抽象类。其源码如下。 + +RegistryFactory 接口 是 Registry 的工厂接口,用来返回 Registry 实例。该接口是一个可扩展接口,可以看到该接口上有个@SPI 注解,并且默认值为 dubbo,也就是默认扩展的是 DubboRegistryFactory。AbstractRegistryFactory 则是实现了 RegistryFactory 接口 的抽象类。其源码如下。 + ```java /** * 注册中心工厂 @@ -1332,8 +1347,11 @@ public abstract class AbstractRegistryFactory implements RegistryFactory { protected abstract Registry createRegistry(URL url); } ``` + ### NotifyListener 和 RegistryDirectory -最后我们来看一下 dubbo-registry-api 模块下的另一个比较重要的组件,NotifyListener接口 和 RegistryDirectory抽象类。NotifyListener接口 只有一个notify方法,通知监听器。当收到服务变更通知时触发。RegistryDirectory是注册中心服务,维护着所有可用的远程Invoker或者本地的Invoker,它的Invoker集合是从注册中心获取的,另外,它实现了NotifyListener接口。比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有 服务提供方,在订阅 及 服务提供方数据有变动时,回调消费方的NotifyListener服务的notify方法,回调接口传入所有服务提供方的url地址然后将urls转化为invokers,也就是refer应用远程服务。源码如下。 + +最后我们来看一下 dubbo-registry-api 模块下的另一个比较重要的组件,NotifyListener 接口 和 RegistryDirectory 抽象类。NotifyListener 接口 只有一个 notify 方法,通知监听器。当收到服务变更通知时触发。RegistryDirectory 是注册中心服务,维护着所有可用的远程 Invoker 或者本地的 Invoker,它的 Invoker 集合是从注册中心获取的,另外,它实现了 NotifyListener 接口。比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有 服务提供方,在订阅 及 服务提供方数据有变动时,回调消费方的 NotifyListener 服务的 notify 方法,回调接口传入所有服务提供方的 url 地址然后将 urls 转化为 invokers,也就是 refer 应用远程服务。源码如下。 + ```java /** * 通知监听器 @@ -2168,4 +2186,4 @@ public class RegistryDirectory extends AbstractDirectory implements Notify } } } -``` \ No newline at end of file +``` diff --git "a/docs/Dubbo/registry/\346\263\250\345\206\214\344\270\255\345\277\203\347\232\204Zookeeper\345\256\236\347\216\260.md" "b/docs/Dubbo/registry/\346\263\250\345\206\214\344\270\255\345\277\203\347\232\204Zookeeper\345\256\236\347\216\260.md" index 63c6052b..23c212f0 100644 --- "a/docs/Dubbo/registry/\346\263\250\345\206\214\344\270\255\345\277\203\347\232\204Zookeeper\345\256\236\347\216\260.md" +++ "b/docs/Dubbo/registry/\346\263\250\345\206\214\344\270\255\345\277\203\347\232\204Zookeeper\345\256\236\347\216\260.md" @@ -1,19 +1,21 @@ -Dubbo的注册中心 虽然提供了多种实现,但生产上的事实标准基本上都是 基于Zookeeper实现的。这种注册中心的实现方法也是Dubbo最为推荐的。为了易于理解 Zookeeper 在 Dubbo 中的应用,我们先简单看一下zookeeper。 +Dubbo 的注册中心虽然提供了多种实现,但生产上的事实标准基本上都是 基于 Zookeeper 实现的。这种注册中心的实现方法也是 Dubbo 最为推荐的。为了易于理解 Zookeeper 在 Dubbo 中的应用,我们先简单看一下 zookeeper。 -由于 Dubbo 是一个分布式RPC开源框架,各服务之间单独部署,往往会出现资源之间数据不一致的问题,比如:某一个服务增加或减少了几台机器,某个服务提供者变更了服务地址,那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo服务是如何被 Zookeeper的数据结构存储管理的呢,zookeeper采用的是树形结构来组织数据节点,它类似于一个标准的文件系统,如下图所示。 +由于 Dubbo 是一个分布式 RPC 开源框架,各服务之间单独部署,往往会出现资源之间数据不一致的问题,比如:某一个服务增加或减少了几台机器,某个服务提供者变更了服务地址,那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo 服务是如何被 Zookeeper 的数据结构存储管理的呢,zookeeper 采用的是树形结构来组织数据节点,它类似于一个标准的文件系统,如下图所示。 -![avatar](../../../images/Dubbo/dubbo注册中心在zookeeper中的结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo注册中心在zookeeper中的结构.png) -该图展示了dubbo在zookeeper中存储的形式以及节点层级。dubbo的Root层是根目录,通过的“group”来设置zookeeper的根节点,缺省值是“dubbo”。Service层是服务接口的全名。Type层是分类,一共有四种分类,分别是providers 服务提供者列表、consumers 服务消费者列表、routes 路由规则列表、configurations 配置规则列表。URL层 根据不同的Type目录:可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的Type关注的URL不同。 +该图展示了 dubbo 在 zookeeper 中存储的形式以及节点层级。dubbo 的 Root 层是根目录,通过的“group”来设置 zookeeper 的根节点,缺省值是“dubbo”。Service 层是服务接口的全名。Type 层是分类,一共有四种分类,分别是 providers 服务提供者列表、consumers 服务消费者列表、routes 路由规则列表、configurations 配置规则列表。URL 层 根据不同的 Type 目录:可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的 Type 关注的 URL 不同。 -zookeeper以斜杠来分割每一层的znode节点,比如第一层根节点dubbo就是“/dubbo”,而第二层的Service层就是/dubbo/com.foo.Barservice,zookeeper的每个节点通过路径来表示以及访问,例如服务提供者启动时,向/dubbo/com.foo.Barservice/providers目录下写入自己的URL地址。 +zookeeper 以斜杠来分割每一层的 znode 节点,比如第一层根节点 dubbo 就是“/dubbo”,而第二层的 Service 层就是/dubbo/com.foo.Barservice,zookeeper 的每个节点通过路径来表示以及访问,例如服务提供者启动时,向/dubbo/com.foo.Barservice/providers 目录下写入自己的 URL 地址。 dubbo-registry-zookeeper 模块的工程结构如下图所示,里面就俩类,非常简单。 -![avatar](../../../images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-registry-zookeeper模块工程结构图.png) ### ZookeeperRegistry -该类继承了FailbackRegistry抽象类,针对注册中心核心的 服务注册、服务订阅、取消注册、取消订阅,查询注册列表进行展开,这里用到了 模板方法设计模式,FailbackRegistry中定义了register()、subscribe()等模板方法和 doRegister()、doSubscribe()抽象方法,ZookeeperRegistry基于zookeeper对这些抽象方法进行了实现。其实你会发现zookeeper虽然是最被推荐的,反而它的实现逻辑相对简单,因为调用了zookeeper服务组件,很多的逻辑不需要在dubbo中自己去实现。 + +该类继承了 FailbackRegistry 抽象类,针对注册中心核心的 服务注册、服务订阅、取消注册、取消订阅,查询注册列表进行展开,这里用到了 模板方法设计模式,FailbackRegistry 中定义了 register()、subscribe()等模板方法和 doRegister()、doSubscribe()抽象方法,ZookeeperRegistry 基于 zookeeper 对这些抽象方法进行了实现。其实你会发现 zookeeper 虽然是最被推荐的,反而它的实现逻辑相对简单,因为调用了 zookeeper 服务组件,很多的逻辑不需要在 dubbo 中自己去实现。 + ```java /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -413,7 +415,9 @@ public class ZookeeperRegistry extends FailbackRegistry { ``` ### ZookeeperRegistryFactory -ZookeeperRegistryFactory 继承了 AbstractRegistryFactory抽象类,实现了其中的抽象方法 如createRegistry(),源码如下。 + +ZookeeperRegistryFactory 继承了 AbstractRegistryFactory 抽象类,实现了其中的抽象方法 如 createRegistry(),源码如下。 + ```java /** * Zookeeper Registry 工厂 @@ -439,4 +443,4 @@ public class ZookeeperRegistryFactory extends AbstractRegistryFactory { return new ZookeeperRegistry(url, zookeeperTransporter); } } -``` \ No newline at end of file +``` diff --git "a/docs/Dubbo/remote/Buffer\347\273\204\344\273\266.md" "b/docs/Dubbo/remote/Buffer\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/remote/Buffer\347\273\204\344\273\266.md" +++ "b/docs/Dubbo/remote/Buffer\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/remote/Dubbo\350\277\234\347\250\213\351\200\232\344\277\241\346\250\241\345\235\227\347\256\200\346\236\220.md" "b/docs/Dubbo/remote/Dubbo\350\277\234\347\250\213\351\200\232\344\277\241\346\250\241\345\235\227\347\256\200\346\236\220.md" index 18068427..c36aa695 100644 --- "a/docs/Dubbo/remote/Dubbo\350\277\234\347\250\213\351\200\232\344\277\241\346\250\241\345\235\227\347\256\200\346\236\220.md" +++ "b/docs/Dubbo/remote/Dubbo\350\277\234\347\250\213\351\200\232\344\277\241\346\250\241\345\235\227\347\256\200\346\236\220.md" @@ -1,30 +1,35 @@ ## dubbo-remoting 模块整体结构设计 -服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分,前面我们分析了有关注册中心的源码,也就是服务管理,接下来要分析的就是跟服务通信有关的源码,也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能,而在对NIO框架选型上,dubbo交由用户选择,它集成了mina、netty、grizzly等各类NIO框架来搭建NIO服务器和客户端,并且利用dubbo的SPI扩展机制可以让用户自定义选择。dubbo-remoting的工程结构如下。 -![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting的工程结构.png) +服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分,前面我们分析了有关注册中心的源码,也就是服务管理,接下来要分析的就是跟服务通信有关的源码,也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能,而在对 NIO 框架选型上,dubbo 交由用户选择,它集成了 mina、netty、grizzly 等各类 NIO 框架来搭建 NIO 服务器和客户端,并且利用 dubbo 的 SPI 扩展机制可以让用户自定义选择。dubbo-remoting 的工程结构如下。 + +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-remoting的工程结构.png) ## dubbo-remoting-api 模块整体结构设计 -本篇我们先来看一下 dubbo-remoting 中 dubbo-remoting-api的项目结构。 -![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting-api的项目结构.png) +本篇我们先来看一下 dubbo-remoting 中 dubbo-remoting-api 的项目结构。 + +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-remoting-api的项目结构.png) dubbo-remoting-api 定义了远程通信模块最核心的 API,对于 dubbo-remoting-api 的解读会分为如下五个部分,其中第五部分会在本文介绍。 -1. buffer包:缓冲在 NIO框架 中是很重要的存在,各个 NIO框架 都实现了自己相应的缓存操作。这个 buffer包 下包括了缓冲区的接口以及抽象类; -2. exchange包:信息交换层,其中封装了请求响应模式,在传输层之上重新封装了 Request-Response 语义,为了满足 RPC 的需求。这层可以认为专注在 Request 和 Response 携带的信息上。该层是 RPC调用 的通讯基础之一; -3. telnet包:dubbo 支持通过 telnet命令 来进行服务治理,该包下就封装了这些通用指令的逻辑实现; -4. transport包:网络传输层,它只负责单向消息传输,是对 Mina、Netty、Grizzly 的抽象,它也可以扩展 UDP 传输,该层也是 RPC调用 的通讯基础之一; -5. 最外层的源码:该部分也是我们接下来要重点解析的。 +1. buffer 包:缓冲在 NIO 框架 中是很重要的存在,各个 NIO 框架 都实现了自己相应的缓存操作。这个 buffer 包 下包括了缓冲区的接口以及抽象类; +2. exchange 包:信息交换层,其中封装了请求响应模式,在传输层之上重新封装了 Request-Response 语义,为了满足 RPC 的需求。这层可以认为专注在 Request 和 Response 携带的信息上。该层是 RPC 调用 的通讯基础之一; +3. telnet 包:dubbo 支持通过 telnet 命令 来进行服务治理,该包下就封装了这些通用指令的逻辑实现; +4. transport 包:网络传输层,它只负责单向消息传输,是对 Mina、Netty、Grizzly 的抽象,它也可以扩展 UDP 传输,该层也是 RPC 调用 的通讯基础之一; +5. 最外层的源码:该部分也是我们接下来要重点解析的。 -结合 dubbo-remoting-api模块 的外层类和包划分,我们看看下面的官方架构图。 +结合 dubbo-remoting-api 模块 的外层类和包划分,我们看看下面的官方架构图。 -![在这里插入图片描述](../../../images/Dubbo/Dubbo整体架构图.png) +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Dubbo整体架构图.png) -红框标注的部分是 dubbo整体架构中的 远程通讯架构,其中 Exchange组件 和 Transport组件 在框架设计中起到了很重要的作用,也是支撑 Remoting 的核心。 +红框标注的部分是 dubbo 整体架构中的 远程通讯架构,其中 Exchange 组件 和 Transport 组件 在框架设计中起到了很重要的作用,也是支撑 Remoting 的核心。 ## dubbo-remoting-api 模块最外层源码解析 + ### Endpoint 接口 -dubbo 抽象出了一个端的概念,也就是Endpoint接口,这个端就是一个点,而点与点之间可以双向传输。在端的基础上再衍生出通道、客户端以及服务端的概念,也就是下面要介绍的 Channel、Client、Server 三个接口。在传输层,Client 和 Server 的区别只是语义上的区别,并不区分请求和应答职责,而在交换层,Client 和 Server 是有方向的端点,所以区分了明确的请求和应答职责。两者都具备发送的能力,只是客户端和服务端所关注的事情不一样,而Endpoint接口抽象的方法就是它们共同拥有的方法。这也就是它们都能被抽象成端的原因。 + +dubbo 抽象出了一个端的概念,也就是 Endpoint 接口,这个端就是一个点,而点与点之间可以双向传输。在端的基础上再衍生出通道、客户端以及服务端的概念,也就是下面要介绍的 Channel、Client、Server 三个接口。在传输层,Client 和 Server 的区别只是语义上的区别,并不区分请求和应答职责,而在交换层,Client 和 Server 是有方向的端点,所以区分了明确的请求和应答职责。两者都具备发送的能力,只是客户端和服务端所关注的事情不一样,而 Endpoint 接口抽象的方法就是它们共同拥有的方法。这也就是它们都能被抽象成端的原因。 + ```java /** * Endpoint. (API/SPI, Prototype, ThreadSafe) @@ -92,8 +97,11 @@ public interface Endpoint { boolean isClosed(); } ``` + ### Channel 接口 + 该接口是通道接口,通道是信息传输的载体。Channel 可读可写,并且可以异步读写。Channel 是 client 和 server 的数据传输桥梁。Channel 和 client 是一对一的,也就是一个 client 对应一个 Channel,而 Channel 和 server 则是多对一,也就是一个 server 可以对应多个 Channel。 + ```java /** * Channel. (API/SPI, Prototype, ThreadSafe) @@ -122,7 +130,9 @@ public interface Channel extends Endpoint { void removeAttribute(String key); } ``` + ### ChannelHandler 接口 + ```java /** * ChannelHandler. (API, Prototype, ThreadSafe) @@ -150,6 +160,7 @@ public interface ChannelHandler { } ``` + ### Client 和 Resetable 接口 ```java @@ -180,6 +191,7 @@ public interface Resetable { ``` ### Server 接口 + ```java /** * Remoting Server. (API/SPI, Prototype, ThreadSafe) @@ -205,7 +217,9 @@ public interface Server extends Endpoint, Resetable { ``` ### Codec2 接口 -这两个都是编解码器 接口,在网络中进行传输的数据 都是原始的字节序列,这就需要 发送端使用编码器把 要传输的有意义的信息 序列化成字节序列,接收端再使用解码器 把字节序列再反序列化成 有效信息,而同时具备这两种功能的单一组件就叫 编解码器。在 dubbo 中 Codec 是老编解码器接口,而Codec2是新编解码器接口,并且 dubbo 已经用 CodecAdapter 把 Codec 适配成 Codec2 了。所以在这里就只介绍下Codec2接口。 + +这两个都是编解码器 接口,在网络中进行传输的数据 都是原始的字节序列,这就需要 发送端使用编码器把 要传输的有意义的信息 序列化成字节序列,接收端再使用解码器 把字节序列再反序列化成 有效信息,而同时具备这两种功能的单一组件就叫 编解码器。在 dubbo 中 Codec 是老编解码器接口,而 Codec2 是新编解码器接口,并且 dubbo 已经用 CodecAdapter 把 Codec 适配成 Codec2 了。所以在这里就只介绍下 Codec2 接口。 + ```java /** * 编解码器接口,需要注意的是: @@ -236,6 +250,7 @@ public interface Codec2 { ``` ### Decodeable 接口 + ```java /** * 可解码的接口,该接口有两个作用,第一是在调用真正的 decode方法 实现的时候会有一些校验, @@ -250,6 +265,7 @@ public interface Decodeable { ``` ### Dispatcher 接口 + ```java /** * 调度器接口,不同的调度器实现,将操作转发到对应的线程池。 @@ -299,6 +315,7 @@ public interface Transporter { ``` ### Transporters 类 + ```java /** * 1、该类用到了设计模式的外观模式,通过该类的包装,隐藏了内部具体的实现细节,降低了程序的复杂度, @@ -371,7 +388,9 @@ public class Transporters { ``` ### 远程通信的异常类 + RemotingException、ExecutionException 和 TimeoutException 是远程通信的异常类,内容比较简单,这里就简单介绍下 一笔带过咯。 -1. RemotingException 继承了 Exception类,是远程通信的基础异常; -2. ExecutionException 继承了 RemotingException类,ExecutionException 是远程通信的执行异常; -3. TimeoutException 继承了 RemotingException类,TimeoutException是超时异常。 \ No newline at end of file + +1. RemotingException 继承了 Exception 类,是远程通信的基础异常; +2. ExecutionException 继承了 RemotingException 类,ExecutionException 是远程通信的执行异常; +3. TimeoutException 继承了 RemotingException 类,TimeoutException 是超时异常。 diff --git "a/docs/Dubbo/remote/Exchange\347\273\204\344\273\266.md" "b/docs/Dubbo/remote/Exchange\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/remote/Exchange\347\273\204\344\273\266.md" +++ "b/docs/Dubbo/remote/Exchange\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/remote/Transport\347\273\204\344\273\266.md" "b/docs/Dubbo/remote/Transport\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/remote/Transport\347\273\204\344\273\266.md" +++ "b/docs/Dubbo/remote/Transport\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/remote/\345\237\272\344\272\216HTTP\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" "b/docs/Dubbo/remote/\345\237\272\344\272\216HTTP\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/remote/\345\237\272\344\272\216HTTP\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" +++ "b/docs/Dubbo/remote/\345\237\272\344\272\216HTTP\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Dubbo/remote/\345\237\272\344\272\216Netty\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" "b/docs/Dubbo/remote/\345\237\272\344\272\216Netty\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Dubbo/remote/\345\237\272\344\272\216Netty\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" +++ "b/docs/Dubbo/remote/\345\237\272\344\272\216Netty\345\256\236\347\216\260\350\277\234\347\250\213\351\200\232\344\277\241.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git a/docs/JDK/String.md b/docs/JDK/String.md deleted file mode 100644 index fcb5dbe4..00000000 --- a/docs/JDK/String.md +++ /dev/null @@ -1 +0,0 @@ -努力编写中... \ No newline at end of file diff --git a/docs/JDK/basic/String.md b/docs/JDK/basic/String.md new file mode 100644 index 00000000..35c85636 --- /dev/null +++ b/docs/JDK/basic/String.md @@ -0,0 +1,203 @@ +String 的源码大家应该都能看懂,这里就不一一分析咯,重点讲一下 equals()和 hashcode()方法,然后看一下 String 类常用方法的实现,就当一起温习一下咯。 + +```java +public final class String + implements java.io.Serializable, Comparable, CharSequence { + + /** 保存String的字符数组 */ + private final char value[]; + + /** 缓存这个String的hash值 */ + private int hash; // Default to 0 + + /** use serialVersionUID from JDK 1.0.2 for interoperability */ + private static final long serialVersionUID = -6849794470754667710L; + + /** + * 1、Object的 hashCode()返回该对象的内存地址编号,而equals()比较的是内存地址是否相等; + * 2、需要注意的是当equals()方法被重写时,hashCode()也要被重写; + * 3、按照一般hashCode()方法的实现来说,equals()相等的两个对象,hashcode()必须保持相等; + * equals()不相等的两个对象,hashcode()未必不相等 + * 4、一个类如果要作为 HashMap 的 key,必须重写equals()和hashCode()方法 + */ + public boolean equals(Object anObject) { + if (this == anObject) { + return true; + } + if (anObject instanceof String) { + String anotherString = (String)anObject; + int n = value.length; + if (n == anotherString.value.length) { + char v1[] = value; + char v2[] = anotherString.value; + int i = 0; + while (n-- != 0) { + if (v1[i] != v2[i]) + return false; + i++; + } + return true; + } + } + return false; + } + + public int hashCode() { + int h = hash; + if (h == 0 && value.length > 0) { + char val[] = value; + + for (int i = 0; i < value.length; i++) { + h = 31 * h + val[i]; + } + hash = h; + } + return h; + } + + /** + * 指定下标的char + */ + public char charAt(int index) { + if ((index < 0) || (index >= value.length)) { + throw new StringIndexOutOfBoundsException(index); + } + return value[index]; + } + + /** + * 是否以 prefix 为前缀 + */ + public boolean startsWith(String prefix) { + return startsWith(prefix, 0); + } + + /** + * 是否以 suffix 为后缀 + */ + public boolean endsWith(String suffix) { + return startsWith(suffix, value.length - suffix.value.length); + } + + /** + * 该String对象 是否满足 regex正则表达式 + */ + public boolean matches(String regex) { + return Pattern.matches(regex, this); + } + + /** + * 字符替换 + */ + public String replace(char oldChar, char newChar) { + if (oldChar != newChar) { + int len = value.length; + int i = -1; + char[] val = value; /* avoid getfield opcode */ + + while (++i < len) { + if (val[i] == oldChar) { + break; + } + } + if (i < len) { + char buf[] = new char[len]; + for (int j = 0; j < i; j++) { + buf[j] = val[j]; + } + while (i < len) { + char c = val[i]; + buf[i] = (c == oldChar) ? newChar : c; + i++; + } + return new String(buf, true); + } + } + return this; + } + + /** + * 子串替换 + */ + public String replaceAll(String regex, String replacement) { + return Pattern.compile(regex).matcher(this).replaceAll(replacement); + } + + /** + * 子串替换,只替换第一个 + */ + public String replaceFirst(String regex, String replacement) { + return Pattern.compile(regex).matcher(this).replaceFirst(replacement); + } + + /** + * 按 regex 切割成多个子串 + */ + public String[] split(String regex) { + return split(regex, 0); + } + + /** + * 剪切指定范围的字符串 + */ + public String substring(int beginIndex) { + if (beginIndex < 0) { + throw new StringIndexOutOfBoundsException(beginIndex); + } + int subLen = value.length - beginIndex; + if (subLen < 0) { + throw new StringIndexOutOfBoundsException(subLen); + } + return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); + } + + public String substring(int beginIndex, int endIndex) { + if (beginIndex < 0) { + throw new StringIndexOutOfBoundsException(beginIndex); + } + if (endIndex > value.length) { + throw new StringIndexOutOfBoundsException(endIndex); + } + int subLen = endIndex - beginIndex; + if (subLen < 0) { + throw new StringIndexOutOfBoundsException(subLen); + } + return ((beginIndex == 0) && (endIndex == value.length)) ? this + : new String(value, beginIndex, subLen); + } + + /** + * 获取该String 对应的 char[] + */ + public char[] toCharArray() { + // Cannot use Arrays.copyOf because of class initialization order issues + char result[] = new char[value.length]; + System.arraycopy(value, 0, result, 0, value.length); + return result; + } + + /** + * 大小写转换 + */ + public String toLowerCase() { + return toLowerCase(Locale.getDefault()); + } + public String toUpperCase() { + return toUpperCase(Locale.getDefault()); + } + + /** + * str在本String对象中第一次出现的下标 + */ + public int indexOf(String str) { + return indexOf(str, 0); + } + + /** + * str在本String对象中最后一次出现的下标 + */ + public int lastIndexOf(String str) { + return lastIndexOf(str, value.length); + } +} +``` diff --git a/docs/JDK/Thread.md b/docs/JDK/basic/Thread.md similarity index 93% rename from docs/JDK/Thread.md rename to docs/JDK/basic/Thread.md index 6dfeb666..30184d61 100644 --- a/docs/JDK/Thread.md +++ b/docs/JDK/basic/Thread.md @@ -1,6 +1,7 @@ -本来想看 ThreadLocal 的源码的,但发现其中最重要的 get/set 方法都是操纵的 Thread类 中的 threadLocals变量 (java.lang.ThreadLocal.ThreadLocalMap),索性先来看一下 Thread 的源码吧,可以留意一下其中与 ThreadLocal 相关的属性,这样下次阅读 ThreadLocal 的核心API时,就能够轻易理解其原理咯。不多BB,直接上硬菜。 +本来想看 ThreadLocal 的源码的,但发现其中最重要的 get/set 方法都是操纵的 Thread 类 中的 threadLocals 变量 (java.lang.ThreadLocal.ThreadLocalMap),索性先来看一下 Thread 的源码吧,可以留意一下其中与 ThreadLocal 相关的属性,这样下次阅读 ThreadLocal 的核心 API 时,就能够轻易理解其原理咯。不多 BB,直接上硬菜。 + +实现多线程从本质上都是由 Thread 类 来完成的,其源码量很多,本次只看一些常见且重要的部分,源码和解析如下。 -实现多线程从本质上都是由 Thread类 来完成的,其源码量很多,本次只看一些常见且重要的部分,源码和解析如下。 ```java public class Thread implements Runnable { /** 这里只看一些 常见的参数 */ @@ -16,7 +17,7 @@ public class Thread implements Runnable { private ThreadGroup group; /** 类加载器 */ private ClassLoader contextClassLoader; - /** + /** * ThreadLocal 能为线程设置线程私有变量 就是通过下面这个threadLocals变量完成的, * ThreadLocal的get/set方法就是通过操作 各个线程的 threadLocals 变量实现的。 * 1、线程A持有一个 ThreadLocalMap 变量; @@ -28,7 +29,7 @@ public class Thread implements Runnable { ThreadLocal.ThreadLocalMap inheritableThreadLocals; /** 线程栈的大小 */ private long stackSize; - /** + /** * Thread类定义了6个线程状态:New、Runnable、Blocked、Waiting、TimedWaiting、Terminated(终止) * 实际上还会把 Runnable 再细分为 就绪(未抢到时间片) 和 运行中(抢到时间片) */ @@ -41,10 +42,10 @@ public class Thread implements Runnable { public static final int MAX_PRIORITY = 10; /** - * 内部枚举类,用来描述线程状态,状态值有: + * 内部枚举类,用来描述线程状态,状态值有: * NEW: 新建,还未调用start()方法; - * RUNNABLE: 运行,在java多线程模型中,就绪和运行都是运行状态; - * BLOCKED: 阻塞; + * RUNNABLE: 运行,在java多线程模型中,就绪和运行都是运行状态; + * BLOCKED: 阻塞; * WAITING: 等待,需要其他的线程来唤醒; * TIMED_WAITING:超时等待,可以在指定的时间内自动醒来,如 sleep()方法; * TERMINATED: 终止,线程执行完毕。 @@ -248,7 +249,7 @@ public class Thread implements Runnable { if (target != null) target.run(); } - + /** * 请求终止线程。interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号, * 告诉它要结束了,具体要中断还是继续运行,将由被通知的线程自己处理 @@ -315,6 +316,7 @@ public class Thread implements Runnable { public final native boolean isAlive(); } ``` + 之前一直对线程状态 及 状态切换的概念模糊不清,现在通过源码中对线程状态的定义,我们可以画张图来重新回顾一下,以使我们对其有更加深刻的理解。 -![avatar](../../images/JDK1.8/ThreadStatusChange.png) \ No newline at end of file +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/ThreadStatusChange.png) diff --git a/docs/JDK/ThreadLocal.md b/docs/JDK/basic/ThreadLocal.md similarity index 89% rename from docs/JDK/ThreadLocal.md rename to docs/JDK/basic/ThreadLocal.md index 1f862798..871a5ee1 100644 --- a/docs/JDK/ThreadLocal.md +++ b/docs/JDK/basic/ThreadLocal.md @@ -1,8 +1,9 @@ -前面我们分析了 Thread类的源码,有了前面的铺垫,通过源码 理解ThreadLocal的秘密就容易多了。 +前面我们分析了 Thread 类的源码,有了前面的铺垫,通过源码 理解 ThreadLocal 的秘密就容易多了。 -ThreadLocal类 提供了 get/set线程局部变量的实现,ThreadLocal成员变量与正常的成员变量不同,每个线程都可以通过 ThreadLocal成员变量 get/set自己的专属值。ThreadLocal实例 通常是类中的私有静态变量,常用于将状态与线程关联,例如:用户ID或事务ID。 +ThreadLocal 类 提供了 get/set 线程局部变量的实现,ThreadLocal 成员变量与正常的成员变量不同,每个线程都可以通过 ThreadLocal 成员变量 get/set 自己的专属值。ThreadLocal 实例 通常是类中的私有静态变量,常用于将状态与线程关联,例如:用户 ID 或事务 ID。 + +tips:在类中定义 ThreadLocal 变量时,一般在定义时就进行实例化! -tips:在类中定义ThreadLocal变量时,一般在定义时就进行实例化! ```java public class ThreadLocal { @@ -31,7 +32,7 @@ public class ThreadLocal { // threadLocals变量 return t.threadLocals; } - + void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } @@ -256,12 +257,13 @@ public class ThreadLocal { } } ``` + 简单画个图总结一下 ThreadLocal 的原理,如下。 -![avatar](../../images/JDK1.8/ThreadLocal原理.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/ThreadLocal原理.png) -最后强调一下 ThreadLocal的使用注意事项: +最后强调一下 ThreadLocal 的使用注意事项: 1. ThreadLocal 不是用来解决线程安全问题的,多线程不共享,不存在竞争!其目的是使线程能够使用本地变量。 -2. 项目如果使用了线程池,那么线程回收后ThreadLocal变量要remove掉,否则线程池回收线程后,变量还在内存中,可能会带来意想不到的后果!例如Tomcat容器的线程池,可以在拦截器中处理:继承 HandlerInterceptorAdapter,然后复写 afterCompletion()方法,remove掉变量!!! \ No newline at end of file +2. 项目如果使用了线程池,那么线程回收后 ThreadLocal 变量要 remove 掉,否则线程池回收线程后,变量还在内存中,可能会带来意想不到的后果!例如 Tomcat 容器的线程池,可以在拦截器中处理:继承 HandlerInterceptorAdapter,然后复写 afterCompletion()方法,remove 掉变量!!! diff --git a/docs/JDK/collection/ArrayList.md b/docs/JDK/collection/ArrayList.md new file mode 100644 index 00000000..7d866af9 --- /dev/null +++ b/docs/JDK/collection/ArrayList.md @@ -0,0 +1,388 @@ +# 一文直接带你吃透 ArrayList + +> ArrayList 是日常开发中相当常见、面试也相当常考的一种 JDK 集合类,了解并熟悉、甚至能实现一个 ArrayList 对面试、提升自己编码功底大有益处。 + +## 一、写给小白 ArrayList 简单使用技巧 + +这部分是 ArrayList 的简单使用技巧,主要是介绍 ArrayList 的几个常见方法。 + +```java +/** + * 编写一个ArrayList的简单实用demo + * ArrayList 的常见方法包括: + * add(element):添加元素 + * get(index):获取下标元素 + * remove(index):移除下标对应元素 + * set(index,element):将index处的元素修改为element + */ +public class arrayList { + public static void main(String[] args) { + // 创建 ArrayList 的对象 + ArrayList al = new ArrayList(); + // 添加元素 + al.add("finky"); + // 构造随机数并进行添加 + Random rnd = new Random(); + for (int i = 0; i < 20; i++) { + al.add(rnd.nextInt(1000)); + } + // 取出ArrayList里的元素进行打印 + for (int i = 0; i < al.size(); i++) { + System.out.print(al.get(i) + " "); + } + // 修改0号index成的元素为doocs + System.out.println(); + al.set(0, "doocs"); + System.out.println(al.get(0)); + // 移除“doocs”元素 + al.remove(0); + System.out.println(al.get(0)); + } +} +``` + +```java +// 这是上面打印后的demo,可以看到第0处下标元素先是修改成了doocs,进行移除后,第0处下标元素变成了912 +finky 912 922 284 305 675 565 159 109 73 298 491 920 296 397 358 145 610 190 839 845 +doocs +912 +``` + +## 二、ArrayList 的源码分析 + +我们来看看 ArrayList 的源码: + +#### 1、来看看 ArrayList 的初始化: + +```java +// ArrayList 初始化时默认大小为10 +private static final int DEFAULT_CAPACITY = 10; + +// 直接初始化的话一个空数组 +private static final Object[] EMPTY_ELEMENTDATA = {}; + +// 初始化ArrayList,传入初始化时的大小 +public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } +} +// 如果不传入大小的话就默认大小是10,那么这里就有一个问题:我们上面插入的元素超过了10,继续插入元素就会进行拷贝扩容,性能不是特别高。所以我们一般情况下初始化时给定一个比较靠谱的数组大小,避免到时候导致元素不断拷贝 +public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; +} + +``` + +总结一下 ArrayList 初始化:我们创建 ArrayList 对象时,如果没有传入对应的大小,就会默认创建一个元素大小为 10 的数组,下次插入元素超过 10 时,会进行数组的拷贝扩容,这样性能消耗太高,所以建议就是在初始化时给定一个不要太小的容量大小。== + +#### 2、 ArrayList 的 add 方法: + +先上`add` 方法的代码: + +```java +public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; +} + +public void add(int index, E element) { + rangeCheckForAdd(index); + ensureCapacityInternal(size + 1); // Increments modCount!! + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; +} + +public void add(E e) { + checkForComodification(); + try { + int i = cursor; + ArrayList.this.add(i, e); + cursor = i + 1; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } +} + +private void rangeCheck(int index) { + if (index < 0 || index >= this.size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } +} +``` + +![arraylist添加集合的方法](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/arraylist的add方法.png) + +先判断当前数组元素是否满了,如果塞满了就会进行数组扩容,随后进行数组拷贝。 + +再然后插入元素,同时对应的 index++。 + +#### 3、瞧瞧 ArrayList 的 set 方法: + +```java +public E set(int index, E element) { + rangeCheck(index); + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; +} + +public void set(E e) { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + ArrayList.this.set(lastRet, e); + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } +} +``` + +1、先进行 index 判断是否越界,如果没有越界的话获取原来的旧的值 + +2、进行替换并返回该位置原来的旧的值 + +#### 4、ArrayList 的 get 方法: + +```java +public E get(int index) { + rangeCheck(index); + + return elementData(index); +} +``` + +进行 index 是否越界的判断,然后去取对应下标的值。 + +#### 5、ArrayList 的 remove 方法: + +```java +public void remove() { + if (lastRet < 0) + throw new IllegalStateException(); + checkForComodification(); + + try { + ArrayList.this.remove(lastRet); + cursor = lastRet; + lastRet = -1; + expectedModCount = modCount; + } catch (IndexOutOfBoundsException ex) { + throw new ConcurrentModificationException(); + } +} + +public E remove(int index) { + // 进行index是否越界的判断 + rangeCheck(index); + checkForComodification(); + E result = parent.remove(parentOffset + index); + this.modCount = parent.modCount; + this.size--; + return result; +} + +public E remove(int index) { + rangeCheck(index); + modCount++; + E oldValue = elementData(index); + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; + return oldValue; +} +``` + +![arrayList删除元素的过程.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/arrayList删除元素的过程.png) + +1、先进行下标是否越界的判断,获取 index 处的元素值(这是要删除的值) + +2、然后进行元素拷贝,把 index 后面的元素往前拷贝 + +#### 6、关于 ArrayList 动态扩容和数组拷贝: + +```java +private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); +} + +private void ensureExplicitCapacity(int minCapacity) { + modCount++; + if (minCapacity - elementData.length > 0) + grow(minCapacity); +} + +private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + // 扩容的代码:这里做了位运算,相当于数组扩容了1.5倍 + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // 随后进行元素拷贝 + elementData = Arrays.copyOf(elementData, newCapacity); +} + +``` + +现在假定场景:arraylist 中已经有 10 个元素类,要放第 11 个元素。 + +此时进行容量检测,出现问题:空间大小不够。 + +解决方法:此时进行数组扩容右位移 1(**相当于总容量多加 1.5 倍**)扩容,老的大小+老大小的一半,进行元素拷贝 + +## 三、来仿照 JDK 源码写一个自己的 ArrayList 把 + +```java +public class OwnArrayList { + private E data[]; + private int size; + + public OwnArrayList(int capacity) { + data = (E[]) new Object[capacity]; + size = 0; + } + // 初始化是默认设置大小为20 + public OwnArrayList() { + this(20); + } + + // 获取数组容量 + public int getCapacity() { + return data.length; + } + + // 获取数组元素个数 + public int getSize() { + return size; + } + + // 判断数组是否为空 + public boolean isEmpity() { + return size == 0; + } + + // 获取index索引位置的元素 + public E get(int index) { + if (index < 0 || index >= size) + throw new IllegalArgumentException("add failed,the index should >= 0 or <= size"); + return data[index]; + } + + // 修改index索引位置的元素为e + public void set(int index, E e) { + if (index < 0 || index >= size) + throw new IllegalArgumentException("add failed,the index should >= 0 or <= size"); + data[index] = e; + } + + // 在数组中间插入一个元素 + public void add(int index, E element) { + if (size == data.length) { + throw new IllegalArgumentException("AddLast failed,array has already full"); + } + if (index < 0 || index > size) { + throw new IllegalArgumentException("add failed,the index should >= 0 or <= size"); + } + for (int i = size - 1; i >= index; i--) { + data[i + 1] = data[i]; + } + data[index] = element; + size++; + } + + // 向数组元素末尾添加一个元素 + public void addLast(E element) { + add(size,element); + } + + // 在数组头部插入一个元素 + public void addFirst(E element) { + add(0, element); + } + + // 判断是否含有元素 + public boolean contains(E e) { + for (int i = 0; i < size; i++) + if (data[i] == e) + return true; + return false; + } + + // 查找元素e的位置 + public int find(E e) { + for (int i = 0; i < size; i++) { + if (data[i] == e) { + return i; + } + } + return -1; + } + + // 删除index位置的元素 + public E remove(int index) { + if (index < 0 || index > size) { + throw new IllegalArgumentException("index should be 0 to size"); + } + E remove_element = data[index]; + for (int i = index + 1; i < size; i++) { + data[i - 1] = data[i]; + } + size--; + return remove_element; + } + + // 删除末尾元素 + // 注意:这是逻辑删除,但是size的大小已经做了相应的减少,所以从实际意义上我们外界并不能访问到末尾元素的值 + public E removelast() { + return remove(size - 1); + } + + // 删除开头元素 + public E removeFirst() { + return remove(0); + } + + // 将数组空间的容量变成newCapacity大小 + private void resize(int newCapacity) { + newCapacity = getCapacity()*2; + E[] newData = (E[]) new Object[newCapacity]; + for (int i = 0; i < size; i++) + newData[i] = data[i]; + data = newData; + } +} +``` + +## 四、面试时关于 ArrayList 要说的事 + +如果有人问你 ArrayList 知多少,我觉得可以从这几个方面出发: + +ArrayList 的底层是基于数组进行的,进行随机位置的插入和删除、以及扩容时性能很差,但进行随机的读和取时速度却很快。 + +接着可以从源码的角度分析 add、remove、set、get、数组扩容拷贝的过程场景。 + +最后也是特别重要的一点,就是要积极掌握主动性,延伸出 LinkedList 的特点、源码、两者间的对比等。 + +注:当需要动态数组时我们通常使用 ArrayList 而不是使用类似的 vector,这里有一点说明一下,就是尽管 Vector 的方法都是线程安全的,但其在单线程下需要花费的时间更多,而 ArrayList 尽管不是线程安全的,但其花费的时间很少。 + +## 终:参考资料 + +1. JDK 集合框架 ArrayList 源码 +2. 《Core.Java.Volume.I.Fundamentals.11th.Edition》 diff --git a/docs/JDK/ConcurrentHashMap.md b/docs/JDK/collection/ConcurrentHashMap.md similarity index 91% rename from docs/JDK/ConcurrentHashMap.md rename to docs/JDK/collection/ConcurrentHashMap.md index 668c9ff9..ece25675 100644 --- a/docs/JDK/ConcurrentHashMap.md +++ b/docs/JDK/collection/ConcurrentHashMap.md @@ -1,7 +1,7 @@ HashMap 源码中主要了解其核心源码及实现逻辑。ConcurrentHashMap 就不再重复那些数据结构相关的内容咯,这里重点看一下它的并发安全实现。源码如下。 ```java -public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap, +public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap, Serializable { /* --------- 常量及成员变量的设计 几乎与HashMap相差无几 -------- */ @@ -32,12 +32,12 @@ public class ConcurrentHashMap extends AbstractMap implements Concurre private static final float LOAD_FACTOR = 0.75f; /** - * + * */ transient volatile Node[] table; /** - * + * */ private transient volatile Node[] nextTable; @@ -75,8 +75,8 @@ public class ConcurrentHashMap extends AbstractMap implements Concurre this.sizeCtl = cap; } - /** - * ConcurrentHashMap 的核心就在于其put元素时 利用synchronized局部锁 和 + /** + * ConcurrentHashMap 的核心就在于其put元素时 利用synchronized局部锁 和 * CAS乐观锁机制 大大提升了本集合的并发能力,比JDK7的分段锁性能更强 */ public V put(K key, V value) { @@ -161,5 +161,6 @@ public class ConcurrentHashMap extends AbstractMap implements Concurre } } ``` -**与JDK1.7在同步机制上的区别** 总结如下: -JDK1.7 使用的是分段锁机制,其内部类Segment 继承了 ReentrantLock,将 容器内的数组划分成多段区域,每个区域对应一把锁,相比于HashTable确实提升了不少并发能力,但在数据量庞大的情况下,性能依然不容乐观,只能通过不断的增加锁来维持并发性能。而JDK1.8则使用了 CAS乐观锁 + synchronized局部锁 处理并发问题,锁粒度更细,即使数据量很大也能保证良好的并发性。 \ No newline at end of file + +**与 JDK1.7 在同步机制上的区别** 总结如下: +JDK1.7 使用的是分段锁机制,其内部类 Segment 继承了 ReentrantLock,将 容器内的数组划分成多段区域,每个区域对应一把锁,相比于 HashTable 确实提升了不少并发能力,但在数据量庞大的情况下,性能依然不容乐观,只能通过不断的增加锁来维持并发性能。而 JDK1.8 则使用了 CAS 乐观锁 + synchronized 局部锁 处理并发问题,锁粒度更细,即使数据量很大也能保证良好的并发性。 diff --git a/docs/JDK/HashMap.md b/docs/JDK/collection/HashMap.md similarity index 96% rename from docs/JDK/HashMap.md rename to docs/JDK/collection/HashMap.md index 8459d110..3cbf91e9 100644 --- a/docs/JDK/HashMap.md +++ b/docs/JDK/collection/HashMap.md @@ -1,9 +1,11 @@ 作为工作中最重要、最常用的容器之一,当然还是要自己动手写一篇 HashMap 的源码解析来加深对其的印象咯,而且它的设计与实现 也有很多值得学习的地方。 ## 源码赏析 -JDK1.8 的HashMap 底层使用的是 动态数组,数组中元素存放的是 链表或红黑树。核心源码如下。 + +JDK1.8 的 HashMap 底层使用的是 动态数组,数组中元素存放的是 链表或红黑树。核心源码如下。 + ```java -public class HashMap extends AbstractMap implements Map, +public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { /** @@ -25,7 +27,7 @@ public class HashMap extends AbstractMap implements Map, * 当前 HashMap 所能容纳键值对数量的最大值,超过这个值,则需扩容 */ int threshold; - + /** * 已使用的容量 */ @@ -35,12 +37,12 @@ public class HashMap extends AbstractMap implements Map, * Node数组,实际存放 键值对 的地方 */ transient Node[] table; - + /** * 链表转红黑树的阈值,链表长度达到此值,会进化成红黑树 */ static final int TREEIFY_THRESHOLD = 8; - + /** * 系列构造方法,推荐在初始化时根据实际情况设置好初始容量,用好了可以显著减少 resize,提升效率 */ @@ -89,7 +91,7 @@ public class HashMap extends AbstractMap implements Map, if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 如果桶中的引用类型为 TreeNode,则调用红黑树的插入方法 - else if (p instanceof TreeNode) + else if (p instanceof TreeNode) e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); else { // 对链表进行遍历,并统计链表长度 @@ -103,7 +105,7 @@ public class HashMap extends AbstractMap implements Map, treeifyBin(tab, hash); break; } - + // 条件为 true,表示当前链表包含要插入的键值对,终止遍历 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) @@ -111,7 +113,7 @@ public class HashMap extends AbstractMap implements Map, p = e; } } - + // 判断要插入的键值对是否存在 HashMap 中 if (e != null) { // existing mapping for key V oldValue = e.value; @@ -144,7 +146,7 @@ public class HashMap extends AbstractMap implements Map, if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; - } + } // 按旧容量和阈值的2倍计算新容量和阈值的大小 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) @@ -159,7 +161,7 @@ public class HashMap extends AbstractMap implements Map, newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } - + // newThr 为 0 时,按阈值计算公式进行计算 if (newThr == 0) { float ft = (float)newCap * loadFactor; @@ -233,7 +235,7 @@ public class HashMap extends AbstractMap implements Map, if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { // 如果hash和key都与 第一个元素相同,则第一个元素就是我们要获取的,直接返回 - if (first.hash == hash && + if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { @@ -312,17 +314,21 @@ public class HashMap extends AbstractMap implements Map, } } ``` + 源码部分 结合注释还是很容易看懂的,比较复杂的是红黑树这种数据结构,以及红黑树与链表之间的相互转换。下面我们回顾下这个数据结构。 + ## 红黑树 + 红黑树是一种自平衡的二叉查找树,比普通的二叉查找树效率更高,它可在 O(logN) 时间内完成查找、增加、删除等操作。 普通的二叉查找树在极端情况下可退化成链表,导致 增、删、查 效率低下。红黑树通过定义一些性质,将任意节点的左右子树高度差控制在规定范围内,以达到平衡状态,红黑树的性质定义如下。 + 1. 节点是红色或黑色。 2. 根是黑色。 -3. 所有叶子都是黑色(叶子是NIL节点)。 +3. 所有叶子都是黑色(叶子是 NIL 节点)。 4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。) 5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 红黑树的操作和其他树一样,包括查找、插入、删除等,其查找过程和二叉查找树一样简单,但插入和删除操作要复杂的多,这也是其 为保持平衡性 不会退化成链表 所付出的代价。红黑树为保持平衡性 所进行的操作主要有 旋转(左旋、右旋)和变色。 -红黑树的实现 确实比较复杂,光是理解其 插入、删除 的操作原理 就蛮费劲,所以这里先挖个坑,后面单独用一篇博文来分析 HashMap的 内部类TreeNode 对红黑树数据结构的实现。 \ No newline at end of file +红黑树的实现 确实比较复杂,光是理解其 插入、删除 的操作原理 就蛮费劲,所以这里先挖个坑,后面单独用一篇博文来分析 HashMap 的 内部类 TreeNode 对红黑树数据结构的实现。 diff --git a/docs/JDK/collection/HashSet.md b/docs/JDK/collection/HashSet.md new file mode 100644 index 00000000..8d09c8ca --- /dev/null +++ b/docs/JDK/collection/HashSet.md @@ -0,0 +1,117 @@ +HashSet 本身并没有什么特别的东西,它提供的所有集合核心功能,都是基于 HashMap 来实现的。如果了解 HashMap 源码的实现,HashSet 源码看起来跟玩一样。我的博客中有专门分析 HashMap 源码的文章,不熟悉的请自行翻阅。 + +HashSet 的特点如下: + +- 内部使用 HashMap 的 key 存储元素,以此来保证**元素不重复**; +- HashSet 是无序的,因为 HashMap 的 key 是**无序**的; +- HashSet 中允许有一个 null 元素,因为 HashMap 允许 key 为 null; +- HashSet 是**非线程安全**的。 + +```java +public class HashSet extends AbstractSet implements Set, Cloneable, java.io.Serializable { + static final long serialVersionUID = -5024744406713321676L; + + // 基于HashMap实现 + private transient HashMap map; + + // 只需要用到HashMap中key唯一的特性,所以value全部使用同一个 Object实例填充,节省内存空间 + private static final Object PRESENT = new Object(); + + /** + * 实例化 HashSet 的时候,初始化内部的 HashMap + */ + public HashSet() { + map = new HashMap<>(); + } + + /** + * 根据一个集合实例,实例化 HashSet + */ + public HashSet(Collection c) { + map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); + addAll(c); + } + + /** + * 根据初始容量和扩容因子实例化 HashSet,减少rehash频率,提升性能,原理与HashMap相同 + */ + public HashSet(int initialCapacity, float loadFactor) { + map = new HashMap<>(initialCapacity, loadFactor); + } + + /** + * 同上 + */ + public HashSet(int initialCapacity) { + map = new HashMap<>(initialCapacity); + } + + HashSet(int initialCapacity, float loadFactor, boolean dummy) { + map = new LinkedHashMap<>(initialCapacity, loadFactor); + } + + /** + * 返回迭代器,用于迭代 + * 下面所有的功能都是基于 HashMap 来实现的 + */ + public Iterator iterator() { + return map.keySet().iterator(); + } + + /** + * 元素个数 + */ + public int size() { + return map.size(); + } + + /** + * 是否为空 + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * 是否包含给定元素 + */ + public boolean contains(Object o) { + return map.containsKey(o); + } + + /** + * 添加元素,如果 Set集合中未包含该元素,返回true + */ + public boolean add(E e) { + return map.put(e, PRESENT)==null; + } + + /** + * 删除元素,如果Set集合包含该元素,返回true + */ + public boolean remove(Object o) { + return map.remove(o)==PRESENT; + } + + /** + * 清除元素 + */ + public void clear() { + map.clear(); + } + + /** + * 浅克隆 + */ + @SuppressWarnings("unchecked") + public Object clone() { + try { + HashSet newSet = (HashSet) super.clone(); + newSet.map = (HashMap) map.clone(); + return newSet; + } catch (CloneNotSupportedException e) { + throw new InternalError(e); + } + } +} +``` diff --git a/docs/JDK/collection/LinkedHashMap.md b/docs/JDK/collection/LinkedHashMap.md new file mode 100644 index 00000000..e24916d6 --- /dev/null +++ b/docs/JDK/collection/LinkedHashMap.md @@ -0,0 +1,262 @@ +HashMap 大家都清楚,底层是 数组 + (链表 / 红黑树),**元素是无序的**,而 LinkedHashMap 则比 HashMap 多了这一个功能,并且,LinkedHashMap 的有序可以按两种顺序排列,一种是按照插入的顺序,一种是按照访问的顺序(初始化 LinkedHashMap 对象时设置 accessOrder 参数为 true),而其内部是靠 建立一个双向链表 来维护这个顺序的,在每次插入、删除后,都会调用一个函数来进行 双向链表的维护,这也是实现 LRU Cache 功能的基础。 + +先说几个比较重要的结论,大家可以根据这些结论从后面的源码解析中 得到证据。 + +1. LinkedHashMap 继承了 HashMap,所以和 HashMap 的底层数据结构是一样的,都是数组+链表+红黑树,扩容机制也一样; +2. LinkedHashMap 是通过双向链表来维护数据的,与 HashMap 的拉链式存储不一样; +3. LinkedHashMap 存储顺序与添加顺序是一样得,同时可以根据 accessOrder 参数 来决定是否在访问时移动元素,以实现 LRU 功能。 + +```java +public class LinkedHashMap extends HashMap implements Map { + + /** + * 在 HashMap.Node节点 的基础上增加了 “前继节点” 和 “后继节点” 这种双向链表的功能特性 + */ + static class Entry extends HashMap.Node { + Entry before, after; + Entry(int hash, K key, V value, Node next) { + super(hash, key, value, next); + } + } + + /** + * 记录这个 LinkedHashMap容器的 头节点 + */ + transient LinkedHashMap.Entry head; + + /** + * 记录这个 LinkedHashMap容器的 尾节点 + */ + transient LinkedHashMap.Entry tail; + + /** + * 是否根据访问 进行排序,true为是,可通过构造方法进行设置 + */ + final boolean accessOrder; + + // 下面是一些私有的内部公用方法 + + // 将元素连接到链表尾部 + private void linkNodeLast(LinkedHashMap.Entry p) { + LinkedHashMap.Entry last = tail; + tail = p; + if (last == null) + head = p; + else { + p.before = last; + last.after = p; + } + } + + // apply src's links to dst + private void transferLinks(LinkedHashMap.Entry src, LinkedHashMap.Entry dst) { + LinkedHashMap.Entry b = dst.before = src.before; + LinkedHashMap.Entry a = dst.after = src.after; + if (b == null) + head = dst; + else + b.after = dst; + if (a == null) + tail = dst; + else + a.before = dst; + } + + // 下面是一些 重写的 HashMap 的 hook methods,其中 afterNodeInsertion、afterNodeRemoval + // afterNodeAccess及方法,在每次插入、删除、访问后,都会回调 用来维护双向链表 + + void reinitialize() { + super.reinitialize(); + head = tail = null; + } + + Node newNode(int hash, K key, V value, Node e) { + LinkedHashMap.Entry p = + new LinkedHashMap.Entry(hash, key, value, e); + linkNodeLast(p); + return p; + } + + Node replacementNode(Node p, Node next) { + LinkedHashMap.Entry q = (LinkedHashMap.Entry)p; + LinkedHashMap.Entry t = + new LinkedHashMap.Entry(q.hash, q.key, q.value, next); + transferLinks(q, t); + return t; + } + + TreeNode newTreeNode(int hash, K key, V value, Node next) { + TreeNode p = new TreeNode(hash, key, value, next); + linkNodeLast(p); + return p; + } + + TreeNode replacementTreeNode(Node p, Node next) { + LinkedHashMap.Entry q = (LinkedHashMap.Entry)p; + TreeNode t = new TreeNode(q.hash, q.key, q.value, next); + transferLinks(q, t); + return t; + } + + // 在删除元素之后,将元素从双向链表中删除 + void afterNodeRemoval(Node e) { // unlink + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.before = p.after = null; + if (b == null) + head = a; + else + b.after = a; + if (a == null) + tail = b; + else + a.before = b; + } + + // 可用于删除最老的元素 + void afterNodeInsertion(boolean evict) { // possibly remove eldest + LinkedHashMap.Entry first; + if (evict && (first = head) != null && removeEldestEntry(first)) { + K key = first.key; + removeNode(hash(key), key, null, false, true); + } + } + // 是否删除 最近最少使用的元素 + protected boolean removeEldestEntry(Map.Entry eldest) { + return false; + } + + // 在访问元素之后,将该元素放到双向链表的尾巴处 + void afterNodeAccess(Node e) { // move node to last + LinkedHashMap.Entry last; + if (accessOrder && (last = tail) != e) { + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.after = null; + if (b == null) + head = a; + else + b.after = a; + if (a != null) + a.before = b; + else + last = b; + if (last == null) + head = p; + else { + p.before = last; + last.after = p; + } + tail = p; + ++modCount; + } + } + + void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) { + s.writeObject(e.key); + s.writeObject(e.value); + } + } + + /** + * 跟 HashMap 的构造方法没啥区别,初始容量、扩容因子 用以减少resize和rehash,提升容器整体性能 + */ + public LinkedHashMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + accessOrder = false; + } + + public LinkedHashMap(int initialCapacity) { + super(initialCapacity); + accessOrder = false; + } + + /** + * 注意!accessOrder参数默认为false,如果想使用 LRU机制,记得设为 true + */ + public LinkedHashMap() { + super(); + accessOrder = false; + } + + public LinkedHashMap(Map m) { + super(); + accessOrder = false; + putMapEntries(m, false); + } + + /** + * 使用这个构造方法 设置accessOrder + */ + public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { + super(initialCapacity, loadFactor); + this.accessOrder = accessOrder; + } + + /** + * 是否包含指定元素 + */ + public boolean containsValue(Object value) { + for (LinkedHashMap.Entry e = head; e != null; e = e.after) { + V v = e.value; + if (v == value || (value != null && value.equals(v))) + return true; + } + return false; + } + + /** + * 获取指定key对应的value,如果accessOrder为true,会回调afterNodeAccess方法 + * 将元素放到队尾 + */ + public V get(Object key) { + Node e; + if ((e = getNode(hash(key), key)) == null) + return null; + if (accessOrder) + afterNodeAccess(e); + return e.value; + } + + /** + * 根据 key 获取对应的 value,如果key不存在,则返回给定的默认值 defaultValue + */ + public V getOrDefault(Object key, V defaultValue) { + Node e; + if ((e = getNode(hash(key), key)) == null) + return defaultValue; + if (accessOrder) + afterNodeAccess(e); + return e.value; + } + + /** + * {@inheritDoc} + */ + public void clear() { + super.clear(); + head = tail = null; + } + + /** + * 获取key的set集合 + */ + public Set keySet() { + Set ks = keySet; + if (ks == null) { + ks = new LinkedKeySet(); + keySet = ks; + } + return ks; + } + + /** + * 返回 键值对 的Set集合 + */ + public Set> entrySet() { + Set> es; + return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es; + } +} +``` diff --git a/docs/JDK/collection/LinkedList.md b/docs/JDK/collection/LinkedList.md new file mode 100644 index 00000000..b3787c53 --- /dev/null +++ b/docs/JDK/collection/LinkedList.md @@ -0,0 +1 @@ +努力编写中... diff --git a/docs/JDK/collection/TreeSet.md b/docs/JDK/collection/TreeSet.md new file mode 100644 index 00000000..b3787c53 --- /dev/null +++ b/docs/JDK/collection/TreeSet.md @@ -0,0 +1 @@ +努力编写中... diff --git "a/docs/JDK/Executor\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266.md" "b/docs/JDK/concurrentCoding/Executor\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266.md" similarity index 90% rename from "docs/JDK/Executor\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266.md" rename to "docs/JDK/concurrentCoding/Executor\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266.md" index 87cee44f..7f8e251c 100644 --- "a/docs/JDK/Executor\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266.md" +++ "b/docs/JDK/concurrentCoding/Executor\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266.md" @@ -1,17 +1,20 @@ ## 线程池核心组件图解 + 看源码之前,先了解一下该组件 最主要的几个 接口、抽象类和实现类的结构关系。 -![avatar](../../images/JDK1.8/线程池组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/线程池组件类图.png) -该组件中,Executor 和 ExecutorService接口 定义了线程池最核心的几个方法,提交任务submit -()、关闭线程池shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。 +该组件中,Executor 和 ExecutorService 接口 定义了线程池最核心的几个方法,提交任务 submit +()、关闭线程池 shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor 接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。 另外还有一个常见的工具类 Executors,里面为开发者封装了一些可以直接拿来用的线程池。 ## 源码赏析 + 话不多说,直接上源码。(这里只看最主要的代码部分) -### Executor 和 ExecutorService接口 +### Executor 和 ExecutorService 接口 + ```java public interface Executor { @@ -39,7 +42,9 @@ public interface ExecutorService extends Executor { Future submit(Runnable task); } ``` + ### AbstractExecutorService 抽象类 + ```java /** * 该抽象类最主要的内容就是,实现了 ExecutorService 中的 submit()系列方法 @@ -74,7 +79,8 @@ public abstract class AbstractExecutorService implements ExecutorService { } ``` -### ThreadPoolExecutor +### ThreadPoolExecutor + ```java public class ThreadPoolExecutor extends AbstractExecutorService { @@ -89,7 +95,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { /** 用于创建线程的 线程工厂 */ private volatile ThreadFactory threadFactory; - + /** 核心线程数 */ private volatile int corePoolSize; @@ -102,7 +108,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * ** 构造方法 ** * ************** */ - + /** 最后都使用了最后一个构造方法的实现 */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, @@ -209,12 +215,15 @@ public class ThreadPoolExecutor extends AbstractExecutorService { } } ``` -ThreadPoolExecutor 中的 execute()方法 执行 Runnable任务 的流程逻辑可以用下图表示。 -![avatar](../../../images/ConcurrentProgramming/线程池流程.png) +ThreadPoolExecutor 中的 execute()方法 执行 Runnable 任务 的流程逻辑可以用下图表示。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/ConcurrentProgramming/线程池流程.png) ### 工具类 Executors + 看类名也知道,它最主要的作用就是提供 static 的工具方法,为开发者提供各种封装好的 具有各自特性的线程池。 + ```java public class Executors { @@ -249,4 +258,4 @@ public class Executors { new SynchronousQueue()); } } -``` \ No newline at end of file +``` diff --git "a/docs/JDK/concurrentCoding/JUC\345\271\266\345\217\221\345\214\205UML\345\205\250\351\207\217\347\261\273\345\233\276.md" "b/docs/JDK/concurrentCoding/JUC\345\271\266\345\217\221\345\214\205UML\345\205\250\351\207\217\347\261\273\345\233\276.md" new file mode 100644 index 00000000..cbf1e7eb --- /dev/null +++ "b/docs/JDK/concurrentCoding/JUC\345\271\266\345\217\221\345\214\205UML\345\205\250\351\207\217\347\261\273\345\233\276.md" @@ -0,0 +1,5 @@ +利用 IDEA 整理类图还是蛮不错的,虽然这个功能 BUG 很多。下图是 J.U.C 并发包中所有类组成的类图,源码看多了 再去整理这个图,感觉还是很爽的。 + +根据功能,主要划分了六个部分,其中比较重要的是:线程池及其相关类、并发容器、AQS 与锁与同步工具类、原子类。图可能整理的不够细致,但看着这些类,回想一下其中的源码实现,感觉能侃一天。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/JUC全量UML地图.png) diff --git "a/docs/JDK/Lock\351\224\201\347\273\204\344\273\266.md" "b/docs/JDK/concurrentCoding/Lock\351\224\201\347\273\204\344\273\266.md" similarity index 95% rename from "docs/JDK/Lock\351\224\201\347\273\204\344\273\266.md" rename to "docs/JDK/concurrentCoding/Lock\351\224\201\347\273\204\344\273\266.md" index a5b749d0..91440629 100644 --- "a/docs/JDK/Lock\351\224\201\347\273\204\344\273\266.md" +++ "b/docs/JDK/concurrentCoding/Lock\351\224\201\347\273\204\344\273\266.md" @@ -1,14 +1,17 @@ ## 类图结构 -J.U.C 的锁组件中 类相对较少,从JDK相应的包中也能看出来,下图标记了其中最主要的几个接口和类,也是本文要分析的重点。 -![avatar](../../images/JDK1.8/JUC的locks包.png) +J.U.C 的锁组件中 类相对较少,从 JDK 相应的包中也能看出来,下图标记了其中最主要的几个接口和类,也是本文要分析的重点。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/JUC的locks包.png) 下图 将这几个接口和类 以类图的方式展现出来,其中包含了它们所声明的主要方法。 -![avatar](../../images/JDK1.8/JUC锁组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/JUC锁组件类图.png) ## Lock 组件 + Lock 组件的结构很简单,只有一个接口和一个实现类,源码如下。 + ```java public interface Lock { @@ -183,7 +186,9 @@ public class ReentrantLock implements Lock, java.io.Serializable { ``` ## ReadWriteLock 组件 -ReadWriteLock 组件的结构也很简单,与上面的 Lock组件 不同的是,它提供了 公平的读锁写锁,以及非公平的读锁写锁。 + +ReadWriteLock 组件的结构也很简单,与上面的 Lock 组件 不同的是,它提供了 公平的读锁写锁,以及非公平的读锁写锁。 + ```java public interface ReadWriteLock { /** @@ -402,7 +407,7 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab } } } - + final boolean isWriteLocked() { return exclusiveCount(getState()) != 0; } @@ -518,7 +523,9 @@ public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializab ``` ## AbstractQueuedSynchronizer -最后看一下抽象类 AbstractQueuedSynchronizer,在同步组件的实现中,AQS是核心部分,同步组件的实现者通过使用 AQS 提供的模板方法实现同步组件语义,AQS 则实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等等一些底层的实现处理。AQS 的核心包括:同步队列,独占式锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现,而这些实际上则是AQS提供出来的模板方法。源码如下。 + +最后看一下抽象类 AbstractQueuedSynchronizer,在同步组件的实现中,AQS 是核心部分,同步组件的实现者通过使用 AQS 提供的模板方法实现同步组件语义,AQS 则实现了对同步状态的管理,以及对阻塞线程进行排队,等待通知等等一些底层的实现处理。AQS 的核心包括:同步队列,独占式锁的获取和释放,共享锁的获取和释放以及可中断锁,超时等待锁获取这些特性的实现,而这些实际上则是 AQS 提供出来的模板方法。源码如下。 + ```java public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { @@ -645,4 +652,4 @@ public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchron return false; } } -``` \ No newline at end of file +``` diff --git a/docs/JDK/concurrentCoding/Semaphore.md b/docs/JDK/concurrentCoding/Semaphore.md new file mode 100644 index 00000000..117bb75c --- /dev/null +++ b/docs/JDK/concurrentCoding/Semaphore.md @@ -0,0 +1,280 @@ +Semaphore 信号量,可用于控制一定时间内,并发执行的线程数,基于 AQS 实现。可应用于网关限流、资源限制 (如 最大可发起连接数)。由于 release() 释放许可时,未对释放许可数做限制,所以可以通过该方法增加总的许可数量。 + +**获取许可** 支持公平和非公平模式,默认非公平模式。公平模式无论是否有许可,都会先判断是否有线程在排队,如果有线程排队,则进入排队,否则尝试获取许可;非公平模式无论许可是否充足,直接尝试获取许可。 + +不多废话,下面直接看源码。 + +#### 核心内部类 Sync + +```java +abstract static class Sync extends AbstractQueuedSynchronizer { + + private static final long serialVersionUID = 1192457210091910933L; + + /* 赋值state为总许可数 */ + Sync(int permits) { + setState(permits); + } + + /* 剩余许可数 */ + final int getPermits() { + return getState(); + } + + /* 自旋 + CAS非公平获取 */ + final int nonfairTryAcquireShared(int acquires) { + for (;;) { + // 剩余可用许可数 + int available = getState(); + // 本次获取许可后,剩余许可 + int remaining = available - acquires; + // 如果获取后,剩余许可大于0,则CAS更新剩余许可,否则获取失败失败 + if (remaining < 0 || + compareAndSetState(available, remaining)) + return remaining; + } + } + + /** + * 自旋 + CAS 释放许可 + * 由于未对释放许可数做限制,所以可以通过release动态增加许可数量 + */ + protected final boolean tryReleaseShared(int releases) { + for (;;) { + // 当前剩余许可 + int current = getState(); + // 许可更新值 + int next = current + releases; + // 如果许可更新值为负数,说明许可数量溢出,抛出错误 + if (next < current) // overflow + throw new Error("Maximum permit count exceeded"); + // CAS更新许可数量 + if (compareAndSetState(current, next)) + return true; + } + } + + /* 自旋 + CAS 减少许可数量 */ + final void reducePermits(int reductions) { + for (;;) { + // 当前剩余许可 + int current = getState(); + // 更新值 + int next = current - reductions; + // 较少许可数错误,抛出异常 + if (next > current) // underflow + throw new Error("Permit count underflow"); + // CAS更新许可数 + if (compareAndSetState(current, next)) + return; + } + } + + /* 丢弃所有许可 */ + final int drainPermits() { + for (;;) { + int current = getState(); + if (current == 0 || compareAndSetState(current, 0)) + return current; + } + } +} + +/** + * 非公平模式 + */ +static final class NonfairSync extends Sync { + private static final long serialVersionUID = -2694183684443567898L; + + NonfairSync(int permits) { + super(permits); + } + + protected int tryAcquireShared(int acquires) { + return nonfairTryAcquireShared(acquires); + } +} + +/** + * 公平模式 + */ +static final class FairSync extends Sync { + private static final long serialVersionUID = 2014338818796000944L; + + FairSync(int permits) { + super(permits); + } + + /** + * 公平模式获取许可 + * 公平模式不论许可是否充足,都会判断同步队列中是否有线程在等地,如果有,获取失败,排队阻塞 + */ + protected int tryAcquireShared(int acquires) { + for (;;) { + // 如果有线程在排队,立即返回 + if (hasQueuedPredecessors()) + return -1; + // 自旋 + cas获取许可 + int available = getState(); + int remaining = available - acquires; + if (remaining < 0 || + compareAndSetState(available, remaining)) + return remaining; + } + } +} +``` + +#### 主要 API + +```java +public class Semaphore implements java.io.Serializable { + + private static final long serialVersionUID = -3222578661600680210L; + + /** All mechanics via AbstractQueuedSynchronizer subclass */ + private final Sync sync; + + /** + * 根据给定的 总许可数permits,创建 Semaphore + */ + public Semaphore(int permits) { + sync = new NonfairSync(permits); + } + + /** + * fair为true表示使用公平锁模式,false使用非公平锁 + */ + public Semaphore(int permits, boolean fair) { + sync = fair ? new FairSync(permits) : new NonfairSync(permits); + } + + // --------------------- 获取许可 -------------------- + + /* 获取指定数量的许可 */ + public void acquire(int permits) throws InterruptedException { + if (permits < 0) throw new IllegalArgumentException(); + sync.acquireSharedInterruptibly(permits); + } + + /* 获取一个许可 */ + public void acquire() throws InterruptedException { + sync.acquireSharedInterruptibly(1); + } + + public final void acquireSharedInterruptibly(int arg) + throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); + if (tryAcquireShared(arg) < 0) // 获取许可,剩余许可>=0,则获取许可成功,<0获取许可失败,进入排队 + doAcquireSharedInterruptibly(arg); + } + + protected int tryAcquireShared(int acquires) { + return nonfairTryAcquireShared(acquires); + } + + /** + * @return 剩余许可数量。非负数,获取许可成功,负数,获取许可失败 + */ + final int nonfairTryAcquireShared(int acquires) { + for (;;) { + int available = getState(); + int remaining = available - acquires; + if (remaining < 0 || + compareAndSetState(available, remaining)) + return remaining; + } + } + + /** + * 获取许可失败,当前线程进入同步队列,排队阻塞 + */ + private void doAcquireSharedInterruptibly(int arg) + throws InterruptedException { + // 创建同步队列节点,并入队 + final Node node = addWaiter(Node.SHARED); + boolean failed = true; + try { + for (;;) { + // 如果当前节点是第二个节点,尝试获取锁 + final Node p = node.predecessor(); + if (p == head) { + int r = tryAcquireShared(arg); + if (r >= 0) { + setHeadAndPropagate(node, r); + p.next = null; // help GC + failed = false; + return; + } + } + // 阻塞当前线程 + if (shouldParkAfterFailedAcquire(p, node) && + parkAndCheckInterrupt()) + throw new InterruptedException(); + } + } finally { + if (failed) + cancelAcquire(node); + } + } + + // --------------------- 释放归还许可 ------------------------- + + /* 释放指定数量的许可 */ + public void release(int permits) { + if (permits < 0) throw new IllegalArgumentException(); + sync.releaseShared(permits); + } + + /* 释放一个许可 */ + public void release() { + sync.releaseShared(1); + } + + public final boolean releaseShared(int arg) { + // 归还许可成功 + if (tryReleaseShared(arg)) { + doReleaseShared(); + return true; + } + return false; + } + + /** + * 释放许可 + * 由于未对释放许可数做限制,所以可以通过release动态增加许可数量 + */ + protected final boolean tryReleaseShared(int releases) { + for (;;) { + int current = getState(); + int next = current + releases; + if (next < current) // overflow + throw new Error("Maximum permit count exceeded"); + if (compareAndSetState(current, next)) + return true; + } + } + + private void doReleaseShared() { + // 自旋,唤醒等待的第一个线程(其他线程将由第一个线程向后传递唤醒) + for (;;) { + Node h = head; + if (h != null && h != tail) { + int ws = h.waitStatus; + if (ws == Node.SIGNAL) { + if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) + continue; // loop to recheck cases + // 唤醒第一个等待线程 + unparkSuccessor(h); + } + else if (ws == 0 && + !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) + continue; // loop on failed CAS + } + if (h == head) // loop if head changed + break; + } + } +} +``` diff --git "a/docs/JDK/concurrentCoding/\350\257\246\350\247\243AbstractQueuedSynchronizer.md" "b/docs/JDK/concurrentCoding/\350\257\246\350\247\243AbstractQueuedSynchronizer.md" new file mode 100644 index 00000000..e25f07dd --- /dev/null +++ "b/docs/JDK/concurrentCoding/\350\257\246\350\247\243AbstractQueuedSynchronizer.md" @@ -0,0 +1,501 @@ +## 简介 + +AbstractQueuedSynchronizer 是 Doug Lea 大师创作的用来构建锁或者其他同步组件的基础框架类。J.U.C 中许多锁和并发工具类的核心实现都依赖于 AQS,如:ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch 等。 + +AQS 的源码中 方法很多,但主要做了三件事情: + +1. 管理 同步状态; +2. 维护 同步队列; +3. 阻塞和唤醒 线程。 + +另外,从行为上来区分就是 获取锁 和 释放锁,从模式上来区分就是 独占锁 和 共享锁。 + +## 实现原理 + +AQS 内部维护了一个 FIFO 队列来管理锁。线程首先会尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个 Node 节点放入同步队列阻塞起来,当持有锁的线程释放锁时,就会唤醒队列中的后继线程。 + +#### 获取锁的伪代码 + +``` +while (不满足获取锁的条件) { + 把当前线程包装成节点插入同步队列 + if (需要阻塞当前线程) + 阻塞当前线程直至被唤醒 +} +将当前线程从同步队列中移除 +``` + +#### 释放锁的伪代码 + +``` +修改同步状态 +if (修改后的状态允许其他线程获取到锁) + 唤醒后继线程 +``` + +## 源码解析 + +#### AQS 的核心数据结构 Node(内部类) + +```java +/** + * 当共享资源被某个线程占有,其他请求该资源的线程将会阻塞,从而进入同步队列。 + * AQS 中的同步队列通过链表实现,下面的内部类 Node 便是其实现的载体 + */ +static final class Node { + + /* 用于标记一个节点在共享模式下等待 */ + static final Node SHARED = new Node(); + + /* 用于标记一个节点在独占模式下等待 */ + static final Node EXCLUSIVE = null; + + /* 当前线程因为超时或者中断被取消。这是一个终结态,也就是状态到此为止 */ + static final int CANCELLED = 1; + + /** + * 当前线程的后继线程被阻塞或者即将被阻塞,当前线程释放锁或者取消后需要唤醒后继线程。 + * 这个状态一般都是后继线程来设置前驱节点的 + */ + static final int SIGNAL = -1; + + /* 当前线程在condition队列中 */ + static final int CONDITION = -2; + + /** + * 用于将唤醒后继线程传递下去,这个状态的引入是为了完善和增强共享锁的唤醒机制。 + * 在一个节点成为头节点之前,是不会跃迁为此状态的 + */ + static final int PROPAGATE = -3; + + /* 等待状态 */ + volatile int waitStatus; + + /* 前驱节点 */ + volatile Node prev; + + /* 后继节点 */ + volatile Node next; + + /* 节点对应的线程 */ + volatile Thread thread; + + /* 等待队列中的后继节点 */ + Node nextWaiter; + + /* 当前节点是否处于共享模式等待 */ + final boolean isShared() { + return nextWaiter == SHARED; + } + + /* 获取前驱节点,如果为空的话抛出空指针异常 */ + final Node predecessor() throws NullPointerException { + Node p = prev; + if (p == null) { + throw new NullPointerException(); + } else { + return p; + } + } + + Node() { + } + + /* addWaiter会调用此构造函数 */ + Node(Thread thread, Node mode) { + this.nextWaiter = mode; + this.thread = thread; + } + + /* Condition会用到此构造函数 */ + Node(Thread thread, int waitStatus) { + this.waitStatus = waitStatus; + this.thread = thread; + } +} +``` + +#### 获取独占锁的实现 + +```java +/** + * 首先尝试获取一次锁,如果成功,则返回; + * 否则会把当前线程包装成Node插入到队列中,在队列中会检测是否为head的直接后继,并尝试获取锁, + * 如果获取失败,则阻塞当前线程,直至被 "释放锁的线程" 唤醒或者被中断,随后再次尝试获取锁,如此反复 + */ +public final void acquire(int arg) { + if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) + selfInterrupt(); +} + +/** + * 在队列中新增一个节点 + */ +private Node addWaiter(Node mode) { + Node node = new Node(Thread.currentThread(), mode); + Node pred = tail; + // 快速尝试 + if (pred != null) { + node.prev = pred; + // 通过CAS在队尾插入当前节点 + if (compareAndSetTail(pred, node)) { + pred.next = node; + return node; + } + } + // 初始情况或者在快速尝试失败后插入节点 + enq(node); + return node; +} + +/** + * 通过循环+CAS在队列中成功插入一个节点后返回 + */ +private Node enq(final Node node) { + for (;;) { + Node t = tail; + // 初始化head和tail + if (t == null) { + if (compareAndSetHead(new Node())) + tail = head; + } else { + /* + * AQS的精妙在于很多细节代码,比如需要用CAS往队尾里增加一个元素 + * 此处的else分支是先在CAS的if前设置node.prev = t,而不是在CAS成功之后再设置。 + * 一方面是基于CAS的双向链表插入目前没有完美的解决方案,另一方面这样子做的好处是: + * 保证每时每刻tail.prev都不会是一个null值,否则如果node.prev = t + * 放在下面if的里面,会导致一个瞬间tail.prev = null,这样会使得队列不完整 + */ + node.prev = t; + // CAS设置tail为node,成功后把老的tail也就是t连接到node + if (compareAndSetTail(t, node)) { + t.next = node; + return t; + } + } + } +} + +/** + * 在队列中的节点通过此方法获取锁 + */ +final boolean acquireQueued(final Node node, int arg) { + boolean failed = true; + try { + boolean interrupted = false; + for (;;) { + final Node p = node.predecessor(); + /* + * 检测当前节点的前驱节点是否为head,这是试获取锁的资格。 + * 如果是的话,则调用tryAcquire尝试获取锁,成功,则将head置为当前节点 + */ + if (p == head && tryAcquire(arg)) { + setHead(node); + p.next = null; // help GC + failed = false; + return interrupted; + } + /* + * 如果未成功获取锁,则根据前驱节点判断是否要阻塞。 + * 如果阻塞过程中被中断,则置interrupted标志位为true。 + * shouldParkAfterFailedAcquire方法在前驱状态不为SIGNAL的情况下都会循环重试获取锁 + */ + if (shouldParkAfterFailedAcquire(p, node) && + parkAndCheckInterrupt()) + interrupted = true; + } + } finally { + if (failed) + cancelAcquire(node); + } +} + +/** + * 根据前驱节点中的waitStatus来判断是否需要阻塞当前线程 + */ +private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { + int ws = pred.waitStatus; + if (ws == Node.SIGNAL) + /* + * 前驱节点设置为SIGNAL状态,在释放锁的时候会唤醒后继节点, + * 所以后继节点(也就是当前节点)现在可以阻塞自己 + */ + return true; + if (ws > 0) { + /* + * 前驱节点状态为取消,向前遍历,更新当前节点的前驱为往前第一个非取消节点。 + * 当前线程会之后会再次回到循环并尝试获取锁 + */ + do { + node.prev = pred = pred.prev; + } while (pred.waitStatus > 0); + pred.next = node; + } else { + /** + * 等待状态为0或者PROPAGATE(-3),设置前驱的等待状态为SIGNAL, + * 并且之后会回到循环再次重试获取锁 + */ + compareAndSetWaitStatus(pred, ws, Node.SIGNAL); + } + return false; +} + + +/** + * 该方法实现某个node取消获取锁 + */ +private void cancelAcquire(Node node) { + + if (node == null) + return; + + node.thread = null; + + // 遍历并更新节点前驱,把node的prev指向前部第一个非取消节点 + Node pred = node.prev; + while (pred.waitStatus > 0) + node.prev = pred = pred.prev; + + // 记录pred节点的后继为predNext,后续CAS会用到 + Node predNext = pred.next; + + // 直接把当前节点的等待状态置为取消,后继节点即便也在cancel可以跨越node节点 + node.waitStatus = Node.CANCELLED; + + /* + * 如果CAS将tail从node置为pred节点了 + * 则剩下要做的事情就是尝试用CAS将pred节点的next更新为null以彻底切断pred和node的联系。 + * 这样一来就断开了pred与pred的所有后继节点,这些节点由于变得不可达,最终会被回收掉。 + * 由于node没有后继节点,所以这种情况到这里整个cancel就算是处理完毕了。 + * + * 这里的CAS更新pred的next即使失败了也没关系,说明有其它新入队线程或者其它取消线程更新掉了。 + */ + if (node == tail && compareAndSetTail(node, pred)) { + compareAndSetNext(pred, predNext, null); + } else { + // 如果node还有后继节点,这种情况要做的事情是把pred和后继非取消节点拼起来 + int ws; + if (pred != head && + ((ws = pred.waitStatus) == Node.SIGNAL || + (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && + pred.thread != null) { + Node next = node.next; + /* + * 如果node的后继节点next非取消状态的话,则用CAS尝试把pred的后继置为node的后继节点 + * 这里if条件为false或者CAS失败都没关系,这说明可能有多个线程在取消,总归会有一个能成功的 + */ + if (next != null && next.waitStatus <= 0) + compareAndSetNext(pred, predNext, next); + } else { + /* + * 这时说明pred == head或者pred状态取消或者pred.thread == null + * 在这些情况下为了保证队列的活跃性,需要去唤醒一次后继线程。 + * 举例来说pred == head完全有可能实际上目前已经没有线程持有锁了, + * 自然就不会有释放锁唤醒后继的动作。如果不唤醒后继,队列就挂掉了。 + * + * 这种情况下看似由于没有更新pred的next的操作,队列中可能会留有一大把的取消节点。 + * 实际上不要紧,因为后继线程唤醒之后会走一次试获取锁的过程, + * 失败的话会走到shouldParkAfterFailedAcquire的逻辑。 + * 那里面的if中有处理前驱节点如果为取消则维护pred/next,踢掉这些取消节点的逻辑。 + */ + unparkSuccessor(node); + } + + /* + * 取消节点的next之所以设置为自己本身而不是null, + * 是为了方便AQS中Condition部分的isOnSyncQueue方法, + * 判断一个原先属于条件队列的节点是否转移到了同步队列。 + * + * 因为同步队列中会用到节点的next域,取消节点的next也有值的话, + * 可以断言next域有值的节点一定在同步队列上。 + * + * 在GC层面,和设置为null具有相同的效果 + */ + node.next = node; + } +} + +/** + * 唤醒后继线程 + */ +private void unparkSuccessor(Node node) { + int ws = node.waitStatus; + // 尝试将node的等待状态置为0,这样的话,后继争用线程可以有机会再尝试获取一次锁 + if (ws < 0) + compareAndSetWaitStatus(node, ws, 0); + + Node s = node.next; + /* + * 这里的逻辑就是如果node.next存在并且状态不为取消,则直接唤醒s即可 + * 否则需要从tail开始向前找到node之后最近的非取消节点。 + * + * 这里为什么要从tail开始向前查找也是值得琢磨的: + * 如果读到s == null,不代表node就为tail,参考addWaiter以及enq函数中的我的注释。 + * 不妨考虑到如下场景: + * 1. node某时刻为tail + * 2. 有新线程通过addWaiter中的if分支或者enq方法添加自己 + * 3. compareAndSetTail成功 + * 4. 此时这里的Node s = node.next读出来s == null,但事实上node已经不是tail,它有后继了! + */ + if (s == null || s.waitStatus > 0) { + s = null; + for (Node t = tail; t != null && t != node; t = t.prev) + if (t.waitStatus <= 0) + s = t; + } + if (s != null) + LockSupport.unpark(s.thread); +} +``` + +#### 释放独占锁的实现 + +释放一个独占锁,首先会调用 tryRelease 方法,在完全释放掉独占锁后,其后继线程是可以获取到独占锁的,因此释放线程需要做的事情是:唤醒一个队列中的后继线程,让它去尝试获取独占锁。 + +```java +public final boolean release(int arg) { + if (tryRelease(arg)) { + /* + * 此时的head节点可能有3种情况: + * 1. null (AQS的head延迟初始化+无竞争的情况) + * 2. 当前线程在获取锁时new出来的节点通过setHead设置的 + * 3. 由于通过tryRelease已经完全释放掉了独占锁,有新的节点在acquireQueued中获取到了独占锁,并设置了head + + * 第三种情况可以再分为两种情况: + * 情况一: + * 时刻1:线程A通过acquireQueued,持锁成功,set了head + * 时刻2:线程B通过tryAcquire试图获取独占锁失败失败,进入acquiredQueued + * 时刻3:线程A通过tryRelease释放了独占锁 + * 时刻4:线程B通过acquireQueued中的tryAcquire获取到了独占锁并调用setHead + * 时刻5:线程A读到了此时的head实际上是线程B对应的node + * 情况二: + * 时刻1:线程A通过tryAcquire直接持锁成功,head为null + * 时刻2:线程B通过tryAcquire试图获取独占锁失败失败,入队过程中初始化了head,进入acquiredQueued + * 时刻3:线程A通过tryRelease释放了独占锁,此时线程B还未开始tryAcquire + * 时刻4:线程A读到了此时的head实际上是线程B初始化出来的傀儡head + */ + Node h = head; + // head节点状态不会是CANCELLED,所以这里h.waitStatus != 0相当于h.waitStatus < 0 + if (h != null && h.waitStatus != 0) + // 唤醒后继线程,此函数在acquire中已经分析过,不再列举说明 + unparkSuccessor(h); + return true; + } + return false; +} +``` + +整个 release 做的事情就是: + +1. 调用 tryRelease; +2. 如果 tryRelease 返回 true 也就是独占锁被完全释放,唤醒后继线程。 + +#### 获取共享锁的实现 + +共享锁允许多个线程持有,如果要使用 AQS 中的共享锁,在实现 tryAcquireShared 方法 时需要注意,返回负数表示获取失败,返回 0 表示成功,但是后继争用线程不会成功,返回正数表示获取成功,并且后继争用线程也可能成功。 + +```java +public final void acquireShared(int arg) { + if (tryAcquireShared(arg) < 0) + doAcquireShared(arg); +} + +private void doAcquireShared(int arg) { + final Node node = addWaiter(Node.SHARED); + boolean failed = true; + try { + boolean interrupted = false; + for (;;) { + final Node p = node.predecessor(); + if (p == head) { + int r = tryAcquireShared(arg); + // 一旦共享获取成功,设置新的头结点,并且唤醒后继线程 + if (r >= 0) { + setHeadAndPropagate(node, r); + p.next = null; // help GC + if (interrupted) + selfInterrupt(); + failed = false; + return; + } + } + if (shouldParkAfterFailedAcquire(p, node) && + parkAndCheckInterrupt()) + interrupted = true; + } + } finally { + if (failed) + cancelAcquire(node); + } +} + +/** + * 这个函数做的事情有两件: + * 1. 在获取共享锁成功后,设置head节点 + * 2. 根据调用tryAcquireShared返回的状态以及节点本身的等待状态来判断是否需要唤醒后继线程 + */ +private void setHeadAndPropagate(Node node, int propagate) { + // 把当前的head封闭在方法栈上,用以下面的条件检查 + Node h = head; + setHead(node); + /* + * propagate是tryAcquireShared的返回值,这是决定是否传播唤醒的依据之一。 + * h.waitStatus为SIGNAL或者PROPAGATE时也根据node的下一个节点共享来决定是否传播唤醒, + * 这里为什么不能只用propagate > 0来决定是否可以传播在本文下面的思考问题中有相关讲述 + */ + if (propagate > 0 || h == null || h.waitStatus < 0 || + (h = head) == null || h.waitStatus < 0) { + Node s = node.next; + if (s == null || s.isShared()) + doReleaseShared(); + } +} + +/** + * 这是共享锁中的核心唤醒函数,主要做的事情就是唤醒下一个线程或者设置传播状态。 + * 后继线程被唤醒后,会尝试获取共享锁,如果成功之后,则又会调用setHeadAndPropagate,将唤醒传播下去。 + * 这个函数的作用是保障在acquire和release存在竞争的情况下,保证队列中处于等待状态的节点能够有办法被唤醒。 + */ +private void doReleaseShared() { + /* + * 以下的循环做的事情就是,在队列存在后继线程的情况下,唤醒后继线程; + * 或者由于多线程同时释放共享锁由于处在中间过程,读到head节点等待状态为0的情况下, + * 虽然不能unparkSuccessor,但为了保证唤醒能够正确稳固传递下去,设置节点状态为PROPAGATE。 + * 这样的话获取锁的线程在执行setHeadAndPropagate时可以读到PROPAGATE,从而由获取锁的线程去释放后继等待线程 + */ + for (;;) { + Node h = head; + // 如果队列中存在后继线程。 + if (h != null && h != tail) { + int ws = h.waitStatus; + if (ws == Node.SIGNAL) { + if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) + continue; + unparkSuccessor(h); + } + // 如果h节点的状态为0,需要设置为PROPAGATE用以保证唤醒的传播。 + else if (ws == 0 && + !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) + continue; + } + // 检查h是否仍然是head,如果不是的话需要再进行循环。 + if (h == head) + break; + } +} +``` + +#### 释放共享锁的实现 + +共享锁的获取和释放都会涉及到 doReleaseShared 方法,也就是后继线程的唤醒。 + +```java +public final boolean releaseShared(int arg) { + if (tryReleaseShared(arg)) { + // doReleaseShared的实现上面获取共享锁已经介绍 + doReleaseShared(); + return true; + } + return false; +} +``` diff --git "a/docs/LearningExperience/ConcurrentProgramming/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\234\250\345\220\204\344\270\273\346\265\201\346\241\206\346\236\266\344\270\255\347\232\204\345\272\224\347\224\250.md" "b/docs/LearningExperience/ConcurrentProgramming/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\234\250\345\220\204\344\270\273\346\265\201\346\241\206\346\236\266\344\270\255\347\232\204\345\272\224\347\224\250.md" index 6f8e128e..b0ae5c1a 100644 --- "a/docs/LearningExperience/ConcurrentProgramming/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\234\250\345\220\204\344\270\273\346\265\201\346\241\206\346\236\266\344\270\255\347\232\204\345\272\224\347\224\250.md" +++ "b/docs/LearningExperience/ConcurrentProgramming/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\234\250\345\220\204\344\270\273\346\265\201\346\241\206\346\236\266\344\270\255\347\232\204\345\272\224\347\224\250.md" @@ -1,43 +1,57 @@ Spring、Netty、Mybatis 等框架的代码中大量运用了 Java 多线程编程技巧。并发编程处理的恰当与否,将直接影响架构的性能。本章通过对 这些框架源码 的分析,结合并发编程的常用技巧,来讲解多线程编程在这些主流框架中的应用。 -## Java内存模型 -JVM规范 定义了 Java内存模型 来屏蔽掉各种操作系统、虚拟机实现厂商和硬件的内存访问差异,以确保 Java 程序 在所有操作系统和平台上能够达到一致的内存访问效果。 +## Java 内存模型 + +JVM 规范 定义了 Java 内存模型 来屏蔽掉各种操作系统、虚拟机实现厂商和硬件的内存访问差异,以确保 Java 程序 在所有操作系统和平台上能够达到一致的内存访问效果。 ### 工作内存和主内存 -Java内存模型 规定所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,工作内存保存了 对应该线程使用的变量的主内存副本拷贝。线程对这些变量的操作都在自己的工作内存中进行,不能直接操作主内存 和 其他工作内存中存储的变量或者变量副本。线程间的变量传递需通过主内存来完成,三者的关系如下图所示。 + +Java 内存模型 规定所有的变量都存储在主内存中,每个线程都有自己独立的工作内存,工作内存保存了 对应该线程使用的变量的主内存副本拷贝。线程对这些变量的操作都在自己的工作内存中进行,不能直接操作主内存 和 其他工作内存中存储的变量或者变量副本。线程间的变量传递需通过主内存来完成,三者的关系如下图所示。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221000348294.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) -### Java内存操作协议 -Java内存模型定义了8种操作来完成主内存和工作内存的变量访问,具体如下。 + +### Java 内存操作协议 + +Java 内存模型定义了 8 种操作来完成主内存和工作内存的变量访问,具体如下。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200221001115193.png) + - read:把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。 - load:把从主内存中读取的变量值载入工作内存的变量副本中。 - use:把工作内存中一个变量的值传递给 Java 虚拟机执行引擎。 - assign:把从执行引擎接收到的变量的值赋值给工作内存中的变量。 -- store:把工作内存中一个变量的值传送到主内存中,以便随后的write操作。 +- store:把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作。 - write:工作内存传递过来的变量值放入主内存中。 - lock:把主内存的一个变量标识为某个线程独占的状态。 - unlock:把主内存中 一个处于锁定状态的变量释放出来,被释放后的变量才可以被其他线程锁定。 ### 内存模型三大特性 + #### 1、原子性 -这个概念与事务中的原子性大概一致,表明此操作是不可分割,不可中断的,要么全部执行,要么全部不执行。 Java内存模型直接保证的原子性操作包括read、load、use、assign、store、write、lock、unlock这八个。 + +这个概念与事务中的原子性大概一致,表明此操作是不可分割,不可中断的,要么全部执行,要么全部不执行。 Java 内存模型直接保证的原子性操作包括 read、load、use、assign、store、write、lock、unlock 这八个。 #### 2、可见性 -可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java内存模型 是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是 volatile变量 都是如此,普通变量与 volatile变量 的区别是,volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。除了 volatile 外,synchronized 也提供了可见性,synchronized 的可见性是由 “对一个变量执行 unlock操作 之前,必须先把此变量同步回主内存中(执行 store、write 操作)” 这条规则获得。 + +可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。Java 内存模型 是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是 volatile 变量 都是如此,普通变量与 volatile 变量 的区别是,volatile 的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此,可以说 volatile 保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。除了 volatile 外,synchronized 也提供了可见性,synchronized 的可见性是由 “对一个变量执行 unlock 操作 之前,必须先把此变量同步回主内存中(执行 store、write 操作)” 这条规则获得。 #### 3、有序性 + 单线程环境下,程序会 “有序的”执行,即:线程内表现为串行语义。但是在多线程环境下,由于指令重排,并发执行的正确性会受到影响。在 Java 中使用 volatile 和 synchronized 关键字,可以保证多线程执行的有序性。volatile 通过加入内存屏障指令来禁止内存的重排序。synchronized 通过加锁,保证同一时刻只有一个线程来执行同步代码。 ## volatile 的应用 -打开 NioEventLoop 的代码中,有一个控制 IO操作 和 其他任务运行比例的,用 volatile 修饰的 int类型字段 ioRatio,代码如下。 + +打开 NioEventLoop 的代码中,有一个控制 IO 操作 和 其他任务运行比例的,用 volatile 修饰的 int 类型字段 ioRatio,代码如下。 + ```java private volatile int ioRatio = 50; ``` + 这里为什么要用 volatile 修饰呢?我们首先对 volatile 关键字进行说明,然后再结合 Netty 的代码进行分析。 -关键字 volatile 是 Java 提供的最轻量级的同步机制,Java 内存模型对 volatile 专门定义了一些特殊的访问规则。下面我们就看它的规则。当一个变量被 volatile 修饰后,它将具备以下两种特性。 +关键字 volatile 是 Java 提供的最轻量级的同步机制,Java 内存模型对 volatile 专门定义了一些特殊的访问规则。下面我们就看它的规则。当一个变量被 volatile 修饰后,它将具备以下两种特性。 + - 线程可见性:当一个线程修改了被 volatile 修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改(什么叫立即看到最新的修改?感觉这句话太口语化且模糊,搞不太懂!),而普通变量却做不到这点。 - 禁止指令重排序优化:普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题,代码如下。 + ```java public class ThreadStopExample { @@ -63,20 +77,26 @@ public class ThreadStopExample { } } ``` + 我们预期程序会在 3s 后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下。 + ```java if (!stop) While(true) ...... ``` -workThread线程 在执行重排序后的代码时,是无法发现 变量stop 被其它线程修改的,因此无法停止运行。要解决这个问题,只要将 stop 前增加 volatile 修饰符即可。volatile 解决了如下两个问题。第一,主线程对 stop 的修改在 workThread线程 中可见,也就是说 workThread线程 立即看到了其他线程对于 stop变量 的修改。第二,禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。 + +workThread 线程 在执行重排序后的代码时,是无法发现 变量 stop 被其它线程修改的,因此无法停止运行。要解决这个问题,只要将 stop 前增加 volatile 修饰符即可。volatile 解决了如下两个问题。第一,主线程对 stop 的修改在 workThread 线程 中可见,也就是说 workThread 线程 立即看到了其他线程对于 stop 变量 的修改。第二,禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。 一些人认为使用 volatile 可以代替传统锁,提升并发性能,这个认识是错误的。volatile 仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠 volatile 来完全替代传统的锁。根据经验总结,volatile 最适用的场景是 “ 一个线程写,其他线程读 ”,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。下面我们继续对 Netty 的源码做分析。上面讲到了 ioRatio 被定义成 volatile,下面看看代码为什么要这样定义。 + ```java final long ioTime = System.nanoTime() - ioStartTime; runAllTasks(ioTime * (100 - ioRatio) / ioRatio); ``` -通过代码分析我们发现,在 NioEventLoop线程 中,ioRatio 并没有被修改,它是只读操作。既然没有修改,为什么要定义成 volatile 呢?继续看代码,我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法。 + +通过代码分析我们发现,在 NioEventLoop 线程 中,ioRatio 并没有被修改,它是只读操作。既然没有修改,为什么要定义成 volatile 呢?继续看代码,我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法。 + ```java public void setIoRatio(int ioRatio) { if (ioRatio <= 0 || ioRatio > 100) { @@ -85,13 +105,17 @@ workThread线程 在执行重排序后的代码时,是无法发现 变量stop this.ioRatio = ioRatio; } ``` -首先,NioEventLoop线程 没有调用该 set方法,说明调整 IO执行时间比例 是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对 volatile 的应用总结,此时可以使用 volatile 来代替传统的 synchronized关键字,以提升并发访问的性能。 + +首先,NioEventLoop 线程 没有调用该 set 方法,说明调整 IO 执行时间比例 是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对 volatile 的应用总结,此时可以使用 volatile 来代替传统的 synchronized 关键字,以提升并发访问的性能。 ## ThreadLocal 的应用及源码解析 -ThreadLocal 又称为线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的 TLS区域。使用 ThreadLocal变量 的 set(T value)方法 可以将数据存入 该线程本地存储区,使用 get() 方法 可以获取到之前存入的值。 -### ThreadLocal的常见应用 +ThreadLocal 又称为线程本地存储区(Thread Local Storage,简称为 TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的 TLS 区域。使用 ThreadLocal 变量 的 set(T value)方法 可以将数据存入 该线程本地存储区,使用 get() 方法 可以获取到之前存入的值。 + +### ThreadLocal 的常见应用 + 不使用 ThreadLocal。 + ```java public class SessionBean { public static class Session { @@ -125,7 +149,9 @@ public class SessionBean { } } ``` -上述代码中,session需要在方法间传递才可以修改和读取,保证线程中各方法操作的是一个。下面看一下使用 ThreadLocal 的代码。 + +上述代码中,session 需要在方法间传递才可以修改和读取,保证线程中各方法操作的是一个。下面看一下使用 ThreadLocal 的代码。 + ```java public class SessionBean { //定义一个静态ThreadLocal变量session,就能够保证各个线程有自己的一份,并且方法可以方便获取,不用传递 @@ -162,19 +188,22 @@ public class SessionBean { } } ``` + 在方法的内部实现中,直接可以通过 session.get() 获取到当前线程的 session,省掉了参数在方法间传递的环节。 -### ThreadLocal的实现原理 -一般,类属性中的数据是多个线程共享的,但 ThreadLocal类型的数据 声明为类属性,却可以为每一个使用它(通过set(T value)方法)的线程存储 线程私有的数据,通过其源码我们可以发现其中的原理。 +### ThreadLocal 的实现原理 + +一般,类属性中的数据是多个线程共享的,但 ThreadLocal 类型的数据 声明为类属性,却可以为每一个使用它(通过 set(T value)方法)的线程存储 线程私有的数据,通过其源码我们可以发现其中的原理。 + ```java public class ThreadLocal { /** * 下面的 getMap()方法 传入当前线程,获得一个ThreadLocalMap对象,说明每一个线程维护了 * 自己的一个 map,保证读取出来的value是自己线程的。 - * + * * ThreadLocalMap 是ThreadLocal静态内部类,存储value的键值就是ThreadLocal本身。 - * + * * 因此可以断定,每个线程维护一个ThreadLocalMap的键值对映射Map。不同线程的Map的 key值 是一样的, * 都是ThreadLocal,但 value 是不同的。 */ @@ -204,11 +233,13 @@ public class ThreadLocal { ``` ### ThreadLocal 在 Spring 中的使用 -Spring事务处理的设计与实现中大量使用了 ThreadLocal类,比如,TransactionSynchronizationManager 维护了一系列的 ThreadLocal变量,用于存储线程私有的 事务属性及资源。源码如下。 + +Spring 事务处理的设计与实现中大量使用了 ThreadLocal 类,比如,TransactionSynchronizationManager 维护了一系列的 ThreadLocal 变量,用于存储线程私有的 事务属性及资源。源码如下。 + ```java /** * 管理每个线程的资源和事务同步的中心帮助程序。供资源管理代码使用,但不供典型应用程序代码使用。 - * + * * 资源管理代码应该检查线程绑定的资源,如,JDBC连接 或 Hibernate Sessions。 * 此类代码通常不应该将资源绑定到线程,因为这是事务管理器的职责。另一个选项是, * 如果事务同步处于活动状态,则在首次使用时延迟绑定,以执行跨任意数量资源的事务。 @@ -274,8 +305,10 @@ public abstract class TransactionSynchronizationManager { } ``` -### ThreadLocal 在 Mybatis中的使用 -Mybatis 的 SqlSession对象 也是各线程私有的资源,所以对其的管理也使用到了 ThreadLocal类。源码如下。 +### ThreadLocal 在 Mybatis 中的使用 + +Mybatis 的 SqlSession 对象 也是各线程私有的资源,所以对其的管理也使用到了 ThreadLocal 类。源码如下。 + ```java public class SqlSessionManager implements SqlSessionFactory, SqlSession { @@ -395,9 +428,12 @@ public class SqlSessionManager implements SqlSessionFactory, SqlSession { } ``` -## J.U.C包的实际应用 +## J.U.C 包的实际应用 + ### 线程池 ThreadPoolExecutor + 首先通过 ThreadPoolExecutor 的源码 看一下线程池的主要参数及方法。 + ```java public class ThreadPoolExecutor extends AbstractExecutorService { @@ -426,7 +462,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { * 用于传输和保存等待执行任务的阻塞队列 */ private final BlockingQueue workQueue; - + /** * 线程工厂 * 用于创建新线程。threadFactory 创建的线程也是采用 new Thread() 方式,threadFactory @@ -544,7 +580,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { } tryTerminate(); } - + /** * 立即关闭线程池,已有的任务也会被抛弃 */ @@ -569,30 +605,36 @@ public class ThreadPoolExecutor extends AbstractExecutorService { } } ``` + 线程池执行流程,如下图所示。 -![avatar](images/ConcurrentProgramming/线程池流程.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/ConcurrentProgramming/线程池流程.png) + +#### Executors 提供的 4 种线程池 + +Executors 类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池:CachedThreadPool,FixedThreadPool,ScheduledThreadPool 和 SingleThreadExecutor。其功能如下。 -#### Executors 提供的4种线程池 -Executors类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池:CachedThreadPool,FixedThreadPool,ScheduledThreadPool 和 SingleThreadExecutor。其功能如下。 1. CachedThreadPool:用来创建一个几乎可以无限扩大的线程池(最大线程数为 Integer.MAX_VALUE),适用于执行大量短生命周期的异步任务。 -2. FixedThreadPool:创建一个固定大小的线程池,保证线程数可控,不会造成线程过多,导致系统负载更为严重。 -3. SingleThreadExecutor:创建一个单线程的线程池,可以保证任务按调用顺序执行。 +2. FixedThreadPool:创建一个固定大小的线程池,保证线程数可控,不会造成线程过多,导致系统负载更为严重。 +3. SingleThreadExecutor:创建一个单线程的线程池,可以保证任务按调用顺序执行。 4. ScheduledThreadPool:适用于执行 延时 或者 周期性 任务。 #### 如何配置线程池 -- **CPU密集型任务** -尽量使用较小的线程池,一般为 CPU核心数+1。 因为 CPU密集型任务 使得 CPU使用率 很高,若开过多的线程数,会造成 CPU 过度切换。 -- **IO密集型任务** -可以使用稍大的线程池,一般为 2*CPU核心数。 IO密集型任务 CPU使用率 并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU时间。 +- **CPU 密集型任务** + 尽量使用较小的线程池,一般为 CPU 核心数+1。 因为 CPU 密集型任务 使得 CPU 使用率 很高,若开过多的线程数,会造成 CPU 过度切换。 + +- **IO 密集型任务** + 可以使用稍大的线程池,一般为 2\*CPU 核心数。 IO 密集型任务 CPU 使用率 并不高,因此可以让 CPU 在等待 IO 的时候有其他线程去处理别的任务,充分利用 CPU 时间。 #### 线程池的实际应用 -Tomcat 在分发 web请求 时使用了线程池来处理。 +Tomcat 在分发 web 请求 时使用了线程池来处理。 ### BlockingQueue + #### 核心方法 + ```java public interface BlockingQueue extends Queue { @@ -632,23 +674,22 @@ public interface BlockingQueue extends Queue { int drainTo(Collection c, int maxElements); } ``` + #### 主要实现类 + - **ArrayBlockingQueue** -基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。 -ArrayBlockingQueue 在生产者放入数据 和 消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue。ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。 + 基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。 + ArrayBlockingQueue 在生产者放入数据 和 消费者获取数据时,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue。ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。 - **LinkedBlockingQueue** -基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。 -需要注意的是,如果构造一个 LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。 + 基于链表的阻塞队列,同 ArrayListBlockingQueue 类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue 可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而 LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。 + 需要注意的是,如果构造一个 LinkedBlockingQueue 对象,而没有指定其容量大小,LinkedBlockingQueue 会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。 - **PriorityBlockingQueue** - 基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。 - - - + 基于优先级的阻塞队列(优先级的判断通过构造函数传入的 Compator 对象来决定),但需要注意的是 PriorityBlockingQueue 并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁。 +### CAS 指令和原子类(应用比较多的就是计数器) -### CAS指令和原子类(应用比较多的就是计数器) 互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为**阻塞同步**,它属于一种**悲观的并发策略,我们称之为悲观锁**。随着硬件和操作系统指令集的发展和优化,产生了**非阻塞同步**,被称为**乐观锁**。简单地说,就是**先进行操作,操作完成之后再判断操作是否成功,是否有并发问题,如果有则进行失败补偿,如果没有就算操作成功**,这样就从根本上避免了同步锁的弊端。 -目前,在Java中应用最广泛的非阻塞同步就是 CAS。从 JDK1.5 以后,可以使用 CAS 操作,该操作由 sun.misc.Unsafe 类里的 compareAndSwapInt() 和 compareAndSwapLong() 等方法实现。通常情况下 sun.misc.Unsafe类 对于开发者是不可见的,因此,JDK 提供了很多 CAS包装类 简化开发者的使用,如 AtomicInteger。使用 Java 自带的 Atomic原子类,可以避免同步锁带来的并发访问性能降低的问题,减少犯错的机会。 +目前,在 Java 中应用最广泛的非阻塞同步就是 CAS。从 JDK1.5 以后,可以使用 CAS 操作,该操作由 sun.misc.Unsafe 类里的 compareAndSwapInt() 和 compareAndSwapLong() 等方法实现。通常情况下 sun.misc.Unsafe 类 对于开发者是不可见的,因此,JDK 提供了很多 CAS 包装类 简化开发者的使用,如 AtomicInteger。使用 Java 自带的 Atomic 原子类,可以避免同步锁带来的并发访问性能降低的问题,减少犯错的机会。 diff --git "a/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\345\210\233\345\273\272\345\236\213).md" "b/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\345\210\233\345\273\272\345\236\213).md" index ac1fa149..083b0f2a 100644 --- "a/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\345\210\233\345\273\272\345\236\213).md" +++ "b/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\345\210\233\345\273\272\345\236\213).md" @@ -1,22 +1,26 @@ -设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring系列、Mybatis)及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 +设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring 系列、Mybatis)及 JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 本篇博文主要看一下创建型的几个设计模式,即,单例模式、各种工厂模式 及 建造者模式。 ## 单例模式 + ### 个人理解 -确保某个类只有一个实例,并提供该实例的获取方法。实际应用很多,不管是框架、JDK还是实际的项目开发,但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用,但通过“双检锁机制”来保证单例的实现很少见。 + +确保某个类只有一个实例,并提供该实例的获取方法。实际应用很多,不管是框架、JDK 还是实际的项目开发,但大都会使用“饿汉式”或“枚举”来实现单例。“懒汉式”也有一些应用,但通过“双检锁机制”来保证单例的实现很少见。 ### 实现方式 -最简单的就是 使用一个私有构造函数、一个私有静态变量,以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多BB咯,这里强调一下双检锁懒汉式实现的坑,以及枚举方式的实现吧,最后再结合spring源码 扩展一下单例bean的实现原理。 + +最简单的就是 使用一个私有构造函数、一个私有静态变量,以及一个公共静态方法的方式来实现。懒汉式、饿汉式等简单实现就不多 BB 咯,这里强调一下双检锁懒汉式实现的坑,以及枚举方式的实现吧,最后再结合 spring 源码 扩展一下单例 bean 的实现原理。 **1. 双检锁实现的坑** + ```java /** * @author 云之君 * 双检锁 懒汉式,实现线程安全的单例 * 关键词:JVM指令重排、volatile、反射攻击 */ -public class Singleton3 { +public class Singleton3 { /** * 对于我们初级开发来说,这个volatile在实际开发中可能见过,但很少会用到 * 这里加个volatile进行修饰,也是本单例模式的精髓所在。 @@ -31,11 +35,11 @@ public class Singleton3 { * 回答:第一个线程在执行第2步之前就已经释放了锁吗?(没有)。如果不使用volatile修饰instance变量,那么其他线程进来的时候,看到的instance就有可能不是null的,因为已经执行了第3步,那么此时这个线程(执行 return instance;)使用的instance是一个没有初始化的instance,就会有问题。 */ private volatile static Singleton3 instance; - + private Singleton3(){ - + } - + public static Singleton3 getInstance(){ if(instance == null){ synchronized(Singleton3.class){ @@ -48,10 +52,12 @@ public class Singleton3 { } } ``` + **2. 枚举实现** -其它的单例模式实现往往都会面临序列化 和 反射攻击的问题,比如上面的Singleton3如果实现了Serializable接口,那么在每次序列化时都会创建一个新对象,若要保证单例,必须声明所有字段都是transient的,并且提供一个readResolve()方法。反射攻击可以通过setAccessible()方法将私有的构造方法公共化,进而实例化。若要防止这种攻击,就需要在构造方法中添加 防止实例化第二个对象的代码。 +其它的单例模式实现往往都会面临序列化 和 反射攻击的问题,比如上面的 Singleton3 如果实现了 Serializable 接口,那么在每次序列化时都会创建一个新对象,若要保证单例,必须声明所有字段都是 transient 的,并且提供一个 readResolve()方法。反射攻击可以通过 setAccessible()方法将私有的构造方法公共化,进而实例化。若要防止这种攻击,就需要在构造方法中添加 防止实例化第二个对象的代码。 + +枚举实现的单例在面对 复杂的序列化及反射攻击时,依然能够保持自己的单例状态,所以被认为是单例的最佳实践。比如,mybatis 在定义 SQL 命令类型时就使用到了枚举。 -枚举实现的单例在面对 复杂的序列化及反射攻击时,依然能够保持自己的单例状态,所以被认为是单例的最佳实践。比如,mybatis在定义SQL命令类型时就使用到了枚举。 ```java package org.apache.ibatis.mapping; @@ -63,8 +69,10 @@ public enum SqlCommandType { } ``` -### JDK中的范例 +### JDK 中的范例 + **1. java.lang.Runtime** + ```java /** * 每个Java应用程序都有一个单例的Runtime对象,通过getRuntime()方法获得 @@ -86,6 +94,7 @@ public class Runtime { ``` **2. java.awt.Desktop** + ```java public class Desktop { @@ -120,24 +129,26 @@ public class Desktop { } ``` -### Spring的单例bean是如何实现的? -Spring实现单例bean是使用map注册表和synchronized同步机制实现的,通过分析spring的 AbstractBeanFactory 中的 doGetBean 方法和DefaultSingletonBeanRegistry的getSingleton()方法,可以理解其实现原理。 +### Spring 的单例 bean 是如何实现的? + +Spring 实现单例 bean 是使用 map 注册表和 synchronized 同步机制实现的,通过分析 spring 的 AbstractBeanFactory 中的 doGetBean 方法和 DefaultSingletonBeanRegistry 的 getSingleton()方法,可以理解其实现原理。 + ```java public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { ...... - + /** * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * 真正实现向IOC容器获取Bean的功能,也是触发依赖注入(DI)功能的地方 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ @SuppressWarnings("unchecked") - protected T doGetBean(final String name, final Class requiredType, final Object[] args, + protected T doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { - + ...... - + //创建单例模式bean的实例对象 if (mbd.isSingleton()) { //这里使用了一个匿名内部类,创建Bean实例对象,并且注册给所依赖的对象 @@ -161,9 +172,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp //获取给定Bean的实例对象 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } - + ...... - + } } @@ -175,13 +186,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements /** 单例的bean实例的缓存 */ private final Map singletonObjects = new ConcurrentHashMap(64); - + /** * 返回给定beanName的 已经注册的 单例bean,如果没有注册,则注册并返回 */ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { Assert.notNull(beanName, "'beanName' must not be null"); - + // 加锁,保证单例bean在多线程环境下不会创建多个 synchronized (this.singletonObjects) { // 先从缓存中取,有就直接返回,没有就创建、注册到singletonObjects、返回 @@ -227,9 +238,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements ``` ## 简单工厂模式 + ### 个人理解 + 把同一系列类的实例化交由一个工厂类进行集中管控。与其说它是一种设计模式,倒不如把它看成一种编程习惯,因为它不符合“开闭原则”,增加新的产品类需要修改工厂类的代码。 + ### 简单实现 + ```java public interface Hero { void speak(); @@ -261,14 +276,19 @@ public class HeroFactory { } } ``` -这种设计方式只在我们产品的“FBM资金管理”模块有看到过,其中对100+个按钮类进行了集中管控,不过其设计结构比上面这种要复杂的多。 + +这种设计方式只在我们产品的“FBM 资金管理”模块有看到过,其中对 100+个按钮类进行了集中管控,不过其设计结构比上面这种要复杂的多。 ## 工厂方法模式 + ### 个人理解 + 在顶级工厂(接口/抽象类)中定义 产品类的获取方法,由具体的子工厂实例化对应的产品,一般是一个子工厂对应一个特定的产品,实现对产品的集中管控,并且符合“开闭原则”。 -### Mybatis中的范例 -mybatis中数据源DataSource的获取使用到了该设计模式。接口DataSourceFactory定义了获取DataSource对象的方法,各实现类 完成了获取对应类型的DataSource对象的实现。(mybatis的源码都是缩进两个空格,难道国外的编码规范有独门派系?) +### Mybatis 中的范例 + +mybatis 中数据源 DataSource 的获取使用到了该设计模式。接口 DataSourceFactory 定义了获取 DataSource 对象的方法,各实现类 完成了获取对应类型的 DataSource 对象的实现。(mybatis 的源码都是缩进两个空格,难道国外的编码规范有独门派系?) + ```java public interface DataSourceFactory { @@ -283,7 +303,7 @@ public interface DataSourceFactory { public class JndiDataSourceFactory implements DataSourceFactory { private DataSource dataSource; - + @Override public DataSource getDataSource() { return dataSource; @@ -348,21 +368,25 @@ public interface DataSource extends CommonDataSource, Wrapper { throws SQLException; } ``` -DataSource最主要的几个实现类内容都比较多,代码就不贴出来咯,感兴趣的同学可以到我的源码分析专题中看到详细解析。 + +DataSource 最主要的几个实现类内容都比较多,代码就不贴出来咯,感兴趣的同学可以到我的源码分析专题中看到详细解析。 **tips:什么时候该用简单工厂模式?什么时候该用工厂方法模式呢?** 个人认为,工厂方法模式符合“开闭原则”,增加新的产品类不用修改代码,应当优先考虑使用这种模式。如果产品类结构简单且数量庞大时,还是使用简单工厂模式更容易维护些,如:上百个按钮类。 ## 抽象工厂模式 + ### 个人理解 + 设计结构上与“工厂方法”模式很像,最主要的区别是,工厂方法模式中 一个子工厂只对应**一个**具体的产品,而抽象工厂模式中,一个子工厂对应**一组**具有相关性的产品,即,存在多个获取不同产品的方法。这种设计模式也很少见人用,倒是“工厂方法”模式见的最多。 ### 简单实现 + ```java public abstract class AbstractFactory { abstract protected AbstractProductA createProductA(); - + abstract protected AbstractProductB createProductB(); } @@ -373,7 +397,7 @@ public class ConcreteFactory1 extends AbstractFactory { protected AbstractProductA createProductA() { return new ProductA1(); } - + @Override protected AbstractProductB createProductB() { return new ProductB1(); @@ -387,7 +411,7 @@ public class ConcreteFactory2 extends AbstractFactory { protected AbstractProductA createProductA() { return new ProductA2(); } - + @Override protected AbstractProductB createProductB() { return new ProductB2(); @@ -401,7 +425,7 @@ public class Client { AbstractFactory factory = new ConcreteFactory1(); AbstractProductA productA = factory.createProductA(); AbstractProductB productB = factory.createProductB(); - + ... // 结合使用productA和productB进行后续操作 ... @@ -409,8 +433,10 @@ public class Client { } ``` -### JDK中的范例 -JDK的javax.xml.transform.TransformerFactory组件使用了类似“抽象工厂”模式的设计,抽象类TransformerFactory定义了两个抽象方法newTransformer()和newTemplates()分别用于生成Transformer对象 和 Templates对象,其两个子类进行了不同的实现,源码如下(版本1.8)。 +### JDK 中的范例 + +JDK 的 javax.xml.transform.TransformerFactory 组件使用了类似“抽象工厂”模式的设计,抽象类 TransformerFactory 定义了两个抽象方法 newTransformer()和 newTemplates()分别用于生成 Transformer 对象 和 Templates 对象,其两个子类进行了不同的实现,源码如下(版本 1.8)。 + ```java public abstract class TransformerFactory { @@ -427,7 +453,7 @@ public abstract class TransformerFactory { */ public class TransformerFactoryImpl extends SAXTransformerFactory implements SourceLoader, ErrorListener { - + @Override public Transformer newTransformer(Source source) throws TransformerConfigurationException { final Templates templates = newTemplates(source); @@ -441,7 +467,7 @@ public class TransformerFactoryImpl @Override public Templates newTemplates(Source source) throws TransformerConfigurationException { - + ...... return new TemplatesImpl(bytecodes, transletName, @@ -483,10 +509,12 @@ public class SmartTransformerFactoryImpl extends SAXTransformerFactory { ``` ## 建造者模式 + ### 个人理解 + 该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。 -![avatar](../../../images/DesignPattern/建造者模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/建造者模式类图.png) 该模式的主要角色如下: @@ -495,10 +523,12 @@ public class SmartTransformerFactoryImpl extends SAXTransformerFactory { - 导演(Director):通过调用具体建造者创建需要的产品对象; - 产品(Product):被建造的复杂对象。 -其中的导演角色不必了解产品类的内部细节,只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造,使产品对象的上层代码与产品对象的创建过程解耦。建造者模式将复杂产品的创建过程分散到不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的, 因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展,这符合“开放一封闭” 原则。 +其中的导演角色不必了解产品类的内部细节,只提供需要的信息给建造者,由具体建造者处理这些信息(这个处理过程可能会比较复杂)并完成产品构造,使产品对象的上层代码与产品对象的创建过程解耦。建造者模式将复杂产品的创建过程分散到不同的构造步骤中,这样可以对产品创建过程实现更加精细的控制,也会使创建过程更加清晰。每个具体建造者都可以创建出完整的产品对象,而且具体建造者之间是相互独立的, 因此系统就可以通过不同的具体建造者,得到不同的产品对象。当有新产品出现时,无须修改原有的代码,只需要添加新的具体建造者即可完成扩展,这符合“开放一封闭” 原则。 + +### 典型的范例 StringBuilder 和 StringBuffer + +相信在拼 SQL 语句时大家一定经常用到 StringBuffer 和 StringBuilder 这两个类,它们就用到了建造者设计模式,源码如下(版本 1.8): -### 典型的范例 StringBuilder和StringBuffer -相信在拼SQL语句时大家一定经常用到StringBuffer和StringBuilder这两个类,它们就用到了建造者设计模式,源码如下(版本1.8): ```java abstract class AbstractStringBuilder implements Appendable, CharSequence { @@ -511,7 +541,7 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence { * The count is the number of characters used. */ int count; - + /** * Creates an AbstractStringBuilder of the specified capacity. */ @@ -582,12 +612,15 @@ public final class StringBuffer extends AbstractStringBuilder } } ``` -### Mybatis中的范例 -MyBatis 的初始化过程使用了建造者模式,抽象类 BaseBuilder 扮演了“建造者接口”的角色,对一些公用方法进行了实现,并定义了公共属性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等实现类扮演了“具体建造者”的角色,分别用于解析mybatis-config.xml配置文件、映射配置文件 以及 SQL节点。Configuration 和 SqlSessionFactoryBuilder 则分别扮演了“产品” 和 “导演”的角色。**即,SqlSessionFactoryBuilder 使用了 BaseBuilder建造者组件 对复杂对象 Configuration 进行了构建。** -BaseBuilder组件的设计与上面标准的建造者模式是有很大不同的,BaseBuilder的建造者模式主要是为了将复杂对象Configuration的构建过程分解的层次更清晰,将整个构建过程分解到多个“具体构造者”类中,需要这些“具体构造者”共同配合才能完成Configuration的构造,单个“具体构造者”不具有单独构造产品的能力,这与StringBuilder及StringBuffer是不同的。 +### Mybatis 中的范例 + +MyBatis 的初始化过程使用了建造者模式,抽象类 BaseBuilder 扮演了“建造者接口”的角色,对一些公用方法进行了实现,并定义了公共属性。XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 等实现类扮演了“具体建造者”的角色,分别用于解析 mybatis-config.xml 配置文件、映射配置文件 以及 SQL 节点。Configuration 和 SqlSessionFactoryBuilder 则分别扮演了“产品” 和 “导演”的角色。**即,SqlSessionFactoryBuilder 使用了 BaseBuilder 建造者组件 对复杂对象 Configuration 进行了构建。** + +BaseBuilder 组件的设计与上面标准的建造者模式是有很大不同的,BaseBuilder 的建造者模式主要是为了将复杂对象 Configuration 的构建过程分解的层次更清晰,将整个构建过程分解到多个“具体构造者”类中,需要这些“具体构造者”共同配合才能完成 Configuration 的构造,单个“具体构造者”不具有单独构造产品的能力,这与 StringBuilder 及 StringBuffer 是不同的。 个人理解的构建者模式 其核心就是用来构建复杂对象的,比如 mybatis 对 Configuration 对象的构建。当然,我们也可以把 对这个对象的构建过程 写在一个类中,来满足我们的需求,但这样做的话,这个类就会变得及其臃肿,难以维护。所以把整个构建过程合理地拆分到多个类中,分别构建,整个代码就显得非常规整,且思路清晰,而且 建造者模式符合 开闭原则。其源码实现如下。 + ```java public abstract class BaseBuilder { @@ -832,4 +865,4 @@ public class SqlSessionFactoryBuilder { return new DefaultSqlSessionFactory(config); } } -``` \ No newline at end of file +``` diff --git "a/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\347\273\223\346\236\204\345\236\213).md" "b/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\347\273\223\346\236\204\345\236\213).md" index 646709d7..d6366baf 100644 --- "a/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\347\273\223\346\236\204\345\236\213).md" +++ "b/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\347\273\223\346\236\204\345\236\213).md" @@ -1,17 +1,21 @@ -设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring系列、Mybatis)及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 +设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring 系列、Mybatis)及 JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 本篇博文主要看一下结构型的几个设计模式,即,适配器模式、代理模式 及 装饰器模式。 ## 适配器模式 + #### 个人理解 + 从名字就很好理解,主要起到一个连接适配的作用。生活中也有很多这样的例子,比如我们给笔记本充电,不能直接使用国家标准电源,都需要一个“电源适配器”来适配电源输入的电流。使用适配器模式最大的好处就是复用现有组件。应用程序需要复用现有的类,但接口不能被该应用程序兼容,则无法直接使用。这种场景下就适合使用适配器模式实现接口的适配,从而完成组件的复用。 -很明显,适配器模式通过提供 Adapter 的方式完成接口适配,实现了程序复用 Adaptee(被适配者) 的需求,避免了修改 Adaptee 实现接口,当有新的 Adaptee 需要被复用时,只要添加新的 Adapter 即可,这是符合“开放封闭”原则的。 +很明显,适配器模式通过提供 Adapter 的方式完成接口适配,实现了程序复用 Adaptee(被适配者) 的需求,避免了修改 Adaptee 实现接口,当有新的 Adaptee 需要被复用时,只要添加新的 Adapter 即可,这是符合“开放封闭”原则的。 + +本模式的应用也比较广泛,因为实际的开发中也有很多适配工作要做,所以 这些都可以考虑使用适配器模式。在 spring 及 mybatis 中也使用了本模式,分析如下。 + +#### Spring 中的应用 -本模式的应用也比较广泛,因为实际的开发中也有很多适配工作要做,所以 这些都可以考虑使用适配器模式。在spring及mybatis中也使用了本模式,分析如下。 +Spring 在 AOP 模块中,设计了一套 AdvisorAdapter 组件,将各种 Advice 对象适配成了相对应的 MethodInterceptor 对象。其中,AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter 及 ThrowsAdviceAdapter 实现类扮演了“适配器”的角色,AfterReturningAdvice、MethodBeforeAdvice 及 ThrowsAdvice 扮演了“被适配者”角色,而 AfterReturningAdviceInterceptor、MethodBeforeAdviceInterceptor 及 ThrowsAdviceInterceptor 则扮演了“适配目标”的角色。其源码实现如下。 -#### Spring中的应用 -Spring 在 AOP 模块中,设计了一套 AdvisorAdapter 组件,将各种 Advice 对象适配成了相对应的 MethodInterceptor 对象。其中,AfterReturningAdviceAdapter、MethodBeforeAdviceAdapter 及 ThrowsAdviceAdapter 实现类扮演了“适配器”的角色,AfterReturningAdvice、MethodBeforeAdvice 及 ThrowsAdvice 扮演了“被适配者”角色,而AfterReturningAdviceInterceptor、MethodBeforeAdviceInterceptor 及 ThrowsAdviceInterceptor 则扮演了“适配目标”的角色。其源码实现如下。 ```java /** * Advice 适配器的顶级接口 @@ -23,7 +27,7 @@ public interface AdvisorAdapter { * 此适配器是否能适配 给定的 advice 对象 */ boolean supportsAdvice(Advice advice); - + /** * 获取传入的 advisor 中的 Advice 对象,将其适配成 MethodInterceptor 对象 */ @@ -42,7 +46,7 @@ class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable { public boolean supportsAdvice(Advice advice) { return (advice instanceof AfterReturningAdvice); } - + public MethodInterceptor getInterceptor(Advisor advisor) { AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice(); return new AfterReturningAdviceInterceptor(advice); @@ -61,7 +65,7 @@ class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { public boolean supportsAdvice(Advice advice) { return (advice instanceof MethodBeforeAdvice); } - + public MethodInterceptor getInterceptor(Advisor advisor) { MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); return new MethodBeforeAdviceInterceptor(advice); @@ -80,7 +84,7 @@ class ThrowsAdviceAdapter implements AdvisorAdapter, Serializable { public boolean supportsAdvice(Advice advice) { return (advice instanceof ThrowsAdvice); } - + public MethodInterceptor getInterceptor(Advisor advisor) { return new ThrowsAdviceInterceptor(advisor.getAdvice()); } @@ -117,7 +121,7 @@ public interface ThrowsAdvice extends AfterAdvice { public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable { private final AfterReturningAdvice advice; - + /** * 为给定的 advice 创建一个 AfterReturningAdviceInterceptor 对象 */ @@ -125,7 +129,7 @@ public class AfterReturningAdviceInterceptor implements MethodInterceptor, After Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } - + public Object invoke(MethodInvocation mi) throws Throwable { Object retVal = mi.proceed(); this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); @@ -136,7 +140,7 @@ public class AfterReturningAdviceInterceptor implements MethodInterceptor, After public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { private MethodBeforeAdvice advice; - + /** * 为指定的advice创建对应的MethodBeforeAdviceInterceptor对象 */ @@ -144,7 +148,7 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Seriali Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } - + /** * 这个invoke方法是拦截器的回调方法,会在代理对象的方法被调用时触发回调 */ @@ -159,17 +163,17 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Seriali public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { private static final String AFTER_THROWING = "afterThrowing"; - + private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class); - + private final Object throwsAdvice; - + private final Map exceptionHandlerMap = new HashMap(); - + public ThrowsAdviceInterceptor(Object throwsAdvice) { Assert.notNull(throwsAdvice, "Advice must not be null"); this.throwsAdvice = throwsAdvice; - + // 配置 throwsAdvice 的回调 Method[] methods = throwsAdvice.getClass().getMethods(); for (Method method : methods) { @@ -184,13 +188,13 @@ public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice { } } } - + if (this.exceptionHandlerMap.isEmpty()) { throw new IllegalArgumentException( "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]"); } } - + public Object invoke(MethodInvocation mi) throws Throwable { // 把对目标对象的方法调用放入 try/catch 中,并在 catch 中触发 // throwsAdvice 的回调,把异常接着向外抛,不做过多处理 @@ -219,7 +223,7 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se * 实现 spring AOP 的 Advice 增强功能相对应 */ private final List adapters = new ArrayList(3); - + /** * 将已实现的 AdviceAdapter 加入 list */ @@ -228,18 +232,18 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se registerAdvisorAdapter(new AfterReturningAdviceAdapter()); registerAdvisorAdapter(new ThrowsAdviceAdapter()); } - + public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List interceptors = new ArrayList(3); - + // 从Advisor通知器中获取配置的Advice Advice advice = advisor.getAdvice(); - + // 如果advice是MethodInterceptor类型的,直接加进interceptors,不用适配 if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); } - + // 如果advice不是MethodInterceptor类型的,就将其适配成MethodInterceptor, // 当前的DefaultAdvisorAdapterRegistry对象 在初始化时就已经为 adapters 添加了 // 三种 AdvisorAdapter 的实例 @@ -255,11 +259,11 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se } return interceptors.toArray(new MethodInterceptor[interceptors.size()]); } - + public void registerAdvisorAdapter(AdvisorAdapter adapter) { this.adapters.add(adapter); } - + /** * 如果adviceObject是Advisor的实例,则将adviceObject转换成Advisor类型并返回 */ @@ -283,12 +287,15 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se } } ``` + 像这样整理出来以后,其类结构及层次设计还是比较清晰明了的,比起很多书上范例的浅尝辄止,结合这些实际场景及源码去理解这些设计模式,要让人更加印象深刻。 -#### Mybatis中的应用 +#### Mybatis 中的应用 + MyBatis 的日志模块中使用了适配器模式,MyBatis 内部调用其日志模块时,使用了其内部接口(org.apache.ibatis.logging.Log)。但是 Log4j、Slf4j 等第三方日志框架对外提供的接口各不相同,MyBatis 为了集成和复用这些第三方日志框架,在其日志模块中提供了多种 Adapter 实现 如:Log4jImpl、Slf4jImpl 等等,它们将这些 “第三方日志框架对外的接口方法” 适配成 “Log 接口方法”,这样 MyBatis 内部就可以统一通过该 Log 接口调用第三方日志框架的功能了。 -其中,Log 接口定义了日志模块的功能,日志适配器 Log4jImpl、Slf4jImpl 等通过实现此接口,将对应框架中的日志类 (Logger) 里的方法 适配成Log接口中定义的方法。 +其中,Log 接口定义了日志模块的功能,日志适配器 Log4jImpl、Slf4jImpl 等通过实现此接口,将对应框架中的日志类 (Logger) 里的方法 适配成 Log 接口中定义的方法。 + ```java /** * mybatis的日志接口,统一了不同日志框架的 日志操作, @@ -426,13 +433,17 @@ public class Jdk14LoggingImpl implements Log { ``` ## 代理模式 + #### 个人理解 -代理模式的实际应用 主要体现在框架开发中,日常业务上的开发工作中很少有场景需要使用该模式。而代理模式中 动态代理尤为重要,不管是自己公司的内部框架 还是 一些知名的开源框架,很多重要的实现都用到了该模式。比如,有些 CS架构中,Client端的远程方法调用 就使用了动态代理,在invoke()方法中 为被代理对象调用的方法 织入远程调用处理,然后将远程处理的结果返回给调用者;Spring的AOP也是优先使用JDK动态代理来完成;Mybatis为JDBC操作织入日志处理,等等。下面我们结合源码来深入理解一下这个模式。 + +代理模式的实际应用 主要体现在框架开发中,日常业务上的开发工作中很少有场景需要使用该模式。而代理模式中 动态代理尤为重要,不管是自己公司的内部框架 还是 一些知名的开源框架,很多重要的实现都用到了该模式。比如,有些 CS 架构中,Client 端的远程方法调用 就使用了动态代理,在 invoke()方法中 为被代理对象调用的方法 织入远程调用处理,然后将远程处理的结果返回给调用者;Spring 的 AOP 也是优先使用 JDK 动态代理来完成;Mybatis 为 JDBC 操作织入日志处理,等等。下面我们结合源码来深入理解一下这个模式。 #### 动态代理原理 -静态代理没什么好讲的,很少见用到,功能也比较薄弱,本篇重点讲解动态代理。首先了解一下JDK动态代理的原理,这对理解 Spring AOP 部分的源码及实现原理也很有帮助。 + +静态代理没什么好讲的,很少见用到,功能也比较薄弱,本篇重点讲解动态代理。首先了解一下 JDK 动态代理的原理,这对理解 Spring AOP 部分的源码及实现原理也很有帮助。 JDK 动态代理的实现原理是,动态创建代理类井通过指定类加载器加载,然后在创建代理对象时将 InvokerHandler 对象作为构造参数传入。当调用代理对象的方法时,会调用 InvokerHandler 的 invoke() 方法,并最终调用真正业务对象的相应方法。 JDK 动态代理不仅在 Spring 及 MyBatis 的多个模块中都有所涉及, 在其它很多开源框架中也能看到其身影。 + ```java /** * 一般会使用实现了 InvocationHandler 的类作为代理对象的生产工厂, @@ -440,20 +451,20 @@ JDK 动态代理的实现原理是,动态创建代理类井通过指定类加 * 这些我们都能通过下面这段代码看懂,但代理对象是如何生成的?invoke()方法又是如何被调用的呢? */ public class ProxyFactory implements InvocationHandler{ - + private Object target = null; - + public Object getInstanse(Object target){ - + this.target = target; - return Proxy.newProxyInstance(target.getClass().getClassLoader(), + return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } - + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - + Object ret = null; System.out.println("前置增强"); ret = method.invoke(target, args); @@ -471,7 +482,7 @@ public class TargetObject implements MyInterface { @Override public void play() { System.out.println("妲己,陪你玩 ~"); - + } } @@ -500,7 +511,7 @@ public class ProxyTest { // 总的来说,就是在invoke()方法中完成target目标方法的调用,及前置后置增强, // JDK动态生成的代理类中对 invoke() 方法进行了回调 } - + /** * 将ProxyGenerator生成的动态代理类的输出到文件中,利用反编译工具luyten等就可 * 以看到生成的代理类的源码咯,下面给出了其反编译好的代码实现 @@ -537,7 +548,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { private static Method m0; private static Method m3; private static Method m2; - + static { try { $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); @@ -553,11 +564,11 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new NoClassDefFoundError(ex2.getMessage()); } } - + public $Proxy0(final InvocationHandler invocationHandler) { super(invocationHandler); } - + public final void play() { try { // 这个 h 其实就是我们调用 Proxy.newProxyInstance() 方法时传进去的ProxyFactory(InvocationHandler对象), @@ -572,7 +583,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new UndeclaredThrowableException(t); } } - + public final boolean equals(final Object o) { try { return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o }); @@ -584,7 +595,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new UndeclaredThrowableException(t); } } - + public final int hashCode() { try { return (int)super.h.invoke(this, $Proxy0.m0, null); @@ -596,7 +607,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new UndeclaredThrowableException(t); } } - + public final String toString() { try { return (String)super.h.invoke(this, $Proxy0.m2, null); @@ -612,7 +623,9 @@ public final class $Proxy0 extends Proxy implements MyInterface { ``` #### Spring 中的应用 -Spring 在生成动态代理类时,会优先选择使用JDK动态代理,除非被代理类没有实现接口。 + +Spring 在生成动态代理类时,会优先选择使用 JDK 动态代理,除非被代理类没有实现接口。 + ```java /** * 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy @@ -623,7 +636,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); } - + /** * 获取 JVM 动态生成的代理对象 */ @@ -631,15 +644,15 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } - + // 获取代理类要实现的接口 Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); - + // 通过 Proxy 生成代理对象 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } - + /** * 本类所生成的代理对象中,所有方法的调用 都会回调本方法。 * 根据用户的配置,对指定的切面进行相应的增强 @@ -648,12 +661,12 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; - + // 通过 targetSource 可以获取被代理对象 TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; - + try { // 如果目标对象调用的是 Obejct 类中的基本方法,如:equals、hashCode 则进行相应的处理 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { @@ -669,24 +682,24 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa // 使用代理配置对 ProxyConfig 进行服务调用 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } - + Object retVal; - + if (this.advised.exposeProxy) { // 如果有必要,可以援引 oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } - + // 获取目标对象,为目标方法的调用做准备 target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } - + // 获取定义好的拦截器链 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); - + // 如果没有配置拦截器,就直接调用目标对象target的method方法,并获取返回值 if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); @@ -698,7 +711,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa // 沿着拦截器链继续向下处理 retVal = invocation.proceed(); } - + // 获取 method 返回值的类型 Class returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType.isInstance(proxy) && @@ -725,8 +738,10 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa } ``` -#### Mybatis中的应用 +#### Mybatis 中的应用 + Mybatis 的 PooledConnection 类中封装了数据库连接的代理对象,对数据库连接的操作大都会通过该代理对象完成。 + ```java /** * Mybatis 封装的数据库连接类,它实现了 InvocationHandler 接口,封装了真正的 @@ -805,24 +820,28 @@ class PooledConnection implements InvocationHandler { ``` ## 装饰器模式 + #### 个人理解 -在实际生产中,新需求在软件的整个生命过程中总是不断出现的。当有新需求出现时,就需要为某些组件添加新的功能来满足这些需求。 添加新功能的方式有很多,我们可以直接修改已有组件的代码井添加相应的新功能,但这样会破坏己有组件的稳定性,修改完成后,整个组件需要重新进行测试才能上线使用。 这种方式显然违反了 “开放封闭” 原则。 -另一种方式是使用继承,我们可以创建子类并在子类中添加新功能实现扩展。 这种方法是静态的,用户不能控制增加行为的方式和时机。 而且有些情况下继承是不可行的,例如 己有组件是被 final 修饰的类。 另外,如果待添加的新功能存在多种组合,使用继承方式可能会导致大量子类的出现。 例如,有 4 个待添加的新功能,系统需要动态使用任意多个功能的组合, 则需要添加 15 个子类才能满足全部需求。 +在实际生产中,新需求在软件的整个生命过程中总是不断出现的。当有新需求出现时,就需要为某些组件添加新的功能来满足这些需求。 添加新功能的方式有很多,我们可以直接修改已有组件的代码井添加相应的新功能,但这样会破坏己有组件的稳定性,修改完成后,整个组件需要重新进行测试才能上线使用。 这种方式显然违反了 “开放封闭” 原则。 + +另一种方式是使用继承,我们可以创建子类并在子类中添加新功能实现扩展。 这种方法是静态的,用户不能控制增加行为的方式和时机。 而且有些情况下继承是不可行的,例如 己有组件是被 final 修饰的类。 另外,如果待添加的新功能存在多种组合,使用继承方式可能会导致大量子类的出现。 例如,有 4 个待添加的新功能,系统需要动态使用任意多个功能的组合, 则需要添加 15 个子类才能满足全部需求。 装饰器模式能够帮助我们解决上述问题,装饰器可以动态地为对象添加功能,它是基于组合的方式实现该功能的。在实践中,我们应该尽量使用组合的方式来扩展系统的功能,而非使用继承的方式。通过装饰器模式的介绍,可以帮助读者更好地理解设计模式中常见的一句话:组合优于继承。下面先来看一下装饰器模式的类图,及其核心角色。 -![avatar](../../../images/DesignPattern/装饰器模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/装饰器模式类图.png) - Component (组件):组件接口定义了全部 “组件实现类” 以及所有 “装饰器实现” 的行为。 -- ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。 +- ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。 - Decorator (装饰器):所有装饰器的父类,它是一个实现了 Component 接口的抽象类,并持有一个 Component 被装饰对象,这就实现了装饰器的嵌套组合和复用。 -- ConcreteDecorator (具体的装饰器实现类):该实现类要向被装饰对象添加某些功能,被装饰的对象只要是 Component 类型即可。 +- ConcreteDecorator (具体的装饰器实现类):该实现类要向被装饰对象添加某些功能,被装饰的对象只要是 Component 类型即可。 + +#### Mybatis 中的应用 -#### Mybatis中的应用 -在 MyBatis 的缓存模块中,使用了装饰器模式的变体,其中将 Decorator 接口和 Component 接口合并为一个 Component 接口,即,去掉了 Decorator 这个中间层,ConcreteDecorator 直接实现了Component 接口。 +在 MyBatis 的缓存模块中,使用了装饰器模式的变体,其中将 Decorator 接口和 Component 接口合并为一个 Component 接口,即,去掉了 Decorator 这个中间层,ConcreteDecorator 直接实现了 Component 接口。 + +MyBatis 中缓存模块相关的代码位于 cache 包下, 其中 Cache 接口是缓存模块的核心接口,它定义了所有缓存的基本行为,扮演了 Component 的角色。实现类 PerpetualCache 扮演了 ConcreteComponent 的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象的方法实现了 Cache 接口中定义的相应方法。而 cache 包下的 decorators 包中,则定义了一系列 ConcreteDecorator 的实现,如 BlockingCache、FifoCache 及 LruCache 等等,它们都持有一个 Cache 类型的对象,通过嵌套组合的方式为该 Cache 对象 装饰相应的功能。其源码实现如下。 -MyBatis 中缓存模块相关的代码位于 cache 包下, 其中 Cache 接口是缓存模块的核心接口,它定义了所有缓存的基本行为,扮演了 Component 的角色。实现类 PerpetualCache 扮演了 ConcreteComponent 的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象的方法实现了 Cache 接口中定义的相应方法。而 cache 包下的 decorators 包中,则定义了一系列 ConcreteDecorator 的实现,如 BlockingCache、FifoCache 及 LruCache 等等,它们都持有一个 Cache 类型的对象,通过嵌套组合的方式为该 Cache对象 装饰相应的功能。其源码实现如下。 ```java public interface Cache { @@ -1110,4 +1129,4 @@ public class FifoCache implements Cache { keyList.clear(); } } -``` \ No newline at end of file +``` diff --git "a/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\350\241\214\344\270\272\345\236\213).md" "b/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\350\241\214\344\270\272\345\236\213).md" index 4c3f6417..cc1f96ba 100644 --- "a/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\350\241\214\344\270\272\345\236\213).md" +++ "b/docs/LearningExperience/DesignPattern/\344\273\216Spring\345\217\212Mybatis\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217(\350\241\214\344\270\272\345\236\213).md" @@ -1,23 +1,26 @@ -设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring系列、Mybatis)及JDK源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 +设计模式是解决问题的方案,从大神的代码中学习对设计模式的使用,可以有效提升个人编码及设计代码的能力。本系列博文用于总结阅读过的框架源码(Spring 系列、Mybatis)及 JDK 源码中 所使用过的设计模式,并结合个人工作经验,重新理解设计模式。 本篇博文主要看一下行为型的几个设计模式,即,策略模式、模板方法模式、迭代器模式、观察者模式 及 责任链模式。 - ## 策略模式 + #### 个人理解 + 去年看了蛮多源码,发现 框架的开发者在实际使用设计模式时,大都会根据实际情况 使用其变体,老老实实按照书上的类图及定义去设计代码的比较少。不过我们依然还是先看一下书上的定义,然后比较一下理论与实践的一些差别吧。策略模式的类图及定义如下。 -![avatar](../../../images/DesignPattern/策略模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/策略模式类图.png) 定义一系列算法,封装每个算法 并使它们可以互换。该模式的主要角色如下: -- Strategy接口:用于定义一个算法族,它们都具有behavior()方法; -- Context:使用该算法的类,持有Strategy对象,其中的setStrategy(Strategy stra)方法可以动态地改变strategy对象,以此改变自己所使用的算法。 +- Strategy 接口:用于定义一个算法族,它们都具有 behavior()方法; +- Context:使用该算法的类,持有 Strategy 对象,其中的 setStrategy(Strategy stra)方法可以动态地改变 strategy 对象,以此改变自己所使用的算法。 + +很多书上都使用 Duck 和 QuackBehavior 作为示例进行说明,这里就不重复咯,主要看一下 Spring 中是如何使用该模式的。 -很多书上都使用Duck和QuackBehavior作为示例进行说明,这里就不重复咯,主要看一下Spring中是如何使用该模式的。 +#### Spring 中的实现 + +Spring 的 AbstractAutowireCapableBeanFactory 在进行 bean 实例化时使用了策略模式的变种,其中 InstantiationStrategy 接口 定义了实例化方法,实现类 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy 分别实现了各自的算法,AbstractAutowireCapableBeanFactory 则通过持有 InstantiationStrategy 对象,对算进行使用。其源码实现如下。 -#### Spring中的实现 -Spring的 AbstractAutowireCapableBeanFactory 在进行bean实例化时使用了策略模式的变种,其中InstantiationStrategy 接口 定义了实例化方法,实现类SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy 分别实现了各自的算法,AbstractAutowireCapableBeanFactory 则通过持有 InstantiationStrategy 对象,对算进行使用。其源码实现如下。 ```java /** * 本接口用于定义bean实例的创建,通过给定的RootBeanDefinition对象 @@ -32,10 +35,10 @@ public interface InstantiationStrategy { */ Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) throws BeansException; - + Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Constructor ctor, Object[] args) throws BeansException; - + Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Object factoryBean, Method factoryMethod, Object[] args) throws BeansException; @@ -54,7 +57,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { synchronized (beanDefinition.constructorArgumentLock) { // 获取对象的构造方法或生成对象的工厂方法对bean进行实例化 constructorToUse = (Constructor) beanDefinition.resolvedConstructorOrFactoryMethod; - + // 如果前面没有获取到构造方法,则通过反射获取 if (constructorToUse == null) { // 使用JDK的反射机制,判断要实例化的Bean是否是接口 @@ -106,33 +109,33 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt */ protected Object instantiateWithMethodInjection( RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { - + // 必须生成cglib子类 return new CglibSubclassCreator(beanDefinition, owner).instantiate(null, null); } - + @Override protected Object instantiateWithMethodInjection( RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Constructor ctor, Object[] args) { - + return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args); } - + /** * 为避免3.2之前的Spring版本中的外部cglib依赖而创建的内部类 */ private static class CglibSubclassCreator { - + private final RootBeanDefinition beanDefinition; - + private final BeanFactory owner; - + public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) { this.beanDefinition = beanDefinition; this.owner = owner; } - + //使用CGLIB进行Bean对象实例化 public Object instantiate(Constructor ctor, Object[] args) { //实例化Enhancer对象,并为Enhancer对象设置父类,生成Java对象的参数,比如:基类、回调方法等 @@ -145,7 +148,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt new LookupOverrideMethodInterceptor(), new ReplaceOverrideMethodInterceptor() }); - + //使用CGLIB的create方法生成实例对象 return (ctor == null) ? enhancer.create() : enhancer.create(ctor.getParameterTypes(), args); } @@ -158,18 +161,18 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac /** 创建bean实例的策略,注意 这里直接实例化的是 CglibSubclassingInstantiationStrategy 对象 */ private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy(); - + /** * 设置用于创建bean实例的实例化策略,默认使用CglibSubclassingInstantiationStrategy */ public void setInstantiationStrategy(InstantiationStrategy instantiationStrategy) { this.instantiationStrategy = instantiationStrategy; } - + protected InstantiationStrategy getInstantiationStrategy() { return this.instantiationStrategy; } - + /** * 使用默认的无参构造方法实例化Bean对象 */ @@ -187,7 +190,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac }, getAccessControlContext()); } else { - + /** * !!!!!!!!!!!!!! * 使用初始化策略实例化Bean对象 @@ -203,19 +206,22 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); } } - + ... - + } ``` -与标准的策略模式的设计区别在于,实现类CglibSubclassingInstantiationStrategy并不是直接实现了InstantiationStrategy接口,而是继承了SimpleInstantiationStrategy,SimpleInstantiationStrategy直接实现了 通过JDK反射机制实例化bean的策略,而CglibSubclassingInstantiationStrategy则是在自己的私有静态内部类中 完成的 通过CGLIB实例化bean的策略。 -另外,虽然AbstractAutowireCapableBeanFactory默认持有的是CglibSubclassingInstantiationStrategy的实例,但具体使用哪个实现类中的策略,则是由CglibSubclassingInstantiationStrategy的父类SimpleInstantiationStrategy中的instantiate()方法决定的。也就是说,虽然持有的是CglibSubclassingInstantiationStrategy对象,但实际上可能使用的是 JDK反射机制实例化bean的策略。 +与标准的策略模式的设计区别在于,实现类 CglibSubclassingInstantiationStrategy 并不是直接实现了 InstantiationStrategy 接口,而是继承了 SimpleInstantiationStrategy,SimpleInstantiationStrategy 直接实现了 通过 JDK 反射机制实例化 bean 的策略,而 CglibSubclassingInstantiationStrategy 则是在自己的私有静态内部类中 完成的 通过 CGLIB 实例化 bean 的策略。 + +另外,虽然 AbstractAutowireCapableBeanFactory 默认持有的是 CglibSubclassingInstantiationStrategy 的实例,但具体使用哪个实现类中的策略,则是由 CglibSubclassingInstantiationStrategy 的父类 SimpleInstantiationStrategy 中的 instantiate()方法决定的。也就是说,虽然持有的是 CglibSubclassingInstantiationStrategy 对象,但实际上可能使用的是 JDK 反射机制实例化 bean 的策略。 设计模式的生产实践可能比 理论上的那些示例复杂的多,所以,若想确实提高自己代码的设计能力,还是要摆脱书本,多看实际应用。 #### Mybatis 中的实现 -mybatis 的 DefaultSqlSession 使用了策略模式,DefaultSqlSession 扮演了 Context 的角色,Executor 接口及其实现类扮演了策略接口及实现。DefaultSqlSession 持有 Executor 对象,在DefaultSqlSession 实例化时通过构造方法传入具体的 Executor 对象,根据持有的 Executor 对象的不同,而使用不同的策略进行数据库操作。具体使用哪个 Executor 的实例,由 Configuration 的 newExecutor() 方法决定。 + +mybatis 的 DefaultSqlSession 使用了策略模式,DefaultSqlSession 扮演了 Context 的角色,Executor 接口及其实现类扮演了策略接口及实现。DefaultSqlSession 持有 Executor 对象,在 DefaultSqlSession 实例化时通过构造方法传入具体的 Executor 对象,根据持有的 Executor 对象的不同,而使用不同的策略进行数据库操作。具体使用哪个 Executor 的实例,由 Configuration 的 newExecutor() 方法决定。 + ```java public class DefaultSqlSession implements SqlSession { @@ -272,7 +278,7 @@ public interface Executor { void rollback(boolean required) throws SQLException; Transaction getTransaction(); - + ...... } @@ -280,7 +286,7 @@ public interface Executor { public abstract class BaseExecutor implements Executor { ...... - + protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<>(); @@ -290,7 +296,7 @@ public abstract class BaseExecutor implements Executor { this.configuration = configuration; this.wrapper = this; } - + ...... } @@ -334,7 +340,7 @@ public class SimpleExecutor extends BaseExecutor { closeStatement(stmt); } } - + ...... } @@ -380,7 +386,7 @@ public class BatchExecutor extends BaseExecutor { handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; } - + ...... } @@ -407,19 +413,23 @@ public class Configuration { executor = (Executor) interceptorChain.pluginAll(executor); return executor; } - + ...... } ``` ## 模板方法模式 + #### 个人理解 -在该模式中,一个算法可以分为多个步骤,这些步骤的执行次序在一个被称为“模板方法”的方法中定义,而算法的每个步骤都对应着一个方法,这些方法被称为 “基本方法”。 模板方法按照它定义的顺序依次调用多个基本方法,从而实现整个算法流程。在模板方法模式中,会将模板方法的实现以及那些固定不变的基本方法的实现放在父类中,而那些不固定的基 本方法在父类中只是抽象方法,其真正的实现代码会被延迟到子类中完成。 -我觉得这是最简单且常用的设计模式之一咯,自己在实现一些功能时也会使用这种模式,在抽象类中定义好流程的执行顺序,通用的流程在抽象类中实现,个性化的流程交给各个子类去实现。spring及mybatis中均有应用。 +在该模式中,一个算法可以分为多个步骤,这些步骤的执行次序在一个被称为“模板方法”的方法中定义,而算法的每个步骤都对应着一个方法,这些方法被称为 “基本方法”。 模板方法按照它定义的顺序依次调用多个基本方法,从而实现整个算法流程。在模板方法模式中,会将模板方法的实现以及那些固定不变的基本方法的实现放在父类中,而那些不固定的基 本方法在父类中只是抽象方法,其真正的实现代码会被延迟到子类中完成。 + +我觉得这是最简单且常用的设计模式之一咯,自己在实现一些功能时也会使用这种模式,在抽象类中定义好流程的执行顺序,通用的流程在抽象类中实现,个性化的流程交给各个子类去实现。spring 及 mybatis 中均有应用。 + +#### Spring 中的应用 -#### Spring中的应用 Spring 中的 AbstractApplicationContext 和其子类 AbstractRefreshableApplicationContext、GenericApplicationContext 使用了模板方法模式。源码实现及详细注释如下。 + ```java public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean { @@ -438,11 +448,11 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader } return beanFactory; } - + protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException; - + public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; - + } @@ -476,7 +486,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } } - + @Override public final ConfigurableListableBeanFactory getBeanFactory() { synchronized (this.beanFactoryMonitor) { @@ -501,15 +511,17 @@ public class GenericApplicationContext extends AbstractApplicationContext implem this.beanFactory.setSerializationId(getId()); this.refreshed = true; } - + public final ConfigurableListableBeanFactory getBeanFactory() { return this.beanFactory; } } ``` -#### Mybatis中的应用 -mybatis的 Executor 组件使用了该模式,其中抽象类 BaseExecutor 定义了模板方法和抽象方法,实现类 SimpleExecutor、BatchExecutor 及 ReuseExecutor 对抽象方法进行具体实现。源码如下。 +#### Mybatis 中的应用 + +mybatis 的 Executor 组件使用了该模式,其中抽象类 BaseExecutor 定义了模板方法和抽象方法,实现类 SimpleExecutor、BatchExecutor 及 ReuseExecutor 对抽象方法进行具体实现。源码如下。 + ```java public abstract class BaseExecutor implements Executor { @@ -818,16 +830,20 @@ public class ReuseExecutor extends BaseExecutor { } } ``` -可以看得出来,模板方法就是BaseExecutor的update()、flushStatements()、queryFromDatabase() 及 queryCursor(),分别使用了抽象方法doUpdate()、doFlushStatements()、doQuery() 及 doQueryCursor()。 + +可以看得出来,模板方法就是 BaseExecutor 的 update()、flushStatements()、queryFromDatabase() 及 queryCursor(),分别使用了抽象方法 doUpdate()、doFlushStatements()、doQuery() 及 doQueryCursor()。 ## 迭代器模式 + #### 个人理解 -这个模式最经典的实现莫过于 Java的集合类咯。同样还是先简单介绍一下这个设计模式,然后结合ArrayList的源码进行分析。 + +这个模式最经典的实现莫过于 Java 的集合类咯。同样还是先简单介绍一下这个设计模式,然后结合 ArrayList 的源码进行分析。 本设计模式用于提供一种遍历集合元素的方法,且不暴露集合对象的内部表示。其主要角色 和 简单实现如下: - Aggregate:聚合类,有一个可以获取 Iterator 对象的 iterator() 方法; -- Iterator:主要定义了hasNest() 和 next()方法; +- Iterator:主要定义了 hasNest() 和 next()方法; + ```java public interface Aggregate { Iterator iterator(); @@ -837,14 +853,14 @@ public interface Aggregate { public class ConcreteAggregate implements Aggregate { private Integer[] elements; - + public ConcreteAggregate() { elements = new Integer[10]; for (int i = 0; i < elements.length; i++) { elements[i] = i; } } - + @Override public Iterator iterator() { return new ConcreteIterator(elements); @@ -855,7 +871,7 @@ public class ConcreteAggregate implements Aggregate { public interface Iterator { boolean hasNext(); - + Integer next(); } @@ -864,16 +880,16 @@ public class ConcreteIterator implements Iterator { private Integer[] elements; private int position = 0; - + public ConcreteIterator(Integer[] elements) { this.elements = elements; } - + @Override public boolean hasNext() { return position < elements.length; } - + @Override public Integer next() { return elements[position ++]; @@ -894,6 +910,7 @@ public class Client { ``` #### ArrayList 对迭代器模式的实现 + ```java public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { @@ -945,16 +962,20 @@ public class ArrayList extends AbstractList ``` ## 观察者模式 + #### 个人理解 -这个模式也是平时很少使用的,所以就简单介绍一下,然后结合JDK中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下: -![avatar](../../../images/DesignPattern/观察者模式类图.png) +这个模式也是平时很少使用的,所以就简单介绍一下,然后结合 JDK 中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下: + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/观察者模式类图.png) -- Subject主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的; -- Observer观察者:其注册需要Subject的registerObserver()方法。 +- Subject 主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的; +- Observer 观察者:其注册需要 Subject 的 registerObserver()方法。 + +#### JDK 中的源码实现 + +java.util 包中提供了 Observable 类和 Observer 接口,其中要求,被观察者需要继承 Observable 类,观察则需要实现 Observer 接口。下面看一下其源码实现。 -#### JDK中的源码实现 -java.util包中提供了 Observable 类和 Observer 接口,其中要求,被观察者需要继承Observable类,观察则需要实现Observer接口。下面看一下其源码实现。 ```java /** * 当一个类希望获知 所观察的对象的变化时,可以通过实现本接口来完成 @@ -987,7 +1008,7 @@ public interface Observer { */ public class Observable { private boolean changed = false; - /** + /** * 通过一个Vector来维护 观察者列表。 * 由于该集合主要涉及元素的增删操作,所以个人认为使用LinkedList * 效果会更好一下 @@ -1076,24 +1097,27 @@ public class Observable { ``` ## 责任链模式 -一般用在消息请求的处理上,如 Netty 的 ChannelHandler组件,Tomcat 对 HTTP 请求的处理。我们当然可以将 请求的处理逻辑都写在一个类中,但这个类会非常雕肿且不易于维护,不符合开发封闭原则。 -在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 Handler 处理类中,这样我们就可以根据业务需求,将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler对象 都包含对下一个 Handler对象 的引用,一个 Handler对象 处理完请求消息(或不能处理该请求)时, 会把请求传给下一个 Handler对象 继续处理,依此类推,直至整条责任链结束。简单看一下责任链模式的类图。 +一般用在消息请求的处理上,如 Netty 的 ChannelHandler 组件,Tomcat 对 HTTP 请求的处理。我们当然可以将 请求的处理逻辑都写在一个类中,但这个类会非常雕肿且不易于维护,不符合开发封闭原则。 + +在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 Handler 处理类中,这样我们就可以根据业务需求,将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler 对象 都包含对下一个 Handler 对象 的引用,一个 Handler 对象 处理完请求消息(或不能处理该请求)时, 会把请求传给下一个 Handler 对象 继续处理,依此类推,直至整条责任链结束。简单看一下责任链模式的类图。 -![avatar](../../../images/DesignPattern/责任链模式.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/责任链模式.png) #### Netty 中的应用 -在 Netty 中,将 Channel 的数据管道抽象为 ChannelPipeline,消息在 ChannelPipeline 中流动和传递。ChannelPipeline 是 ChannelHandler 的容器,持有 I/O事件拦截器 ChannelHandler 的链表,负责对 ChannelHandler 的管理和调度。由 ChannelHandler 对 I/O事件 进行拦截和处理,并可以通过接口方便地新增和删除 ChannelHandler 来实现不同业务逻辑的处理。下图是 ChannelPipeline源码中描绘的责任链事件处理过程。 -![avatar](../../../images/Netty/ChannelPipeline责任链事件处理过程.png) + +在 Netty 中,将 Channel 的数据管道抽象为 ChannelPipeline,消息在 ChannelPipeline 中流动和传递。ChannelPipeline 是 ChannelHandler 的容器,持有 I/O 事件拦截器 ChannelHandler 的链表,负责对 ChannelHandler 的管理和调度。由 ChannelHandler 对 I/O 事件 进行拦截和处理,并可以通过接口方便地新增和删除 ChannelHandler 来实现不同业务逻辑的处理。下图是 ChannelPipeline 源码中描绘的责任链事件处理过程。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ChannelPipeline责任链事件处理过程.png) 其具体过程处理如下: -1. 底层SocketChannel 的 read方法 读取 ByteBuf,触发 ChannelRead事件,由 I/O线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead()方法,将消息传输到 ChannelPipeline中。 +1. 底层 SocketChannel 的 read 方法 读取 ByteBuf,触发 ChannelRead 事件,由 I/O 线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead()方法,将消息传输到 ChannelPipeline 中。 2. 消息依次被 InboundHandler 1、InboundHandler 2 … InboundHandler N 拦截处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。 3. 当调用 ChannelHandlerContext 的 write()方法 发送消息,消息从 OutbountHandler 1 开始 一直到 OutboundHandler N,最终被添加到消息发送缓冲区中等待刷新和发送。 -在 Netty 中将事件根据源头的不同分为 InBound事件 和 OutBound事件。InBound事件 通常由 I/O线程 触发,例如 TCP链路 建立和关闭、读事件等等,分别会触发相应的事件方法。而 OutBound事件 则一般由用户主动发起的 网络I/O操作,例如用户发起的连接操作,绑定操作和消息发送操作等,也会分别触发相应的事件方法。由于 netty 中提供了一个抽象类 ChannelHandlerAdapter,它默认不处理拦截的事件。所以,在实际编程过程中,我们只需要继承 ChannelHandlerAdapter,在我们的 自定义Handler 中覆盖业务关心的事件方法即可。其源码如下。 +在 Netty 中将事件根据源头的不同分为 InBound 事件 和 OutBound 事件。InBound 事件 通常由 I/O 线程 触发,例如 TCP 链路 建立和关闭、读事件等等,分别会触发相应的事件方法。而 OutBound 事件 则一般由用户主动发起的 网络 I/O 操作,例如用户发起的连接操作,绑定操作和消息发送操作等,也会分别触发相应的事件方法。由于 netty 中提供了一个抽象类 ChannelHandlerAdapter,它默认不处理拦截的事件。所以,在实际编程过程中,我们只需要继承 ChannelHandlerAdapter,在我们的 自定义 Handler 中覆盖业务关心的事件方法即可。其源码如下。 + ```java /** * 它扮演了 责任链模式中的 Client角色,持有 构造 并使用 ChannelHandler责任链 @@ -1208,6 +1232,6 @@ abstract class AbstractChannelHandlerContext extends DefaultAttributeMap volatile AbstractChannelHandlerContext prev; ...... - + } -``` \ No newline at end of file +``` diff --git "a/docs/LearningExperience/DesignPattern/\344\273\216\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\346\204\237\346\202\237.md" "b/docs/LearningExperience/DesignPattern/\344\273\216\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\346\204\237\346\202\237.md" index 4d6dbf0a..b3787c53 100644 --- "a/docs/LearningExperience/DesignPattern/\344\273\216\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\346\204\237\346\202\237.md" +++ "b/docs/LearningExperience/DesignPattern/\344\273\216\346\241\206\346\236\266\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\346\204\237\346\202\237.md" @@ -1,6 +1 @@ 努力编写中... - - - - - diff --git "a/docs/LearningExperience/EncodingSpecification/\344\270\200\344\270\252\347\250\213\345\272\217\345\221\230\347\232\204\350\207\252\346\210\221\344\277\256\345\205\273.md" "b/docs/LearningExperience/EncodingSpecification/\344\270\200\344\270\252\347\250\213\345\272\217\345\221\230\347\232\204\350\207\252\346\210\221\344\277\256\345\205\273.md" deleted file mode 100644 index 1fa1cb16..00000000 --- "a/docs/LearningExperience/EncodingSpecification/\344\270\200\344\270\252\347\250\213\345\272\217\345\221\230\347\232\204\350\207\252\346\210\221\344\277\256\345\205\273.md" +++ /dev/null @@ -1,847 +0,0 @@ -本文用于总结《阿里Java开发手册》、《用友技术review手册》及个人Java开发工作经验,并结合这半年来的源码阅读经验进行编写。回顾那些写过的和读过的代码,回顾自己。 - -## 第一章 基础编码规范 -### 1.1 命名规范 -- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。 -tips:JDK动态代理生成的代理类 类名使用了\$符号开头,如\$Proxy1。 - -- 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 -tips:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用。alibaba,yonyou,Beijing等国际通用的名称,可视同英文。 -在我们的财务相关模块的工程代码及数据库表设计中,可以看到一些拼音首字母缩写的命名方式,如:arap_djzb,arap是“应收应付”的英文缩写,djzb是“单据主表”的汉语拼音首字母,zdr、shr、lrr都是汉语拼音首字母缩写。当然,这些都是历史包袱,经历了这么多年的代码积累,很难从底层去修正咯,但在自己的实际编码中要以史为鉴,让自己的代码更加优雅规范,这也是同事是否尊重你的重要考量之一。 - -- 类名使用UpperCamelCase——大驼峰风格,但以下情形例外: DO / BO / DTO/ VO / AO / -PO / UID等。 -tips:合理的类名后缀能够让我们在开发中快速地找到自己想要的代码,想看某个业务层就ctrl + shift + T搜索“XXXBO”,想看某展示层代码 就搜索“XXXVO”。 - -- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。 -例如:Spring框架的AbstractApplicationContext和Mybatis框架的BaseExecutor都是抽象类。 - -- 方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase——小驼峰风格。 - -- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。 -tips:实际编码中,有时确实会嫌某常量名太长,不便于使用。以后应该在语义完整清楚的情况下再考虑尽量缩短名称长度。 - -- 类型与中括号紧挨相连来表示数组。 -正例:定义整形数组 int[] arrayDemo; -反例:在 main 参数中,使用 String args[]来定义。 - -- POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。 -反例:定义为基本数据类型 Boolean isDeleted 的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,“误以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。 -tips:我们的VO类中有很多is开头的Boolean类型变量,如:DJZBHeaderVO中的isjszxzf(是否结算中心支付)字段。 - -- 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用 单数形式,但是类名如果有复数含义,类名可以使用复数形式。 -正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils(此规则参考 spring 的框架结构) - -- 杜绝完全不规范的缩写,避免望文不知义。 -反例:AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随 意缩写严重降低了代码的可阅读性。 - -- 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词 组合来表达其意。 -正例:在 JDK 中,表达原子更新的类名为:AtomicReferenceFieldUpdater。 -反例:变量 int a 的随意命名方式。 - -- 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。 -tips:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。 如:Spring框架的BeanFactory(工厂模式)、JdkDynamicAopProxy(JDK动态代理模式)。 - -- 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。 -正例:接口方法签名 void commit(); -接口基础常量 String COMPANY = "alibaba"; -反例:接口方法定义 public abstract void f(); -说明:JDK8 中接口允许有默认实现,那么这个 default 方法,是对所有实现类都有价值的默认实现。 - -- 接口和实现类的命名有两套规则: -【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部 的实现类用 Impl 的后缀与接口区别。 -正例:CacheServiceImpl 实现 CacheService 接口。 -【推荐】 如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形式)。 -正例:AbstractTranslator 实现 Translatable 接口。 - -- 枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。 -说明:枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。 -正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。 - -- 各层命名规约: -A) Service/DAO 层方法命名规约 -1) 获取单个对象的方法用 get 做前缀。 -2) 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。 -3) 获取统计值的方法用 count 做前缀。 -4) 插入的方法用 save/insert 做前缀。 -5) 删除的方法用 remove/delete 做前缀。 -6) 修改的方法用 update 做前缀。 -B) 领域模型命名规约 -1) 数据对象:xxxDO,xxx 即为数据表名。 -2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。 -3) 展示对象:xxxVO,xxx 一般为网页名称。 -4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。 - -### 1.2 常量定义 -- 不允许任何魔法值(意义不明的变量 / 常量)直接出现在代码中。 -反例: -String key = "Id#taobao_" + tradeId; -cache.put(key, value); - -- 在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。 -说明:Long a = 2l; 写的是数字的 21,还是 Long 型的 2? - -- 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。 -说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。 -正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。 - -- 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。 -1)跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。 -2)应用内共享常量:放置在一方库中,通常是子模块中的 constant 目录下。 -反例:易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示 “是” 的变量。 -类 A 中:public static final String YES = "yes"; -类 B 中:public static final String YES = "y"; -A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致线上问题。 -3)子工程内部共享常量:即在当前子工程的 constant 目录下。 -4)包内共享常量:即在当前包下单独的 constant 目录下。 -5)类内共享常量:直接在类内部 private static final 定义。 - -- 如果变量值仅在一个固定范围内变化用 enum 类型来定义。 -说明:如果存在名称之外的延伸属性应使用 enum 类型,下面正例中的数字就是延伸信息,表示一年中的第几个季节。 -正例: -```java -public enum SeasonEnum { - SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); - private int seq; - - SeasonEnum(int seq){ - this.seq = seq; - } -} -``` - -### 1.3 代码格式 -代码格式无非就是一些空格、换行、缩进的问题,没必要死记,直接用开发工具(eclipse、IDEA)format一下即可,省时省力。 - -### 1.4 OOP 规约 -- 避免通过一个类的对象引用访问此类的静态变量或方法,增加编译器解析成本。 - -- 所有的覆写方法,必须加@Override 注解。 -说明:getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0,加@Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名(由方法名、参数的类型及**顺序** 确定唯一的方法签名)进行修改,其实现类会马上编译报错。 - -- 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。 -说明:可变参数必须放置在参数列表的最后。(能用数组的就不要使用可变参数编程,可变参数在编译时会被编译成数组类型。可变参数能兼容数组类参数,但是数组类参数却无法兼容可变参数。可变参数类型必须作为参数列表的最后一项,且不能放在定长参数的前面。) -正例:public List listUsers(String type, Long... ids) {...} - -- 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。 -tips: -一方库:本工程范围内,各个模块和包之间的相互依赖。 -二方库:引入的同一个公司内部的其他工程。 -三方库:公司以外的其他依赖,比如apache,google等。 - -- 不能使用过时的类或方法。 -说明:java.net.URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数 decode(String source, String encode)。接口提供方既然明确是过时接口, 那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。 - -- Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。 -正例:"test".equals(object); -反例:object.equals("test"); -说明:推荐使用 java.util.Objects#equals(JDK7 引入的工具类)。**个人认为,当要比较两个不确定的对象时,可以考虑使用这个类,如果只是想确定某个对象是否为目标值,使用上面的方法并不差**。 - -- 所有的相同类型的包装类对象之间值的比较,全部使用 equals() 方法比较。 -说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑, 推荐使用 equals() 方法进行判断。 - -- 关于基本数据类型与包装数据类型的使用标准如下: -1)【强制】所有的 POJO 类属性必须使用包装数据类型。 -2)【强制】RPC 方法的返回值和参数必须使用包装数据类型。 -3)【推荐】所有的局部变量使用基本数据类型。 -说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。 -正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。(不是很理解这个结论,ResultSet.getInt()等方法获得的是基本数据类型,ORM映射时怎么就拆箱咯?) -反例:比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。 - -- 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。 -反例:POJO 类的 gmtCreate 默认值为 new Date(),但是这个属性在数据提取时并没有置入具 体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间。 - -- 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列化失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。 -说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。 - -- **构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init() 方法中。** -在很多client端的代码中有看到这种编码方式。 - -- POJO 类必须写 toString() 方法。使用 IDE 中的工具:source -> generate toString() 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString()。 -说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。 - -- 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起, 便于阅读。 - -- 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。 -说明:公有方法是类的调用者和维护者最关心的方法,首屏展示最好;保护方法虽然只是子类 关心,也可能是“模板设计模式”下的核心方法;而私有方法外部一般不需要特别关心,是一个 黑盒实现;因为承载的信息价值较低,所有 Service 和 DAO 的 getter/setter 方法放在类体 最后。 - -- setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在 getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度。 - -- 循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。 - -- final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字: -1) 不允许被继承的类,如:String 类。 -2) 不允许修改引用的域对象。 -3) 不允许被重写的方法,如:POJO 类的 setter 方法。 -4) **不允许运行过程中重新赋值的局部变量**,可以看到有些方法的形参中使用了final修饰。 -5) 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好 地进行重构。 - -- 慎用 Object 的 clone 方法来拷贝对象。 -说明:对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现域对象的 深度遍历式拷贝。 - -- **类成员与方法访问控制从严**(合理使用Java的访问修饰符): -1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。 -2) 工具类不允许有 public 或 default 构造方法。 -3) 类非 static 成员变量并且与子类共享,必须是 protected。 -4) 类非 static 成员变量并且仅在本类使用,必须是 private。 -5) 类 static 成员变量如果仅在本类使用,必须是 private。 -6) 若是 static 成员变量,考虑是否为 final。 -7) 类成员方法只供类内部调用,必须是 private。 -8) 类成员方法只对继承类公开,那么限制为 protected。 说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。 -思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作 用域太大,无限制的到处跑,那么你会担心的。 - -### 1.5 集合处理 -- 关于 hashCode 和 equals 的处理,遵循如下规则: -1) 只要重写 equals,就必须重写 hashCode。 -2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的 对象必须重写这两个方法。 -3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。 -说明:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象 作为 key 来使用。 - -- ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。 -说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。 - -- 在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。 - -- 使用集合转数组的方法,必须使用集合的 toArray(T[] array)方法,传入的是类型完全一样的数组,大小就是 list.size()。 -说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配 内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ] 的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集 合元素个数一致。 -```java -// 正例: -List list = new ArrayList(2); -list.add("guan"); -list.add("bao"); -String[] array = new String[list.size()]; -array = list.toArray(array); -// 反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类, -// 若强转其它 类型数组将出现 ClassCastException 错误。 - -// 这是我平时的写法,初始化一个list.size()大小的数组似乎效率更好一些,如果数组的容量 -// 比list小,原来的数组对象不会被使用,浪费系统资源 -String[] strs = list.toArray(new String[0]); -``` - -- 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear 方法会抛出 UnsupportedOperationException 异常。 -说明:asList() 的返回对象是一个 Arrays 的内部类ArrayList(而不是java.util.ArrayList),该内部类 并没有实现集合的修改/删除等方法。Arrays.asList 体现的是适配器模式,只是转换接口,后台的数据仍是数组。 -```java -String[] str = new String[] { "you", "wu" }; -List list = Arrays.asList(str); -第一种情况:list.add("yangguanbao"); 运行时异常。 -第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。 -``` - -- 泛型通配符来接收返回的数据,此写法的泛型集合不能使用 add() 方 法,而不能使用 get() 方法,作为接口调用赋值时易出错。 -```java -// :上界通配符(Upper Bounds Wildcards) -// :下界通配符(Lower Bounds Wildcards) -List list1; // list1 的元素的类型只能是 C 和 C 的子类。 -List list2; // list2 的元素的类型只能是 C 和 C 的父类。 -// 简单来说就是 上界为 C 类型范围粗略理解为 [C,+∞), -// 不允许添加除 null 的元素,获取的元素类型是 C ; -// 下界为 C 类型范围粗略理解为 (-∞,C],允许添加 C 以及 C 的子类类型元素, -// 获取的元素类型是 Object - -// 扩展说一下 PECS(Producer Extends Consumer Super)原则。 -// 第一、频繁往外读取内容的,适合用。 -// 第二、经常往里插入的,适合用。 -``` - -- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。 -```java -// 正例: -List list = new ArrayList<>(); -list.add("1"); -list.add("2"); -Iterator iterator = list.iterator(); -while (iterator.hasNext()) { - String item = iterator.next(); - if (删除元素的条件) { - iterator.remove(); - } -} - -// 反例:对比ArrayList的remove()和Iterator的remove()方法,可以找到其中的坑。 -for (String item : list) { - if ("1".equals(item)) { - list.remove(item); - } -} -``` - -- 在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort(), Collections.sort() 会报 IllegalArgumentException 异常。 -说明:三个条件如下 1) x,y 的比较结果和 y,x 的比较结果相反。 2) x>y,y>z,则 x>z。 3) x=y,则 x,z 比较结果和 y,z 比较结果相同。 -```java -// 反例:下例中没有处理相等的情况,实际使用中可能会出现异常: -new Comparator() { - @Override - public int compare(Student o1, Student o2) { - return o1.getId() > o2.getId() ? 1 : -1; - } -}; -``` - -- 集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。 -说明:菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。 -```java -// 正例: -// <> diamond 方式 -HashMap userCache = new HashMap<>(16); -// 全省略方式 -ArrayList users = new ArrayList(10); -``` - -- 集合初始化时,指定集合初始值大小。 -说明:HashMap 使用 HashMap(int initialCapacity) 初始化。 -正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。 -反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大,resize() 需要重建 hash 表,严重影响性能。 - -- 使用 entrySet 遍历 Map 类集合 K-V,而不是 keySet 方式进行遍历。 -说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach() 方法。 -正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是 一个 Set 集合对象;entrySet()返回的是 K-V 值组合集合。 -```java -// 创建一个Map -Map infoMap = new HashMap<>(); -infoMap.put("name", "Zebe"); -infoMap.put("site", "www.zebe.me"); -infoMap.put("email", "zebe@vip.qq.com"); - -// 传统的Map迭代方式 -for (Map.Entry entry : infoMap.entrySet()) { - System.out.println(entry.getKey() + ":" + entry.getValue()); -} - -// JDK8的迭代方式 -infoMap.forEach((key, value) -> { - System.out.println(key + ":" + value); -}); -``` - -- 高度注意 Map 类集合 K-V 能不能存储 null 值的情况,如下表格: - -集合类 | Key | Value | Super | 说明 --|-|-|-|- -HashMap | **允许为 null** | **允许为 null** | AbstractMap | 线程不安全 -ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap| 锁分段技术(JDK8:CAS) -Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 -TreeMap | 不允许为 null | **允许为 null** | AbstractMap | 线程不安全 - -反例: 由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上, 存储 null 值时会抛出 NPE 异常。 - -- 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。 -说明:有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。 - -- 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。 - -### 1.6 并发处理 -- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。 -说明:资源驱动类、工具类、单例工厂类都需要注意。 - -- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 -```java -// 正例: -public class TimerTaskThread extends Thread { - public TimerTaskThread() { - super.setName("TimerTaskThread"); - ... - } -} -``` - -- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 -说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。 -tips:我们的代码中的很多线程都是自行显式创建的,很少见到通过线程池进行统一管理的。 - -- 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 -说明:Executors 返回的线程池对象的弊端如下: -1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 -2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 - -- SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。 -```java -// 正例:注意线程安全,使用 DateUtils。亦推荐如下处理: -private static final ThreadLocal df = new ThreadLocal() { - @Override - protected DateFormat initialValue() { - return new SimpleDateFormat("yyyy-MM-dd"); - } -}; -// 说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, -// DateTimeFormatter 代替 SimpleDateFormat, -// 官方给出的解释:simple beautiful strong immutable thread-safe。 -``` - -- 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。 -说明:尽可能使加锁的代码块工作量尽可能的小,**避免在锁代码块中调用 RPC 方法**。 - -- **对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序**,否则可能会造成死锁。 -说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。 - -- 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。 -说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。 - -- 多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。 - -- 使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。 -说明:注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。 - -- 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。 -说明:Random 实例包括 java.util.Random 的实例或者 Math.random()的方式。 -正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保证每个线程持有一个实例。 - -- 在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患(指令重排会导致 双检锁失效,产生隐患)(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。 -```java -// 反例: -class LazyInitDemo { - private Helper helper = null; - public Helper getHelper() { - if (helper == null) - synchronized(this) { - if (helper == null) - helper = new Helper(); - } - return helper; - } - // other methods and fields... -} -``` - -- volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。 -```java -// 如果是 count++操作,使用如下类实现: -AtomicInteger count = new AtomicInteger(); -count.addAndGet(1); -// 如果是 JDK8,推 荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。 -``` - -- HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险。 - -- ThreadLocal 无法解决共享对象的更新问题,ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。 - -### 1.7 控制语句 -- 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。 - -- 在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码。 - -- 在高并发场景中,避免使用“等于”判断作为中断或退出的条件。 -说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。 -反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。 - -- 表达异常的分支时,少用 if-else 方式,这种方式可以改写成: -```java -if (condition) { - ... - return obj; -} -// 接着写 else 的业务逻辑代码; -// 说明:如果非得使用 if()...else if()...else...方式表达逻辑,【强制】避免后续代码维护困难, -// 请勿超过 3 层。 -// 正例:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句示例如下: -public void today() { - if (isBusy()) { - System.out.println(“change time.”); - return; - } - if (isFree()) { - System.out.println(“go to travel.”); - return; - } - System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”); - return; -} -``` - -- 除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。 -说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢? -```java -// 正例: -// 伪代码如下 -final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); -if (existed) { - ... -} -// 反例:在我们的代码中可以看到很多这种把复杂冗长的逻辑判断写在if语句中的 -if ((file.open(fileName, "w") != null) && (...) || (...)) { - ... -} -``` - -- **循环体中的语句要考量性能**,以下操作尽量移至循环体外处理,如:定义对象、变量、 获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外)。 - -- 避免采用取反逻辑运算符。 -说明:取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。 -正例:使用 if (x < 628) 来表达 x 小于 628。 -反例:使用 if (!(x >= 628)) 来表达 x 小于 628。 - -- 接口入参保护(即,参数校验),这种场景常见的是用作批量操作的接口。 - -- 下列情形,需要进行参数校验: -1) 调用频次低的方法。 -2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。 -3) 需要极高稳定性和可用性的方法。 -4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。 -5) 敏感权限入口。 - -- 下列情形,不需要进行参数校验: -1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。 -2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。 -3) 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。 - -### 1.8 注释规约 -注释感觉是我们代码规范的重灾区咯,也是大家最容易忽略的地方。 - -- 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/** 内容 */格式,不得使用 // xxx 方式。 说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注 释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。 - -- 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,还必须指出该方法做什么事情,实现什么功能。 -说明:对子类的实现要求,或者调用注意事项,请一并说明。 - -- 所有的类都必须添加创建者和创建日期。 - -- 方法内部单行注释,在被注释语句上方另起一行,使用 // 注释。方法内部多行注释 使用 /* */ 注释,注意与代码对齐。 - -- 所有的枚举类型字段必须要有注释,说明每个数据项的用途。 - -- 与其用“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。 -反例:“TCP 连接超时” 解释成 “传输控制协议连接超时”,理解反而费脑筋。 - -- 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。 -说明:代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后, 就失去了导航的意义。 - -- 谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。 -说明:代码被注释掉有两种可能性。 -1)后续会恢复此段代码逻辑。 -2)永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码)。 - -- 对于注释的要求: -第一、能够准确反应设计思想和代码逻辑; -第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。 - -- 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。 -```java -// 反例: -// put elephant into fridge -put(elephant, fridge); -// 方法名 put,加上两个有意义的变量名 elephant 和 fridge,已经说明了这是在干什么, -// 语义清晰的代码不需要额外的注释。 -``` - -- 特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描, 经常清理此类标记。线上故障有时候就是来源于这些标记处的代码。 -1) 待办事宜(TODO):( 标记人,标记时间,[预计处理时间] ) -表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc 还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。 -2) 错误,不能工作(FIXME):( 标记人,标记时间,[预计处理时间] ) -在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。 - -### 1.9 其它 -- 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 -说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”); - -- velocity 调用 POJO 类的属性时,建议直接使用属性名取值即可,模板引擎会自动按规范调用 POJO 的 getXxx(),如果是 boolean 基本数据类型变量(boolean 命名不需要加 is 前缀),会自动调用isXxx()方法。 -说明:注意如果是 Boolean 包装类对象,优先调用 getXxx()的方法。 - -- 注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够 取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。 - -- 获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime(); -说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime()的方式。在 JDK8 中, 针对统计时间等场景,推荐使用 Instant 类。 - -- 不要在视图模板中加入任何复杂的逻辑。 -说明:根据 MVC 理论,视图的职责是展示,不要抢模型和控制器的活。 - -- 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。 - -- 及时清理不再使用的代码段或配置信息。 -说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。 -正例:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///)来说明注释掉代码的理由。 - -## 第二章 异常与日志规范 -### 2.1 异常处理 -- Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。 -说明:无法通过预检查的异常除外,比如,**在解析字符串形式的数字时,不得不通过 catch NumberFormatException 来实现**。 -正例:if (obj != null) {...} -反例:try { obj.method(); } catch (NullPointerException e) {…} - -- 异常不要用来做流程控制,条件控制。 -说明:异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。 - -- catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。 对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。 -说明:对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。 -正例:用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,在程序上作出分门别类的判断,并提示给用户。 - -- 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。 - -- 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。 - -- finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。 -说明:如果 JDK7 及以上,可以使用 try-with-resources 方式。 - -- 不要在 finally 块中使用 return。 -说明:finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。 - -- 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。 -说明:如果预期对方抛的是绣球,实际接到的是铅球,就会产生意外情况。 - -- 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。 -说明:本手册明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。 - -- 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景: -1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。 -反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。 -2) 数据库的查询结果可能为 null。 -3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。 -4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。 -5) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。 -6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。 -正例:使用 JDK8 的 Optional 类来防止 NPE 问题。 - -- 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(), 更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等。 - -- 对于公司外的 http/api 开放接口必须使用“错误码”;而应用内部推荐异常抛出; 跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误简短信息”。 -说明:关于 RPC 方法返回方式使用 Result 方式的理由。 -1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 -2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。 - -- 避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。 -说明:随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。 -```java -// 正例: -// 一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取: -private boolean checkParam(DTO dto) {...} -``` - -### 2.2 日志规约 -- 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。 -```java -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -private static final Logger logger = LoggerFactory.getLogger(Abc.class); -``` - -- 日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。 - -- 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。 logType:日志类型,如 stats/monitor/access 等;logName:日志描述。这种命名的好处: 通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。 -正例:mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log -说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。 - -- 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。 -说明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。 -```java -// 正例: -//(条件)建议采用如下方式 -if (logger.isDebugEnabled()) { - logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); -} -// 正例:(占位符) -logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol); -``` - -- 避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。 -```xml - - -``` - -- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。 -正例:logger.error(各类参数或者对象 toString() + "_" + e.getMessage(), e); - -- 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。 -说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处? - -- 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。 -说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息。 - -- 尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话 使用中文描述即可,否则容易产生歧义。国际化团队或海外部署的服务器由于字符集问题,【强制】 使用全英文来注释和描述日志错误信息。 - -## 第三章 数据库规范 -我们 to B 的业务主要使用的是Oracle和SQL server,去年参与适配了国产的华为GaussDB及达梦DM数据库。 -### 3.1 建表规约 -- 临时库、表名必须以tmp为前缀,如果是按照日期生成的,以日期为后缀 - -- 备份库、表必须以bak为前缀,如果是按照日期生成的,以日期为后缀 - -- 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1 表示是,0 表示否)。 -说明:任何字段如果为非负数,必须是 unsigned。 -注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在<resultMap>设置从is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。 正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。 -tips:我们使用的是dr字段代表逻辑删除,且POJO和布尔字段也未使用上述规范,这也与我们的JDBC框架有关,我们的JDBC框架是自己设计的,与mybatis等主流框架有很大不同。 - -- 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。 -说明:**MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。** -正例:aliyun_admin,rdc_config,level3_name -反例:AliyunAdmin,rdcConfig,level_3_name - -- 表名不使用复数名词。 -说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。 - -- 禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。 - -- 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。 -说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。 - -- 小数类型为 decimal,禁止使用 float 和 double。 -说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到错误的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。 - -- 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。 -tips:公司这一点倒是做的比较规范。 - -- varchar 是不定长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。 -tips:Oracle的varchar最大长度为4000,SQL server 8000,这是之前适配数据库时踩过的坑。 - -- 表必备三字段:id, gmt_create, gmt_modified。 -说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create、gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新。 - -- **表的命名最好是加上“业务名称_表的作用”**。 正例:alipay_task / force_project / trade_config - -- 库名与应用名称尽量一致。 - -- 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。 - -- 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循: -1)不是频繁修改的字段。 -2)不是 varchar 超长字段,更不能是 text 字段。 -正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。 - -- 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 -说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。 - -- 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。 -正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。 - -### 3.2 SQL语句 -- 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 -说明:**count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行**。 - -- **count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0**。 - -- 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。 -正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table; - -- 使用 ISNULL()来判断是否为 NULL 值。 -说明:NULL 与任何值的直接比较都为 NULL。 -1) NULL<>NULL 的返回结果是 NULL,而不是 false。 -2) NULL=NULL 的返回结果是 NULL,而不是 true。 -3) NULL<>1 的返回结果是 NULL,而不是 true。 - -- 在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。 - -- 不得使用外键与级联,一切外键概念必须在应用层解决。 -说明: 以学生和成绩的关系为例,学生表中的student_id是主键,那么成绩表中的student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。 - -- 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。 - -- 数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。 - -- in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控 制在 1000 个之内。 - -- 如果有国际化需要,所有的字符存储与表示,均以 utf-8 编码,注意字符统计函数 的区别。 -说明: -SELECT LENGTH("轻松工作"); 返回为 12 -SELECT CHARACTER_LENGTH("轻松工作"); 返回为 4 -如果需要存储表情,那么选择 utf8mb4 来进行存储,注意它与 utf-8 编码的区别。 - -- TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。 -说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。 - -### 3.3 ORM映射 -我们的JDBC框架是自己研发的,之前也有看过Mybatis的源码,两者的设计及使用还是差别挺大的。 -- 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 -说明: -1)增加查询分析器解析成本。 -2)增减字段容易与 resultMap 配置不一致。 -3)无用字 段增加网络消耗,尤其是 text 类型的字段。 - -- POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。 -说明:参见定义 POJO 类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。 在 MyBatis Generator 生成的代码中,需要进行对应的修改。 - -- 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个 POJO 类与之对应。 -说明:配置映射关系,使字段与 DO 类解耦,方便维护。 - -- sql.xml 配置参数使用:#{}, #param# 不要使用${} 此种方式容易出现 SQL 注入。 - -- iBATIS 自带的 queryForList(String statementName,int start,int size)不推 荐使用。 -说明:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再通过subList 取 start,size 的子集合。 -```java -// 正例: -Map map = new HashMap<>(); -map.put("start", start); -map.put("size", size); -``` - -- 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。 -说明:resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控。 - -- 更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。 - -- 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。 - -- @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。 - -- <isEqual>中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;<isNotEmpty>表示不为空且不为 null 时执行;<isNotNull>表示不为 null 值时 执行。 - -### 3.4 索引规范 -- 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。 -说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律(只要有这个可能性 就一定会发生),必然有脏数据产生。 - -- 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。 -说明:即使双表 join 也要注意表索引、SQL 性能。 - -- 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。 -说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。 - -- 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 -说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。 - -- 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。 -正例:where a=? and b=? order by c; 索引:a_b_c -反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。 - -- 利用覆盖索引来进行查询操作,避免回表。 -说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。 -正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain 的结果,extra 列会出现:using index。 - -- 利用延迟关联或者子查询优化超多分页场景。 -说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。 -正例:先快速定位需要获取的 id 段,然后再关联: -SELECT a.* FROM 表1 a, ( select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id - -- SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。 -说明: -1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 -2)ref 指的是使用普通的索引(normal index)。 -3)range 对索引进行范围检索。 -反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较range 还低,与全表扫描是小巫见大巫。 - -- 建组合索引的时候,区分度最高的在最左边。 -正例:如果 where a=? and b=? ,如果 a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。 -说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c。 - -- 防止因字段类型不同造成的隐式转换,导致索引失效。 - -- 创建索引时避免有如下极端误解: -1)宁滥勿缺。认为一个查询就需要建一个索引。 -2)宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。 -3)抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。 - -## 安全规约 -- 隶属于用户个人的页面或者功能必须进行权限控制校验。 -说明:防止没有做水平权限校验就可随意访问、修改、删除别人的数据,比如查看他人的私信 内容、修改他人的订单。 - -- 用户敏感数据禁止直接展示,必须对展示数据进行脱敏。 -说明:中国大陆个人手机号码显示为:158****9119,隐藏中间 4 位,防止隐私泄露。 - -- 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库。 - -- 用户请求传入的任何参数必须做有效性验证。 -说明:忽略参数校验可能导致: -1)page size 过大导致内存溢出; -2)恶意 order by 导致数据库慢查询; -3)任意重定向; -4)SQL 注入; -5)反序列化注入; -6)正则输入源串拒绝服务 ReDoS; -说明:Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。 - -- 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。 - -- 表单、AJAX 提交必须执行 CSRF 安全验证。 -说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中用户参数进行相应修改。 - -- 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。 -说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。 - -- 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。 \ No newline at end of file diff --git "a/docs/LearningExperience/PersonalExperience/\345\210\235\347\272\247\345\274\200\345\217\221\350\200\205\345\272\224\350\257\245\344\273\216spring\346\272\220\347\240\201\344\270\255\345\255\246\344\273\200\344\271\210.md" "b/docs/LearningExperience/PersonalExperience/\345\210\235\347\272\247\345\274\200\345\217\221\350\200\205\345\272\224\350\257\245\344\273\216spring\346\272\220\347\240\201\344\270\255\345\255\246\344\273\200\344\271\210.md" index 264c6966..57367d42 100644 --- "a/docs/LearningExperience/PersonalExperience/\345\210\235\347\272\247\345\274\200\345\217\221\350\200\205\345\272\224\350\257\245\344\273\216spring\346\272\220\347\240\201\344\270\255\345\255\246\344\273\200\344\271\210.md" +++ "b/docs/LearningExperience/PersonalExperience/\345\210\235\347\272\247\345\274\200\345\217\221\350\200\205\345\272\224\350\257\245\344\273\216spring\346\272\220\347\240\201\344\270\255\345\255\246\344\273\200\344\271\210.md" @@ -9,6 +9,7 @@ 下面我将分三个部分,谈一谈自己的经验。 ### 一、工作方面(编码规范、编码能力、设计模式、英文阅读) + 我所从事的行业做的是 toB 的业务,产品底层平台的框架,代码累累,堆积成山,很多框架都是零几年写的,有的甚至比 Spring 还早。且最近国产化、中台、云服务等概念都在不断落地中,有框架源码的阅读经验,让我能够更从容地面对公司研发的新框架,所维护的产品适配华为高斯数据库时,也更清楚可能是 JDBC 框架中哪里做了特殊处理所导致的问题。当然,最主要的还是对个人编码规范的养成,设计模式的理解应用,英文阅读的能力提升。 作为一个初入职场的开发者,编码规范是一个很重要的点,能够让你写出的代码易于维护、阅读和理解。比如,Spring 框架虽然类图体系复杂丰富,但对于类、方法、参数等的命名非常规范;注释注解也非常严谨,注重格式,不会偷懒;对于异常和日志的处理也具有很好的参考价值。比如,之前产品中有遇到一个“将业务表单中的小数从科学计数法转换成普通计数法”(数值过大的 Double 类型数字默认会以科学记数法显示,这是用户无法接受的),研读了复杂的业务代码之后,发现填充到表单前的数据都是 Object 类型的,且丢失了原本类型,无法通过 instanceof 判断应该转成 String 还是 Double,这让我和我的师傅都有点头疼,但 Spring 源码中有过一段以异常捕获机制处理逻辑代码的片段让我灵光乍现,于是我直接将 Object 强转成 Double 并使其不做科学记数法的处理,并将这段代码 try 住,如果没抛异常,就转换成了 Double,抛了异常,就在 catch 中强转成 String。 @@ -17,15 +18,18 @@ 从 IoC 的各顶层接口到中间一层一层的抽象类,再到最后的实现类,这一整套体系的设计和实现,对自己在日常工作中设计某些功能的接口、抽象类和具体实现,都带来了很有价值的参考,设计模式和巧妙的编码技巧也渐渐变得触手可及。比如,设计一个 VO 字段校验功能时,会先定义一个顶层接口,抽象出公共方法,抽象类中有做必输项字段非空校验的,在其中利用模板方法模式对公共功能做具体实现,特性化功能写成抽象方法交由各子类具体实现即可。 -Spring 上很多接口和抽象类,其注解甚至比代码还多,我也经常尝试着去阅读理解这些注释,看看自己的理解与书上的差异,用这种方式来提升英文技术文档的阅读能力,往往更实在一些。 +Spring 上很多接口和抽象类,其注释甚至比代码还多,我也经常尝试着去阅读理解这些注释,看看自己的理解与书上的差异,用这种方式来提升英文技术文档的阅读能力,往往更实在一些。 ### 二、学习方面(学习模式的构建、学以致用) + 虽然是做技术的,但我也是一个很爱出去耍的人。构建好自己的学习模式能够让你更从容地面对工作和生活。不加班的情况下(所幸部门加班并不太多),我一般会在晚饭之后以及周日时间充电。不管是学技术还是其它什么东西,我认为 以视频为入口,以业界公认的名书继续深入理解,以社交圈的同行或网上社区为输出交流管道,最后持久化到思维导图及学习文档中。Spring 源码学习是我工作之后对自己学习模式构建的一个尝试,构建起这种学习模式之后,个人的工作和生活也变得更加协调平衡,不至于在繁杂忙碌的工作中渐渐丧失学习能力。另外一个比较重要的就是,看 Spring 源码时经常能看到一些与公司框架有异曲同工之妙的编码技巧及实现,比如异常的批量抛出,ConcurrentHashMap 初始化其容量,ThreadLocal 的使用等等,这些都是在读 Spring 源码之前很少会注意或使用的。 ### 三、社交方面(GitHub、事业部内部授课) + 对于我来说,既然辛辛苦苦搞懂了一个技术,那就一定得输出自己的理解和经验,装波逼,不然辛辛苦苦几个月,什么产出都没有,过一段时间又把学得给忘了,这和被白嫖有什么区别。而输出知识的话当然要选一些比较优质的平台,比如 GayHub,Doocs 组织和其创建者就是我在 GitHub 上认识的,这些大佬之所以牛逼,能够成事,必然有其原因,加入他们的组织跟着混,准能学到更多我想要的东西(不仅仅是技术方面)。 另外,我所在的事业部也有一个“王者荣耀”的学习进阶活动,将自己的学习成果整理成简单、易于理解的内部授课也更容易获得同事的认可与信赖。 ### 个人建议 -对于初级开发者学习 Spring 源码来说,我建议配合阿里的《Java 开发手册》一起看,因为编码能力和框架设计能力是需要很长时间的经验积累才能得到大幅提升的,而编码规范则是我们最开始就能做到并做好的事情,也是很多成熟公司越来越重视的东西。另外,阿里的《Java 开发手册》中不少规范都是参考了 Spring 框架的,这也从侧面体现了 Spring 作为业界知名框架,其编码的规范性是深受认可的。 \ No newline at end of file + +对于初级开发者学习 Spring 源码来说,我建议配合阿里的《Java 开发手册》一起看,因为编码能力和框架设计能力是需要很长时间的经验积累才能得到大幅提升的,而编码规范则是我们最开始就能做到并做好的事情,也是很多成熟公司越来越重视的东西。另外,阿里的《Java 开发手册》中不少规范都是参考了 Spring 框架的,这也从侧面体现了 Spring 作为业界知名框架,其编码的规范性是深受认可的。 diff --git "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/1\343\200\201\345\217\215\345\260\204\345\267\245\345\205\267\347\256\261\345\222\214TypeHandler\347\263\273\345\210\227.md" "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/1\343\200\201\345\217\215\345\260\204\345\267\245\345\205\267\347\256\261\345\222\214TypeHandler\347\263\273\345\210\227.md" index f57a2701..9076e4be 100644 --- "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/1\343\200\201\345\217\215\345\260\204\345\267\245\345\205\267\347\256\261\345\222\214TypeHandler\347\263\273\345\210\227.md" +++ "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/1\343\200\201\345\217\215\345\260\204\345\267\245\345\205\267\347\256\261\345\222\214TypeHandler\347\263\273\345\210\227.md" @@ -1,7 +1,11 @@ -在 Mybatis 的基础支持层主要看一下支撑 ORM实现 的底层代码。 +在 Mybatis 的基础支持层主要看一下支撑 ORM 实现 的底层代码。 + ## 1 反射工具包 + ### 1.1Reflector -Reflector类 主要实现了对 JavaBean 的元数据属性的封装,比如:可读属性列表,可写属性列表;及反射操作的封装,如:属性对应的 setter方法,getter方法 的反射调用。源码实现如下: + +Reflector 类 主要实现了对 JavaBean 的元数据属性的封装,比如:可读属性列表,可写属性列表;及反射操作的封装,如:属性对应的 setter 方法,getter 方法 的反射调用。源码实现如下: + ```java public class Reflector { @@ -55,8 +59,11 @@ public class Reflector { } } ``` + ### 1.2 ReflectorFactory + 顾名思义,Reflector 的工厂模式,跟大部分工厂类一样,里面肯定有通过标识获取对象的方法。类的设计也遵照了 接口,实现类的模式,虽然本接口只有一个默认实现。 + ```java public interface ReflectorFactory { @@ -88,7 +95,7 @@ public class DefaultReflectorFactory implements ReflectorFactory { return new Reflector(type); } } - + public DefaultReflectorFactory() { } @@ -110,8 +117,11 @@ public class CustomReflectorFactory extends DefaultReflectorFactory { } ``` + ### 1.3 ObjectFactory + 该类也是接口加一个默认实现类,并且支持自定义扩展,Mybatis 中有很多这样的设计方式。 + ```java /** * MyBatis uses an ObjectFactory to create all needed new Objects. @@ -205,10 +215,15 @@ public class DefaultObjectFactory implements ObjectFactory, Serializable { } } ``` + ## 2 类型转换 -类型转换是实现 ORM 的重要一环,由于数据库中的数据类型与 Java语言 的数据类型并不对等,所以在 PrepareStatement 为 sql语句 绑定参数时,需要从 Java类型 转换成 JDBC类型,而从结果集获取数据时,又要将 JDBC类型 转换成 Java类型,Mybatis 使用 TypeHandler 完成了上述的双向转换。 + +类型转换是实现 ORM 的重要一环,由于数据库中的数据类型与 Java 语言 的数据类型并不对等,所以在 PrepareStatement 为 sql 语句 绑定参数时,需要从 Java 类型 转换成 JDBC 类型,而从结果集获取数据时,又要将 JDBC 类型 转换成 Java 类型,Mybatis 使用 TypeHandler 完成了上述的双向转换。 + ### 2.1 JdbcType + Mybatis 通过 JdbcType 这个枚举类型代表了 JDBC 中的数据类型。 + ```java /** * 该枚举类描述了 JDBC 中的数据类型 @@ -281,8 +296,11 @@ public enum JdbcType { } ``` + ### 2.2 TypeHandler -TypeHandler 是 Mybatis 中所有类型转换器的顶层接口,主要用于实现数据从 Java类型 到 JdbcType类型 的相互转换。 + +TypeHandler 是 Mybatis 中所有类型转换器的顶层接口,主要用于实现数据从 Java 类型 到 JdbcType 类型 的相互转换。 + ```java public interface TypeHandler { @@ -377,12 +395,16 @@ public class IntegerTypeHandler extends BaseTypeHandler { } } ``` -TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java对象,可以在映射文件中定义合适的映射规则 <resultMap> 完成映射。 + +TypeHandler 主要用于单个参数的类型转换,如果要将多个列的值转换成一个 Java 对象,可以在映射文件中定义合适的映射规则 <resultMap> 完成映射。 + ### 2.3 TypeHandlerRegistry + TypeHandlerRegistry 主要负责管理所有已知的 TypeHandler,Mybatis 在初始化过程中会为所有已知的 TypeHandler 创建对象,并注册到 TypeHandlerRegistry。 + ```java // TypeHandlerRegistry 中的核心字段如下 - + /** 该集合主要用于从结果集读取数据时,将数据从 JDBC类型 转换成 Java类型 */ private final Map> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); @@ -395,8 +417,10 @@ TypeHandlerRegistry 主要负责管理所有已知的 TypeHandler,Mybatis 在 /** key:TypeHandler 的类型;value:该 TypeHandler类型 对应的 TypeHandler对象 */ private final Map, TypeHandler> allTypeHandlersMap = new HashMap<>(); ``` -**1、注册TypeHandler对象** -TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 的功能,该方法存在多种重载,但大多数 register()方法 最终都会走 register(Type javaType, JdbcType jdbcType, TypeHandler handler) 的处理逻辑,该重载方法中分别指定了 TypeHandler 能够处理的 Java类型、JDBC类型、TypeHandler对象。 + +**1、注册 TypeHandler 对象** +TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler 对象 的功能,该方法存在多种重载,但大多数 register()方法 最终都会走 register(Type javaType, JdbcType jdbcType, TypeHandler handler) 的处理逻辑,该重载方法中分别指定了 TypeHandler 能够处理的 Java 类型、JDBC 类型、TypeHandler 对象。 + ```java /** * TypeHandlerRegistry 中对 register()方法 实现了多种重载,本 register()方法 @@ -414,7 +438,9 @@ TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 allTypeHandlersMap.put(handler.getClass(), handler); } ``` -另外,TypeHandlerRegistry 还提供了扫描并注册指定包目录下 TypeHandler实现类 的 register()方法 重载。 + +另外,TypeHandlerRegistry 还提供了扫描并注册指定包目录下 TypeHandler 实现类 的 register()方法 重载。 + ```java /** * 从指定 包名packageName 中获取自定义的 TypeHandler实现类 @@ -432,7 +458,9 @@ TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 } } ``` + 最后看一下 TypeHandlerRegistry 的构造方法,其通过多种 register()方法 重载,完成了所有已知的 TypeHandler 的重载。 + ```java /** * 进行 Java 及 JDBC基本数据类型 的 TypeHandler 注册 @@ -515,8 +543,10 @@ TypeHandlerRegistry 中的 register()方法 实现了注册 TypeHandler对象 register(JapaneseDate.class, new JapaneseDateTypeHandler()); } ``` -**2、查找TypeHandler** -TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也就是为了方便获取,其对应的方法为 getTypeHandler(),该方法也存在多种重载,其中最重要的一个重载为 getTypeHandler(Type type, JdbcType jdbcType),它会根据指定的 Java类型 和 JdbcType类型 查找相应的 TypeHandler对象。 + +**2、查找 TypeHandler** +TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也就是为了方便获取,其对应的方法为 getTypeHandler(),该方法也存在多种重载,其中最重要的一个重载为 getTypeHandler(Type type, JdbcType jdbcType),它会根据指定的 Java 类型 和 JdbcType 类型 查找相应的 TypeHandler 对象。 + ```java /** * 获取 TypeHandler对象 @@ -545,4 +575,5 @@ TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也 return (TypeHandler) handler; } ``` -除了 Mabatis 本身自带的 TypeHandler实现,我们还可以添加自定义的 TypeHandler实现类,在配置文件 mybatis-config.xml 中的 <typeHandler> 标签下配置好 自定义TypeHandler,Mybatis 就会在初始化时解析该标签内容,完成 自定义TypeHandler 的注册。 \ No newline at end of file + +除了 Mybatis 本身自带的 TypeHandler 实现,我们还可以添加自定义的 TypeHandler 实现类,在配置文件 mybatis-config.xml 中的 <typeHandler> 标签下配置好 自定义 TypeHandler,Mybatis 就会在初始化时解析该标签内容,完成 自定义 TypeHandler 的注册。 diff --git "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/2\343\200\201DataSource\345\217\212Transaction\346\250\241\345\235\227.md" "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/2\343\200\201DataSource\345\217\212Transaction\346\250\241\345\235\227.md" index 1cece8dd..e95b386f 100644 --- "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/2\343\200\201DataSource\345\217\212Transaction\346\250\241\345\235\227.md" +++ "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/2\343\200\201DataSource\345\217\212Transaction\346\250\241\345\235\227.md" @@ -1,8 +1,13 @@ 在数据持久层,数据源和事务是两个非常重要的组件,对数据持久层的影响很大,在实际开发中,一般会使用 Mybatis 集成第三方数据源组件,如:c3p0、Druid,另外,Mybatis 也提供了自己的数据库连接池实现,本文会通过 Mybatis 的源码实现来了解数据库连接池的设计。而事务方面,一般使用 Spring 进行事务的管理,这里不做详细分析。下面我们看一下 Mybatis 是如何对这两部分进行封装的。 + ## 1 DataSource -常见的数据源都会实现 javax.sql.DataSource接口,Mybatis 中提供了两个该接口的实现类,分别是:PooledDataSource 和 UnpooledDataSource,并使用不同的工厂类分别管理这两个类的对象。 + +常见的数据源都会实现 javax.sql.DataSource 接口,Mybatis 中提供了两个该接口的实现类,分别是:PooledDataSource 和 UnpooledDataSource,并使用不同的工厂类分别管理这两个类的对象。 + ### 1.1 DataSourceFactory -DataSourceFactory系列类 的设计比较简单,DataSourceFactory 作为顶级接口,UnpooledDataSourceFactory 实现了该接口,PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。 + +DataSourceFactory 系列类 的设计比较简单,DataSourceFactory 作为顶级接口,UnpooledDataSourceFactory 实现了该接口,PooledDataSourceFactory 又继承了 UnpooledDataSourceFactory。 + ```java public interface DataSourceFactory { @@ -70,7 +75,9 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory { ``` ### 1.2 UnpooledDataSource -本实现类实现了 DataSource接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下: + +本实现类实现了 DataSource 接口 中的 getConnection() 及其重载方法,用于获取数据库连接。其中的主要属性及方法如下: + ```java public class UnpooledDataSource implements DataSource { @@ -163,17 +170,23 @@ public class UnpooledDataSource implements DataSource { } } ``` + ### 1.3 PooledDataSource + 数据库建立连接是非常耗时的,且并发的连接数也非常有限。而数据库连接池可以实现数据库的重用、提高响应速度、防止数据库因连接过多而假死等。 -数据库连接池的设计思路一般为: -1. 连接池初始化时创建一定数量的连接,并添加到连接池中备用; -2. 当程序需要使用数据库连接时,从连接池中请求,用完后会将其返还给连接池,而不是直接关闭; -3. 连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会短暂阻塞后重新尝试获取连接,如此循环,直到有连接可用; -4. 如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。 +**数据库连接池的设计思路一般为:** + +1. **连接池初始化时创建一定数量的连接,并添加到连接池中备用;** +2. **当程序需要使用数据库连接时,从连接池中请求,用完后会将其返还给连接池,而不是直接关闭;** +3. **连接池会控制总连接上限及空闲连接上线,如果连接池中的连接总数已达上限,且都被占用,后续的连接请求会短暂阻塞后重新尝试获取连接,如此循环,直到有连接可用;** +4. **如果连接池中空闲连接较多,已达到空闲连接上限,则返回的连接会被关闭掉,以降低系统开销。** PooledDataSource 实现了简易的数据库连接池功能,其创建数据库连接的功能依赖了上面的 UnpooledDataSource。 + #### 1.3.1 PooledConnection -PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection数据库连接对象 及其代理对象(JDK动态代理生成的)。PooledConnection 继承了 JDK动态代理 的 InvocationHandler接口。 + +PooledDataSource 通过管理 PooledConnection 来实现对 java.sql.Connection 的管理。PooledConnection 封装了 java.sql.Connection 数据库连接对象 及其代理对象(JDK 动态代理生成的)。PooledConnection 继承了 JDK 动态代理 的 InvocationHandler 接口。 + ```java class PooledConnection implements InvocationHandler { @@ -228,8 +241,11 @@ class PooledConnection implements InvocationHandler { } } ``` + #### 1.3.2 PoolState + PoolState 主要用于管理 PooledConnection 对象状态,其通过持有两个 List<PooledConnection>集合 分别管理空闲状态的连接 和 活跃状态的连接。另外,PoolState 还定义了一系列用于统计的字段。 + ```java public class PoolState { @@ -335,11 +351,13 @@ public class PoolState { } } ``` + #### 1.3.3 PooledDataSource -PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource对象 创建的,并由 PoolState 管理所有连接的状态。 -PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图: -![avatar](../../../images/mybatis/数据库连接池流程图.png) +PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。 +PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection 对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图: + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/数据库连接池流程图.png) ```java public class PooledDataSource implements DataSource { @@ -633,7 +651,9 @@ public class PooledDataSource implements DataSource { } } ``` -最后,我们来看一下 popConnection() 和 pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql语句,检测连接是否正常。 + +最后,我们来看一下 popConnection() 和 pushConnection() 都调用了的 isValid()方法,该方法除了检测 PooledConnection 中的 valid 字段 外 还还会调用 PooledDataSource 中的 pingConnection()方法,让数据库连接对象 执行指定的 sql 语句,检测连接是否正常。 + ```java class PooledConnection implements InvocationHandler { /** @@ -703,9 +723,12 @@ public class PooledDataSource implements DataSource { } } ``` + ## 2 Transaction -遵循 “接口-实现类” 的设计原则,Mybatis 也是先使用 Transaction接口 对数据库事务做了抽象,而实现类则只提供了两个,即:JdbcTransaction 和 ManagedTransaction。这两种对象的获取,使用了两个对应的工厂类 JdbcTransactionFactory 和 ManagedTransactionFactory。 + +遵循 “接口-实现类” 的设计原则,Mybatis 也是先使用 Transaction 接口 对数据库事务做了抽象,而实现类则只提供了两个,即:JdbcTransaction 和 ManagedTransaction。这两种对象的获取,使用了两个对应的工厂类 JdbcTransactionFactory 和 ManagedTransactionFactory。 不过一般我们并不会使用 Mybatis 管理事务,而是将 Mybatis 集成到 Spring,由 Spring 进行事务的管理。细节部分会在后面的文章中详细讲解。 + ```java public interface Transaction { @@ -985,4 +1008,4 @@ public class ManagedTransactionFactory implements TransactionFactory { return new ManagedTransaction(ds, level, closeConnection); } } -``` \ No newline at end of file +``` diff --git "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/3\343\200\201binding\346\250\241\345\235\227.md" "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/3\343\200\201binding\346\250\241\345\235\227.md" index 11b01a02..a3034170 100644 --- "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/3\343\200\201binding\346\250\241\345\235\227.md" +++ "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/3\343\200\201binding\346\250\241\345\235\227.md" @@ -1,6 +1,7 @@ -binding模块 主要为了解决一个历史遗留问题,原先查询一个 VO对象 时需要调用 SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法,执行指定的 sql语句,第一个参数 selectXXVOById 指定了执行的 sql语句id,如果我们不小心写错了参数,Mybatis 是无法在初始化时发现这个错误的,只会在实际调用 queryForObject(“selectXXVOById”, primaryKey)方法 时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而 binding模块 就像 Java的泛型机制 一样,将程序的错误提前暴露出来,为开发人员省去不少排查问题的精力。 +binding 模块主要为了解决一个历史遗留问题,原先查询一个 VO 对象 时需要调用 SqlSession.queryForObject(“selectXXVOById”, primaryKey)方法,执行指定的 sql 语句,第一个参数 selectXXVOById 指定了执行的 sql 语句 id,如果我们不小心写错了参数,Mybatis 是无法在初始化时发现这个错误的,只会在实际调用 queryForObject(“selectXXVOById”, primaryKey)方法 时才会抛出异常,这对于工程师来说是非常难受的,就像泛型出来之前,很多类型转换不会在编译期发现错误一样。而 binding 模块 就像 Java 的泛型机制 一样,将程序的错误提前暴露出来,为开发人员省去不少排查问题的精力。 + +binding 模块 的解决方案是,定义一个 Mapper 接口,在接口中定义 sql 语句 对应的 方法名 Id 及 参数,这些方法在 Mybatis 的初始化过程中,会与该 Mapper 接口 对应的映射配置文件中的 sql 语句 相关联,如果存在无法关联的 sql 语句,Mybatis 就会抛出异常,帮助我们及时发现问题。示例代码如下: -binding模块 的解决方案是,定义一个 Mapper接口,在接口中定义 sql语句 对应的 方法名Id 及 参数,这些方法在 Mybatis 的初始化过程中,会与该 Mapper接口 对应的映射配置文件中的 sql语句 相关联,如果存在无法关联的 sql语句,Mybatis 就会抛出异常,帮助我们及时发现问题。示例代码如下: ```java public interface HeroMapper { // 映射文件中会存在一个 select * from hs_sell @@ -20,11 +19,9 @@ ``` +![image-20191219151247240](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219151247240.png) - -![image-20191219151247240](/image/mybatis/image-20191219151247240.png) - -![image-20191219151408597](/image/mybatis/image-20191219151408597.png) +![image-20191219151408597](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219151408597.png) ```java public class MixedSqlNode implements SqlNode { @@ -43,9 +40,7 @@ public class MixedSqlNode implements SqlNode { } ``` - - -- 根据mapper.xml文件中的代码流程 需要走 +- 根据 mapper.xml 文件中的代码流程 需要走 `org.apache.ibatis.scripting.xmltags.StaticTextSqlNode#apply` @@ -53,7 +48,7 @@ public class MixedSqlNode implements SqlNode { `org.apache.ibatis.scripting.xmltags.IfSqlNode#apply` -![image-20191219152254274](/image/mybatis/image-20191219152254274.png) +![image-20191219152254274](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219152254274.png) ```java /** @@ -82,15 +77,15 @@ public class StaticTextSqlNode implements SqlNode { - `org.apache.ibatis.scripting.xmltags.DynamicContext#appendSql` - ```JAVA - public void appendSql(String sql) { - sqlBuilder.add(sql); - } + ```java + public void appendSql(String sql) { + sqlBuilder.add(sql); + } ``` -- 解析`trim`标签 +- 解析`trim`标签 -![image-20191219152502960](/image/mybatis/image-20191219152502960.png) +![image-20191219152502960](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219152502960.png) - 在解析`trim`的时候会往下解析下级标签 @@ -105,11 +100,9 @@ public class StaticTextSqlNode implements SqlNode { } ``` - +![image-20191219152655746](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219152655746.png) -![image-20191219152655746](/image/mybatis/image-20191219152655746.png) - -```JAVA +```java @Override public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { @@ -123,7 +116,7 @@ public class StaticTextSqlNode implements SqlNode { - `evaluator.evaluateBoolean(test, context.getBindings())`方法 -```JAVA +```java /** * @param expression 判断语句,ID != null * @param parameterObject 参数列表 @@ -142,7 +135,7 @@ public class StaticTextSqlNode implements SqlNode { ``` -```JAVA +```java /** * 取值 * @param expression 判断语句,ID=NULL @@ -161,21 +154,19 @@ public class StaticTextSqlNode implements SqlNode { ``` -![image-20191219153341466](/image/mybatis/image-20191219153341466.png) +![image-20191219153341466](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219153341466.png) 存在返回`true` -执行完成就得到了一个sql +执行完成就得到了一个 sql -![image-20191219153553127](/image/mybatis/image-20191219153553127.png) +![image-20191219153553127](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219153553127.png) 继续执行`org.apache.ibatis.scripting.xmltags.DynamicSqlSource#getBoundSql`方法 -![image-20191219155129772](/image/mybatis/image-20191219155129772.png) - -- 发送sql`org.apache.ibatis.executor.SimpleExecutor#doQuery` - +![image-20191219155129772](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219155129772.png) +- 发送 sql`org.apache.ibatis.executor.SimpleExecutor#doQuery` - 调用链路如下 @@ -233,7 +224,7 @@ public class StaticTextSqlNode implements SqlNode { } return list; } - + ``` - `org.apache.ibatis.executor.BaseExecutor#queryFromDatabase` @@ -253,20 +244,20 @@ public class StaticTextSqlNode implements SqlNode { } return list; } - + ``` - `org.apache.ibatis.executor.BaseExecutor#doQuery` - `org.apache.ibatis.executor.SimpleExecutor#doQuery` -![image-20191219160832704](/image/mybatis/image-20191219160832704.png) +![image-20191219160832704](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219160832704.png) ```java private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 数据库连接 Connection connection = getConnection(statementLog); - // stms 创建 + // stmt 创建 // org.apache.ibatis.executor.statement.BaseStatementHandler.prepare stmt = handler.prepare(connection, transaction.getTimeout()); // 参数放入 @@ -276,13 +267,11 @@ public class StaticTextSqlNode implements SqlNode { ``` -![image-20191219160908212](/image/mybatis/image-20191219160908212.png) +![image-20191219160908212](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219160908212.png) - `org.apache.ibatis.executor.statement.BaseStatementHandler#prepare` - `org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement` - - ```java @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { @@ -326,11 +315,9 @@ public class StaticTextSqlNode implements SqlNode { - 这个方法都去了`java.sql.Connection#prepareStatement(java.lang.String, java.lang.String[])` - - - 接下来需要考虑的问题是如何将`?`换成我们的参数`2` - ![image-20191219161555793](/image/mybatis/image-20191219161555793.png) + ![image-20191219161555793](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219161555793.png) - `org.apache.ibatis.executor.statement.StatementHandler#parameterize` - `org.apache.ibatis.executor.statement.RoutingStatementHandler#parameterize` @@ -339,15 +326,11 @@ public class StaticTextSqlNode implements SqlNode { - `org.apache.ibatis.executor.parameter.ParameterHandler` - `org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters` - - - - -![image-20191219162258040](/image/mybatis/image-20191219162258040.png) +![image-20191219162258040](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219162258040.png) 这样就拿到了`value`的值 -![image-20191219162506920](/image/mybatis/image-20191219162506920.png) +![image-20191219162506920](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219162506920.png) 准备工作就绪了发送就可以了 @@ -374,13 +357,11 @@ public class StaticTextSqlNode implements SqlNode { - `org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets` - `org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets` +![image-20191219163628214](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219163628214.png) +![image-20191219163640968](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219163640968.png) -![image-20191219163628214](/image/mybatis/image-20191219163628214.png) - -![image-20191219163640968](/image/mybatis/image-20191219163640968.png) - -![image-20191219163957488](/image/mybatis/image-20191219163957488.png) +![image-20191219163957488](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219163957488.png) 处理后结果如上 @@ -431,4 +412,3 @@ public class StaticTextSqlNode implements SqlNode { } ``` - diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybats-GenericTokenParser.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-GenericTokenParser.md" similarity index 97% rename from "docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybats-GenericTokenParser.md" rename to "docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-GenericTokenParser.md" index 4b36efd9..5e6415b9 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybats-GenericTokenParser.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-GenericTokenParser.md" @@ -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) \ No newline at end of file +![image-20191219100446796](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219100446796.png) diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MapperMethod.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MapperMethod.md" index 9f34c645..7476a1dc 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MapperMethod.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MapperMethod.md" @@ -1,4 +1,5 @@ # MapperMethod + - Author: [HuiFer](https://github.com/huifer) - Description: 该文介绍 mybatis MapperMethod 源码 - 源码地址: `org.apache.ibatis.binding.MapperMethod`,核心方法是`execute` @@ -91,8 +92,8 @@ ``` - - 返回值为多个的情况 + ```java /** * 针对多个查询结果进行 ,转换成不同的 list 或者数组 @@ -128,6 +129,7 @@ ``` ### convertToArray + ```java /** * 转换为数组 @@ -154,7 +156,9 @@ } ``` + ### convertToDeclaredCollection + ```java /** * 转换为不同的list对象 @@ -176,28 +180,22 @@ ``` - - 上述两个为转换的过程,其实质还是在 `org.apache.ibatis.session.SqlSession` 中做执行操作 +## debug +- 修改 mapper 返回数组对`org.apache.ibatis.binding.MapperMethod#convertToArray`方法进行测试 -## debug -- 修改mapper返回数组对`org.apache.ibatis.binding.MapperMethod#convertToArray`方法进行测试 ```java HsSell[] list(@Param("ID") Integer id); ``` -![image-20191219092442456](../../../images/mybatis/image-20191219092442456.png) +![image-20191219092442456](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219092442456.png) -- 修改mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试 +- 修改 mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试 ```java LinkedList list(@Param("ID") Integer id); ``` - - -![image-20191219093043035](../../../images/mybatis/image-20191219093043035.png) - - - +![image-20191219093043035](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219093043035.png) diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MetaObject.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MetaObject.md" index 2d01f71d..e7eb696a 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MetaObject.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MetaObject.md" @@ -1,7 +1,9 @@ # Mybatis MetaObject + - Author: [HuiFer](https://github.com/huifer) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码位于:`org.apache.ibatis.reflection.MetaObject` + ```java /** * @author Clinton Begin @@ -106,7 +108,7 @@ public class MetaObject { } } - + } -``` \ No newline at end of file +``` diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MethodSignature.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MethodSignature.md" index 1a88cf2d..56b590df 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MethodSignature.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MethodSignature.md" @@ -1,8 +1,10 @@ # MethodSignature + - Author: [HuiFer](https://github.com/huifer) - Description: 该文介绍 mybatis MethodSignature 类 - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - `org.apache.ibatis.binding.MapperMethod.MethodSignature` + ```java /** * 方法签名 @@ -88,7 +90,7 @@ } /** - * 是否uresultHandler + * 是否有 resultHandler * * @return */ @@ -176,4 +178,4 @@ } } -``` \ No newline at end of file +``` diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ObjectWrapper.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ObjectWrapper.md" index 0eb9e0fa..bdea8ed4 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ObjectWrapper.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ObjectWrapper.md" @@ -1,11 +1,13 @@ # Mybatis ObjectWrapper + - Author: [HuiFer](https://github.com/huifer) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - 源码位于: `org.apache.ibatis.reflection.wrapper.ObjectWrapper`‘ 类图: -![image-20191223100956713](../../../images/mybatis/image-20191223100956713.png) +![image-20191223100956713](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191223100956713.png) + ```java public interface ObjectWrapper { @@ -114,7 +116,9 @@ public interface ObjectWrapper { } ``` + ## BaseWrapper + ```java /** * @author Clinton Begin @@ -230,6 +234,7 @@ public abstract class BaseWrapper implements ObjectWrapper { ``` ## BeanWrapper + ```java public class BeanWrapper extends BaseWrapper { @@ -448,7 +453,9 @@ public class BeanWrapper extends BaseWrapper { } ``` + ## MapWrapper + ```java public class MapWrapper extends BaseWrapper { @@ -583,7 +590,9 @@ public class MapWrapper extends BaseWrapper { } ``` + ## CollectionWrapper + ```java public class CollectionWrapper implements ObjectWrapper { @@ -675,4 +684,4 @@ public class CollectionWrapper implements ObjectWrapper { } } -``` \ No newline at end of file +``` diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ParamNameResolver.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ParamNameResolver.md" index 5f79e78b..f3a5861b 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ParamNameResolver.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-ParamNameResolver.md" @@ -1,10 +1,13 @@ -# ParamNameResolver 源码解析 +# ParamNameResolver 源码解析 + - Author: [HuiFer](https://github.com/huifer) - Description: 该文介绍 mybatis `@Param` 注解和`ParamNameResolver` - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) ## 源码 + - `org.apache.ibatis.reflection.ParamNameResolver` + ```java /** * {@link Param} 注解的扫描工具和处理工具 @@ -142,8 +145,11 @@ public class ParamNameResolver { } ``` + ## debug 阶段 -- 测试用例为同一个 , 每次修改mapper方法参数来进行debug + +- 测试用例为同一个 , 每次修改 mapper 方法参数来进行 debug + ```java @Test void testXmlConfigurationLoad() throws IOException { @@ -160,15 +166,13 @@ public class ParamNameResolver { } ``` - - - ```java List list( Integer id); ``` + 如果不写`@Param`称则返回 -![image-20191219083223084](assets/image-20191219083223084.png) +![image-20191219083223084](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/image-20191219083223084.png) ```java List list(@Param("ID") Integer id); @@ -176,11 +180,9 @@ public class ParamNameResolver { - 写`@Param`返回 -![image-20191219083344439](../../../images/mybatis/image-20191219083344439.png) - -![image-20191219083354873](../../../images/mybatis/image-20191219083354873.png) - +![image-20191219083344439](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219083344439.png) +![image-20191219083354873](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219083354873.png) - `org.apache.ibatis.reflection.ParamNameResolver#getNamedParams` @@ -188,17 +190,14 @@ public class ParamNameResolver { List list( Integer id); ``` -![image-20191219084455292](../../../images/mybatis/image-20191219084455292.png) - - - +![image-20191219084455292](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219084455292.png) ```java List list(@Param("ID") Integer id); ``` -​ 写上`@Param` +​ 写上`@Param` -![image-20191219084943102](../../../images/mybatis/image-20191219084943102.png) +![image-20191219084943102](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219084943102.png) -![image-20191219085131167](../../../images/mybatis/image-20191219085131167.png) \ No newline at end of file +![image-20191219085131167](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219085131167.png) diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-SqlCommand.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-SqlCommand.md" index 75d5788e..30108253 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-SqlCommand.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-SqlCommand.md" @@ -1,9 +1,11 @@ -# sqlCommand +# sqlCommand + - Author: [HuiFer](https://github.com/huifer) -- Description: 该文介绍 mybatis sqlCommand类的源码 +- Description: 该文介绍 mybatis sqlCommand 类的源码 - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) - `org.apache.ibatis.binding.MapperMethod.SqlCommand` + ```java /** * 核心内容: sql id , Sql 类型 @@ -92,14 +94,6 @@ ``` +![image-20191218191512184](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191218191512184.png) -![image-20191218191512184](../../../images/mybatis/image-20191218191512184.png) - - - -![image-20191218191550550](../../../images/mybatis/image-20191218191550550.png) - - - - - +![image-20191218191550550](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191218191550550.png) diff --git "a/docs/Netty/AdvancedFeaturesOfNetty/Netty\346\236\266\346\236\204\350\256\276\350\256\241.md" "b/docs/Netty/AdvancedFeaturesOfNetty/Netty\346\236\266\346\236\204\350\256\276\350\256\241.md" index 5ae870be..9ed58720 100644 --- "a/docs/Netty/AdvancedFeaturesOfNetty/Netty\346\236\266\346\236\204\350\256\276\350\256\241.md" +++ "b/docs/Netty/AdvancedFeaturesOfNetty/Netty\346\236\266\346\236\204\350\256\276\350\256\241.md" @@ -1,73 +1,88 @@ 本博文用于重点分析 Netty 的逻辑架构及关键的架构质量属性,希望有助于大家从 Netty 的架构设计中汲取营养,设计出高性能、高可靠 性和可扩展的程序。 -## Netty的三层架构设计 +## Netty 的三层架构设计 + Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。 -![avatar](../../../images/Netty/Netty逻辑架构图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty逻辑架构图.png) ### 通信调度层 Reactor -它由一系列辅助类完成,包括 Reactor线程 NioEventLoop 及其父类,NioSocketChannel / NioServerSocketChannel 及其父类,Buffer组件,Unsafe组件 等。该层的主要职责就是**监听网络的读写和连接操作**,负责**将网络层的数据读取到内存缓冲区**,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 PipeLine 中,由 PipeLine 管理的责任链来进行后续的处理。 + +它由一系列辅助类完成,包括 Reactor 线程 NioEventLoop 及其父类,NioSocketChannel / NioServerSocketChannel 及其父类,Buffer 组件,Unsafe 组件 等。该层的主要职责就是**监听网络的读写和连接操作**,负责**将网络层的数据读取到内存缓冲区**,然后触发各种网络事件,例如连接创建、连接激活、读事件、写事件等,将这些事件触发到 PipeLine 中,由 PipeLine 管理的责任链来进行后续的处理。 ### 责任链层 Pipeline -它负责上述的各种网络事件 在责任链中的有序传播,同时负责动态地编排责任链。责任链可以选择监听和处理自己关心的事件,它可以拦截处理事件,以及向前向后传播事件。不同应用的 Handler节点 的功能也不同,通常情况下,往往会开发 编解码Hanlder 用于消息的编解码,可以将外部的协议消息转换成 内部的POJO对象,这样上层业务则只需要关心处理业务逻辑即可,不需要感知底层的协议差异和线程模型差异,实现了架构层面的分层隔离。 + +它负责上述的各种网络事件 在责任链中的有序传播,同时负责动态地编排责任链。责任链可以选择监听和处理自己关心的事件,它可以拦截处理事件,以及向前向后传播事件。不同应用的 Handler 节点 的功能也不同,通常情况下,往往会开发 编解码 Hanlder 用于消息的编解码,可以将外部的协议消息转换成 内部的 POJO 对象,这样上层业务则只需要关心处理业务逻辑即可,不需要感知底层的协议差异和线程模型差异,实现了架构层面的分层隔离。 ### 业务逻辑编排层 Service ChannelHandler -业务逻辑编排层通常有两类:一类是纯粹的业务逻辑编排,还有一类是其他的应用层协议插件,用于特定协议相关的会话和链路管理。例如,CMPP协议,用于管理和中国移动短信系统的对接。 -架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心责任链的拦截和 业务Handler 的编排。因为应用层协议栈往往是开发一次,到处运行,所以实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需关心业务逻辑定制。这种分层的架构设计理念实现了 NIO框架 各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制。 +业务逻辑编排层通常有两类:一类是纯粹的业务逻辑编排,还有一类是其他的应用层协议插件,用于特定协议相关的会话和链路管理。例如,CMPP 协议,用于管理和中国移动短信系统的对接。 + +架构的不同层面,需要关心和处理的对象都不同,通常情况下,对于业务开发者,只需要关心责任链的拦截和 业务 Handler 的编排。因为应用层协议栈往往是开发一次,到处运行,所以实际上对于业务开发者来说,只需要关心服务层的业务逻辑开发即可。各种应用协议以插件的形式提供,只有协议开发人员需要关注协议插件,对于其他业务开发人员来说,只需关心业务逻辑定制。这种分层的架构设计理念实现了 NIO 框架 各层之间的解耦,便于上层业务协议栈的开发和业务逻辑的定制。 正是由于 Netty 的分层架构设计非常合理,基于 Netty 的各种应用服务器和协议栈开发才能够如雨后春笋般得到快速发展。 ## 关键的架构质量属性 + ### 性能 -影响最终产品的性能因素非常多,其中软件因素如下。 + +影响最终产品的性能因素非常多,其中软件因素如下。 + - 架构不合理导致的性能问题; - 编码实现不合理导致的性能问题,例如,锁没用好导致的性能瓶颈。 -硬件因素如下。 +硬件因素如下。 + - 服务器硬件配置太低导致的性能问题; -- 带宽、磁盘的 IOPS 等限制导致的 IO操作 性能差; +- 带宽、磁盘的 IOPS 等限制导致的 IO 操作 性能差; - 测试环境被共用导致被测试的软件产品受到影响。 尽管影响产品性能的因素非常多,但是架构的性能模型合理与否对性能的影响非常大。如果一个产品的架构设计得不好,无论开发如何努力,都很难开发出一个高性能、高可用的软件产品。 -“性能是设计出来的,而不是测试出来的”。下面我们看看 Netty 的架构设计是如何实现高性能的。 -1. 采用非阻塞的 NIO类库,基于 Reactor 模式实现,解决了传统 同步阻塞IO模式 下一个服务端无法平滑地处理线性增长的客户端的问题。 +“性能是设计出来的,而不是测试出来的”。下面我们看看 Netty 的架构设计是如何实现高性能的。 + +1. 采用非阻塞的 NIO 类库,基于 Reactor 模式实现,解决了传统 同步阻塞 IO 模式 下一个服务端无法平滑地处理线性增长的客户端的问题。 2. TCP 接收和发送缓冲区**使用直接内存代替堆内存,避免了内存复制**,提升了 IO 读取和写入的性能。 3. 支持通过内存池的方式循环利用 ByteBuffer,避免了频繁创建和销毁 ByteBuffer 带来的性能损耗。 -4. 可配置的 IO线程数、TCP参数 等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。 +4. 可配置的 IO 线程数、TCP 参数 等,为不同的用户场景提供定制化的调优参数,满足不同的性能场景。 5. 采用环形数组缓冲区实现无锁化并发编程,代替传统的线程安全容器或者锁。 6. 合理地使用线程安全容器、原子类等,提升系统的并发处理能力。 -7. 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的 CPU资源消耗问题。 -8. 通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少了频繁 GC 带来的延时和 CPU损耗。 +7. 关键资源的处理使用单线程串行化的方式,避免多线程并发访问带来的锁竞争和额外的 CPU 资源消耗问题。 +8. 通过引用计数器及时地申请释放不再被引用的对象,细粒度的内存管理降低了 GC 的频率,减少了频繁 GC 带来的延时和 CPU 损耗。 ### 可靠性 -作为一个高性能的异步通信框架,架构的可靠性是大家选择的另一个重要依据。下面我们看一下 Netty架构 的可靠性设计。 + +作为一个高性能的异步通信框架,架构的可靠性是大家选择的另一个重要依据。下面我们看一下 Netty 架构 的可靠性设计。 **1、链路有效性检测** 由于长连接不需要每次发送消息都创建链路,也不需要在消息交互完成时关闭链路,因此相对于短连接性能更高。对于长连接,一旦链路建立成功便一直维系双方之间的链路,直到系统退出。 -为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用周期性心跳的原因是:在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙 Hang住,或者遭遇网络闪断、网络单通等,通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时,瞬间的海量业务冲击会导致消息积压无法发送给对方,由于链路的重建需要时间,这期间业务会大量失败 (集群或者分布式组网情况会好一些)。为了解决这个问题,需要周期性的 “心跳检测” 对链路进行有效性检查,一旦发生问题,可以及时关闭链路,重建 TCP连接。 +为了保证长连接的链路有效性,往往需要通过心跳机制周期性地进行链路检测。使用周期性心跳的原因是:在系统空闲时,例如凌晨,往往没有业务消息。如果此时链路被防火墙 Hang 住,或者遭遇网络闪断、网络单通等,通信双方无法识别出这类链路异常。等到第二天业务高峰期到来时,瞬间的海量业务冲击会导致消息积压无法发送给对方,由于链路的重建需要时间,这期间业务会大量失败 (集群或者分布式组网情况会好一些)。为了解决这个问题,需要周期性的 “心跳检测” 对链路进行有效性检查,一旦发生问题,可以及时关闭链路,重建 TCP 连接。 -当有业务消息时,无须心跳检测,可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。为了支持心跳机制,Netty 提供了如下两种链路空闲检测机制。 -- 读空闲超时机制:当经过 连续的周期 T 没有消息可读时,触发 超时Handler,用户可以基于 该读空闲超时Handler 发送心跳消息,进行链路检测,如果连续 N个周期 仍然没有读取到心跳消息,可以主动关闭这条链路。 -- 写空闲超时机制:当经过 连续的周期 T 没有消息要发送时,触发 超时Handler,用户可以基于 该写空闲超时Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有接收到对方的心跳消息,可以主动关闭这条链路。 +当有业务消息时,无须心跳检测,可以由业务消息进行链路可用性检测。所以心跳消息往往是在链路空闲时发送的。为了支持心跳机制,Netty 提供了如下两种链路空闲检测机制。 + +- 读空闲超时机制:当经过 连续的周期 T 没有消息可读时,触发 超时 Handler,用户可以基于 该读空闲超时 Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有读取到心跳消息,可以主动关闭这条链路。 +- 写空闲超时机制:当经过 连续的周期 T 没有消息要发送时,触发 超时 Handler,用户可以基于 该写空闲超时 Handler 发送心跳消息,进行链路检测,如果连续 N 个周期 仍然没有接收到对方的心跳消息,可以主动关闭这条链路。 为了满足不同用户场景的心跳定制,Netty 提供了空闲状态检测事件通知机制,用户可以订阅:空闲超时事件、读空闲超时机制、写空闲超时事件,在接收到对应的空闲事件之后,灵活地进行定制。 **2、内存保护机制** -Netty 提供多种机制对内存进行保护,包括以下几个方面。 +Netty 提供多种机制对内存进行保护,包括以下几个方面。 + - 通过对象引用计数器对 Netty 的 ByteBuffer 等内置对象进行细粒度的内存申请和释放,对非法的对象引用进行检测和保护。 - 通过内存池来重用 ByteBuffer,节省内存。 - 可设置的内存容量上限,包括 ByteBuffer、线程池线程数等。 ### 可定制性 -Netty 的可定制性主要体现在以下几点。 + +Netty 的可定制性主要体现在以下几点。 + - 责任链模式:ChannelPipeline 基于责任链模式开发,便于业务逻辑的拦截、定制和扩展。 - 基于接口的开发:关键的类库都提供了接口或者抽象类,如果 Netty 自身的实现无法满足用户的需求,可以由用户自定义实现相关接口。 - 提供了大量工厂类,通过重载这些工厂类可以按需创建出用户实现的对象。 - 提供了大量的系统参数供用户按需设置,增强系统的场景定制性。 ### 可扩展性 -基于 Netty 的 基本NIO框架,可以方便地进行应用层协议定制,例如,HTTP协议栈、Thrift协议栈、FTP协议栈 等。这些扩展不需要修改 Netty 的源码,直接基于 Netty 的二进制类库即可实现协议的扩展和定制。目前,业界存在大量的基于 Netty框架 开发的协议,例如基于 Netty 的 HTTP协议、Dubbo协议、RocketMQ内部私有协议 等。 \ No newline at end of file + +基于 Netty 的 基本 NIO 框架,可以方便地进行应用层协议定制,例如,HTTP 协议栈、Thrift 协议栈、FTP 协议栈 等。这些扩展不需要修改 Netty 的源码,直接基于 Netty 的二进制类库即可实现协议的扩展和定制。目前,业界存在大量的基于 Netty 框架 开发的协议,例如基于 Netty 的 HTTP 协议、Dubbo 协议、RocketMQ 内部私有协议 等。 diff --git "a/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\345\217\257\351\235\240\346\200\247\350\256\276\350\256\241.md" "b/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\345\217\257\351\235\240\346\200\247\350\256\276\350\256\241.md" index d0e8de79..c7764bbd 100644 --- "a/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\345\217\257\351\235\240\346\200\247\350\256\276\350\256\241.md" +++ "b/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\345\217\257\351\235\240\346\200\247\350\256\276\350\256\241.md" @@ -1 +1 @@ -努力编写中...... \ No newline at end of file +努力编写中...... diff --git "a/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\346\200\247\350\203\275\344\271\213\351\201\223.md" "b/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\346\200\247\350\203\275\344\271\213\351\201\223.md" index b98f50a1..bb4a2718 100644 --- "a/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\346\200\247\350\203\275\344\271\213\351\201\223.md" +++ "b/docs/Netty/AdvancedFeaturesOfNetty/Netty\351\253\230\346\200\247\350\203\275\344\271\213\351\201\223.md" @@ -1,71 +1,83 @@ -作为一个高性能的 NIO通信框架,Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO框架 之一,Netty 可以完全满足不同领域对高性能通信的需求。本章我们将从架构层对 Netty 的高性能设计和关键代码实现进行剖析,看 Netty 是如何支撑高性能网络通信的。 +作为一个高性能的 NIO 通信框架,Netty 被广泛应用于大数据处理、互联网消息中间件、游戏和金融行业等。大多数应用场景对底层的通信框架都有很高的性能要求,作为综合性能最高的 NIO 框架 之一,Netty 可以完全满足不同领域对高性能通信的需求。本章我们将从架构层对 Netty 的高性能设计和关键代码实现进行剖析,看 Netty 是如何支撑高性能网络通信的。 ## RPC 调用性能模型分析 + ### 传统 RPC 调用性能差的原因 + **一、网络传输方式问题。** -传统的 RPC框架 或者基于 RMI 等方式的 远程过程调用 采用了同步阻塞I/O,当客户端的并发压力或者网络时延增大之后,同步阻塞I/O 会由于频繁的 wait 导致 I/O线程 经常性的阻塞,由于线程无法高效的工作,I/O 处理能力自然下降。 +传统的 RPC 框架 或者基于 RMI 等方式的 远程过程调用 采用了同步阻塞 I/O,当客户端的并发压力或者网络时延增大之后,同步阻塞 I/O 会由于频繁的 wait 导致 I/O 线程 经常性的阻塞,由于线程无法高效的工作,I/O 处理能力自然下降。 -采用 BIO通信模型 的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,接收到客户端连接之后,为其创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的 “ 一请求,一应答 ” 模型。该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是 Java虛拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。 +采用 BIO 通信模型 的服务端,通常由一个独立的 Acceptor 线程 负责监听客户端的连接,接收到客户端连接之后,为其创建一个新的线程处理请求消息,处理完成之后,返回应答消息给客户端,线程销毁,这就是典型的 “ 一请求,一应答 ” 模型。该架构最大的问题就是不具备弹性伸缩能力,当并发访问量增加后,服务端的线程个数和并发访问数成线性正比,由于线程是 Java 虛拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能急剧下降,随着并发量的继续增加,可能会发生句柄溢出、线程堆栈溢出等问题,并导致服务器最终宕机。 **二、序列化性能差。** -Java序列化 存在如下几个典型问题: -1. Java 序列化机制是 Java 内部的一 种对象编解码技术,无法跨语言使用。例如对于异构系统之间的对接,Java序列化 后的码流需要能够通过其他语言反序列化成原始对象,这很难支持。 -2. 相比于其他开源的序列化框架,Java序列化 后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。 +Java 序列化 存在如下几个典型问题: + +1. Java 序列化机制是 Java 内部的一 种对象编解码技术,无法跨语言使用。例如对于异构系统之间的对接,Java 序列化 后的码流需要能够通过其他语言反序列化成原始对象,这很难支持。 +2. 相比于其他开源的序列化框架,Java 序列化 后的码流太大,无论是网络传输还是持久化到磁盘,都会导致额外的资源占用。 3. 序列化性能差,资源占用率高 ( 主要是 CPU 资源占用高 )。 **三、线程模型问题。** -由于采用 同步阻塞I/O,这会导致每个 TCP连接 都占用 1 个线程,由于线程资源是 JVM虚拟机 非常宝贵的资源,当 I/O 读写阻塞导致线程无法及时释放时,会导致系统性能急剧下降,严重的甚至会导致虚拟机无法创建新的线程。 +由于采用 同步阻塞 I/O,这会导致每个 TCP 连接 都占用 1 个线程,由于线程资源是 JVM 虚拟机 非常宝贵的资源,当 I/O 读写阻塞导致线程无法及时释放时,会导致系统性能急剧下降,严重的甚至会导致虚拟机无法创建新的线程。 ### IO 通信性能三原则 -尽管影响 I/O通信性能 的因素非常多,但是从架构层面看主要有三个要素。 -1. 传输:用什么样的通道将数据发送给对方。可以选择 BIO、NIO 或者 AIO,I/O模型 在很大程度上决定了通信的性能; -2. 协议:采用什么样的通信协议,HTTP等公有协议或者内部私有协议。协议的选择不同,性能也不同。相比于公有协议,内部私有协议的性能通常可以被设计得更优; + +尽管影响 I/O 通信性能 的因素非常多,但是从架构层面看主要有三个要素。 + +1. 传输:用什么样的通道将数据发送给对方。可以选择 BIO、NIO 或者 AIO,I/O 模型 在很大程度上决定了通信的性能; +2. 协议:采用什么样的通信协议,HTTP 等公有协议或者内部私有协议。协议的选择不同,性能也不同。相比于公有协议,内部私有协议的性能通常可以被设计得更优; 3. 线程模型:数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,Reactor 线程模型的不同,对性能的影响也非常大。 ## 异步非阻塞通信 -在 I/O编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O多路复用技术 进行处理。I/O多路复用技术 通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程 / 多进程模型比,I/O多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。 -JDK1.4 提供了对非阻塞 I/O 的支持,JDK1.5 使用 epoll 替代了传统的 select / poll,极大地提升了 NIO通信 的性能。 +在 I/O 编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 I/O 多路复用技术 进行处理。I/O 多路复用技术 通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程 / 多进程模型比,I/O 多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。 + +JDK1.4 提供了对非阻塞 I/O 的支持,JDK1.5 使用 epoll 替代了传统的 select / poll,极大地提升了 NIO 通信 的性能。 -与 Socket 和 ServerSocket 类相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择 同步阻塞I/O 以降低编程复杂度。但是对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。 +与 Socket 和 ServerSocket 类相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员一般可以根据自己的需要来选择合适的模式,一般来说,低负载、低并发的应用程序可以选择 同步阻塞 I/O 以降低编程复杂度。但是对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。 -Netty 的 I/O 线程 NioEventLoop 由于聚合了 多路复用器Selector,可以同时并发处理成百上千个客户端 SocketChannel。由于读写操作都是非阻塞的,这就可以充分提升 I/O线程 的运行效率,避免由频繁的 I/O阻塞 导致的线程挂起。另外,由于 Netty 采用了异步通信模式,一个 I/O线程 可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统 同步阻塞I/O “ 一连接,一线程 ” 模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 +Netty 的 I/O 线程 NioEventLoop 由于聚合了 多路复用器 Selector,可以同时并发处理成百上千个客户端 SocketChannel。由于读写操作都是非阻塞的,这就可以充分提升 I/O 线程 的运行效率,避免由频繁的 I/O 阻塞 导致的线程挂起。另外,由于 Netty 采用了异步通信模式,一个 I/O 线程 可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统 同步阻塞 I/O “ 一连接,一线程 ” 模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 ## 高效的 Reactor 线程模型 -常用的 Reactor线程模型 有三种,分别如下。 -1. Reactor单线程模型; + +常用的 Reactor 线程模型 有三种,分别如下。 + +1. Reactor 单线程模型; 2. Reactor 多线程模型; -3. 主从Reactor多线程模型。 +3. 主从 Reactor 多线程模型。 + +Reactor 单线程模型,指的是所有的 I/O 操作 都在同一个 NIO 线程 上面完成,NIO 线程 的职责如下: -Reactor单线程模型,指的是所有的 I/O操作 都在同一个 NIO线程 上面完成,NIO线程 的职责如下: -1. 作为 NIO服务端,接收客户端的 TCP连接; -2. 作为 NIO客户端,向服务端发起 TCP连接; +1. 作为 NIO 服务端,接收客户端的 TCP 连接; +2. 作为 NIO 客户端,向服务端发起 TCP 连接; 3. 读取通信对端的请求或者应答消息; 4. 向通信对端发送消息请求或者应答消息。 -由于 Reactor模式 使用的是 异步非阻塞I/O,所有的 I/O操作 都不会导致阻塞,理论上一个线程可以独立处理所有 I/O 相关的操作。从架构层面看,一个 NIO线程 确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户Handler 可以通过 NIO线程 将消息发送给客户端。 +由于 Reactor 模式 使用的是 异步非阻塞 I/O,所有的 I/O 操作 都不会导致阻塞,理论上一个线程可以独立处理所有 I/O 相关的操作。从架构层面看,一个 NIO 线程 确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler 上进行消息解码。用户 Handler 可以通过 NIO 线程 将消息发送给客户端。 + +对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下。 + +1. 一个 NIO 线程 同时处理成百上千的链路,性能上无法支撑。即便 NIO 线程 的 CPU 负荷 达到 100%,也无法满足海量消息的编码,解码、读取和发送; +2. 当 NIO 线程 负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程 的负载,最终会导致大量消息积压和处理超时,NIO 线程 会成为系统的性能瓶颈; +3. 可靠性问题。一旦 NIO 线程 意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。 -对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下。 -1. 一个 NIO线程 同时处理成百上千的链路,性能上无法支撑。即便 NIO线程 的 CPU负荷 达到 100%,也无法满足海量消息的编码,解码、读取和发送; -2. 当 NIO线程 负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO线程 的负载,最终会导致大量消息积压和处理超时,NIO线程 会成为系统的性能瓶颈; -3. 可靠性问题。一旦 NIO线程 意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。 +为了解决这些问题,演进出了 Reactor 多线程模型,下面我们看一下 Reactor 多线程模型。 -为了解决这些问题,演进出了 Reactor多线程模型,下面我们看一下 Reactor多线程模型。 +Rector 多线程模型 与单线程模型最大的区别就是有一组 NIO 线程 处理 I/O 操作,它的特点如下。 -Rector多线程模型 与单线程模型最大的区别就是有一组 NIO线程 处理 I/O操作,它的特点如下。 -1. 有一个专门的 NIO线程 —— Acceptor线程 用于监听服务端口,接收客户端的 TCP连接请求; -2. 网络IO操作 —— 读、写等由一个 NIO线程池 负责,线程池可以采用标准的 JDK线程池 实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO线程 负责消息的读取、解码、编码和发送; -3. 1 个 NIO线程 可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO线程,以防止发生并发操作问题。 +1. 有一个专门的 NIO 线程 —— Acceptor 线程 用于监听服务端口,接收客户端的 TCP 连接请求; +2. 网络 IO 操作 —— 读、写等由一个 NIO 线程池 负责,线程池可以采用标准的 JDK 线程池 实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程 负责消息的读取、解码、编码和发送; +3. 1 个 NIO 线程 可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,以防止发生并发操作问题。 -在绝大多数场景下,Reactor多线程模型 都可以满足性能需求,但是,在极特殊应用场景中,一个 NIO线程 负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手消息进行安全认证,认证本身非常损耗性能。在这类场景下,单独一个 Acceptor线程 可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor线程模型 —— 主从Reactor多线程模型。 +在绝大多数场景下,Reactor 多线程模型 都可以满足性能需求,但是,在极特殊应用场景中,一个 NIO 线程 负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手消息进行安全认证,认证本身非常损耗性能。在这类场景下,单独一个 Acceptor 线程 可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor 线程模型 —— 主从 Reactor 多线程模型。 -主从Reactor线程模型 的特点是,服务端用于接收客户端连接的不再是个单线程的连接处理Acceptor,而是一个独立的 Acceptor线程池。Acceptor 接收到客户端 TCP连接请求 处理完成后 ( 可能包含接入认证等 ),将新创建的 SocketChannel 注册到 I/O处理线程池 的某个I/O线程 上,由它负责 SocketChannel 的读写和编解码工作。Acceptor线程池 只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到 I/O处理线程池的 I/O线程 上,每个 I/O线程 可以同时监听 N 个链路,对链路产生的 IO事件 进行相应的 消息读取、解码、编码及消息发送等操作。 +主从 Reactor 线程模型 的特点是,服务端用于接收客户端连接的不再是个单线程的连接处理 Acceptor,而是一个独立的 Acceptor 线程池。Acceptor 接收到客户端 TCP 连接请求 处理完成后 ( 可能包含接入认证等 ),将新创建的 SocketChannel 注册到 I/O 处理线程池 的某个 I/O 线程 上,由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池 只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到 I/O 处理线程池的 I/O 线程 上,每个 I/O 线程 可以同时监听 N 个链路,对链路产生的 IO 事件 进行相应的 消息读取、解码、编码及消息发送等操作。 -利用 主从Reactor线程模型,可以解决 1 个 Acceptor线程 无法有效处理所有客户端连接的性能问题。因此,Netty官方 也推荐使用该线程模型。 +利用 主从 Reactor 线程模型,可以解决 1 个 Acceptor 线程 无法有效处理所有客户端连接的性能问题。因此,Netty 官方 也推荐使用该线程模型。 -事实上,Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup实例 并进行适当的参数配置,就可以支持上述三种 Reactor线程模型。可以根据业务场景的性能诉求,选择不同的线程模型。 +事实上,Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup 实例 并进行适当的参数配置,就可以支持上述三种 Reactor 线程模型。可以根据业务场景的性能诉求,选择不同的线程模型。 Netty 单线程模型 服务端代码示例如下。 + ```java EventLoopGroup reactor = new NioEventLoopGroup(1); ServerBootstrap bootstrap = new ServerBootstrap(); @@ -75,6 +87,7 @@ Netty 单线程模型 服务端代码示例如下。 ``` Netty 多线程模型 代码示例如下。. + ```java EventLoopGroup acceptor = new NioEventLoopGroup(1); EventLoopGroup ioGroup = new NioEventLoopGroup(); @@ -83,7 +96,9 @@ Netty 多线程模型 代码示例如下。. .channel(NioServerSocketChannel.class) ...... ``` + Netty 主从多线程模型 代码示例如下。 + ```java EventLoopGroup acceptorGroup = new NioEventLoopGroup(); EventLoopGroup ioGroup = new NioEventLoopGroup(); @@ -94,48 +109,60 @@ Netty 主从多线程模型 代码示例如下。 ``` ## 无锁化的串行设计 + 在大多数场景下,并行多线程处理可以提升系统的并发性能。但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会导致性能的下降。为了尽可能地避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。 -为了尽可能提升性能,Netty 对消息的处理 采用了串行无锁化设计,在 I/O线程 内部进行串行操作,避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。 +为了尽可能提升性能,Netty 对消息的处理 采用了串行无锁化设计,在 I/O 线程 内部进行串行操作,避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。 -![avatar](../../../images/Netty/Netty串行化设计工作原理.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty串行化设计工作原理.png) -Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。 +Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的 Handler,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。 ## 零拷贝 -Netty的 “ 零拷贝 ” 主要体现在如下三个方面。 -第一种情况。Netty 的接收和发送 ByteBuffer 采用 堆外直接内存 (DIRECT BUFFERS) 进行 Socket读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的 堆内存(HEAP BUFFERS) 进行 Socket读写,JVM 会将 堆内存Buffer 拷贝一份到 直接内存 中,然后才写入 Socket。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。 +Netty 的 “ 零拷贝 ” 主要体现在如下三个方面。 -下面我们继续看第二种 “ 零拷贝 ” 的实现 CompositeByteBuf,它对外将多个 ByteBuf 封装成一个 ByteBuf,对外提供统一封装后的 ByteBuf接口。CompositeByteBuf 实际就是个 ByteBuf 的装饰器,它将多个 ByteBuf 组合成一个集合,然后对外提供统一的 ByteBuf接口,添加 ByteBuf,不需要做内存拷贝。 +第一种情况。Netty 的接收和发送 ByteBuffer 采用 堆外直接内存 (DIRECT BUFFERS) 进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的 堆内存(HEAP BUFFERS) 进行 Socket 读写,JVM 会将 堆内存 Buffer 拷贝一份到 直接内存 中,然后才写入 Socket。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。 + +下面我们继续看第二种 “ 零拷贝 ” 的实现 CompositeByteBuf,它对外将多个 ByteBuf 封装成一个 ByteBuf,对外提供统一封装后的 ByteBuf 接口。CompositeByteBuf 实际就是个 ByteBuf 的装饰器,它将多个 ByteBuf 组合成一个集合,然后对外提供统一的 ByteBuf 接口,添加 ByteBuf,不需要做内存拷贝。 第三种 “ 零拷贝 ” 就是文件传输,Netty 文件传输类 DefaultFileRegion 通过 transferTo()方法 将文件发送到目标 Channel 中。很多操作系统直接将文件缓冲区的内容发送到目标 Channel 中,而不需要通过循环拷贝的方式,这是一种更加高效的传输方式,提升了传输性能,降低了 CPU 和内存占用,实现了文件传输的 “ 零拷贝 ” 。 ## 内存池 -随着 JVM虚拟机 和 JIT即时编译技术 的发展,对象的分配和回收是个非常轻量级的工作。但是对于 缓冲区Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。ByteBuf 的子类中提供了多种 PooledByteBuf 的实现,基于这些实现 Netty 提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。 + +随着 JVM 虚拟机 和 JIT 即时编译技术 的发展,对象的分配和回收是个非常轻量级的工作。但是对于 缓冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。ByteBuf 的子类中提供了多种 PooledByteBuf 的实现,基于这些实现 Netty 提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。 ## Socket 与 SocketChannel -网络由下往上分为 物理层、数据链路层、网络层、传输层和应用层。IP协议 对应于网络层,TCP协议 对应于传输层,而 HTTP协议 对应于应用层,三者从本质上来说没有可比性,Socket 则是对 TCP/IP协议 的封装和应用 (程序员层面上)。也可以说,TPC/IP协议 是传输层协议,主要解决数据如何在网络中传输,而 HTTP 是应用层协议,主要解决如何包装数据。Socket 是对 TCP/IP协议 的封装,Socket 本身并不是协议,而是一个 调用接口(API)。 通过 Socket,我们才能使用 TCP/IP协议。 + +网络由下往上分为 物理层、数据链路层、网络层、传输层和应用层。IP 协议 对应于网络层,TCP 协议 对应于传输层,而 HTTP 协议 对应于应用层,三者从本质上来说没有可比性,Socket 则是对 TCP/IP 协议 的封装和应用 (程序员层面上)。也可以说,TPC/IP 协议 是传输层协议,主要解决数据如何在网络中传输,而 HTTP 是应用层协议,主要解决如何包装数据。Socket 是对 TCP/IP 协议 的封装,Socket 本身并不是协议,而是一个 调用接口(API)。 通过 Socket,我们才能使用 TCP/IP 协议。 + ### 一、利用 Socket 建立网络连接的步骤 -建立 Socket连接 至少需要一对套接字,其中一个运行于客户端,称为 clientSocket ,另一个运行于服务器端,称为 serverSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。 + +建立 Socket 连接 至少需要一对套接字,其中一个运行于客户端,称为 clientSocket ,另一个运行于服务器端,称为 serverSocket 。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。 + 1. 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。 2. 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。 3. 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给 客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。 -### 二、HTTP连接 的特点 -  HTTP协议 是 Web联网 的基础,也是手机联网常用的协议之一,HTTP协议 是建立在 TCP协议 之上的一种应用。HTTP连接 最显著的特点是客户端发送的每次请求 都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为 “一次连接”。 + +### 二、HTTP 连接 的特点 + +HTTP 协议 是 Web 联网 的基础,也是手机联网常用的协议之一,HTTP 协议 是建立在 TCP 协议 之上的一种应用。HTTP 连接 最显著的特点是客户端发送的每次请求 都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为 “一次连接”。 + ### 三、TCP 和 UDP 的区别 + 1. TCP 是面向连接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但 TCP 的三次握手在很大程度上 保证了连接的可靠性。而 UDP 不是面向连接的,UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说 UDP 是无连接的、不可靠的一种数据传输协议。 2. 也正由于 1 所说的特点,使得 UDP 的开销更小,数据传输速率更高,因为不必进行收发数据的确认,所以 UDP 的实时性更好。 ### 四、Socket 与 SocketChannel 有什么区别 Socket、SocketChannel 二者的实质都是一样的,都是为了实现客户端与服务器端的连接而存在的,但是在使用上却有很大的区别。具体如下: -1. 所属包不同。Socket 在 java.net包 中,而 SocketChannel 在 java.nio包 中。 -2. 异步方式不同。从包的不同,我们大体可以推断出他们主要的区别:Socket 是阻塞连接,SocketChannel 可以设置为非阻塞连接。使用 ServerSocket 与 Socket 的搭配,服务端Socket 往往要为每一个 客户端Socket 分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中。过多的线程也会影响服务器的性能。而使用 SocketChannel 与 ServerSocketChannel 的搭配可以非阻塞通信,这样使得服务器端只需要一个线程就能处理所有 客户端Socket 的请求。 + +1. 所属包不同。Socket 在 java.net 包 中,而 SocketChannel 在 java.nio 包 中。 +2. 异步方式不同。从包的不同,我们大体可以推断出他们主要的区别:Socket 是阻塞连接,SocketChannel 可以设置为非阻塞连接。使用 ServerSocket 与 Socket 的搭配,服务端 Socket 往往要为每一个 客户端 Socket 分配一个线程,而每一个线程都有可能处于长时间的阻塞状态中。过多的线程也会影响服务器的性能。而使用 SocketChannel 与 ServerSocketChannel 的搭配可以非阻塞通信,这样使得服务器端只需要一个线程就能处理所有 客户端 Socket 的请求。 3. 性能不同。一般来说,高并发场景下,使用 SocketChannel 与 ServerSocketChannel 的搭配会有更好的性能。 -4. 使用方式不同。Socket、ServerSocket类 可以传入不同参数直接实例化对象并绑定 IP 和 端口。而 SocketChannel、ServerSocketChannel类 需要借助 Selector类。 +4. 使用方式不同。Socket、ServerSocket 类 可以传入不同参数直接实例化对象并绑定 IP 和 端口。而 SocketChannel、ServerSocketChannel 类 需要借助 Selector 类。 -下面是 SocketChannel方式 需要用到的几个核心类: +下面是 SocketChannel 方式 需要用到的几个核心类: ServerSocketChannel:ServerSocket 的替代类, 支持阻塞通信与非阻塞通信。 @@ -143,7 +170,8 @@ SocketChannel:Socket 的替代类, 支持阻塞通信与非阻塞通信。 Selector:为 ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器读就绪和写就绪事件。 -SelectionKey:代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄。当一个 SelectionKey对象 位于 Selector对象 的 selected-keys集合 中时,就表示与这个 SelectionKey对象 相关的事件发生了。在 SelectionKey类 中有如下几个静态常量: +SelectionKey:代表 ServerSocketChannel 及 SocketChannel 向 Selector 注册事件的句柄。当一个 SelectionKey 对象 位于 Selector 对象 的 selected-keys 集合 中时,就表示与这个 SelectionKey 对象 相关的事件发生了。在 SelectionKey 类 中有如下几个静态常量: + - SelectionKey.OP_ACCEPT,客户端连接就绪事件,等于监听 serverSocket.accept(),返回一个 socket。 - SelectionKey.OP_CONNECT,准备连接服务器就绪,跟上面类似,只不过是对于 socket 的,相当于监听了 socket.connect()。 - SelectionKey.OP_READ,读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作了。 diff --git "a/docs/Netty/IOTechnologyBase/IO\346\250\241\345\236\213.md" "b/docs/Netty/IOTechnologyBase/IO\346\250\241\345\236\213.md" index cf5002e6..b3951a76 100644 --- "a/docs/Netty/IOTechnologyBase/IO\346\250\241\345\236\213.md" +++ "b/docs/Netty/IOTechnologyBase/IO\346\250\241\345\236\213.md" @@ -1,57 +1,68 @@ ## Linux 网络 IO 模型简介 -Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个fd (file descriptor,文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。根据UNIX网络编程对 I/O模型 的分类,UNIX 提供了5种 I/O模型,分别如下。 -#### 1、阻塞IO模型 +Linux 的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个 fd (file descriptor,文件描述符)。而对一个 socket 的读写也会有相应的描述符,称为 socket fd (socket 描述符),描述符就是一个数字,它指向内核中的一个结构体(文件路径,数据区等一些属性)。根据 UNIX 网络编程对 I/O 模型 的分类,UNIX 提供了 5 种 I/O 模型,分别如下。 + +#### 1、阻塞 IO 模型 + 在内核将数据准备好之前,系统调用会一直等待所有的套接字(Socket)传来数据,默认的是阻塞方式。 -![avatar](../../../images/Netty/阻塞IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/阻塞IO模型.png) + +Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom 方法,OS 会判断来自网络的数据报是否准备好,当数据报准备好了之后,OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。 -Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom方法,OS 会判断来自网络的数据报是否准备好,当数据报准备好了之后,OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。 +BIO 中的阻塞,就是阻塞在 2 个地方: -BIO中的阻塞,就是阻塞在2个地方: 1. OS 等待数据报通过网络发送过来,如果建立连接后数据一直没过来,就会白白浪费线程的资源; 2. 将数据从内核空间拷贝到用户空间。 -在这2个时候,我们的线程会一直被阻塞,啥事情都不干。 -#### 2、非阻塞IO模型 +在这 2 个时候,我们的线程会一直被阻塞,啥事情都不干。 -![avatar](../../../images/Netty/非阻塞IO模型.png) +#### 2、非阻塞 IO 模型 -每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于CPU来说是较大的浪费,一般只有在特定的场景下才使用。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/非阻塞IO模型.png) -从图中可以看到,非阻塞IO 的 recvfrom调用 会立即得到一个返回结果(数据报是否准备好),我们可以根据返回结果继续执行不同的逻辑。而阻塞IO 的recvfrom调用,如果无数据报准备好,一定会被阻塞住。虽然 非阻塞IO 比 阻塞IO 少了一段阻塞的过程,但事实上 非阻塞IO 这种方式也是低效的,因为我们不得不使用轮询方法区一直问 OS:“我的数据好了没啊”。 +每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。 + +从图中可以看到,非阻塞 IO 的 recvfrom 调用 会立即得到一个返回结果(数据报是否准备好),我们可以根据返回结果继续执行不同的逻辑。而阻塞 IO 的 recvfrom 调用,如果无数据报准备好,一定会被阻塞住。虽然 非阻塞 IO 比 阻塞 IO 少了一段阻塞的过程,但事实上 非阻塞 IO 这种方式也是低效的,因为我们不得不使用轮询方法区一直问 OS:“我的数据好了没啊”。 **BIO 不会在 拷贝数据之前 阻塞,但会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方,Non-Blocking 还是会阻塞的。** -#### 3、IO复用模型 -Linux 提供 select/poll,进程通过将一个或多个 fd 传递给 select 或 poll系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限,因此它的使用也受到了一些制约。Linux 还提供了一个 epoll系统调用,epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。 -![avatar](../../../images/Netty/IO复用模型.png) +#### 3、IO 复用模型 + +Linux 提供 select/poll,进程通过将一个或多个 fd 传递给 select 或 poll 系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限,因此它的使用也受到了一些制约。Linux 还提供了一个 epoll 系统调用,epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/IO复用模型.png) -#### 4、信号驱动IO模型 -首先开启套接口信号驱动IO功能,并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。 +#### 4、信号驱动 IO 模型 -![avatar](../../../images/Netty/信号驱动IO模型.png) +首先开启套接口信号驱动 IO 功能,并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。 -#### 5、异步IO模型 -告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动IO 由内核通知我们何时可以开始一个 IO 操作;异步IO模型 由内核通知我们 IO操作何时已经完成。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/信号驱动IO模型.png) -![avatar](../../../images/Netty/异步IO模型.png) +#### 5、异步 IO 模型 -从这五种 IO模型的结构 也可以看出,阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。 +告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动 IO 由内核通知我们何时可以开始一个 IO 操作;异步 IO 模型 由内核通知我们 IO 操作何时已经完成。 -最后,我们看一下数据从客户端到服务器,再由服务器返回结果数据的整体IO流程,以便我们更好地理解上述的IO模型。 -![avatar](../../../images/Netty/数据在客户端及服务器之间的整体IO流程.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/异步IO模型.png) + +从这五种 IO 模型的结构 也可以看出,阻塞程度:阻塞 IO>非阻塞 IO>多路转接 IO>信号驱动 IO>异步 IO,效率是由低到高的。 + +最后,我们看一下数据从客户端到服务器,再由服务器返回结果数据的整体 IO 流程,以便我们更好地理解上述的 IO 模型。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/数据在客户端及服务器之间的整体IO流程.png) ## IO 多路复用技术 -Java NIO 的核心类库中 多路复用器Selector 就是基于 epoll 的多路复用技术实现。 -在 IO编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO多路复用技术 进行处理。IO多路复用技术 通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,IO多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源,IO多路复用 的主要应用场景如下。 +Java NIO 的核心类库中 多路复用器 Selector 就是基于 epoll 的多路复用技术实现。 + +在 IO 编程 过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者 IO 多路复用技术 进行处理。IO 多路复用技术 通过把多个 IO 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程/多进程模型比,IO 多路复用 的最大优势是系统开销小,系统不需要创建新的额外进程或线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源,IO 多路复用 的主要应用场景如下。 + - 服务器需要同时处理多个处于监听状态或者多个连接状态的套接字; - 服务器需要同时处理多种网络协议的套接字。 -目前支持 IO多路复用 的系统调用有 select、pselect、poll、epoll,在 Linux网络编程 过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点,epoll 作了很多重大改进,现总结如下。 -1. 支持一个进程打开的 socket描述符 (fd) 不受限制(仅受限于操作系统的最大文件句柄数); -2. IO效率 不会随着 FD 数目的增加而线性下降; -3. epoll的API更加简单。 +目前支持 IO 多路复用 的系统调用有 select、pselect、poll、epoll,在 Linux 网络编程 过程中,很长一段时间都使用 select 做轮询和网络事件通知,然而 select 的一些固有缺陷导致了它的应用受到了很大的限制,最终 Linux 选择了 epoll。epoll 与 select 的原理比较类似,为了克服 select 的缺点,epoll 作了很多重大改进,现总结如下。 + +1. 支持一个进程打开的 socket 描述符 (fd) 不受限制(仅受限于操作系统的最大文件句柄数); +2. IO 效率 不会随着 FD 数目的增加而线性下降; +3. epoll 的 API 更加简单。 -值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。 \ No newline at end of file +值得说明的是,用来克服 select/poll 缺点的方法不只有 epoll, epoll 只是一种 Linux 的实现方案。 diff --git "a/docs/Netty/IOTechnologyBase/Selector\343\200\201SelectionKey\345\217\212Channel\347\273\204\344\273\266.md" "b/docs/Netty/IOTechnologyBase/Selector\343\200\201SelectionKey\345\217\212Channel\347\273\204\344\273\266.md" new file mode 100644 index 00000000..89d63c29 --- /dev/null +++ "b/docs/Netty/IOTechnologyBase/Selector\343\200\201SelectionKey\345\217\212Channel\347\273\204\344\273\266.md" @@ -0,0 +1,311 @@ +Selector、SelectionKey 和 Channel 这三个组件构成了 Java nio 包的核心,也是 Reactor 模型在代码层面的体现。Selector 能让单线程同时处理多个客户端 Channel,非常适用于高并发,传输数据量较小的场景。要使用 Selector,首先要将对应的 Channel 及 IO 事件(读、写、连接)注册到 Selector,注册后会产生一个 SelectionKey 对象,用于关联 Selector 和 Channel,及后续的 IO 事件处理。这三者的关系如下图所示。 + +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Selector和SelectionKey和Channel关系图.png) + +对 nio 编程不熟的同学可以搜索一些简单的 demo 跑一下,下面 我们直接进入源码,窥探一些 nio 的奥秘。 + +### Selector + +其实,不管是 Selector 还是 SelectionKey 的源码,其具体实现类都是依赖于底层操作系统的,这里我们只看一下抽象类 Selector 的源码,日后有时间,再找一些具体的实现类深入分析一下。 + +```java +public abstract class Selector implements Closeable { + + protected Selector() { } + + /** + * 获取一个 Selector对象,具体实现依赖于底层操作系统 + */ + public static Selector open() throws IOException { + return SelectorProvider.provider().openSelector(); + } + + /** + * 判断该 Selector 是否已开启 + */ + public abstract boolean isOpen(); + + /** + * 当前所有向Selector注册的Channel 所对应的SelectionKey的集合 + */ + public abstract Set keys(); + + /** + * 相关事件已经被 Selector 捕获的 SelectionKey的集合 + */ + public abstract Set selectedKeys(); + + /** + * 阻塞到至少有一个通道在你注册的事件上就绪了 + */ + public abstract int select() throws IOException; + + /** + * 和select()一样,除了最长会阻塞timeout毫秒 + */ + public abstract int select(long timeout) throws IOException; + + /** + * 此方法执行非阻塞的选择操作,如果自从上一次选择操作后, + * 没有通道变成可选择的,则此方法直接返回 0 + */ + public abstract int selectNow() throws IOException; + + /** + * 用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效 + * 通道本身并不会关闭 + */ + public abstract void close() throws IOException; +} +``` + +### SelectionKey + +表示 SelectableChannel 在 Selector 中的注册的标记 / 句柄。 + +```java +public abstract class SelectionKey { + + protected SelectionKey() { } + + + // -- Channel and selector operations -- + + /** + * 获取该 SelectionKey 对应的Channel,Channel注册到Selector时会产生该 SelectionKey对象 + */ + public abstract SelectableChannel channel(); + + /** + * 获取该 SelectionKey 对应的 Selector + */ + public abstract Selector selector(); + + /** + * 该 SelectionKey 是否是有效的 + */ + public abstract boolean isValid(); + + // ------ Operation-set accessors ------ + + /** + * 获取该 SelectionKey 的兴趣事件 (既 SelectionKey 的4个 事件静态常量) + */ + public abstract int interestOps(); + + /** + * 设置该 SelectionKey 的兴趣事件 + */ + public abstract SelectionKey interestOps(int ops); + + /** + * 获取该 SelectionKey 的已操作集 + */ + public abstract int readyOps(); + + + // ------ Operation bits and bit-testing convenience methods ------ + + /** + * channel中的数据是否已经可以读取 + */ + public static final int OP_READ = 1 << 0; + + /** + * channel是否可以开始写入数据 + */ + public static final int OP_WRITE = 1 << 2; + + /** + * channel是否已经建立连接 + */ + public static final int OP_CONNECT = 1 << 3; + + /** + * ServerSocketChannel 是否可以与客户端建立连接 + */ + public static final int OP_ACCEPT = 1 << 4; + + /** + * channel是否可读 + */ + public final boolean isReadable() { + return (readyOps() & OP_READ) != 0; + } + + /** + * channel是否可写 + */ + public final boolean isWritable() { + return (readyOps() & OP_WRITE) != 0; + } + + /** + * channel是否建立连接 + */ + public final boolean isConnectable() { + return (readyOps() & OP_CONNECT) != 0; + } + + /** + * ServerSocketChannel是否可与客户端channel建立连接 + */ + public final boolean isAcceptable() { + return (readyOps() & OP_ACCEPT) != 0; + } +} +``` + +### Channel 组件 + +平时编码用的比较多的就是 SocketChannel 和 ServerSocketChannel,而将 Channel 与 Selecor 关联到一起的核心 API 则定义在它们的公共父类 SelectableChannel 中,整个 Channel 组件的核心类图如下所示。 + +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Channel组件.png) + +#### SelectableChannel + +```java +public abstract class SelectableChannel extends AbstractInterruptibleChannel implements Channel { + + protected SelectableChannel() { } + + /** + * 当前channel是否注册到了某个selector上,新创建的channel都是未注册状态 + */ + public abstract boolean isRegistered(); + + /** + * 根据给定的 Selector,获取本channel注册上去的 SelectionKey + */ + public abstract SelectionKey keyFor(Selector sel); + + /** + * 将当前channel及关注的事件,注册到Selector上,返回一个 SelectionKey + */ + public final SelectionKey register(Selector sel, int ops) throws ClosedChannelException { + return register(sel, ops, null); + } + + public abstract SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException; + + /** + * 设置该channel的阻塞模式,默认为 true阻塞 + */ + public abstract SelectableChannel configureBlocking(boolean block) throws IOException; + + /** + * 是否为阻塞IO模式 + */ + public abstract boolean isBlocking(); +} +``` + +#### ServerSocketChannel + +相当于 BIO 中的 ServerSocket,主要用于服务端与客户端建立连接通信的 channel。 + +```java +public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel { + + protected ServerSocketChannel(SelectorProvider provider) { + super(provider); + } + + /** + * 获取一个 ServerSocketChannel实例,具体实现依赖底层操作系统 + */ + public static ServerSocketChannel open() throws IOException { + return SelectorProvider.provider().openServerSocketChannel(); + } + + // -- ServerSocket-specific operations -- + + /** + * 绑定ip地址及要监听的端口 + */ + public final ServerSocketChannel bind(SocketAddress local) throws IOException { + return bind(local, 0); + } + + public abstract ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException; + + /** + * 与一个客户端channel建立连接,返回该客户端的存根 SocketChannel + */ + public abstract SocketChannel accept() throws IOException; +} +``` + +#### SocketChannel + +相当于 BIO 中的 Socket,主要用于通信双方的读写操作。 + +```java +public abstract class SocketChannel extends AbstractSelectableChannel + implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel { + + protected SocketChannel(SelectorProvider provider) { + super(provider); + } + + /** + * 根据 SocketAddress 获取一个 SocketChannel,具体实现依赖底层操作系统 + */ + public static SocketChannel open(SocketAddress remote) throws IOException { + SocketChannel sc = open(); + try { + sc.connect(remote); + } catch (Throwable x) { + try { + sc.close(); + } catch (Throwable suppressed) { + x.addSuppressed(suppressed); + } + throw x; + } + assert sc.isConnected(); + return sc; + } + + public static SocketChannel open() throws IOException { + return SelectorProvider.provider().openSocketChannel(); + } + + // -- Socket-specific operations -- + + /** + * 绑定要连接的远程服务的ip及端口 + */ + @Override + public abstract SocketChannel bind(SocketAddress local) throws IOException; + + /** + * 该channel与服务端是否已连接 + */ + public abstract boolean isConnected(); + + // -- ByteChannel operations -- + + /** + * 将 channel 中的数据读到 ByteBuffer + */ + public abstract int read(ByteBuffer dst) throws IOException; + + public final long read(ByteBuffer[] dsts) throws IOException { + return read(dsts, 0, dsts.length); + } + + public abstract long read(ByteBuffer[] dsts, int offset, int length) throws IOException; + + /** + * 将 ByteBuffer 中的数据写到 channel + */ + public abstract int write(ByteBuffer src) throws IOException; + + public final long write(ByteBuffer[] srcs) throws IOException { + return write(srcs, 0, srcs.length); + } + + public abstract long write(ByteBuffer[] srcs, int offset, int length) throws IOException; +} +``` diff --git "a/docs/Netty/IOTechnologyBase/\345\233\233\347\247\215IO\347\274\226\347\250\213\345\217\212\345\257\271\346\257\224.md" "b/docs/Netty/IOTechnologyBase/\345\233\233\347\247\215IO\347\274\226\347\250\213\345\217\212\345\257\271\346\257\224.md" index efeac55f..8b11fe14 100644 --- "a/docs/Netty/IOTechnologyBase/\345\233\233\347\247\215IO\347\274\226\347\250\213\345\217\212\345\257\271\346\257\224.md" +++ "b/docs/Netty/IOTechnologyBase/\345\233\233\347\247\215IO\347\274\226\347\250\213\345\217\212\345\257\271\346\257\224.md" @@ -1,30 +1,36 @@ -## 传统的BIO编程 -网络编程的基本模型是 Client/Server 模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket) 进行通信。 +## 传统的 BIO 编程 -在基于传统同步阻塞模型开发中,ServerSocket 负责绑定IP 地址,启动监听端口,Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。 +网络编程的基本模型是 Client/Server 模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的 IP 地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket) 进行通信。 -### BIO通信模型 -通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor线程 负责监听客户端的连接,它接收到客户 +在基于传统同步阻塞模型开发中,ServerSocket 负责绑定 IP 地址,启动监听端口,Socket 负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。 + +### BIO 通信模型 + +通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程 负责监听客户端的连接,它接收到客户 端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。 -![avatar](../../../images/Netty/BIO通信模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/BIO通信模型.png) + +该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈 1: 1 的正比关系,由于线程是 Java 虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。 + +在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。为了改进 一线程一连接 模型,后来又演进出了一种通过线程池或者消息队列实现 1 个或者多个线程处理 N 个客户端的模型,由于它的底层通信机制依然使用 同步阻塞 IO,所以被称为 “伪异步”。 + +## 伪异步 IO 编程 -该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1: 1的正比关系,由于线程是 Java虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。 +为了解决 同步阻塞 IO 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成 客户端个数 M:线程池最大线程数 N 的比例关系,其中 M 可以远远大于 N。通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。 -在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。为了改进 一线程一连接 模型,后来又演进出了一种通过线程池或者消息队列实现1个或者多个线程处理N个客户端的模型,由于它的底层通信机制依然使用 同步阻塞IO,所以被称为 “伪异步”。 +### 伪异步 IO 模型图 -## 伪异步IO编程 -为了解决 同步阻塞IO 面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成 客户端个数M:线程池最大线程数N 的比例关系,其中 M 可以远远大于 N。通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。 +采用线程池和任务队列可以实现一种叫做 伪异步的 IO 通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task 对象 (该类实现了 java.lang.Runnable 接口),投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 -### 伪异步IO模型图 -采用线程池和任务队列可以实现一种叫做 伪异步的IO通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task对象 (该类实现了java.lang.Runnable接口),投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/伪异步IO通信模型.png) -![avatar](../../../images/Netty/伪异步IO通信模型.png) +伪异步 IO 通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。 -伪异步 IO通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。 +### 伪异步 IO 编程弊端分析 + +要对 伪异步 IO 编程 的弊端进行深入分析,首先我们看两个 Java 同步 IO 的 API 说明,随后结合代码进行详细分析。 -### 伪异步IO编程弊端分析 -要对 伪异步IO编程 的弊端进行深入分析,首先我们看两个 Java同步IO 的 API说明,随后结合代码进行详细分析。 ```java public abstract class InputStream implements Closeable { @@ -45,15 +51,17 @@ public abstract class InputStream implements Closeable { public abstract int read() throws IOException; } ``` + 注意其中的一句话 **“This method blocks until input data is available, the end of the stream is detected, or an exception is thrown”**,当对 Socket 的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件。 - 有数据可读; - 可用数据已经读取完毕; -- 发生空指针或者 IO异常。 +- 发生空指针或者 IO 异常。 -这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞,如果对方要 60s 才能够将数据发送完成,读取一方的 IO线程 也将会被同步阻塞 60s,在此期间,其他接入消息只能在消息队列中排队。 +这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程将被长时间阻塞,如果对方要 60s 才能够将数据发送完成,读取一方的 IO 线程 也将会被同步阻塞 60s,在此期间,其他接入消息只能在消息队列中排队。 + +下面我们接着对输出流进行分析,还是看 JDK IO 类库 输出流的 API 文档,然后结合文档说明进行故障分析。 -下面我们接着对输出流进行分析,还是看 JDK IO类库 输出流的 API文档,然后结合文档说明进行故障分析。 ```java public abstract class OutputStream implements Closeable, Flushable { @@ -69,71 +77,83 @@ public abstract class OutputStream implements Closeable, Flushable { } } ``` -当调用 OutputStream 的 write()方法 写输出流的时候,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。学习过 TCP/IP 相关知识的人都知道,当消息的接收方处理缓慢的时候,将不能及时地从 TCP缓冲区 读取数据,这将会导致发送方的 TCP window size 不断减小,直到为 0,双方处于 Keep-Alive状态,消息发送方将不能再向 TCP缓冲区 写入消息,这时如果采用的是 同步阻塞IO,write操作 将会被无限期阻塞,直到 TCP window size 大于0 或者发生 IO异常。 -通过对输入和输出流的 API文档 进行分析,我们了解到读和写操作都是同步阻塞的,阻塞的时间取决于对方 IO线程 的处理速度和 网络IO 的传输速度。本质上来讲,我们无法保证生产环境的网络状况和对方的应用程序能足够快,如果我们的应用程序依赖对方的处理速度,它的可靠性就非常差。也许在实验室进行的性能测试结果令人满意,但是一旦上线运行,面对恶劣的网络环境和良莠不齐的第三方系统,问题就会如火山一样喷发。 +当调用 OutputStream 的 write()方法 写输出流的时候,它将会被阻塞,直到所有要发送的字节全部写入完毕,或者发生异常。学习过 TCP/IP 相关知识的人都知道,当消息的接收方处理缓慢的时候,将不能及时地从 TCP 缓冲区 读取数据,这将会导致发送方的 TCP window size 不断减小,直到为 0,双方处于 Keep-Alive 状态,消息发送方将不能再向 TCP 缓冲区 写入消息,这时如果采用的是 同步阻塞 IO,write 操作 将会被无限期阻塞,直到 TCP window size 大于 0 或者发生 IO 异常。 + +通过对输入和输出流的 API 文档 进行分析,我们了解到读和写操作都是同步阻塞的,阻塞的时间取决于对方 IO 线程 的处理速度和 网络 IO 的传输速度。本质上来讲,我们无法保证生产环境的网络状况和对方的应用程序能足够快,如果我们的应用程序依赖对方的处理速度,它的可靠性就非常差。也许在实验室进行的性能测试结果令人满意,但是一旦上线运行,面对恶劣的网络环境和良莠不齐的第三方系统,问题就会如火山一样喷发。 -伪异步IO 实际上仅仅是对之前 IO线程模型 的一个简单优化,它无法从根本上解决 同步IO 导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。 +伪异步 IO 实际上仅仅是对之前 IO 线程模型 的一个简单优化,它无法从根本上解决 同步 IO 导致的通信线程阻塞问题。下面我们就简单分析下通信对方返回应答时间过长会引起的级联故障。 -1. 服务端处理缓慢,返回应答消息耗费60s, 平时只需要10ms。 -2. 采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞60s。 -3. 假如所有的可用线程都被故障服务器阻塞,那后续所有的1/O消息都将在队列中排队。 +1. 服务端处理缓慢,返回应答消息耗费 60s, 平时只需要 10ms。 +2. 采用伪异步 I/O 的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞 60s。 +3. 假如所有的可用线程都被故障服务器阻塞,那后续所有的 1/O 消息都将在队列中排队。 4. 由于线程池采用阻塞队列实现,当队列积满之后,后续入队列的操作将被阻塞。 -5. 由于前端只有一个Accptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时。 +5. 由于前端只有一个 Accptor 线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时。 6. 由于几乎所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。 -## NIO编程 -与 Socket类 和 ServerSocket类 相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自 -己的需要来选择合适的模式。一般来说,低负载、低并发的应用程序可以选择 同步阻塞IO,以降低编程复杂度;对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。 +## NIO 编程 -### NIO类库简介 -NIO类库 是在 JDK 1.4 中引入的。NIO 弥补了原来 同步阻塞IO 的不足,它在 标准Java代码 中提供了高速的、面向块的IO。下面我们简单看一下 NIO类库 及其 相关概念。 +与 Socket 类 和 ServerSocket 类 相对应,NIO 也提供了 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好,非阻塞模式则正好相反。开发人员可以根据自 +己的需要来选择合适的模式。一般来说,低负载、低并发的应用程序可以选择 同步阻塞 IO,以降低编程复杂度;对于高负载、高并发的网络应用,需要使用 NIO 的非阻塞模式进行开发。 -**1、缓冲区Buffer** -Buffer对象 包含了一些要写入或者要读出的数据。在 NIO类库 中加入 Buffer对象,是其与 原IO类库 的一个重要区别。在面向流的 IO 中,可以将数据直接写入或者将数据直接读到 Stream对象 中。在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。 +### NIO 类库简介 -缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte数组。除了 ByteBuffer,还有其他的一些缓冲区,事实上,每一种 Java基本类型(除了 boolean)都对应有一种与之对应的缓冲区,如:CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer组件中主要类的类图如下所示。 +NIO 类库 是在 JDK 1.4 中引入的。NIO 弥补了原来 同步阻塞 IO 的不足,它在 标准 Java 代码 中提供了高速的、面向块的 IO。下面我们简单看一下 NIO 类库 及其 相关概念。 -![avatar](../../../images/Netty/Buffer组件类图.png) +**1、缓冲区 Buffer** +Buffer 对象 包含了一些要写入或者要读出的数据。在 NIO 类库 中加入 Buffer 对象,是其与 原 IO 类库 的一个重要区别。在面向流的 IO 中,可以将数据直接写入或者将数据直接读到 Stream 对象 中。在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。 -除了ByteBuffer,每一个 Buffer类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准IO操作 都使用 ByteBuffer,所以它在具有一般缓冲区的操作之外还提供了一些特有的操作,以方便网络读写。 +缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了 ByteBuffer,还有其他的一些缓冲区,事实上,每一种 Java 基本类型(除了 boolean)都对应有一种与之对应的缓冲区,如:CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer 组件中主要类的类图如下所示。 -**2、通道Channel** -Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream,要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX网络编程模型 中,底层操作系统的通道都是全双工的,同时支持读写操作。Channel组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Buffer组件类图.png) -![avatar](../../../images/Netty/Channel组件类图.png) +除了 ByteBuffer,每一个 Buffer 类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准 IO 操作 都使用 ByteBuffer,所以它在具有一般缓冲区的操作之外还提供了一些特有的操作,以方便网络读写。 -**3、多路复用器Selector** -多路复用器Selector 是 Java NIO编程 的基础,熟练地掌握 Selector 对于 NIO编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询 “注册在其上的Channel”,如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO操作。 +**2、通道 Channel** +Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream,要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX 网络编程模型 中,底层操作系统的通道都是全双工的,同时支持读写操作。Channel 组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。 -一个 多路复用器Selector 可以同时轮询多个 Channel,由于 JDK 使用了 epoll() 代替传统的 select 的实现,所以它并没有最大连接句柄的限制。这也就意味着,只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。下面,我们通过 NIO编程的序列图 和 源码分析来熟悉相关的概念。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Channel组件类图.png) -### NIO服务端序列图 +**3、多路复用器 Selector** +多路复用器 Selector 是 Java NIO 编程 的基础,熟练地掌握 Selector 对于 NIO 编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector 会不断地轮询 “注册在其上的 Channel”,如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO 操作。 -![avatar](../../../images/Netty/NIO服务端序列图.png) +一个 多路复用器 Selector 可以同时轮询多个 Channel,由于 JDK 使用了 epoll() 代替传统的 select 的实现,所以它并没有最大连接句柄的限制。这也就意味着,只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。下面,我们通过 NIO 编程的序列图 和 源码分析来熟悉相关的概念。 -下面,我们看一下 NIO服务端 的主要创建过程。 +### NIO 服务端序列图 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NIO服务端序列图.png) + +下面,我们看一下 NIO 服务端 的主要创建过程。 1、打开 ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的 父管道,示例代码如下。 + ```java ServerSocketChannel acceptorSvr = ServerSocketChannel.open(); ``` + 2、绑定监听端口,设置连接为非阻塞模式,示例代码如下。 + ```java acceptorSvr.socket().bind(new InetSocketAddress(InetAddress.getByName("IP"), port)); acceptorSvr.configureBlocking(false); ``` -3、创建 Reactor线程,创建多路复用器并启动线程,示例代码如下。 + +3、创建 Reactor 线程,创建多路复用器并启动线程,示例代码如下。 + ```java Selector selector = Selector.open(); New Thread (new ReactorTask()).start(); ``` -4、将 ServerSocketChannel 注册到 Reactor线程 的 多路复用器Selector 上,监听 ACCEPT事件,示例代码如下。 + +4、将 ServerSocketChannel 注册到 Reactor 线程 的 多路复用器 Selector 上,监听 ACCEPT 事件,示例代码如下。 + ```java SelectionKey key = acceptorSvr.register(selector, SelectionKey.OP_ ACCEPT, ioHandler); ``` -5、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的Key,示例代码如下。 + +5、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的 Key,示例代码如下。 + ```java int num = selector.select(); Set selectedKeys = selector.selectedKeys(); @@ -142,25 +162,35 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann SelectionKey key = (SelectionKey) it.next(); // .... deal with IO event ... ``` -6、多路复用器Selector 监听到有新的客户端接入,处理新的接入请求,完成 TCP三次握手,建立物理链路,示例代码如下。 + +6、多路复用器 Selector 监听到有新的客户端接入,处理新的接入请求,完成 TCP 三次握手,建立物理链路,示例代码如下。 + ```java SocketChannel channel = svrChannel.accept(); ``` + 7、设置客户端链路为非阻塞模式,示例代码如下。 + ```java channel.configureBlocking(false); channel.socket().setReuseAddress(true); ...... ``` -8、将新接入的客户端连接注册到 Reactor线程 的多路复用器上,监听读操作,读取客户端发送的网络消息,示例代码如下。 + +8、将新接入的客户端连接注册到 Reactor 线程 的多路复用器上,监听读操作,读取客户端发送的网络消息,示例代码如下。 + ```java SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ, ioHandler); ``` + 9、异步读取客户端请求消息到缓冲区,示例代码如下。 + ```java int readNumber = channel.read(receivedBuffer); ``` + 10、对 ByteBuffer 进行编解码,如果有半包消息指针 reset,继续读取后续的报文,将解码成功的消息封装成 Task,投递到业务线程池中,进行业务逻辑编排,示例代码如下。 + ```java List messageList = null; while (byteBuffer.hasRemain()) { @@ -183,32 +213,42 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann } } ``` -11、将 POJO对象 encode 成 ByteBuffer,调用 SocketChannel 的 异步write接口,将消息异步发送给客户端,示例代码如下。 + +11、将 POJO 对象 encode 成 ByteBuffer,调用 SocketChannel 的 异步 write 接口,将消息异步发送给客户端,示例代码如下。 + ```java socketChannel.write(byteBuffer); ``` -注意:如果发送区 TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入 TCP缓冲区。对于 “半包问题” 此处暂不赘述,后续会单独写一篇详细分析 Netty 的处理策略。 + +注意:如果发送区 TCP 缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,直到整包消息写入 TCP 缓冲区。对于 “半包问题” 此处暂不赘述,后续会单独写一篇详细分析 Netty 的处理策略。 ### NIO 客户端序列图 -![avatar](../../../images/Netty/NIO客户端序列图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NIO客户端序列图.png) 1、打开 SocketChannel,绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。 + ```java SocketChannel clientChannel = SocketChannel.open(); ``` -2、设置 SocketChannel 为非阻塞模式,同时设置客户端连接的 TCP参数,示例代码如下。 + +2、设置 SocketChannel 为非阻塞模式,同时设置客户端连接的 TCP 参数,示例代码如下。 + ```java clientChannel.configureBlocking(false); socket.setReuseAddress(true); socket.setReceiveBufferSize(BUFFER_SIZE); socket.setSendBufferSize(BUFFER_SIZE); ``` + 3、异步连接服务端,示例代码如下。 + ```java boolean connected = clientChannel.connect( new InetSocketAddress("ip", port) ); ``` -4、判断是否连接成功,如果连接成功,则直接注册读状态位到多路复用器中,如果当前没有连接成功,则重连 (异步连接,返回false,说明客户端已经发送 syne包,服务端没有返回 ack包,物理链路还没有建立),示例代码如下。 + +4、判断是否连接成功,如果连接成功,则直接注册读状态位到多路复用器中,如果当前没有连接成功,则重连 (异步连接,返回 false,说明客户端已经发送 syne 包,服务端没有返回 ack 包,物理链路还没有建立),示例代码如下。 + ```java if (connected) { clientChannel.register(selector, SelectionKey.OP_READ, ioHandler); @@ -216,16 +256,22 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler); } ``` -5、向 Reactor线程 的多路复用器注册 OP_CONNECT 状态位,监听服务端的 TCP ACK应答,示例代码如下。 + +5、向 Reactor 线程 的多路复用器注册 OP_CONNECT 状态位,监听服务端的 TCP ACK 应答,示例代码如下。 + ```java clientChannel.register(selector, SelectionKey.OP_CONNECT, ioHandler); ``` -6、创建 Reactor线程,创建多路复用器并启动线程,代码如下。 + +6、创建 Reactor 线程,创建多路复用器并启动线程,代码如下。 + ```java Selector selector = Selector.open(); New Thread( new ReactorTask() ).start(); ``` -7、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的Key,代码如下。 + +7、多路复用器在线程 run()方法 的无限循环体内轮询 准备就绪的 Key,代码如下。 + ```java int num = selector.select(); Set selectedKeys = selector.selectedKeys(); @@ -235,27 +281,37 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann // ... deal with IO event ... } ``` -8、接收 connect事件,并进行处理,示例代码如下。 + +8、接收 connect 事件,并进行处理,示例代码如下。 + ```java if (key.isConnectable()) { // handlerConnect(); } ``` + 9、判断连接结果,如果连接成功,注册读事件到多路复用器,示例代码如下。 + ```java if(channel.finishConnect()) { registerRead(); } ``` + 10、注册读事件到多路复用器,示例代码如下。 + ```java clientChannel.register(selector, SelectionKey.OP_READ, ioHandler); ``` + 11、异步读客户端请求消息到缓冲区,示例代码如下。 + ```java int readNumber = channel.read(receivedBuffer); ``` + 12、对 ByteBuffer 进行编解码,如果有半包消息接收缓冲区 Reset,继续读取后续的报文,将解码成功的消息封装成 Task,投递到业务线程池中,进行业务逻辑编排。示例代码如下。 + ```java List messageList = null; while (byteBuffer.hasRemain()) { @@ -278,41 +334,51 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann } } ``` -13、将 POJO对象 encode成 ByteBuffer,调用 SocketChannel 的 异步write接口,将消息异步发送给客户端。示例代码如下。 + +13、将 POJO 对象 encode 成 ByteBuffer,调用 SocketChannel 的 异步 write 接口,将消息异步发送给客户端。示例代码如下。 + ```java socketChannel.write(buffer); ``` -## AIO编程 +## AIO 编程 + NIO2.0 引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取获取操作结果。 -- 通过 java.util.concurrent.Future类 来表示异步操作的结果; -- 在执行异步操作的时候传入一个 java.nio.channels.CompletionHandler接口 的实现类作为操作完成的回调。 -NIO2.0 的异步套接字通道是真正的 异步非阻塞IO,对应于 UNIX网络编程 中的 事件驱动IO (AIO)。它不需要通过多路复用器 (Selector) 对注册的通道进行轮询操作即可实现异步读写,从而简化了 NIO 的编程模型。 +- 通过 java.util.concurrent.Future 类 来表示异步操作的结果; +- 在执行异步操作的时候传入一个 java.nio.channels.CompletionHandler 接口 的实现类作为操作完成的回调。 + +NIO2.0 的异步套接字通道是真正的 异步非阻塞 IO,对应于 UNIX 网络编程 中的 事件驱动 IO (AIO)。它不需要通过多路复用器 (Selector) 对注册的通道进行轮询操作即可实现异步读写,从而简化了 NIO 的编程模型。 由于在实际开发中使用较少,所以这里不对 AIO 进行详细分析。 -## 四种IO编程模型的对比 -对比之前,这里再澄清一下 “伪异步IO” 的概念。伪异步IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO编程 没有流行之前,为了解决 Tomcat 通信线程同步IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO线程 和业务线程间的直接访问,这样业务线程就不会被 IO线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO线程 或者进行 IO读写,这样也就不会被同步阻塞。 +## 四种 IO 编程模型的对比 -![avatar](../../../images/Netty/四种IO模型的功能特性对比图.png) +对比之前,这里再澄清一下 “伪异步 IO” 的概念。伪异步 IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO 编程 没有流行之前,为了解决 Tomcat 通信线程同步 IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO 线程 和业务线程间的直接访问,这样业务线程就不会被 IO 线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO 线程 或者进行 IO 读写,这样也就不会被同步阻塞。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/四种IO模型的功能特性对比图.png) ## 选择 Netty 开发项目的理由 -从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。 -### 为什么不选择 Java原生NIO 进行开发 + +从可维护性角度看,由于 NIO 采用了异步非阻塞编程模型,而且是一个 IO 线程 处理多条链路,它的调试和跟踪非常麻烦,特别是生产环境中的问题,我们无法进行有效的调试和跟踪,往往只能靠一些日志来辅助分析,定位难度很大。 + +### 为什么不选择 Java 原生 NIO 进行开发 + 1. NIO 的类库和 API 使用起来非常繁杂,需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer 等。 -2. 需要具备其他的额外技能做铺垫,例如,熟悉 Java多线程编程。这是因为 NIO编程 涉及到 Reactor模式,你必须对 多线程 和 网路编程 非常熟悉,才能编写出高质量的 NIO程序。 +2. 需要具备其他的额外技能做铺垫,例如,熟悉 Java 多线程编程。这是因为 NIO 编程 涉及到 Reactor 模式,你必须对 多线程 和 网路编程 非常熟悉,才能编写出高质量的 NIO 程序。 3. 可靠性能力补齐,工作量和难度都非常大。例如客户端面临:断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理,等问题。 -4. JDK NIO 的 BUG,例如臭名昭著的 epoll bug,它会导致 Selector空轮询,最终导致 CPU 100%。虽然官方声称修复了该问题,但是直到 JDK 1.7版本 该问题仍旧未得到彻底的解决。 +4. JDK NIO 的 BUG,例如臭名昭著的 epoll bug,它会导致 Selector 空轮询,最终导致 CPU 100%。虽然官方声称修复了该问题,但是直到 JDK 1.7 版本 该问题仍旧未得到彻底的解决。 ### 为什么选择 Netty 进行开发 -Netty 是业界最流行的 NIO框架 之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC框架 Avro ,阿里的 RPC框架 Dubbo 就使用了 Netty 作为底层通信框架。通过对Netty的分析,我们将它的优点总结如下。 -- API使用简单,开发门槛低; + +Netty 是业界最流行的 NIO 框架 之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,已经得到成百上千的商用项目验证,例如 Hadoop 的 RPC 框架 Avro ,阿里的 RPC 框架 Dubbo 就使用了 Netty 作为底层通信框架。通过对 Netty 的分析,我们将它的优点总结如下。 + +- API 使用简单,开发门槛低; - 功能强大,预置了多种编解码功能,支持多种主流协议; - 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活地扩展; -- 性能高,通过与其他业界主流的 NIO框架 对比,Netty 的综合性能最优; +- 性能高,通过与其他业界主流的 NIO 框架 对比,Netty 的综合性能最优; - 成熟、稳定,Netty 修复了已经发现的所有 JDK NIO BUG,业务开发人员不需要再为 NIO 的 BUG 而烦恼; - 社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时,更多的新功能会加入; - 经历了大规模的商业应用考验,质量得到验证。Netty 在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。 -正是因为这些优点,Netty 逐渐成为了 Java NIO编程 的首选框架。 \ No newline at end of file +正是因为这些优点,Netty 逐渐成为了 Java NIO 编程 的首选框架。 diff --git "a/docs/Netty/IOTechnologyBase/\346\212\212\350\242\253\350\257\264\347\203\202\347\232\204BIO\343\200\201NIO\343\200\201AIO\345\206\215\344\273\216\345\244\264\345\210\260\345\260\276\346\211\257\344\270\200\351\201\215.md" "b/docs/Netty/IOTechnologyBase/\346\212\212\350\242\253\350\257\264\347\203\202\347\232\204BIO\343\200\201NIO\343\200\201AIO\345\206\215\344\273\216\345\244\264\345\210\260\345\260\276\346\211\257\344\270\200\351\201\215.md" index 060394a0..aea5a9c5 100644 --- "a/docs/Netty/IOTechnologyBase/\346\212\212\350\242\253\350\257\264\347\203\202\347\232\204BIO\343\200\201NIO\343\200\201AIO\345\206\215\344\273\216\345\244\264\345\210\260\345\260\276\346\211\257\344\270\200\351\201\215.md" +++ "b/docs/Netty/IOTechnologyBase/\346\212\212\350\242\253\350\257\264\347\203\202\347\232\204BIO\343\200\201NIO\343\200\201AIO\345\206\215\344\273\216\345\244\264\345\210\260\345\260\276\346\211\257\344\270\200\351\201\215.md" @@ -1,38 +1,46 @@ -网上关于各种IO的博文已经多到飞起,如果你是大神,可以跳过我这个菜鸟的拙文,本博文没有什么特别NB的东西,只是集百家之长,并且以自己感到简单舒适的方式输出自己的理解,及学习过程中的经验。 -## IO及基本概念 +网上关于各种 IO 的博文已经多到飞起,如果你是大神,可以跳过我这个菜鸟的拙文,本博文没有什么特别 NB 的东西,只是集百家之长,并且以自己感到简单舒适的方式输出自己的理解,及学习过程中的经验。 + +## IO 及基本概念 + #### 1、流的概念和作用 -**流**:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。 -**流的本质**:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 -**流的作用**:为数据源和目的地建立一个输送通道。 -Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。 - -每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。 -#### 2、IO模型 -五种IO模型包括:阻塞IO、非阻塞IO、信号驱动IO、IO多路复用、异步IO。其中,前四个被称为同步IO。在网络环境下,可以将IO分为两步: -1.等待数据到来; -2.数据搬迁。 -在互联网应用中,IO线程大多被阻塞在等待数据的过程中,所以,如果要想提高IO效率,需要降低等待的时间。 -##### 2.1 阻塞IO(Blocking I/O) + +**流**:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象。<Thinking in Java> +**流的本质**:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 +**流的作用**:为数据源和目的地建立一个输送通道。 +Java 中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。 + +每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。 + +#### 2、IO 模型 + +五种 IO 模型包括:阻塞 IO、非阻塞 IO、信号驱动 IO、IO 多路复用、异步 IO。其中,前四个被称为同步 IO。在网络环境下,可以将 IO 分为两步: 1.等待数据到来; 2.数据搬迁。 +在互联网应用中,IO 线程大多被阻塞在等待数据的过程中,所以,如果要想提高 IO 效率,需要降低等待的时间。 + +##### 2.1 阻塞 IO(Blocking I/O) + 在内核将数据准备好之前,系统调用会一直等待所有的套接字(Socket),默认的是阻塞方式。 -![avatar](../../../images/Netty/阻塞IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/阻塞IO模型.png) + +Java 中的 socket.read()会调用 native read(),而 Java 中的 native 方法会调用操作系统底层的 dll,而 dll 是 C/C++编写的,图中的 recvfrom 其实是 C 语言 socket 编程中的一个方法。所以其实我们在 Java 中调用 socket.read()最后也会调用到图中的 recvfrom 方法。 -Java中的socket.read()会调用native read(),而Java中的native方法会调用操作系统底层的dll,而dll是C/C++编写的,图中的recvfrom其实是C语言socket编程中的一个方法。所以其实我们在Java中调用socket.read()最后也会调用到图中的recvfrom方法。 +应用程序(也就是我们的代码)想要读取数据就会调用 recvfrom,而 recvfrom 会通知 OS 来执行,OS 就会判断数据报是否准备好(比如判断是否收到了一个完整的 UDP 报文,如果收到 UDP 报文不完整,那么就继续等待)。当数据包准备好了之后,OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read()就会解除阻塞,并得到 read 的结果。 -应用程序(也就是我们的代码)想要读取数据就会调用recvfrom,而recvfrom会通知OS来执行,OS就会判断数据报是否准备好(比如判断是否收到了一个完整的UDP报文,如果收到UDP报文不完整,那么就继续等待)。当数据包准备好了之后,OS就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后socket.read()就会解除阻塞,并得到read的结果。 +BIO 中的阻塞,就是阻塞在 2 个地方: -BIO中的阻塞,就是阻塞在2个地方: -1. OS等待数据报(通过网络发送过来)准备好。 +1. OS 等待数据报(通过网络发送过来)准备好。 2. 将数据从内核空间拷贝到用户空间。 -在这2个时候,我们的BIO程序就是占着茅坑不拉屎,啥事情都不干。 -##### 2.2 非阻塞IO(Noblocking I/O) +在这 2 个时候,我们的 BIO 程序就是占着茅坑不拉屎,啥事情都不干。 + +##### 2.2 非阻塞 IO(Noblocking I/O) -![avatar](../../../images/Netty/非阻塞IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/非阻塞IO模型.png) -每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮寻。但是,轮寻对于CPU来说是较大的浪费,一般只有在特定的场景下才使用。 +每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮询。但是,轮询对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。 + +Java 的 NIO 就是采用这种方式,当我们 new 了一个 socket 后我们可以设置它是非阻塞的。比如: -Java的NIO就是采用这种方式,当我们new了一个socket后我们可以设置它是非阻塞的。比如: ```java // 初始化一个 serverSocketChannel serverSocketChannel = ServerSocketChannel.open(); @@ -41,81 +49,97 @@ serverSocketChannel.bind(new InetSocketAddress(8000)); // 即 accept()会立即得到返回 serverSocketChannel.configureBlocking(false); ``` -上面的代码是设置ServerSocketChannel为非阻塞,SocketChannel也可以设置。 -从图中可以看到,当设置为非阻塞后,我们的socket.read()方法就会立即得到一个返回结果(成功 or 失败),我们可以根据返回结果执行不同的逻辑,比如在失败时,我们可以做一些其他的事情。但事实上这种方式也是低效的,因为我们不得不使用轮询方法区一直问OS:“我的数据好了没啊”。 +上面的代码是设置 ServerSocketChannel 为非阻塞,SocketChannel 也可以设置。 + +从图中可以看到,当设置为非阻塞后,我们的 socket.read()方法就会立即得到一个返回结果(成功 or 失败),我们可以根据返回结果执行不同的逻辑,比如在失败时,我们可以做一些其他的事情。但事实上这种方式也是低效的,因为我们不得不使用轮询方法去一直问 OS:“我的数据好了没啊”。 + +**NIO 不会在 recvfrom(询问数据是否准备好)时阻塞,但还是会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方,Non-Blocking 还是会阻塞的。** -**BIO 不会在recvfrom(询问数据是否准备好)时阻塞,但还是会在将数据从内核空间拷贝到用户空间时阻塞。一定要注意这个地方,Non-Blocking还是会阻塞的。** -##### 2.3 IO多路复用(I/O Multiplexing) +##### 2.3 IO 多路复用(I/O Multiplexing) -![avatar](../../../images/Netty/IO复用模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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 容量。 其中,不同的操作系统,对此有不同的实现: -Windows:selector -Linux:epoll -Mac:kqueue -其中epoll,kqueue比selector更为高效,这是因为他们监听方式的不同。selector的监听是通过轮询FD_SETSIZE来问每一个socket:“你改变了吗?”,假若监听到事件,那么selector就会调用相应的事件处理器进行处理。但是epoll与kqueue不同,他们把socket与事件绑定在一起,当监听到socket变化时,立即可以调用相应的处理。 -**selector,epoll,kqueue都属于Reactor IO设计。** + +- Windows:selector +- Linux:epoll +- Mac:kqueue + +其中 epoll,kqueue 比 selector 更为高效,这是因为他们监听方式的不同。selector 的监听是通过轮询 FD_SETSIZE 来问每一个 socket:“你改变了吗?”,假若监听到事件,那么 selector 就会调用相应的事件处理器进行处理。但是 epoll 与 kqueue 不同,他们把 socket 与事件绑定在一起,当监听到 socket 变化时,立即可以调用相应的处理。 +**selector,epoll,kqueue 都属于 Reactor IO 设计。** + ##### 2.4 信号驱动(Signal driven IO) -![avatar](../../../images/Netty/信号驱动IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/信号驱动IO模型.png) + +信号驱动 IO 模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对 SIGIO 信号进行捕捉,并且调用我的信号处理函数来获取数据报。 + +##### 2.5 异步 IO(Asynchronous I/O) + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/异步IO模型.png) -信号驱动IO模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对SIGIO信号进行捕捉,并且调用我的信号处理函数来获取数据报。 -##### 2.5 异步IO(Asynchronous I/O) +Asynchronous IO 调用中是真正的无阻塞,其他 IO model 中多少会有点阻塞。程序发起 read 操作之后,立刻就可以开始去做其它的事。而在内核角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。 -![avatar](../../../images/Netty/异步IO模型.png) +可以看出,阻塞程度:阻塞 IO>非阻塞 IO>多路转接 IO>信号驱动 IO>异步 IO,效率是由低到高的。 -Asynchronous IO调用中是真正的无阻塞,其他IO model中多少会有点阻塞。程序发起read操作之后,立刻就可以开始去做其它的事。而在内核角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。 +##### 2.6 Blocking IO 与 Non-Blocking IO 区别? -可以看出,阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。 +阻塞或非阻塞只涉及程序和 OS,Blocking IO 会一直 block 程序直到 OS 返回,而 Non-Block IO 在 OS 内核准备数据包的情况下会立即得到返回。 -##### 2.6 Blocking IO 与Non-Blocking IO 区别? -阻塞或非阻塞只涉及程序和OS,Blocking IO 会一直block程序直到OS返回,而Non-Block IO在OS内核准备数据包的情况下会立即得到返回。 ##### 2.7 Asynchronous IO 与 Synchronous IO? -只要有block就是同步IO,完全没有block则是异步IO。所以我们之前所说的Blocking IO、Non-Blocking IO、IO Multiplex,均为Synchronous IO,只有Asynchronous IO为异步IO。 + +只要有 block 就是同步 IO,完全没有 block 则是异步 IO。所以我们之前所说的 Blocking IO、Non-Blocking IO、IO Multiplex,均为 Synchronous IO,只有 Asynchronous IO 为异步 IO。 + ##### 2.8 Non-Blocking IO 不是会立即返回没有阻塞吗? -**Non-Blocking IO在数据包准备时是非阻塞的,但是在将数据从内核空间拷贝到用户空间还是会阻塞**。而Asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,由内核完成读写,完成读写操作后kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。 -#### 3、IO模式(Reactor与Proactor) + +**Non-Blocking IO 在数据包准备时是非阻塞的,但是在将数据从内核空间拷贝到用户空间还是会阻塞**。而 Asynchronous IO 则不一样,当进程发起 IO 操作之后,就直接返回再也不理睬了,由内核完成读写,完成读写操作后 kernel 发送一个信号,告诉进程说 IO 完成。在这整个过程中,进程完全没有被 block。 + +#### 3、IO 模式(Reactor 与 Proactor) + ##### 3.1 Reactor -Reactor(反应器)的设计是一种事件驱动思想,比如Java NIO中,socket过来时有四种事件: + +Reactor(反应器)的设计是一种事件驱动思想,比如 Java NIO 中,socket 过来时有四种事件: connectable acceptable readable writable -我们为每一种事件都编写一个处理器,然后设置每个socket要监听哪种情况,随后就可以调用对应的处理器。 +我们为每一种事件都编写一个处理器,然后设置每个 socket 要监听哪种情况,随后就可以调用对应的处理器。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121200143647.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) -图中的input就可以当作socket,中间的Service Hanlder&event dispatch的作用就是监听每一个socket(需要实现把socket注册进来,并指定要监听哪种情况),然后给socket派发不同的事件。 +图中的 input 就可以当作 socket,中间的 Service Hanlder&event dispatch 的作用就是监听每一个 socket(需要实现把 socket 注册进来,并指定要监听哪种情况),然后给 socket 派发不同的事件。 + ##### 3.2 Proactor ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019112120035031.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) -Proactor与Reactor较为类似,以读取数据为例: -**Reactor模式** +Proactor 与 Reactor 较为类似,以读取数据为例: +**Reactor 模式** - 1. 应用程序注册读就绪事件和相关联的事件处理器 - 2. 事件分离器等待事件的发生 - 3. 当发生 读就绪事件 的时候,事件分离器调用第一步注册的事件处理器 - 4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理 +1. 应用程序注册读就绪事件和相关联的事件处理器 +2. 事件分离器等待事件的发生 +3. 当发生 读就绪事件 的时候,事件分离器调用第一步注册的事件处理器 +4. 事件处理器首先执行实际的读取操作,然后根据读取到的内容进行进一步的处理 -**Proactor模式** +**Proactor 模式** - 1. 应用程序调用一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于Reactor的关键。 - 2. 事件分离器等待读取操作完成事件 - 3. 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步IO都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于Reactor的一点,Proactor中,应用程序需要传递缓存区。 - 4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。 +1. 应用程序调用一个异步读取操作,然后注册相应的事件处理器,此时事件处理器不关注读取就绪事件,而是关注读取完成事件,这是区别于 Reactor 的关键。 +2. 事件分离器等待读取操作完成事件 +3. 在事件分离器等待读取操作完成的时候,操作系统调用内核线程完成读取操作(异步 IO 都是操作系统负责将数据读写到应用传递进来的缓冲区供应用程序操作,操作系统扮演了重要角色),并将读取的内容放入用户传递过来的缓存区中。这也是区别于 Reactor 的一点,Proactor 中,应用程序需要传递缓存区。 +4. 事件分离器捕获到读取完成事件后,激活应用程序注册的事件处理器,事件处理器直接从缓存区读取数据,而不需要进行实际的读取操作。 **区别** -Reactor中,监听是否有可读或可写事件,然后读/写操作是由程序进行的。而Proactor中,直接监听读/写操作是否完成,也就是说读/写操作是否OS来完成,并将读写数据放入一个缓冲区提供给程序。 +Reactor 中,监听是否有可读或可写事件,然后读/写操作是由程序进行的。而 Proactor 中,直接监听读/写操作是否完成,也就是说读/写操作是否 OS 来完成,并将读写数据放入一个缓冲区提供给程序。 #### 4、同步与异步,阻塞与非阻塞 + 同步/异步(描述网络通信模式,适用于请求-响应模型) 同步:发送方发送请求后,需要等待接收响应,否则将一直等待 -异步:发送方发送请求后,不需要等待响应,可以继续发送下一个请求,或者主动挂起线程并释放CPU +异步:发送方发送请求后,不需要等待响应,可以继续发送下一个请求,或者主动挂起线程并释放 CPU 阻塞/非阻塞(描述进程的函数调用方式) 阻塞:IO 调用会一直阻塞,直至结果返回才能继续执行 @@ -123,15 +147,17 @@ Reactor中,监听是否有可读或可写事件,然后读/写操作是由程 总结,同步异步和阻塞非阻塞是两个不同的概念,用简单的数据库查询来举一个例子: 如果发送一个请求,需要等待数据库响应,否则将一直等待,这就是同步 -如果发送一个请求,不需要数据库响应,就可以继续发送下一个请求(NIO模式、回调通知模式),或者主动将任务插入队列中,主动挂起线程并释放CPU(异步队列模式),这就是异步 +如果发送一个请求,不需要数据库响应,就可以继续发送下一个请求(NIO 模式、回调通知模式),或者主动将任务插入队列中,主动挂起线程并释放 CPU(异步队列模式),这就是异步 一般来说,同步是最简单的编程方式,而异步编程虽然需要一定技术,但是却能提升系统性能。对于阻塞与非阻塞,阻塞的实时响应性更好,但在高并发情况下阻塞线程数会急剧增加,导致大量的上下文切换会引起挂起/唤醒线程的性能损耗,而非阻塞的性能吞吐量更高,但由于其是顺序执行每一个事件,一旦处理某一个事件过久,会影响后续事件的处理,因此实时响应性较差。 -## Java中的BIO -#### 传统Socket阻塞案例代码 +## Java 中的 BIO + +#### 传统 Socket 阻塞案例代码 + ```java public class TraditionalSocketDemo { - + public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(7777); System.out.println("服务端启动..."); @@ -158,11 +184,14 @@ public class TraditionalSocketDemo { } } ``` -在debugger代码的过程中会发现,服务端启动,只有当客户端就绪后才进行下一步操作(如果客户端没有就绪,线程阻塞),客户端发送请求,程序才继续往下执行,如果客户端没有发出请求,线程阻塞;**上面的代码有两个阻塞点**: - 1. **等待客户端就绪**; - 2. **等待OS将数据从内核拷贝到用户空间(应用程序可以操作的内存空间)**; -#### 传统bio多线程版本 +在 debugger 代码的过程中会发现,服务端启动,只有当客户端就绪后才进行下一步操作(如果客户端没有就绪,线程阻塞),客户端发送请求,程序才继续往下执行,如果客户端没有发出请求,线程阻塞;**上面的代码有两个阻塞点**: + +1. **等待客户端就绪**; +2. **等待 OS 将数据从内核拷贝到用户空间(应用程序可以操作的内存空间)**; + +#### 传统 bio 多线程版本 + ```java public class TraditionalSocketDemo2 { @@ -201,40 +230,51 @@ public class TraditionalSocketDemo2 { } } ``` -能够解决传统的BIO问题,但是会出现:多少个客户端多少个线程,请求和线程的个数1:1关系;操作系统资源耗尽,服务端挂了。使用线程池虽然能控制服务线程的数量,但应对高并发量的访问时,依然会导致大量线程处于阻塞状态,严重影响服务效率。 -## Java中的NIO -NIO是一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存(区别于JVM的运行时数据区),然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的直接引用进行操作。这样能在一些场景显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。 -#### 1、Java NIO组件 -NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统IO是基于字节流和字符流进行操作(基于流),而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。 +能够解决传统的 BIO 问题,但是会出现:多少个客户端多少个线程,请求和线程的个数 1:1 关系;操作系统资源耗尽,服务端挂了。使用线程池虽然能控制服务线程的数量,但应对高并发量的访问时,依然会导致大量线程处于阻塞状态,严重影响服务效率。 + +## Java 中的 NIO + +NIO 是一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存(区别于 JVM 的运行时数据区),然后通过一个存储在 java 堆里面的 DirectByteBuffer 对象作为这块内存的直接引用进行操作。这样能在一些场景显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 + +#### 1、Java NIO 组件 + +NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统 IO 是基于字节流和字符流进行操作(基于流),而 NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。 ##### 1.1 Buffer -Buffer(缓冲区)是一个用于存储特定基本类型数据的容器。除了boolean外,其余每种基本类型都有一个对应的buffer类。Buffer类的子类有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer 。 + +Buffer(缓冲区)是一个用于存储特定基本类型数据的容器。除了 boolean 外,其余每种基本类型都有一个对应的 buffer 类。Buffer 类的子类有 ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer 。 ##### 1.2 Channel -Channel(通道)表示到实体,如硬件设备、文件、**网络套接字**或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接。Channel接口的常用实现类有FileChannel(对应文件IO)、DatagramChannel(对应UDP)、SocketChannel和ServerSocketChannel(对应TCP的客户端和服务器端)。**Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作**。 + +Channel(通道)表示到实体,如硬件设备、文件、**网络套接字**或可以执行一个或多个不同 I/O 操作(如读取或写入)的程序组件的开放的连接。Channel 接口的常用实现类有 FileChannel(对应文件 IO)、DatagramChannel(对应 UDP)、SocketChannel 和 ServerSocketChannel(对应 TCP 的客户端和服务器端)。**Channel 和 IO 中的 Stream(流)是差不多一个等级的。只不过 Stream 是单向的,譬如:InputStream, OutputStream.而 Channel 是双向的,既可以用来进行读操作,又可以用来进行写操作**。 ##### 1.3 Selector -Selector(选择器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。即用选择器,借助单一线程,就可对数量庞大的活动I/O通道实施监控和维护。 -写就绪相对有一点特殊,一般来说,你不应该注册写事件。写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。 +Selector(选择器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。即用选择器,借助单一线程,就可对数量庞大的活动 I/O 通道实施监控和维护。 + +写就绪相对有一点特殊,一般来说,你不应该注册写事件。写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个 CPU 资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。 -基于阻塞式I/O的多线程模型中,Server为每个Client连接创建一个处理线程,每个处理线程阻塞式等待可能达到的数据,一旦数据到达,则立即处理请求、返回处理结果并再次进入等待状态。由于每个Client连接有一个单独的处理线程为其服务,因此可保证良好的响应时间。但当系统负载增大(并发请求增多)时,Server端需要的线程数会增加,对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。 +基于阻塞式 I/O 的多线程模型中,Server 为每个 Client 连接创建一个处理线程,每个处理线程阻塞式等待可能达到的数据,一旦数据到达,则立即处理请求、返回处理结果并再次进入等待状态。由于每个 Client 连接有一个单独的处理线程为其服务,因此可保证良好的响应时间。但当系统负载增大(并发请求增多)时,Server 端需要的线程数会增加,对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。 -但是,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。 +但是,现代的操作系统和 CPU 在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个 CPU 有多个内核,不使用多任务可能是在浪费 CPU 能力。 -传统的IO处理方式,一个线程处理一个网络连接 +传统的 IO 处理方式,一个线程处理一个网络连接 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019112120352588.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) -NIO处理方式,一个线程可以管理过个网络连接 +NIO 处理方式,一个线程可以管理多个网络连接 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191121203602279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM4MDM4Mzk2,size_16,color_FFFFFF,t_70) -#### 2、NIO服务器端如何实现非阻塞? -服务器上所有Channel需要向Selector注册,而Selector则负责监视这些Socket的IO状态(观察者),当其中任意一个或者多个Channel具有可用的IO操作时,该Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个Channel具有可用的IO操作,并提供了selectedKeys()方法来返回这些Channel对应的SelectionKey集合(一个SelectionKey对应一个就绪的通道)。正是通过Selector,使得服务器端只需要不断地调用Selector实例的select()方法即可知道当前所有Channel是否有需要处理的IO操作。注:java NIO就是多路复用IO,jdk7之后底层是epoll模型。 -#### 3、Java NIO的简单实现 +#### 2、NIO 服务器端如何实现非阻塞? + +服务器上所有 Channel 需要向 Selector 注册,而 Selector 则负责监视这些 Socket 的 IO 状态(观察者),当其中任意一个或者多个 Channel 具有可用的 IO 操作时,该 Selector 的 select()方法将会返回大于 0 的整数,该整数值就表示该 Selector 上有多少个 Channel 具有可用的 IO 操作,并提供了 selectedKeys()方法来返回这些 Channel 对应的 SelectionKey 集合(一个 SelectionKey 对应一个就绪的通道)。正是通过 Selector,使得服务器端只需要不断地调用 Selector 实例的 select()方法即可知道当前所有 Channel 是否有需要处理的 IO 操作。注:java NIO 就是多路复用 IO,jdk7 之后底层是 epoll 模型。 + +#### 3、Java NIO 的简单实现 + ##### 3.1 服务端 + ```java public class NioServer { @@ -329,7 +369,9 @@ public class NioServer { } } ``` + ##### 3.2 客户端 + ```java public class NioClient { private static final String host = "127.0.0.1"; @@ -397,16 +439,14 @@ public class NioClient { } ``` -## Java中的AIO -JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO 2.0。Java正式提供了异步文件I/O操作,同时提供了与UNIX网络编程事件驱动I/O对应的AIO。NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。 - -异步通道获取获取操作结果方式: -1. 使用java.util.concurrent.Future类表示异步操作的结果; -2. 在执行异步操作的时候传入一个java.nio.channels,操作完成后胡回调CompletionHandler接口的实现类。 - -NIO 2.0的异步套接字通道是真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。 +## Java 中的 AIO +JDK1.7 升级了 NIO 类库,升级后的 NIO 类库被称为 NIO 2.0。Java 正式提供了异步文件 I/O 操作,同时提供了与 UNIX 网络编程事件驱动 I/O 对应的 AIO。NIO 2.0 引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。 +异步通道获取获取操作结果方式: +1. 使用 java.util.concurrent.Future 类表示异步操作的结果; +2. 在执行异步操作的时候传入一个 java.nio.channels,操作完成后会回调 CompletionHandler 接口的实现类。 +NIO 2.0 的异步套接字通道是真正的异步非阻塞 I/O,对应于 UNIX 网络编程中的事件驱动 I/O(AIO)。 diff --git "a/docs/Netty/IOTechnologyBase/\350\257\246\350\247\243selector\343\200\201poll\345\222\214epoll.md" "b/docs/Netty/IOTechnologyBase/\350\257\246\350\247\243selector\343\200\201poll\345\222\214epoll.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Netty/IOTechnologyBase/\350\257\246\350\247\243selector\343\200\201poll\345\222\214epoll.md" +++ "b/docs/Netty/IOTechnologyBase/\350\257\246\350\247\243selector\343\200\201poll\345\222\214epoll.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\273\204\344\273\266.md" "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\273\204\344\273\266.md" +++ "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ChannelPipeline\345\222\214ChannelHandler\347\273\204\344\273\266.md" "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ChannelPipeline\345\222\214ChannelHandler\347\273\204\344\273\266.md" index fcb5dbe4..ce32337e 100644 --- "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ChannelPipeline\345\222\214ChannelHandler\347\273\204\344\273\266.md" +++ "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/ChannelPipeline\345\222\214ChannelHandler\347\273\204\344\273\266.md" @@ -1 +1,260 @@ -努力编写中... \ No newline at end of file +Netty 的 ChannelPipeline 和 ChannelHandler 机制类似于 Servlet 和 Filter 过滤器,这类拦截器实际上是职责链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定制。 + +Servlet Filter 能够以声明的方式(web.xml 配置文件)插入到 HTTP 请求响应的处理过程中,用于拦截请求和响应,以便能够查看、提取或以某种方式操作正在客户端和服务器之间交换的数据。拦截器封装了业务定制逻辑,能够实现对 Web 应用程序 的预处理和事后处理。 + +Netty 的 Channel 过滤器 实现原理与 Servlet Filter 机制 一致,它将 Channel 的数据管道 抽象为 ChannelPipeline,消息在 ChannelPipeline 中流动和传递。ChannelPipeline 持有 I/O 事件拦截器 ChannelHandler 链表,由 ChannelHandler 链表 对 IO 事件 进行拦截和处理,可以通过新增和删除 ChannelHandler 来实现不同的业务逻辑定制,不需要对已有的 ChannelHandler 进行修改,能够实现对修改封闭和对扩展的支持。 + +下面我们对 ChannelPipeline 和 ChannelHandler 的功能进行简单地介绍,然后分析下其源码设计。 + +## ChannelPipeline 的功能和作用 + +ChannelPipeline 是 ChannelHandler 的容器,它负责 ChannelHandler 的管理、事件拦截与调度。 + +#### ChannelPipeline 的事件处理 + +下图展示了一个消息被 ChannelPipeline 的 ChannelHandler 链拦截和处理的全过程。 + +```java + * I/O Request + * via {@link Channel} or + * {@link ChannelHandlerContext} + * | + * +---------------------------------------------------+---------------+ + * | ChannelPipeline | | + * | \|/ | + * | +---------------------+ +-----------+----------+ | + * | | Inbound Handler N | | Outbound Handler 1 | | + * | +----------+----------+ +-----------+----------+ | + * | /|\ | | + * | | \|/ | + * | +----------+----------+ +-----------+----------+ | + * | | Inbound Handler N-1 | | Outbound Handler 2 | | + * | +----------+----------+ +-----------+----------+ | + * | /|\ . | + * | . . | + * | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()| + * | [ method call] [method call] | + * | . . | + * | . \|/ | + * | +----------+----------+ +-----------+----------+ | + * | | Inbound Handler 2 | | Outbound Handler M-1 | | + * | +----------+----------+ +-----------+----------+ | + * | /|\ | | + * | | \|/ | + * | +----------+----------+ +-----------+----------+ | + * | | Inbound Handler 1 | | Outbound Handler M | | + * | +----------+----------+ +-----------+----------+ | + * | /|\ | | + * +---------------+-----------------------------------+---------------+ + * | \|/ + * +---------------+-----------------------------------+---------------+ + * | | | | + * | [ Socket.read() ] [ Socket.write() ] | + * | | + * | Netty Internal I/O Threads (Transport Implementation) | + * +-------------------------------------------------------------------+ +``` + +从上图可以看出 消息读取和发送处理全流程为: + +1. 底层的 SocketChannel.read()方法 读取 ByteBuf,触发 ChannelRead 事件,由 IO 线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead(Object msg)方法,将消息传输到 ChannelPipeline 中。 +2. 消息依次被 HeadHandler、ChannelHandler1、ChannelHandler2 … TailHandler 拦截和处理,在这个过程中,任何 ChannelHandler 都可以中断当前的流程,结束消息的传递。 +3. 调用 ChannelHandlerContext 的 write 方法 发送消息,消息从 TailHandler 开始途经 ChannelHandlerN … ChannelHandler1、HeadHandler,最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的 Future 返回。 + +Netty 中的事件分为 Inbound 事件 和 Outbound 事件。Inbound 事件 通常由 I/O 线程 触发,例如 TCP 链路建立事件、链路关闭事件、读事件、异常通知事件等,它对应上图的左半部分。触发 Inbound 事件 的方法如下。 + +1. ChannelHandlerContext.fireChannelRegistered():Channel 注册事件; +2. ChannelHandlerContext.fireChannelActive():TCP 链路建立成功,Channel 激活事件; +3. ChannelHandlerContext.fireChannelRead(Object):读事件; +4. ChannelHandlerContext.fireChannelReadComplete():读操作完成通知事件; +5. ChannelHandlerContext.fireExceptionCaught(Throwable):异常通知事件; +6. ChannelHandlerContext.fireUserEventTriggered(Object):用户自定义事件; +7. ChannelHandlerContext.fireChannelWritabilityChanged():Channel 的可写状态变化; +8. ChannelHandlerContext.fireChannellnactive():TCP 连接关闭,链路不可用通知事件。 + +Outbound 事件 通常是由用户主动发起的 网络 IO 操作,例如用户发起的连接操作、绑定操作、消息发送等操作,它对应上图的右半部分。触发 Outbound 事件 的方法如下: + +1. ChannelHandlerContext.bind(SocketAddress, ChannelPromise):绑定本地地址事件; +2. ChannelHandlerContext.connect(SocketAddress, SocketAddress, ChannelPromise):连接服务端事件; +3. ChannelHandlerContext.write(Object, ChannelPromise):发送事件; +4. ChannelHandlerContext.flush():刷新事件; +5. ChannelHandlerContext.read():读事件; +6. ChannelHandlerContext.disconnect(ChannelPromise):断开连接事件; +7. ChannelHandlerContext.close(ChannelPromise):关闭当前 Channel 事件。 + +#### ChannelPipeline 自定义拦截器 + +ChannelPipeline 通过 ChannelHandler 来实现事件的拦截和处理,由于 ChannelHandler 中的事件种类繁多,不同的 ChannelHandler 可能只需要关心其中的个别事件,所以,自定义的 ChannelHandler 只需要继承 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter,覆盖自己关心的方法即可。 + +下面的两个示例分别展示了:拦截 Channel Active 事件,打印 TCP 链路建立成功日志,和 链路关闭的时候释放资源,代码如下。 + +```java +public class MyInboundHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelActive(ChannelHandlerContext context) { + System.out.println("欢迎来到,LPL!"); + context.fireChannelActive(); + } +} + +public class MyOutboundHandler extends ChannelOutboundHandlerAdapter { + @Override + public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { + System.out.println("游戏结束..."); + ctx.close(); + } +} +``` + +#### 构建 pipeline + +使用 Netty 时,用户不需要自己创建 pipeline,因为使用 ServerBootstrap 或者 Bootstrap 进行配置后,Netty 会为每个 Channel 连接 创建一个独立的 pipeline。我们只需将自定义的 ChannelHandler 加入到 pipeline 即可。相关代码如下。 + +```java +ServerBootstrap server = new ServerBootstrap(); +server.childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + /** 解析自定义协议 */ + pipeline.addLast(new MyDecoder()); + pipeline.addLast(new MyEncoder()); + pipeline.addLast(new SocketHandler()); + /** 解析Http请求 */ + pipeline.addLast(new HttpServerCodec()); + //主要是将同一个http请求或响应的多个消息对象变成一个 fullHttpRequest完整的消息对象 + pipeline.addLast(new HttpObjectAggregator(64 * 1024)); + //主要用于处理大数据流,比如一个1G大小的文件如果你直接传输肯定会撑暴jvm内存的 ,加上这个handler我们就不用考虑这个问题了 + pipeline.addLast(new ChunkedWriteHandler()); + } +}); +``` + +对于类似编解码这样的 ChannelHandler,它存在先后顺序,例如 MessageToMessageDecoder,在它之前往往需要有 ByteToMessageDecoder 将 ByteBuf 解码为对象,然后将对象做二次解码 得到最终的 POJO 对象。pipeline 支持指定位置添加或者删除 ChannelHandler。 + +#### ChannelPipeline 的主要特性 + +ChannelPipeline 支持运行时动态的添加或者删除 ChannelHandler,在某些场景下这个特性非常实用。例如当业务高峰期需要对系统做拥塞保护时,就可以根据当前的系统时间进行判断,如果处于业务高峰期,则动态地将 系统拥塞保护 ChannelHandler 添加到当前的 ChannelPipeline 中,当高峰期过去之后,再动态删除 拥塞保护 ChannelHandler。 + +ChannelPipeline 是线程安全的,这意味着 N 个业务线程可以并发地操作 ChannelPipeline 而不存在多线程并发问题。但 ChannelHandler 不是线程安全的,这意味着 我们需要自己保证 ChannelHandler 的线程安全。 + +## ChannelPipeline 源码解析 + +ChannelPipeline 的代码比较简单,它实际上是一个 ChannelHandler 容器,内部维护了一个 ChannelHandler 的链表和迭代器,可以方便地进行 ChannelHandler 的 CRUD。 + +另外一个比较重要的部分是,当发生某个 I/O 事件 时,如 链路建立、链路关闭、读写操作 等,都会产一个事件,事件在 pipeline 中传播和处理,它是事件处理的总入口。由于 网络 I/O 相关的事件有限,因此 Netty 对这些事件进行了统一抽象,Netty 提供的 和用户自定义的 ChannelHandler 会对感兴趣的事件进行拦截和处理。 + +pipeline 中以 fireXXX 命名的方法都是从 I/O 线程 流向 用户业务 Handler 的 inbound 事件,它们的实现因功能而异,但是处理步骤类似,都是 调用 HeadHandler 对应的 fireXXX 方法,然后执行事件相关的逻辑操作。 + +```java +public interface ChannelPipeline + extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable> { + + /** + * 管理 ChannelHandler 的api + */ + ChannelPipeline addFirst(String name, ChannelHandler handler); + + ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler); + + ChannelPipeline addLast(String name, ChannelHandler handler); + + ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler); + + ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler); + + ChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, ChannelHandler handler); + + ChannelPipeline addAfter(String baseName, String name, ChannelHandler handler); + + ChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, ChannelHandler handler); + + ChannelPipeline addFirst(ChannelHandler... handlers); + + ChannelPipeline addFirst(EventExecutorGroup group, ChannelHandler... handlers); + + ChannelPipeline addLast(ChannelHandler... handlers); + + ChannelPipeline addLast(EventExecutorGroup group, ChannelHandler... handlers); + + ChannelPipeline remove(ChannelHandler handler); + + ChannelHandler remove(String name); + + T remove(Class handlerType); + + ChannelHandler removeFirst(); + + ChannelHandler removeLast(); + + ChannelPipeline replace(ChannelHandler oldHandler, String newName, ChannelHandler newHandler); + + ChannelHandler replace(String oldName, String newName, ChannelHandler newHandler); + + T replace(Class oldHandlerType, String newName, ChannelHandler newHandler); + + ChannelHandler first(); + + ChannelHandler last(); + + ChannelHandler get(String name); + + T get(Class handlerType); + + /** + * 处理 I/O事件 的api + */ + @Override + ChannelPipeline fireChannelRegistered(); + + @Override + ChannelPipeline fireChannelUnregistered(); + + @Override + ChannelPipeline fireChannelActive(); + + @Override + ChannelPipeline fireChannelInactive(); + + @Override + ChannelPipeline fireExceptionCaught(Throwable cause); + + @Override + ChannelPipeline fireUserEventTriggered(Object event); + + @Override + ChannelPipeline fireChannelRead(Object msg); + + @Override + ChannelPipeline fireChannelReadComplete(); + + @Override + ChannelPipeline fireChannelWritabilityChanged(); + + @Override + ChannelPipeline flush(); +} +``` + +## ChannelHandler 的功能和作用 + +ChannelHandler 负责对 I/O 事件 进行拦截处理,它可以选择性地 拦截处理感兴趣的事件,也可以透传和终止事件的传递。基于 ChannelHandler 接口,我们可以方便地进行业务逻辑定制,如 打印日志、统一封装异常信息、性能统计和消息编解码等。 + +#### ChannelHandlerAdapter + +大部分 ChannelHandler 都会选择性 拦截处理感兴趣的 I/O 事件,忽略其他事件,然后交由下一个 ChannelHandler 进行拦截处理。这会导致一个问题:自定义 ChannelHandler 必须要实现 ChannelHandler 的所有接口,包括它不关心的那些事件处理接口,这会导致用户代码的冗余和臃肿,代码的可维护性也会变差。 + +为了解决这个问题,Netty 提供了 ChannelHandlerAdapter 基类,和 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter 两个实现类,如果 自定义 ChannelHandler 关心某个事件,只需要继承 ChannelInboundHandlerAdapter / ChannelOutboundHandlerAdapter 覆盖对应的方法即可,对于不关心的,可以直接继承使用父类的方法,这样子类的代码就会非常简洁清晰。 + +## ChannelHandler 组件 的类结构 + +相对于 ByteBuf 和 Channel,ChannelHandler 的类继承关系稍微简单些,但是它的子类非常多,功能各异,主要可以分为如下四类。 + +1. ChannelPipeline 的系统 ChannelHandler,用于 I/O 操作 和对事件进行预处理,对用户不可见,这类 ChannelHandler 主要包括 HeadHandler 和 TailHandler; +2. 编解码 ChannelHandler,如 MessageToMessageEncoder、MessageToMessageDecoder、MessageToMessageCodec; +3. 其他系统功能性 ChannelHandler,如 流量整型 Handler、读写超时 Handler、日志 Handler 等; +4. 自定义 ChannelHandler。 + +ChannelHandler 组件 的核心类及常用类的类图如下。 + +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ChannelHandler组件.png) diff --git "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Channel\345\222\214Unsafe\347\273\204\344\273\266.md" "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Channel\345\222\214Unsafe\347\273\204\344\273\266.md" index fcb5dbe4..e5438f3a 100644 --- "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Channel\345\222\214Unsafe\347\273\204\344\273\266.md" +++ "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Channel\345\222\214Unsafe\347\273\204\344\273\266.md" @@ -1 +1,963 @@ -努力编写中... \ No newline at end of file +类似于 java.nio 包 的 Channel,Netty 提供了自己的 Channel 和其子类实现,用于异步 I/O 操作 等。Unsafe 是 Channel 的内部接口,聚合在 Channel 中协助进行网络读写相关的操作,因为它的设计初衷就是 Channel 的内部辅助类,不应该被 Netty 框架 的上层使用者调用,所以被命名为 Unsafe。 + +## Channel 组件 + +Netty 的 **Channel 组件 是 Netty 对网络操作的封装**,**如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty 框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外,Netty 并没有直接使用 java.nio 包 的 SocketChannel 和 ServerSocketChannel,而是**使用 NioSocketChannel 和 NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel 接口 的 API 开始分析,然后看一下其重要子类的源码实现。 + +为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。 +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty的Channel组件.png) + +#### Channel 接口 + +```java +public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable { + + /** + * Channel 需要注册到 EventLoop 的多路复用器上,用于处理 I/O事件, + * EventLoop 实际上就是处理网络读写事件的 Reactor线程。 + */ + EventLoop eventLoop(); + + /** + * ChannelMetadata 封装了 TCP参数配置 + */ + ChannelMetadata metadata(); + + /** + * 对于服务端Channel而言,它的父Channel为空; + * 对于客户端Channel,它的 父Channel 就是创建它的 ServerSocketChannel + */ + Channel parent(); + + /** + * 每个 Channel 都有一个全局唯一标识 + */ + ChannelId id(); + + /** + * 获取当前 Channel 的配置信息,如 CONNECT_TIMEOUT_MILLIS + */ + ChannelConfig config(); + + /** + * 当前 Channel 是否已经打开 + */ + boolean isOpen(); + + /** + * 当前 Channel 是否已注册进 EventLoop + */ + boolean isRegistered(); + + /** + * 当前 Channel 是否已激活 + */ + boolean isActive(); + + /** + * 当前 Channel 的本地绑定地址 + */ + SocketAddress localAddress(); + + /** + * 当前 Channel 的远程绑定地址 + */ + SocketAddress remoteAddress(); + + /** + * 当前 Channel 是否可写 + */ + boolean isWritable(); + + /** + * 当前 Channel 内部的 Unsafe对象 + */ + Unsafe unsafe(); + + /** + * 当前 Channel 持有的 ChannelPipeline + */ + ChannelPipeline pipeline(); + + /** + * 从当前 Channel 中读取数据到第一个 inbound缓冲区 中,如果数据被成功读取, + * 触发ChannelHandler.channelRead(ChannelHandlerContext,Object)事件。 + * 读取操作API调用完成之后,紧接着会触发ChannelHandler.channelReadComplete(ChannelHandlerContext)事件, + * 这样业务的ChannelHandler可以决定是否需要继续读取数据。如果己经有读操作请求被挂起,则后续的读操作会被忽略。 + */ + @Override + Channel read(); + + /** + * 将之前写入到发送环形数组中的消息全部写入到目标Chanel中,发送给通信对方 + */ + @Override + Channel flush(); +} +``` + +#### AbstractChannel + +```java +public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { + + // 父Channel + private final Channel parent; + // Channel的全局唯一标识 + private final ChannelId id; + // 内部辅助类 Unsafe + private final Unsafe unsafe; + // Netty 会为每一个 channel 创建一个 pipeline + private final DefaultChannelPipeline pipeline; + + // 本地地址 + private volatile SocketAddress localAddress; + // 远程主机地址 + private volatile SocketAddress remoteAddress; + // 注册到了哪个 EventLoop 上 + private volatile EventLoop eventLoop; + // 是否已注册 + private volatile boolean registered; + + /** + * channnel 会将 网络IO操作 触发到 ChannelPipeline 对应的事件方法。 + * Netty 基于事件驱动,我们也可以理解为当 Chnanel 进行 IO操作 时会产生对应的IO 事件, + * 然后驱动事件在 ChannelPipeline 中传播,由对应的 ChannelHandler 对事件进行拦截和处理, + * 不关心的事件可以直接忽略 + */ + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return pipeline.bind(localAddress); + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return pipeline.bind(localAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return pipeline.connect(remoteAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return pipeline.connect(remoteAddress, localAddress); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return pipeline.connect(remoteAddress, promise); + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return pipeline.connect(remoteAddress, localAddress, promise); + } + + @Override + public ChannelFuture disconnect() { + return pipeline.disconnect(); + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return pipeline.disconnect(promise); + } + + @Override + public ChannelFuture close() { + return pipeline.close(); + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return pipeline.close(promise); + } + + @Override + public ChannelFuture deregister() { + return pipeline.deregister(); + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return pipeline.deregister(promise); + } + + @Override + public Channel flush() { + pipeline.flush(); + return this; + } + + @Override + public Channel read() { + pipeline.read(); + return this; + } + + @Override + public ChannelFuture write(Object msg) { + return pipeline.write(msg); + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return pipeline.write(msg, promise); + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return pipeline.writeAndFlush(msg); + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return pipeline.writeAndFlush(msg, promise); + } +} +``` + +#### AbstractNioChannel + +```java +public abstract class AbstractNioChannel extends AbstractChannel { + + // AbstractNioChannel 是 NioSocketChannel和NioServerSocketChannel 的公共父类,所以定义 + // 了一个 java.nio 的 SocketChannel 和 ServerSocketChannel 的公共父类 SelectableChannel, + // 用于设置 SelectableChannel参数 和进行 IO操作 + private final SelectableChannel ch; + // 它代表了 JDK 的 SelectionKey.OP_READ + protected final int readInterestOp; + // 该 SelectionKey 是 Channel 注册到 EventLoop 后返回的, + // 由于 Channel 会面临多个业务线程的并发写操作,当 SelectionKey 被修改了, + // 需要让其他业务线程感知到变化,所以使用volatile保证修改的可见性 + volatile SelectionKey selectionKey; + + /** + * Channel 的注册 + */ + @Override + protected void doRegister() throws Exception { + boolean selected = false; + for (;;) { + try { + selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); + return; + } catch (CancelledKeyException e) { + if (!selected) { + // Force the Selector to select now as the "canceled" SelectionKey may still be + // cached and not removed because no Select.select(..) operation was called yet. + eventLoop().selectNow(); + selected = true; + } else { + // We forced a select operation on the selector before but the SelectionKey is still cached + // for whatever reason. JDK bug ? + throw e; + } + } + } + } + + protected SelectableChannel javaChannel() { + return ch; + } + + @Override + protected void doBeginRead() throws Exception { + // Channel.read() 或 ChannelHandlerContext.read() 被调用 + final SelectionKey selectionKey = this.selectionKey; + if (!selectionKey.isValid()) { + return; + } + + readPending = true; + + final int interestOps = selectionKey.interestOps(); + if ((interestOps & readInterestOp) == 0) { + selectionKey.interestOps(interestOps | readInterestOp); + } + } +} +``` + +#### NioServerSocketChannel + +```java +public class NioServerSocketChannel extends AbstractNioMessageChannel + implements io.netty.channel.socket.ServerSocketChannel { + + // java.nio 包的内容,用于获取 java.nio.channels.ServerSocketChannel 实例 + private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); + + private static ServerSocketChannel newSocket(SelectorProvider provider) { + try { + /** + * 获取的是 java.nio.channels.ServerSocketChannel 实例 + */ + return provider.openServerSocketChannel(); + } catch (IOException e) { + throw new ChannelException("Failed to open a server socket.", e); + } + } + + /** + * Create a new instance + */ + public NioServerSocketChannel() { + this(newSocket(DEFAULT_SELECTOR_PROVIDER)); + } + + /** + * 在父类中完成了 非阻塞IO的配置,及事件的注册 + */ + public NioServerSocketChannel(ServerSocketChannel channel) { + super(null, channel, SelectionKey.OP_ACCEPT); + config = new NioServerSocketChannelConfig(this, javaChannel().socket()); + } + + /** + * 对 NioServerSocketChannel 来说,它的读取操作就是接收客户端的连接,创建 NioSocketChannel对象 + */ + @Override + protected int doReadMessages(List buf) throws Exception { + // 首先通过 ServerSocketChannel 的 accept()方法 接收新的客户端连接, + // 获取 java.nio.channels.SocketChannel 对象 + SocketChannel ch = SocketUtils.accept(javaChannel()); + + try { + // 如果获取到客户端连接对象 SocketChannel,则利用当前的 NioServerSocketChannel、EventLoop + // 和 SocketChannel 创建新的 NioSocketChannel,并添加到 buf 中 + if (ch != null) { + buf.add(new NioSocketChannel(this, ch)); + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } +} +``` + +#### NioSocketChannel + +```java +public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel { + + // 与 NioServerSocketChannel 一样,也依赖了 java.nio包 的API + private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider(); + + /** + * 从这里可以看出,NioSocketChannel 对 java.nio.channels.SocketChannel 做了进一步封装 + * 使其 适用于 Netty框架 + */ + private static SocketChannel newSocket(SelectorProvider provider) { + try { + return provider.openSocketChannel(); + } catch (IOException e) { + throw new ChannelException("Failed to open a socket.", e); + } + } + + /** + * Create a new instance + */ + public NioSocketChannel() { + this(DEFAULT_SELECTOR_PROVIDER); + } + + public NioSocketChannel(SelectorProvider provider) { + this(newSocket(provider)); + } + + public NioSocketChannel(SocketChannel socket) { + this(null, socket); + } + + public NioSocketChannel(Channel parent, SocketChannel socket) { + // 在父类中完成 非阻塞IO的配置,注册事件 + super(parent, socket); + config = new NioSocketChannelConfig(this, socket.socket()); + } + + @Override + protected SocketChannel javaChannel() { + return (SocketChannel) super.javaChannel(); + } + + @Override + public boolean isActive() { + SocketChannel ch = javaChannel(); + return ch.isOpen() && ch.isConnected(); + } + + /** + * 与远程服务器建立连接 + */ + @Override + protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + if (localAddress != null) { + doBind0(localAddress); + } + + boolean success = false; + try { + // 根据远程地址建立TCP连接,对连接结果进行判断 + boolean connected = SocketUtils.connect(javaChannel(), remoteAddress); + if (!connected) { + selectionKey().interestOps(SelectionKey.OP_CONNECT); + } + success = true; + return connected; + } finally { + if (!success) { + doClose(); + } + } + } + + /** + * 关闭 Channel + */ + @Override + protected void doClose() throws Exception { + super.doClose(); + javaChannel().close(); + } + + /** + * 从 Channel 中读取数据 + */ + @Override + protected int doReadBytes(ByteBuf byteBuf) throws Exception { + final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); + allocHandle.attemptedBytesRead(byteBuf.writableBytes()); + return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead()); + } + + @Override + protected int doWriteBytes(ByteBuf buf) throws Exception { + final int expectedWrittenBytes = buf.readableBytes(); + return buf.readBytes(javaChannel(), expectedWrittenBytes); + } + + /** + * 向 Channel 中写数据 + */ + @Override + protected void doWrite(ChannelOutboundBuffer in) throws Exception { + SocketChannel ch = javaChannel(); + int writeSpinCount = config().getWriteSpinCount(); + do { + if (in.isEmpty()) { + // All written so clear OP_WRITE + clearOpWrite(); + // Directly return here so incompleteWrite(...) is not called. + return; + } + + // Ensure the pending writes are made of ByteBufs only. + int maxBytesPerGatheringWrite = ((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite(); + ByteBuffer[] nioBuffers = in.nioBuffers(1024, maxBytesPerGatheringWrite); + int nioBufferCnt = in.nioBufferCount(); + + // Always us nioBuffers() to workaround data-corruption. + // See https://github.com/netty/netty/issues/2761 + switch (nioBufferCnt) { + case 0: + // We have something else beside ByteBuffers to write so fallback to normal writes. + writeSpinCount -= doWrite0(in); + break; + case 1: { + // Only one ByteBuf so use non-gathering write + // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need + // to check if the total size of all the buffers is non-zero. + ByteBuffer buffer = nioBuffers[0]; + int attemptedBytes = buffer.remaining(); + final int localWrittenBytes = ch.write(buffer); + if (localWrittenBytes <= 0) { + incompleteWrite(true); + return; + } + adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite); + in.removeBytes(localWrittenBytes); + --writeSpinCount; + break; + } + default: { + // Zero length buffers are not added to nioBuffers by ChannelOutboundBuffer, so there is no need + // to check if the total size of all the buffers is non-zero. + // We limit the max amount to int above so cast is safe + long attemptedBytes = in.nioBufferSize(); + final long localWrittenBytes = ch.write(nioBuffers, 0, nioBufferCnt); + if (localWrittenBytes <= 0) { + incompleteWrite(true); + return; + } + // Casting to int is safe because we limit the total amount of data in the nioBuffers to int above. + adjustMaxBytesPerGatheringWrite((int) attemptedBytes, (int) localWrittenBytes, + maxBytesPerGatheringWrite); + in.removeBytes(localWrittenBytes); + --writeSpinCount; + break; + } + } + } while (writeSpinCount > 0); + + incompleteWrite(writeSpinCount < 0); + } +} +``` + +## Unsafe 功能简介 + +Unsafe 接口 实际上是 **Channel 接口 的辅助接口**,它不应该被用户代码直接调用。**实际的 IO 读写操作 都是由 Unsafe 接口 负责完成的**。 + +```java +public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable { + + interface Unsafe { + + /** + * 返回绑定的 本地地址 + */ + SocketAddress localAddress(); + + /** + * 返回绑定的 远程地址 + */ + SocketAddress remoteAddress(); + + /** + * 将 Channel 注册到 EventLoop 上 + */ + void register(EventLoop eventLoop, ChannelPromise promise); + + /** + * 绑定 本地地址 到 Channel 上 + */ + void bind(SocketAddress localAddress, ChannelPromise promise); + + /** + * 连接到远程服务器 + */ + void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise); + + /** + * 断开连接 + */ + void disconnect(ChannelPromise promise); + + /** + * 关闭 Channel + */ + void close(ChannelPromise promise); + + /** + * 读就绪 网络事件 + */ + void beginRead(); + + /** + * 发送数据 + */ + void write(Object msg, ChannelPromise promise); + + /** + * 将缓冲区的数据 刷到 Channel + */ + void flush(); + } +} +``` + +#### AbstractUnsafe + +```java +public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { + + protected abstract class AbstractUnsafe implements Unsafe { + /** + * 将当前 Unsafe 对应的 Channel 注册到 EventLoop 的多路复用器上, + * 然后调用 DefaultChannelPipeline 的 fireChannelRegistered()方法, + * 如果 Channel 被激活 则调用 DefaultChannelPipeline 的 fireChannelActive()方法 + */ + @Override + public final void register(EventLoop eventLoop, final ChannelPromise promise) { + if (eventLoop == null) { + throw new NullPointerException("eventLoop"); + } + if (isRegistered()) { + promise.setFailure(new IllegalStateException("registered to an event loop already")); + return; + } + if (!isCompatible(eventLoop)) { + promise.setFailure( + new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); + return; + } + + AbstractChannel.this.eventLoop = eventLoop; + + if (eventLoop.inEventLoop()) { + register0(promise); + } else { + try { + eventLoop.execute(new Runnable() { + @Override + public void run() { + register0(promise); + } + }); + } catch (Throwable t) { + logger.warn( + "Force-closing a channel whose registration task was not accepted by an event loop: {}", + AbstractChannel.this, t); + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + } + + private void register0(ChannelPromise promise) { + try { + // check if the channel is still open as it could be closed in the mean time when the register + // call was outside of the eventLoop + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + boolean firstRegistration = neverRegistered; + doRegister(); + neverRegistered = false; + registered = true; + + // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the + // user may already fire events through the pipeline in the ChannelFutureListener. + pipeline.invokeHandlerAddedIfNeeded(); + + safeSetSuccess(promise); + pipeline.fireChannelRegistered(); + // Only fire a channelActive if the channel has never been registered. This prevents firing + // multiple channel actives if the channel is deregistered and re-registered. + if (isActive()) { + if (firstRegistration) { + pipeline.fireChannelActive(); + } else if (config().isAutoRead()) { + // This channel was registered before and autoRead() is set. This means we need to begin read + // again so that we process inbound data. + // + // See https://github.com/netty/netty/issues/4805 + beginRead(); + } + } + } catch (Throwable t) { + // Close the channel directly to avoid FD leak. + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + + /** + * 绑定指定的端口,对于服务端 用于绑定监听端口, + * 对于客户端,主要用于指定 客户端Channel 的本地绑定Socket地址。 + */ + @Override + public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { + assertEventLoop(); + + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + // See: https://github.com/netty/netty/issues/576 + if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) && + localAddress instanceof InetSocketAddress && + !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() && + !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) { + // Warn a user about the fact that a non-root user can't receive a + // broadcast packet on *nix if the socket is bound on non-wildcard address. + logger.warn( + "A non-root user can't receive a broadcast packet if the socket " + + "is not bound to a wildcard address; binding to a non-wildcard " + + "address (" + localAddress + ") anyway as requested."); + } + + boolean wasActive = isActive(); + try { + doBind(localAddress); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (!wasActive && isActive()) { + invokeLater(new Runnable() { + @Override + public void run() { + pipeline.fireChannelActive(); + } + }); + } + + safeSetSuccess(promise); + } + + /** + * 客户端 或 服务端,主动关闭连接 + */ + @Override + public final void disconnect(final ChannelPromise promise) { + assertEventLoop(); + + if (!promise.setUncancellable()) { + return; + } + + boolean wasActive = isActive(); + try { + doDisconnect(); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (wasActive && !isActive()) { + invokeLater(new Runnable() { + @Override + public void run() { + pipeline.fireChannelInactive(); + } + }); + } + + safeSetSuccess(promise); + closeIfClosed(); // doDisconnect() might have closed the channel + } + + /** + * 在链路关闭之前需要首先判断是否处于刷新状态,如果处于刷新状态说明还有消息尚 + * 未发送出去,需要等到所有消息发送完成再关闭链路,因此,将关闭操作封装成Runnable稍后再执行 + */ + @Override + public final void close(final ChannelPromise promise) { + assertEventLoop(); + + close(promise, CLOSE_CLOSED_CHANNEL_EXCEPTION, CLOSE_CLOSED_CHANNEL_EXCEPTION, false); + } + + /** + * 本方法实际上将消息添加到环形发送数组中,并不是真正的写Channel + */ + @Override + public final void write(Object msg, ChannelPromise promise) { + assertEventLoop(); + + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null) { + // If the outboundBuffer is null we know the channel was closed and so + // need to fail the future right away. If it is not null the handling of the rest + // will be done in flush0() + // See https://github.com/netty/netty/issues/2362 + safeSetFailure(promise, newWriteException(initialCloseCause)); + // release message now to prevent resource-leak + ReferenceCountUtil.release(msg); + return; + } + + int size; + try { + msg = filterOutboundMessage(msg); + size = pipeline.estimatorHandle().size(msg); + if (size < 0) { + size = 0; + } + } catch (Throwable t) { + safeSetFailure(promise, t); + ReferenceCountUtil.release(msg); + return; + } + + outboundBuffer.addMessage(msg, size, promise); + } + + /** + * 将缓冲区中待发送的消息全部写入 Channel,并发送给通信对方 + */ + @Override + public final void flush() { + assertEventLoop(); + + ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null) { + return; + } + + outboundBuffer.addFlush(); + flush0(); + } + + @SuppressWarnings("deprecation") + protected void flush0() { + if (inFlush0) { + // Avoid re-entrance + return; + } + + final ChannelOutboundBuffer outboundBuffer = this.outboundBuffer; + if (outboundBuffer == null || outboundBuffer.isEmpty()) { + return; + } + + inFlush0 = true; + + // Mark all pending write requests as failure if the channel is inactive. + if (!isActive()) { + try { + if (isOpen()) { + outboundBuffer.failFlushed(FLUSH0_NOT_YET_CONNECTED_EXCEPTION, true); + } else { + // Do not trigger channelWritabilityChanged because the channel is closed already. + outboundBuffer.failFlushed(newFlush0Exception(initialCloseCause), false); + } + } finally { + inFlush0 = false; + } + return; + } + + try { + doWrite(outboundBuffer); + } catch (Throwable t) { + if (t instanceof IOException && config().isAutoClose()) { + /** + * Just call {@link #close(ChannelPromise, Throwable, boolean)} here which will take care of + * failing all flushed messages and also ensure the actual close of the underlying transport + * will happen before the promises are notified. + * + * This is needed as otherwise {@link #isActive()} , {@link #isOpen()} and {@link #isWritable()} + * may still return {@code true} even if the channel should be closed as result of the exception. + */ + initialCloseCause = t; + close(voidPromise(), t, newFlush0Exception(t), false); + } else { + try { + shutdownOutput(voidPromise(), t); + } catch (Throwable t2) { + initialCloseCause = t; + close(voidPromise(), t2, newFlush0Exception(t), false); + } + } + } finally { + inFlush0 = false; + } + } + } +} +``` + +#### AbstractNioUnsafe + +AbstractNioUnsafe 是 AbstractUnsafe 类 的 NIO 实现,它主要实现了 connect 、finishConnect 等方法。 + +```java +public abstract class AbstractNioChannel extends AbstractChannel { + + /** + * 获取当前的连接状态进行缓存,然后发起连接操作。 + */ + @Override + public final void connect( + final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + try { + if (connectPromise != null) { + // Already a connect in process. + throw new ConnectionPendingException(); + } + + boolean wasActive = isActive(); + if (doConnect(remoteAddress, localAddress)) { + fulfillConnectPromise(promise, wasActive); + } else { + connectPromise = promise; + requestedRemoteAddress = remoteAddress; + + // Schedule connect timeout. + int connectTimeoutMillis = config().getConnectTimeoutMillis(); + if (connectTimeoutMillis > 0) { + connectTimeoutFuture = eventLoop().schedule(new Runnable() { + @Override + public void run() { + ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise; + ConnectTimeoutException cause = + new ConnectTimeoutException("connection timed out: " + remoteAddress); + if (connectPromise != null && connectPromise.tryFailure(cause)) { + close(voidPromise()); + } + } + }, connectTimeoutMillis, TimeUnit.MILLISECONDS); + } + + promise.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isCancelled()) { + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + close(voidPromise()); + } + } + }); + } + } catch (Throwable t) { + promise.tryFailure(annotateConnectException(t, remoteAddress)); + closeIfClosed(); + } + } + + /** + * 对 TCP三次握手连接结果 进行判断 + */ + @Override + public final void finishConnect() { + // Note this method is invoked by the event loop only if the connection attempt was + // neither cancelled nor timed out. + + assert eventLoop().inEventLoop(); + + try { + boolean wasActive = isActive(); + doFinishConnect(); + fulfillConnectPromise(connectPromise, wasActive); + } catch (Throwable t) { + fulfillConnectPromise(connectPromise, annotateConnectException(t, requestedRemoteAddress)); + } finally { + // Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used + // See https://github.com/netty/netty/issues/1770 + if (connectTimeoutFuture != null) { + connectTimeoutFuture.cancel(false); + } + connectPromise = null; + } + } + } +} +``` diff --git "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/EventLoop\345\222\214EventLoopGroup\347\273\204\344\273\266.md" "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/EventLoop\345\222\214EventLoopGroup\347\273\204\344\273\266.md" deleted file mode 100644 index fcb5dbe4..00000000 --- "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/EventLoop\345\222\214EventLoopGroup\347\273\204\344\273\266.md" +++ /dev/null @@ -1 +0,0 @@ -努力编写中... \ No newline at end of file diff --git "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/EventLoop\347\273\204\344\273\266.md" "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/EventLoop\347\273\204\344\273\266.md" new file mode 100644 index 00000000..16cc8787 --- /dev/null +++ "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/EventLoop\347\273\204\344\273\266.md" @@ -0,0 +1,161 @@ +## Netty 的线程模型 + +Netty 线程模型 的设计,也是基于 Reactor 模型,尽管不同的 NIO 框架 对于 Reactor 模式 的实现存在差异,但本质上还是遵循了 Reactor 的基础线程模型。 + +#### Reactor 单线程模型 + +Reactor 单线程模型,是指所有的 I/O 操作 都在同一个 NIO 线程 上完成。NIO 线程 的职责如下。 + +1. 作为 NIO 服务端,接收客户端的 TCP 连接; +2. 作为 NIO 客户端,向服务端发起 TCP 连接; +3. 读取通信对端的请求或者应答消息; +4. 向通信对端发送消息请求或者应答消息。 + +理论上一个 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 线程 可能会处理不过来,成为系统的性能瓶颈。 + +#### Reactor 主从多线程模型 + +主从 Reactor 多线程模型的特点是,服务端用于接收客户端连接的是一个独立的 NIO 线程池。**Acceptor 线程 与客户端建立 TCP 连接 后,将新的 SocketChannel 注册到 NIO 线程池 的某个 NIO 线程 上,由该 NIO 线程 负责轮询 SocketChannel 上的 IO 事件,并进行事件处理**。 + +利用 主从多线程模型,可以解决一个服务端监听线程无法有效处理所有客户端连接的性能不足问题。在 Netty 的官方 Demo 中,也是推荐使用该线程模型。 + +#### Netty 多线程编程最佳实践 + +1. **如果业务逻辑比较简单,并且没有 数据库操作、线程阻塞的磁盘操作、网路操作等,可以直接在 NIO 线程 上完成业务逻辑编排,不需要切换到用户线程;** +2. **如果业务逻辑比较复杂,不要在 NIO 线程 上完成,建议将解码后的 POJO 消息 封装成 Task,分发到 业务线程池 中由业务线程执行,以保证 NIO 线程 尽快被释放,处理其他的 I/O 操作。** +3. **由于用户场景不同,对于一些复杂系统,很难根据 理论公式 计算出最优线程配置,只能是 结合公式给出一个相对合理的范围,然后对范围内的数据进行性能测试,选择相对最优配置。** + +## NioEventLoop 源码解析 + +```java +public final class NioEventLoop extends SingleThreadEventLoop { + + /** + * 作为 NIO框架 的 Reactor线程,NioEventLoop 需要处理 网络I/O读写事件,因此它必 + * 须聚合一个多路复用器对象 Selector + */ + private Selector selector; + // 通过 provider.open() 从操作系统底层获取 Selector实例 + private final SelectorProvider provider; + + /** + * 轮询 事件就绪的channel,进行 IO事件处理 + */ + private void processSelectedKeys() { + if (selectedKeys != null) { + processSelectedKeysOptimized(); + } else { + processSelectedKeysPlain(selector.selectedKeys()); + } + } + + private void processSelectedKeysPlain(Set selectedKeys) { + // check if the set is empty and if so just return to not create garbage by + // creating a new Iterator every time even if there is nothing to process. + // See https://github.com/netty/netty/issues/597 + if (selectedKeys.isEmpty()) { + return; + } + + // 这些代码在 nio编程中应该很熟悉咯 + Iterator i = selectedKeys.iterator(); + for (;;) { + final SelectionKey k = i.next(); + final Object a = k.attachment(); + i.remove(); + + if (a instanceof AbstractNioChannel) { + processSelectedKey(k, (AbstractNioChannel) a); + } else { + @SuppressWarnings("unchecked") + NioTask task = (NioTask) a; + processSelectedKey(k, task); + } + + if (!i.hasNext()) { + break; + } + + if (needsToSelectAgain) { + selectAgain(); + selectedKeys = selector.selectedKeys(); + + // Create the iterator again to avoid ConcurrentModificationException + if (selectedKeys.isEmpty()) { + break; + } else { + i = selectedKeys.iterator(); + } + } + } + } + + /** + * 轮询 事件就绪的channel,进行 IO事件处理 + */ + private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { + // 获取 channel 的内部辅助类 Unsafe,通过 Unsafe 进行IO事件处理 + final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); + if (!k.isValid()) { + final EventLoop eventLoop; + try { + // 获取要处理 channel 所绑定的 eventLoop线程,如果绑定的不是当前的 IO线程的事件,就不处理 + eventLoop = ch.eventLoop(); + } catch (Throwable ignored) { + // If the channel implementation throws an exception because there is no event loop, we ignore this + // because we are only trying to determine if ch is registered to this event loop and thus has authority + // to close ch. + return; + } + // Only close ch if ch is still registered to this EventLoop. ch could have deregistered from the event loop + // and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is + // still healthy and should not be closed. + // See https://github.com/netty/netty/issues/5125 + if (eventLoop != this || eventLoop == null) { + return; + } + // close the channel if the key is not valid anymore + unsafe.close(unsafe.voidPromise()); + return; + } + + try { + int readyOps = k.readyOps(); + // We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise + // the NIO JDK channel implementation may throw a NotYetConnectedException. + if ((readyOps & SelectionKey.OP_CONNECT) != 0) { + // remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking + // See https://github.com/netty/netty/issues/924 + int ops = k.interestOps(); + ops &= ~SelectionKey.OP_CONNECT; + k.interestOps(ops); + + unsafe.finishConnect(); + } + + // 处理写事件 + if ((readyOps & SelectionKey.OP_WRITE) != 0) { + // Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write + ch.unsafe().forceFlush(); + } + + // 处理读事件 + if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { + unsafe.read(); + } + } catch (CancelledKeyException ignored) { + unsafe.close(unsafe.voidPromise()); + } + } +} +``` diff --git "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Future\345\222\214Promise\347\273\204\344\273\266.md" "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Future\345\222\214Promise\347\273\204\344\273\266.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Future\345\222\214Promise\347\273\204\344\273\266.md" +++ "b/docs/Netty/Netty\344\270\273\350\246\201\347\273\204\344\273\266\346\272\220\347\240\201\345\210\206\346\236\220/Future\345\222\214Promise\347\273\204\344\273\266.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" "b/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" index d0e8de79..c7764bbd 100644 --- "a/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" +++ "b/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216HTTP\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" @@ -1 +1 @@ -努力编写中...... \ No newline at end of file +努力编写中...... diff --git "a/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216WebSocket\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" "b/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216WebSocket\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" index d0e8de79..c7764bbd 100644 --- "a/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216WebSocket\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" +++ "b/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216WebSocket\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" @@ -1 +1 @@ -努力编写中...... \ No newline at end of file +努力编写中...... diff --git "a/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216\350\207\252\345\256\232\344\271\211\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" "b/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216\350\207\252\345\256\232\344\271\211\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" index d0e8de79..c7764bbd 100644 --- "a/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216\350\207\252\345\256\232\344\271\211\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" +++ "b/docs/Netty/Netty\345\244\232\345\215\217\350\256\256\345\274\200\345\217\221/\345\237\272\344\272\216\350\207\252\345\256\232\344\271\211\345\215\217\350\256\256\347\232\204Netty\345\274\200\345\217\221.md" @@ -1 +1 @@ -努力编写中...... \ No newline at end of file +努力编写中...... diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\232\204\345\206\205\345\255\230\346\263\204\346\274\217\345\216\237\345\233\240\344\270\216\346\243\200\346\265\213\345\216\237\347\220\206.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\232\204\345\206\205\345\255\230\346\263\204\346\274\217\345\216\237\345\233\240\344\270\216\346\243\200\346\265\213\345\216\237\347\220\206.md" new file mode 100644 index 00000000..d22cbc7e --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/ByteBuf\347\232\204\345\206\205\345\255\230\346\263\204\346\274\217\345\216\237\345\233\240\344\270\216\346\243\200\346\265\213\345\216\237\347\220\206.md" @@ -0,0 +1,112 @@ +该文所涉及的 netty 源码版本为 4.1.6。 + +## Netty 中的 ByteBuf 为什么会发生内存泄漏 + +在 Netty 中,ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 `byte[]` 数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。 + +在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 `retain()` 方法被调用时,将会增加 refCnt 的计数,而其 `release()` 方法被调用时将会减少其被引用数计数。 + +```java +private boolean release0(int decrement) { + for (;;) { + int refCnt = this.refCnt; + if (refCnt < decrement) { + throw new IllegalReferenceCountException(refCnt, -decrement); + } + if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) { + if (refCnt == decrement) { + deallocate(); + return true; + } + return false; + } + } +} +``` + +当调用了 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; + } +} +``` + +以默认的 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; +} +``` + +在包装类中,如果该 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 堆外内存的资料进行阅读。 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/FastThreadLocal\346\272\220\347\240\201\345\210\206\346\236\220.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/FastThreadLocal\346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 00000000..e2ece3d8 --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/FastThreadLocal\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,186 @@ +# Netty 的 FastThreadLocal 源码解析 + +该文中涉及到的 Netty 源码版本为 4.1.6。 + +## Netty 的 FastThreadLocal 是什么 + +> A special variant of ThreadLocal that yields higher access performance when accessed from a FastThreadLocalThread. +> Internally, a FastThreadLocal uses a constant index in an array, instead of using hash code and hash table, to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash table, and it is useful when accessed frequently. +> To take advantage of this thread-local variable, your thread must be a FastThreadLocalThread or its subtype. By default, all threads created by DefaultThreadFactory are FastThreadLocalThread due to this reason. +> Note that the fast path is only possible on threads that extend FastThreadLocalThread, because it requires a special field to store the necessary state. An access by any other kind of thread falls back to a regular ThreadLocal. + +以上是 Netty 官方文档中关于 FastThreadLocal 的介绍。 + +简而言之,FastThreadLocal 是在 ThreadLocal 实现上的一种变种,相比 ThreadLocal 内部通过将自身 hash 的方式在 hashTable 上定位需要的变量存储位置,FastThreadLocal 选择在数组上的一个固定的常量位置来存放线程本地变量,这样的操作看起来并没有太大区别,但是相比 ThreadLocal 的确体现了性能上的优势,尤其是在读操作频繁的场景下。 + +## 如何使用 FastThreadLocal + +如果想要得到 FastThreadLocal 的速度优势,必须通过 FastThreadLocalThread 或者其子类的线程,才可以使用,因为这个原因,Netty 的 DefaultThreadFactory,其内部默认线程工厂的 newThread()方法就是直接初始化一个 FastThreadLocalThread ,以便期望在 ThreadLocal 的操作中,得到其性能上带来的优势。 + +```java +protected Thread newThread(Runnable r, String name) { + return new FastThreadLocalThread(threadGroup, r, name); +} +``` + +## FastThreadLocal 的源码实现 + +### FastThreadLocal 被访问的入口 + +当需要用到 FastThreadLocal 的时候,想必和 jdk 原生的 ThreadLocal 的 api 类似,都是通过初始化一个新的 FastThreadLocal 之后,通过其 set()方法初始化并放入一个变量作为线程本地变量存储。 + +```java +public final void set(V value) { + if (value != InternalThreadLocalMap.UNSET) { + set(InternalThreadLocalMap.get(), value); + } else { + remove(); + } +} +``` + +因此,在 FastThreadLocal 的 set()方法中,可以看到,存储本地线程变量的数据结构是一个 InternalThreadLocalMap。 + +```java +private InternalThreadLocalMap threadLocalMap; +``` + +在 FastThreadLocalThread 中,因为本身 threadLocalMap 就是其中的一个成员,能够快速得到返回。而其他线程实现,就将面临没有这个成员的尴尬,Netty 也给出了相应的兼容。 + +```java +public static InternalThreadLocalMap get() { + Thread thread = Thread.currentThread(); + if (thread instanceof FastThreadLocalThread) { + return fastGet((FastThreadLocalThread) thread); + } else { + return slowGet(); + } +} +``` + +InternalThreadLocalMap 的 get()方法中,当前线程如果是 FastThreadLocalThread 或是其子类的实现,变直接返回其 InternalThreadLocalMap 进行操作,但对于不属于上述条件的线程,Netty 通过 slowGet()的方式,也将返回一个 InternalThreadLocalMap。 + +```java +private static InternalThreadLocalMap slowGet() { + ThreadLocal slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; + InternalThreadLocalMap ret = slowThreadLocalMap.get(); + if (ret == null) { + ret = new InternalThreadLocalMap(); + slowThreadLocalMap.set(ret); + } + return ret; +} +``` + +在 slowGet()方法中,当前线程对应的 InternalThreadLocalMap 会通过原生 jdk 下 ThreadLocal 的方式存储并通过 ThreadLocal 返回,因此,在这个场景下,使用的还是 jdk 原生的 ThreadLocal,但是只占用了原生 ThreadLocal 下的 Entry[]数组的一个位置,具体的变量还是存放在专门为 FastThreadLocal 服务的 InternalThreadLocalMap 中。 +在此,随着 InternalThreadLocalMap 的得到并返回,针对 FastThreadLocal 的 get 和 set 操作,也将变为操作 InternalThreadLocalMap 来达到目的,FastThreadLocal 性能优越的原因,也在 InternalThreadLocalMap 当中。 + +### InternalThreadLocalMap 的内部构造 + +```java +static final AtomicInteger nextIndex = new AtomicInteger(); + +Object[] indexedVariables; +``` + +InternalThreadlocalMap 主要由以上两个成员组成,其中 indexedVariables 作为一个 Object[]数组,直接用来存放 FastThreadLocal 对应的 value,每个 FastThreadLocal 对象都会在相应的线程的 ThreadLocalMap 中被分配到对应的 index,而这里的具体下标,则由以上的 nextIndex 成员在每个 FastThreadLocal 初始化的时候分配。 + +```java +private final int index; + +public FastThreadLocal() { + index = InternalThreadLocalMap.nextVariableIndex(); +} +``` + +每个 FastThreadLocal 在构造方法的过程中,都会通过 InternalThreadlocalMap 的 nextVariableIndex()返回 nextIndex 自加后的结果作为其在 InternalThreadlocalMap 上的下标。后续该 FastThreadLocal 在操作变量的时候可以直接通过该 index 定位到 Object[]数组上的位置。 + +```java +private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex(); +``` + +而数组上的下标有一个特殊位,一般在其首位也就是 0 的位置,这个位置在 FastThreadLocal 类被加载的时候作为静态变量被设置。在这个位置上,存放的是一个 FastThreadLocal 对象集合,每个存放到 InternalThreadlocalMap 中的 FastThreadLocal 都会被保存在首位的集合中。 + +```java +public static final Object UNSET = new Object(); +``` + +另外,为了具体区分保存的变量是 null 还是不存在当前变量,InternalThreadLocalMap 中定义了一个为 NULL 的成员变量,以便区分上述情况,在一开始,InternalThreadLocalMap 中的 indexedVariables 数组都是 NULL。 + +### FastThreadLocal 的 set()方法的源码分析 + +相比 FastThreadLocal 的 set 操作,get 方法的过程与逻辑都要简单的多,因此此处主要以其 set 方法为主。 + +```java +public final void set(V value) { + if (value != InternalThreadLocalMap.UNSET) { + set(InternalThreadLocalMap.get(), value); + } else { + remove(); + } +} + +public final void set(InternalThreadLocalMap threadLocalMap, V value) { + if (value != InternalThreadLocalMap.UNSET) { + if (threadLocalMap.setIndexedVariable(index, value)) { + addToVariablesToRemove(threadLocalMap, this); + } + } else { + remove(threadLocalMap); + } +} +``` + +在其 set()方法中,首先会判断 set 的值是否是 InternalThreadLocalMap 中的 NULL 对象来判断是 set 操作还是 remove 操作,如果不是,会通过 InternalThreadLocalMap.get()方法获取当前线程对应的 InternalThreadLocalMap,获取的过程在前文已经描述过。 +之后的主要流程主要分为两步: + +- 调用 InternalThreadLocalMap 的 setIndexedVariable()方法,将该 FastThreadLocal 成员在构造方法中获得到的 InternalThreadLocalMap 上的下标作为入参传入。 + +```java +public boolean setIndexedVariable(int index, Object value) { + Object[] lookup = indexedVariables; + if (index < lookup.length) { + Object oldValue = lookup[index]; + lookup[index] = value; + return oldValue == UNSET; + } else { + expandIndexedVariableTableAndSet(index, value); + return true; + } +} +``` + +在 InternalThreadLocalMap 的 setIndexedVariable()方法过程中,set 的过程并不复杂,找到对应的下标,并将对应的值放到 InternalThreadLocalMap 数组下标对应的位置上即宣告结束。但是,因为 FastThreadLocal 在构造过程中虽然预先获得了对应的下标,但是实际上的数组大小可能完全还没有达到相应的大小,就要在此处通过 expandIndexedVariableTableAndSet()方法进行扩容,由于是数组的缘故,只需要扩容后将原来的值复制过去,并将剩余的值用 NULL 对象填满即可。 + +- 如果上一步 set 成功,通过 addToVariablesToRemove()方法将该 FastThreadLocal 对象放入到 InternalThreadLocalMap 的数组中的首位集合中。在这个集合中,对于 FastThreadLocal 是一个强引用。 + +这样,对于 FastThreadLocal 的一次 set 操作即宣告结束。 + +## 相比 ThreadLocal,FastThreadLocal 到底快在哪里 + +- FastThreadLocal 在具体的定位的过程中,只需要根据在构造方法里获取得到的具体下标就可以定位到具体的数组位置进行变量的存取,而在 jdk 原生的 ThreadLocal 中,具体位置的下标获取不仅需要计算 ThreadLocal 的 hash 值,并需要在 hashTable 上根据 key 定位的结果,一旦定位之后的结果上已经存在其他 ThreadLocal 的变量,那么则是通过线性探测法,在 hashTable 上寻找下一个位置进行,相比 FastThreadLocal 定位的过程要复杂的多。 +- FastThreadLocal 由于采取数组的方式,当面对扩容的时候,只需要将原数组中的内容复制过去,并用 NULL 对象填满剩余位置即可,而在 ThreadLocal 中,由于 hashTable 的缘故,在扩容后还需要进行一轮 rehash,在这过程中,仍旧存在 hash 冲突的可能。 +- 在 FastThreadLocal 中,遍历当前线程的所有本地变量,只需要将数组首位的集合即可,不需要遍历数组上的每一个位置。 +- 在原生的 ThreadLocal 中,由于可能存在 ThreadLocal 被回收,但是当前线程仍旧存活的情况导致 ThreadLocal 对应的本地变量内存泄漏的问题,因此在 ThreadLocal 的每次操作后,都会进行启发式的内存泄漏检测,防止这样的问题产生,但也在每次操作后花费了额外的开销。而在 FastThreadLocal 的场景下,由于数组首位的 FastThreadLocal 集合中保持着所有 FastThreadLocal 对象的引用,因此当外部的 FastThreadLocal 的引用被置为 null,该 FastThreadLocal 对象仍旧保持着这个集合的引用,不会被回收掉,只需要在线程当前业务操作后,手动调用 FastThreadLocal 的 removeAll()方法,将会遍历数组首位集合,回收掉所有 FastThreadLocal 的变量,避免内存泄漏的产生,也减少了原生 ThreadLocal 的启发式检测开销。 + +```java +private static final class DefaultRunnableDecorator implements Runnable { + + private final Runnable r; + + DefaultRunnableDecorator(Runnable r) { + this.r = r; + } + + @Override + public void run() { + try { + r.run(); + } finally { + FastThreadLocal.removeAll(); + } + } +} +``` + +在 Netty 的 DefaultThreadFactory 中,每个线程在执行为任务后都会调用 FastThreadLocal 的 removeAll()方法。 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/HashedWheelTimer&schedule.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/HashedWheelTimer&schedule.md" new file mode 100644 index 00000000..5e126c5b --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/HashedWheelTimer&schedule.md" @@ -0,0 +1,477 @@ +# 前言 + +前段时间在给自己的玩具项目设计的时候就遇到了一个场景需要定时任务,于是就趁机了解了目前主流的一些定时任务方案,比如下面这些: + +- Timer(halo 博客源码中用到了) +- ScheduledExecutorService +- ThreadPoolTaskScheduler(基于 ScheduledExecutorService) +- Netty 的 schedule(用到了 PriorityQueue) +- Netty 的 HashedWheelTimer(时间轮) +- Kafka 的 TimingWheel(层级时间轮) + +还有一些分布式的定时任务: + +- Quartz +- xxl-job(我实习公司就在用这个) +- ... + +因为我玩具项目实现业务 ACK 的方案就打算用 HashedWheelTimer,所以本节核心是分析 HashedWheelTimer,另外会提下它与 schedule 的区别,其它定时任务实现原理就请自动 Google 吧。 + +> Netty Version:4.1.42 + +# HashedWheelTimer 实现图示 + +![HashedWheelTimer实现图示.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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 + ")"); + } + + // 尝试启动workerThread,startTime=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 unprocessedTimeouts = new HashSet(); + +// 时钟指针转动的次数 +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( + this, command, null, ScheduledFutureTask.deadlineNanos(unit.toNanos(delay)))); +} +``` + +继续跟进 schedule 方法看看: +`io.netty.util.concurrent.AbstractScheduledEventExecutor#schedule(io.netty.util.concurrent.ScheduledFutureTask)` + +```java +private ScheduledFuture schedule(final ScheduledFutureTask 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> scheduledTaskQueue() { + if (scheduledTaskQueue == null) { + scheduledTaskQueue = new DefaultPriorityQueue>( + SCHEDULED_FUTURE_TASK_COMPARATOR, + // Use same initial capacity as java.util.PriorityQueue + 11); + } + return scheduledTaskQueue; +} +``` + +可以看到返回值就是 PriorityQueue,它是一个最小堆实现的优先队列。 + +# 扩展 + +### 不同实现的时间复杂度 + +这里我就直接贴下网上大佬给出的解释: + +如果使用最小堆实现的优先级队列: +![最小堆.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595756711656.png) + +- 大致意思就是你的任务如果插入到堆顶,时间复杂度为 O(log(n))。 + +如果使用链表(既然有说道,那就扩展下): +![链表.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595756928493.png) + +- 中间插入后的事件复杂度为 O(n) + +单个时间轮: +![单个时间轮.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595757035360.png) + +- 复杂度可以降至 O(1)。 + +记录轮数的时间轮(其实就是文章开头的那个): +![记录轮数的时间轮.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595757110003.png) + +层级时间轮: +![层级时间轮.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595757328715.png) + +- 时间复杂度是 O(n),n 是轮子的数量,除此之外还要计算一个轮子上的 bucket。 + +### 单时间轮缺点 + +根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 bucket 数就会变得非常多,从而导致内存占用持续升高(CPU 空转时间还是不变的,仅仅是内存需求变高了),如下图: + +![image.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/HashedWheelTimer\346\227\266\351\227\264\350\275\256\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/HashedWheelTimer\346\227\266\351\227\264\350\275\256\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 00000000..f722f58a --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/HashedWheelTimer\346\227\266\351\227\264\350\275\256\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,149 @@ +该文所涉及的 Netty 源码版本为 4.1.6。 + +## HashedWheelTimer 是什么 + +Netty 的时间轮 `HashedWheelTimer` 给出了一个**粗略的定时器实现**,之所以称之为粗略的实现是**因为该时间轮并没有严格的准时执行定时任务**,而是在每隔一个时间间隔之后的时间节点执行,并执行当前时间节点之前到期的定时任务。 + +当然具体的定时任务的时间执行精度可以通过调节 HashedWheelTimer 构造方法的时间间隔的大小来进行调节,在大多数网络应用的情况下,由于 IO 延迟的存在,并**不会严格要求具体的时间执行精度**,所以默认的 100ms 时间间隔可以满足大多数的情况,不需要再花精力去调节该时间精度。 + +## HashedWheelTimer 的实现原理 + +### HashedWheelTimer 内部的数据结构 + +```java +private final HashedWheelBucket[] wheel; +``` + +HashedWheelTimer 的主体数据结构 wheel 是一个**由多个链表所组成的数组**,默认情况下该数组的大小为 512。当定时任务准备加入到时间轮中的时候,将会以其等待执行的时间为依据选择该数组上的一个具体槽位上的链表加入。 + +```java +private HashedWheelTimeout head; +private HashedWheelTimeout tail; +``` + +在这个 wheel 数组中,每一个槽位都是一条由 HashedWheelTimeout 所组成的**链表**,其中链表中的**每一个节点都是一个等待执行的定时任务**。 + +### HashedWheelTimer 内部的线程模型 + +在 HashedWheelTimer 中,其内部是一个单线程的 worker 线程,通过类似 eventloop 的工作模式进行定时任务的调度。 + +```java +@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(). + startTimeInitialized.countDown(); + + do { + final long deadline = waitForNextTick(); + if (deadline > 0) { + transferTimeoutsToBuckets(); + HashedWheelBucket bucket = + wheel[(int) (tick & mask)]; + 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; + } + unprocessedTimeouts.add(timeout); + } +} +``` + +简单看到 HashedWheelTimer 内部的 woker 线程的 `run()`方法,在其首先会记录启动时间作为 startTime 作为接下来调度定时任务的时间依据,而之后会通过 CountDownLatch 来通知所有外部线程当前 worker 工作线程已经初始化完毕。之后的循环体便是当时间轮持续生效的时间里的具体调度逻辑。**时间刻度是时间轮的一个重要属性**,其默认为 100ms,此处的循环间隔便是时间轮的时间刻度,默认情况下就是间隔 100ms 进行一次调度循环。工作线程会维护当前工作线程具体循环了多少轮,用于定位具体执行触发时间轮数组上的哪一个位置上的链表。当时间轮准备 shutdown 的阶段,最后的代码会对未执行的任务整理到未执行的队列中。 + +由此可见,**worker 线程的 run()方法中基本定义了工作线程的整个生命周期,从初始的初始化到循环体中的具体调度,最后到未执行任务的具体清理**。整体的调度逻辑便主要在这里执行。值得注意的是,在这里的前提下,每个 HashedWheelTimer 时间轮都会有一个工作线程进行调度,所以不需要在 netty 中在每一个连接中单独使用一个 HashedWheelTimer 来进行定时任务的调度,否则可能将对性能产生影响。 + +### 向 HashedWheelTimer 加入一个定时任务的流程 + +当调用 HashedWheelTimer 的 newTimeout()方法的时候,即是将定时任务加入时间轮中的 api。 + +```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"); + } + start(); + + long deadline = System.nanoTime() + unit.toNanos(delay) - startTime; + HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline); + timeouts.add(timeout); + return timeout; +} +``` + +当此次是首次向该时间轮加入定时任务的时候,将会通过 start()方法开始执行上文所述的 worker 工作线程的启动与循环调度逻辑,这里暂且不提。之后计算定时任务触发时间相对于时间轮初始化时间的相对时间间隔 deadline,并将其包装为一个链表节点 HashedWheelTimeout ,投入到 timeouts 队列中,等待 worker 工作线程在下一轮调度循环中将其加入到时间轮的具体链表中等待触发执行,timeouts 的实现是一个 mpsc 队列,关于 mpsc 队列可以查看[此文](https://mp.weixin.qq.com/s/VVoDJwrLZrN3mm-jaQJayQ),这里也符合**多生产者单消费者的队列模型**。 + +### HashedWheelTimer 中工作线程的具体调度 + +```java +do { + final long deadline = waitForNextTick(); + if (deadline > 0) { + transferTimeoutsToBuckets(); + HashedWheelBucket bucket = + wheel[(int) (tick & mask)]; + bucket.expireTimeouts(deadline); + tick++; + } +} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED); +``` + +在 HashedWheelTimer 中的工作线程 run()方法的主要循环中,主要分为三个步骤。 + +首先 worker 线程会通过 `waitForNextTick()`方法根据时间轮的时间刻度等待一轮循环的开始,在默认情况下时间轮的时间刻度是 100ms,那么此处 worker 线程也将在这个方法中 sleep 相应的时间等待下一轮循环的开始。此处也决定了时间轮的定时任务时间精度。 + +当 worker 线程经过相应时间间隔的 sleep 之后,也代表新的一轮调度开始。此时,会通过 `transferTimeoutsToBuckets()`方法将之前刚刚加入到 timeouts 队列中的定时任务放入到时间轮具体槽位上的链表中。 + +```java +for (int i = 0; i < 100000; i++) { + HashedWheelTimeout timeout = timeouts.poll(); + if (timeout == null) { + // all processed + break; + } + if (timeout.state() == HashedWheelTimeout.ST_CANCELLED + || !timeout.compareAndSetState(HashedWheelTimeout.ST_INIT, HashedWheelTimeout.ST_IN_BUCKET)) { + timeout.remove(); + continue; + } + long calculated = timeout.deadline / tickDuration; + long remainingRounds = (calculated - tick) / wheel.length; + timeout.remainingRounds = remainingRounds; + + 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); +} +``` + +首先,在每一轮的调度中,最多只会从 `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 等待下一轮的执行。 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/MpscLinkedQueue\351\230\237\345\210\227\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/MpscLinkedQueue\351\230\237\345\210\227\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 00000000..e4aeb11d --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/MpscLinkedQueue\351\230\237\345\210\227\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,69 @@ +该文所涉及的 netty 源码版本为 4.1.6。 + +## MpscLinkedQueue 是什么 + +在 Netty 的核心中的核心成员 NioEventLoop 中,其中任务队列的实现 taskQueue 便是 MpscLinkedQueue。MpscLinkedQueue 是 Netty 所实现的一个基于多生产者单消费者的无锁队列,针对 NioEventLoop 中任务队列的特点,其单消费者的场景在一开始就避免了从队列中取数据时加锁的必要,而其最精妙的地方便是在多生产者并发从队列中添加数据的时候也没有加锁,达到 Netty 所期望的高性能实现。这是如何实现的? + +## MpscLinkedQueue 无锁并发线程安全写入原理 + +### MpscLinkedQueue 对于尾结点的维护 + +首先,MpscLinkedQueue 继承自 AtomicReference,也就是说 MpscLinkedQueue 通过继承自 AtomicReference 的方式,显式地维护了一个提供原子读写能力的变量 value。而在 MpscLinkedQueue 中,这个 value 是其内部维护的队列的尾结点。 + +### MpscLinkedQueue 对于头结点的维护 + +而后,来看 MpscLinkedQueue 的构造方法。 + +```java +MpscLinkedQueue() { + MpscLinkedQueueNode tombstone = new DefaultNode(null); + headRef = new FullyPaddedReference>(); + headRef.set(tombstone); + setTail(tombstone); +} +``` + +在 MpscLinkedQueue 中,维护着 headRef 头结点字段,其队列内部节点的实现是一个 MpscLinkedQueueNode。MpscLinkedQueueNode 是一个除了存放具体队列元素外只有 next 字段的节点,也就是说,MpscLinkedQueue 的队列是单向的。在构造方法的最后,通过 setTail()方法的,将 MpscLinkedQueue 的尾结点字段 value 也设置为头结点。MpscLinkedQueue 的头结点字段 headRef 的存在可以方便后续直接从头结点开始的队列操作,消费者可以简单判断头尾节点是否相等来确认队列中是否有元素可以消费。 + +### MpscLinkedQueue 如何做到线程安全的无锁加入 + +```java +@Override +@SuppressWarnings("unchecked") +public boolean offer(E value) { + if (value == null) { + throw new NullPointerException("value"); + } + + final MpscLinkedQueueNode newTail; + if (value instanceof MpscLinkedQueueNode) { + newTail = (MpscLinkedQueueNode) value; + newTail.setNext(null); + } else { + newTail = new DefaultNode(value); + } + + MpscLinkedQueueNode oldTail = replaceTail(newTail); + oldTail.setNext(newTail); + return true; +} + +private MpscLinkedQueueNode replaceTail(MpscLinkedQueueNode node) { + return getAndSet(node); +} +``` + +MpscLinkedQueue 的 offer()方法很简短,但是恰恰就是整个添加队列元素加入的流程,当元素被加入的时候,首先判断加入的元素是否是 MpscLinkedQueueNode,如果不是则进行封装。之后便是整个操作的重点: + +- 通过 replaceTail()方法,将当前被加入的节点通过 AtomicReference 所提供的 getAndSet()方法将其设为队列的尾结点,并返回先前的尾结点。这次操作由 UNSAFE 的 CAS 来保证操作的原子性。 +- 之后将之前的尾结点的 next 指向新加入的节点,本次加入宣告结束。 + 整个操作就到此结束,这里可以看出,MpscLinkedQueue 利用了 AtomicReference 底层 UNSAFE 的能力,通过 CAS 确保新设置进入 value 的节点必定能够和原先的节点达成一个且唯一的联系,那么只需要自顶向下不断通过将这个联系变成引用,那么一条队列便形成了。由于其实现是链表而不是数组,也就没有涉及到资源的竞争,在不加锁的前提下其队列顺序可能不会严格按照加入顺序,但这在当前场景下并不是问题。在这个前提,高并发的插入场景下,每个新进入的新节点都将获取原尾位置 value 上的节点,而自身将会被设置为其后驱节点重新放到尾结点位置上,CAS 在不加锁的前提下保证了前后节点对应关系的唯一性,完成了并发条件下不加锁的线程安全写入。 + +### MpscLinkedQueue 不支持 remove() + +在 MpscLinkedQueue 中,是不支持 remove()的方法去从队列中移除任意一个元素的。原因很简单,消费者和生产者是无锁的,消费者可以通过比较队首和队尾元素是否一致来保证线程安全地从队首取数据,但是 remove()从队列中任意位置修改数据是线程不安全的,主要体现在移除队尾元素可能会导致正在加入的新元素被丢弃。 + +## MpscLinkedQueue 另外的实现细节 + +- MpscLinkedQueue 中的头节点被通过 FullyPaddedReference 封装。其内部前后分别填充 56 字节和 64 字节来进行填充以避免伪共享导致的性能损耗,使得其头结点可以高效被访问。关于伪共享的相关知识可以通过搜索引擎进行查询。 +- MpscLinkedQueue 在消费者消费数据后,当将下一个节点设置为头结点的时候,并不是直接进行赋值,而是通过 UNSAFE 来根据偏移量赋值,这样做将略微提高性能,主要是内存屏障 storestrore 和 loadstrore 之间的性能差异。 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/Recycler\345\257\271\350\261\241\346\261\240\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/Recycler\345\257\271\350\261\241\346\261\240\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 00000000..b0c51232 --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/Recycler\345\257\271\350\261\241\346\261\240\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,143 @@ +该文所涉及的 netty 源码版本为 4.1.6。 + +## Netty 的对象池 Recycler 是什么 + +Recycler 是 Netty 中基于 ThreadLocal 的轻量化的对象池实现。既然是基于 ThreadLocal,那么就可以将其理解为当前线程在通过对象池 Recycler 得到一个对象之后,在回收对象的时候,不需要将其销毁,而是放回到该线程的对象池中即可,在该线程下一次用到该对象的时候,不需要重新申请空间创建,而是直接重新从对象池中获取。 + +## Recycler 在 netty 中被如何使用 + +Recycler 对象池在 netty 中最重要的使用,就在于 netty 的池化 ByteBuf 的场景下。首先,何为池化?以 PooledDirectByteBuf 举例,每一个 PooledDirectByteBuf 在应用线程中使用完毕之后,并不会被释放,而是等待被重新利用,类比线程池每个线程在执行完毕之后不会被立即释放,而是等待下一次执行的时候被重新利用。所谓的对象池也是如此,池化减少了 ByteBuf 创建和销毁的开销,也是 netty 高性能表现的基石之一。 + +```java +private static final Recycler RECYCLER = new Recycler() { + @Override + protected PooledDirectByteBuf newObject(Handle handle) { + return new PooledDirectByteBuf(handle, 0); + } +}; + +static PooledDirectByteBuf newInstance(int maxCapacity) { + PooledDirectByteBuf buf = RECYCLER.get(); + buf.reuse(maxCapacity); + return buf; +} +``` + +PooledDirectByteBuf 在其类加载的过程中,初始化了一个静态的 RECYCLER 成员,通过重写其 newObject()方法达到使 Recycler 可以初始化一个 PooledDirectByteBuf。而在接下来的使用中,只需要通过静态方法 newInstance()就可以从 RECYCLER 对象池的 get()方法获取一个新的 PooledDirectByteBuf 对象返回,而重写的方法 newObject()中的入参 Handler 则提供了 recycle()方法给出了对象重新放入池中回收的能力,这里的具体实现在下文展开。因此,newInstance()方法和 recycle()方法就提供了对象池出池和入池的能力,也通过此,PooledDirectByteBuf 达到了池化的目标。 + +## Recycler 的实现原理分析 + +**Recycler 的实现原理很抽象,可以先直接阅读文末的例子再阅读这部分内容。** +Recycler 中,最核心的是两个通过 ThreadLocal 作为本地线程私有的两个成员,而其实现原理只需要围绕这两个成员分析,就可以对对象池的设计有直接的理解和认识。 + +- 第一个成员是在 Recycler 被定义的 Stack 成员对象。 + +```java +private final FastThreadLocal> threadLocal = new FastThreadLocal>() { + @Override + protected Stack initialValue() { + return new Stack(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor, + ratioMask, maxDelayedQueuesPerThread); + } +}; +``` + +顾名思义,这个 Stack 主体是一个堆栈,但是其还维护着一个链表,而链表中的每一个节点都是一个队列。 + +```java +private DefaultHandle[] elements; +private WeakOrderQueue cursor, prev; +``` + +上述的 elements 数组便是存放当前线程被回收的对象,当当前线程从该线程的 Recycler 对象池尝试获取新的对象的时候,首先就会从当前 Stack 的这个数组中尝试获取已经在先前被创建并且在当前线程被回收的对象,因为当对象池的对象在当前线程被调用 recycle()的时候,是会直接放到 elements 数组中等待下一次的利用。 那么问题来了,如果从该线程中被申请的这个对象是在另外一个线程中被调用 recycle()方法回收呢?那么该对象就会处于链表中的队列中,当堆栈数组中的对象不存在的时候,将会尝试把链表队列中的对象转移到数组中供当前线程获取。那么其他线程是如何把被回收的对象放到这些链表中的队列的呢?接下来就是另一个成员的使命了。 + +- 第二个成员是在 Recycler 中也是通过 ThreadLocal 所实现的一个线程本地变量,DELAYED_RECYCLED ,是一个 Stack 和队列的映射 Map。 + +```java +private static final FastThreadLocal, WeakOrderQueue>> DELAYED_RECYCLED = + new FastThreadLocal, WeakOrderQueue>>() { + @Override + protected Map, WeakOrderQueue> initialValue() { + return new WeakHashMap, WeakOrderQueue>(); + } +}; +``` + +第二个成员 DELAYED_RECYCLED 可以通过上文的 Stack 获取一个队列。 +在前一个成员的解释中提到,当别的线程调用另一个线程的对象池的 recycle()方法进行回收的时候,并不会直接落到持有对象池的线程的 Stack 数组当中,当然原因也很简单,在并发情况下这样的操作显然是线程不安全的,而加锁也会带来性能的开销。因此,netty 在 Recycler 对象池中通过更巧妙的方式解决这一问题。 +在前面提到,除了数组,Stack 还持有了一系列队列的组成的链表,这些链表中的每一个节点都是一个队列,这些队列又存放着别的线程所回收到当前线程对象池的对象。那么,这些队列就是各个线程针对持有对象池的专属回收队列,说起来很拗口,看下面的代码。 + +```java +private void pushLater(DefaultHandle item, Thread thread) { + // we don't want to have a ref to the queue as the value in our weak map + // so we null it out; to ensure there are no races with restoring it later + // we impose a memory ordering here (no-op on x86) + Map, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get(); + WeakOrderQueue queue = delayedRecycled.get(this); + if (queue == null) { + if (delayedRecycled.size() >= maxDelayedQueues) { + // Add a dummy queue so we know we should drop the object + delayedRecycled.put(this, WeakOrderQueue.DUMMY); + return; + } + // Check if we already reached the maximum number of delayed queues and if we can allocate at all. + if ((queue = WeakOrderQueue.allocate(this, thread)) == null) { + // drop object + return; + } + delayedRecycled.put(this, queue); + } else if (queue == WeakOrderQueue.DUMMY) { + // drop object + return; + } + + queue.add(item); +} + +private WeakOrderQueue(Stack stack, Thread thread) { + head = tail = new Link(); + owner = new WeakReference(thread); + synchronized (stack) { + next = stack.head; + stack.head = this; + } + + // Its important that we not store the Stack itself in the WeakOrderQueue as the Stack also is used in + // the WeakHashMap as key. So just store the enclosed AtomicInteger which should allow to have the + // Stack itself GCed. + availableSharedCapacity = stack.availableSharedCapacity; +} +``` + +pushLater()方法发生在当一个对象被回收的时候,当当前线程不是这个对象所申请的时候的线程时,将会通过该对象的 Stack 直接去通过 DELAYED_RECYCLED 映射到一条队列上,如果没有则创建并建立映射,再把该对象放入到该队列中,以上操作结束后该次回收即宣告结束 + +```java +private WeakOrderQueue(Stack stack, Thread thread) { + head = tail = new Link(); + owner = new WeakReference(thread); + synchronized (stack) { + next = stack.head; + stack.head = this; + } + + // Its important that we not store the Stack itself in the WeakOrderQueue as the Stack also is used in + // the WeakHashMap as key. So just store the enclosed AtomicInteger which should allow to have the + // Stack itself GCed. + availableSharedCapacity = stack.availableSharedCapacity; +} +``` + +如果在操作中,队列是被创建的,会把该队列放置在 Stack 中的链表里的头结点,保证创建该对象的线程在数组空了之后能够通过链表访问到该队列并将该队列中的回收对象重新放到数组中等待被下次重新利用,队列交给 A 线程的链表是唯一的阻塞操作。在这里通过一次阻塞操作,避免后续都不存在资源的竞争问题。 + +## 举一个例子来解释对象池的原理 + +_A 线程申请,A 线程回收的场景。_ + +- 显然,当对象的申请与回收是在一个线程中时,直接把对象放入到 A 线程的对象池中即可,不存在资源的竞争,简单轻松。 + +_A 线程申请,B 线程回收的场景。_ + +- 首先,当 A 线程通过其对象池申请了一个对象后,在 B 线程调用 recycle()方法回收该对象。显然,该对象是应该回收到 A 线程私有的对象池当中的,不然,该对象池也失去了其意义。 +- 那么 B 线程中,并不会直接将该对象放入到 A 线程的对象池中,如果这样操作在多线程场景下存在资源的竞争,只有增加性能的开销,才能保证并发情况下的线程安全,显然不是 netty 想要看到的。 +- 那么 B 线程会专门申请一个针对 A 线程回收的专属队列,在首次创建的时候会将该队列放入到 A 线程对象池的链表首节点(这里是唯一存在的资源竞争场景,需要加锁),并将被回收的对象放入到该专属队列中,宣告回收结束。 +- 在 A 线程的对象池数组耗尽之后,将会尝试把各个别的线程针对 A 线程的专属队列里的对象重新放入到对象池数组中,以便下次继续使用。 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/\345\206\205\345\255\230\346\261\240\344\271\213PoolChunk\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/\345\206\205\345\255\230\346\261\240\344\271\213PoolChunk\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" new file mode 100644 index 00000000..5f7da9e3 --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/\345\206\205\345\255\230\346\261\240\344\271\213PoolChunk\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" @@ -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,也就是 8kb,maxOrder 默认情况下是 11,因此在这个情况下 PoolChunk 中的二叉树的叶子节点数量是 2048,chunkSize 的大小则是 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 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[] subpages = this.subpages; + final int pageSize = this.pageSize; + + freeBytes -= pageSize; + + int subpageIdx = subpageIdx(id); + PoolSubpage subpage = subpages[subpageIdx]; + if (subpage == null) { + subpage = new PoolSubpage(head, this, id, runOffset(id), pageSize, normCapacity); + subpages[subpageIdx] = subpage; + } else { + subpage.init(head, normCapacity); + } + return subpage.allocate(); + } +} +``` + +当向 PoolChunk 申请的内存大小小于 pageSize 的时候,将直接通过 allocateSubpage()方法尝试直接在叶子结点,也就是二叉树的最后一层选择一个空的还未使用的叶子结点,在选择的叶子结点中构造一个 PoolSubPage 来返回,而不需要耗费整整一个叶子结点导致内存占用浪费。 diff --git "a/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/\345\206\205\345\255\230\346\261\240\344\271\213\344\273\216\345\206\205\345\255\230\346\261\240\347\224\263\350\257\267\345\206\205\345\255\230.md" "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/\345\206\205\345\255\230\346\261\240\344\271\213\344\273\216\345\206\205\345\255\230\346\261\240\347\224\263\350\257\267\345\206\205\345\255\230.md" new file mode 100644 index 00000000..06d86a2b --- /dev/null +++ "b/docs/Netty/Netty\346\212\200\346\234\257\347\273\206\350\212\202\346\272\220\347\240\201\345\210\206\346\236\220/\345\206\205\345\255\230\346\261\240\344\271\213\344\273\216\345\206\205\345\255\230\346\261\240\347\224\263\350\257\267\345\206\205\345\255\230.md" @@ -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 qInit`:存储内存利用率 0-25%的 poolChunk +- `PoolChunkList q000`:存储内存利用率 1-50%的 poolChunk +- `PoolChunkList q025`:存储内存利用率 25-75%的 poolChunk +- `PoolChunkList q050`:存储内存利用率 50-100%的 poolChunk +- `PoolChunkList q075`:存储内存利用率 75-100%的 poolChunk +- `PoolChunkList q100`:存储内存利用率 100%的 poolChunk、 + 当申请的内存大于一个 page(8kb)但又小于一个 poolChunk(2048kb)总大小的时候,将会从各个 PoolChunkList 中尝试获取一个 poolChunk 从中返回。PoolChunkList 是一个由 poolChunk 组成的链表。 + 以上几个 PoolChunkList,由符合各个内存利用率的 poolChunk 组成,这几个 PoolChunkList 之间又互相首尾连接组成队列,方便 PoolChunk 在各个队列中根据自己当前的利用率进行转移到对应的位置上。 + 最后,当申请的内存大于一个 poolChunk 大小的时候将会直接申请一段非池化的内存返回,并不会占用内存池中的内存空间。 + +最后,到了从 poolChunk 中申请内存的场景,这一部分在[该文](https://github.com/doocs/source-code-hunter/blob/main/docs/Netty/Netty技术细节源码分析/内存池之PoolChunk设计与实现.md)中已经详细说明,这部分也是内存池中获取内存的最后一步。 diff --git "a/docs/Netty/TCP\347\262\230\346\213\206\345\214\205/TCP\347\262\230\346\213\206\345\214\205\351\227\256\351\242\230\345\217\212Netty\344\270\255\347\232\204\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/docs/Netty/TCP\347\262\230\346\213\206\345\214\205/TCP\347\262\230\346\213\206\345\214\205\351\227\256\351\242\230\345\217\212Netty\344\270\255\347\232\204\350\247\243\345\206\263\346\226\271\346\241\210.md" index f9c1ba36..47d271c4 100644 --- "a/docs/Netty/TCP\347\262\230\346\213\206\345\214\205/TCP\347\262\230\346\213\206\345\214\205\351\227\256\351\242\230\345\217\212Netty\344\270\255\347\232\204\350\247\243\345\206\263\346\226\271\346\241\210.md" +++ "b/docs/Netty/TCP\347\262\230\346\213\206\345\214\205/TCP\347\262\230\346\213\206\345\214\205\351\227\256\351\242\230\345\217\212Netty\344\270\255\347\232\204\350\247\243\345\206\263\346\226\271\346\241\210.md" @@ -1,47 +1,64 @@ -## TCP粘包/拆包 -熟悉 TCP编程 的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP底层 的 粘包/拆包机制。TCP粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。 +## TCP 粘包/拆包 -### TCP粘包/拆包问题说明 -TCP 是个 “流” 协议,所谓流,就是没有界限的一串数据。TCP底层 并不了解上层(如 HTTP协议)业务数据的具体含义,它会根据 TCP缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP粘包和拆包问题。我们可以通过下面的示例图,对 TCP粘包和拆包问题 进行说明。 +熟悉 TCP 编程的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP 底层 的 粘包/拆包机制。TCP 粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP 粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。 -![avatar](../../../images/Netty/TCP粘包拆包问题.png) +### TCP 粘包/拆包问题说明 + +TCP 是个 “流” 协议,所谓流,就是没有界限的一串数据。TCP 底层 并不了解上层(如 HTTP 协议)业务数据的具体含义,它会根据 TCP 缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。我们可以通过下面的示例图,对 TCP 粘包和拆包问题 进行说明。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/TCP粘包拆包问题.png) + +假设客户端依次发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。 -假设客户端依次发送了两个数据包 DI 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。 1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包; -2. 服务端一次接收到了两个数据包,DI 和 D2 粘合在一起,被称为 TCP粘包; -3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 DI包 和 D2包的部分内容,第二次读取到了 D2包 的剩余内容,这被称为 TCP拆包; -4. 服务端分两次读取到了两个数据包,第一次读取到了 D1包的部分内容,第二次读取到了 D1包的剩余内容 和 D2包的整包。 +2. 服务端一次接收到了两个数据包,D1 和 D2 粘合在一起,被称为 TCP 粘包; +3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 D1 包 和 D2 包的部分内容,第二次读取到了 D2 包 的剩余内容,这被称为 TCP 拆包; +4. 服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部分内容,第二次读取到了 D1 包的剩余内容 和 D2 包的整包。 + +如果此时服务端 TCP 接收滑窗非常小,而 数据包 D1 和 D2 比较大,很有可能会发生第 5 种可能,即服务端分多次才能将 D1 和 D2 包 接收完全,期间发生多次拆包。 -如果此时服务端 TCP 接收滑窗非常小,而 数据包DI 和 D2 比较大,很有可能会发生第5种可能,即服务端分多次才能将 D1 和 D2包 接收完全,期间发生多次拆包。 +### TCP 粘包/拆包发生的原因 -### TCP粘包/拆包发生的原因 -问题产生的原因有三个,分别如下。 -1. **应用程序 write写入的字节大小 超出了 套接口发送缓冲区大小;** -2. 进行 MSS 大小的 TCP分段; -3. 以太网帧的 payload 大于 MTU 进行 IP分片。 +问题产生的原因有三个,分别如下。 + +1. **应用程序 write 写入的字节大小 超出了 套接口发送缓冲区大小;** +2. 进行 MSS 大小的 TCP 分段; +3. 以太网帧的 payload 大于 MTU 进行 IP 分片。 ### 粘拆包问题的解决策略 -由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。 -1. 固定消息长度,例如,每个报文的大小为 固定长度200字节,如果不够,空位补空格; -2. 在包尾使用 “回车换行符” 等特殊字符,作为消息结束的标志,例如FTP协议,这种方式在文本协议中应用比较广泛; -3. 将消息分为消息头和消息体,在消息头中定义一个 长度字段Len 来标识消息的总长度; + +由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。 + +1. 固定消息长度,例如,每个报文的大小为 固定长度 200 字节,如果不够,空位补空格; +2. 在包尾使用 “回车换行符” 等特殊字符,作为消息结束的标志,例如 FTP 协议,这种方式在文本协议中应用比较广泛; +3. 将消息分为消息头和消息体,在消息头中定义一个 长度字段 Len 来标识消息的总长度; 4. 更复杂的应用层协议。 -介绍完了 TCP粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP粘包/拆包问题的。 +**注意**:从 TCP 流式设计上来看,TCP 粘包其实是一个伪命题。应用层协议需要自己划分消息的边界。**TCP 粘包问题是因为应用层协议开发者的错误设计导致的,他们忽略了 TCP 协议数据传输的核心机制 — 基于字节流,其本身并不存在数据包的概念。** 所有在 TCP 中传输的数据都是以流的形式进行传输,这就需要应用层协议开发者自行设计消息的边界划分规则。所以粘包总的来说还是以下两点: -## 利用 Netty的解码器 解决 TCP粘拆包问题 -根据上面的 粘拆包问题解决策略,Netty 提供了相应的解码器实现。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑TCP的粘包和拆包。 +- TCP 协议是面向字节流的协议,它可能会重新分割组合应用层协议的消息到多个数据段中; +- 应用层协议没有定义消息的边界,导致数据的接收方无法按边界拆分粘连的消息。 + +介绍完了 TCP 粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP 粘包/拆包问题的。 + +## 利用 Netty 的解码器 解决 TCP 粘拆包问题 + +根据上面的 粘拆包问题解决策略,Netty 提供了相应的解码器实现。有了这些解码器,用户不需要自己对读取的报文进行人工解码,也不需要考虑 TCP 的粘包和拆包。 ### LineBasedFrameDecoder 和 StringDecoder 的原理分析 -为了解决 TCP粘包 / 拆包 导致的 半包读写问题,Netty 默认提供了多种编解码器用于处理半包,只要能熟练掌握这些类库的使用,TCP粘拆包问题 从此会变得非常容易,你甚至不需要关心它们,这也是其他 NIO框架 和 JDK原生的 NIO API 所无法匹敌的。对于使用者来说,只要将支持半包解码的 Handler 添加到 ChannelPipeline对象 中即可,不需要写额外的代码,使用起来非常简单。 + +为了解决 TCP 粘包 / 拆包 导致的 半包读写问题,Netty 默认提供了多种编解码器用于处理半包,只要能熟练掌握这些类库的使用,TCP 粘拆包问题 从此会变得非常容易,你甚至不需要关心它们,这也是其他 NIO 框架 和 JDK 原生的 NIO API 所无法匹敌的。对于使用者来说,只要将支持半包解码的 Handler 添加到 ChannelPipeline 对象 中即可,不需要写额外的代码,使用起来非常简单。 + ```java // 示例代码,其中 socketChannel 是一个 SocketChannel对象 socketChannel.pipeline().addLast( new LineBasedFrameDecoder(1024) ); socketChannel.pipeline().addLast( new StringDecoder() ); ``` + LineBasedFrameDecoder 的工作原理是它依次遍历 ByteBuf 中的可读字节,判断看是否有 “\n” 或者 “\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。 -StringDecoder 的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的 Handler。LineBasedFrameDecoder + StringDecoder组合 就是按行切换的文本解码器,它被设计用来支持 TCP 的粘包和拆包。 +StringDecoder 的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的 Handler。LineBasedFrameDecoder + StringDecoder 组合 就是按行切换的文本解码器,它被设计用来支持 TCP 的粘包和拆包。 ### 其它解码器 -除了 LineBasedFrameDecoder 以外,还有两个常用的解码器 DelimiterBasedFrameDecoder 和 FixedLengthFrameDecoder,前者能自动对 “以分隔符做结束标志的消息” 进行解码,后者可以自动完成对定长消息的解码。使用方法也和前面的示例代码相同,结合 字符串解码器StringDecoder,轻松完成对很多消息的自动解码。 \ No newline at end of file + +除了 LineBasedFrameDecoder 以外,还有两个常用的解码器 DelimiterBasedFrameDecoder 和 FixedLengthFrameDecoder,前者能自动对 “以分隔符做结束标志的消息” 进行解码,后者可以自动完成对定长消息的解码。使用方法也和前面的示例代码相同,结合 字符串解码器 StringDecoder,轻松完成对很多消息的自动解码。 diff --git "a/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\345\256\242\346\210\267\347\253\257\345\274\200\345\217\221.md" "b/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\345\256\242\346\210\267\347\253\257\345\274\200\345\217\221.md" index 4b5bfb4a..8d26618d 100644 --- "a/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\345\256\242\346\210\267\347\253\257\345\274\200\345\217\221.md" +++ "b/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\345\256\242\346\210\267\347\253\257\345\274\200\345\217\221.md" @@ -1,30 +1,35 @@ -相对于服务端,Netty客户端 的创建更加复杂,除了要考虑线程模型、异步连接、客户端连接超时等因素外,还需要对连接过程中的各种异常进行考虑。本章将对 Netty客户端 创建的关键流程和源码进行分析,以期读者能够了解客户端创建的细节。 +相对于服务端,Netty 客户端 的创建更加复杂,除了要考虑线程模型、异步连接、客户端连接超时等因素外,还需要对连接过程中的各种异常进行考虑。本章将对 Netty 客户端 创建的关键流程和源码进行分析,以期读者能够了解客户端创建的细节。 ## 基于 Netty 创建客户端的流程分析 -Netty 为了向使用者屏蔽 NIO通信 的底层细节,在和用户交互的边界做了封装,目的就是为了减少用户开发工作量,降低开发难度。Bootstrap 是 Socket 客户端创建工具类,用户通过 Bootstrap 可以方便地创建 Netty 的客户端并发起 异步TCP连接操作。 + +Netty 为了向使用者屏蔽 NIO 通信 的底层细节,在和用户交互的边界做了封装,目的就是为了减少用户开发工作量,降低开发难度。Bootstrap 是 Socket 客户端创建工具类,用户通过 Bootstrap 可以方便地创建 Netty 的客户端并发起 异步 TCP 连接操作。 ### 基于 Netty 创建客户端 时序图 -![avatar](../../../images/Netty/基于Netty创建客户端时序图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/基于Netty创建客户端时序图.png) ### Netty 创建客户端 流程分析 -1. 用户线程创建 Bootstrap实例,通过 API 设置客户端相关的参数,异步发起客户端连接; -2. 创建处理客户端连接、I/O 读写的 Reactor线程组 NioEventLoopGroup。可以通过构造函数指定 IO线程 的个数,默认为 CPU 内核数的 2 倍; -3. 通过 Bootstrap 的 ChannelFactory 和用户指定的 Channel类型 创建用于客户端连接的 NioSocketChannel,它的功能类似于 JDK NIO类库 提供的 SocketChannel; + +1. 用户线程创建 Bootstrap 实例,通过 API 设置客户端相关的参数,异步发起客户端连接; +2. 创建处理客户端连接、I/O 读写的 Reactor 线程组 NioEventLoopGroup。可以通过构造函数指定 IO 线程 的个数,默认为 CPU 内核数的 2 倍; +3. 通过 Bootstrap 的 ChannelFactory 和用户指定的 Channel 类型 创建用于客户端连接的 NioSocketChannel,它的功能类似于 JDK NIO 类库 提供的 SocketChannel; 4. 创建默认的 Channel、Handler、Pipeline,用于调度和执行网络事件; -5. 异步发起 TCP连接,判断连接是否成功。如果成功,则直接将 NioSocketChannel 注册到多路复用器上,监听读操作位,用于数据报读取和消息发送;如果没有立即连接成功,则注册连接监听位到多路复用器,等待连接结果; +5. 异步发起 TCP 连接,判断连接是否成功。如果成功,则直接将 NioSocketChannel 注册到多路复用器上,监听读操作位,用于数据报读取和消息发送;如果没有立即连接成功,则注册连接监听位到多路复用器,等待连接结果; 6. 注册对应的网络监听状态位到多路复用器; 7. 由多路复用器在 IO 现场中轮询各 Channel,处理连接结果; -8. 如果连接成功,设置 Future结果,发送连接成功事件,触发 ChanneIPipeline 执行; +8. 如果连接成功,设置 Future 结果,发送连接成功事件,触发 ChanneIPipeline 执行; 9. 由 ChannelPipeline 调度执行系统和用户的 ChannelHandler,执行业务逻辑。 ## Netty 客户端创建源码分析 -Netty客户端 的创建流程比较繁琐,下面我们针对关键步骤和代码进行分析,通过梳理关键流程来掌握客户端创建的原理。 + +Netty 客户端 的创建流程比较繁琐,下面我们针对关键步骤和代码进行分析,通过梳理关键流程来掌握客户端创建的原理。 ### 客户端连接辅助类 BootStrap -Bootstrap 是 Netty 提供的客户端连接工具类,主要用于简化客户端的创建,下面我们对它的 主要API 进行讲解。 -设置 lO线程组:NIO的特点就是,一个多路复用器可以同时处理上干条链路,这就意味着,NIO模式中 一个线程可以处理多个 TCP连接。考虑到 lO线程 的处理性能,大多数 NIO框架 都采用线程池的方式处理 IO读写,Netty 也不例外。客户端相对于服务端,只需要一个处理 IO读写 的线程组即可,因为 Bootstrap 提供了 设置IO线程组 的接口,代码如下。 +Bootstrap 是 Netty 提供的客户端连接工具类,主要用于简化客户端的创建,下面我们对它的 主要 API 进行讲解。 + +设置 lO 线程组:NIO 的特点就是,一个多路复用器可以同时处理上干条链路,这就意味着,NIO 模式中 一个线程可以处理多个 TCP 连接。考虑到 lO 线程 的处理性能,大多数 NIO 框架 都采用线程池的方式处理 IO 读写,Netty 也不例外。客户端相对于服务端,只需要一个处理 IO 读写 的线程组即可,因为 Bootstrap 提供了 设置 IO 线程组 的接口,代码如下。 + ```java public abstract class AbstractBootstrap, C extends Channel> implements Cloneable { @@ -42,49 +47,55 @@ public abstract class AbstractBootstrap, C ext } } ``` -由于 Netty 的 NIO线程组 默认采用 EventLoopGroup接口,因此线程组参数使用 EventLoopGroup。 -TCP参数设置接口:无论是 NIO,还是 BIO,创建客户端套接字的时候通常都会设置连接参数,例如接收和发送缓冲区大小、连接超时时间等。Bootstrap 也提供了客户端 TCP参数设置接口,代码如下。 +由于 Netty 的 NIO 线程组 默认采用 EventLoopGroup 接口,因此线程组参数使用 EventLoopGroup。 + +TCP 参数设置接口:无论是 NIO,还是 BIO,创建客户端套接字的时候通常都会设置连接参数,例如接收和发送缓冲区大小、连接超时时间等。Bootstrap 也提供了客户端 TCP 参数设置接口,代码如下。 + ```java - public B option(ChannelOption option, T value) { - if (option == null) { - throw new NullPointerException("option"); +public B option(ChannelOption option, T value) { + if (option == null) { + throw new NullPointerException("option"); + } else { + if (value == null) { + synchronized(this.options) { + this.options.remove(option); + } } else { - if (value == null) { - synchronized(this.options) { - this.options.remove(option); - } - } else { - synchronized(this.options) { - this.options.put(option, value); - } + synchronized(this.options) { + this.options.put(option, value); } - - return this; } + + return this; } +} ``` -Netty 提供的 主要TCP参数 如下。 -1、SO_TIMEOUT:控制读取操作将阻塞多少毫秒。如果返回值为0,计时器就被禁止了,该线程将无限期阻塞; +Netty 提供的 主要 TCP 参数 如下。 + +1、SO_TIMEOUT:控制读取操作将阻塞多少毫秒。如果返回值为 0,计时器就被禁止了,该线程将无限期阻塞; 2、SO_SNDBUF:套接字使用的发送缓冲区大小; 3、SO_RCVBUF:套接字使用的接收缓冲区大小; -4、SO_REUSEADDR:用于决定 如果网络上仍然有数据向旧的 ServerSocket 传输数据,是否允许新的 ServerSocket 绑定到与旧的 ServerSocket 同样的端口上。SO_REUSEADDR选项 的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口; -5、CONNECT_TIMEOUT_MILLIS:客户端连接超时时间,由于 NIO原生的客户端 并不提供设置连接超时的接口,因此,Netty 采用的是自定义连接超时定时器负责检测和超时控制; +4、SO_REUSEADDR:用于决定 如果网络上仍然有数据向旧的 ServerSocket 传输数据,是否允许新的 ServerSocket 绑定到与旧的 ServerSocket 同样的端口上。SO_REUSEADDR 选项 的默认值与操作系统有关,在某些操作系统中,允许重用端口,而在某些操作系统中不允许重用端口; +5、CONNECT_TIMEOUT_MILLIS:客户端连接超时时间,由于 NIO 原生的客户端 并不提供设置连接超时的接口,因此,Netty 采用的是自定义连接超时定时器负责检测和超时控制; + +Channel 接口:用于指定客户端使用的 Channel 接口,对于 TCP 客户端连接,默认使用 NioSocketChannel,代码如下。 -Channel接口:用于指定客户端使用的 Channel接口,对于 TCP客户端连接,默认使用 NioSocketChannel,代码如下。 ```java - public B channel(Class channelClass) { - if (channelClass == null) { - throw new NullPointerException("channelClass"); - } else { - return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass))); - } +public B channel(Class channelClass) { + if (channelClass == null) { + throw new NullPointerException("channelClass"); + } else { + return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass))); } +} ``` -BootstrapChannelFactory 利用 参数channelClass,通过反射机制创建 NioSocketChannel对象。 -设置 Handler接口:Bootstrap 为了简化 Handler 的编排,提供了 Channellnitializer,它继承了 ChannelHandlerAdapter,当 TCP链路 注册成功之后,调用 initChannel 接口,用于设置用户 ChanneIHandler。它的代码如下。 +BootstrapChannelFactory 利用 参数 channelClass,通过反射机制创建 NioSocketChannel 对象。 + +设置 Handler 接口:Bootstrap 为了简化 Handler 的编排,提供了 Channellnitializer,它继承了 ChannelHandlerAdapter,当 TCP 链路 注册成功之后,调用 initChannel 接口,用于设置用户 ChanneIHandler。它的代码如下。 + ```java public abstract class ChannelInitializer extends ChannelInboundHandlerAdapter { @@ -97,13 +108,17 @@ public abstract class ChannelInitializer extends ChannelInbou } } ``` + 最后一个比较重要的接口就是发起客户端连接,代码如下。 + ```java - ChannelFuture f = b.connect(host, port).sync(); +ChannelFuture f = b.connect(host, port).sync(); ``` ### 客户端连接操作 + 首先要创建和初始化 NioSocketChannel,代码如下。 + ```java public class Bootstrap extends AbstractBootstrap { @@ -197,51 +212,62 @@ public class Bootstrap extends AbstractBootstrap { } } ``` -需要注意的是,SocketChannel 执行 connect() 操作后有以下三种结果。 -1. 连接成功,返回True; -2. 暂时没有连接上,服务端没有返回 ACK应答,连接结果不确定,返回False; -3. 连接失败,直接抛出 IO异常。 -如果是第二种结果,需要将 NioSocketChannel 中的 selectionKey 设置为 OP_CONNECT,监听连接结果。异步连接返回之后,需要判断连接结果,如果连接成功,则触发 ChannelActive 事件。ChannelActive事件 最终会将 NioSocketChannel 中的 selectionKey 设置为 SelectionKey.OP_READ,用于监听网络读操作。如果没有立即连接上服务端,则注册 SelectionKey.OP_CONNECT 到多路复用器。如果连接过程发生异常,则关闭链路,进入连接失败处理流程。 +需要注意的是,SocketChannel 执行 connect() 操作后有以下三种结果。 + +1. 连接成功,返回 True; +2. 暂时没有连接上,服务端没有返回 ACK 应答,连接结果不确定,返回 False; +3. 连接失败,直接抛出 IO 异常。 + +如果是第二种结果,需要将 NioSocketChannel 中的 selectionKey 设置为 OP_CONNECT,监听连接结果。异步连接返回之后,需要判断连接结果,如果连接成功,则触发 ChannelActive 事件。ChannelActive 事件 最终会将 NioSocketChannel 中的 selectionKey 设置为 SelectionKey.OP_READ,用于监听网络读操作。如果没有立即连接上服务端,则注册 SelectionKey.OP_CONNECT 到多路复用器。如果连接过程发生异常,则关闭链路,进入连接失败处理流程。 ### 异步连接结果通知 -NioEventLoop 的 Selector 轮询 客户端连接Channel,当服务端返回握手应答之后,对连接结果进行判断,代码如下。 + +NioEventLoop 的 Selector 轮询 客户端连接 Channel,当服务端返回握手应答之后,对连接结果进行判断,代码如下。 + ```java - if ((readyOps & 8) != 0) { - int ops = k.interestOps(); - ops &= -9; - k.interestOps(ops); - unsafe.finishConnect(); - } +if ((readyOps & 8) != 0) { + int ops = k.interestOps(); + ops &= -9; + k.interestOps(ops); + unsafe.finishConnect(); +} ``` + 下面对 finishConnect()方法 进行分析,代码如下。 + ```java - try { - boolean wasActive = AbstractNioChannel.this.isActive(); - AbstractNioChannel.this.doFinishConnect(); - this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, wasActive); - } catch (Throwable var5) { - ...... - } +try { + boolean wasActive = AbstractNioChannel.this.isActive(); + AbstractNioChannel.this.doFinishConnect(); + this.fulfillConnectPromise(AbstractNioChannel.this.connectPromise, wasActive); +} catch (Throwable var5) { + ...... +} ``` + doFinishConnect()方法 用于判断 JDK 的 SocketChannel 的连接结果,如果未出错 表示连接成功,其他值或者发生异常表示连接失败。 + ```java - protected void doFinishConnect() throws Exception { - if (!this.javaChannel().finishConnect()) { - throw new Error(); - } +protected void doFinishConnect() throws Exception { + if (!this.javaChannel().finishConnect()) { + throw new Error(); } +} ``` + 连接成功之后,调用 fufillConectPromise()方法,触发链路激活事件,该事件由 ChannelPipeline 进行传播。 ### 客户端连接超时机制 -对于SocketChannel接口,JDK并没有提供连接超时机制,需要NIO框架或者用户自己扩展实现。Netty利用定时器提供了客户端连接超时控制功能,下面我们对该功能进行详细讲解。 -首先,用户在创建Netty 客户端的时候,可以通过ChannelOption.CONNECT_TIMEOUT_MILLIS配置项设置连接超时时间,代码如下。 +对于 SocketChannel 接口,JDK 并没有提供连接超时机制,需要 NIO 框架或者用户自己扩展实现。Netty 利用定时器提供了客户端连接超时控制功能,下面我们对该功能进行详细讲解。 + +首先,用户在创建 Netty 客户端的时候,可以通过 ChannelOption.CONNECT_TIMEOUT_MILLIS 配置项设置连接超时时间,代码如下。 + ```java - Bootstrap b = new Bootstrap(); - b.group(workerGroup); - b.channel(NioSocketChannel.class); - b.option(ChannelOption.SO_KEEPALIVE, true); - b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); -``` \ No newline at end of file +Bootstrap b = new Bootstrap(); +b.group(workerGroup); +b.channel(NioSocketChannel.class); +b.option(ChannelOption.SO_KEEPALIVE, true); +b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000); +``` diff --git "a/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\346\234\215\345\212\241\347\253\257\345\274\200\345\217\221.md" "b/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\346\234\215\345\212\241\347\253\257\345\274\200\345\217\221.md" index be99cd08..05c44164 100644 --- "a/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\346\234\215\345\212\241\347\253\257\345\274\200\345\217\221.md" +++ "b/docs/Netty/\345\237\272\344\272\216Netty\345\274\200\345\217\221\346\234\215\345\212\241\347\253\257\345\217\212\345\256\242\346\210\267\347\253\257/\345\237\272\344\272\216Netty\347\232\204\346\234\215\345\212\241\347\253\257\345\274\200\345\217\221.md" @@ -1,18 +1,20 @@ ## Netty 服务端创建源码分析 -当我们直接使用 JDK 的 NIO类库 开发基于 NIO 的异步服务端时,需要用到 多路复用器Selector、ServerSocketChannel、SocketChannel、ByteBuffer、SelectionKey 等,相比于传统的 BIO开发,NIO 的开发要复杂很多,开发出稳定、高性能的异步通信框架,一直是个难题。Netty 为了向使用者屏蔽 NIO通信 的底层细节,在和用户交互的边界做了封装,目的就是为了减少用户开发工作量,降低开发难度。ServerBootstrap 是 Socket服务端 的启动辅助类,用户通过 ServerBootstrap 可以方便地创建 Netty 的服务端。 + +当我们直接使用 JDK 的 NIO 类库 开发基于 NIO 的异步服务端时,需要用到 多路复用器 Selector、ServerSocketChannel、SocketChannel、ByteBuffer、SelectionKey 等,相比于传统的 BIO 开发,NIO 的开发要复杂很多,开发出稳定、高性能的异步通信框架,一直是个难题。Netty 为了向使用者屏蔽 NIO 通信 的底层细节,在和用户交互的边界做了封装,目的就是为了减少用户开发工作量,降低开发难度。ServerBootstrap 是 Socket 服务端 的启动辅助类,用户通过 ServerBootstrap 可以方便地创建 Netty 的服务端。 ### Netty 服务端创建时序图 -![avatar](../../../images/Netty/Netty服务端创建时序图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty服务端创建时序图.png) -下面我们对 Netty服务端创建 的关键步骤和原理进行详细解析。 +下面我们对 Netty 服务端创建 的关键步骤和原理进行详细解析。 -1、**创建 ServerBootstrap实例**。ServerBootstrap 是 Netty服务端 的 启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层对各种 原生NIO 的 API 进行了封装,减少了用户与 底层API 的接触,降低了开发难度。ServerBootstrap 中只有一个 public 的无参的构造函数可以给用户直接使用,ServerBootstrap 只开放一个无参的构造函数 的根本原因是 它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入 Builder建造者模式。 +1、**创建 ServerBootstrap 实例**。ServerBootstrap 是 Netty 服务端 的 启动辅助类,它提供了一系列的方法用于设置服务端启动相关的参数。底层对各种 原生 NIO 的 API 进行了封装,减少了用户与 底层 API 的接触,降低了开发难度。ServerBootstrap 中只有一个 public 的无参的构造函数可以给用户直接使用,ServerBootstrap 只开放一个无参的构造函数 的根本原因是 它的参数太多了,而且未来也可能会发生变化,为了解决这个问题,就需要引入 Builder 建造者模式。 -2、**设置并绑定 Reactor线程池**。Netty 的 Reactor线程池 是 EventLoopGroup,它实际上是一个 EventLoop数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 Channel,Selector 的轮询操作由绑定的 EventLoop线程 的 run()方法 驱动,在一个循环体内循环执行。值得说明的是,EventLoop 的职责不仅仅是处理 网络IO事件,用户自定义的Task 和 定时任务Task 也统一由 EventLoop 负责处理,这样线程模型就实现了统一。从调度层面看,也不存在从 EventLoop线程 中再启动其他类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了 IO线程 的处理和调度性能。 +2、**设置并绑定 Reactor 线程池**。Netty 的 Reactor 线程池 是 EventLoopGroup,它实际上是一个 EventLoop 数组。EventLoop 的职责是处理所有注册到本线程多路复用器 Selector 上的 Channel,Selector 的轮询操作由绑定的 EventLoop 线程 的 run()方法 驱动,在一个循环体内循环执行。值得说明的是,EventLoop 的职责不仅仅是处理 网络 IO 事件,用户自定义的 Task 和 定时任务 Task 也统一由 EventLoop 负责处理,这样线程模型就实现了统一。从调度层面看,也不存在从 EventLoop 线程 中再启动其他类型的线程用于异步执行另外的任务,这样就避免了多线程并发操作和锁竞争,提升了 IO 线程 的处理和调度性能。 -3、**设置并绑定 服务端Channel**。作为 NIO服务端,需要创建 ServerSocketChannel,Netty 对 原生NIO类库 进行了封装,对应的实现是NioServerSocketChannel。对于用户而言,不需要关心 服务端Channel 的底层实现细节和工作原理,只需要指定具体使用哪种服务端 Channel 即可。因此,Netty 中 ServerBootstrap的基类 提供了 channel()方法,用于指定 服务端Channel 的类型。Netty 通过工厂类,利用反射创建 NioServerSocketChannel对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代 +3、**设置并绑定 服务端 Channel**。作为 NIO 服务端,需要创建 ServerSocketChannel,Netty 对 原生 NIO 类库 进行了封装,对应的实现是 NioServerSocketChannel。对于用户而言,不需要关心 服务端 Channel 的底层实现细节和工作原理,只需要指定具体使用哪种服务端 Channel 即可。因此,Netty 中 ServerBootstrap 的基类 提供了 channel()方法,用于指定 服务端 Channel 的类型。Netty 通过工厂类,利用反射创建 NioServerSocketChannel 对象。由于服务端监听端口往往只需要在系统启动时才会调用,因此反射对性能的影响并不大。相关代 码如下。 + ```java public abstract class AbstractBootstrap, C extends Channel> implements Cloneable { @@ -28,7 +30,8 @@ public abstract class AbstractBootstrap, C ext } ``` -4、**链路建立的时候创建并初始化 ChannelPipeline**。ChannelPipeline 并不是 NIO服务端 必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。网络事件以事件流的形式在 ChannelPipeline 中流转,由 ChannelPipeline 根据 ChannelHandler的执行策略 调度 ChannelHandler的执行。典型的网络事件如下。 +4、**链路建立的时候创建并初始化 ChannelPipeline**。ChannelPipeline 并不是 NIO 服务端 必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行 ChannelHandler。网络事件以事件流的形式在 ChannelPipeline 中流转,由 ChannelPipeline 根据 ChannelHandler 的执行策略 调度 ChannelHandler 的执行。典型的网络事件如下。 + 1. 链路注册; 2. 链路激活; 3. 链路断开; @@ -38,7 +41,8 @@ public abstract class AbstractBootstrap, C ext 7. 链路发生异常; 8. 发生用户自定义事件。 -5、**初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler**。ChannelHandler 是 Netty 提供给用户定制和扩展的关键接口。利用 ChannelHandler 用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL 认证、流量控制和流量整形等。Netty 同时也提供了大量的 系统ChannelHandler 供用户使用,比较实用的 系统ChannelHandler 总结如下。 +5、**初始化 ChannelPipeline 完成之后,添加并设置 ChannelHandler**。ChannelHandler 是 Netty 提供给用户定制和扩展的关键接口。利用 ChannelHandler 用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL 认证、流量控制和流量整形等。Netty 同时也提供了大量的 系统 ChannelHandler 供用户使用,比较实用的 系统 ChannelHandler 总结如下。 + 1. 系统编解码框架,ByteToMessageCodec; 2. 基于长度的半包解码器,LengthFieldBasedFrameDecoder; 3. 码流日志打印 Handler,LoggingHandler; @@ -46,7 +50,8 @@ public abstract class AbstractBootstrap, C ext 5. 链路空闲检测 Handler,IdleStateHandler; 6. 流量整形 Handler,ChannelTrafficShapingHandler; 7. Base64 编解码,Base64Decoder 和 Base64Encoder。 -创建和添加 ChannelHandler 的代码示例如下。 + 创建和添加 ChannelHandler 的代码示例如下。 + ```java .childHandler( new ChannelInitializer() { @Override @@ -58,29 +63,31 @@ public abstract class AbstractBootstrap, C ext 6、**绑定并启动监听端口**。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将 ServerSocketChannel 注册到 Selector 上监听客户端连接。 -7、**Selector 轮询**。由 Reactor线程 NioEventLoop 负责调度和执行 Selector 轮询操作,选择准备就绪的 Channel集合,相关代码如下。 +7、**Selector 轮询**。由 Reactor 线程 NioEventLoop 负责调度和执行 Selector 轮询操作,选择准备就绪的 Channel 集合,相关代码如下。 + ```java public final class NioEventLoop extends SingleThreadEventLoop { private void select(boolean oldWakenUp) throws IOException { Selector selector = this.selector; - + ...... int selectedKeys = selector.select(timeoutMillis); selectCnt ++; ...... - + } } ``` -8、**当轮询到 准备就绪的Channel 之后,就由 Reactor线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。 +8、**当轮询到 准备就绪的 Channel 之后,就由 Reactor 线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。 -![avatar](../../../images/Netty/ChannelPipeline的调度相关方法.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ChannelPipeline的调度相关方法.png) + +9、**执行 Netty 中 系统的 ChannelHandler 和 用户添加定制的 ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler,相关代码如下。 -9、**执行 Netty 中 系统的ChannelHandler 和 用户添加定制的ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler,相关代码如下。 ```java public class DefaultChannelPipeline implements ChannelPipeline { @@ -92,13 +99,17 @@ public class DefaultChannelPipeline implements ChannelPipeline { } ``` -### 结合 Netty源码 对服务端的创建过程进行解析 -首先通过构造函数创建 ServerBootstrap实例,随后,通常会创建两个 EventLoopGroup实例 (也可以只创建一个并共享),代码如下。 +### 结合 Netty 源码 对服务端的创建过程进行解析 + +首先通过构造函数创建 ServerBootstrap 实例,随后,通常会创建两个 EventLoopGroup 实例 (也可以只创建一个并共享),代码如下。 + ```java EventLoopGroup acceptorGroup = new NioEventLoopGroup(); EventLoopGroup iOGroup = new NioEventLoopGroup(); ``` -NioEventLoopGroup 实际就是一个 Reactor线程池,负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过 ServerBootstrap 的 group()方法 将两个 EventLoopGroup实例 传入,代码如下。 + +NioEventLoopGroup 实际就是一个 Reactor 线程池,负责调度和执行客户端的接入、网络读写事件的处理、用户自定义任务和定时任务的执行。通过 ServerBootstrap 的 group()方法 将两个 EventLoopGroup 实例 传入,代码如下。 + ```java public class ServerBootstrap extends AbstractBootstrap { @@ -120,7 +131,9 @@ public class ServerBootstrap extends AbstractBootstrap, C extends Channel> implements Cloneable { @@ -142,7 +155,9 @@ public abstract class AbstractBootstrap, C ext } } ``` -该方法会被客户端和服务端重用,用于设置 工作IO线程,执行和调度网络事件的读写。线程组和线程类型设置完成后,需要设置 服务端Channel 用于端口监听和客户端链路接入。Netty 通过 Channel工厂类 来创建不同类型的 Channel,对于服务端,需要创建 NioServerSocketChannel。所以,通过指定 Channel类型 的方式创建 Channel工厂。ReflectiveChannelFactory 可以根据 Channel的类型 通过反射创建 Channel的实例,服务端需要创建的是 NioServerSocketChannel实例,代码如下。 + +该方法会被客户端和服务端重用,用于设置 工作 IO 线程,执行和调度网络事件的读写。线程组和线程类型设置完成后,需要设置 服务端 Channel 用于端口监听和客户端链路接入。Netty 通过 Channel 工厂类 来创建不同类型的 Channel,对于服务端,需要创建 NioServerSocketChannel。所以,通过指定 Channel 类型 的方式创建 Channel 工厂。ReflectiveChannelFactory 可以根据 Channel 的类型 通过反射创建 Channel 的实例,服务端需要创建的是 NioServerSocketChannel 实例,代码如下。 + ```java public class ReflectiveChannelFactory implements ChannelFactory { @@ -168,17 +183,19 @@ public class ReflectiveChannelFactory implements ChannelFacto } } ``` -指定 NioServerSocketChannel 后,需要设置 TCP 的一些参数,作为服务端,主要是设置 TCP 的 backlog参数。 -backlog 指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列 和 已连接队列,根据 TCP三次握手 的 三个子过程来分隔这两个队列。服务器处于 listen状态 时,收到客户端 syn过程(connect) 时在未完成队列中创建一个新的条目,然后用三次握手的第二个过程,即服务器的 syn响应客户端,此条目在第三个过程到达前 (客户端对服务器 syn 的 ack) 一直保留在未完成连接队列中,如果三次握手完成,该条目将从未完成连接队列搬到已完成连接队列尾部。当进程调用 accept 时,从已完成队列中的头部取出一个条目给进程,当已完成队列为空时进程将睡眠,直到有条目在已完成连接队列中才唤醒。backlog 被规定为两个队列总和的最大值,大多数实现默认值为 5,但在高并发 Web服务器 中此值显然不够。 需要设置此值更大一些的原因是,未完成连接队列的长度可能因为客户端 syn 的到达及等待三次握手的第三个过程延时 而增大。Netty 默认的 backlog 为 100,当然,用户可以修改默认值,这需要根据实际场景和网络状况进行灵活设置。 +指定 NioServerSocketChannel 后,需要设置 TCP 的一些参数,作为服务端,主要是设置 TCP 的 backlog 参数。 + +backlog 指定了内核为此套接口排队的最大连接个数,对于给定的监听套接口,内核要维护两个队列:未链接队列 和 已连接队列,根据 TCP 三次握手 的 三个子过程来分隔这两个队列。服务器处于 listen 状态 时,收到客户端 syn 过程(connect) 时在未完成队列中创建一个新的条目,然后用三次握手的第二个过程,即服务器的 syn 响应客户端,此条目在第三个过程到达前 (客户端对服务器 syn 的 ack) 一直保留在未完成连接队列中,如果三次握手完成,该条目将从未完成连接队列搬到已完成连接队列尾部。当进程调用 accept 时,从已完成队列中的头部取出一个条目给进程,当已完成队列为空时进程将睡眠,直到有条目在已完成连接队列中才唤醒。backlog 被规定为两个队列总和的最大值,大多数实现默认值为 5,但在高并发 Web 服务器 中此值显然不够。 需要设置此值更大一些的原因是,未完成连接队列的长度可能因为客户端 syn 的到达及等待三次握手的第三个过程延时 而增大。Netty 默认的 backlog 为 100,当然,用户可以修改默认值,这需要根据实际场景和网络状况进行灵活设置。 -TCP参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler;父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。 +TCP 参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler;父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。 -![avatar](../../../images/Netty/ServerBootstrap的Handler模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ServerBootstrap的Handler模型.png) -本质区别就是:ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有连接该监听端口的客户端都会执行它;父类AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。 +本质区别就是:ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有连接该监听端口的客户端都会执行它;父类 AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。 服务端启动的最后一步,就是绑定本地端口,启动服务,下面我们来分析下这部分代码。 + ```java public abstract class AbstractBootstrap, C extends Channel> implements Cloneable { @@ -219,7 +236,9 @@ public abstract class AbstractBootstrap, C ext } } ``` -先看下上述代码调用的 initAndRegister()方法。它首先实例化了一个 NioServerSocketChannel类型 的 Channel对象。相关代码如下。 + +先看下上述代码调用的 initAndRegister()方法。它首先实例化了一个 NioServerSocketChannel 类型 的 Channel 对象。相关代码如下。 + ```java final ChannelFuture initAndRegister() { Channel channel = null; @@ -248,7 +267,9 @@ public abstract class AbstractBootstrap, C ext return regFuture; } ``` + NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作主要有以下三点。 + ```java @Override void init(Channel channel) throws Exception { @@ -303,9 +324,11 @@ NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作 }); } ``` -到此,Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。 -![avatar](../../../images/Netty/NioServerSocketChannel的ChannelPipeline.png) -最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor线程 的多路复用器上监听新客户端的接入,代码如下。 + +到此,Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor 线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NioServerSocketChannel的ChannelPipeline.png) +最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor 线程 的多路复用器上监听新客户端的接入,代码如下。 + ```java public abstract class AbstractChannel extends DefaultAttributeMap implements Channel { @@ -317,7 +340,7 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha */ @Override public final void register(EventLoop eventLoop, final ChannelPromise promise) { - + ...... // 首先判断是否是 NioEventLoop 自身发起的操作。如果是,则不存在并发操作,直接 @@ -335,9 +358,9 @@ public abstract class AbstractChannel extends DefaultAttributeMap implements Cha } }); } catch (Throwable t) { - + ...... - + } } } @@ -387,18 +410,21 @@ public abstract class AbstractNioChannel extends AbstractChannel { selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); return; } catch (CancelledKeyException e) { - + ...... - + } } } } ``` + 到此,服务端监听启动部分源码已经分析完成。 -## 结合 Netty源码 对客户端接入过程进行解析 -负责处理网络读写、连接和客户端请求接入的 Reactor线程 就是 NioEventLoop,下面我们看下 NioEventLoop 是如何处理新的客户端连接接入的。当 多路复用器 检测到新的准备就绪的 Channel 时,默认执行 processSelectedKeysOptimized()方法,代码如下。 +## 结合 Netty 源码 对客户端接入过程进行解析 + +负责处理网络读写、连接和客户端请求接入的 Reactor 线程 就是 NioEventLoop,下面我们看下 NioEventLoop 是如何处理新的客户端连接接入的。当 多路复用器 检测到新的准备就绪的 Channel 时,默认执行 processSelectedKeysOptimized()方法,代码如下。 + ```java public final class NioEventLoop extends SingleThreadEventLoop { @@ -521,9 +547,9 @@ public abstract class AbstractNioMessageChannel extends AbstractNioChannel { // channelRead方法 pipeline.fireChannelRead(readBuf.get(i)); } - + ...... - + } } } @@ -546,9 +572,9 @@ public class NioServerSocketChannel extends AbstractNioMessageChannel return 1; } } catch (Throwable t) { - + ...... - + } return 0; } @@ -590,11 +616,13 @@ public class ServerBootstrap extends AbstractBootstrap initlen); + + // 创建字符串,+1是因为 `\0` 结束符 + // sh指向header首字节 + sh = s_malloc(hdrlen+initlen+1); + if (sh == NULL) return NULL; + if (init==SDS_NOINIT) + init = NULL; + else if (!init) + memset(sh, 0, hdrlen+initlen+1); + + // s指向buf + s = (char*)sh+hdrlen; + + // s减1得到flags + fp = ((unsigned char*)s)-1; + + // 赋值len, alloc, flags + ... + + + // 赋值buf[] + if (initlen && init) + memcpy(s, init, initlen); + + // 在s末尾添加\0结束符 + s[initlen] = '\0'; + + // 返回指向buf的指针s + return s; +} +``` + +创建 SDS 的大致流程是这样的:首先根据字符串长度计算得到 type,根据 type 计算头部所需长度,然后动态分配内存空间。通过计算出指向 header 的指针 sh,指向 buf 的指针 s,对结构体各字段进行赋值。 + +注意: + +1. 创建空字符串时,`SDS_TYPE_5` 被强制转换为 `SDS_TYPE_8`(原因是创建空字符串后,内容可能会频繁更新而引发扩容操作,故直接创建为 sdshdr8) +2. 长度计算有 `+1` 操作,因为结束符 `\0` 会占用一个长度的空间。 +3. 返回的是指向 buf 的指针 s。 +4. 创建时分配到字节数 initlen+initlen+1,基本等于结构体头部长度+字符数组长度,没有预留多余空间。 + +### 2. 清空字符串 + +SDS 提供了两种清空字符串的方法。 + +一种是通过 s 偏移得到结构体的地址,然后调用 `s_free` 直接释放内存。 + +```c +void sdsfree(sds s) { + if (s == NULL) return; + + // s减去头部的大小得到结构体的地址 + s_free((char*)s-sdsHdrSize(s[-1])); +} +``` + +另一种是通过重置 len 属性值而达到清空字符串的目的,本质上 buf 并没有被真正清除,新的数据会直接覆盖 buf 中原有的数据,无需申请新的内存空间。 + +```c +void sdsclear(sds s) { + + // 将len属性置为0 + sdssetlen(s, 0); + s[0] = '\0'; +} +``` + +### 3. 更新 len + +因为 sdsnewlen 函数返回的是 char\* 类型的 buf,所以兼容了 c 语言操作字符串的函数, +那么当 `s = ['a', 'b', 'c', '\0']` 时, 再操作`s[2] = '\0'`, 这个时候`sdslen(s)`得到的结果是 3,因为 len 字段没有更新,如果直接更新`'\0'`,需要调用以下函数更新 len + +```c +void sdsupdatelen(sds s) { + size_t reallen = strlen(s); + sdssetlen(s, reallen); +} +``` + +### 4. 拼接字符串 + +SDS 拼接字符串的实现如下: + +```c +sds sdscatsds(sds s, const sds t) { + return sdscatlen(s, t, sdslen(t)); +} +``` + +可以看到 `sdscatsds` 内部调用的是 `sdscatlen`。 + +而 `sdscatlen` 内部的实现相对复杂一些,由于拼接字符串可能涉及 SDS 的扩容,因此 `sdscatlen` 内部调用 `sdsMakeRoomFor` 对拼接的字符串做检查:若无需扩容,直接返回 s;若需要扩容,则返回扩容好的新字符串 s。 + +```c +sds sdscatlen(sds s, const void *t, size_t len) { + // 计算当前字符串长度 + size_t curlen = sdslen(s); + + // 确保s的剩余空间足以拼接上t + s = sdsMakeRoomFor(s,len); + if (s == NULL) return NULL; + + // 拼接s、t + memcpy(s+curlen, t, len); + + // 更新s的len属性 + sdssetlen(s, curlen+len); + + // s末尾添加\0结束符 + s[curlen+len] = '\0'; + + return s; +} + +``` + +SDS 的扩容策略是这样的: + +1. 若 SDS 中剩余空闲长度 avail 大于或等于新增内容的长度 addlen,无需扩容。 +2. 若 SDS 中剩余空闲长度 avail 小于或等于 addlen,则分情况讨论:新增后总长度 `len+addlen < 1MB` 的,按新长度的 2 倍扩容;新增后总长度 `len+addlen >= 1MB` 的,按新长度加上 `1MB` 扩容。 + +```c +sds sdsMakeRoomFor(sds s, size_t addlen) { + void *sh, *newsh; + // 当前剩余长度 + size_t avail = sdsavail(s); + + size_t len, newlen; + char type, oldtype = s[-1] & SDS_TYPE_MASK; + int hdrlen; + + /* 剩余长度>=新增字符串长度,直接返回 */ + if (avail >= addlen) return s; + + // 计算当前字符串长度len + len = sdslen(s); + + sh = (char*)s-sdsHdrSize(oldtype); + + // 计算新长度 + newlen = (len+addlen); + + // 检查长度是否溢出 + assert(newlen > len); + + // 新长度<1MB,按新长度的2倍扩容 + if (newlen < SDS_MAX_PREALLOC) + newlen *= 2; + // 否则按新长度+1MB扩容 + else + newlen += SDS_MAX_PREALLOC; + + // 计算新长度所属类型 + type = sdsReqType(newlen); + + /* type5不支持扩容,强转为type8 */ + if (type == SDS_TYPE_5) type = SDS_TYPE_8; + + hdrlen = sdsHdrSize(type); + + // 检查长度是否溢出 + assert(hdrlen + newlen + 1 > len); + + if (oldtype==type) { + // 类型没变,直接通过realloc扩大动态数组即可。 + newsh = s_realloc(sh, hdrlen+newlen+1); + if (newsh == NULL) return NULL; + s = (char*)newsh+hdrlen; + } else { + // 类型改变了,则说明头部长度也发生了变化,不进行realloc操作,而是直接重新开辟内存 + newsh = s_malloc(hdrlen+newlen+1); + if (newsh == NULL) return NULL; + + // 原内存拷贝到新的内存地址上 + memcpy((char*)newsh+hdrlen, s, len+1); + + // 释放原先空间 + s_free(sh); + s = (char*)newsh+hdrlen; + + // 为flags赋值 + s[-1] = type; + + // 为len属性赋值 + sdssetlen(s, len); + } + + // 为alloc属性赋值 + sdssetalloc(s, newlen); + return s; +} +``` + +## 总结 + +1. SDS 返回的是指向 buf 的指针,同时以`\0`结尾,所以兼容了 C 语言操作字符串的函数,读取内容时,通过 len 属性来限制读取的长度,不受 `\0` 影响,从而保证二进制安全; +2. Redis 根据字符串长度的不同,定义了多种数据结构,包括:sdshdr5/sdshdr8/sdshdr16/sdshdr32/sdshdr64。 +3. SDS 在设计字符串修改出会调用 `sdsMakeRoomFor` 函数进行检查,根据不同情况进行扩容。 diff --git "a/docs/Sentinel/Sentinel\345\272\225\345\261\202LongAdder\347\232\204\350\256\241\346\225\260\345\256\236\347\216\260.md" "b/docs/Sentinel/Sentinel\345\272\225\345\261\202LongAdder\347\232\204\350\256\241\346\225\260\345\256\236\347\216\260.md" new file mode 100644 index 00000000..677130f5 --- /dev/null +++ "b/docs/Sentinel/Sentinel\345\272\225\345\261\202LongAdder\347\232\204\350\256\241\346\225\260\345\256\236\347\216\260.md" @@ -0,0 +1,70 @@ +## LongAdder 的原理 + +在 LongAdder 中,底层通过多个数值进行累加来得到最后的结果。当多个线程对同一个 LongAdder 进行更新的时候,将会对这一些列的集合进行动态更新,以避免多线程之间的资源竞争。当需要得到 LongAdder 的具体的值的时候,将会将一系列的值进行求和作为最后的结果。 + +在高并发的竞争下进行类似指标数据的收集的时候,LongAdder 通常会和 AtomicLong 进行比较,在低竞争的场景下,两者有着相似的性能表现。而当在高并发竞争的场景下,LongAdder 将会表现更高的性能,但是也会伴随更高的内存消耗。 + +## LongAdder 的代码实现 + +```java +transient volatile Cell[] cells; +transient volatile long base; +``` + +cells 是一个简单的 Cell 数组,当比如通过 LongAdder 的 `add()` 方法进行 LongAdder 内部的数据的更新的时候,将会根据每个线程的一个 hash 值与 cells 数组的长度进行取模而定位,并在定位上的位置进行数据更新。而 base 则是当针对 LongAdder 的数据的更新时,并没有线程竞争的时候,将会直接更新在 base 上,而不需要前面提到的 hash 再定位过程,当 LongAdder 的 `sum()` 方法被调用的时候,将会对 cells 的所有数据进行累加在加上 sum 的值进行返回。 + +```java +public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { sum += a.value; } + } + } + return sum; +} +``` + +相比 `sum()` 方法,LongAdder 的 `add()` 方法要复杂得多。 + +```java +public void add(long x) { + Cell[] as; + long b, v; + HashCode hc; + Cell a; + int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) { retryUpdate(x, hc, uncontended); } + } +} +``` + +在 `add()` 方法的一开始,将会观察 cells 数组是否存在,如果不存在,将会尝试直接通过 `casBase()` 方法在 base 上通过 cas 更新,这是在低并发竞争下的 `add()` 流程,这一流程的前提是对于 LongAdder 的更新并没有遭遇别的线程的并发修改。 + +在当 cells 已经存在,而或者对于 base 的 cas 更新失败,都将会将数据的更新落在 cells 数组之上。首先,每个线程都会在其 ThreadLocal 中生成一个线程专有的随机数,并根据这个随机数与 cells 进行取模,定位到的位置进行 cas 修改。在这个流程下,由于根据线程专有的随机数进行 hash 而定位的流程,尽可能的避免了线程间的资源竞争。但是仍旧可能存在 hash 碰撞而导致两个线程定位到了同一个 cells 槽位的情况,这里就需要通过 `retryUpdate()` 方法进行进一步的解决。 + +`retryUpdate()` 方法的代码很长,但是逻辑很清晰,主要分为一下几个流程,其中的主流程是一个死循环,进入 `retryUpdate()` 方法后,将会不断尝试执行主要逻辑,直到对应的逻辑执行完毕: + +1. 当进入 `retryUpdate()` 的时候,cells 数组还没有创建,将会尝试获取锁并初始化 cells 数组并直接在 cells 数组上进行修改,而别的线程在没创建的情况下进入并获取锁失败,将会直接尝试在 base 上进行更行。 +2. 当进入 `retryUpdate()` 的时候,cells 数组已经创建,但是分配给其的数组槽位的 Cells 还没有进行初始化,那么将会尝试获取锁并对该槽位进行初始化。 +3. 当进入 `retryUpdate()` 的时候,cells 数组已经创建,分配给其的槽位的 Cell 也已经完成了初始化,而是因为所定位到的槽位与别的线程发生了 hash 碰撞,那么将会加锁并扩容 cells 数组,之后对该线程持有的 hash 进行 rehash,在下一轮循环中对新定位的槽位数据进行更新。而别的线程在尝试扩容并获取锁失败的时候,将会直接对自己 rehash 并在下一轮的循环中重新在新的 cells 数组中进行定位更新。 + +## Cell 本身的内存填充 + +最后,提一下 cells 数组中的 Cell 对象。 + +```java +volatile long p0, p1, p2, p3, p4, p5, p6; +volatile long value; +volatile long q0, q1, q2, q3, q4, q5, q6; +``` + +每个 Cell 对象中具体存放的 value 前后都由 7 个 long 类型的字段进行内存填充以避免缓存行伪共享而导致的缓存失效。 diff --git "a/docs/Sentinel/Sentinel\346\227\266\351\227\264\347\252\227\345\217\243\347\232\204\345\256\236\347\216\260.md" "b/docs/Sentinel/Sentinel\346\227\266\351\227\264\347\252\227\345\217\243\347\232\204\345\256\236\347\216\260.md" new file mode 100644 index 00000000..1c58f2cf --- /dev/null +++ "b/docs/Sentinel/Sentinel\346\227\266\351\227\264\347\252\227\345\217\243\347\232\204\345\256\236\347\216\260.md" @@ -0,0 +1,39 @@ +## 获取时间窗口的主要流程 + +在 Sentinel 中,主要是通过 LeapArray 类来实现滑动时间窗口的实现和选择。在 sentinel 的这个获取时间窗口并为时间窗口添加指标的过程中,主要的流程为: + +- 根据当前时间选择当前时间应该定位当前时间应该属于的时间窗口 id。 +- 根据时间窗口 id 获取时间窗口。这里可能会存在四种情况: + +1. 时间窗口还未建立,那么将会为此次流量的进入建立一个新的时间窗口返回,并且接下来这个时间窗口内的获取请求都将返回该窗口。 +2. 时间窗口已经建立的情况下,将会直接获取已经存在的符合条件的时间窗口。 +3. 时间窗口可能已经存在,但是当前获取的时间窗口已经过期,需要加锁,并重置当前时间窗口。 +4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。 + +其中的第四个情况表明,sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。 + +```java +protected final AtomicReferenceArray> 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 类来记录单个指标的值而不是 AtomicLong,LongAdder 内部的核心思路是为各个线程分配一个专属变量进行更新,在需要总数的时候对这一系列进行累加,因此在更新值的时候相比 AtomicLong 会尽可能减少线程间的竞争,达到高效的 metric 更新。 diff --git "a/docs/Sentinel/Sentinel\351\231\220\346\265\201\347\256\227\346\263\225\347\232\204\345\256\236\347\216\260.md" "b/docs/Sentinel/Sentinel\351\231\220\346\265\201\347\256\227\346\263\225\347\232\204\345\256\236\347\216\260.md" new file mode 100644 index 00000000..90b2413e --- /dev/null +++ "b/docs/Sentinel/Sentinel\351\231\220\346\265\201\347\256\227\346\263\225\347\232\204\345\256\236\347\216\260.md" @@ -0,0 +1,140 @@ +## Sentinel 中漏桶算法的实现 + +Sentinel 中漏桶算法通过 RateLimiterController 来实现,在漏桶算法中,会记录上一个请求的到达时间,如果新到达的请求与上一次到达的请求之间的时间差小于限流配置所规定的最小时间,新到达的请求将会排队等待规定的最小间隔到达,或是直接失败。 + +```java +@Override +public boolean canPass(Node node, int acquireCount, boolean prioritized) { + if (acquireCount <= 0) { + return true; + } + + if (count <= 0) { + return false; + } + + long currentTime = TimeUtil.currentTimeMillis(); + // 根据配置计算两次请求之间的最小时间 + long costTime = Math.round(1.0 * (acquireCount) / count * 1000); + + // 计算上一次请求之后,下一次允许通过的最小时间 + long expectedTime = costTime + latestPassedTime.get(); + + if (expectedTime <= currentTime) { + // 如果当前时间大于计算的时间,那么可以直接放行 + latestPassedTime.set(currentTime); + return true; + } else { + // 如果没有,则计算相应需要等待的时间 + long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis(); + if (waitTime > maxQueueingTimeMs) { + return false; + } else { + long oldTime = latestPassedTime.addAndGet(costTime); + try { + waitTime = oldTime - TimeUtil.currentTimeMillis(); + // 如果最大等待时间小于需要等待的时间,那么返回失败,当前请求被拒绝 + if (waitTime > maxQueueingTimeMs) { + latestPassedTime.addAndGet(-costTime); + return false; + } + // 在并发条件下等待时间可能会小于等于0 + if (waitTime > 0) { + Thread.sleep(waitTime); + } + return true; + } catch (InterruptedException e) { + } + } + } + return false; +} +``` + +## Sentinel 中令牌桶算法的实现 + +在 Sentinel 中,令牌桶算法通过 WarmUpController 类实现。在这个情况下,当配置每秒能通过多少请求后,那么在这里 sentinel 也会每秒往桶内添加多少的令牌。当一个请求进入的时候,将会从中移除一个令牌。由此可以得出,桶内的令牌越多,也说明当前的系统利用率越低。因此,当桶内的令牌数量超过某个阈值后,那么当前的系统可以称之为处于`饱和`状态。 +当系统处于 `饱和`状态的时候,当前允许的最大 qps 将会随着剩余的令牌数量减少而缓慢增加,达到为系统预热热身的目的。 + +```java +this.count = count; + +this.coldFactor = coldFactor; + +warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1); + +maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor)); + +slope = (coldFactor - 1.0) / count / (maxToken - warningToken); +``` + +其中 count 是当前 qps 的阈值。coldFactor 则为冷却因子,warningToken 则为警戒的令牌数量,warningToken 的值为`(热身时间长度 * 每秒令牌的数量) / (冷却因子 - 1)`。maxToken 则是最大令牌数量,具体的值为 `warningToken + (2 * 热身时间长度 * 每秒令牌数量) / (冷却因子 + 1)`。当当前系统处于热身时间内,其允许通过的最大 qps 为 `1 / (超过警戒数的令牌数 * 斜率 slope + 1 / count)`,而斜率的值为`(冷却因子 - 1) / count / (最大令牌数 - 警戒令牌数)`。 + +举个例子: count = 3, coldFactor = 3,热身时间为 4 的时候,警戒令牌数为 6,最大令牌数为 12,当剩余令牌处于 6 和 12 之间的时候,其 slope 斜率为 1 / 9。 那么当剩余令牌数为 9 的时候的允许 qps 为 1.5。其 qps 将会随着剩余令牌数的不断减少而直到增加到 count 的值。 + +```java +@Override +public boolean canPass(Node node, int acquireCount, boolean prioritized) { + long passQps = (long) node.passQps(); + + long previousQps = (long) node.previousPassQps(); + // 首先重新计算其桶内剩余的数量 + syncToken(previousQps); + + // 开始计算它的斜率 + // 如果进入了警戒线,开始调整他的qps + long restToken = storedTokens.get(); + if (restToken >= warningToken) { + long aboveToken = restToken - warningToken; + // 如果当前剩余的令牌数大于警戒数,那么需要根据准备的计算公式重新计算qps,这个qps小于设定的阈值 + double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); + if (passQps + acquireCount <= warningQps) { + return true; + } + } else { + if (passQps + acquireCount <= count) { + return true; + } + } + + return false; +} + +protected void syncToken(long passQps) { + long currentTime = TimeUtil.currentTimeMillis(); + currentTime = currentTime - currentTime % 1000; + long oldLastFillTime = lastFilledTime.get(); + if (currentTime <= oldLastFillTime) { + return; + } + + long oldValue = storedTokens.get(); + long newValue = coolDownTokens(currentTime, passQps); + + if (storedTokens.compareAndSet(oldValue, newValue)) { + // 从桶内移除相应数量的令牌,并更新最后更新时间 + long currentValue = storedTokens.addAndGet(0 - passQps); + if (currentValue < 0) { + storedTokens.set(0L); + } + lastFilledTime.set(currentTime); + } + +} + +private long coolDownTokens(long currentTime, long passQps) { + long oldValue = storedTokens.get(); + long newValue = oldValue; + + // 当令牌的消耗程度远远低于警戒线的时候,将会补充令牌数 + if (oldValue < warningToken) { + newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); + } else if (oldValue > warningToken) { + if (passQps < (int)count / coldFactor) { + // qps小于阈值 / 冷却因子的时候,说明此时还不需要根据剩余令牌数调整qps的阈值,所以也会补充 + newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); + } + } + return Math.min(newValue, maxToken); +} +``` diff --git "a/docs/Spring/AOP/AOP\346\272\220\347\240\201\345\256\236\347\216\260\345\217\212\345\210\206\346\236\220.md" "b/docs/Spring/AOP/AOP\346\272\220\347\240\201\345\256\236\347\216\260\345\217\212\345\210\206\346\236\220.md" index 629e5a6c..e7b936d0 100644 --- "a/docs/Spring/AOP/AOP\346\272\220\347\240\201\345\256\236\347\216\260\345\217\212\345\210\206\346\236\220.md" +++ "b/docs/Spring/AOP/AOP\346\272\220\347\240\201\345\256\236\347\216\260\345\217\212\345\210\206\346\236\220.md" @@ -1,7 +1,9 @@ 理论性的文字,我觉得就没必要再扯一遍咯,大道理讲这么多,越听越迷糊。不如直接看源码加注释来的明白痛快。所以话不多说,直接上源码。 ## 1 主要的接口 + ### 1.1 Advice 通知 + 本接口定义了切面的增强方式,如:前置增强 BeforeAdvice,后置增强 AfterAdvice,异常增强 ThrowsAdvice 等。下面看两个主要的子接口的源码。 ```java @@ -21,21 +23,23 @@ public interface AfterReturningAdvice extends AfterAdvice { void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable; } ``` + ### 1.2 Pointcut 方法的横切面 + 本接口用来定义需要增强的目标方法的集合,一般使用正则表达式去匹配筛选指定范围内的所有满足条件的目标方法。Pointcut 接口有很多实现,我们主要看一下 JdkRegexpMethodPointcut 和 NameMatchMethodPointcut 的实现原理,前者主要通过正则表达式对方法名进行匹配,后者则通过匹配方法名进行匹配。 ```java // JdkRegexpMethodPointcut 的实现源码 private Pattern[] compiledPatterns = new Pattern[0]; - + protected boolean matches(String pattern, int patternIndex) { Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern); return matcher.matches(); } - + // NameMatchMethodPointcut 的实现源码 - private List mappedNames = new LinkedList(); - + private List mappedNames = new LinkedList(); + public boolean matches(Method method, Class targetClass) { for (String mappedName : this.mappedNames) { if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) { @@ -45,21 +49,23 @@ public interface AfterReturningAdvice extends AfterAdvice { return false; } ``` + ### 1.3 Advisor 通知器 + 将 Pointcut 和 Advice 有效地结合在一起。它定义了在哪些方法(Pointcut)上执行哪些动作(Advice)。下面看一下 DefaultPointcutAdvisor 的源码实现,它通过持有 Pointcut 和 Advice 属性来将两者有效地结合在一起。 ```java public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable { private Pointcut pointcut = Pointcut.TRUE; - + public DefaultPointcutAdvisor() { } - + public DefaultPointcutAdvisor(Advice advice) { this(Pointcut.TRUE, advice); } - + /** * 自己定义了 Pointcut属性,而 Advice属性 则使用父类中的定义 */ @@ -73,25 +79,28 @@ public abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdv //本类是一个抽象类,其持有 Advice 的引用,而对 Pointcut 的引用,则在具体的子类中持有 private Advice advice; - + public void setAdvice(Advice advice) { this.advice = advice; } - + public Advice getAdvice() { return this.advice; } - + @Override public String toString() { return getClass().getName() + ": advice [" + getAdvice() + "]"; } } ``` + ## 2 Spring AOP 的设计与实现 + AOP 的实现代码中,主要使用了 JDK 动态代理,在特定场景下(被代理对象没有 implements 的接口)也用到了 CGLIB 生成代理对象。通过 AOP 的源码设计可以看到,其先为目标对象建立了代理对象,这个代理对象的生成可以使用 JDK 动态代理或 CGLIB 完成。然后启动为代理对象配置的拦截器,对横切面(目标方法集合)进行相应的增强,将 AOP 的横切面设计和 Proxy 模式有机地结合起来,实现了在 AOP 中定义好的各种织入方式。 ### 2.1 ProxyFactoryBean + 这里我们主要以 ProxyFactoryBean 的实现为例,对 AOP 的实现原理进行分析。ProxyFactoryBean 主要持有目标对象 target 的代理对象 aopProxy,和 Advisor 通知器,而 Advisor 持有 Advice 和 Pointcut,这样就可以判断 aopProxy 中的方法 是否是某个指定的切面 Pointcut,然后根据其配置的织入方向(前置增强/后置增强),通过反射为其织入相应的增强行为 Advice。先看一下 ProxyFactoryBean 的配置和使用。 ```xml @@ -111,7 +120,9 @@ AOP 的实现代码中,主要使用了 JDK 动态代理,在特定场景下 ``` + ### 2.2 为配置的 target 生成 AopProxy 代理对象 + ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化,然后根据被代理对象类型的不同,生成代理对象。 ```java @@ -135,6 +146,7 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化, } } ``` + ### 2.3 初始化 Advisor 链 ```java @@ -146,18 +158,18 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化, if (this.advisorChainInitialized) { return; } - + if (!ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); } - + if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) && this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { throw new AopConfigException("Target required after globals"); } - + // 这里添加了 Advisor 链的调用,下面的 interceptorNames 是在配置文件中 // 通过 interceptorNames 进行配置的。由于每一个 Advisor 都是被配置为 bean 的, // 所以通过遍历 interceptorNames 得到的 name,其实就是 bean 的 id,通过这个 name(id) @@ -193,9 +205,11 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化, this.advisorChainInitialized = true; } ``` + 生成 singleton 的代理对象在 getSingletonInstance 方法中完成,这是 ProxyFactoryBean 生成 AopProxy 代理对象的调用入口。代理对象会封装对 target 对象的调用,针对 target 对象的方法调用会被这里生成的代理对象所拦截。 ### 2.4 生成单例代理对象 + ```java /** * 返回此类代理对象的单例实例,如果尚未创建该实例,则单例地创建它 @@ -218,7 +232,7 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化, } return this.singletonInstance; } - + /** * 通过 createAopProxy()方法 返回的 aopProxy 获取代理对象 */ @@ -233,13 +247,13 @@ ProxyFactoryBean 的 getObject() 方法先对通知器链进行了初始化, public class ProxyCreatorSupport extends AdvisedSupport { private AopProxyFactory aopProxyFactory; - + public ProxyCreatorSupport() { // 注意这里实例化的是一个 DefaultAopProxyFactory,所以下面的 createAopProxy() 方法 // 中调用的也是 DefaultAopProxyFactory 的实现 this.aopProxyFactory = new DefaultAopProxyFactory(); } - + protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); @@ -247,11 +261,7 @@ public class ProxyCreatorSupport extends AdvisedSupport { //调用的是 DefaultAopProxyFactory 的实现 return getAopProxyFactory().createAopProxy(this); } - - public AopProxyFactory getAopProxyFactory() { - return this.aopProxyFactory; - } - + public AopProxyFactory getAopProxyFactory() { return this.aopProxyFactory; } @@ -271,7 +281,7 @@ public class ProxyCreatorSupport extends AdvisedSupport { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } - + /** * !!!!!!!!!!!!!!!!!!!!!!!!!! * 如果目标类是接口,则使用 JDK 动态代理,否则使用 CGLIB @@ -288,9 +298,10 @@ public class ProxyCreatorSupport extends AdvisedSupport { } ``` -可以看到其根据目标对象是否实现了接口,而决定是使用 JDK动态代理 还是 CGLIB 去生成代理对象,而 AopProxy 接口的实现类也只有 JdkDynamicAopProxy 和 CglibAopProxy 这两个。 +可以看到其根据目标对象是否为接口,而决定是使用 JDK 动态代理 还是 CGLIB 去生成代理对象,而 AopProxy 接口的实现类也只有 JdkDynamicAopProxy 和 CglibAopProxy 这两个。 + +### 2.5 JDK 动态代理 生成 AopProxy 代理对象 -### 2.5 JDK动态代理 生成 AopProxy代理对象 ```java /** * 可以看到,其实现了 InvocationHandler 接口,所以肯定也定义了一个 使用 java.lang.reflect.Proxy @@ -310,44 +321,45 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa // 这样,当 invoke()方法 被 代理对象aopProxy 调用时,就可以调用 target 的目标方法了 this.advised = config; } - + public Object getProxy() { return getProxy(ClassUtils.getDefaultClassLoader()); } - + public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource()); } - + // 获取代理类要实现的接口 Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); - + // 通过 java.lang.reflect.Proxy 生成代理对象并返回 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); } } ``` -通过 JdkDynamicAopProxy 的源码可以非常清楚地看到,其使用了 JDK动态代理 的方式生成了 代理对象。JdkDynamicAopProxy 实现了 InvocationHandler 接口,并通过 java.lang.reflect.Proxy 的 newProxyInstance()静态方法 生成代理对象并返回。 +通过 JdkDynamicAopProxy 的源码可以非常清楚地看到,其使用了 JDK 动态代理 的方式生成了 代理对象。JdkDynamicAopProxy 实现了 InvocationHandler 接口,并通过 java.lang.reflect.Proxy 的 newProxyInstance()静态方法 生成代理对象并返回。 + +### 2.6 CGLIB 生成 AopProxy 代理对象 -### 2.6 CGLIB 生成 AopProxy代理对象 ```java final class CglibAopProxy implements AopProxy, Serializable { /** AdvisedSupport 持有一个 List属性 */ protected final AdvisedSupport advised; - + public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource()); } - + try { Class rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); - + Class proxySuperClass = rootClass; if (ClassUtils.isCglibProxyClass(rootClass)) { proxySuperClass = rootClass.getSuperclass(); @@ -356,9 +368,9 @@ final class CglibAopProxy implements AopProxy, Serializable { this.advised.addInterface(additionalInterface); } } - + validateClassIfNecessary(proxySuperClass); - + // 创建并配置 Enhancer对象,Enhancer 是 CGLIB 中主要的操作类 Enhancer enhancer = createEnhancer(); if (classLoader != null) { @@ -372,18 +384,18 @@ final class CglibAopProxy implements AopProxy, Serializable { enhancer.setStrategy(new MemorySafeUndeclaredThrowableStrategy(UndeclaredThrowableException.class)); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setInterceptDuringConstruction(false); - + Callback[] callbacks = getCallbacks(rootClass); enhancer.setCallbacks(callbacks); enhancer.setCallbackFilter(new ProxyCallbackFilter( this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset)); - + Class[] types = new Class[callbacks.length]; for (int x = 0; x < types.length; x++) { types[x] = callbacks[x].getClass(); } enhancer.setCallbackTypes(types); - + // 通过 enhancer 生成代理对象 Object proxy; if (this.constructorArgs != null) { @@ -392,7 +404,7 @@ final class CglibAopProxy implements AopProxy, Serializable { else { proxy = enhancer.create(); } - + return proxy; } catch (CodeGenerationException ex) { @@ -413,13 +425,17 @@ final class CglibAopProxy implements AopProxy, Serializable { } } ``` -为 目标对象target 生成 代理对象 之后,在调用 代理对象 的目标方法时,目标方法会进行 invoke()回调(JDK动态代理) 或 callbacks()回调(CGLIB),然后就可以在回调方法中对目标对象的目标方法进行拦截和增强处理了。 + +为 目标对象 target 生成 代理对象 之后,在调用 代理对象 的目标方法时,目标方法会进行 invoke()回调(JDK 动态代理) 或 callbacks()回调(CGLIB),然后就可以在回调方法中对目标对象的目标方法进行拦截和增强处理了。 ## 3 Spring AOP 拦截器调用的实现 -在 Spring AOP 通过 JDK 的 Proxy类 生成代理对象时,相关的拦截器已经配置到了代理对象持有的 InvocationHandler(即,ProxyBeanFactory) 的 invoke() 方法中,拦截器最后起作用,是通过调用代理对象的目标方法时,在代理类中触发了 InvocationHandler 的 invoke() 回调。通过 CGLIB 实现的 AOP,原理与此相似。 + +在 Spring AOP 通过 JDK 的 Proxy 类 生成代理对象时,相关的拦截器已经配置到了代理对象持有的 InvocationHandler(即,ProxyBeanFactory) 的 invoke() 方法中,拦截器最后起作用,是通过调用代理对象的目标方法时,在代理类中触发了 InvocationHandler 的 invoke() 回调。通过 CGLIB 实现的 AOP,原理与此相似。 ### 3.1 JdkDynamicAopProxy 的 invoke() 拦截 + 前面已经通过两种不同的方式生成了 AopProxy 代理对象,下面我们先看一下 JdkDynamicAopProxy 中的 invoke()回调方法 中对拦截器调用的实现。 + ```java final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable { @@ -427,12 +443,12 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa MethodInvocation invocation; Object oldProxy = null; boolean setProxyContext = false; - + //通过 targetSource 可以获取被代理对象 TargetSource targetSource = this.advised.targetSource; Class targetClass = null; Object target = null; - + try { // 如果目标对象调用的是 Obejct类 中的基本方法,如:equals()、hashCode() 则进行相应的处理 if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { @@ -448,24 +464,24 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa // 使用代理配置对 ProxyConfig 进行服务调用 return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } - + Object retVal; - + if (this.advised.exposeProxy) { // 如果有必要,可以援引 oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } - + // 获取目标对象,为目标方法的调用做准备 target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } - + // 获取定义好的拦截器链,即 Advisor列表 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); - + // 如果没有配置拦截器,就直接通过反射调用目标对象 target 的 method对象,并获取返回值 if (chain.isEmpty()) { retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); @@ -477,7 +493,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa // 沿着拦截器链继续向下处理 retVal = invocation.proceed(); } - + // 获取 method 返回值的类型 Class returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType.isInstance(proxy) && @@ -503,7 +519,9 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa } } ``` + ### 3.2 CglibAopProxy 的 intercept() 拦截 + CglibAopProxy 的 intercept() 回调方法实现和 JdkDynamicAopProxy 的 invoke() 非常相似,只是在 CglibAopProxy 中构造 CglibMethodInvocation 对象来完成拦截器链的调用,而在 JdkDynamicAopProxy 中则是通过构造 ReflectiveMethodInvocation 对象来完成的。 ```java @@ -552,7 +570,9 @@ final class CglibAopProxy implements AopProxy, Serializable { } } ``` + ### 3.3 目标对象中目标方法的调用 + 对目标对象中目标方法的调用,是在 AopUtils 工具类中利用反射机制完成的,具体代码如下。 ```java @@ -563,7 +583,7 @@ public abstract class AopUtils { */ public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args) throws Throwable { - + try { // 如果该 method 是 private的,则将其访问权限设为 public的 ReflectionUtils.makeAccessible(method); @@ -583,31 +603,33 @@ public abstract class AopUtils { } } ``` + ### 3.4 AOP 拦截器链的调用 + JdkDynamicAopProxy 和 CglibAopProxy 虽然使用了不同的代理对象,但对 AOP 拦截的处理却是相同的,都是通过 ReflectiveMethodInvocation 的 proceed() 方法实现的。 ```java public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable { protected final Object proxy; - + protected final Object target; - + protected final Method method; - + protected Object[] arguments; - + private final Class targetClass; - + /** MethodInterceptor和InterceptorAndDynamicMethodMatcher的集合 */ protected final List interceptorsAndDynamicMethodMatchers; - + private int currentInterceptorIndex = -1; - + protected ReflectiveMethodInvocation(Object proxy, Object target, Method method, Object[] arguments, Class targetClass, List interceptorsAndDynamicMethodMatchers) { - + this.proxy = proxy; this.target = target; this.targetClass = targetClass; @@ -615,7 +637,7 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea this.arguments = arguments; this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers; } - + public Object proceed() throws Throwable { // 从拦截器链中按顺序依次调用拦截器,直到所有的拦截器调用完毕,开始调用目标方法,对目标方法的调用 // 是在 invokeJoinpoint() 中通过 AopUtils 的 invokeJoinpointUsingReflection() 方法完成的 @@ -623,7 +645,7 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea // invokeJoinpoint() 直接通过 AopUtils 进行目标方法的调用 return invokeJoinpoint(); } - + // 这里沿着定义好的 interceptorsAndDynamicMethodMatchers拦截器链 进行处理, // 它是一个 List,也没有定义泛型,interceptorOrInterceptionAdvice 是其中的一个元素 Object interceptorOrInterceptionAdvice = @@ -651,20 +673,23 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea } } ``` + ### 3.5 配置通知器 + AdvisedSupport 中实现了获取拦截器链的方法,并使用了缓存。 + ```java public class AdvisedSupport extends ProxyConfig implements Advised { /** TargetSource持有一个比较重要的属性,targetClass */ TargetSource targetSource = EMPTY_TARGET_SOURCE; - + /** 缓存 Method对象 和其对应的 拦截器链列表List */ private transient Map> methodCache; - + /** The AdvisorChainFactory to use */ AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory(); - + /** * 获取拦截器链,为提高效率,同时设置了缓存 */ @@ -692,13 +717,13 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ public List getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class targetClass) { - + // Advisor链 已经在传进来的 config 中持有了,这里可以直接使用。 // Advisor 中持有 切面Pointcut 和 增强行为Advice 两个重要属性 List interceptorList = new ArrayList(config.getAdvisors().length); // 判断 config 中的 Advisors 是否符合配置要求 boolean hasIntroductions = hasMatchingIntroductions(config, targetClass); - + // 获取注册器,这是一个单例模式的实现 AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); for (Advisor advisor : config.getAdvisors()) { @@ -739,7 +764,7 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ } return interceptorList; } - + /** * 判断 config 中的 Advisors 是否符合配置要求 */ @@ -783,7 +808,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport return newPrototypeInstance(); } } - + /** * 初始化 Advisor链,可以发现,其中有通过对 IoC容器 的 getBean() 方法的调用来获取配置好的 advisor 通知器 */ @@ -792,18 +817,18 @@ public class ProxyFactoryBean extends ProxyCreatorSupport if (this.advisorChainInitialized) { return; } - + if (!ObjectUtils.isEmpty(this.interceptorNames)) { if (this.beanFactory == null) { throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " + "- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames)); } - + if (this.interceptorNames[this.interceptorNames.length - 1].endsWith(GLOBAL_SUFFIX) && this.targetName == null && this.targetSource == EMPTY_TARGET_SOURCE) { throw new AopConfigException("Target required after globals"); } - + // 这里添加了 Advisor链 的调用,下面的 interceptorNames 是在配置文件中 // 通过 interceptorNames 进行配置的。由于每一个 Advisor 都是被配置为 bean 的, // 所以通过遍历 interceptorNames 得到的 name,其实就是 bean(Advisor) 的 id,通过这个 name(id) @@ -812,7 +837,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport if (logger.isTraceEnabled()) { logger.trace("Configuring advisor or advice '" + name + "'"); } - + if (name.endsWith(GLOBAL_SUFFIX)) { if (!(this.beanFactory instanceof ListableBeanFactory)) { throw new AopConfigException( @@ -821,7 +846,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport addGlobalAdvisor((ListableBeanFactory) this.beanFactory, name.substring(0, name.length() - GLOBAL_SUFFIX.length())); } - + else { // 对当前的 factoryBean 进行类型判断,是属于 单例bean,还是 原型bean Object advice; @@ -845,7 +870,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport } ``` -注意,Advisor 本身就被配置为 bean,所以它的获取也是通过 IoC容器 获得的。 +注意,Advisor 本身就被配置为 bean,所以它的获取也是通过 IoC 容器 获得的。 ### 3.6 Advice 通知的实现 @@ -856,13 +881,13 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ public List getInterceptorsAndDynamicInterceptionAdvice( Advised config, Method method, Class targetClass) { - + // Advisor链 已经在传进来的 config 中持有了,这里可以直接使用 // Advisor 中持有 切面Pointcut 和 增强行为Advice 的引用 List interceptorList = new ArrayList(config.getAdvisors().length); // 判断 config 中的 Advisors 是否符合配置要求 boolean hasIntroductions = hasMatchingIntroductions(config, targetClass); - + // 获取注册器,这是一个单例模式的实现 AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance(); for (Advisor advisor : config.getAdvisors()) { @@ -914,7 +939,7 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se // 持有 AdvisorAdapter 的 list,这个 list 中的 AdvisorAdapter 与 // 实现 Spring AOP 的 advice 增强功能相对应 private final List adapters = new ArrayList(3); - + /** * 将已实现的 AdviceAdapter 加入 list */ @@ -923,7 +948,7 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se registerAdvisorAdapter(new AfterReturningAdviceAdapter()); registerAdvisorAdapter(new ThrowsAdviceAdapter()); } - + /** * 如果 adviceObject 是 Advisor 的实例,则将 adviceObject 转换成 Advisor 类型并返回 */ @@ -945,18 +970,18 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se } throw new UnknownAdviceTypeException(advice); } - + public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException { List interceptors = new ArrayList(3); - + // 从 Advisor 通知器中获取配置的 Advice Advice advice = advisor.getAdvice(); - + // 如果 advice 是 MethodInterceptor 类型的,直接加进 interceptors,不用适配 if (advice instanceof MethodInterceptor) { interceptors.add((MethodInterceptor) advice); } - + // 对通知进行适配,使用已经配置好的三种 AdvisorAdapter,然后从对应的 // adapter 中取出封装好的 AOP 编织功能的拦截器 for (AdvisorAdapter adapter : this.adapters) { @@ -981,7 +1006,7 @@ class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { public boolean supportsAdvice(Advice advice) { return (advice instanceof MethodBeforeAdvice); } - + public MethodInterceptor getInterceptor(Advisor advisor) { MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); return new MethodBeforeAdviceInterceptor(advice); @@ -989,13 +1014,13 @@ class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { } ``` -可以看到,其中的 getInterceptor()方法 把 Advice 从 Advisor 中取出来,然后创建了一个 MethodBeforeAdviceInterceptor对象,并返回,这个对象中持有对 Advice 的引用。下面我们看一下 MethodBeforeAdviceInterceptor 拦截器的源码实现。 +可以看到,其中的 getInterceptor()方法 把 Advice 从 Advisor 中取出来,然后创建了一个 MethodBeforeAdviceInterceptor 对象,并返回,这个对象中持有对 Advice 的引用。下面我们看一下 MethodBeforeAdviceInterceptor 拦截器的源码实现。 ```java public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Serializable { private MethodBeforeAdvice advice; - + /** * 为指定的 advice 创建对应的 MethodBeforeAdviceInterceptor 对象 */ @@ -1003,7 +1028,7 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Seriali Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } - + /** * 这个 invoke()方法 是拦截器的回调方法,会在代理对象的方法被调用时触发回调 */ @@ -1018,7 +1043,7 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, Seriali 可以看到,MethodBeforeAdviceInterceptor 的 invoke()方法 先是触发了 advice 的 before()方法,然后才是 MethodInvocation 的 proceed()方法调用。 -回顾一下之前的代码,在 AopProxy代理对象 触发的 ReflectiveMethodInvocation 的 proceed() 中,在取得 拦截器interceptor 后调用了其 invoke()方法。按照 AOP 的配置规则,ReflectiveMethodInvocation 触发的拦截器 invoke()回调,最终会根据 Advice 类型的不同,触发 Spring 对不同的 Advice 的拦截器封装,比如 MethodBeforeAdvice 最终会触发 MethodBeforeAdviceInterceptor 的 invoke()回调,其它两个以此类推,这里就不逐一分析咯。 +回顾一下之前的代码,在 AopProxy 代理对象 触发的 ReflectiveMethodInvocation 的 proceed() 中,在取得 拦截器 interceptor 后调用了其 invoke()方法。按照 AOP 的配置规则,ReflectiveMethodInvocation 触发的拦截器 invoke()回调,最终会根据 Advice 类型的不同,触发 Spring 对不同的 Advice 的拦截器封装,比如 MethodBeforeAdvice 最终会触发 MethodBeforeAdviceInterceptor 的 invoke()回调,其它两个以此类推,这里就不逐一分析咯。 -另外,可以结合我 GitHub 上对 Spring框架源码 的阅读及个人理解一起看,会更有助于各位开发大佬理解,如果本内容对你们有帮助的,还望各位同学 watch,star,fork,素质三连一波,地址: -https://github.com/AmyliaY/spring-aop-reading \ No newline at end of file +另外,可以结合我 GitHub 上对 Spring 框架源码 的阅读及个人理解一起看,会更有助于各位开发大佬理解,如果本内容对你们有帮助的,还望各位同学 watch,star,fork,素质三连一波,地址: +https://github.com/AmyliaY/spring-aop-reading diff --git "a/docs/Spring/AOP/JDK\345\212\250\346\200\201\344\273\243\347\220\206\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206\350\247\243\346\236\220.md" "b/docs/Spring/AOP/JDK\345\212\250\346\200\201\344\273\243\347\220\206\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206\350\247\243\346\236\220.md" index ade40611..ff34e538 100644 --- "a/docs/Spring/AOP/JDK\345\212\250\346\200\201\344\273\243\347\220\206\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206\350\247\243\346\236\220.md" +++ "b/docs/Spring/AOP/JDK\345\212\250\346\200\201\344\273\243\347\220\206\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206\350\247\243\346\236\220.md" @@ -1,4 +1,5 @@ -最近在看 Spring AOP 部分的源码,所以对JDK动态代理具体是如何实现的这件事产生了很高的兴趣,而且能从源码上了解这个原理的话,也有助于对 spring-aop 模块的理解。话不多说,上代码。 +最近在看 Spring AOP 部分的源码,所以对 JDK 动态代理具体是如何实现的这件事产生了很高的兴趣,而且能从源码上了解这个原理的话,也有助于对 spring-aop 模块的理解。话不多说,上代码。 + ```java /** * 一般会使用实现了 InvocationHandler接口 的类作为代理对象的生产工厂, @@ -6,20 +7,20 @@ * 这些我们都能通过下面这段代码看懂,但代理对象是如何生成的?invoke()方法 又是如何被调用的呢? */ public class ProxyFactory implements InvocationHandler { - + private Object target = null; - + public Object getInstanse(Object target){ - + this.target = target; - return Proxy.newProxyInstance(target.getClass().getClassLoader(), + return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } - + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - + Object ret = null; System.out.println("前置增强"); ret = method.invoke(target, args); @@ -63,7 +64,7 @@ public class ProxyTest { // 总的来说,就是在 invoke()方法 中完成 target目标方法 的调用,及前置后置增强, // JDK 动态生成的代理类中对 invoke()方法 进行了回调 } - + /** * 将 ProxyGenerator 生成的动态代理类的输出到文件中,利用反编译工具 luyten 等就可 * 以看到生成的代理类的源码咯,下面给出了其反编译好的代码实现 @@ -99,7 +100,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { private static Method m0; private static Method m3; private static Method m2; - + static { try { $Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); @@ -115,11 +116,11 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new NoClassDefFoundError(ex2.getMessage()); } } - + public $Proxy0(final InvocationHandler invocationHandler) { super(invocationHandler); } - + public final void play() { try { // 这个 h 其实就是我们调用 Proxy.newProxyInstance()方法 时传进去的 ProxyFactory对象(它实现了 @@ -134,7 +135,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new UndeclaredThrowableException(t); } } - + public final boolean equals(final Object o) { try { return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o }); @@ -146,7 +147,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new UndeclaredThrowableException(t); } } - + public final int hashCode() { try { return (int)super.h.invoke(this, $Proxy0.m0, null); @@ -158,7 +159,7 @@ public final class $Proxy0 extends Proxy implements MyInterface { throw new UndeclaredThrowableException(t); } } - + public final String toString() { try { return (String)super.h.invoke(this, $Proxy0.m2, null); @@ -171,4 +172,4 @@ public final class $Proxy0 extends Proxy implements MyInterface { } } } -``` \ No newline at end of file +``` diff --git "a/docs/Spring/AOP/Spring-Aop\345\246\202\344\275\225\347\224\237\346\225\210.md" "b/docs/Spring/AOP/Spring-Aop\345\246\202\344\275\225\347\224\237\346\225\210.md" index 5e5e9252..adb8b768 100644 --- "a/docs/Spring/AOP/Spring-Aop\345\246\202\344\275\225\347\224\237\346\225\210.md" +++ "b/docs/Spring/AOP/Spring-Aop\345\246\202\344\275\225\347\224\237\346\225\210.md" @@ -1,15 +1,19 @@ # Spring AOP 如何生效 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) ## 解析 -- 在使用 Spring AOP 技术的时候会有下面这段代码在xml配置文件中出现,来达到 Spring 支持 AOP + +- 在使用 Spring AOP 技术的时候会有下面这段代码在 xml 配置文件中出现,来达到 Spring 支持 AOP + ```xml ``` + - 源码阅读目标找到了,那么怎么去找入口或者对这句话的标签解析方法呢?项目中使用搜索 - ![image-20200115083744268](../../../images/spring/image-20200115083744268.png) + ![image-20200115083744268](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115083744268.png) 这样就找到了具体解析方法了 @@ -17,7 +21,8 @@ - 类图 -![image-20200115084031725](../../../images/spring/image-20200115084031725.png) +![image-20200115084031725](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115084031725.png) + ```java @Override @Nullable @@ -50,7 +55,9 @@ } ``` + - `org.springframework.aop.config.AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)` + ```java @Nullable public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( @@ -61,7 +68,9 @@ } ``` + - `org.springframework.aop.config.AopConfigUtils.registerOrEscalateApcAsRequired` + ```java /** * 注册或者升级 bean @@ -103,7 +112,9 @@ } ``` + ### org.springframework.aop.config.AopNamespaceUtils.useClassProxyingIfNecessary + ```java /** * proxy-target-class 和 expose-proxy 标签处理 @@ -124,7 +135,9 @@ } ``` + - `org.springframework.aop.config.AopConfigUtils.forceAutoProxyCreatorToUseClassProxying` + ```java public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry) { if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { @@ -134,8 +147,9 @@ } ``` -- `forceAutoProxyCreatorToExposeProxy`方法就不贴出代码了,操作和`forceAutoProxyCreatorToUseClassProxying`一样都是将读取到的数据放入bean对象作为一个属性存储 +- `forceAutoProxyCreatorToExposeProxy`方法就不贴出代码了,操作和`forceAutoProxyCreatorToUseClassProxying`一样都是将读取到的数据放入 bean 对象作为一个属性存储 ## 总结 -- 实现`org.springframework.beans.factory.xml.BeanDefinitionParser`接口的类,多用于对xml标签的解析,并且入口为`parse`方法,如果是一个bean对象通常会和Spring监听器一起出现 \ No newline at end of file + +- 实现`org.springframework.beans.factory.xml.BeanDefinitionParser`接口的类,多用于对 xml 标签的解析,并且入口为`parse`方法,如果是一个 bean 对象通常会和 Spring 监听器一起出现 diff --git "a/docs/Spring/IoC/1\343\200\201BeanDefinition\347\232\204\350\265\204\346\272\220\345\256\232\344\275\215\350\277\207\347\250\213.md" "b/docs/Spring/IoC/1\343\200\201BeanDefinition\347\232\204\350\265\204\346\272\220\345\256\232\344\275\215\350\277\207\347\250\213.md" index 74b3a2d4..27ca8bbe 100644 --- "a/docs/Spring/IoC/1\343\200\201BeanDefinition\347\232\204\350\265\204\346\272\220\345\256\232\344\275\215\350\277\207\347\250\213.md" +++ "b/docs/Spring/IoC/1\343\200\201BeanDefinition\347\232\204\350\265\204\346\272\220\345\256\232\344\275\215\350\277\207\347\250\213.md" @@ -1,19 +1,22 @@ ## 前言 + 之前一直想系统的拜读一下 spring 的源码,看看它到底是如何吸引身边的大神们对它的设计赞不绝口,虽然每天工作很忙,每天下班后总感觉脑子内存溢出,想去放松一下,但总是以此为借口,恐怕会一直拖下去。所以每天下班虽然有些疲惫,但还是按住自己啃下这块硬骨头。 -spring 源码这种东西真的是一回生二回熟,第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向,不知道看到的这些方法调用的是哪个父类的实现(IoC相关的类图实在太复杂咯,继承体系又深又广),但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。 +spring 源码这种东西真的是一回生二回熟,第一遍会被各种设计模式和繁杂的方法调用搞得晕头转向,不知道看到的这些方法调用的是哪个父类的实现(IoC 相关的类图实在太复杂咯,继承体系又深又广),但当你耐下心来多走几遍,会发现越看越熟练,每次都能 get 到新的点。 另外,对于第一次看 spring 源码的同学,建议先在 B 站上搜索相关视频看一下,然后再结合计文柯老师的《spring 技术内幕》深入理解,最后再输出自己的理解(写博文或部门内部授课)加强印象。 首先对于我们新手来说,还是从我们最常用的两个 IoC 容器开始分析,这次我们先分析 FileSystemXmlApplicationContext 这个 IoC 容器的具体实现,ClassPathXmlApplicationContext 留着下次讲解。 -(PS:可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学的理解。地址: -spring-beans https://github.com/AmyliaY/spring-beans-reading -spring-context https://github.com/AmyliaY/spring-context-reading +(PS:可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学的理解。地址: +spring-beans https://github.com/AmyliaY/spring-beans-reading +spring-context https://github.com/AmyliaY/spring-context-reading ) ## 正文 + 当我们传入一个 Spring 配置文件去实例化 FileSystemXmlApplicationContext 时,可以看一下它的构造方法都做了什么。 + ```java /** * 下面这 4 个构造方法都调用了第 5 个构造方法 @@ -48,7 +51,7 @@ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh // 动态地确定用哪个加载器去加载我们的配置文件 super(parent); - // 告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableApplicationContext + // 告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableConfigApplicationContext setConfigLocations(configLocations); if (refresh) { // 容器初始化 @@ -69,15 +72,17 @@ protected Resource getResourceByPath(String path) { return new FileSystemResource(path); } ``` + 看看其父类 AbstractApplicationContext 实现的 refresh() 方法,该方法就是 IoC 容器初始化的入口类 + ```java /** * 容器初始化的过程:BeanDefinition 的 Resource 定位、BeanDefinition 的载入、BeanDefinition 的注册。 - * BeanDefinition 的载入和 bean 的依赖注入是两个独立的过程,依赖注入一般发生在 应用第一次通过 + * BeanDefinition 的载入和 bean 的依赖注入是两个独立的过程,依赖注入一般发生在 应用第一次通过 * getBean() 方法从容器获取 bean 时。 - * + * * 另外需要注意的是,IoC 容器有一个预实例化的配置(即,将 AbstractBeanDefinition 中的 lazyInit 属性 - * 设为 true),使用户可以对容器的初始化过程做一个微小的调控,lazyInit 设为 false 的 bean + * 设为 true),使用户可以对容器的初始化过程做一个微小的调控,lazyInit 设为 false 的 bean * 将在容器初始化时进行依赖注入,而不会等到 getBean() 方法调用时才进行 */ public void refresh() throws BeansException, IllegalStateException { @@ -98,8 +103,8 @@ public void refresh() throws BeansException, IllegalStateException { // 调用所有注册的 BeanFactoryPostProcessor 的 Bean invokeBeanFactoryPostProcessors(beanFactory); - // 为 BeanFactory 注册 BeanPost 事件处理器. - // BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件 + // 为 BeanFactory 注册 BeanPost 事件处理器. + // BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件 registerBeanPostProcessors(beanFactory); // 初始化信息源,和国际化相关. @@ -133,7 +138,9 @@ public void refresh() throws BeansException, IllegalStateException { } } ``` + 看看 obtainFreshBeanFactory() 方法,该方法告诉了子类去刷新内部的 beanFactory + ```java /** * Tell the subclass to refresh the internal bean factory. @@ -152,7 +159,9 @@ protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { return beanFactory; } ``` + 下面看一下 AbstractRefreshableApplicationContext 中对 refreshBeanFactory() 方法的实现。FileSystemXmlApplicationContext 从上层体系的各抽象类中继承了大量的方法实现,抽象类中抽取大量公共行为进行具体实现,留下 abstract 的个性化方法交给具体的子类实现,这是一个很好的 OOP 编程设计,我们在自己编码时也可以尝试这样设计自己的类图。理清 FileSystemXmlApplicationContext 的上层体系设计,就不易被各种设计模式搞晕咯。 + ```java /** * 在这里完成了容器的初始化,并赋值给自己私有的 beanFactory 属性,为下一步调用做准备 @@ -183,7 +192,9 @@ protected final void refreshBeanFactory() throws BeansException { } } ``` + AbstractXmlApplicationContext 中对 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的实现。 + ```java /* * 实现了基类 AbstractRefreshableApplicationContext 的抽象方法 @@ -200,7 +211,7 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw // 继承了 DefaultResourceLoader,因此,本容器自身也是一个资源加载器 beanDefinitionReader.setResourceLoader(this); // 设置 SAX 解析器,SAX(simple API for XML)是另一种 XML 解析方法。相比于 DOM,SAX 速度更快,占用内存更小。 - // 它逐行扫描文档,一边扫描一边解析。相比于先将整个 XML 文件扫描近内存,再进行解析的 DOM,SAX 可以在解析文档的 + // 它逐行扫描文档,一边扫描一边解析。相比于先将整个 XML 文件扫描进内存,再进行解析的 DOM,SAX 可以在解析文档的 // 任意时刻停止解析,但操作也比 DOM 复杂。 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); @@ -210,13 +221,15 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw loadBeanDefinitions(beanDefinitionReader); } ``` + 继续看 AbstractXmlApplicationContext 中 loadBeanDefinitions() 的重载方法。 + ```java /** * 用传进来的 XmlBeanDefinitionReader 读取器加载 Xml 文件中的 BeanDefinition */ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { - + /** * ClassPathXmlApplicationContext 与 FileSystemXmlApplicationContext 在这里的调用出现分歧, * 各自按不同的方式加载解析 Resource 资源,最后在具体的解析和 BeanDefinition 定位上又会殊途同归。 @@ -239,14 +252,16 @@ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansE } } ``` + AbstractBeanDefinitionReader 中对 loadBeanDefinitions 方法的各种重载及调用。 + ```java /** * loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String) */ public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); - // 计数器,统计加载了多少个配置文件 + // 计数器,统计加载了多少个BeanDefinition int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); @@ -255,7 +270,7 @@ public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreEx } /** - * 重载方法之一,调用了下面的 loadBeanDefinitions(String, Set) 方法 + * 重载方法之一,调用了下面的 loadBeanDefinitions(String, Set) 方法 */ public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); @@ -274,10 +289,10 @@ public int loadBeanDefinitions(String location, Set actualResources) t if (resourceLoader instanceof ResourcePatternResolver) { try { - // 将指定位置的 BeanDefinition 资源文件解析为 IoC 容器封装的资源 - // 加载多个指定位置的 BeanDefinition 资源文件 + // 将指定位置的 BeanDefinition 资源文件解析为 IoC 容器封装的资源 + // 加载多个指定位置的 BeanDefinition 资源文件 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); - // 委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能 + // 委派调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能 int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { @@ -318,7 +333,9 @@ public int loadBeanDefinitions(String location, Set actualResources) t } } ``` + resourceLoader 的 getResource() 方法有多种实现,看清 FileSystemXmlApplicationContext 的继承体系就可以明确,其走的是 DefaultResourceLoader 中的实现。 + ```java /** * 获取 Resource 的具体实现方法 @@ -337,7 +354,7 @@ public Resource getResource(String location) { return new UrlResource(url); } catch (MalformedURLException ex) { - // 如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用容器本身的 + // 如果既不是 classpath 标识,又不是 URL 标识的 Resource 定位,则调用容器本身的 // getResourceByPath() 方法获取 Resource。根据实例化的子类对象,调用其子类对象中 // 重写的此方法,如 FileSystemXmlApplicationContext 子类中对此方法的重写 return getResourceByPath(location); @@ -345,7 +362,9 @@ public Resource getResource(String location) { } } ``` + 其中的 getResourceByPath(location) 方法的实现则是在 FileSystemXmlApplicationContext 中完成的。 + ```java /** * 实例化一个 FileSystemResource 并返回,以便后续对资源的 IO 操作 @@ -359,4 +378,5 @@ protected Resource getResourceByPath(String path) { return new FileSystemResource(path); } ``` + 至此,我们可以看到,FileSystemXmlApplicationContext 的 getResourceByPath() 方法返回了一个 FileSystemResource 对象,接下来 spring 就可以对这个对象进行相关的 I/O 操作,进行 BeanDefinition 的读取和载入了。 diff --git "a/docs/Spring/IoC/2\343\200\201\345\260\206bean\350\247\243\346\236\220\345\260\201\350\243\205\346\210\220BeanDefinition.md" "b/docs/Spring/IoC/2\343\200\201\345\260\206bean\350\247\243\346\236\220\345\260\201\350\243\205\346\210\220BeanDefinition.md" index f9786c87..48511af6 100644 --- "a/docs/Spring/IoC/2\343\200\201\345\260\206bean\350\247\243\346\236\220\345\260\201\350\243\205\346\210\220BeanDefinition.md" +++ "b/docs/Spring/IoC/2\343\200\201\345\260\206bean\350\247\243\346\236\220\345\260\201\350\243\205\346\210\220BeanDefinition.md" @@ -1,11 +1,13 @@ ## 前言 + 接着上一篇的 BeanDefinition 资源定位开始讲。Spring IoC 容器 BeanDefinition 解析过程就是把用户在配置文件中配置的 bean,解析并封装成 IoC 容器可以装载的 BeanDefinition 对象,BeanDefinition 是 Spring 定义的基本数据结构,其中的属性与配置文件中 bean 的属性相对应。 - + (PS:可以结合我 GitHub 上对 Spring 框架源码的阅读及个人理解一起看,会更有助于各位开发大佬理解。地址如下。 -spring-beans https://github.com/AmyliaY/spring-beans-reading -spring-context https://github.com/AmyliaY/spring-context-reading ) +spring-beans https://github.com/AmyliaY/spring-beans-reading +spring-context https://github.com/AmyliaY/spring-context-reading ) ## 正文 + 首先看一下 AbstractRefreshableApplicationContext 的 refreshBeanFactory() 方法,这是一个模板方法,其中调用的 loadBeanDefinitions() 方法是一个抽象方法,交由子类实现。 ```java @@ -38,7 +40,9 @@ protected final void refreshBeanFactory() throws BeansException { } } ``` + 下面看一下 AbstractRefreshableApplicationContext 的子类 AbstractXmlApplicationContext 对 loadBeanDefinitions() 方法的实现。 + ```java @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { @@ -62,7 +66,9 @@ protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throw loadBeanDefinitions(beanDefinitionReader); } ``` + 接着看一下上面最后一个调用的方法 loadBeanDefinitions(XmlBeanDefinitionReader reader)。 + ```java /** * 读取并解析 .xml 文件中配置的 bean,然后封装成 BeanDefinition 对象 @@ -83,7 +89,7 @@ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansE reader.loadBeanDefinitions(configResources); } // 调用其父类 AbstractRefreshableConfigApplicationContext 中的实现,优先返回 - // FileSystemXmlApplicationContext 构造方法中调用 setConfigLocations() 方法设置的资源路径 + // FileSystemXmlApplicationContext 构造方法中调用 setConfigLocations() 方法设置的资源路径 String[] configLocations = getConfigLocations(); if (configLocations != null) { // 这里调用其父类 AbstractBeanDefinitionReader 的方法从配置位置加载 BeanDefinition @@ -91,7 +97,9 @@ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansE } } ``` + AbstractBeanDefinitionReader 对 loadBeanDefinitions() 方法的三重重载。 + ```java /** * loadBeanDefinitions() 方法的重载方法之一,调用了另一个重载方法 loadBeanDefinitions(String location) @@ -129,7 +137,7 @@ public int loadBeanDefinitions(String location, Set actualResources) t // 将指定位置的 bean 配置文件解析为 BeanDefinition 对象 // 加载多个指定位置的 BeanDefinition 资源 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); - // 调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能 + // 调用其子类 XmlBeanDefinitionReader 的方法,实现加载功能 int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { @@ -170,7 +178,9 @@ public int loadBeanDefinitions(String location, Set actualResources) t } } ``` + XmlBeanDefinitionReader 读取器中的方法执行流,按代码的先后顺序。 + ```java /** * XmlBeanDefinitionReader 加载资源的入口方法 @@ -283,7 +293,9 @@ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanD return getRegistry().getBeanDefinitionCount() - countBefore; } ``` + 文档解析器 DefaultBeanDefinitionDocumentReader 对配置文件中元素的解析。 + ```java // 根据 Spring 对 Bean 的定义规则进行解析 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { @@ -311,12 +323,12 @@ protected void doRegisterBeanDefinitions(Element root) { } } - // 具体的解析过程由 BeanDefinitionParserDelegate 实现, + // 具体的解析过程由 BeanDefinitionParserDelegate 实现, // BeanDefinitionParserDelegate中定义了用于解析 bean 的各种属性及方法 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(this.readerContext, root, parent); - + // 前置解析处理,可以在解析 bean 之前进行自定义的解析,增强解析的可扩展性 preProcessXml(root); // 从 Document 的根元素开始进行 Bean 定义的 Document 对象 @@ -339,7 +351,7 @@ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate d Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; - // 如果 ele 定义的 Document 的元素节点使用的是 Spring 默认的 XML 命名空间 + // 如果 ele 定义的 Document 的元素节点使用的是 Spring 默认的 XML 命名空间 if (delegate.isDefaultNamespace(ele)) { // 使用 Spring 的 bean解析规则 解析元素节点 parseDefaultElement(ele, delegate); @@ -370,7 +382,7 @@ private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate deleg else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } - // 若元素节点既不是 也不是 ,即普通的 元素, + // 若元素节点既不是 也不是 ,即普通的 元素, // 则按照 Spring 的 bean解析规则 解析元素 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); @@ -413,7 +425,7 @@ protected void importBeanDefinitionResource(Element ele) { if (logger.isDebugEnabled()) { logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]"); } - } + } catch (BeanDefinitionStoreException ex) { getReaderContext().error( "Failed to import bean definitions from URL location [" + location + "]", ele, ex); @@ -433,7 +445,7 @@ protected void importBeanDefinitionResource(Element ele) { } // 封装的相对路径资源不存在 else { - // 获取 Spring IOC 容器资源读取器的基本路径 + // 获取 Spring IOC 容器资源读取器的基本路径 String baseLocation = getReaderContext().getResource().getURL().toString(); // 根据 Spring IoC 容器资源读取器的基本路径加载给定导入路径的资源 importCount = getReaderContext().getReader().loadBeanDefinitions( @@ -491,8 +503,9 @@ protected void processAliasRegistration(Element ele) { /** * 解析 bean 元素 + */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { - + // BeanDefinitionHolder 是对 BeanDefinition 的进一步封装,持有一个 BeanDefinition 对象 及其对应 // 的 beanName、aliases别名。对 元素的解析由 BeanDefinitionParserDelegate 实现 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); @@ -516,7 +529,9 @@ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate d } } ``` + 看一下 BeanDefinitionParserDelegate 中对 bean 元素的详细解析过程。 + ```java /** * 解析 元素的入口 @@ -531,13 +546,13 @@ public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) { // 获取 元素中的 id 属性值 String id = ele.getAttribute(ID_ATTRIBUTE); - + // 获取 元素中的 name 属性值 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); // 获取 元素中的 alias 属性值 List aliases = new ArrayList(); - + // 将 元素中的所有 name 属性值存放到别名中 if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); @@ -601,14 +616,14 @@ public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefiniti /** * 详细对 元素中的其他属性进行解析,上面的方法中已经对 bean 的 id、name 及 alias 属性进行了处理 - */ + */ public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) { // 记录解析的 this.parseState.push(new BeanEntry(beanName)); - // 这里只读取 元素中配置的 class 名字,然后载入到 BeanDefinition 中去 + // 这里只读取 元素中配置的 class 名字,然后载入到 BeanDefinition 中去 // 只是记录配置的 class 名字,不做实例化,对象的实例化在 getBean() 时发生 String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { @@ -621,8 +636,8 @@ public AbstractBeanDefinition parseBeanDefinitionElement( if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } - - // 根据 元素配置的 class 名称和 parent 属性值创建 BeanDefinition + + // 根据 元素配置的 class 名称和 parent 属性值创建 BeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent); // 对当前的 元素中配置的一些属性进行解析和设置,如配置的单例 (singleton) 属性等 @@ -666,7 +681,9 @@ public AbstractBeanDefinition parseBeanDefinitionElement( return null; } ``` + 对 bean 的部分子元素进行解析的具体实现。 + ```java /** * 解析 元素中所有的 子元素 @@ -695,7 +712,7 @@ public void parsePropertyElement(Element ele, BeanDefinition bd) { } this.parseState.push(new PropertyEntry(propertyName)); try { - // 如果一个 Bean 中已经有同名的 property 存在,则不进行解析,直接返回。 + // 如果一个 Bean 中已经有同名的 property 存在,则不进行解析,直接返回。 // 即如果在同一个 Bean 中配置同名的 property,则只有第一个起作用 if (bd.getPropertyValues().contains(propertyName)) { error("Multiple 'property' definitions for property '" + propertyName + "'", ele); @@ -743,7 +760,7 @@ public Object parsePropertyValue(Element ele, BeanDefinition bd, String property } } - // 判断 property 的属性值是 ref 还是 value,不允许既是 ref 又是 value + // 判断 property 的属性值是 ref 还是 value,不允许既是 ref 又是 value boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); if ((hasRefAttribute && hasValueAttribute) || @@ -809,27 +826,27 @@ public Object parsePropertySubElement(Element ele, BeanDefinition bd, String def } // 如果子元素是 ref,ref 中只能有以下 3 个属性:bean、local、parent else if (nodeNameEquals(ele, REF_ELEMENT)) { - // 获取 元素中的 bean 属性值,引用其他解析的 Bean 的名称 + // 获取 元素中的 bean 属性值,引用其他解析的 Bean 的名称 // 可以不再同一个 Spring 配置文件中,具体请参考 Spring 对 ref 的配置规则 String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); boolean toParent = false; if (!StringUtils.hasLength(refName)) { - // 获取 元素中的 local 属性值,引用同一个 Xml 文件中配置 + // 获取 元素中的 local 属性值,引用同一个 Xml 文件中配置 // 的 Bean 的 id,local 和 ref 不同,local 只能引用同一个配置文件中的 Bean refName = ele.getAttribute(LOCAL_REF_ATTRIBUTE); if (!StringUtils.hasLength(refName)) { // 获取 元素中 parent 属性值,引用父级容器中的 Bean refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); toParent = true; - + if (!StringUtils.hasLength(refName)) { error("'bean', 'local' or 'parent' is required for element", ele); return null; } } } - - // 没有配置 ref 的目标属性值 + + // 没有配置 ref 的目标属性值 if (!StringUtils.hasText(refName)) { error(" element contains empty target attribute", ele); return null; @@ -903,18 +920,19 @@ public List parseListElement(Element collectionEle, BeanDefinition bd) { /** * 具体解析 集合元素, 都使用该方法解析 */ -protected void parseCollectionElements(NodeList elementNodes, Collection target, +protected void parseCollectionElements(NodeList elementNodes, Collection target, BeanDefinition bd, String defaultElementType) { - + // 遍历集合所有节点 for (int i = 0; i < elementNodes.getLength(); i++) { Node node = elementNodes.item(i); // 节点不是 description 节点 if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) { - // 将解析的元素加入集合中,递归调用下一个子元素 + // 将解析的元素加入集合中,递归调用下一个子元素 target.add(parsePropertySubElement((Element) node, bd, defaultElementType)); } } } ``` -经过这样逐层地解析,我们在配置文件中定义的 bean 就被整个解析成了 IoC 容器能够装载和使用的 BeanDefinition对象,这种数据结构可以让 IoC 容器执行索引、查询等操作。经过上述解析,接下来我们就可以将得到的 BeanDefinition对象 注册到 IoC 容器中咯。 + +经过这样逐层地解析,我们在配置文件中定义的 bean 就被整个解析成了 IoC 容器能够装载和使用的 BeanDefinition 对象,这种数据结构可以让 IoC 容器执行索引、查询等操作。经过上述解析,接下来我们就可以将得到的 BeanDefinition 对象 注册到 IoC 容器中咯。 diff --git "a/docs/Spring/IoC/3\343\200\201\345\260\206BeanDefinition\346\263\250\345\206\214\350\277\233IoC\345\256\271\345\231\250.md" "b/docs/Spring/IoC/3\343\200\201\345\260\206BeanDefinition\346\263\250\345\206\214\350\277\233IoC\345\256\271\345\231\250.md" index 7c0759b5..38b4bb85 100644 --- "a/docs/Spring/IoC/3\343\200\201\345\260\206BeanDefinition\346\263\250\345\206\214\350\277\233IoC\345\256\271\345\231\250.md" +++ "b/docs/Spring/IoC/3\343\200\201\345\260\206BeanDefinition\346\263\250\345\206\214\350\277\233IoC\345\256\271\345\231\250.md" @@ -1,19 +1,22 @@ ## 前言 -这篇文章分享一下 spring IoC 容器初始化第三部分的代码,也就是将前面解析出来的 BeanDefinition对象 注册进 IoC 容器,其实就是存入一个 ConcurrentHashMap 中。 + +这篇文章分享一下 spring IoC 容器初始化第三部分的代码,也就是将前面解析出来的 BeanDefinition 对象 注册进 IoC 容器,其实就是存入一个 ConcurrentHashMap 中。 (PS:可以结合我 GitHub 上对 Spring 框架源码的翻译注释一起看,会更有助于各位同学理解,地址: -spring-beans https://github.com/AmyliaY/spring-beans-reading -spring-context https://github.com/AmyliaY/spring-context-reading +spring-beans https://github.com/AmyliaY/spring-beans-reading +spring-context https://github.com/AmyliaY/spring-context-reading ) ## 正文 + 回过头看一下前面在 DefaultBeanDefinitionDocumentReader 中实现的 processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) 方法。 + ```java /** * 将 .xml 文件中的元素解析成 BeanDefinition对象,并注册到 IoC容器 中 */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { - + // BeanDefinitionHolder 是对 BeanDefinition 的进一步封装,它持有一个 BeanDefinition 对象 及其对应 // 的 beanName、aliases别名。 // 对 Document 对象中 元素的解析由 BeanDefinitionParserDelegate 实现 @@ -38,7 +41,9 @@ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate d } } ``` + 接着看一下 BeanDefinitionReaderUtils 的 registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) 方法。 + ```java /** * 将解析到的 BeanDefinition对象 注册到 IoC容器 @@ -65,7 +70,9 @@ public static void registerBeanDefinition( } } ``` + BeanDefinitionRegistry 中的 registerBeanDefinition(String beanName, BeanDefinition beanDefinition) 方法在 DefaultListableBeanFactory 实现类中的具体实现。 + ```java public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { @@ -75,16 +82,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** IoC容器 的实际体现,key --> beanName,value --> BeanDefinition对象 */ private final Map beanDefinitionMap = new ConcurrentHashMap(64); - + /** * 向 IoC容器 注册解析的 beanName 和 BeanDefinition对象 */ public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException { - + Assert.hasText(beanName, "Bean name must not be empty"); Assert.notNull(beanDefinition, "BeanDefinition must not be null"); - + // 校验解析的 BeanDefiniton对象 if (beanDefinition instanceof AbstractBeanDefinition) { try { @@ -95,11 +102,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto "Validation of bean definition failed", ex); } } - + // 注册的过程中需要线程同步,以保证数据的一致性 synchronized (this.beanDefinitionMap) { Object oldBeanDefinition = this.beanDefinitionMap.get(beanName); - + // 检查是否有同名(beanName)的 BeanDefinition 存在于 IoC容器 中,如果已经存在,且不允许覆盖 // 已注册的 BeanDefinition,则抛出注册异常,allowBeanDefinitionOverriding 默认为 true if (oldBeanDefinition != null) { @@ -130,4 +137,4 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto resetBeanDefinition(beanName); } } -``` \ No newline at end of file +``` diff --git "a/docs/Spring/IoC/4\343\200\201\344\276\235\350\265\226\346\263\250\345\205\245(DI).md" "b/docs/Spring/IoC/4\343\200\201\344\276\235\350\265\226\346\263\250\345\205\245(DI).md" index aa4f1735..545321db 100644 --- "a/docs/Spring/IoC/4\343\200\201\344\276\235\350\265\226\346\263\250\345\205\245(DI).md" +++ "b/docs/Spring/IoC/4\343\200\201\344\276\235\350\265\226\346\263\250\345\205\245(DI).md" @@ -1,17 +1,19 @@ ## 前言 -前面我们主要分析了 FileSystemXmlApplicationContext 这个具体的 IoC容器实现类 的初始化源码,在 IoC容器 中建立了 beanName 到 BeanDefinition 的数据映射,通过一个 ConcurrentHashMap。现在我们来看一下 Spring 是如何将 IoC 容器中存在依赖关系的 bean 根据配置联系在一起的。 - -Spring 中触发 IoC容器“依赖注入” 的方式有两种,一个是应用程序通过 getBean()方法 向容器索要 bean实例 时触发依赖注入;另一个是提前给 bean 配置了 lazy-init 属性为 false,Spring 在 IoC容器 初始化会自动调用此 bean 的 getBean() 方法,提前完成依赖注入。总的来说,想提高运行时获取 bean 的效率,可以考虑配置此属性。 + +前面我们主要分析了 FileSystemXmlApplicationContext 这个具体的 IoC 容器实现类 的初始化源码,在 IoC 容器 中建立了 beanName 到 BeanDefinition 的数据映射,通过一个 ConcurrentHashMap。现在我们来看一下 Spring 是如何将 IoC 容器中存在依赖关系的 bean 根据配置联系在一起的。 + +Spring 中触发 IoC 容器“依赖注入” 的方式有两种,一个是应用程序通过 getBean()方法 向容器索要 bean 实例 时触发依赖注入;另一个是提前给 bean 配置了 lazy-init 属性为 false,Spring 在 IoC 容器 初始化会自动调用此 bean 的 getBean() 方法,提前完成依赖注入。总的来说,想提高运行时获取 bean 的效率,可以考虑配置此属性。 下面我将分别解读这两种依赖注入的触发方式,先看 getBean() 的,因为 lazy-init 最后也是通过调用 getBean() 完成的依赖注入。 - (PS:可以结合我 GitHub 上对 Spring 框架源码的阅读及个人理解一起看,会更有助于各位开发姥爷理解,地址: -spring-beans https://github.com/AmyliaY/spring-beans-reading -spring-context https://github.com/AmyliaY/spring-context-reading +spring-beans https://github.com/AmyliaY/spring-beans-reading +spring-context https://github.com/AmyliaY/spring-context-reading ) ## 正文 + 首先看一下 AbstractBeanFactory 中的 getBean() 系列方法及 doGetBean() 具体实现。 + ```java public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory { @@ -19,36 +21,36 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // BeanFactory 接口的实现,下列的 getBean() 方法不论是哪种重载,最后都会走 // doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) 的具体实现 //--------------------------------------------------------------------- - + // 获取 IoC容器 中指定名称的 bean public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); } - + // 获取 IoC容器 中指定名称和类型的 bean public T getBean(String name, Class requiredType) throws BeansException { return doGetBean(name, requiredType, null, false); } - + // 获取 IoC容器 中指定名称和参数的 bean public Object getBean(String name, Object... args) throws BeansException { return doGetBean(name, null, args, false); } - + // 获取 IoC容器 中指定名称、类型和参数的 bean public T getBean(String name, Class requiredType, Object... args) throws BeansException { return doGetBean(name, requiredType, args, false); } - + // 真正实现向 IoC容器 获取 bean 的功能,也是触发 依赖注入(DI) 的地方 @SuppressWarnings("unchecked") - protected T doGetBean(final String name, final Class requiredType, final Object[] args, + protected T doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { - + // 根据用户给定的名称(也可能是别名alias) 获取 IoC容器 中与 BeanDefinition 唯一对应的 beanName final String beanName = transformedBeanName(name); Object bean; - + // 根据 beanName 查看缓存中是否有已实例化的 单例bean,对于 单例bean,整个 IoC容器 只创建一次 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { @@ -61,7 +63,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp logger.debug("Returning cached instance of singleton bean '" + beanName + "'"); } } - // 获取给定 bean 的实例对象,主要是完成 FactoryBean 的相关处理 + // 获取给定 bean 的实例对象,主要是完成 FactoryBean 的相关处理 // 注意:BeanFactory 是一个 IoC容器,它保存了 bean 的基本配置信息。 // 而 FactoryBean 是 IoC容器 中一种特殊的 bean,它能够实例化 bean对象,注意两者之间的区别 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); @@ -71,7 +73,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } - + // 获取当前容器的父容器 BeanFactory parentBeanFactory = getParentBeanFactory(); // 如果当前容器中没有指定的 bean,且当前容器的父容器不为空 @@ -88,18 +90,18 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp return parentBeanFactory.getBean(nameToLookup, requiredType); } } - + // 创建的 bean 是否需要进行类型验证,一般不需要 if (!typeCheckOnly) { // 向容器标记指定的 bean 已经被创建 markBeanAsCreated(beanName); } - + try { // 根据 beanName 获取对应的 RootBeanDefinition final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); - + // 获取当前 bean 所依赖bean 的 beanName,下面的 getBean(dependsOnBean) 方法会触发 // getBean() 的递归调用,直到取到一个不依赖任何其它 bean 的 bean 为止。 // 比如:beanA 依赖了 beanB,而 beanB 依赖了 beanC,那么在实例化 beanA 时会先实例化 @@ -114,7 +116,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp registerDependentBean(dependsOnBean, beanName); } } - + // 如果当前 bean 是单例的 if (mbd.isSingleton()) { // 这里使用了一个匿名内部类,创建 bean实例对象 @@ -133,7 +135,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // 获取给定 bean 的实例对象 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } - + // 创建原型模式的 bean 实例对象 else if (mbd.isPrototype()) { // 原型模式 (Prototype) 每次都会创建一个新的对象 @@ -152,10 +154,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // 获取给定 bean 的实例对象 bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } - - // 要创建的 bean 既不是单例模式,也不是原型模式,则根据该 bean元素 在配置文件中 - // 配置的生命周期范围,选择实例化 bean 的合适方法,这种在 Web 应用程序中 - // 比较常用,如:request、session、application 等的生命周期 + + // 要创建的 bean 既不是单例模式,也不是原型模式,则根据该 bean元素 在配置文件中 + // 配置的生命周期范围,选择实例化 bean 的合适方法,这种在 Web 应用程序中 + // 比较常用,如:request、session、application 等的生命周期 else { // 获取此 bean 生命周期的范围 String scopeName = mbd.getScope(); @@ -193,7 +195,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp throw ex; } } - + // 对要返回的 bean实例对象 进行非空验证和类型检查,如果没问题就返回这个已经完成 依赖注入的bean if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) { try { @@ -211,7 +213,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } } ``` -总的来说,getBean() 方法是依赖注入的起点,之后会调用 createBean(),根据之前解析生成的 BeanDefinition对象 生成 bean 对象,下面我们看看 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中对 createBean() 的具体实现。 + +总的来说,getBean() 方法是依赖注入的起点,之后会调用 createBean(),根据之前解析生成的 BeanDefinition 对象 生成 bean 对象,下面我们看看 AbstractBeanFactory 的子类 AbstractAutowireCapableBeanFactory 中对 createBean() 的具体实现。 + ```java public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { @@ -222,13 +226,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac @Override protected Object createBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { - + if (logger.isDebugEnabled()) { logger.debug("Creating instance of bean '" + beanName + "'"); } // 判断需要创建的 bean 是否可实例化,是否可以通过当前的类加载器加载 resolveBeanClass(mbd, beanName); - + try { // 校验和准备 bean 中的方法覆盖 mbd.prepareMethodOverrides(); @@ -237,7 +241,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName, "Validation of method overrides failed", ex); } - + try { // 如果 bean 配置了后置处理器 PostProcessor,则这里返回一个 proxy 代理对象 Object bean = resolveBeforeInstantiation(beanName, mbd); @@ -249,7 +253,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac throw new BeanCreationException(mbd.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } - + // 创建 bean 实例对象的具体实现 Object beanInstance = doCreateBean(beanName, mbd, args); if (logger.isDebugEnabled()) { @@ -257,12 +261,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } return beanInstance; } - + /** * 创建 bean 实例对象的具体实现,Spring 中以 do 开头的都是方法的具体实现 */ protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) { - + BeanWrapper instanceWrapper = null; // 如果这个 bean 是单例的,则从缓存中获取这个 beanName 对应的 BeanWrapper实例,并清除 if (mbd.isSingleton()) { @@ -276,11 +280,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac */ instanceWrapper = createBeanInstance(beanName, mbd, args); } - + // 获取实例化对象和其类型 final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null); - + // 调用 PostProcessor 后置处理器 synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { @@ -288,7 +292,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac mbd.postProcessed = true; } } - + // 向容器中缓存单例模式的 bean 对象,以防循环引用 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); @@ -304,8 +308,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } }); } - - // bean 对象的初始化,依赖注入在此触发 + + // bean 对象的初始化,依赖注入在此触发 // 这个 exposedObject 在初始化完成之后,将返回作为依赖注入完成后的 bean Object exposedObject = bean; try { @@ -316,7 +320,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac */ populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { - // 初始化 bean对象 + // 初始化 bean对象 exposedObject = initializeBean(beanName, exposedObject, mbd); } } @@ -328,7 +332,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex); } } - + if (earlySingletonExposure) { // 获取指定名称的已注册的 单例bean对象 Object earlySingletonReference = getSingleton(beanName, false); @@ -342,7 +346,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set actualDependentBeans = new LinkedHashSet(dependentBeans.length); - // 获取当前 bean 所依赖的其他 bean + // 获取当前 bean 所依赖的其他 bean for (String dependentBean : dependentBeans) { // 对 依赖bean 进行类型检查 if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) { @@ -361,7 +365,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } } } - + try { // 注册 成完依赖注入的bean registerDisposableBeanIfNecessary(beanName, bean, mbd); @@ -369,19 +373,21 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac catch (BeanDefinitionValidationException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } - + // 为应用返回所需要的实例对象 return exposedObject; } } ``` + 从源码中可以看到 createBeanInstance() 和 populateBean() 这两个方法与依赖注入的实现非常密切,createBeanInstance() 方法中生成了 bean 所包含的 Java 对象,populateBean() 方法对这些生成的 bean 对象之间的依赖关系进行了处理。下面我们先看一下 createBeanInstance() 方法的实现。 + ```java /** * 创建 bean 的实例对象 */ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) { - + // 检查确认 bean 是可实例化的 Class beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { @@ -421,7 +427,7 @@ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { - // 使用容器的自动装配特性,调用匹配的构造方法实例化 + // 使用容器的自动装配特性,调用匹配的构造方法实例化 return autowireConstructor(beanName, mbd, ctors, args); } @@ -459,7 +465,9 @@ protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefin } } ``` -从源码中我们可以看到其调用了 SimpleInstantiationStrategy 实现类来生成 bean 对象,这个类是 Spring 用来生成 bean对象 的默认类,它提供了两种策略来实例化 bean对象,一种是利用 Java 的反射机制,另一种是直接使用 CGLIB。 + +从源码中我们可以看到其调用了 SimpleInstantiationStrategy 实现类来生成 bean 对象,这个类是 Spring 用来生成 bean 对象 的默认类,它提供了两种策略来实例化 bean 对象,一种是利用 Java 的反射机制,另一种是直接使用 CGLIB。 + ```java public class SimpleInstantiationStrategy implements InstantiationStrategy { @@ -498,7 +506,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { } } } - // 根据传入的 Constructor,在 BeanUtils 中调用该 Constructor 的 + // 根据传入的 Constructor,在 BeanUtils 中调用该 Constructor 的 // newInstance(Object...) 方法,实例化指定对象 return BeanUtils.instantiateClass(constructorToUse); } @@ -514,44 +522,46 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { } } ``` + 在 SimpleInstantiationStrategy 的子类 CglibSubclassingInstantiationStrategy 中可以看到使用 CGLIB 进行实例化的源码实现。 + ```java public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy { - + /** * 下面两个方法都通过实例化自己的私有静态内部类 CglibSubclassCreator, * 然后调用该内部类对象的实例化方法 instantiate() 完成实例化 */ protected Object instantiateWithMethodInjection( RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { - + return new CglibSubclassCreator(beanDefinition, owner).instantiate(null, null); } - + @Override protected Object instantiateWithMethodInjection( RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Constructor ctor, Object[] args) { - + return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args); } - + /** * 为避免 3.2 之前的 Spring 版本中的外部 cglib 依赖而创建的内部类。 */ private static class CglibSubclassCreator { - + private static final Log logger = LogFactory.getLog(CglibSubclassCreator.class); - + private final RootBeanDefinition beanDefinition; - + private final BeanFactory owner; - + public CglibSubclassCreator(RootBeanDefinition beanDefinition, BeanFactory owner) { this.beanDefinition = beanDefinition; this.owner = owner; } - + // 使用 CGLIB 进行 bean对象 实例化 public Object instantiate(Constructor ctor, Object[] args) { // 实例化 Enhancer对象,并为 Enhancer对象 设置父类,生成 Java 对象的参数,比如:基类、回调方法等 @@ -564,18 +574,20 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt new LookupOverrideMethodInterceptor(), new ReplaceOverrideMethodInterceptor() }); - + // 使用 CGLIB 的 create() 方法生成实例对象 return (ctor == null) ? enhancer.create() : enhancer.create(ctor.getParameterTypes(), args); } } } ``` -至此,完成了 bean对象 的实例化,然后就可以根据解析得到的 BeanDefinition对象 完成对各个属性的赋值处理,也就是依赖注入。这个实现方法就是前面 AbstractAutowireCapableBeanFactory 类中的 populateBean() 方法。 + +至此,完成了 bean 对象 的实例化,然后就可以根据解析得到的 BeanDefinition 对象 完成对各个属性的赋值处理,也就是依赖注入。这个实现方法就是前面 AbstractAutowireCapableBeanFactory 类中的 populateBean() 方法。 + ```java public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { - + /** * 为属性赋值,完成依赖注入 */ @@ -583,7 +595,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac // 获取 RootBeanDefinition 中设置的 属性值PropertyValues,这些属性值来自对 // .xml 文件中 bean元素 的解析 PropertyValues pvs = mbd.getPropertyValues(); - + // 如果 BeanWrapper对象 为 null,而要注入的属性值不为空,则抛出下述异常 if (bw == null) { if (!pvs.isEmpty()) { @@ -591,14 +603,14 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); } else { - // BeanWrapper对象 为 null,属性值也为空,不需要设置属性值,直接返回 + // BeanWrapper对象 为 null,属性值也为空,不需要设置属性值,直接返回 return; } } - + // 在设置属性之前调用 bean 的 PostProcessor 后置处理器 boolean continueWithPropertyPopulation = true; - + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { @@ -610,34 +622,34 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } } } - + if (!continueWithPropertyPopulation) { return; } - + // 依赖注入开始,首先处理 autowire 自动装配的注入 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { MutablePropertyValues newPvs = new MutablePropertyValues(pvs); - + // 对 autowire 自动装配的处理,根据 bean 名称自动装配注入 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) { autowireByName(beanName, mbd, bw, newPvs); } - + // 根据 bean 类型自动装配注入 if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) { autowireByType(beanName, mbd, bw, newPvs); } - + pvs = newPvs; } - + // 检查容器是否持有用于处理单例模式 bean 关闭时的后置处理器 boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); // bean实例对象 没有依赖,即没有继承基类 boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE); - + if (hasInstAwareBpps || needsDepCheck) { // 从实例对象中提取属性描述符 PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); @@ -665,7 +677,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac */ applyPropertyValues(beanName, mbd, bw, pvs); } - + /** * 解析并注入依赖属性的过程 */ @@ -673,18 +685,18 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac if (pvs == null || pvs.isEmpty()) { return; } - + // 封装属性值 MutablePropertyValues mpvs = null; List original; - + if (System.getSecurityManager()!= null) { if (bw instanceof BeanWrapperImpl) { // 设置安全上下文,JDK 安全机制 ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); } } - + if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; // 如果属性值已经转换 @@ -705,7 +717,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac else { original = Arrays.asList(pvs.getPropertyValues()); } - + // 获取用户自定义的类型转换 TypeConverter converter = getCustomTypeConverter(); if (converter == null) { @@ -713,7 +725,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } // 创建一个 BeanDefinition 属性值解析器,将 BeanDefinition 中的属性值解析为 bean 实例对象的实际值 BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); - + // 为属性的解析值创建一个副本,最后将属性值注入到实例对象中 List deepCopy = new ArrayList(original.size()); boolean resolveNecessary = false; @@ -750,7 +762,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } deepCopy.add(pv); } - // 如果:属性是可转换的,且属性原始值是字符串类型,且属性的原始类型值不是 + // 如果:属性是可转换的,且属性原始值是字符串类型,且属性的原始类型值不是 // 动态生成的字符串,且属性的原始值不是集合或者数组类型 else if (convertible && originalValue instanceof TypedStringValue && !((TypedStringValue) originalValue).isDynamic() && @@ -769,7 +781,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac // 标记属性值已经转换过 mpvs.setConverted(); } - + // 进行属性依赖注入 try { /** @@ -787,7 +799,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } } ``` + BeanDefinitionValueResolver 中解析属性值,对注入类型进行转换的具体实现。 + ```java class BeanDefinitionValueResolver { @@ -805,7 +819,7 @@ class BeanDefinitionValueResolver { */ return resolveReference(argName, ref); } - + // 对属性值是引用容器中另一个 bean 名称的解析 else if (value instanceof RuntimeBeanNameReference) { String refName = ((RuntimeBeanNameReference) value).getBeanName(); @@ -845,7 +859,7 @@ class BeanDefinitionValueResolver { "Error resolving array type for " + argName, ex); } } - // 没有获取到数组的类型,也没有获取到数组元素的类型 + // 没有获取到数组的类型,也没有获取到数组元素的类型 // 则直接设置数组的类型为 Object else { elementType = Object.class; @@ -913,7 +927,7 @@ class BeanDefinitionValueResolver { return evaluate(value); } } - + /** * 解析引用类型的属性值 */ @@ -932,7 +946,7 @@ class BeanDefinitionValueResolver { } return this.beanFactory.getParentBeanFactory().getBean(refName); } - // 从当前的容器中获取指定的引用 bean对象,如果指定的 bean 没有被实例化 + // 从当前的容器中获取指定的引用 bean对象,如果指定的 bean 没有被实例化 // 则会递归触发引用 bean 的初始化和依赖注入 else { Object bean = this.beanFactory.getBean(refName); @@ -947,7 +961,7 @@ class BeanDefinitionValueResolver { "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex); } } - + /** * 解析 array 类型的属性 */ @@ -961,7 +975,7 @@ class BeanDefinitionValueResolver { } return resolved; } - + /** * 解析 list 类型的属性 */ @@ -974,7 +988,7 @@ class BeanDefinitionValueResolver { } return resolved; } - + /** * 解析 set 类型的属性 */ @@ -988,7 +1002,7 @@ class BeanDefinitionValueResolver { } return resolved; } - + /** * 解析 map 类型的属性 */ @@ -1005,29 +1019,31 @@ class BeanDefinitionValueResolver { } } ``` -至此,已经为依赖注入做好了准备,下面就该将 bean对象 设置到它所依赖的另一个 bean 的属性中去。AbstractPropertyAccessor 和其子类 BeanWrapperImpl 完成了依赖注入的详细过程。先看一下 AbstractPropertyAccessor 中的实现。 + +至此,已经为依赖注入做好了准备,下面就该将 bean 对象 设置到它所依赖的另一个 bean 的属性中去。AbstractPropertyAccessor 和其子类 BeanWrapperImpl 完成了依赖注入的详细过程。先看一下 AbstractPropertyAccessor 中的实现。 + ```java public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor { /** - * setPropertyValues() 方法有多种重载,但最终都走的是 + * setPropertyValues() 方法有多种重载,但最终都走的是 * setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)重载方法 */ public void setPropertyValues(Map map) throws BeansException { setPropertyValues(new MutablePropertyValues(map)); } - + public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown) throws BeansException { setPropertyValues(pvs, ignoreUnknown, false); } - + public void setPropertyValues(PropertyValues pvs) throws BeansException { setPropertyValues(pvs, false, false); } - + public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { - + List propertyAccessExceptions = null; List propertyValues = (pvs instanceof MutablePropertyValues ? ((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues())); @@ -1057,7 +1073,7 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl propertyAccessExceptions.add(ex); } } - + // 如果出现 PropertyAccessException 异常,则将这些异常积累起来放到一个集合中,然后一次性抛出!!! // 这种抛异常的方式 在实际的开发中也时常使用,可以好好看一下,对比一下 if (propertyAccessExceptions != null) { @@ -1068,7 +1084,9 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl } } ``` + 最后看一下 BeanWrapperImpl 中的实现。 + ```java public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWrapper { @@ -1090,7 +1108,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (nestedBw == this) { pv.getOriginalPropertyValue().resolvedTokens = tokens; } - + /** * !!!!!!!!!!!!!!! * 进入 bean 属性值注入的具体实现 @@ -1102,7 +1120,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra setPropertyValue(tokens, pv); } } - + /** * !!!!!!!!!!!!!!!! * 依赖注入(将某个bean所依赖的值 注入到这个bean中) 的具体实现 @@ -1114,7 +1132,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra // 以及集合的 size 等信息 String propertyName = tokens.canonicalName; String actualName = tokens.actualName; - + // 对集合类型的属性注入,PropertyTokenHolder 的 keys 是用来保存集合类型属性的 size if (tokens.keys != null) { // 将属性信息从 tokens 拷贝到 getterTokens @@ -1125,7 +1143,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1); Object propValue; try { - // 通过反射机制,调用属性的 getter 方法获取属性值 + // 通过反射机制,调用属性的 getter 方法获取属性值 propValue = getPropertyValue(getterTokens); } catch (NotReadablePropertyException ex) { @@ -1178,7 +1196,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra Class requiredType = GenericCollectionTypeResolver.getCollectionReturnType( pd.getReadMethod(), tokens.keys.length); List list = (List) propValue; - + int index = Integer.parseInt(key); Object oldValue = null; if (isExtractOldValueForEditor() && index < list.size()) { @@ -1189,7 +1207,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length)); // 获取 list 集合的 size int size = list.size(); - // 如果 list 的长度大于属性值的长度,则多余的元素赋值为 null + // 如果 list 的长度大于属性值的长度,则多余的元素赋值为 null if (index >= size && index < this.autoGrowCollectionLimit) { for (int i = size; i < index; i++) { try { @@ -1268,7 +1286,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } pv.getOriginalPropertyValue().resolvedDescriptor = pd; } - + Object oldValue = null; try { Object originalValue = pv.getValue(); @@ -1281,11 +1299,11 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra if (isExtractOldValueForEditor() && pd.getReadMethod() != null) { // 获取属性的 getter 方法 final Method readMethod = pd.getReadMethod(); - // 如果属性的 getter 方法无法访问,则使用 Java 的反射机制强行访问 (暴力读取属性值) + // 如果属性的 getter 方法无法访问,则使用 Java 的反射机制强行访问 (暴力读取属性值) if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()) && !readMethod.isAccessible()) { if (System.getSecurityManager()!= null) { - // 匿名内部类,根据权限修改属性的读取控制限制 + // 匿名内部类,根据权限修改属性的读取控制限制 AccessController.doPrivileged(new PrivilegedAction() { public Object run() { readMethod.setAccessible(true); @@ -1298,7 +1316,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } try { - // 属性没有提供 getter 方法时,调用潜在的读取属性值的方法,获取属性值 + // 属性没有提供 getter 方法时,调用潜在的读取属性值的方法,获取属性值 if (System.getSecurityManager() != null) { oldValue = AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { @@ -1329,7 +1347,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra final Method writeMethod = (pd instanceof GenericTypeAwarePropertyDescriptor ? ((GenericTypeAwarePropertyDescriptor) pd).getWriteMethodForActualAccess() : pd.getWriteMethod()); - // 如果属性的 setter方法 无法访问,则强行设置 setter方法 可访问 (暴力为属性赋值) + // 如果属性的 setter方法 无法访问,则强行设置 setter方法 可访问 (暴力为属性赋值) if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()) && !writeMethod.isAccessible()) { if (System.getSecurityManager()!= null) { AccessController.doPrivileged(new PrivilegedAction() { @@ -1344,12 +1362,12 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } final Object value = valueToApply; - // 如果使用了 Java 的安全机制,则需要权限验证 + // 如果使用了 Java 的安全机制,则需要权限验证 if (System.getSecurityManager() != null) { try { AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { - // 将属性值设置到属性上去 + // 将属性值设置到属性上去 writeMethod.invoke(object, value); return null; } @@ -1360,7 +1378,7 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } else { - // 将属性值设置到属性上去 + // 将属性值设置到属性上去 writeMethod.invoke(this.object, value); } } @@ -1386,11 +1404,14 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra } } ``` -至此,完成了对 bean 的各种属性的依赖注入,在 bean 的实例化和依赖注入的过程中,需要依据 BeanDefinition 中的信息来递归地完成依赖注入。另外,在此过程中存在许多递归调用,一个递归是在上下文体系中查找 当前bean依赖的bean 和创建 当前bean依赖的bean 的递归调用;另一个是在依赖注入时,通过递归调用容器的 getBean() 方法,得到当前 bean 的依赖 bean,同时也触发对依赖 bean 的创建和注入;在对 bean 的属性进行依赖注入时,解析的过程也是递归的。这样,根据依赖关系,从最末层的依赖bean开始,一层一层地完成 bean 的创建和注入,直到最后完成当前 bean 的创建。 + +至此,完成了对 bean 的各种属性的依赖注入,在 bean 的实例化和依赖注入的过程中,需要依据 BeanDefinition 中的信息来递归地完成依赖注入。另外,在此过程中存在许多递归调用,一个递归是在上下文体系中查找 当前 bean 依赖的 bean 和创建 当前 bean 依赖的 bean 的递归调用;另一个是在依赖注入时,通过递归调用容器的 getBean() 方法,得到当前 bean 的依赖 bean,同时也触发对依赖 bean 的创建和注入;在对 bean 的属性进行依赖注入时,解析的过程也是递归的。这样,根据依赖关系,从最末层的依赖 bean 开始,一层一层地完成 bean 的创建和注入,直到最后完成当前 bean 的创建。 ## lazy-init 属性触发的依赖注入 + 最后看一下 lazy-init 触发的预实例化和依赖注入,发生在 IoC 容器完成对 BeanDefinition 的定位、载入、解析和注册之后。通过牺牲 IoC 容器初始化的性能,来有效提升应用第一次获取该 bean 的效率。 lazy-init 实现的入口方法在我们前面解读过的 AbstractApplicationContext 的 refresh() 中,它是 IoC 容器正式启动的标志。 + ```java public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext, DisposableBean { @@ -1410,66 +1431,66 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader synchronized (this.startupShutdownMonitor) { // 调用容器准备刷新的方法,获取容器的当前时间,同时给容器设置同步标识 prepareRefresh(); - + // 告诉子类启动 refreshBeanFactory() 方法,BeanDefinition 资源文件的载入从子类的 // refreshBeanFactory() 方法启动开始 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); - + // 为 BeanFactory 配置容器特性,例如类加载器、事件处理器等 prepareBeanFactory(beanFactory); - + try { // 为容器的某些子类指定特殊的 BeanPost 事件处理器 postProcessBeanFactory(beanFactory); - + // 调用所有注册的 BeanFactoryPostProcessor invokeBeanFactoryPostProcessors(beanFactory); - - // 为 BeanFactory 注册 BeanPost 事件处理器. - // BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件 + + // 为 BeanFactory 注册 BeanPost 事件处理器. + // BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件 registerBeanPostProcessors(beanFactory); - + // 初始化信息源,和国际化相关. initMessageSource(); - + // 初始化容器事件传播器 initApplicationEventMulticaster(); - + // 调用子类的某些特殊 Bean 初始化方法 onRefresh(); - + // 为事件传播器注册事件监听器. registerListeners(); - + /** * !!!!!!!!!!!!!!!!!!!!! * 对配置了 lazy-init属性 为 false 的 bean 进行预实例化 * !!!!!!!!!!!!!!!!!!!!! */ finishBeanFactoryInitialization(beanFactory); - + // 初始化容器的生命周期事件处理器,并发布容器的生命周期事件 finishRefresh(); } - + catch (BeansException ex) { // 销毁以创建的单态 Bean destroyBeans(); - + // 取消 refresh 操作,重置容器的同步标识. cancelRefresh(ex); - + throw ex; } } } - + /** * 对配置了 lazy-init属性 为 false 的 bean 进行预实例化 */ protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { - // 这是 Spring3 以后新加的代码,为容器指定一个转换服务 (ConversionService) - // 在对某些 bean 属性进行转换时使用 + // 这是 Spring3 以后新加的代码,为容器指定一个转换服务 (ConversionService) + // 在对某些 bean 属性进行转换时使用 if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) { beanFactory.setConversionService( @@ -1480,18 +1501,18 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader */ beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)); } - + String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false); for (String weaverAwareName : weaverAwareNames) { getBean(weaverAwareName); } - - // 为了类型匹配,停止使用临时的类加载器 + + // 为了类型匹配,停止使用临时的类加载器 beanFactory.setTempClassLoader(null); - + // 缓存容器中所有注册的 BeanDefinition 元数据,以防被修改 beanFactory.freezeConfiguration(); - + // 对配置了 lazy-init属性 为 false 的 单例bean 进行预实例化处理 beanFactory.preInstantiateSingletons(); } diff --git a/docs/Spring/IoC/BeanFactoryPostProcessor.md b/docs/Spring/IoC/BeanFactoryPostProcessor.md new file mode 100644 index 00000000..57576d9d --- /dev/null +++ b/docs/Spring/IoC/BeanFactoryPostProcessor.md @@ -0,0 +1,467 @@ +# BeanFactoryPostProcessor 源码分析 + +BeanFactoryPostProcessor 是当 BeanDefinition 读取完元数据(也就是从任意资源中定义的 bean 数据)后还未实例化之前可以进行修改 + +抄录并翻译官方的语句 + +> `BeanFactoryPostProcessor` 操作 bean 的元数据配置. 也就是说,Spring IoC 容器允许 `BeanFactoryPostProcessor` 读取配置元数据, 并可能在容器实例化除 `BeanFactoryPostProcessor` 实例之外的任何 bean _之前_ 更改它 + +tip: + +> 在 `BeanFactoryPostProcessor` (例如使用 `BeanFactory.getBean()`) 中使用这些 bean 的实例虽然在技术上是可行的,但这么来做会将 bean 过早实例化, 这违反了标准的容器生命周期. 同时也会引发一些副作用,例如绕过 bean 的后置处理。 + +```java +public interface BeanFactoryPostProcessor { + + /** + *通过ConfigurableListableBeanFactory这个可配置的BeanFactory对我们的bean原数据进行修改 + */ + void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; + +} +``` + +## BeanFactoryPostProcessor 执行时期的探究 + +ApplicationContext 的 refresh() 中的 invokeBeanFactoryPostProcessors 方法就开始创建我们的 BFPP(BeanFactoryPostProcessor)了 + +具体执行方法 invokeBeanFactoryPostProcessors,虽然一百多行代码,其实只需要特别了解的地方就几处。 + +```java +public static void invokeBeanFactoryPostProcessors( + ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) { + + Set processedBeans = new HashSet<>(); + + // 由于我们的beanFactory是DefaultListableBeanFactory实例是BeanDefinitionRegistry的子类所以可以进来 + if (beanFactory instanceof BeanDefinitionRegistry) { + BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; + List regularPostProcessors = new ArrayList<>(); + List registryProcessors = new ArrayList<>(); + + for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { + if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) { + BeanDefinitionRegistryPostProcessor registryProcessor = + (BeanDefinitionRegistryPostProcessor) postProcessor; + registryProcessor.postProcessBeanDefinitionRegistry(registry); + registryProcessors.add(registryProcessor); + } + else { + regularPostProcessors.add(postProcessor); + } + } + // BeanDefinitionRegistryPostProcessor是BFPP的子类但是比BFPP提前执行 + // 顺序实现PriorityOrdered接口先被执行,然后是Ordered接口,最后是什么都没实现的BeanDefinitionRegistryPostProcessor + + /** + *都有beanFactory.getBean方法,证明BeanDefinitionRegistryPostProcessor这个bean现在已经被创建了 + */ + + List currentRegistryProcessors = new ArrayList<>(); + + String[] postProcessorNames = + beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + for (String ppName : postProcessorNames) { + if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); + processedBeans.add(ppName); + } + } + sortPostProcessors(currentRegistryProcessors, beanFactory); + registryProcessors.addAll(currentRegistryProcessors); + invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); + currentRegistryProcessors.clear(); + + postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + for (String ppName : postProcessorNames) { + if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { + currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); + processedBeans.add(ppName); + } + } + sortPostProcessors(currentRegistryProcessors, beanFactory); + registryProcessors.addAll(currentRegistryProcessors); + invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); + currentRegistryProcessors.clear(); + + boolean reiterate = true; + while (reiterate) { + reiterate = false; + postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); + for (String ppName : postProcessorNames) { + if (!processedBeans.contains(ppName)) { + currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); + processedBeans.add(ppName); + reiterate = true; + } + } + sortPostProcessors(currentRegistryProcessors, beanFactory); + registryProcessors.addAll(currentRegistryProcessors); + invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); + currentRegistryProcessors.clear(); + } + + invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); + invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); + } + + else { + invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); + } + // BFPP的执行顺序与上一样 + /** + *都有beanFactory.getBean方法,证明BFPP这个bean现在已经被创建了 + */ + String[] postProcessorNames = + beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); + + + List priorityOrderedPostProcessors = new ArrayList<>(); + List orderedPostProcessorNames = new ArrayList<>(); + List nonOrderedPostProcessorNames = new ArrayList<>(); + for (String ppName : postProcessorNames) { + if (processedBeans.contains(ppName)) { + + } + else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class)); + } + else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { + orderedPostProcessorNames.add(ppName); + } + else { + nonOrderedPostProcessorNames.add(ppName); + } + } + + + sortPostProcessors(priorityOrderedPostProcessors, beanFactory); + invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); + + + List orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size()); + for (String postProcessorName : orderedPostProcessorNames) { + orderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); + } + sortPostProcessors(orderedPostProcessors, beanFactory); + invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); + + + List nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size()); + for (String postProcessorName : nonOrderedPostProcessorNames) { + nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); + } + invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); + + + beanFactory.clearMetadataCache(); +} +``` + +我们可以具体分析一下 BeanFactoryPostProcessor 的子类 CustomEditorConfigurer 自定义属性编辑器来巩固一下执行流程 + +所谓属性编辑器是当你要自定义更改配置文件中的属性属性时,如 String 类型转为 Date 或者其他,下面的一个小例子展示如何 String 类型的属性怎么转化为 Address 属性 + +## 简单工程(Spring-version-5.3.18) + +Person 类 + +```java +package cn.demo1; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@ToString +public class Person { + private String name; + private Address address; +} +``` + +Address 类 + +```java +package cn.demo1; + +@Setter +@Getter +@ToString +public class Address { + private String province; + private String city; +} + +``` + +AddressParse 类 + +```java +package cn.demo1; + +import java.beans.PropertyEditorSupport; + +public class AddressParse extends PropertyEditorSupport { + @Override + public void setAsText(String text) throws IllegalArgumentException { + final String[] vals = text.split(","); + Address addr = new Address(); + addr.setProvince(vals[0]); + addr.setCity(vals[1]); + setValue(addr); + } +} +``` + +MyCustomEditor 类 + +```java +package cn.demo1; + +import org.springframework.beans.PropertyEditorRegistrar; +import org.springframework.beans.PropertyEditorRegistry; + + +public class MyCustomEditor implements PropertyEditorRegistrar { + @Override + public void registerCustomEditors(PropertyEditorRegistry registry) { + registry.registerCustomEditor(Address.class, new AddressParse()); + } +} +``` + +配置文件 test1.xml + +```java + + + + + + + + + + + + + + + + + + +``` + +测试类 EdT + +```java +package cn.test1; + +import cn.demo1.Person; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class EdT { + @Test + public void test1() { + ApplicationContext context = new ClassPathXmlApplicationContext("test1.xml"); + final Person bean = context.getBean(Person.class); + System.out.println(bean); + } +} + +=====================测试结果 + +Person(name=李华, address=Address(province=四川, city=成都)) +``` + +可以看见我们成功的将 String 类型转化为 Address 类型,让我们来看看实现流程, + +- 首先实现 PropertyEditorSupport 来自定义属性编辑规则 +- 其次将你的编辑规则给到 PropertyEditorRegistrar 子类里进行注册 +- 最后在 Spring 中配置 CustomEditorConfigurer 类然后注入你的 PropertyEditorRegistrar 注册器 + +让我们 debug 走一遍 + +如果你已经耐心看完上面的`BeanFactoryPostProcessor执行时期的探究`那么你应该可以知道接下来我们的步骤应该是进入 invokeBeanFactoryPostProcessors 这个方法里了 + +```java +private static void invokeBeanFactoryPostProcessors( + Collection postProcessors, ConfigurableListableBeanFactory beanFactory) { + + for (BeanFactoryPostProcessor postProcessor : postProcessors) { + StartupStep postProcessBeanFactory = beanFactory.getApplicationStartup().start("spring.context.bean-factory.post-process") + .tag("postProcessor", postProcessor::toString); + postProcessor.postProcessBeanFactory(beanFactory); + postProcessBeanFactory.end(); + } + } +``` + +很明显它执行 postProcessBeanFactory 这个方法 + +我们探究的 BFPP 正是 CustomEditorConfigurer,所以这个是 CustomEditorConfigurer 对 BFPP 的 postProcessBeanFactory 实现 + +```java +// 必然有个set方法让我们进行注入 +public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) { + this.propertyEditorRegistrars = propertyEditorRegistrars; +} + +@Override +public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + if (this.propertyEditorRegistrars != null) { + for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { + // 把它加入Bean工厂里后面可以进行调用 + private final Set propertyEditorRegistrars = new LinkedHashSet<>(4); + beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar); + } + } + if (this.customEditors != null) { + this.customEditors.forEach(beanFactory::registerCustomEditor); + } +} +``` + +关于这个注册器使用要到后面填充属性的时候才会用到, + +> 我其实觉得这个有点瑕疵,因为 BFPP 作用影响应该是当 Spring 还未创建 bean 的时候,可以用 BFPP 进行修改操作,可是这个属性编辑却影响了 bean 创建过后的修改操作,那么它就替代了 BPP(BeanPostProcessor)的作用发挥了。(以上仅仅代表个人的观点,有可能是我想错了) + +当我们 debug 到 AbstractAutowireCapableBeanFactory 的 populateBean 这个方法填充 bean 的属性的时候, + +让我们看看它的方法,其中我省略了大部分无关代码 + +```java +protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { + // 这个是如果你配置的bean中有属性值的话 + // 也就是如下的配置,那么pvs不会为空的 + /** + + + + + */ + PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); + + if (pvs != null) { + // 属性操作 + applyPropertyValues(beanName, mbd, bw, pvs); + } +} +``` + +让我们继续看看 applyPropertyValues 这个方法,无关的代码我也给省略了 + +```java +protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { + // PropertyValues接口的默认实现。允许对属性进行简单操作,并提供构造函数以支持从 Map 进行深度复制和构造。 + MutablePropertyValues mpvs = null; + List original; + // 可以进去 + if (pvs instanceof MutablePropertyValues) { + mpvs = (MutablePropertyValues) pvs; + // 默认为false,即我们需要类型转换 + if (mpvs.isConverted()) { + // Shortcut: use the pre-converted values as-is. + try { + bw.setPropertyValues(mpvs); + return; + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } + } + // 把bean的属性以列表的形式展示出来 + original = mpvs.getPropertyValueList(); + } + else { + original = Arrays.asList(pvs.getPropertyValues()); + } + // 默认为空 + TypeConverter converter = getCustomTypeConverter(); + if (converter == null) { + converter = bw; + } + // 就一个组合类,帮助更好的bean的属性的解析 + BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); + + // 深拷贝 + List deepCopy = new ArrayList<>(original.size()); + boolean resolveNecessary = false; + for (PropertyValue pv : original) { + if (pv.isConverted()) { + deepCopy.add(pv); + } + else { + // 获取bean的属性名字 + String propertyName = pv.getName(); + //获取bean属性值的包装对象 + Object originalValue = pv.getValue(); + // 自动装配的事情 + if (originalValue == AutowiredPropertyMarker.INSTANCE) { + Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod(); + if (writeMethod == null) { + throw new IllegalArgumentException("Autowire marker for property without write method: " + pv); + } + originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true); + } + // 把bean的属性值从包装类中分离出来 + Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); + Object convertedValue = resolvedValue; + // 一般为true + boolean convertible = bw.isWritableProperty(propertyName) && + !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); + if (convertible) { + // 这个就是重点,对应我们的属性转化 + convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); + } +} +``` + +继续追踪 + +```java +@Nullable +private Object convertForProperty( + @Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { + // BeanWrapperImpl是继承TypeConverter的 + if (converter instanceof BeanWrapperImpl) { + // 所以执行下面的方法 + return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName); + } + else { + PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName); + MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); + return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam); + } +} +``` + +```java +@Nullable +public Object convertForProperty(@Nullable Object value, String propertyName) throws TypeMismatchException { + CachedIntrospectionResults cachedIntrospectionResults = getCachedIntrospectionResults(); + PropertyDescriptor pd = cachedIntrospectionResults.getPropertyDescriptor(propertyName); + if (pd == null) { + throw new InvalidPropertyException(getRootClass(), getNestedPath() + propertyName, + "No property '" + propertyName + "' found"); + } + TypeDescriptor td = cachedIntrospectionResults.getTypeDescriptor(pd); + if (td == null) { + td = cachedIntrospectionResults.addTypeDescriptor(pd, new TypeDescriptor(property(pd))); + } + // 上面的工作不用管,全是一些前戏工作,这个才是主题,至此我们的流程就到这里结束吧 + // 后面的流程太多了,大部分都是处理细节,你只需要知道大概的脉络就行,就是最终它肯定会 + // 走到AddressParse这个核心处理 + return convertForProperty(propertyName, null, value, td); +} +``` + +你可以自己可以尝试 debug 一下,看别人实践真的不如自己动手实践一下,Spring 的包装类实属太多,但是可以抓住核心流程进行 debug。 diff --git a/docs/Spring/IoC/BeanPostProcessor.md b/docs/Spring/IoC/BeanPostProcessor.md new file mode 100644 index 00000000..f3581a78 --- /dev/null +++ b/docs/Spring/IoC/BeanPostProcessor.md @@ -0,0 +1,145 @@ +# BeanPostProcessor 源码分析 + +BeanPostProcessor 接口也叫 Bean 后置处理器,作用是在 Bean 对象实例化和依赖注入完成后,在配置文件 bean 的 init-method(初始化方法)或者 InitializingBean 的 afterPropertiesSet 的前后添加我们自己的处理逻辑。注意是 Bean 实例化完毕后及依赖注入完成后触发的,接口的源码如下。 + +```java +public interface BeanPostProcessor { + /** + * 实例化、依赖注入完毕, + * 在调用显示的初始化之前完成一些定制的初始化任务 + */ + Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException; + + /** + * 实例化、依赖注入、初始化完毕时执行 + */ + Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException; +} +``` + +共有两种方式实现: + +- 实现 BeanPostProcessor 接口,然后将此类注册到 Spring 即可; +- 第二种是通过`ConfigurableBeanFactory` 的 addBeanPostProcessor 方法进行注册。 + +BeanPostProcess 可以有多个,并且可以通过设置 order 属性来控制这些 BeanPostProcessor 实例的执行顺序。 仅当 BeanPostProcessor 实现 Ordered 接口时,才能设置此属性,或者 PriorityOrdered 接口。 + +如果某个类实现了 BeanPostProcessor 则它会在 AbstractApplicationContext 中的 registerBeanPostProcessors(beanFactory)方法中创建 bean 而不是和普通的 bean 一样在 finishBeanFactoryInitialization(beanFactory)中才被创建。 + +当我们注册 BeanPostProcessor 的时候,其中我省略了大部分无关代码: + +```java +public static void registerBeanPostProcessors( + ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { + // 找到实现BeanPostProcessor接口的子类bean名称 + String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); + // 加一是因为下一行又加了一个BPP(BeanPostProcessor) + int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length; + beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); + + // 排序省略,没啥好讲的,你只需要知道没有实现排序接口的BPP放在了nonOrderedPostProcessorNames这里 + List priorityOrderedPostProcessors = new ArrayList<>(); + List internalPostProcessors = new ArrayList<>(); + List orderedPostProcessorNames = new ArrayList<>(); + List nonOrderedPostProcessorNames = new ArrayList<>(); + for (String ppName : postProcessorNames) { + if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { + BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); + priorityOrderedPostProcessors.add(pp); + if (pp instanceof MergedBeanDefinitionPostProcessor) { + internalPostProcessors.add(pp); + } + } + else if (beanFactory.isTypeMatch(ppName, Ordered.class)) { + orderedPostProcessorNames.add(ppName); + } + else { + nonOrderedPostProcessorNames.add(ppName); + } + } + + // 中间省略了一些.... + + // 重点来了 + List nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size()); + for (String ppName : nonOrderedPostProcessorNames) { + // 当它getBean的时候我们的BPP就开始创建 + BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class); + nonOrderedPostProcessors.add(pp); + if (pp instanceof MergedBeanDefinitionPostProcessor) { + internalPostProcessors.add(pp); + } + } +} +``` + +在此我举例一个典型的例子 AutowiredAnnotationBeanPostProcessor,是 BeanPostProcessor 的一个子类,是@Autowired 和@Value 的具体实现,其他的子类你也可以按如下的流程自行走一边,注意我的例子只是一个最为简单的例子,也就是用@Autowired 注入了一个普通的字段对象 + +我们看看 AutowiredAnnotationBeanPostProcessor 类,当然也是省略大部分代码: + +```java +// 这个类可以看见当我们创建AutowiredAnnotationBeanPostProcessor对象的时候完成了一个工作就是给 +// autowiredAnnotationTypes赋值,这个操作有点超前,后面根据这个判断要注入的类中是否有如下的注解 +public AutowiredAnnotationBeanPostProcessor() { + this.autowiredAnnotationTypes.add(Autowired.class); + this.autowiredAnnotationTypes.add(Value.class); + try { + this.autowiredAnnotationTypes.add((Class) + ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader())); + logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring"); + } + catch (ClassNotFoundException ex) { + // JSR-330 API not available - simply skip. + } +} +// 从InstantiationAwareBeanPostProcessors继承而来 +@Override +public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { + // 寻找注入的元数据,其中它有注解扫描,和类属性信息的填充 + InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); + try { + // 把数据注入到当前的bean,由于需要分析的过程太多就略过怎么实现的 + metadata.inject(bean, beanName, pvs); + } + catch (BeanCreationException ex) { + throw ex; + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); + } + return pvs; +} +``` + +BeanPostProcessor 的职责是在 bean 初始化后进行实例的更改,所以我们在普通 bean 实例化的时候就可以看见它的身影 AbstractAutowireCapableBeanFactory 中的 populateBean 就是给 bean 属性填充值,同样我们省略大部分代码: + +```java +protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { + // true 因为我们有InstantiationAwareBeanPostProcessors的实现子类 + boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); + boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); + + PropertyDescriptor[] filteredPds = null; + if (hasInstAwareBpps) { + if (pvs == null) { + pvs = mbd.getPropertyValues(); + } + for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) { + // 主要方法 + PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); + if (pvsToUse == null) { + if (filteredPds == null) { + filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); + } + pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); + if (pvsToUse == null) { + return; + } + } + pvs = pvsToUse; + } + } +} +``` + +至此当前的 bean 就实现了@Autowired 的字段注入,整个过程看似简单,但却有诸多细节。 diff --git "a/docs/Spring/IoC/\345\276\252\347\216\257\344\276\235\350\265\226.md" "b/docs/Spring/IoC/\345\276\252\347\216\257\344\276\235\350\265\226.md" new file mode 100644 index 00000000..a7506b3e --- /dev/null +++ "b/docs/Spring/IoC/\345\276\252\347\216\257\344\276\235\350\265\226.md" @@ -0,0 +1,268 @@ +# 循环依赖 + +一个对象依赖对象闭环到自己 + +> A -> B -> .... ->A + +tip: + +> 不涉及代理对象问题 + +解决方法:当一个对象已经实例化完毕了,还未初始化的时候,将它注入到它所依赖的已经实例好的对象(提前暴露对象),使得它所依赖的对象是个完整对象(实例化+初始化),然后再将这个完整对象注入给它。 + +## 简单工程(Spring-version-5.3.18) + +我们就用下面两个类进行实践,多个类间依赖也是如此。 + +A 类 + +```java +package cn.demo1; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class A { + private B b; +} +``` + +B 类 + +```java +package cn.demo1; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class B { + private A a; +} +``` + +配置文件 test1.xml + +```xml + + + + + + + + + + +``` + +DefaultSingletonBeanRegistry 类中的几个特别重要的属性 + +```java +// 一级缓存 存放完整Bean对象(实例化+初始化) +private final Map singletonObjects = new ConcurrentHashMap<>(256); + +// 三级缓存 存放一个lambda表达式 +private final Map> singletonFactories = new HashMap<>(16); + +// 二级缓存 存放一个半成品bean对象(只是实例化还未初始化),提前暴露 +private final Map earlySingletonObjects = new ConcurrentHashMap<>(16); +``` + +> 循环依赖问题应该是出现属性填充的时候 + +doCreateBean 这个方法 + +> 可以参照 [createBeanInstance](/docs/Spring/clazz/Spring-beanFactory.md#createbeaninstance) 查看 Spring 是怎么实例化的 + +```java +protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) + throws BeanCreationException { + // bean的包装类 + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + if (instanceWrapper == null) { + // 就只是bean的实例化 + instanceWrapper = createBeanInstance(beanName, mbd, args); + } + Object bean = instanceWrapper.getWrappedInstance(); + Class beanType = instanceWrapper.getWrappedClass(); + if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; + } + + // 一般为true + boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName)); + // ....省略部分 + if (earlySingletonExposure) { + + // 这里是将一段lambda放入三级缓存中,可以看见bean填充属性之前会将三级缓存创建好,它传入了一个还未初始化的bean + addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); + } + Object exposedObject = bean; + try { + populateBean(beanName, mbd, instanceWrapper); + exposedObject = initializeBean(beanName, exposedObject, mbd); + } + // ..........省略部分 + return exposedObject; +} +``` + +addSingletonFactory + +```java +// 其中singletonFactory是一个lambda表达式 +protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(singletonFactory, "Singleton factory must not be null"); + synchronized (this.singletonObjects) { + // 如果我们一级缓存中不存在这个叫beanName的bean + if (!this.singletonObjects.containsKey(beanName)) { + // 放入三级缓存中 + this.singletonFactories.put(beanName, singletonFactory); + // 把二级缓存中叫beanName的半成品bean删除 + this.earlySingletonObjects.remove(beanName); + // 标记当前注册的bean + this.registeredSingletons.add(beanName); + } + } +} +``` + +lambda 所执行的方法 + +```java +protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { + Object exposedObject = bean; + // 普通bean是进不来的 + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { + exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); + } + } + // 直接返回传进来的bean,返回的是一个还未初始化的bean,是提前暴露的 + return exposedObject; +} +``` + +populateBean 中有调用了 applyPropertyValues 这个方法具体详情请点击这里 [applyProertyValues](/docs/Spring/clazz/Spring-beanFactory.md#applypropertyvalues) + +```java +protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { + + // Create a deep copy, resolving any references for values. + List deepCopy = new ArrayList<>(original.size()); + boolean resolveNecessary = false; + for (PropertyValue pv : original) { + if (pv.isConverted()) { + deepCopy.add(pv); + } + else { + // 属性名字 + String propertyName = pv.getName(); + //当你引用另一个bean的时候,会把它封装成RuntimeBeanReference这个对象,便于操作 + Object originalValue = pv.getValue(); + // 这里是解析的工作,也就是会产生循环依赖产生的地方 + Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); + // 省略.... + } +} +``` + +applyPropertyValues 中有个重要的方法调用,省略无关代码 + +```java +// 我们当前需要要的就是 +public Object resolveValueIfNecessary(Object argName, @Nullable Object value) { + // 当前bean的属性值的类型正是这个 + if (value instanceof RuntimeBeanReference) { + RuntimeBeanReference ref = (RuntimeBeanReference) value; + return resolveReference(argName, ref); + } + // 省略... +} +``` + +resolveReferance 中有一段代码 + +```java +// 这个方法会调用getBean +@Nullable +private Object resolveReference(Object argName, RuntimeBeanReference ref) { + // 省略... + + // 上面一般进不去,直接看这个重点 + resolvedName = String.valueOf(doEvaluate(ref.getBeanName())); + // 获取所依赖的bean + bean = this.beanFactory.getBean(resolvedName); + + // 省略... + +} +``` + +getBean 从而到这个 doGetBean 方法,其他代码不多说,最主要是下面这个 + +其中有一段代码,首先它会尝试从缓存中获取到 bean,如果获取不到就创建这个 bean + +```java +Object sharedInstance = getSingleton(beanName); +``` + +> 获取缓存 bean 的顺序是,先从一级缓存中取,若不存在,从二级缓存中取,若还是不存在,则从三级缓存中取 + +```java +@Nullable +protected Object getSingleton(String beanName, boolean allowEarlyReference) { + // 一级缓存中是否存在 + Object singletonObject = this.singletonObjects.get(beanName); + // 如果想要获取的bean正在创建中且无一级缓存 + if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { + // 尝试二级缓存 + singletonObject = this.earlySingletonObjects.get(beanName); + if (singletonObject == null && allowEarlyReference) { + synchronized (this.singletonObjects) { + singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + // 获取二级缓存 + singletonObject = this.earlySingletonObjects.get(beanName); + if (singletonObject == null) { + // 获取三级缓存 + ObjectFactory singletonFactory = this.singletonFactories.get(beanName); + if (singletonFactory != null) { + // 调用三级缓存,这个地方就会调用我们的lambda表达式了 + /** + *() -> getEarlyBeanReference(beanName, mbd, bean) + *这里就是我们解决办法的地方,因为所有普通的bean会首先提前进行三级缓存 + *所以这里会获取到还未初始化的bean,从而赋值到所依赖当前singletonObject对象的bean + */ + singletonObject = singletonFactory.getObject(); + // 放入二级缓存中 + this.earlySingletonObjects.put(beanName, singletonObject); + // 三级缓存中移除当前beanName的lambda表达式 + this.singletonFactories.remove(beanName); + } + } + } + } + } + } + // 完整对象或者还未初始化的对象 + return singletonObject; +} +``` + +最后经历这个就获取到一个半成品对象所依赖的一个完整对象,然后再将完整对象注入半成品对象中。 + +## 历程 + +> 该历程仅代表当前这个项目工程 + +![image](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/循环依赖.png) diff --git a/docs/Spring/JDBC/Spring-jdbc.md b/docs/Spring/JDBC/Spring-jdbc.md index 056086b2..e69de29b 100644 --- a/docs/Spring/JDBC/Spring-jdbc.md +++ b/docs/Spring/JDBC/Spring-jdbc.md @@ -1,522 +0,0 @@ -# Spring JDBC -- Author: [HuiFer](https://github.com/huifer) -- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - - - -## 环境搭建 - -- 依赖 - - ```gradle - compile(project(":spring-jdbc")) - compile group: 'com.alibaba', name: 'druid', version: '1.1.21' - compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.47' - ``` - -- db配置 - - ```properties - jdbc.url= - jdbc.driverClass= - jdbc.username= - jdbc.password= - ``` - -- 实体对象 - - ```JAVA - public class HsLog { - private Integer id; - - private String source; - - public Integer getId() { - return id; - } - - public void setId(Integer id) { - this.id = id; - } - - public String getSource() { - return source; - } - - public void setSource(String source) { - this.source = source; - } - } - ``` - - - -- DAO - - ```JAVA - public interface HsLogDao { - List findAll(); - - void save(HsLog hsLog); - } - - ``` - - - -- 实现类 - - ```JAVA - public class HsLogDaoImpl extends JdbcDaoSupport implements HsLogDao { - - - @Override - public List findAll() { - return this.getJdbcTemplate().query("select * from hs_log", new HsLogRowMapper()); - - } - - @Override - public void save(HsLog hsLog) { - this.getJdbcTemplate().update("insert into hs_log (SOURCE) values(?)" - , new Object[]{ - hsLog.getSource(), - } - - ); - } - - class HsLogRowMapper implements RowMapper { - - public HsLog mapRow(ResultSet rs, int rowNum) throws SQLException { - - HsLog log = new HsLog(); - log.setId(rs.getInt("id")); - log.setSource(rs.getString("source")); - return log; - } - - } - } - - ``` - - - -- xml - - ```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ``` - -- 运行方法 - - ```JAVA - - public class SpringJDBCSourceCode { - public static void main(String[] args) { - ApplicationContext applicationContext = new ClassPathXmlApplicationContext("JDBC-demo.xml"); - HsLogDaoImpl bean = applicationContext.getBean(HsLogDaoImpl.class); - System.out.println(bean.findAll()); - HsLog hsLog = new HsLog(); - hsLog.setSource("jlkjll"); - bean.save(hsLog); - - } - } - - ``` - - - - - - - -## 链接对象构造 - -`Connection con = DataSourceUtils.getConnection(obtainDataSource());` - -```java - public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException { - try { - return doGetConnection(dataSource); - } - catch (SQLException ex) { - throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex); - } - catch (IllegalStateException ex) { - throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage()); - } - } - -``` - -### org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection - -```java - public static Connection doGetConnection(DataSource dataSource) throws SQLException { - Assert.notNull(dataSource, "No DataSource specified"); - - ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); - if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { - conHolder.requested(); - if (!conHolder.hasConnection()) { - logger.debug("Fetching resumed JDBC Connection from DataSource"); - // 设置连接对象 - conHolder.setConnection(fetchConnection(dataSource)); - } - return conHolder.getConnection(); - } - // Else we either got no holder or an empty thread-bound holder here. - - logger.debug("Fetching JDBC Connection from DataSource"); - // 获取链接 - Connection con = fetchConnection(dataSource); - - // 当前线程支持同步 - if (TransactionSynchronizationManager.isSynchronizationActive()) { - try { - // Use same Connection for further JDBC actions within the transaction. - // Thread-bound object will get removed by synchronization at transaction completion. - // 在同一个事物中使用同一个链接对象 - ConnectionHolder holderToUse = conHolder; - if (holderToUse == null) { - holderToUse = new ConnectionHolder(con); - } - else { - holderToUse.setConnection(con); - } - // 记录链接数量 - holderToUse.requested(); - TransactionSynchronizationManager.registerSynchronization( - new ConnectionSynchronization(holderToUse, dataSource)); - holderToUse.setSynchronizedWithTransaction(true); - if (holderToUse != conHolder) { - TransactionSynchronizationManager.bindResource(dataSource, holderToUse); - } - } - catch (RuntimeException ex) { - // Unexpected exception from external delegation call -> close Connection and rethrow. - releaseConnection(con, dataSource); - throw ex; - } - } - - return con; - } - -``` - - - - - -## 释放资源 - -`releaseConnection(con, dataSource);` - -- `org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection` - -```JAVA - public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) { - try { - doReleaseConnection(con, dataSource); - } - catch (SQLException ex) { - logger.debug("Could not close JDBC Connection", ex); - } - catch (Throwable ex) { - logger.debug("Unexpected exception on closing JDBC Connection", ex); - } - } - -``` - -```java -public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException { - if (con == null) { - return; - } - if (dataSource != null) { - ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); - if (conHolder != null && connectionEquals(conHolder, con)) { - // It's the transactional Connection: Don't close it. - // 连接数-1 - conHolder.released(); - return; - } - } - // 处理其他情况 - doCloseConnection(con, dataSource); - } -``` - - - - - -### org.springframework.transaction.support.ResourceHolderSupport - -链接数 - -```JAVA - /** - * Increase the reference count by one because the holder has been requested - * (i.e. someone requested the resource held by it). - */ - public void requested() { - this.referenceCount++; - } - - /** - * Decrease the reference count by one because the holder has been released - * (i.e. someone released the resource held by it). - */ - public void released() { - this.referenceCount--; - } -``` - - - - - - - -## 查询解析 - -### org.springframework.jdbc.core.JdbcTemplate - -```XML - - - - -``` -- 从配置中可以知道 JdbcTemplate 需要 dataSource 属性, 就从这里开始讲起 -- `org.springframework.jdbc.support.JdbcAccessor.setDataSource`, 这段代码就只做了赋值操作(依赖注入) -```java -public void setDataSource(@Nullable DataSource dataSource) { - this.dataSource = dataSource; - } -``` -- 下面`hsLogDao`也是依赖注入本篇不做详细讲述。 - - - -### org.springframework.jdbc.core.JdbcTemplate#query(java.lang.String, org.springframework.jdbc.core.RowMapper) - -```java - @Override - public List findAll() { - return this.getJdbcTemplate().query("select * from hs_log", new HsLogRowMapper()); - } - -``` - -```java - @Override - @Nullable - public T query(final String sql, final ResultSetExtractor rse) throws DataAccessException { - Assert.notNull(sql, "SQL must not be null"); - Assert.notNull(rse, "ResultSetExtractor must not be null"); - if (logger.isDebugEnabled()) { - logger.debug("Executing SQL query [" + sql + "]"); - } - - /** - * Callback to execute the query. - */ - class QueryStatementCallback implements StatementCallback, SqlProvider { - @Override - @Nullable - public T doInStatement(Statement stmt) throws SQLException { - ResultSet rs = null; - try { - // 执行sql - rs = stmt.executeQuery(sql); - // 1. org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData - return rse.extractData(rs); - } - finally { - JdbcUtils.closeResultSet(rs); - } - } - - @Override - public String getSql() { - return sql; - } - } - - return execute(new QueryStatementCallback()); - } - -``` - -```java - @Override - @Nullable - public T execute(StatementCallback action) throws DataAccessException { - Assert.notNull(action, "Callback object must not be null"); - - Connection con = DataSourceUtils.getConnection(obtainDataSource()); - Statement stmt = null; - try { - stmt = con.createStatement(); - applyStatementSettings(stmt); - // 执行 - T result = action.doInStatement(stmt); - handleWarnings(stmt); - return result; - } - catch (SQLException ex) { - // Release Connection early, to avoid potential connection pool deadlock - // in the case when the exception translator hasn't been initialized yet. - String sql = getSql(action); - JdbcUtils.closeStatement(stmt); - stmt = null; - DataSourceUtils.releaseConnection(con, getDataSource()); - con = null; - throw translateException("StatementCallback", sql, ex); - } - finally { - JdbcUtils.closeStatement(stmt); - DataSourceUtils.releaseConnection(con, getDataSource()); - } - } -``` - -```JAVA - @Override - public List extractData(ResultSet rs) throws SQLException { - List results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>()); - int rowNum = 0; - while (rs.next()) { - // 调用自定义的 rowMapper 进行数据处理 - T t = this.rowMapper.mapRow(rs, rowNum++); - results.add(t); - } - return results; - } - -``` - -![image-20200109150841916](../../../images/spring/image-20200109150841916.png) - -这样就可以获取到了 - -方法`result`没有什么操作直接返回即可 - -```java - private static T result(@Nullable T result) { - Assert.state(result != null, "No result"); - return result; - } -``` - - - - - - - -## 插入解析 - -```java -@Override - public void save(HsLog hsLog) { - this.getJdbcTemplate().update("insert into hs_log (SOURCE) values(?)" - , new Object[]{ - hsLog.getSource(), - } - - ); - } -``` - -`org.springframework.jdbc.core.JdbcTemplate#update(org.springframework.jdbc.core.PreparedStatementCreator, org.springframework.jdbc.core.PreparedStatementSetter)` - -```java - protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss) - throws DataAccessException { - - logger.debug("Executing prepared SQL update"); - - return updateCount(execute(psc, ps -> { - try { - if (pss != null) { - // 设置请求参数 - pss.setValues(ps); - } - int rows = ps.executeUpdate(); - if (logger.isTraceEnabled()) { - logger.trace("SQL update affected " + rows + " rows"); - } - return rows; - } - finally { - if (pss instanceof ParameterDisposer) { - ((ParameterDisposer) pss).cleanupParameters(); - } - } - })); - } - -``` - - - - - diff --git a/docs/Spring/RMI/Spring-RMI.md b/docs/Spring/RMI/Spring-RMI.md index 3e319052..a9597980 100644 --- a/docs/Spring/RMI/Spring-RMI.md +++ b/docs/Spring/RMI/Spring-RMI.md @@ -1,18 +1,25 @@ # Spring RMI + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - Spring 远程服务调用 + ## DEMO + ### 服务提供方 -- 服务提供方需要准备**接口**、**接口实现泪** + +- 服务提供方需要准备**接口**、**接口实现类** - 接口 + ```java public interface IDemoRmiService { int add(int a, int b); } ``` + - 接口实现 + ```java public class IDemoRmiServiceImpl implements IDemoRmiService { @Override @@ -21,7 +28,9 @@ public class IDemoRmiServiceImpl implements IDemoRmiService { } } ``` -- xml配置文件 + +- xml 配置文件 + ```xml ``` + - 运行 + ```java public class RMIServerSourceCode { public static void main(String[] args) throws Exception { @@ -50,8 +61,11 @@ public class RMIServerSourceCode { } ``` + ### 服务调用方 + - xml 配置 + ```java ``` -**注意:rmi使用小写** + +**注意:rmi 使用小写** 大写报错信息: + ```text Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'rmiClient' defined in class path resource [rmi/RMIClientSourceCode.xml]: Invocation of init method failed; nested exception is org.springframework.remoting.RemoteLookupFailureException: Service URL [RMI://localhost:9999/springRmi] is invalid; nested exception is java.net.MalformedURLException: invalid URL scheme: RMI://localhost:9999/springRmi at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1795) @@ -99,7 +115,9 @@ Caused by: java.net.MalformedURLException: invalid URL scheme: RMI://localhost:9 ... 17 more ``` + - 运行 + ```java public class RMIClientSourceCode { public static void main(String[] args) { @@ -112,10 +130,15 @@ public class RMIClientSourceCode { ``` --- + ## 服务端初始化 -- `org.springframework.remoting.rmi.RmiServiceExporter`,该类定义了spring的远程服务信息 + +- `org.springframework.remoting.rmi.RmiServiceExporter`,该类定义了 spring 的远程服务信息 + ### RmiServiceExporter + - 基础属性如下 + ```java public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean { /** @@ -173,9 +196,10 @@ public class RMIClientSourceCode { private boolean createdRegistry = false; } ``` + - 属性设置不说了,看接口实现了那些 - 1. InitializingBean: 初始化bean调用`afterPropertiesSet`方法 - 2. DisposableBean: 摧毁bean调用`destroy`方法 + 1. InitializingBean: 初始化 bean 调用`afterPropertiesSet`方法 + 2. DisposableBean: 摧毁 bean 调用`destroy`方法 ```java @@ -268,7 +292,9 @@ public class RMIClientSourceCode { ``` #### checkService -- 校验service 是否为空 + +- 校验 service 是否为空 + ```java /** * Check whether the service reference has been set. @@ -282,8 +308,10 @@ public class RMIClientSourceCode { ``` #### getRegistry + - 获取 Registry 实例 - - 简单说就是通过host和port创建socket + - 简单说就是通过 host 和 port 创建 socket + ```java protected Registry getRegistry(String registryHost, int registryPort, @Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory) @@ -305,8 +333,11 @@ public class RMIClientSourceCode { } } ``` + #### getObjectToExport -- 初始化并且获取缓存的object + +- 初始化并且获取缓存的 object + ```java protected Remote getObjectToExport() { // determine remote object @@ -331,8 +362,11 @@ public class RMIClientSourceCode { } ``` + #### getProxyForService + - 获取代理服务 + ```java protected Object getProxyForService() { // service 校验 @@ -365,13 +399,15 @@ public class RMIClientSourceCode { } ``` + - 这里要注意,切片方法的`invoke`调用 - `RmiInvocationWrapper`的`invoke`方法会在调用方调用接口时候触发 +#### rebind 和 bind -#### rebind 和 bind - 绑定和重新绑定 - 这部分源码在: `sun.rmi.registry.RegistryImpl` + ```java public void rebind(String var1, Remote var2) throws RemoteException, AccessException { checkAccess("Registry.rebind"); @@ -380,6 +416,7 @@ public class RMIClientSourceCode { ``` + ```java public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException { checkAccess("Registry.bind"); @@ -393,10 +430,13 @@ public class RMIClientSourceCode { } } ``` -- 共同维护` private Hashtable bindings = new Hashtable(101);`这个对象 + +- 共同维护` private Hashtable bindings = new Hashtable(101);`这个对象 #### unexportObjectSilently + - 出现异常时候调用方法 + ```java private void unexportObjectSilently() { @@ -411,14 +451,14 @@ public class RMIClientSourceCode { } ``` + --- + ## 客户端初始化 ### RmiProxyFactoryBean -![image-20200225104850528](../../../images/spring/image-20200226082614312.png) - - +![image-20200225104850528](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226082614312.png) - 该类实现了`InitializingBean`接口直接看`afterPropertiesSet`方法 @@ -453,18 +493,13 @@ public class RMIClientSourceCode { throw new IllegalArgumentException("Property 'serviceUrl' is required"); } } - - ``` - - + ``` ### org.springframework.remoting.support.UrlBasedRemoteAccessor#afterPropertiesSet - 该方法对 `serviceUrl`进行空判断,如果是空的抛出异常 - - ```java /** * 判断服务地址是否为空 @@ -477,12 +512,8 @@ public class RMIClientSourceCode { } ``` - - ### org.springframework.remoting.rmi.RmiClientInterceptor#afterPropertiesSet - - ```java @Override public void afterPropertiesSet() { @@ -494,9 +525,7 @@ public class RMIClientSourceCode { 1. 调用父类的`afterPropertiesSet`方法判断`serviceUrl`是否为空 2. 执行`prepare()`方法 - - -```JAVA +```java public void prepare() throws RemoteLookupFailureException { // Cache RMI stub on initialization? if (this.lookupStubOnStartup) { @@ -524,7 +553,7 @@ public class RMIClientSourceCode { #### org.springframework.remoting.rmi.RmiClientInterceptor#lookupStub -```JAVA +```java protected Remote lookupStub() throws RemoteLookupFailureException { try { Remote stub = null; @@ -576,8 +605,6 @@ protected Remote lookupStub() throws RemoteLookupFailureException { } ``` - - ### org.springframework.remoting.rmi.RmiProxyFactoryBean#afterPropertiesSet ```java @@ -594,14 +621,11 @@ protected Remote lookupStub() throws RemoteLookupFailureException { ``` - - - ### 增强调用 - 通过类图我们可以知道`RmiProxyFactoryBean`实现了`MethodInterceptor`,具体实现方法在`org.springframework.remoting.rmi.RmiClientInterceptor#invoke` -```JAVA +```java @Override public Object invoke(MethodInvocation invocation) throws Throwable { // 获取remote @@ -625,11 +649,7 @@ protected Remote lookupStub() throws RemoteLookupFailureException { ``` - - - - -```JAVA +```java protected Remote getStub() throws RemoteLookupFailureException { if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) { // 如果缓存stub存在直接获取,否则创建 @@ -646,13 +666,9 @@ protected Remote lookupStub() throws RemoteLookupFailureException { } ``` - - - `org.springframework.remoting.rmi.RmiClientInterceptor#doInvoke(org.aopalliance.intercept.MethodInvocation, org.springframework.remoting.rmi.RmiInvocationHandler)` - - -```JAVA +```java @Nullable protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler) throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { @@ -671,15 +687,13 @@ protected Remote lookupStub() throws RemoteLookupFailureException { - `RmiInvocationHandler`类图 -![image-20200226082614312](../../../images/spring/image-20200226082614312.png) - - +![image-20200226082614312](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226082614312.png) 最后的`invoke`方法 - `org.springframework.remoting.rmi.RmiInvocationWrapper#invoke` - ```JAVA + ```java /** * Delegates the actual invocation handling to the RMI exporter. * @@ -691,28 +705,28 @@ protected Remote lookupStub() throws RemoteLookupFailureException { @Nullable public Object invoke(RemoteInvocation invocation) throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { - + return this.rmiExporter.invoke(invocation, this.wrappedObject); } ``` - 继续跟踪`org.springframework.remoting.rmi.RmiBasedExporter#invoke` - ```JAVA + ```java @Override protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - + return super.invoke(invocation, targetObject); } ``` - 继续跟踪`org.springframework.remoting.support.RemoteInvocationBasedExporter#invoke` - ```JAVA + ```java protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - + if (logger.isTraceEnabled()) { logger.trace("Executing " + invocation); } @@ -738,20 +752,16 @@ protected Remote lookupStub() throws RemoteLookupFailureException { throw ex; } } - - ``` - + ``` - 关键语句**`return getRemoteInvocationExecutor().invoke(invocation, targetObject);`** 类图 -![image-20200226083247784](../../../images/spring/image-20200226083247784.png) - - +![image-20200226083247784](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226083247784.png) -```JAVA +```java public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor { @Override @@ -766,7 +776,7 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor } ``` -```JAVA +```java public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { @@ -777,103 +787,83 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor - 这部分流程相对清晰,从对象中获取函数,调用这个函数 - - --- - - -## 服务端debug +## 服务端 debug - `org.springframework.remoting.rmi.RmiServiceExporter#afterPropertiesSet`打上断点 -![image-20200226084056993](../../../images/spring/image-20200226084056993.png) - -可以看到此时的数据字段和我们的xml配置中一致 - +![image-20200226084056993](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084056993.png) +可以看到此时的数据字段和我们的 xml 配置中一致 - `org.springframework.remoting.rmi.RmiServiceExporter#prepare`断点 - ![image-20200226084200428](../../../images/spring/image-20200226084200428.png) + ![image-20200226084200428](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084200428.png) 往下一直走 - ![image-20200226084400939](../../../images/spring/image-20200226084400939.png) + ![image-20200226084400939](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084400939.png) - ​ 这一行是jdk的就不进去看了 + ​ 这一行是 jdk 的就不进去看了 执行完成就创建出了 `Registry` - ![image-20200226084514795](../../../images/spring/image-20200226084514795.png) + ![image-20200226084514795](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084514795.png) - `org.springframework.remoting.rmi.RmiBasedExporter#getObjectToExport` 直接看结果对象 - ![image-20200226084640683](../../../images/spring/image-20200226084640683.png) - + ![image-20200226084640683](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084640683.png) +- 执行 bind -- 执行bind + ![image-20200226084923783](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084923783.png) - ![image-20200226084923783](../../../images/spring/image-20200226084923783.png) - - ![image-20200226084914000](../../../images/spring/image-20200226084914000.png) + ![image-20200226084914000](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084914000.png) - 此时服务端信息已经成功记录并且启动 -## 客户端debug - -![image-20200226085433130](../../../images/spring/image-20200226085433130.png) - -![image-20200226085440865](../../../images/spring/image-20200226085440865.png) +## 客户端 debug +![image-20200226085433130](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226085433130.png) +![image-20200226085440865](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226085440865.png) remote 对象 -![image-20200226085727426](../../../images/spring/image-20200226085727426.png) +![image-20200226085727426](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226085727426.png) - 服务提供接口 -![image-20200226085839496](../../../images/spring/image-20200226085839496.png) - - +![image-20200226085839496](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226085839496.png) - serviceProxy - ![image-20200226090042946](../../../images/spring/image-20200226090042946.png) - - + ![image-20200226090042946](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090042946.png) - 方法调用 - - 使用的是AOP技术进行的,AOP相关技术不在此处展开 + - 使用的是 AOP 技术进行的,AOP 相关技术不在此处展开 -![image-20200226090315865](../../../images/spring/image-20200226090315865.png) +![image-20200226090315865](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090315865.png) stub 对象 -![image-20200226090432052](../../../images/spring/image-20200226090432052.png) +![image-20200226090432052](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090432052.png) - - - - -![image-20200226090650154](../../../images/spring/image-20200226090650154.png) +![image-20200226090650154](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090650154.png) - `invocation` - ![image-20200226090719108](../../../images/spring/image-20200226090719108.png) + ![image-20200226090719108](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090719108.png) - `targetObject` - ![image-20200226090827849](../../../images/spring/image-20200226090827849.png) - - + ![image-20200226090827849](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090827849.png) - 反射执行`method`结束整个调用 - ![image-20200226090945418](../../../images/spring/image-20200226090945418.png) + ![image-20200226090945418](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090945418.png) - 此时得到结果RMI调用结束 \ No newline at end of file + 此时得到结果 RMI 调用结束 diff --git "a/docs/Spring/Spring5\346\226\260\347\211\271\346\200\247/Spring-spring-components.md" "b/docs/Spring/Spring5\346\226\260\347\211\271\346\200\247/Spring-spring-components.md" index e4377df4..01a4f416 100644 --- "a/docs/Spring/Spring5\346\226\260\347\211\271\346\200\247/Spring-spring-components.md" +++ "b/docs/Spring/Spring5\346\226\260\347\211\271\346\200\247/Spring-spring-components.md" @@ -1,14 +1,18 @@ # Spring5 新特性 - spring.components + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - ## 解析 + - 相关类: `org.springframework.context.index.CandidateComponentsIndexLoader` - 测试用例: `org.springframework.context.annotation.ClassPathScanningCandidateComponentProviderTests.defaultsWithIndex`,`org.springframework.context.index.CandidateComponentsIndexLoaderTests` - `CandidateComponentsIndexLoader`是怎么找出来的,全文搜索`spring.components` + ### 使用介绍 + - 下面是从`resources/example/scannable/spring.components`测试用例中复制过来的,从中可以发现等号左侧放的是我们写的组件,等号右边是属于什么组件 + ``` example.scannable.AutowiredQualifierFooService=example.scannable.FooService example.scannable.DefaultNamedComponent=org.springframework.stereotype.Component @@ -23,7 +27,9 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component ``` ### debug + - 入口 `org.springframework.context.index.CandidateComponentsIndexLoader.loadIndex` + ```java @Nullable public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) { @@ -35,6 +41,7 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component } ``` + ```java /** * 解析 META-INF/spring.components 文件 @@ -99,11 +106,8 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component } ``` +![image-20200115105941265](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115105941265.png) - - - -![image-20200115105941265](../../../images/spring/image-20200115105941265.png) - 该类给`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents`提供了帮助 ```java @@ -121,4 +125,4 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component } } -``` \ No newline at end of file +``` diff --git "a/docs/Spring/SpringMVC/IoC\345\256\271\345\231\250\345\234\250Web\347\216\257\345\242\203\344\270\255\347\232\204\345\220\257\345\212\250.md" "b/docs/Spring/SpringMVC/IoC\345\256\271\345\231\250\345\234\250Web\347\216\257\345\242\203\344\270\255\347\232\204\345\220\257\345\212\250.md" index 4eeeb436..cfe791a7 100644 --- "a/docs/Spring/SpringMVC/IoC\345\256\271\345\231\250\345\234\250Web\347\216\257\345\242\203\344\270\255\347\232\204\345\220\257\345\212\250.md" +++ "b/docs/Spring/SpringMVC/IoC\345\256\271\345\231\250\345\234\250Web\347\216\257\345\242\203\344\270\255\347\232\204\345\220\257\345\212\250.md" @@ -1,9 +1,11 @@ -## 1 Web环境中的SpringMVC -在 Web环境 中,SpringMVC 是建立在 IoC容器 基础上的。了解 SpringMVC,首先要了解 Spring 的 IoC容器 是如何在 Web环境 中被载入并起作用的。 +## 1 Web 环境中的 SpringMVC -Spring 的 IoC 是一个独立模块,它并不直接在 Web容器 中发挥作用,如果要在 Web环境 中使用 IoC容器,需要 Spring 为 IoC 设计一个启动过程,把 IoC容器 导入,并在 Web容器 中建立起来。具体说来,这个启动过程是和 Web容器 的启动过程集成在一起的。在这个过程中,一方面处理 Web容器 的启动,另一方面通过设计特定的 Web容器拦截器,将 IoC容器 载人到 Web环境 中来,并将其初始化。在这个过程建立完成以后,IoC容器 才能正常工作,而 SpringMVC 是建立在 IoC容器 的基础上的,这样才能建立起 MVC框架 的运行机制,从而响应从 Web容器 传递的 HTTP请求。 +在 Web 环境 中,SpringMVC 是建立在 IoC 容器 基础上的。了解 SpringMVC,首先要了解 Spring 的 IoC 容器 是如何在 Web 环境 中被载入并起作用的。 + +Spring 的 IoC 是一个独立模块,它并不直接在 Web 容器 中发挥作用,如果要在 Web 环境 中使用 IoC 容器,需要 Spring 为 IoC 设计一个启动过程,把 IoC 容器 导入,并在 Web 容器 中建立起来。具体说来,这个启动过程是和 Web 容器 的启动过程集成在一起的。在这个过程中,一方面处理 Web 容器 的启动,另一方面通过设计特定的 Web 容器拦截器,将 IoC 容器 载人到 Web 环境 中来,并将其初始化。在这个过程建立完成以后,IoC 容器 才能正常工作,而 SpringMVC 是建立在 IoC 容器 的基础上的,这样才能建立起 MVC 框架 的运行机制,从而响应从 Web 容器 传递的 HTTP 请求。 + +下面以 Tomcat 作为 Web 容器 的例子进行分析。在 Tomcat 中,web.xml 是应用的部署描述文件。在 web.xml 中常常经常能看到与 Spring 相关的部署描述。 -下面以 Tomcat 作为 Web容器 的例子进行分析。在 Tomcat 中,web.xml 是应用的部署描述文件。在 web.xml 中常常经常能看到与 Spring 相关的部署描述。 ```xml sample @@ -22,34 +24,38 @@ Spring 的 IoC 是一个独立模块,它并不直接在 Web容器 中发挥作 org.springframework.web.context.ContextLoaderListener ``` -web.xml 是 SpringMVC 与 Tomcat 的接口部分。这个部署描述文件中,首先定义了一个 Servlet对象,它是 SpringMVC 的 DispatcherServlet。这个 DispatcherServlet 是 MVC 中很重要的一个类,起着分发请求的作用。 -同时,在部署描述中,还为这个 DispatcherServlet 定义了对应的 URL映射,以指定这个 Servlet 需要处理的 HTTP请求范围。context-param参数 用来指定 IoC容器 读取 Bean 的 XML文件 的路径,在这里,这个配置文件被定义为 WEB-INF/applicationContext.xml。其中可以看到 Spring应用 的 Bean配置。 +web.xml 是 SpringMVC 与 Tomcat 的接口部分。这个部署描述文件中,首先定义了一个 Servlet 对象,它是 SpringMVC 的 DispatcherServlet。这个 DispatcherServlet 是 MVC 中很重要的一个类,起着分发请求的作用。 + +同时,在部署描述中,还为这个 DispatcherServlet 定义了对应的 URL 映射,以指定这个 Servlet 需要处理的 HTTP 请求范围。context-param 参数 用来指定 IoC 容器 读取 Bean 的 XML 文件 的路径,在这里,这个配置文件被定义为 WEB-INF/applicationContext.xml。其中可以看到 Spring 应用 的 Bean 配置。 + +最后,作为 Spring MVC 的启动类,ContextLoaderListener 被定义为一个监听器,这个监听器是与 Web 服务器 的生命周期相关联的,由 ContextLoaderListener 监听器 负责完成 IoC 容器 在 Web 环境 中的启动工作。 + +DispatchServlet 和 ContextLoaderListener 提供了在 Web 容器 中对 Spring 的接口,也就是说,这些接口与 Web 容器 耦合是通过 ServletContext 来实现的(ServletContext 是容器和应用沟通的桥梁,从一定程度上讲 ServletContext 就是 servlet 规范 的体现)。这个 ServletContext 为 Spring 的 IoC 容器 提供了一个宿主环境,在宿主环境中,Spring MVC 建立起一个 IoC 容器 的体系。这个 IoC 容器体系 是通过 ContextLoaderListener 的初始化来建立的,在建立 IoC 容器体系 后,把 DispatchServlet 作为 Spring MVC 处理 Web 请求 的转发器建立起来,从而完成响应 HTTP 请求 的准备。有了这些基本配置,建立在 IoC 容器 基础上的 SpringMVC 就可以正常地发挥作用了。下面我们看一下 IoC 容器 在 Web 容器 中的启动代码实现。 -最后,作为 Spring MVC 的启动类,ContextLoaderListener 被定义为一个监听器,这个监听器是与 Web服务器 的生命周期相关联的,由 ContextLoaderListener监听器 负责完成 IoC容器 在 Web环境 中的启动工作。 +## 2 IoC 容器启动的基本过程 -DispatchServlet 和 ContextLoaderListener 提供了在 Web容器 中对 Spring 的接口,也就是说,这些接口与 Web容器 耦合是通过 ServletContext 来实现的(ServletContext 是容器和应用沟通的桥梁,从一定程度上讲 ServletContext 就是 servlet规范 的体现)。这个 ServletContext 为 Spring 的 IoC容器 提供了一个宿主环境,在宿主环境中,Spring MVC 建立起一个 IoC容器 的体系。这个 IoC容器体系 是通过 ContextLoaderListener 的初始化来建立的,在建立 IoC容器体系 后,把 DispatchServlet 作为 Spring MVC 处理 Web请求 的转发器建立起来,从而完成响应 HTTP请求 的准备。有了这些基本配置,建立在 IoC容器 基础上的 SpringMVC 就可以正常地发挥作用了。下面我们看一下 IoC容器 在 Web容器 中的启动代码实现。 +IoC 容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC 容器 在 Web 应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC 对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在 Web 容器 中启动 Spring 应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML 时序图 如下图所示。 -## 2 IoC容器启动的基本过程 -IoC容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC容器 在 Web应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在 Web容器 中启动 Spring应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML时序图 如下图所示。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/Web容器启动spring应用程序过程图.png) -![avatar](../../../images/springMVC/Web容器启动spring应用程序过程图.png) +在 web.xml 中,已经配置了 ContextLoaderListener,它是 Spring 提供的类,是为在 Web 容器 中建立 IoC 容器 服务的,它实现了 ServletContextListener 接口,这个接口是在 Servlet API 中定义的,提供了与 Servlet 生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 和 上下文销毁 contextDestroyed()方法。而在 Web 容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外,ContextLoaderListener 还继承了 ContextLoader,具体的载入 IoC 容器 的过程是由 ContextLoader 来完成的。 -在 web.xml 中,已经配置了 ContextLoaderListener,它是 Spring 提供的类,是为在 Web容器 中建立 IoC容器 服务的,它实现了 ServletContextListener接口,这个接口是在 Servlet API 中定义的,提供了与 Servlet生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 和 上下文销毁 contextDestroyed()方法。而在 Web容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外,ContextLoaderListener 还继承了 ContextLoader,具体的载入 IoC容器 的过程是由 ContextLoader 来完成的。 +在 ContextLoader 中,完成了两个 IoC 容器 建立的基本过程,一个是在 Web 容器 中建立起 双亲 IoC 容器,另一个是生成相应的 WebApplicationContext 并将其初始化。 -在 ContextLoader 中,完成了两个 IoC容器 建立的基本过程,一个是在 Web容器 中建立起 双亲IoC容器,另一个是生成相应的 WebApplicationContext 并将其初始化。 +## 3 Web 容器中的上下文设计 -## 3 Web容器中的上下文设计 -先从 Web容器 中的上下文入手,看看 Web环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web环境 中使用 IoC容器, -Spring 为 Web应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。 +先从 Web 容器 中的上下文入手,看看 Web 环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web 环境 中使用 IoC 容器, +Spring 为 Web 应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。 -![avatar](../../../images/springMVC/WebApplicationContext接口的类继承关系.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/WebApplicationContext接口的类继承关系.png) -在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex接口 与 BeanFactory接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。 +在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex 接口 与 BeanFactory 接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。 + +同样,在源代码中,也可以分析出类似的继承关系,在 WebApplicationContext 中可以看到相关的常量设计,比如 ROOT* WEB* APPLICATION_CONTEXT_ATTRIBUTE 等,这个常量是用来索引在 ServletContext 中存储的根上下文的。这个接口类定义的接口方法比较简单,在这个接口中,定义了一 +个 getServletContext()方法,通过这个方法可以得到当前 Web 容器 的 Servlet 上下文环境,通过 +这个方法,相当于提供了一个 Web 容器级别的 全局环境。 -同样,在源代码中,也可以分析出类似的继承关系,在 WebApplicationContext 中可以看到相关的常量设计,比如 ROOT_ WEB_ APPLICATION_CONTEXT_ATTRIBUTE 等,这个常量是用来索引在 ServletContext 中存储的根上下文的。这个接口类定义的接口方法比较简单,在这个接口中,定义了一 -个 getServletContext()方法,通过这个方法可以得到当前 Web容器 的 Servlet上下文环境,通过 -这个方法,相当于提供了一个 Web容器级别的 全局环境。 ```java public interface WebApplicationContext extends ApplicationContext { @@ -57,26 +63,28 @@ public interface WebApplicationContext extends ApplicationContext { * 该常量用于在 ServletContext 中存取根上下文 */ String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT"; - + /** * 对于 WebApplicationContext 来说,需要得到 Web容器 的 ServletContext */ ServletContext getServletContext(); } ``` -在启动过程中,Spring 会使用一个默认的 WebApplicationContext 实现作为 IoC容器,这个默认使用的 IoC容器 就是 XmlWebApplicationContext,它继承了 ApplicationContext,在 ApplicationContext 的基础上,增加了对 Web环境 和 XML配置定义 的处理。在 XmlWebApplicationContext 的初始化过程中,Web容器 中的 IoC容器 被建立起来,从而在 Web容器 中建立起整个 Spring应用。与前面博文中分析的 IoC容器 的初始化一样,这个过程也有 loadBeanDefinition()方法 对 BeanDefinition 的载入。在 Web环境 中,对定位 BeanDefinition 的 Resource 有特别的要求,这个要求的处理体现在对 getDefaultConfigLocations()方法 的处理中。这里使用了默认的 BeanDefinition 的配置路径,这个路径在 XmlWebApplicationContext 中作为一个常量定义好了,即 /WEB-INF/applicationContext.xml。 + +在启动过程中,Spring 会使用一个默认的 WebApplicationContext 实现作为 IoC 容器,这个默认使用的 IoC 容器 就是 XmlWebApplicationContext,它继承了 ApplicationContext,在 ApplicationContext 的基础上,增加了对 Web 环境 和 XML 配置定义 的处理。在 XmlWebApplicationContext 的初始化过程中,Web 容器 中的 IoC 容器 被建立起来,从而在 Web 容器 中建立起整个 Spring 应用。与前面博文中分析的 IoC 容器 的初始化一样,这个过程也有 loadBeanDefinition()方法 对 BeanDefinition 的载入。在 Web 环境 中,对定位 BeanDefinition 的 Resource 有特别的要求,这个要求的处理体现在对 getDefaultConfigLocations()方法 的处理中。这里使用了默认的 BeanDefinition 的配置路径,这个路径在 XmlWebApplicationContext 中作为一个常量定义好了,即 /WEB-INF/applicationContext.xml。 + ```java public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { /** 若不指定其它文件,Spring 默认从 "/WEB-INF/applicationContext.xml" 目录文件 初始化 IoC容器 */ public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; - + /** 默认的配置文件在 /WEB-INF/ 目录下 */ public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; - + /** 默认的配置文件后缀名为 .xml */ public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; - + /** * 此加载过程在容器 refresh() 时启动 */ @@ -84,22 +92,22 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 使用 XmlBeanDefinitionReader 对指定的 BeanFactory 进行解析 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); - + // 初始化 beanDefinitionReader 的属性,其中,设置 ResourceLoader 是因为 XmlBeanDefinitionReader // 是 DefaultResource 的子类,所有这里同样会使用 DefaultResourceLoader 来定位 BeanDefinition beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); - + // 该方法是一个空实现 initBeanDefinitionReader(beanDefinitionReader); // 使用初始化完成的 beanDefinitionReader 来加载 BeanDefinitions loadBeanDefinitions(beanDefinitionReader); } - + protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) { } - + /** * 获取所有的配置文件,然后一个一个载入 BeanDefinition */ @@ -111,7 +119,7 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC } } } - + /** * 获取默认路径 "/WEB-INF/***.xml" 下的配置文件, * 或者获取 "/WEB-INF/applicationContext.xml" 配置文件 @@ -127,18 +135,22 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC } } ``` -从上面的代码中可以看到,在 XmlWebApplicationContext 中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取 BeanDefinition信息,在这里,就转化为如何在 Web容器环境 中获得 BeanDefinition信息。在获得 BeanDefinition信息 之后,后面的过程基本上就和前面分析的 XmlFileSystemBeanFactory 一样,是通过 XmlBeanDefinitionReader 来载入 BeanDefinition信息 的,最终完成整个上下文的初始化过程。 -## 4 ContextLoader的设计与实现 -对于 Spring 承载的 Web应用 而言,可以指定在 Web应用程序 启动时载入 IoC容器(或者称为WebApplicationContext)。这个功能是由 ContextLoaderListener 来完成的,它是在 Web容器 中配置的监听器,会监听 Web容器 的启动,然后载入 IoC容器。这个 ContextLoaderListener 通过使用 ContextLoader 来完成实际的 WebApplicationContext,也就是 IoC容器 的初始化工作。这个 ContextLoader 就像 Spring应用程序 在 Web容器 中的启动器。这个启动过程是在 Web容器 中发生的,所以需要根据 Web容器 部署的要求来定义 ContextLoader,相关的配置在概述中已经看到了,这里就不重复了。 -为了了解 IoC容器 在 Web容器 中的启动原理,这里对 启动器ContextLoaderListener 的实现进行分析。**这个监听器是启动 根IoC容器 并把它载入到 Web容器 的主要功能模块,也是整个 Spring Web应用 加载 IoC 的第一个地方**。从加载过程可以看到,首先从 Servlet事件 中得到 ServletContext,然后可以读取配置在 web.xml 中的各个相关的属性值,接着 ContextLoader 会实例化 WebApplicationContext,并完成其载人和初始化过程。这个被初始化的第一个上下文,作为根上下文而存在,这个根上下文载入后,被绑定到 Web应用程序 的 ServletContext 上。任何需要访问根上下文的应用程序代码都可以从 WebApplicationContextUtils类 的静态方法中得到。 +从上面的代码中可以看到,在 XmlWebApplicationContext 中,基本的上下文功能都已经通过类的继承获得,这里需要处理的是,如何获取 BeanDefinition 信息,在这里,就转化为如何在 Web 容器环境 中获得 BeanDefinition 信息。在获得 BeanDefinition 信息 之后,后面的过程基本上就和前面分析的 XmlFileSystemBeanFactory 一样,是通过 XmlBeanDefinitionReader 来载入 BeanDefinition 信息 的,最终完成整个上下文的初始化过程。 + +## 4 ContextLoader 的设计与实现 + +对于 Spring 承载的 Web 应用 而言,可以指定在 Web 应用程序 启动时载入 IoC 容器(或者称为 WebApplicationContext)。这个功能是由 ContextLoaderListener 来完成的,它是在 Web 容器 中配置的监听器,会监听 Web 容器 的启动,然后载入 IoC 容器。这个 ContextLoaderListener 通过使用 ContextLoader 来完成实际的 WebApplicationContext,也就是 IoC 容器 的初始化工作。这个 ContextLoader 就像 Spring 应用程序 在 Web 容器 中的启动器。这个启动过程是在 Web 容器 中发生的,所以需要根据 Web 容器 部署的要求来定义 ContextLoader,相关的配置在概述中已经看到了,这里就不重复了。 + +为了了解 IoC 容器 在 Web 容器 中的启动原理,这里对 启动器 ContextLoaderListener 的实现进行分析。**这个监听器是启动 根 IoC 容器 并把它载入到 Web 容器 的主要功能模块,也是整个 Spring Web 应用 加载 IoC 的第一个地方**。从加载过程可以看到,首先从 Servlet 事件 中得到 ServletContext,然后可以读取配置在 web.xml 中的各个相关的属性值,接着 ContextLoader 会实例化 WebApplicationContext,并完成其载人和初始化过程。这个被初始化的第一个上下文,作为根上下文而存在,这个根上下文载入后,被绑定到 Web 应用程序 的 ServletContext 上。任何需要访问根上下文的应用程序代码都可以从 WebApplicationContextUtils 类 的静态方法中得到。 + +下面分析具体的根上下文的载人过程。在 ContextLoaderListener 中,实现的是 **ServletContextListener 接口,这个接口里的函数会结合 Web 容器 的生命周期被调用**。因为 ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于 ServletContext 的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext 被创建的时候,服务器关闭时,ServletContext 将被销毁的时候等。对应这些事件及 Web 容器状态 的变化,在监听器中定义了对应的事件响应的回调方法。比如,在服务器启动时,ServletContextListener 的 contextInitialized()方法 被调用,服务器将要关闭时,ServletContextListener 的 contextDestroyed()方法 被调用。了解了 Web 容器 中监听器的工作原理,下面看看服务器启动时 ContextLoaderListener 的调用完成了什么。在这个初始化回调中,创建了 ContextLoader,同时会利用创建出来的 ContextLoader 来完成 IoC 容器 的初始化。 -下面分析具体的根上下文的载人过程。在 ContextLoaderListener 中,实现的是 **ServletContextListener接口,这个接口里的函数会结合 Web容器 的生命周期被调用**。因为 ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化,会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于 ServletContext 的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext 被创建的时候,服务器关闭时,ServletContext 将被销毁的时候等。对应这些事件及 Web容器状态 的变化,在监听器中定义了对应的事件响应的回调方法。比如,在服务器启动时,ServletContextListener 的 contextInitialized()方法 被调用,服务器将要关闭时,ServletContextListener 的 contextDestroyed()方法 被调用。了解了 Web容器 中监听器的工作原理,下面看看服务器启动时 ContextLoaderListener 的调用完成了什么。在这个初始化回调中,创建了 ContextLoader,同时会利用创建出来的 ContextLoader 来完成 IoC容器 的初始化。 ```java public class ContextLoaderListener extends ContextLoader implements ServletContextListener { private ContextLoader contextLoader; - + /** * 启动 web应用 的根上下文 */ @@ -157,21 +169,21 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte public class ContextLoader { public static final String CONTEXT_CLASS_PARAM = "contextClass"; - + public static final String CONTEXT_ID_PARAM = "contextId"; - + public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses"; - + public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; - + public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector"; - + public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey"; - + private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; - + private static final Properties defaultStrategies; - + static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized @@ -184,7 +196,7 @@ public class ContextLoader { throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); } } - + /** * 由 ContextLoader 完成根上下文在 Web容器 中的创建。这个根上下文是作为 Web容器 中唯一的实例而存在的, * 根上下文创建成功后会被存到 Web容器 的 ServletContext 中,供需要时使用。存取这个根上下文的路径是由 @@ -203,7 +215,7 @@ public class ContextLoader { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); - + try { if (this.context == null) { // 这里创建在 ServletContext 中存储的根上下文 @@ -225,7 +237,7 @@ public class ContextLoader { // 将上面创建的 WebApplicationContext实例 存到 ServletContext 中,注意同时被存入的常量 // ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后的应用都会根据这个属性获取根上下文 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); - + ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; @@ -233,7 +245,7 @@ public class ContextLoader { else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } - + if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); @@ -242,7 +254,7 @@ public class ContextLoader { long elapsedTime = System.currentTimeMillis() - startTime; logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } - + return this.context; } catch (RuntimeException ex) { @@ -256,7 +268,7 @@ public class ContextLoader { throw err; } } - + /** * 创建 WebApplicationContext 的实例化对象 */ @@ -270,7 +282,7 @@ public class ContextLoader { // 直接实例化需要产生的 IoC容器 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } - + /** * 在确定使用何种 IoC容器 的过程中可以看到,应用可以在部署描述符中指定使用什么样的 IoC容器, * 这个指定操作是通过 CONTEXT_ CLASS_ PARAM参数 的设置完成的。如果没有指定特定的 IoC容器, @@ -301,7 +313,7 @@ public class ContextLoader { } } } - + protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // The application context id is still set to its original default value @@ -323,7 +335,7 @@ public class ContextLoader { } } } - + // 设置 ServletContext 及配置文件的位置参数 wac.setServletContext(sc); String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM); @@ -336,4 +348,5 @@ public class ContextLoader { } } ``` -这就是 IoC容器 在 Web容器 中的启动过程,与应用中启动 IoC容器 的方式相类似,所不同的是这里需要考虑 Web容器 的环境特点,比如各种参数的设置,IoC容器 与 Web容器 ServletContext 的结合等。在初始化这个上下文以后,该上下文会被存储到 SevletContext 中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动 SpringMVC 时,我们还会看到这个上下文被以后的 DispatcherServlet 在进行自己持有的上下文的初始化时,设置为 DispatcherServlet 自带的上下文的双亲上下文。 \ No newline at end of file + +这就是 IoC 容器 在 Web 容器 中的启动过程,与应用中启动 IoC 容器 的方式相类似,所不同的是这里需要考虑 Web 容器 的环境特点,比如各种参数的设置,IoC 容器 与 Web 容器 ServletContext 的结合等。在初始化这个上下文以后,该上下文会被存储到 SevletContext 中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动 SpringMVC 时,我们还会看到这个上下文被以后的 DispatcherServlet 在进行自己持有的上下文的初始化时,设置为 DispatcherServlet 自带的上下文的双亲上下文。 diff --git a/docs/Spring/SpringMVC/SpringMVC-CROS.md b/docs/Spring/SpringMVC/SpringMVC-CROS.md index 767a9a2b..a9c36673 100644 --- a/docs/Spring/SpringMVC/SpringMVC-CROS.md +++ b/docs/Spring/SpringMVC/SpringMVC-CROS.md @@ -1,8 +1,6 @@ # Spring-MVC 跨域 - - -## CrossOrigin注解 +## CrossOrigin 注解 - 通过注解设置跨域 demo 如下 @@ -28,11 +26,7 @@ public class JSONController { ``` - - - - -- 切入点: +- 切入点: - `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod` @@ -55,20 +49,20 @@ public class JSONController { assertUniqueMethodMapping(handlerMethod, mapping); // 设置值 this.mappingLookup.put(mapping, handlerMethod); - + // 获取url List directUrls = getDirectUrls(mapping); for (String url : directUrls) { // 设置 this.urlLookup.add(url, mapping); } - + String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } - + /** * 跨域设置 * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#initCorsConfiguration(Object, Method, RequestMappingInfo)} @@ -77,7 +71,7 @@ public class JSONController { if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } - + this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { @@ -85,14 +79,14 @@ public class JSONController { this.readWriteLock.writeLock().unlock(); } } - + ``` - 着重查看**`CorsConfiguration`**初始化方法 - `org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#initCorsConfiguration` -```JAVA +```java @Override protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { // 重新创建,为什么不作为参数传递: 还有别的实现方法 @@ -123,15 +117,11 @@ public class JSONController { } ``` - - 信息截图: -![image-20200123085741347](../../../images/springMVC/clazz/image-20200123085741347.png) - -![image-20200123085756168](../../../images/springMVC/clazz/image-20200123085756168.png) - +![image-20200123085741347](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123085741347.png) +![image-20200123085756168](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123085756168.png) ### updateCorsConfig @@ -174,30 +164,18 @@ public class JSONController { ``` - - - - 最终解析结果 -![image-20200123085946476](../../../images/springMVC/clazz/image-20200123085946476.png) - +![image-20200123085946476](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123085946476.png) - -- 解析完成后放入 `corsLookup`对象中 类:**`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping`** +- 解析完成后放入 `corsLookup`对象中 类:**`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping`** ```java if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } - - ``` - - - - - + ``` ## xml 配置方式 @@ -215,11 +193,11 @@ public class JSONController { ``` -- `mvc`标签解析类: `org.springframework.web.servlet.config.MvcNamespaceHandler`,这个类对Spring配置文件中的``标签做了解析设定,如这次我们的关注点**`CORS`** +- `mvc`标签解析类: `org.springframework.web.servlet.config.MvcNamespaceHandler`,这个类对 Spring 配置文件中的``标签做了解析设定,如这次我们的关注点**`CORS`** ```java public class MvcNamespaceHandler extends NamespaceHandlerSupport { - + /** * 初始化一些SpringMvc 的解析类 */ @@ -242,35 +220,31 @@ public class JSONController { registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser()); // tiles 处理器 registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser()); - + registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser()); - + registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser()); // 跨域处理 registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser()); } - - } - - ``` - - + } + ``` ### CorsBeanDefinitionParser #### 类图 -![image-20200123090442409](../../../images/springMVC/clazz/image-20200123090442409.png) +![image-20200123090442409](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123090442409.png) #### 解析 - 实现**BeanDefinitionParser** 接口的都有一个**parse**方法直接看方法. - - 通过查看我们可以知道最终目的获取xml标签中的属性,对 **CorsConfiguration**进行初始化,最后Spring中注册 + - 通过查看我们可以知道最终目的获取 xml 标签中的属性,对 **CorsConfiguration**进行初始化,最后 Spring 中注册 -```JAVA +```java public class CorsBeanDefinitionParser implements BeanDefinitionParser { @Override @@ -329,12 +303,10 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { - 属性截图 - ![image-20200123090851644](../../../images/springMVC/clazz/image-20200123090851644.png) + ![image-20200123090851644](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123090851644.png) - 可以看出这个是我们的第一个跨域配置的信息 - - - 注册方法 ```java @@ -360,20 +332,16 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { } return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME); } - - ``` - -- ![image-20200123091445694](../../../images/springMVC/clazz/image-20200123091445694.png) - - + ``` +- ![image-20200123091445694](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123091445694.png) ## CorsConfiguration - 跨域信息 -```JAVA +```java /** * 允许请求源 */ @@ -417,10 +385,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { private Long maxAge; ``` - - - - ## 处理请求 - 请求处理的一部分,前置后置都还有其他处理,这里只对跨域请求进行说明 @@ -441,16 +405,16 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } - + HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); - + if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { logger.debug("Mapped to " + executionChain.getHandler()); } - + // 判断是否为跨域请求 if (CorsUtils.isCorsRequest(request)) { CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request); @@ -459,10 +423,10 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } - + return executionChain; } - + ``` ### 判断是否跨域 @@ -491,8 +455,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { ``` - - ### 跨域拦截器创建 ```java @@ -512,10 +474,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { ``` - - - - ### 跨域拦截器 ```java @@ -547,19 +505,13 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { ``` - - - - ### DefaultCorsProcessor - 经过跨域拦截器 **`CorsInterceptor`**之后会调用 -![image-20200123093733129](../../../images/springMVC/clazz/image-20200123093733129.png) - +![image-20200123093733129](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123093733129.png) - -```JAVA +```java @Override @SuppressWarnings("resource") public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, @@ -599,8 +551,6 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { ``` - - ### 模拟请求 ``` @@ -610,6 +560,4 @@ Origin: localhost 变量截图 - - -![image-20200123093032179](../../../images/springMVC/clazz/image-20200123093032179.png) \ No newline at end of file +![image-20200123093032179](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123093032179.png) diff --git "a/docs/Spring/SpringMVC/SpringMVC\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" "b/docs/Spring/SpringMVC/SpringMVC\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" index 52e9c1a8..67835e28 100644 --- "a/docs/Spring/SpringMVC/SpringMVC\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" +++ "b/docs/Spring/SpringMVC/SpringMVC\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" @@ -1,27 +1,31 @@ -## 1 SpringMVC应用场景 -在使用 SpringMVC 时,除了要在 web.xml 中配置 ContextLoaderListener 外,还要对 DispatcherServlet 进行配置。作为一个 Servlet,这个 DispatcherServlet 实现的是 Sun 的 J2EE核心模式 中的 前端控制器模式(Front Controller), 作为一个前端控制器,所有的 Web请求 都需要通过它来进行转发、匹配、数据处理,然后转由页面进行展现,因此这个 DispatcerServlet 可以看成是 SpringMVC实现 中最为核心的部分。 +## 1 SpringMVC 应用场景 -在 SpringMVC 中,对于不同的 Web请求 的映射需求,SpringMVC 提供了不同的 HandlerMapping 的实现,可以让应用开发选取不同的映射策略。DispatcherSevlet 默认了 BeanNameUrlHandlerMapping 作为映射策略实现。除了映射策略可以定制外,SpringMVC 还提供了各种 Controller 的实现来供应用扩展和使用,以应对不同的控制器使用场景,这些 Controller控制器 需要实现 handleRequest()接口方法,并返回 ModelAndView对象。SpringMVC 还提供了各种视图实现,比如常用的 JSP视图。除此之外,SpringMVC 还提供了拦截器供应用使用,允许应用对 Web请求 进行拦截,以及前置处理和后置处理。 +在使用 SpringMVC 时,除了要在 web.xml 中配置 ContextLoaderListener 外,还要对 DispatcherServlet 进行配置。作为一个 Servlet,这个 DispatcherServlet 实现的是 Sun 的 J2EE 核心模式 中的 前端控制器模式(Front Controller), 作为一个前端控制器,所有的 Web 请求 都需要通过它来进行转发、匹配、数据处理,然后转由页面进行展现,因此这个 DispatcerServlet 可以看成是 SpringMVC 实现 中最为核心的部分。 -## 2 SpringMVC设计概览 -在完成对 ContextLoaderListener 的初始化以后,Web容器 开始初始化 DispatcherServlet,这个初始化的启动与在 web.xml 中对载入次序的定义有关。DispatcherServlet 会建立自己的上下文来持有SpringMVC 的 Bean对象,在建立这个自己持有的 IoC容器 时,会**从 ServletContext 中得到根上下文**作为 DispatcherServlet 持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到 ServletContext 中,供以后检索和使用。 +在 SpringMVC 中,对于不同的 Web 请求 的映射需求,SpringMVC 提供了不同的 HandlerMapping 的实现,可以让应用开发选取不同的映射策略。DispatcherSevlet 默认了 BeanNameUrlHandlerMapping 作为映射策略实现。除了映射策略可以定制外,SpringMVC 还提供了各种 Controller 的实现来供应用扩展和使用,以应对不同的控制器使用场景,这些 Controller 控制器 需要实现 handleRequest()接口方法,并返回 ModelAndView 对象。SpringMVC 还提供了各种视图实现,比如常用的 JSP 视图。除此之外,SpringMVC 还提供了拦截器供应用使用,允许应用对 Web 请求 进行拦截,以及前置处理和后置处理。 -为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet类 的继承关系。 +## 2 SpringMVC 设计概览 -![avatar](../../../images/springMVC/DispatcherServlet的继承关系.png) +在完成对 ContextLoaderListener 的初始化以后,Web 容器 开始初始化 DispatcherServlet,这个初始化的启动与在 web.xml 中对载入次序的定义有关。DispatcherServlet 会建立自己的上下文来持有 SpringMVC 的 Bean 对象,在建立这个自己持有的 IoC 容器 时,会**从 ServletContext 中得到根上下文**作为 DispatcherServlet 持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到 ServletContext 中,供以后检索和使用。 -DispatcherServlet 通过继承 FrameworkServlet 和 HttpServletBean 而继承了 HttpServlet,通过使用Servlet API 来对 HTTP请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC模块 与 Web容器 集成的处理前端。 +为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet 类 的继承关系。 -DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法,在这个方法里,DispatcherServlet 对 MVC模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP请求 进行响应,作为一个 Servlet,Web容器 会调用 Servlet 的doGet() 和 doPost()方法,在经过 FrameworkServlet 的 processRequest() 简单处理后,会调用 DispatcherServlet 的 doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/DispatcherServlet的继承关系.png) -![avatar](../../../images/springMVC/DispatcherServlet的处理过程.png) +DispatcherServlet 通过继承 FrameworkServlet 和 HttpServletBean 而继承了 HttpServlet,通过使用 Servlet API 来对 HTTP 请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC 模块 与 Web 容器 集成的处理前端。 + +DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法,在这个方法里,DispatcherServlet 对 MVC 模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP 请求 进行响应,作为一个 Servlet,Web 容器 会调用 Servlet 的 doGet() 和 doPost()方法,在经过 FrameworkServlet 的 processRequest() 简单处理后,会调用 DispatcherServlet 的 doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC 模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/DispatcherServlet的处理过程.png) + +## 3 DispatcherServlet 的启动和初始化 -## 3 DispatcherServlet的启动和初始化 前面大致描述了 SpringMVC 的工作流程,下面看一下 DispatcherServlet 的启动和初始化的代码设计及实现。 -作为 Servlet,DispatcherServlet 的启动与 Servlet 的启动过程是相联系的。在 Servlet 的初始化过程中,Servlet 的 init()方法 会被调用,以进行初始化,DispatcherServlet 的基类 HttpServletBean 实现了该方法。在初始化开始时,需要读取配置在 ServletContext 中的 Bean属性参数,这些属性参数设置在 web.xml 的 Web容器初始化参数 中。使用编程式的方式来设置这些 Bean属性,在这里可以看到对 PropertyValues 和 BeanWrapper 的使用。对于这些和依赖注人相关的类的使用,在分析 IoC容器 的初始化时,尤其是在依赖注入实现分析时,有过“亲密接触”。只是这里的依赖注人是与 Web容器 初始化相关的。 +作为 Servlet,DispatcherServlet 的启动与 Servlet 的启动过程是相联系的。在 Servlet 的初始化过程中,Servlet 的 init()方法 会被调用,以进行初始化,DispatcherServlet 的基类 HttpServletBean 实现了该方法。在初始化开始时,需要读取配置在 ServletContext 中的 Bean 属性参数,这些属性参数设置在 web.xml 的 Web 容器初始化参数 中。使用编程式的方式来设置这些 Bean 属性,在这里可以看到对 PropertyValues 和 BeanWrapper 的使用。对于这些和依赖注人相关的类的使用,在分析 IoC 容器 的初始化时,尤其是在依赖注入实现分析时,有过“亲密接触”。只是这里的依赖注人是与 Web 容器 初始化相关的。 + +接着会执行 DispatcherServlet 持有的 IoC 容器 的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个 DispatcherServlet 持有的上下文被设置为根上下文的子上下文。一个 Web 应用 中可以容纳多个 Servlet 存在;与此相对应,对于应用在 Web 容器 中的上下体系,一个根上下文可以作为许多 Servlet 上下文 的双亲上下文。了解 IoC 工作原理的读者知道,在向 IoC 容器 getBean() 时,IoC 容器 会首先向其双亲上下文去 getBean(),也就是说,在根上下文中定义的 Bean 是可以被各个 Servlet 持有的上下文得到和共享的。DispatcherServlet 持有的 上下文被建立起来以后,也需要和其他 IoC 容器 一样完成初始化,这个初始化也是通过 refresh()方法 来完成的。最后,DispatcherServlet 给这个自己持有的上下文命名,并把它设置到 Web 容器 的上下文中,这个名称和在 web.xml 中设置的 DispatcherServlet 的 Servlet 名称 有关,从而保证了这个上下文在 Web 环境上下文体系 中的唯一性。 -接着会执行 DispatcherServlet 持有的 IoC容器 的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个 DispatcherServlet 持有的上下文被设置为根上下文的子上下文。一个 Web应用 中可以容纳多个 Servlet 存在;与此相对应,对于应用在 Web容器 中的上下体系,一个根上下文可以作为许多 Servlet上下文 的双亲上下文。了解 IoC 工作原理的读者知道,在向 IoC容器 getBean() 时,IoC容器 会首先向其双亲上下文去 getBean(),也就是说,在根上下文中定义的 Bean 是可以被各个 Servlet 持有的上下文得到和共享的。DispatcherServlet 持有的 上下文被建立起来以后,也需要和其他 IoC容器 一样完成初始化,这个初始化也是通过 refresh()方法 来完成的。最后,DispatcherServlet 给这个自己持有的上下文命名,并把它设置到 Web容器 的上下文中,这个名称和在 web.xml 中设置的 DispatcherServlet 的 Servlet名称 有关,从而保证了这个上下文在 Web环境上下文体系 中的唯一性。 ```java public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { @@ -29,7 +33,7 @@ public abstract class HttpServletBean extends HttpServlet implements Environment if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } - + // 获取 Servlet 的初始化参数,对 bean属性 进行配置 try { PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); @@ -43,10 +47,10 @@ public abstract class HttpServletBean extends HttpServlet implements Environment logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); throw ex; } - + // 这个方法会调用子类的实现,进行具体的初始化 initServletBean(); - + if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } @@ -58,17 +62,17 @@ public abstract class FrameworkServlet extends HttpServletBean { /** 此 servlet 的 WebApplicationContext */ private WebApplicationContext webApplicationContext; - + /** 我们是否应该将当前 Servlet 的上下文 webApplicationContext 设为 ServletContext 的属性 */ private boolean publishContext = true; - + public FrameworkServlet() { } - + public FrameworkServlet(WebApplicationContext webApplicationContext) { this.webApplicationContext = webApplicationContext; } - + /** * 覆盖了父类 HttpServletBean 的空实现 */ @@ -79,7 +83,7 @@ public abstract class FrameworkServlet extends HttpServletBean { this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); } long startTime = System.currentTimeMillis(); - + try { // 初始化上下文 this.webApplicationContext = initWebApplicationContext(); @@ -93,14 +97,14 @@ public abstract class FrameworkServlet extends HttpServletBean { this.logger.error("Context initialization failed", ex); throw ex; } - + if (this.logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - startTime; this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + elapsedTime + " ms"); } } - + /** * 为这个 Servlet 初始化一个公共的 WebApplicationContext实例 */ @@ -109,7 +113,7 @@ public abstract class FrameworkServlet extends HttpServletBean { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; - + if (this.webApplicationContext != null) { // 可以在本对象被构造时注入一个 webApplicationContext实例 wac = this.webApplicationContext; @@ -118,7 +122,7 @@ public abstract class FrameworkServlet extends HttpServletBean { if (!cwac.isActive()) { // 上下文尚未刷新 -> 提供诸如设置父上下文、设置应用程序上下文id等服务 if (cwac.getParent() == null) { - // 上下文实例在没有显式父实例的情况下被注入 -> + // 上下文实例在没有显式父实例的情况下被注入 -> // 将根上下文(如果有的话;可以为空)设置为父上下文 cwac.setParent(rootContext); } @@ -127,7 +131,7 @@ public abstract class FrameworkServlet extends HttpServletBean { } } if (wac == null) { - // 在本对象被构造时没有注入上下文实例 -> + // 在本对象被构造时没有注入上下文实例 -> // 查看是否已在 servlet上下文 中注册了上下文实例。 // 如果存在一个,则假定父上下文(如果有的话)已经被设置, // 并且用户已经执行了任何初始化,例如设置上下文ID @@ -137,13 +141,13 @@ public abstract class FrameworkServlet extends HttpServletBean { // 没有为此 servlet 定义上下文实例 -> 创建本地实例 wac = createWebApplicationContext(rootContext); } - + if (!this.refreshEventReceived) { // 上下文不是支持刷新的 ConfigurableApplicationContext,或者 // 在构造时注入的上下文已经完成刷新 -> 在此处手动触发 onRefresh()方法 onRefresh(wac); } - + if (this.publishContext) { // 把当前建立的上下文保存到 ServletContext 中,使用的属性名是和 当前servlet名 相关的 String attrName = getServletContextAttributeName(); @@ -153,12 +157,14 @@ public abstract class FrameworkServlet extends HttpServletBean { "' as ServletContext attribute with name [" + attrName + "]"); } } - + return wac; } } ``` -至此,这个 MVC 的上下文就建立起来了,具体取得根上下文的过程在 WebApplicationContextUtils 中实现。这个根上下文是 ContextLoader 设置到 ServletContext 中去的,使用的属性是 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,ContextLoader 还对这个 IoC容器 的 Bean 配置文件进行了设置,默认的位置是在 /WEB-INF/applicationContext.xml文件 中。由于这个根上下文是 DispatcherServlet 建立的上下文的 双亲上下文,所以根上下文中管理的 Bean 也可以被 DispatcherServlet 的上下文使用。通过 getBean() 向 IoC容器 获取 Bean 时,容器会先到它的 双亲IoC容器 中获取。 + +至此,这个 MVC 的上下文就建立起来了,具体取得根上下文的过程在 WebApplicationContextUtils 中实现。这个根上下文是 ContextLoader 设置到 ServletContext 中去的,使用的属性是 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,ContextLoader 还对这个 IoC 容器 的 Bean 配置文件进行了设置,默认的位置是在 /WEB-INF/applicationContext.xml 文件 中。由于这个根上下文是 DispatcherServlet 建立的上下文的 双亲上下文,所以根上下文中管理的 Bean 也可以被 DispatcherServlet 的上下文使用。通过 getBean() 向 IoC 容器 获取 Bean 时,容器会先到它的 双亲 IoC 容器 中获取。 + ```java /** * 这是一个封装了很多静态方法的抽象工具类,所以只能调用其静态方法, @@ -173,7 +179,7 @@ public abstract class WebApplicationContextUtils { public static WebApplicationContext getWebApplicationContext(ServletContext sc) { return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); } - + /** * 查找此 web应用程序 的自定义 WebApplicationContext */ @@ -199,7 +205,9 @@ public abstract class WebApplicationContextUtils { } ) ``` -回到 FrameworkServlet 的实现中来看一下,DispatcherServlet 的上下文是怎样建立的,这个建立过程与前面建立根上下文的过程非常类似。建立 DispatcherServlet 的上下文,需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象,并为它设置参数。根据默认的配置,这个上下文对象也是 XmlWebApplicationContext对象,这个类型是在 DEFAULT_CONTEXT_CLASS参数 中设置好并允许 BeanUtilis 使用的。在实例化结束后,需要为这个上下文对象设置好一些基本的配置,这些配置包括它的双亲上下文、Bean配置文件 的位置等。完成这些配置以后,最后通过调用 IoC容器 的 refresh()方法 来完成 IoC容器 的最终初始化,这和前面我们对 IoC容器实现原理 的分析中所看到的 IoC容器初始化 的过程是一致的。 + +回到 FrameworkServlet 的实现中来看一下,DispatcherServlet 的上下文是怎样建立的,这个建立过程与前面建立根上下文的过程非常类似。建立 DispatcherServlet 的上下文,需要把根上下文作为参数传递给它。然后使用反射技术来实例化上下文对象,并为它设置参数。根据默认的配置,这个上下文对象也是 XmlWebApplicationContext 对象,这个类型是在 DEFAULT_CONTEXT_CLASS 参数 中设置好并允许 BeanUtilis 使用的。在实例化结束后,需要为这个上下文对象设置好一些基本的配置,这些配置包括它的双亲上下文、Bean 配置文件 的位置等。完成这些配置以后,最后通过调用 IoC 容器 的 refresh()方法 来完成 IoC 容器 的最终初始化,这和前面我们对 IoC 容器实现原理 的分析中所看到的 IoC 容器初始化 的过程是一致的。 + ```java public abstract class FrameworkServlet extends HttpServletBean { @@ -210,7 +218,7 @@ public abstract class FrameworkServlet extends HttpServletBean { protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) { return createWebApplicationContext((ApplicationContext) parent); } - + protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { // 默认为 XmlWebApplicationContext.class Class contextClass = getContextClass(); @@ -228,18 +236,18 @@ public abstract class FrameworkServlet extends HttpServletBean { // 实例化需要的上下文对象,并为其设置属性 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); - + wac.setEnvironment(getEnvironment()); // 这里设置的 双亲上下文,就是在 ContextLoader 中建立的根上下文 wac.setParent(parent); wac.setConfigLocation(getContextConfigLocation()); - + // 配置并且刷新 WebApplicationContext对象 configureAndRefreshWebApplicationContext(wac); - + return wac; } - + protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // 应用程序上下文id 仍设置为其原始默认值,如果该 id 不为空的话 @@ -267,34 +275,36 @@ public abstract class FrameworkServlet extends HttpServletBean { } } } - + // 设置其它配置信息 wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); - + // 在刷新上下文的任何情况下,都将会调用 此wac 的 env的 initPropertySources()方法。 // 在此处执行此方法,以确保在刷新上下文之前,servlet属性源 已准备就绪 ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig()); } - + postProcessWebApplicationContext(wac); - + applyInitializers(wac); - + // IoC容器 都是通过该方法完成 容器初始化的 wac.refresh(); } } ``` -这时候 DispatcherServlet 中的 IoC容器 已经建立起来了,这个 IoC容器 是 根上下文 的子容器。如果要查找一个由 DispatcherServlet 所持有的 IoC容器 来管理的 Bean,系统会首先到 根上下文 中去查找。如果查找不到,才会到 DispatcherServlet 所管理的 IoC容器 去进行查找,这是由 IoC容器 的 getBean() 的实现来决定的。通过一系列在 Web容器 中执行的动作,在这个上下文体系建立和初始化完毕的基础上,SpringMVC 就可以发挥其作用了。下面来分析一下 SpringMVC 的具体实现。 -在前面分析 DispatchServlet 的初始化过程中可以看到,DispatchServlet 持有一个以自己的 Servlet名称 命名的 IoC容器。这个 IoC容器 是一个 WebApplicationContext对象,这个 IoC容器 建立起来以后,意味着 DispatcherServlet 拥有自己的 Bean定义空间,这为使用各个独立的 XML文件 来配置 MVC 中各个 Bean 创造了条件。由于在初始化结束以后,与 Web容器 相关的加载过程实际上已经完成了,SpringMVC 的具体实现和普通的 Spring应用程序 的实现并没有太大的差别。 +这时候 DispatcherServlet 中的 IoC 容器 已经建立起来了,这个 IoC 容器 是 根上下文 的子容器。如果要查找一个由 DispatcherServlet 所持有的 IoC 容器 来管理的 Bean,系统会首先到 根上下文 中去查找。如果查找不到,才会到 DispatcherServlet 所管理的 IoC 容器 去进行查找,这是由 IoC 容器 的 getBean() 的实现来决定的。通过一系列在 Web 容器 中执行的动作,在这个上下文体系建立和初始化完毕的基础上,SpringMVC 就可以发挥其作用了。下面来分析一下 SpringMVC 的具体实现。 + +在前面分析 DispatchServlet 的初始化过程中可以看到,DispatchServlet 持有一个以自己的 Servlet 名称 命名的 IoC 容器。这个 IoC 容器 是一个 WebApplicationContext 对象,这个 IoC 容器 建立起来以后,意味着 DispatcherServlet 拥有自己的 Bean 定义空间,这为使用各个独立的 XML 文件 来配置 MVC 中各个 Bean 创造了条件。由于在初始化结束以后,与 Web 容器 相关的加载过程实际上已经完成了,SpringMVC 的具体实现和普通的 Spring 应用程序 的实现并没有太大的差别。 + +在 DispatcherServlet 的初始化过程中,以对 HandlerMapping 的初始化调用作为触发点,了解 SpringMVC 模块 初始化的方法调用关系。这个调用关系最初是由 HttpServletBean 的 init()方法 触发的,这个 HttpServletBean 是 HttpServlet 的子类。接着会在 HttpServletBean 的子类 FrameworkServlet 中对 IoC 容器 完成初始化,在这个初始化方法中,会调用 DispatcherServlet 的 initStrategies()方法,该方法包括对各种 MVC 框架 的实现元素,比如支持国际化的 LocalResolver、支持 request 映射的 HandlerMappings,以及视图生成的 ViewResolver 等。由该方法启动整个 SpringMVC 框架 的初始化。 -在 DispatcherServlet 的初始化过程中,以对 HandlerMapping 的初始化调用作为触发点,了解 SpringMVC模块 初始化的方法调用关系。这个调用关系最初是由 HttpServletBean 的 init()方法 触发的,这个 HttpServletBean 是 HttpServlet 的子类。接着会在 HttpServletBean 的子类 FrameworkServlet 中对 IoC容器 完成初始化,在这个初始化方法中,会调用 DispatcherServlet 的 initStrategies()方法,该方法包括对各种 MVC框架 的实现元素,比如支持国际化的 LocalResolver、支持 request 映射的 HandlerMappings,以及视图生成的 ViewResolver 等。由该方法启动整个 SpringMVC框架 的初始化。 ```java public class DispatcherServlet extends FrameworkServlet { /** @@ -318,14 +328,16 @@ public class DispatcherServlet extends FrameworkServlet { initRequestToViewNameTranslator(context); // 解析模板中的内容 initViewResolvers(context); - + initFlashMapManager(context); } } ``` -对于具体的初始化过程,根据上面的方法名称,很容易理解。以 HandlerMapping 为例来说明这个 initHandlerMappings()过程。这里的 Mapping关系 的作用是,为 HTTP请求 找到相应的 Controller控制器,从而利用这些 控制器Controller 去完成设计好的数据处理工作。 -HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 Web 这个特定的应用环境中,这些控制器是与具体的 HTTP请求 相对应的。在 HandlerMapping初始化 的过程中,把在 Bean配置文件 中配置好的 HandlerMapping 从 IoC容器 中取得。 +对于具体的初始化过程,根据上面的方法名称,很容易理解。以 HandlerMapping 为例来说明这个 initHandlerMappings()过程。这里的 Mapping 关系 的作用是,为 HTTP 请求 找到相应的 Controller 控制器,从而利用这些 控制器 Controller 去完成设计好的数据处理工作。 + +HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 Web 这个特定的应用环境中,这些控制器是与具体的 HTTP 请求 相对应的。在 HandlerMapping 初始化 的过程中,把在 Bean 配置文件 中配置好的 HandlerMapping 从 IoC 容器 中取得。 + ```java /** * 初始化此类使用的 HandlerMappings。 @@ -333,7 +345,7 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W */ private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; - + // 这个 detectAllHandlerMappings 默认为 true,表示从所有的 IoC容器 中获取所有的HandlerMappings if (this.detectAllHandlerMappings) { // 查找所有的 HandlerMapping,从 应用上下文context 及其双亲上下文中 @@ -357,7 +369,7 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W // 忽略,稍后将添加默认的 HandlerMapping } } - + // 如果找不到其他映射,请通过注册默认的 HandlerMapping 确保至少有一个 HandlerMapping if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); @@ -368,33 +380,37 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W } } ``` -经过以上读取过程,handlerMappings变量 就已经获取了在 Bean 中配置好的映射关系。其他的初始化过程和 handlerMappings 比较类似,都是直接从 IoC容器 中读入配置,所以这里的 MVC初始化过程 是建立在 IoC容器 已经初始化完成的基础上的。 -## 4 SpringMVC处理分发HTTP请求 -### 4.1 HandlerMapping的配置和设计原理 -前面分析了 DispatcherServlet 对 SpringMVC框架 的初始化过程,在此基础上,我们再进一步分析 HandlerMapping 的实现原理,看看这个 MVC框架 中比较关键的控制部分是如何实现的。 +经过以上读取过程,handlerMappings 变量 就已经获取了在 Bean 中配置好的映射关系。其他的初始化过程和 handlerMappings 比较类似,都是直接从 IoC 容器 中读入配置,所以这里的 MVC 初始化过程 是建立在 IoC 容器 已经初始化完成的基础上的。 + +## 4 SpringMVC 处理分发 HTTP 请求 + +### 4.1 HandlerMapping 的配置和设计原理 + +前面分析了 DispatcherServlet 对 SpringMVC 框架 的初始化过程,在此基础上,我们再进一步分析 HandlerMapping 的实现原理,看看这个 MVC 框架 中比较关键的控制部分是如何实现的。 + +在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP 请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL 请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。 -在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/HandlerMapping组件.png) -![avatar](../../../images/springMVC/HandlerMapping组件.png) +以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL 请求 和控制器的对应关系,使 SpringMVC +应用 可以根据 HTTP 请求 确定一个对应的 Controller。具体来说,这些映射关系是通过 HandlerMapping 接口 来封装的,在 HandlerMapping 接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP 请求 对应的 HandlerExecutionChain,在这个 HandlerExecutionChain 中,封装了具体的 Controller 对象。 -以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL请求 和控制器的对应关系,使 SpringMVC -应用 可以根据 HTTP请求 确定一个对应的 Controller。具体来说,这些映射关系是通过 HandlerMapping接口 来封装的,在 HandlerMapping接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP请求 对应的 HandlerExecutionChain,在这个 HandlerExecutionChain 中,封装了具体的 Controller对象。 ```java public interface HandlerMapping { String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping"; - + String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"; - + String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping"; - + String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables"; - + String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables"; - + String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"; - + /** * 返回的这个 HandlerExecutionChain 不但持有 handler本身,还包括了处理这个 HTTP请求 的拦截器链 */ @@ -402,25 +418,27 @@ public interface HandlerMapping { } ``` -这个 HandlerExecutionChain 的实现看起来比较简洁,它持有一个 拦截器链(HandlerInterceptor对象列表) 和一个 handler对象,这个 handler对象 实际上就是 HTTP请求 对应的 Controller,在持有这个 handler对象 的同时,还在 HandlerExecutionChain 中设置了一个拦截器链,通过这个拦截器链中的拦截器,可以为 handler对象 提供功能的增强。要完成这些工作,需要对拦截器链和 handler 都进行配置,这些配置都是在 HandlerExecutionChain 的初始化函数中完成的。为了维护这个拦截器链和 handler,HandlerExecutionChain 还提供了一系列与拦截器链维护相关的操作,比如,为拦截器链增加拦截器的 addInterceptor()方法。 + +这个 HandlerExecutionChain 的实现看起来比较简洁,它持有一个 拦截器链(HandlerInterceptor 对象列表) 和一个 handler 对象,这个 handler 对象 实际上就是 HTTP 请求 对应的 Controller,在持有这个 handler 对象 的同时,还在 HandlerExecutionChain 中设置了一个拦截器链,通过这个拦截器链中的拦截器,可以为 handler 对象 提供功能的增强。要完成这些工作,需要对拦截器链和 handler 都进行配置,这些配置都是在 HandlerExecutionChain 的初始化函数中完成的。为了维护这个拦截器链和 handler,HandlerExecutionChain 还提供了一系列与拦截器链维护相关的操作,比如,为拦截器链增加拦截器的 addInterceptor()方法。 + ```java public class HandlerExecutionChain { private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class); - + private final Object handler; - + private HandlerInterceptor[] interceptors; - + private List interceptorList; - + private int interceptorIndex = -1; - - + + public HandlerExecutionChain(Object handler) { this(handler, null); } - + public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) { if (handler instanceof HandlerExecutionChain) { HandlerExecutionChain originalChain = (HandlerExecutionChain) handler; @@ -434,11 +452,11 @@ public class HandlerExecutionChain { this.interceptors = interceptors; } } - + public Object getHandler() { return this.handler; } - + /** * 为拦截器链 添加拦截器 */ @@ -446,7 +464,7 @@ public class HandlerExecutionChain { initInterceptorList(); this.interceptorList.add(interceptor); } - + /** * 批量添加拦截器 */ @@ -456,7 +474,7 @@ public class HandlerExecutionChain { this.interceptorList.addAll(Arrays.asList(interceptors)); } } - + /** * 延迟初始化 interceptorList 和 interceptors集合 */ @@ -469,14 +487,14 @@ public class HandlerExecutionChain { this.interceptors = null; } } - + public HandlerInterceptor[] getInterceptors() { if (this.interceptors == null && this.interceptorList != null) { this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]); } return this.interceptors; } - + @Override public String toString() { if (this.handler == null) { @@ -494,9 +512,10 @@ public class HandlerExecutionChain { } } ``` -HandlerExecutionChain 中定义的 Handler 和 HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping,要做的就是根据 URL映射 的方式,注册 Handler 和 HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping 是 ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间映射关系的 handlerMap 的建立。 -![avatar](../../../images/springMVC/SimpleUrlHandlerMapping的继承关系.png) +HandlerExecutionChain 中定义的 Handler 和 HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping,要做的就是根据 URL 映射 的方式,注册 Handler 和 HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP 请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping 是 ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间映射关系的 handlerMap 的建立。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/SimpleUrlHandlerMapping的继承关系.png) ```java public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { @@ -506,7 +525,7 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { super.initApplicationContext(); registerHandlers(this.urlMap); } - + /** * 为相应的路径注册 URL映射 中指定的所有 handlers处理程序 */ @@ -534,7 +553,9 @@ public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { } } ``` -这个 SimpleUrlHandlerMapping 注册过程的完成,很大一部分需要它的基类来配合,这个基类就是 AbstractUrlHandlerMapping。在 AbstractUrlHandlerMapping 的处理过程中,如果使用 Bean 的名称作为映射,那么直接从容器中获取这个 HTTP映射 对应的 Bean,然后还要对不同的 URL配置 进行解析处理,比如在 HTTP请求 中配置成 “/” 和 通配符“/*” 的 URL,以及正常的 URL请求,完成这个解析处理过程以后,会把 URL 和 handler 作为键值对放到一个 handlerMap 中去。 + +这个 SimpleUrlHandlerMapping 注册过程的完成,很大一部分需要它的基类来配合,这个基类就是 AbstractUrlHandlerMapping。在 AbstractUrlHandlerMapping 的处理过程中,如果使用 Bean 的名称作为映射,那么直接从容器中获取这个 HTTP 映射 对应的 Bean,然后还要对不同的 URL 配置 进行解析处理,比如在 HTTP 请求 中配置成 “/” 和 通配符“/\*” 的 URL,以及正常的 URL 请求,完成这个解析处理过程以后,会把 URL 和 handler 作为键值对放到一个 handlerMap 中去。 + ```java public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { @@ -547,7 +568,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport registerHandler(urlPath, beanName); } } - + /** * 为给定的 URL路径 注册指定的 handler处理程序 */ @@ -555,7 +576,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport Assert.notNull(urlPath, "URL path must not be null"); Assert.notNull(handler, "Handler object must not be null"); Object resolvedHandler = handler; - + // 如果使用 bean名称 进行映射,就直接从 IoC容器 中获取该 bean名称 对应的 handler if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; @@ -563,7 +584,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport resolvedHandler = getApplicationContext().getBean(handlerName); } } - + Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { @@ -596,7 +617,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport } } } - + /** * 为此 handler映射 设置 根handler,即要为根路径("/")注册的 handler *

Default is {@code null}, indicating no root handler. @@ -604,27 +625,30 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport public void setRootHandler(Object rootHandler) { this.rootHandler = rootHandler; } - + public Object getRootHandler() { return this.rootHandler; } - + /** * 设置 此handler映射 的默认 handler。如果未找到特定映射,则将返回 此handler */ public void setDefaultHandler(Object defaultHandler) { this.defaultHandler = defaultHandler; } - + public Object getDefaultHandler() { return this.defaultHandler; } } ``` -这里的 handlerMap 是一个 HashMap,其中保存了 “URL请求” --> “Controller对象” 的映射关系,这个 handlerMap 是在 AbstractUrlHandlerMapping 中定义的( Map handlerMap = new LinkedHashMap() ),这个配置好 URL请求 和 handler映射数据 的 handlerMap,为 SpringMVC 响应 HTTP请求 准备好了基本的映射数据,根据这个 handlerMap 以及设置于其中的映射数据,可以方便地由 URL请求 得到它所对应的 handler。有了这些准备工作,SpringMVC 就可以等待 HTTP请求 的到来了。 -### 4.2 使用HandlerMapping完成请求的映射处理 -继续通过 SimpleUrlHandlerMapping的实现 来分析 HandlerMapping 的 接口方法getHandler(),该方法会根据初始化时得到的映射关系来生成 DispatcherServlet 需要的 HandlerExecutionChain,也就是说,这个 getHandler()方法 是实际使用 HandlerMapping 完成请求的映射处理的地方。在前面的 HandlerExecutionChain 的执行过程中,首先在 AbstractHandlerMapping 中启动 getHandler() 的调用。 +这里的 handlerMap 是一个 HashMap,其中保存了 “URL 请求” --> “Controller 对象” 的映射关系,这个 handlerMap 是在 AbstractUrlHandlerMapping 中定义的( Map handlerMap = new LinkedHashMap() ),这个配置好 URL 请求 和 handler 映射数据 的 handlerMap,为 SpringMVC 响应 HTTP 请求 准备好了基本的映射数据,根据这个 handlerMap 以及设置于其中的映射数据,可以方便地由 URL 请求 得到它所对应的 handler。有了这些准备工作,SpringMVC 就可以等待 HTTP 请求 的到来了。 + +### 4.2 使用 HandlerMapping 完成请求的映射处理 + +继续通过 SimpleUrlHandlerMapping 的实现 来分析 HandlerMapping 的 接口方法 getHandler(),该方法会根据初始化时得到的映射关系来生成 DispatcherServlet 需要的 HandlerExecutionChain,也就是说,这个 getHandler()方法 是实际使用 HandlerMapping 完成请求的映射处理的地方。在前面的 HandlerExecutionChain 的执行过程中,首先在 AbstractHandlerMapping 中启动 getHandler() 的调用。 + ```java public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered { /** @@ -653,7 +677,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport } } ``` -取得 handler 的具体过程在 getHandlerInternal()方法 中实现,这个方法接受 HTTP请求 作为参数,它的实现在 AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping 中,这个实现过程包括从 HTTP请求 中得到 URL,并根据 URL 到 urlMapping 中获得 handler。 + +取得 handler 的具体过程在 getHandlerInternal()方法 中实现,这个方法接受 HTTP 请求 作为参数,它的实现在 AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping 中,这个实现过程包括从 HTTP 请求 中得到 URL,并根据 URL 到 urlMapping 中获得 handler。 + ```java public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { /** @@ -695,7 +721,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { } return handler; } - + /** * 查找给定 URL路径 的 handler实例 */ @@ -736,7 +762,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath); - + // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map uriTemplateVariables = new LinkedHashMap(); @@ -757,20 +783,23 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { } } ``` -经过这一系列对 HTTP请求 进行解析和匹配 handler 的过程,得到了与请求对应的 handler处理器。在返回的 handler 中,已经完成了在 HandlerExecutionChain 中进行封装的工作,为 handler 对 HTTP请求 的响应做好了准备。 -### 4.3 DispatcherServlet对HTTP请求的分发处理 -DispatcherServlet 是 SpringMVC框架 中非常重要的一个类,不但建立了自己持有的 IoC容器,还肩负着请求分发处理的重任,对 HTTP请求 的处理是在 doService()方法 中完成的。DispatcherServlet 是 HttpServlet 的子类 ,与其他 HttpServlet 一样,可以通过 doService() 来响应 HTTP的请求。然而,依照 SpringMVC 的使用,业务逻辑的调用入口是在 handler 的 handler()方法 中实现的,这是连接 SpringMVC 和应用业务逻辑实现的地方。 +经过这一系列对 HTTP 请求 进行解析和匹配 handler 的过程,得到了与请求对应的 handler 处理器。在返回的 handler 中,已经完成了在 HandlerExecutionChain 中进行封装的工作,为 handler 对 HTTP 请求 的响应做好了准备。 + +### 4.3 DispatcherServlet 对 HTTP 请求的分发处理 + +DispatcherServlet 是 SpringMVC 框架 中非常重要的一个类,不但建立了自己持有的 IoC 容器,还肩负着请求分发处理的重任,对 HTTP 请求 的处理是在 doService()方法 中完成的。DispatcherServlet 是 HttpServlet 的子类 ,与其他 HttpServlet 一样,可以通过 doService() 来响应 HTTP 的请求。然而,依照 SpringMVC 的使用,业务逻辑的调用入口是在 handler 的 handler()方法 中实现的,这是连接 SpringMVC 和应用业务逻辑实现的地方。 + ```java public class DispatcherServlet extends FrameworkServlet { /** 此 DispatcherServlet 使用的 HandlerMapping对象列表 */ private List handlerMappings; - + /** 此 DispatcherServlet 使用的 HandlerAdapter对象列表 */ private List handlerAdapters; - - + + /** * 公开 DispatcherServlet 特定的请求属性,并将其委托给 doDispatch()方法 进行实际的分发 */ @@ -786,7 +815,7 @@ public class DispatcherServlet extends FrameworkServlet { + resumed + " processing " + request.getMethod() + " request for [" + requestUri + "]"); } - + // Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map attributesSnapshot = null; @@ -802,14 +831,14 @@ public class DispatcherServlet extends FrameworkServlet { } } } - + // 使框架对象对处理程序和视图对象可用 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource()); - + FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap != null) { @@ -818,7 +847,7 @@ public class DispatcherServlet extends FrameworkServlet { } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); - + try { doDispatch(request, response); } @@ -832,8 +861,8 @@ public class DispatcherServlet extends FrameworkServlet { } } } - - /** + + /** * 中央控制器,控制请求的转发 * 对请求的处理实际上是由 doDispatch() 来完成的,它是 DispatcherServlet 完成 HTTP请求 分发处理的主要方法, * 包括准备 ModelAndView,调用 getHandler()方法 来响应 HTTP请求,然后通过执行 Handler的处理 来获取请求的 @@ -844,19 +873,19 @@ public class DispatcherServlet extends FrameworkServlet { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; - + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); - + try { // 为视图准备好一个 ModelAndView,这个 ModelAndView 持有 handler处理请求的结果 ModelAndView mv = null; Exception dispatchException = null; - + try { // 1.检查是否是文件上传的请求 processedRequest = checkMultipart(request); multipartRequestParsed = processedRequest != request; - + // 2.取得处理当前请求的 Controller对象,这里也称为 hanlder处理器,这里并不是 // 直接返回 controller对象,而是返回的 HandlerExecutionChain请求处理器链对象, // 该对象封装了 handler 和 interceptors @@ -866,15 +895,15 @@ public class DispatcherServlet extends FrameworkServlet { noHandlerFound(processedRequest, response); return; } - - // 3. 获取处理 request 的处理器适配器 HandlerAdapter + + // 3. 获取处理 request 的处理器适配器 HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); - + // 获取 请求方式,如:GET, POST, PUT String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { - + long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { @@ -887,12 +916,12 @@ public class DispatcherServlet extends FrameworkServlet { return; } } - + // 4.拦截器的预处理方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } - + try { // 5.实际的处理器处理请求,返回结果视图对象 mv = ha.handle(processedRequest, response, @@ -903,7 +932,7 @@ public class DispatcherServlet extends FrameworkServlet { return; } } - + // 结果视图对象的处理 applyDefaultViewName(request, mv); // 6.拦截器的后处理方法 @@ -936,7 +965,7 @@ public class DispatcherServlet extends FrameworkServlet { } } } - + /** * 返回此请求的 HandlerExecutionChain,按顺序尝试所有的 HandlerMapping */ @@ -945,7 +974,7 @@ public class DispatcherServlet extends FrameworkServlet { throws Exception { return getHandler(request); } - + /** * 返回此请求的 HandlerExecutionChain */ @@ -965,7 +994,7 @@ public class DispatcherServlet extends FrameworkServlet { } return null; } - + /** * 返回 此处理程序对象handler 的 HandlerAdapter */ @@ -984,7 +1013,9 @@ public class DispatcherServlet extends FrameworkServlet { } } ``` -通过判断,可以知道这个 handler 是不是 Controller接口 的实现,比如可以通过具体 HandlerAdapter 的实现来了解这个适配过程。以 SimpleControllerHandlerAdapter的实现 为例来了解这个判断是怎样起作用的。 + +通过判断,可以知道这个 handler 是不是 Controller 接口 的实现,比如可以通过具体 HandlerAdapter 的实现来了解这个适配过程。以 SimpleControllerHandlerAdapter 的实现 为例来了解这个判断是怎样起作用的。 + ```java public class SimpleControllerHandlerAdapter implements HandlerAdapter { @@ -992,20 +1023,21 @@ public class SimpleControllerHandlerAdapter implements HandlerAdapter { public boolean supports(Object handler) { return (handler instanceof Controller); } - + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - + return ((Controller) handler).handleRequest(request, response); } - + public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; } - + } ``` -经过上面一系列的处理,得到了 handler对象,接着就可以开始调用 handler对象 中的 HTTP响应动作了。在 handler 中封装了应用业务逻辑,由这些逻辑对 HTTP请求 进行相应的处理,生成需要的数据,并把这些数据封装到 ModelAndView对象 中去,这个 ModelAndView 的数据封装是 SpringMVC框架 的要求。对 handler 来说, 这些都是通过调用 handler()方法 中的 handleRequest()方法 来触发完成的。在得到 ModelAndView对象 以后,这个 ModelAndView对象 会被交给 MVC模式 中的视图类,由视图类对 ModelAndView对象 中的数据进行呈现。 + +经过上面一系列的处理,得到了 handler 对象,接着就可以开始调用 handler 对象 中的 HTTP 响应动作了。在 handler 中封装了应用业务逻辑,由这些逻辑对 HTTP 请求 进行相应的处理,生成需要的数据,并把这些数据封装到 ModelAndView 对象 中去,这个 ModelAndView 的数据封装是 SpringMVC 框架 的要求。对 handler 来说, 这些都是通过调用 handler()方法 中的 handleRequest()方法 来触发完成的。在得到 ModelAndView 对象 以后,这个 ModelAndView 对象 会被交给 MVC 模式 中的视图类,由视图类对 ModelAndView 对象 中的数据进行呈现。 diff --git "a/docs/Spring/SpringMVC/\346\270\251\344\271\240\344\270\200\344\270\213servlet.md" "b/docs/Spring/SpringMVC/\346\270\251\344\271\240\344\270\200\344\270\213servlet.md" index 1faf209d..b3787c53 100644 --- "a/docs/Spring/SpringMVC/\346\270\251\344\271\240\344\270\200\344\270\213servlet.md" +++ "b/docs/Spring/SpringMVC/\346\270\251\344\271\240\344\270\200\344\270\213servlet.md" @@ -1 +1 @@ -努力编写中... +努力编写中... diff --git "a/docs/Spring/SpringTransaction/Spring\344\270\216\344\272\213\345\212\241\345\244\204\347\220\206.md" "b/docs/Spring/SpringTransaction/Spring\344\270\216\344\272\213\345\212\241\345\244\204\347\220\206.md" index bff12ac6..9614dc48 100644 --- "a/docs/Spring/SpringTransaction/Spring\344\270\216\344\272\213\345\212\241\345\244\204\347\220\206.md" +++ "b/docs/Spring/SpringTransaction/Spring\344\270\216\344\272\213\345\212\241\345\244\204\347\220\206.md" @@ -1,18 +1,21 @@ -JavaEE应用 中的事务处理是一个重要并且涉及范围很广的领域。事务管理的实现往往涉及并发和数据一致性方面的问题。作为应用平台的 Spring,具有在多种环境中配置和使用事务处理的能力,也就是说通过使用 Spring 的事务组件,可以把事务处理的工作统一起来,并为事务处理提供通用的支持。 +JavaEE 应用中的事务处理是一个重要并且涉及范围很广的领域。事务管理的实现往往涉及并发和数据一致性方面的问题。作为应用平台的 Spring,具有在多种环境中配置和使用事务处理的能力,也就是说通过使用 Spring 的事务组件,可以把事务处理的工作统一起来,并为事务处理提供通用的支持。 -在涉及单个数据库局部事务的事务处理中,事务的最终实现和数据库的支持是紧密相关的。对局部数据库事务来说,一个事务处理的操作单元往往对应着一系列的数据库操作。数据库产品对这些数据库的 SQL操作 已经提供了原子性的支持,对 SQL操作 而言,它的操作结果有两种: 一种是提交成功,数据库操作成功;另一种是回滚,数据库操作不成功,恢复到操作以前的状态。 +在涉及单个数据库局部事务的事务处理中,事务的最终实现和数据库的支持是紧密相关的。对局部数据库事务来说,一个事务处理的操作单元往往对应着一系列的数据库操作。数据库产品对这些数据库的 SQL 操作 已经提供了原子性的支持,对 SQL 操作 而言,它的操作结果有两种: 一种是提交成功,数据库操作成功;另一种是回滚,数据库操作不成功,恢复到操作以前的状态。 -在事务处理中,事务处理单元的设计与相应的业务逻辑设计有很紧密的联系。在很多情况下,一个业务逻辑处理不会只有一个单独的数据库操作,而是有一组数据库操作。在这个处理过程中,首先涉及的是事务处理单元划分的问题,Spring 借助 IoC容器 的强大配置能力,为应用提供了声明式的事务划分方式,这种声明式的事务处理,为 Spring应用 使用事务管理提供了统一的方式。有了 Spring事务管理 的支持,只需要通过一些简单的配置,应用就能完成复杂的事务处理工作,从而为用户使用事务处理提供很大的方便。 -## 1 Spring事务处理 的设计概览 -Spring事务处理模块 的类层次结构如下图所示。 +在事务处理中,事务处理单元的设计与相应的业务逻辑设计有很紧密的联系。在很多情况下,一个业务逻辑处理不会只有一个单独的数据库操作,而是有一组数据库操作。在这个处理过程中,首先涉及的是事务处理单元划分的问题,Spring 借助 IoC 容器 的强大配置能力,为应用提供了声明式的事务划分方式,这种声明式的事务处理,为 Spring 应用 使用事务管理提供了统一的方式。有了 Spring 事务管理 的支持,只需要通过一些简单的配置,应用就能完成复杂的事务处理工作,从而为用户使用事务处理提供很大的方便。 -![avatar](../../../images/springTransaction/Spring事务处理模块类层次结构.png) +## 1 Spring 事务处理的设计概览 -从上图可以看到,Spring事务处理模块 是通过 AOP功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP功能,通过这个 TransactionProxyFactoryBean 可以生成 Proxy代理对象,在这个代理对象中,通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。 +Spring 事务处理模块的类层次结构如下图所示。 -对于具体的事务处理实现,比如事务的生成、提交、回滚、挂起等,由于不同的底层数据库有不同的支持方式,因此,在 Spring事务处理中,对主要的事务实现做了一个抽象和适配。适配的具体事务处理器包括:对 DataSource数据源 的事务处理支持,对 Hibernate数据源 的事务处理支持,对 JDO数据源 的事务处理支持,对 JPA 和 JTA 等数据源的事务处理支持等。这一系列的事务处理支持,都是通过设计 PlatformTransactionManager、AbstractPlatformTransactionManager 以及一系列具体事务处理器来实现的,而 PlatformTransactionManager 又实现了 TransactionInterceptor接口,通过这样一个接口实现设计,就把这一系列的事务处理的实现与前面提到的 TransactionProxyFactoryBean 结合起来,从而形成了一个 Spring声明式事务处理 的设计体系。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/Spring事务处理模块类层次结构.png) -## 2 Spring事务处理 的应用场景 -Spring 作为应用平台或框架的设计出发点是支持 POJO的开发,这点在实现事务处理的时候也不例外。在 Spring 中,它既支持编程式事务管理方式,又支持声明式事务处理方式,在使用 Spring 处理事务的时候,声明式事务处理通常比编程式事务管理更方便些。 +从上图可以看到,Spring 事务处理模块 是通过 AOP 功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring 事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP 功能,通过这个 TransactionProxyFactoryBean 可以生成 Proxy 代理对象,在这个代理对象中,通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。 + +对于具体的事务处理实现,比如事务的生成、提交、回滚、挂起等,由于不同的底层数据库有不同的支持方式,因此,在 Spring 事务处理中,对主要的事务实现做了一个抽象和适配。适配的具体事务处理器包括:对 DataSource 数据源 的事务处理支持,对 Hibernate 数据源 的事务处理支持,对 JDO 数据源 的事务处理支持,对 JPA 和 JTA 等数据源的事务处理支持等。这一系列的事务处理支持,都是通过设计 PlatformTransactionManager、AbstractPlatformTransactionManager 以及一系列具体事务处理器来实现的,而 PlatformTransactionManager 又实现了 TransactionInterceptor 接口,通过这样一个接口实现设计,就把这一系列的事务处理的实现与前面提到的 TransactionProxyFactoryBean 结合起来,从而形成了一个 Spring 声明式事务处理 的设计体系。 + +## 2 Spring 事务处理 的应用场景 + +Spring 作为应用平台或框架的设计出发点是支持 POJO 的开发,这点在实现事务处理的时候也不例外。在 Spring 中,它既支持编程式事务管理方式,又支持声明式事务处理方式,在使用 Spring 处理事务的时候,声明式事务处理通常比编程式事务管理更方便些。 Spring 对应用的支持,一方面,通过声明式事务处理,将事务处理的过程和业务代码分离出来。这种声明方式实际上是通过 AOP 的方式来完成的。显然,Spring 已经把那些通用的事务处理过程抽象出来,并通过 AOP 的方式进行封装,然后用声明式的使用方式交付给客户使用。这样,应用程序可以更简单地管理事务,并且只需要关注事务的处理策略。另一方面,应用在选择数据源时可能会采取不同的方案,当以 Spring 作为平台时,Spring 在应用和具体的数据源之间,搭建一个中间平台,通过这个中间平台,解耦应用和具体数据源之间的绑定,并且,Spring 为常用的数据源的事务处理支持提供了一系列的 TransactionManager。这些 Spring 封装好的 TransactionManager 为应用提供了很大的方便,因为在这些具体事务处理过程中,已经根据底层的实现,封装好了事务处理的设置以及与特定数据源相关的特定事务处理过程,这样应用在使用不同的数据源时,可以做到事务处理的即开即用。这样的另一个好处是,如果应用有其他的数据源事务处理需要, Spring 也提供了一种一致的方式。这种 有机的事务过程抽象 和 具体的事务处理 相结合的设计,是我们在日常的开发中非常需要模仿学习的。 diff --git "a/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" "b/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" index 0fe6ec18..e7036fae 100644 --- "a/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" +++ "b/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" @@ -1,4 +1,5 @@ ## 1 事务处理的编程式使用 + ```java TransactionDefinition td = new DefaultTransactionDefinition(); // transactionManager 是某一个具体的 PlatformTransactionManager实现类 的对象 @@ -12,16 +13,18 @@ } transactionManager.commit(ts); ``` -在使用编程式事务处理的过程中,利用 DefaultTransactionDefinition对象 来持有事务处理属性。同时,在创建事务的过程中得到一个 TransactionStatus对象,然后通过直接调用 transactionManager对象 的 commit() 和 rollback()方法 来完成事务处理。在这个编程式使用事务管理的过程中,没有看到框架特性的使用,非常简单和直接,很好地说明了事务管理的基本实现过程,以及在 Spring事务处理实现 中涉及一些主要的类,比如 TransationStatus、TransactionManager 等,对这些类的使用与声明式事务处理的最终实现是一样的。 -与编程式使用事务管理不同,在使用声明式事务处理的时候,因为涉及 Spring框架 对事务处理的统一管理,以及对并发事务和事务属性的处理,所以采用的是一个比较复杂的处理过程,但复杂归复杂,这个过程对使用声明式事务处理的应用来说,基本上是不可见的,而是由 Spring框架 来完成的。有了这些背景铺垫和前面对 AOP封装事务处理 的了解,下面来看看 Spring 是如何提供声明式事务处理的,Spring 在这个相对较为复杂的过程中封装了什么。这层封装包括在事务处理中事务的创建、提交和回滚等比较核心的操作。 +在使用编程式事务处理的过程中,利用 DefaultTransactionDefinition 对象 来持有事务处理属性。同时,在创建事务的过程中得到一个 TransactionStatus 对象,然后通过直接调用 transactionManager 对象 的 commit() 和 rollback()方法 来完成事务处理。在这个编程式使用事务管理的过程中,没有看到框架特性的使用,非常简单和直接,很好地说明了事务管理的基本实现过程,以及在 Spring 事务处理实现 中涉及一些主要的类,比如 TransationStatus、TransactionManager 等,对这些类的使用与声明式事务处理的最终实现是一样的。 + +与编程式使用事务管理不同,在使用声明式事务处理的时候,因为涉及 Spring 框架 对事务处理的统一管理,以及对并发事务和事务属性的处理,所以采用的是一个比较复杂的处理过程,但复杂归复杂,这个过程对使用声明式事务处理的应用来说,基本上是不可见的,而是由 Spring 框架 来完成的。有了这些背景铺垫和前面对 AOP 封装事务处理 的了解,下面来看看 Spring 是如何提供声明式事务处理的,Spring 在这个相对较为复杂的过程中封装了什么。这层封装包括在事务处理中事务的创建、提交和回滚等比较核心的操作。 ## 2 事务的创建 -作为声明式事务处理实现的起始点,需要注意 TransactionInterceptor拦截器 的 invoke()回调 中使用的 createTransactionIfNecessary()方法,这个方法是在 TransactionInterceptor 的基类 TransactionAspectSupport 中实现的。为了了解这个方法的实现,先分析一下 TransactionInterceptor 的基类实现 TransactionAspectSupport,并以这个方法的实现为入口,了解 Spring 是如何根据当前的事务状态和事务属性配置完成事务创建的。 -这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction事务对象 的过程,在 AbstractTransactionManager实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus,并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定,从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。 +作为声明式事务处理实现的起始点,需要注意 TransactionInterceptor 拦截器 的 invoke()回调 中使用的 createTransactionIfNecessary()方法,这个方法是在 TransactionInterceptor 的基类 TransactionAspectSupport 中实现的。为了了解这个方法的实现,先分析一下 TransactionInterceptor 的基类实现 TransactionAspectSupport,并以这个方法的实现为入口,了解 Spring 是如何根据当前的事务状态和事务属性配置完成事务创建的。 + +这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction 事务对象 的过程,在 AbstractTransactionManager 实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus,并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定,从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。 -![avatar](images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png) ```java public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { @@ -102,9 +105,11 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } ``` + 在以上的处理过程之后,可以看到,具体的事务创建可以交给事务处理器来完成。在事务的创建过程中,已经为事务的管理做好了准备,包括记录事务处理状态,以及绑定事务信息和线程等。下面到事务处理器中去了解一下更底层的事务创建过程。 -createTransactionIfNecessary()方法 通过调用 PlatformTransactionManager 的 getTransaction()方法,生成一个 TransactionStatus对象,封装了底层事务对象的创建。可以看到,AbstractPlatformTransactionManager 提供了创建事务的模板,这个模板会被具体的事务处理器所使用。从下面的代码中可以看到,AbstractPlatformTransactionManager 会根据事务属性配置和当前进程绑定的事务信息,对事务是否需要创建,怎样创建 进行一些通用的处理,然后把事务创建的底层工作交给具体的事务处理器完成。尽管具体的事务处理器完成事务创建的过程各不相同,但是不同的事务处理器对事务属性和当前进程事务信息的处理都是相同的,在 **AbstractPlatformTransactionManager** 中完成了该实现,这个实现过程是 Spring 提供统一事务处理的一个重要部分。 +createTransactionIfNecessary()方法 通过调用 PlatformTransactionManager 的 getTransaction()方法,生成一个 TransactionStatus 对象,封装了底层事务对象的创建。可以看到,AbstractPlatformTransactionManager 提供了创建事务的模板,这个模板会被具体的事务处理器所使用。从下面的代码中可以看到,AbstractPlatformTransactionManager 会根据事务属性配置和当前进程绑定的事务信息,对事务是否需要创建,怎样创建 进行一些通用的处理,然后把事务创建的底层工作交给具体的事务处理器完成。尽管具体的事务处理器完成事务创建的过程各不相同,但是不同的事务处理器对事务属性和当前进程事务信息的处理都是相同的,在 **AbstractPlatformTransactionManager** 中完成了该实现,这个实现过程是 Spring 提供统一事务处理的一个重要部分。 + ```java public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { @@ -116,27 +121,27 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran // doGetTransaction() 是抽象方法,Transaction对象 的取得由具体的事务管理器 // 实现,比如:DataSourceTransactionManager Object transaction = doGetTransaction(); - + // 缓存 debug标志,以避免重复检查 boolean debugEnabled = logger.isDebugEnabled(); - + if (definition == null) { // 如果没有给出事务定义,则使用默认值 definition = new DefaultTransactionDefinition(); } - + // 检查当前线程是否已经存在事务,如果已经存在事务,则根据事务属性中定义的 事务传播属性配置 // 来处理事务的产生 if (isExistingTransaction(transaction)) { // 对当前线程中已经有事务存在的情况进行处理,结果封装在 TransactionStatus 中 return handleExistingTransaction(definition, transaction, debugEnabled); } - + // 检查 definition 中 timeout属性 的设置 if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); } - + // 当前没有事务存在,这时需要根据事务属性设置来创建事务 // 这里可以看到对事务传播属性配置的处理,比如:MANDATORY、REQUIRED、REQUIRES_NEW、NESTED等 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { @@ -146,7 +151,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { - + SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); @@ -175,11 +180,13 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); } } -} +} ``` -从上面的代码中可以看到,AbstractTransactionManager 提供的创建事务的实现模板,在这个模板的基础上,具体的事务处理器需要定义自己的实现来完成底层的事务创建工作,比如需要实现 isExistingTransaction() 和 doBegin()方法。关于这些由具体事务处理器实现的方法会在下面结合具体的事务处理器实现,如:DataSourceTransactionManager、HibernateTransactionManager进行分析。 -事务创建的结果是生成一个 TransactionStatus对象, 通过这个对象来保存事务处理需要的基本信息,这个对象与前面提到过的 TransactionInfo对象 联系在一起, TransactionStatus 是 TransactionInfo 的一个属性,然后会把 TransactionInfo 保存在 ThreadLocal对象 里,这样当前线程可以通过 ThreadLocal对象 取得 TransactionInfo,以及与这个事务对应的 TransactionStatus对象,从而把事务的处理信息与调用事务方法的当前线程绑定起来。在 AbstractPlatformTransactionManager 创建事务的过程中,可以看到 TransactionStatus 的创建过程。 +从上面的代码中可以看到,AbstractTransactionManager 提供的创建事务的实现模板,在这个模板的基础上,具体的事务处理器需要定义自己的实现来完成底层的事务创建工作,比如需要实现 isExistingTransaction() 和 doBegin()方法。关于这些由具体事务处理器实现的方法会在下面结合具体的事务处理器实现,如:DataSourceTransactionManager、HibernateTransactionManager 进行分析。 + +事务创建的结果是生成一个 TransactionStatus 对象, 通过这个对象来保存事务处理需要的基本信息,这个对象与前面提到过的 TransactionInfo 对象 联系在一起, TransactionStatus 是 TransactionInfo 的一个属性,然后会把 TransactionInfo 保存在 ThreadLocal 对象 里,这样当前线程可以通过 ThreadLocal 对象 取得 TransactionInfo,以及与这个事务对应的 TransactionStatus 对象,从而把事务的处理信息与调用事务方法的当前线程绑定起来。在 AbstractPlatformTransactionManager 创建事务的过程中,可以看到 TransactionStatus 的创建过程。 + ```java public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { @@ -189,7 +196,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran protected DefaultTransactionStatus newTransactionStatus( TransactionDefinition definition, Object transaction, boolean newTransaction, boolean newSynchronization, boolean debug, Object suspendedResources) { - + // 这里判断是不是新事务,如果是新事务,需要把事务属性存放到当前线程中 // TransactionSynchronizationManager 维护了一系列的 ThreadLocal变量 // 来保持事务属性,比如,并发事务隔离级别,是否有活跃的事务等 @@ -202,9 +209,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } } ``` -新事务的创建是比较好理解的,这里需要根据事务属性配置进行创建。所谓创建,首先是把创建工作交给具体的事务处理器来完成,比如 DataSourceTransactionManager,把创建的事务对象在 TransactionStatus 中保存下来,然后将其他的事务属性和 线程ThreadLocal变量 进行绑定。 -相对于创建全新事务的另一种情况是:在创建当前事务时,线程中已经有事务存在了。这种情况同样需要处理,在声明式事务处理中,在当前线程调用事务方法的时候,就会考虑事务的创建处理,这个处理在方法 handleExistingTransaction() 中完成。这里对现有事务的处理,会涉及事务传播属性的具体处理,比如 PROPAGATION_NOT_SUPPORTED、PROPAGATION_ REQUIRES_ NEW等。 +新事务的创建是比较好理解的,这里需要根据事务属性配置进行创建。所谓创建,首先是把创建工作交给具体的事务处理器来完成,比如 DataSourceTransactionManager,把创建的事务对象在 TransactionStatus 中保存下来,然后将其他的事务属性和 线程 ThreadLocal 变量 进行绑定。 + +相对于创建全新事务的另一种情况是:在创建当前事务时,线程中已经有事务存在了。这种情况同样需要处理,在声明式事务处理中,在当前线程调用事务方法的时候,就会考虑事务的创建处理,这个处理在方法 handleExistingTransaction() 中完成。这里对现有事务的处理,会涉及事务传播属性的具体处理,比如 PROPAGATION*NOT_SUPPORTED、PROPAGATION* REQUIRES\_ NEW 等。 + ```java public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { @@ -214,13 +223,13 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { - + // PROPAGATION_NEVER 表示 以非事务方式执行,如果当前存在事务,则抛出异常 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } - + // PROPAGATION_NOT_SUPPORTED 表示 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) { if (debugEnabled) { @@ -234,7 +243,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran return prepareTransactionStatus( definition, null, false, newSynchronization, debugEnabled, suspendedResources); } - + // PROPAGATION_REQUIRES_NEW 表示 新建事务,如果当前存在事务,把当前事务挂起 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { if (debugEnabled) { @@ -260,7 +269,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran throw beginErr; } } - + // PROPAGATION_NESTED 表示 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务, // 则执行与 PROPAGATION_REQUIRED 类似的操作 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { @@ -288,7 +297,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran return status; } } - + if (debugEnabled) { logger.debug("Participating in existing transaction"); } @@ -321,7 +330,9 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran ``` ## 3 事务的挂起 + 事务的挂起牵涉线程与事务处理信息的保存,下面看一下事务挂起的实现。 + ```java public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { @@ -371,10 +382,13 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } } ``` + 基于以上内容,就可以完成声明式事务处理的创建了。声明式事务处理能使事务处理应用的开发变得简单,但是简单的背后,蕴含着平台付出的许多努力。 ## 4 事务的提交 -下面来看看事务提交是如何实现的。有了前面的对事务创建的分析,下面来分析一下在 Spring 中,声明式事务处理的事务提交是如何完成的。事务提交的调用入口是 TransactionInteceptor 的 invoke()方法,事务提交的具体实现则在其基类 TransactionAspectSupport 的 commitTransactionAfterReturning(TransactionInfo txInfo)方法 中,其中的参数 txInfo 是创建事务时生成的。同时,Spring 的事务管理框架生成的 TransactionStatus对象 就包含在 TransactionInfo对象 中。这个 commitTransactionAfterReturning()方法 在 TransactionInteceptor 的实现部分是比较简单的,它通过直接调用事务处理器来完成事务提交。 + +下面来看看事务提交是如何实现的。有了前面的对事务创建的分析,下面来分析一下在 Spring 中,声明式事务处理的事务提交是如何完成的。事务提交的调用入口是 TransactionInteceptor 的 invoke()方法,事务提交的具体实现则在其基类 TransactionAspectSupport 的 commitTransactionAfterReturning(TransactionInfo txInfo)方法 中,其中的参数 txInfo 是创建事务时生成的。同时,Spring 的事务管理框架生成的 TransactionStatus 对象 就包含在 TransactionInfo 对象 中。这个 commitTransactionAfterReturning()方法 在 TransactionInteceptor 的实现部分是比较简单的,它通过直接调用事务处理器来完成事务提交。 + ```java public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { @@ -391,7 +405,9 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } ``` + 与前面分析事务的创建过程一样,我们需要到事务管理器中去看看事务是如何提交的。同样,在 AbstractPlatformTransactionManager 中也有一个模板方法支持具体的事务管理器对事务提交的实现,这个模板方法的实现与前面我们看到的 getTransaction() 很像。 + ```java public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { @@ -463,7 +479,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran doRollbackOnCommitException(status, err); throw err; } - + // 触发器 afterCommit()回调,其中抛出的异常已传播到调用方,但该事务仍被视为已提交 try { triggerAfterCommit(status); @@ -471,7 +487,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran finally { triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); } - + } finally { cleanupAfterCompletion(status); @@ -479,9 +495,11 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } } ``` + 可以看到,事务提交的准备都是由具体的事务处理器来实现的。当然,对这些事务提交的处理,需要通过对 TransactionStatus 保存的事务处理的相关状态进行判断。提交过程涉及 AbstractPlatformTransactionManager 中的 doCommit() 和 prepareForCommit()方法,它们都是抽象方法,都在具体的事务处理器中完成实现,在下面对具体事务处理器的实现原理的分析中,可以看到对这些实现方法的具体分析。 ## 5 事务的回滚 + ```java public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable { @@ -541,6 +559,7 @@ public abstract class AbstractPlatformTransactionManager implements PlatformTran } } ``` + 以上对事务的创建、提交和回滚的实现原理进行了分析,这些过程的实现都比较复杂,一方面 这些处理会涉及很多事务属性的处理;另一方面 会涉及事务处理过程中状态的设置,同时在事务处理的过程中,有许多处理也需要根据相应的状态来完成。这样看来,在实现事务处理的基本过程中就会产生许多事务处理的操作分支。 -但总的来说,在事务执行的实现过程中,作为执行控制的 TransactionInfo对象 和 TransactionStatus对象 特别值得我们注意,比如它们如何与线程进行绑定,如何记录事务的执行情况等。如果大家在配置事务属性时有什么疑惑,不妨直接看看这些事务属性的处理过程,通过对这些实现原理的了解,可以极大地提高对这些事务处理属性使用的理解程度。 \ No newline at end of file +但总的来说,在事务执行的实现过程中,作为执行控制的 TransactionInfo 对象 和 TransactionStatus 对象 特别值得我们注意,比如它们如何与线程进行绑定,如何记录事务的执行情况等。如果大家在配置事务属性时有什么疑惑,不妨直接看看这些事务属性的处理过程,通过对这些实现原理的了解,可以极大地提高对这些事务处理属性使用的理解程度。 diff --git "a/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\347\256\241\347\220\206\345\231\250\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" "b/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\347\256\241\347\220\206\345\231\250\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" index 7a4f04ba..9df97229 100644 --- "a/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\347\256\241\347\220\206\345\231\250\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" +++ "b/docs/Spring/SpringTransaction/Spring\344\272\213\345\212\241\347\256\241\347\220\206\345\231\250\347\232\204\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md" @@ -1,18 +1,20 @@ -## 1 Spring事务处理的应用场景 -下面,我们以 DataSourceTransactionManager事务管理器 为例,看一下在具体的事务管理器中如何实现事务创建、提交和回滚这些底层的事务处理操作。DataSourceTransationManager 和其他事务管理器一样,如 JtaTransactionManager,JpaTransactionManager 和 JdoTransactionManager,都继承自 AbstractPlatformManager,作为一个基类,AbstractPlatfromManager 封装了 Spring事务处理 中通用的处理部分,比如事务的创建、提交、回滚,事务状态和信息的处理,与线程的绑定等,有了这些通用处理的支持,对于具体的事务管理器而言,它们只需要处理和具体数据源相关的组件设置就可以了,比如在 HibernateTransactionManager 中,就只需要配置好和 Hibnernate事务处理 相关的接口以及相关的设置。所以,从 PlatformTransactionManager组件 的设计关系上,我们也可以看到,Spring事务处理 的主要过程是分两个部分完成的,通用的事务处理框架是在 AbstractPlatformManager 中完成,而 Spring 的事务接口与数据源实现的接口,多半是由具体的事务管理器来完成,它们都是作为 AbstractPlatformManager 的子类来是使用的。 +## 1 Spring 事务处理的应用场景 -可以看到,在 PlatformTransactionManager组件 的设计中 ,通过 PlatformTransactionManager接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。 +下面,我们以 DataSourceTransactionManager 事务管理器 为例,看一下在具体的事务管理器中如何实现事务创建、提交和回滚这些底层的事务处理操作。DataSourceTransationManager 和其他事务管理器一样,如 JtaTransactionManager,JpaTransactionManager 和 JdoTransactionManager,都继承自 AbstractPlatformManager,作为一个基类,AbstractPlatfromManager 封装了 Spring 事务处理 中通用的处理部分,比如事务的创建、提交、回滚,事务状态和信息的处理,与线程的绑定等,有了这些通用处理的支持,对于具体的事务管理器而言,它们只需要处理和具体数据源相关的组件设置就可以了,比如在 HibernateTransactionManager 中,就只需要配置好和 Hibnernate 事务处理 相关的接口以及相关的设置。所以,从 PlatformTransactionManager 组件 的设计关系上,我们也可以看到,Spring 事务处理 的主要过程是分两个部分完成的,通用的事务处理框架是在 AbstractPlatformManager 中完成,而 Spring 的事务接口与数据源实现的接口,多半是由具体的事务管理器来完成,它们都是作为 AbstractPlatformManager 的子类来是使用的。 -![avatar](../../../images/springTransaction/PlatformTransactionManager组件的设计.png) +可以看到,在 PlatformTransactionManager 组件 的设计中 ,通过 PlatformTransactionManager 接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager 抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。 -## 2 DataSourceTransactionManager的实现 -我们先看一下 DataSourceTransactionManager,在这个事务管理器中,它的实现直接与事务处理的底层实现相关。在事务开始的时候,会调用 doBegin()方法,首先会得到相对应的 Connection,然后可以根据事务设置的需要,对 Connection 的相关属性进行配置,比如将 Connection 的 autoCommit功能 关闭,并对像 TimeoutInSeconds 这样的事务处理参数进行设置,最后通过 TransactionSynchronizationManager 来对资源进行绑定。 +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/PlatformTransactionManager组件的设计.png) -从下面的代码中可以看到,DataSourceTransactionManager 作为 AbstractPlatformTransactionManager 的子类,在 AbstractPlatformTransactionManager 中已经为事务实现设计好了一系列的模板方法,比如 事务的提交、回滚处理等。在 DataSourceTransactionManager 中, 可以看到对模板方法中一些抽象方法的具体实现。例如,由 DataSourceTransactionManager 的 doBegin()方法 实现负责事务的创建工作。具体来说,如果使用 DataSource 创建事务,最终通过设置 Connection 的 autoCommit属性 来对事务处理进行配置。在实现过程中,需要把数据库的 Connection 和当前的线程进行绑定。对于事务的提交和回滚,都是通过直接调用 Connection 的提交和回滚来完成的,在这个实现过程中,如何取得事务处理场景中的 Connection对象,也是一个值得注意的地方。 +## 2 DataSourceTransactionManager 的实现 -上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 +我们先看一下 DataSourceTransactionManager,在这个事务管理器中,它的实现直接与事务处理的底层实现相关。在事务开始的时候,会调用 doBegin()方法,首先会得到相对应的 Connection,然后可以根据事务设置的需要,对 Connection 的相关属性进行配置,比如将 Connection 的 autoCommit 功能 关闭,并对像 TimeoutInSeconds 这样的事务处理参数进行设置,最后通过 TransactionSynchronizationManager 来对资源进行绑定。 -![avatar](../../../images/springTransaction/实现DataSourceTransactionManager的时序图.png) +从下面的代码中可以看到,DataSourceTransactionManager 作为 AbstractPlatformTransactionManager 的子类,在 AbstractPlatformTransactionManager 中已经为事务实现设计好了一系列的模板方法,比如 事务的提交、回滚处理等。在 DataSourceTransactionManager 中, 可以看到对模板方法中一些抽象方法的具体实现。例如,由 DataSourceTransactionManager 的 doBegin()方法 实现负责事务的创建工作。具体来说,如果使用 DataSource 创建事务,最终通过设置 Connection 的 autoCommit 属性 来对事务处理进行配置。在实现过程中,需要把数据库的 Connection 和当前的线程进行绑定。对于事务的提交和回滚,都是通过直接调用 Connection 的提交和回滚来完成的,在这个实现过程中,如何取得事务处理场景中的 Connection 对象,也是一个值得注意的地方。 + +上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit 属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 + +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/实现DataSourceTransactionManager的时序图.png) ```java public class DataSourceTransactionManager extends AbstractPlatformTransactionManager @@ -20,7 +22,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan /** 持有 javax.sql.DataSource对象 */ private DataSource dataSource; - + /** * 这里是产生 Transaction对象 的地方,为 Transaction 的创建提供服务,对数据库而言, * 事务工作是由 Connection 来完成的。这里把数据库的 Connection对象 放到了 ConnectionHolder 中, @@ -38,7 +40,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan txObject.setConnectionHolder(conHolder, false); return txObject; } - + /** * 判断是否存在活跃的事务,由 ConnectionHolder 的 transactionActive属性 来控制 */ @@ -47,7 +49,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; return (txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive()); } - + /** * 这里是处理事务开始的地方,在这里设置隔离级别,但忽略超时 */ @@ -55,7 +57,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; - + try { if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { @@ -65,13 +67,13 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan } txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } - + txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); - + Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); - + // 这里是 数据库Connection 完成事务处理的重要配置,需要把 autoCommit属性 关掉 if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); @@ -81,24 +83,24 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan con.setAutoCommit(false); } txObject.getConnectionHolder().setTransactionActive(true); - + int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } - + // 把当前的 数据库Connection 与线程绑定 if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder()); } } - + catch (Throwable ex) { DataSourceUtils.releaseConnection(con, this.dataSource); throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); } } - + /** * 事务提交的具体实现 */ @@ -117,7 +119,7 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan throw new TransactionSystemException("Could not commit JDBC transaction", ex); } } - + /** * 事务提交的具体实现,通过 Connection对象 的 rollback()方法 实现 */ @@ -137,22 +139,25 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan } } ``` -上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。看到这里,大家一定会觉得非常的熟悉。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 + +上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit 属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。看到这里,大家一定会觉得非常的熟悉。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 ## 3 小结 -总体来说,从声明式事务的整个实现中我们看到,声明式事务处理完全可以看成是一个具体的 Spring AOP应用。从这个角度来看,Spring事务处理 的实现本身就为应用开发者提供了一个非常优秀的 AOP应用 参考实例。在 Spring 的声明式事务处理中,采用了 IoC容器 的 Bean配置 为事务方法调用提供事务属性设置,从而为应用对事务处理的使用提供方便。 -有了声明式的使用方式,可以把对事务处理的实现与应用代码分离出来。从 Spring实现 的角度来看,声明式事务处理的大致实现过程是这样的:在为事务处理配置好 AOP 的基础设施(比如,对应的 Proxy代理对象 和 事务处理Interceptor拦截器对象)之后,首先需要完成对这些事务属性配置的读取,这些属性的读取处理是在 TransactionInterceptor 中实现的,在完成这些事务处理属性的读取之后,Spring 为事务处理的具体实现做好了准备。可以看到,Spring声明式事务处理 的过程同时也是一个整合事务处理实现到 Spring AOP 和 IoC容器 中去的过程。我们在整个过程中可以看到下面一些要点,在这些要点中,体现了对 Spring框架 的基本特性的灵活使用。 -- 如何封装各种不同事务处理环境下的事务处理,具体来说,作为应用平台的 Spring,它没法对应用使用什么样的事务处理环境做出限制,这样,对应用户使用的不同的事务处理器,Spring事务处理平台 都需要为用户提供服务。这些事务处理实现包括在应用中常见的 DataSource 的 Connection、Hibermate 的 Transaction等,Spring事务处理 通过一种统一的方式把它们封装起来,从而实现一个通用的事务处理过程,实现这部分事务处理对应用透明,使应用即开即用。 +总体来说,从声明式事务的整个实现中我们看到,声明式事务处理完全可以看成是一个具体的 Spring AOP 应用。从这个角度来看,Spring 事务处理 的实现本身就为应用开发者提供了一个非常优秀的 AOP 应用 参考实例。在 Spring 的声明式事务处理中,采用了 IoC 容器 的 Bean 配置 为事务方法调用提供事务属性设置,从而为应用对事务处理的使用提供方便。 + +有了声明式的使用方式,可以把对事务处理的实现与应用代码分离出来。从 Spring 实现 的角度来看,声明式事务处理的大致实现过程是这样的:在为事务处理配置好 AOP 的基础设施(比如,对应的 Proxy 代理对象 和 事务处理 Interceptor 拦截器对象)之后,首先需要完成对这些事务属性配置的读取,这些属性的读取处理是在 TransactionInterceptor 中实现的,在完成这些事务处理属性的读取之后,Spring 为事务处理的具体实现做好了准备。可以看到,Spring 声明式事务处理 的过程同时也是一个整合事务处理实现到 Spring AOP 和 IoC 容器 中去的过程。我们在整个过程中可以看到下面一些要点,在这些要点中,体现了对 Spring 框架 的基本特性的灵活使用。 + +- 如何封装各种不同事务处理环境下的事务处理,具体来说,作为应用平台的 Spring,它没法对应用使用什么样的事务处理环境做出限制,这样,对应用户使用的不同的事务处理器,Spring 事务处理平台 都需要为用户提供服务。这些事务处理实现包括在应用中常见的 DataSource 的 Connection、Hibermate 的 Transaction 等,Spring 事务处理 通过一种统一的方式把它们封装起来,从而实现一个通用的事务处理过程,实现这部分事务处理对应用透明,使应用即开即用。 - 如何读取事务处理属性值,在事务处理属性正确读取的基础上,结合事务处理代码,从而完成在既定的事务处理配置下,事务处理方法的实现。 -- 如何灵活地使用 Spring AOP框架,对事务处理进行封装,提供给应用即开即用的声明式事务处理功能。 +- 如何灵活地使用 Spring AOP 框架,对事务处理进行封装,提供给应用即开即用的声明式事务处理功能。 -在这个过程中,有几个 Spring事务处理 的核心类是我们需要关注的。其中包括 **TransactionInterceptor**,它是使用 AOP 实现声明式事务处理的拦截器,封装了 Spring 对声明式事务处理实现的基本过程;还包括 TransactionAttributeSource 和 **TransactionAttribute** 这两个类,它们封装了对声明式事务处理属性的识别,以及信息的读入和配置。我们看到的 TransactionAttribute对象,可以视为对事务处理属性的数据抽象,如果在使用声明式事务处理的时候,应用没有配置这些属性,Spring 将为用户提供 DefaultTransactionAttribute对象,该对象提供了默认的事务处理属性设置。 +在这个过程中,有几个 Spring 事务处理 的核心类是我们需要关注的。其中包括 **TransactionInterceptor**,它是使用 AOP 实现声明式事务处理的拦截器,封装了 Spring 对声明式事务处理实现的基本过程;还包括 TransactionAttributeSource 和 **TransactionAttribute** 这两个类,它们封装了对声明式事务处理属性的识别,以及信息的读入和配置。我们看到的 TransactionAttribute 对象,可以视为对事务处理属性的数据抽象,如果在使用声明式事务处理的时候,应用没有配置这些属性,Spring 将为用户提供 DefaultTransactionAttribute 对象,该对象提供了默认的事务处理属性设置。 -在事务处理过程中,可以看到 **TransactionInfo** 和 **TransactionStatus** 这两个对象,它们是存放事务处理信息的主要数据对象,它们通过与线程的绑定来实现事务的隔离性。具体来说,TransactionInfo对象 本身就像是一个栈,对应着每一次事务方法的调用,它会保存每一次事务方法调用的事务处理信息。值得注意的是,在 TransactionInfo对象 中,它持有 TransactionStatus对象,这个 TransactionStatus 是非常重要的。由这个 TransactionStatus 来掌管事务执行的详细信息,包括具体的事务对象、事务执行状态、事务设置状态等。 +在事务处理过程中,可以看到 **TransactionInfo** 和 **TransactionStatus** 这两个对象,它们是存放事务处理信息的主要数据对象,它们通过与线程的绑定来实现事务的隔离性。具体来说,TransactionInfo 对象 本身就像是一个栈,对应着每一次事务方法的调用,它会保存每一次事务方法调用的事务处理信息。值得注意的是,在 TransactionInfo 对象 中,它持有 TransactionStatus 对象,这个 TransactionStatus 是非常重要的。由这个 TransactionStatus 来掌管事务执行的详细信息,包括具体的事务对象、事务执行状态、事务设置状态等。 -在事务的创建、启动、提交和回滚的过程中,都需要与这个 TransactionStatus对象 中的数据打交道。在准备完这些与事务管理有关的数据之后,具体的事务处理是由 事务处理器TransactionManager 来完成的。在事务处理器完成事务处理的过程中,与具体事务处理器无关的操作都被封装到 AbstractPlatformTransactionManager 中实现了。这个抽象的事务处理器为不同的具体事务处理器提供了通用的事务处理模板,它封装了在事务处理过程中,与具体事务处理器无关的公共的事务处理部分。我们在具体的事务处理器(比如 DataSourceTransactionManager 和 HibernateTransactionManager)的实现中可以看到,最为底层的事务创建、挂起、提交、回滚操作。 +在事务的创建、启动、提交和回滚的过程中,都需要与这个 TransactionStatus 对象 中的数据打交道。在准备完这些与事务管理有关的数据之后,具体的事务处理是由 事务处理器 TransactionManager 来完成的。在事务处理器完成事务处理的过程中,与具体事务处理器无关的操作都被封装到 AbstractPlatformTransactionManager 中实现了。这个抽象的事务处理器为不同的具体事务处理器提供了通用的事务处理模板,它封装了在事务处理过程中,与具体事务处理器无关的公共的事务处理部分。我们在具体的事务处理器(比如 DataSourceTransactionManager 和 HibernateTransactionManager)的实现中可以看到,最为底层的事务创建、挂起、提交、回滚操作。 在 Spring 中,也可以通过编程式的方法来使用事务处理器,以帮助我们处理事务。在编程式的事务处理使用中, TransactionDefinition 是定义事务处理属性的类。对于事务处理属性,Spring 还提供了一个默认的事务属性 DefaultTransactionDefinition 来供用户使用。这种事务处理方式在实现上看起来比声明式事务处理要简单,但编程式实现事务处理却会造成事务处理与业务代码的紧密耦合,因而不经常被使用。在这里,我们之所以举编程式使用事务处理的例子,是因为通过了解编程式事务处理的使用,可以清楚地了解 Spring 统一实现事务处理的大致过程。 -有了这个背景,结合对声明式事务处理实现原理的详细分析,比如在声明式事务处理中,使用 AOP 对事务处理进行封装,对事务属性配置进行的处理,与线程绑定从而处理事务并发,并结合事务处理器的使用等,能够在很大程度上提高我们对整个 Spring事务处理实现 的理解。 \ No newline at end of file +有了这个背景,结合对声明式事务处理实现原理的详细分析,比如在声明式事务处理中,使用 AOP 对事务处理进行封装,对事务属性配置进行的处理,与线程绑定从而处理事务并发,并结合事务处理器的使用等,能够在很大程度上提高我们对整个 Spring 事务处理实现 的理解。 diff --git "a/docs/Spring/SpringTransaction/Spring\345\243\260\346\230\216\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206.md" "b/docs/Spring/SpringTransaction/Spring\345\243\260\346\230\216\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206.md" index 37f3cb0c..a5076450 100644 --- "a/docs/Spring/SpringTransaction/Spring\345\243\260\346\230\216\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206.md" +++ "b/docs/Spring/SpringTransaction/Spring\345\243\260\346\230\216\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206.md" @@ -1,15 +1,19 @@ ## 1 设计原理与基本过程 -在使用 Spring声明式事务处理 的时候,一种常用的方法是结合 IoC容器 和 Spring 已有的 TransactionProxyFactoryBean 对事务管理进行配置,比如,可以在这个 TransactionProxyFactoryBean 中为事务方法配置传播行为、并发事务隔离级别等事务处理属性,从而对声明式事务的处理提供指导。具体来说,在对声明式事务处理的原理分析中,声明式事务处理的实现大致可以分为以下几个部分: - - 读取和处理在 IoC容器 中配置的事务处理属性,并转化为 Spring事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor,从名字可以看出,它是一个 AOP通知器,Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事物处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。 - - Spring事务处理模块 实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性,以及与线程绑定完成事务处理的过程,Spring 通过 TransactionInfo 和 TransactionStatus 这两个数据对象,在事务处理过程中记录和传递相关执行场景。 - - 底层的事务处理实现。对于底层的事务操作,Spring 委托给具体的事务处理器来完成,这些具体的事务处理器,就是在 IoC容器 中配置声明式事务处理时,配置的 PlatformTransactionManager 的具体实现,比如 DataSourceTransactionManager 和 HibernateTransactionManager 等。 +在使用 Spring 声明式事务处理 的时候,一种常用的方法是结合 IoC 容器 和 Spring 已有的 TransactionProxyFactoryBean 对事务管理进行配置,比如,可以在这个 TransactionProxyFactoryBean 中为事务方法配置传播行为、并发事务隔离级别等事务处理属性,从而对声明式事务的处理提供指导。具体来说,在对声明式事务处理的原理分析中,声明式事务处理的实现大致可以分为以下几个部分: + +- 读取和处理在 IoC 容器 中配置的事务处理属性,并转化为 Spring 事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor,从名字可以看出,它是一个 AOP 通知器,Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC 容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事务处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。 +- Spring 事务处理模块 实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性,以及与线程绑定完成事务处理的过程,Spring 通过 TransactionInfo 和 TransactionStatus 这两个数据对象,在事务处理过程中记录和传递相关执行场景。 +- 底层的事务处理实现。对于底层的事务操作,Spring 委托给具体的事务处理器来完成,这些具体的事务处理器,就是在 IoC 容器 中配置声明式事务处理时,配置的 PlatformTransactionManager 的具体实现,比如 DataSourceTransactionManager 和 HibernateTransactionManager 等。 ## 2 实现分析 + ### 2.1 事务处理拦截器的配置 -和前面的思路一样,从声明式事务处理的基本用法入手,来了解它的基本实现原理。在使用声明式事务处理的时候,需要在 IoC容器 中配置 TransactionProxyFactoryBean,见名知义,这是一个 FactoryBean,有一个 getObject()方法。在 IoC容器 进行注入的时候,会创建 TransactionInterceptor对象,而这个对象会创建一个 TransactionAttributePointcut,为读取 TransactionAttribute 做准备。在容器初始化的过程中,由于实现了 InitializingBean接口,因此 AbstractSingletonProxyFactoryBean 会实现 afterPropertiesSet()方法,正是在这个方法实例化了一个 ProxyFactory,建立起 Spring AOP 的应用,在这里,会为这个 ProxyFactory 设置通知、目标对象,并最终返回 Proxy代理对象。在 Proxy代理对象 建立起来以后,在调用其代理方法的时候,会调用相应的 TransactionInterceptor拦截器,在这个调用中,会根据 TransactionAttribute 配置的事务属性进行配置,从而为事务处理做好准备。 -从 TransactionProxyFactoryBean 入手,通过代码实现来了解 Spring 是如何通过 AOP功能 来完成事务管理配置的,Spring 为声明式事务处理的实现所做的一些准备工作:包括为 AOP 配置基础设施,这些基础设施包括设置 拦截器TransactionInterceptor、通知器DefaultPointcutAdvisor 或 TransactionAttributeSourceAdvisor。同时,在 TransactionProxyFactoryBean 的实现中, 还可以看到注人进来的 PlatformTransactionManager 和 事务处理属性TransactionAttribute 等。 +和前面的思路一样,从声明式事务处理的基本用法入手,来了解它的基本实现原理。在使用声明式事务处理的时候,需要在 IoC 容器 中配置 TransactionProxyFactoryBean,见名知义,这是一个 FactoryBean,有一个 getObject()方法。在 IoC 容器 进行注入的时候,会创建 TransactionInterceptor 对象,而这个对象会创建一个 TransactionAttributePointcut,为读取 TransactionAttribute 做准备。在容器初始化的过程中,由于实现了 InitializingBean 接口,因此 AbstractSingletonProxyFactoryBean 会实现 afterPropertiesSet()方法,正是在这个方法实例化了一个 ProxyFactory,建立起 Spring AOP 的应用,在这里,会为这个 ProxyFactory 设置通知、目标对象,并最终返回 Proxy 代理对象。在 Proxy 代理对象 建立起来以后,在调用其代理方法的时候,会调用相应的 TransactionInterceptor 拦截器,在这个调用中,会根据 TransactionAttribute 配置的事务属性进行配置,从而为事务处理做好准备。 + +从 TransactionProxyFactoryBean 入手,通过代码实现来了解 Spring 是如何通过 AOP 功能 来完成事务管理配置的,Spring 为声明式事务处理的实现所做的一些准备工作:包括为 AOP 配置基础设施,这些基础设施包括设置 拦截器 TransactionInterceptor、通知器 DefaultPointcutAdvisor 或 TransactionAttributeSourceAdvisor。同时,在 TransactionProxyFactoryBean 的实现中, 还可以看到注人进来的 PlatformTransactionManager 和 事务处理属性 TransactionAttribute 等。 + ```java /** * 代理工厂bean 用于简化声明式事务处理,这是标准 AOP 的一个方便的替代方案 @@ -21,11 +25,11 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe /** 事务拦截器,通过 AOP 来发挥作用,Spring 在此拦截器中封装了事务处理实现 */ private final TransactionInterceptor transactionInterceptor = new TransactionInterceptor(); - + /** 切面 */ private Pointcut pointcut; - - + + /** * 通过依赖注入的事务属性以 properties的形式 出现 * 把从 beandefinition 中读到的事务管理的属性信息注入到 transactionInterceptor @@ -33,7 +37,7 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionInterceptor.setTransactionManager(transactionManager); } - + /** * 创建 AOP 对事务处理的 advisor * 本方法在 IoC容器 完成 Bean的依赖注入时,通过 initializeBean()方法 被调用 @@ -51,54 +55,56 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe return new TransactionAttributeSourceAdvisor(this.transactionInterceptor); } } - + public void setTransactionAttributes(Properties transactionAttributes) { this.transactionInterceptor.setTransactionAttributes(transactionAttributes); } - + public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) { this.transactionInterceptor.setTransactionAttributeSource(transactionAttributeSource); } - + public void setPointcut(Pointcut pointcut) { this.pointcut = pointcut; } - + public void setBeanFactory(BeanFactory beanFactory) { this.transactionInterceptor.setBeanFactory(beanFactory); } } ``` -以上代码完成了 AOP配置,对于用户来说,一个值得关心的问题是,Spring 的 TransactionInterceptor配置 是在什么时候被启动并成为 Advisor通知器 的一部分的呢?从对 createMainInterceptor()方法 的调用分析中可以看到,这个 createMainInterceptor()方法 在 IoC容器 完成 Bean的依赖注入时,通过 initializeBean()方法 被调用,具体的调用过程如下图所示。 -![avatar](../../../images/springTransaction/createMainInterceptor()方法的调用链.png) +以上代码完成了 AOP 配置,对于用户来说,一个值得关心的问题是,Spring 的 TransactionInterceptor 配置 是在什么时候被启动并成为 Advisor 通知器 的一部分的呢?从对 createMainInterceptor()方法 的调用分析中可以看到,这个 createMainInterceptor()方法 在 IoC 容器 完成 Bean 的依赖注入时,通过 initializeBean()方法 被调用,具体的调用过程如下图所示。 + +![avatar](<../../../images/springTransaction/createMainInterceptor()方法的调用链.png>) + +在 TransactionProxyFactoryBean 的父类 AbstractSingletonProxyFactoryBean 中的 afterPropertiesSet()方法,是 Spring 事务处理 完成 AOP 配置 的地方,在建立 TransactionProxyFactoryBean 的事务处理拦截器的时候,首先需要对 ProxyFactoryBean 的 目标 Bean 设置进行检查,如果这个 目标 Bean 的设置是正确的,就会创建一个 ProxyFactory 对象,从而实现 AOP 的使用。在 afterPropertiesSet() 的方法实现中,可以看到为 ProxyFactory 生成代理对象、配置通知器、设置代理接口方法等。 -在 TransactionProxyFactoryBean 的父类 AbstractSingletonProxyFactoryBean 中的 afterPropertiesSet()方法,是 Spring事务处理 完成 AOP配置 的地方,在建立 TransactionProxyFactoryBean 的事务处理拦截器的时候,首先需要对 ProxyFactoryBean 的 目标Bean 设置进行检查,如果这个 目标Bean 的设置是正确的,就会创建一个 ProxyFactory对象,从而实现 AOP 的使用。在 afterPropertiesSet() 的方法实现中,可以看到为 ProxyFactory 生成代理对象、配置通知器、设置代理接口方法等。 ```java public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanClassLoaderAware, InitializingBean { private Object target; - + private Class[] proxyInterfaces; - + private Object[] preInterceptors; - + private Object[] postInterceptors; - + /** Default is global AdvisorAdapterRegistry */ private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance(); - + private transient ClassLoader proxyClassLoader; - + private Object proxy; - + /** * 处理完成 AOP配置,创建 ProxyFactory对象,为其生成代理对象 * 配置通知器、设置代理接口方法 */ public void afterPropertiesSet() { - + // 校验target(目标对象) if (this.target == null) { throw new IllegalArgumentException("Property 'target' is required"); @@ -109,17 +115,17 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig if (this.proxyClassLoader == null) { this.proxyClassLoader = ClassUtils.getDefaultClassLoader(); } - + // 使用 ProxyFactory 完成 AOP的基本功能,ProxyFactory 提供 proxy对象 // 并将 TransactionIntercepter 设为 target方法调用的拦截器 ProxyFactory proxyFactory = new ProxyFactory(); - + if (this.preInterceptors != null) { for (Object interceptor : this.preInterceptors) { proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor)); } } - + // 加入 Advisor通知器,可以加入两种通知器,分别是: // DefaultPointcutAdvisor、TransactionAttributeSourceAdvisor // 这里通过调用 TransactionProxyFactoryBean实例 的 createMainInterceptor()方法 @@ -127,19 +133,19 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig // 的链表LinkedList,通过对这个链表中的元素执行增、删、改等操作,用来管理 // 配置给 ProxyFactory 的通知器 proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor())); - + if (this.postInterceptors != null) { for (Object interceptor : this.postInterceptors) { proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor)); } } - + proxyFactory.copyFrom(this); - + // 这里创建 AOP 的目标源,与在其它地方使用 ProxyFactory 没什么差别 TargetSource targetSource = createTargetSource(this.target); proxyFactory.setTargetSource(targetSource); - + if (this.proxyInterfaces != null) { proxyFactory.setInterfaces(this.proxyInterfaces); } @@ -148,16 +154,19 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig proxyFactory.setInterfaces( ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader)); } - + // 设置代理对象 this.proxy = proxyFactory.getProxy(this.proxyClassLoader); } } ``` -DefaultAopProxyFactory 创建 AOP Proxy 的过程在前面分析 AOP的实现原理 时已分析过,这里就不再重复了。可以看到,通过以上的一系列步骤,Spring 为实现事务处理而设计的 拦截器TransctionInterceptor 已经设置到 ProxyFactory 生成的 AOP代理对象 中去了,这里的 TransactionInterceptor 是作为 AOP Advice 的拦截器来实现它的功能的。在 IoC容器 中,配置其他与事务处理有关的属性,比如,比较熟悉的 transactionManager 和事务处理的属性,也同样会被设置到已经定义好的 TransactionInterceptor 中去。这些属性配置在 TransactionInterceptor,对事务方法进行拦截时会起作用。在 AOP配置 完成以后,可以看到,在 Spring声明式事务处理实现 中的一些重要的类已经悄然登场,比如 TransactionAttributeSourceAdvisor 和 TransactionInterceptor,正是这些类通过 AOP 封装了 Spring 对事务处理的基本实现。 + +DefaultAopProxyFactory 创建 AOP Proxy 的过程在前面分析 AOP 的实现原理 时已分析过,这里就不再重复了。可以看到,通过以上的一系列步骤,Spring 为实现事务处理而设计的 拦截器 TransctionInterceptor 已经设置到 ProxyFactory 生成的 AOP 代理对象 中去了,这里的 TransactionInterceptor 是作为 AOP Advice 的拦截器来实现它的功能的。在 IoC 容器 中,配置其他与事务处理有关的属性,比如,比较熟悉的 transactionManager 和事务处理的属性,也同样会被设置到已经定义好的 TransactionInterceptor 中去。这些属性配置在 TransactionInterceptor,对事务方法进行拦截时会起作用。在 AOP 配置 完成以后,可以看到,在 Spring 声明式事务处理实现 中的一些重要的类已经悄然登场,比如 TransactionAttributeSourceAdvisor 和 TransactionInterceptor,正是这些类通过 AOP 封装了 Spring 对事务处理的基本实现。 ### 2.2 事务处理配置的读入 -在 AOP配置 完成的基础上,以 TransactionAttributeSourceAdvisor的实现 为入口,了解具体的事务属性配置是如何读入的。 + +在 AOP 配置 完成的基础上,以 TransactionAttributeSourceAdvisor 的实现 为入口,了解具体的事务属性配置是如何读入的。 + ```java public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor { @@ -165,7 +174,7 @@ public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor { * 与其它 Advisor 一样,同样需要定义 AOP 中用到的 Interceptor 和 Pointcut */ private TransactionInterceptor transactionInterceptor; - + /** * 对于 切面Pointcut,这里使用了一个匿名内部类 */ @@ -181,7 +190,9 @@ public class TransactionAttributeSourceAdvisor extends AbstractPointcutAdvisor { }; } ``` -在声明式事务处理中,通过对目标对象的方法调用进行拦截来实现事务处理的织入,这个拦截通过 AOP 发挥作用。在 AOP 中,对于拦截的启动,首先需要对方法调用是否需要拦截进行判断,而判断的依据是那些在 TransactionProxyFactoryBean 中为目标对象设置的事务属性。也就是说,需要判断当前的目标方法调用是不是一个配置好的并且需要进行事务处理的方法调用。具体来说,这个匹配判断在 TransactionAttributeSourcePointcut 的 matches()方法 中完成,该方法实现 首先把事务方法的属性配置读取到 TransactionAttributeSource对象 中,有了这些事务处理的配置以后,根据当前方法调用的 Method对象 和 目标对象,对是否需要启动事务处理拦截器进行判断。 + +在声明式事务处理中,通过对目标对象的方法调用进行拦截来实现事务处理的织入,这个拦截通过 AOP 发挥作用。在 AOP 中,对于拦截的启动,首先需要对方法调用是否需要拦截进行判断,而判断的依据是那些在 TransactionProxyFactoryBean 中为目标对象设置的事务属性。也就是说,需要判断当前的目标方法调用是不是一个配置好的并且需要进行事务处理的方法调用。具体来说,这个匹配判断在 TransactionAttributeSourcePointcut 的 matches()方法 中完成,该方法实现 首先把事务方法的属性配置读取到 TransactionAttributeSource 对象 中,有了这些事务处理的配置以后,根据当前方法调用的 Method 对象 和 目标对象,对是否需要启动事务处理拦截器进行判断。 + ```java abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { @@ -191,7 +202,9 @@ abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPoi } } ``` -在 Pointcut 的 matches()判断过程 中,会用到 TransactionAttributeSource对象,这个 TransactionAttributeSource对象 是在对 TransactionInterceptor 进行依赖注入时就配置好的。它的设置是在 TransactionInterceptor 的基类 TransactionAspectSupport 中完成的,配置的是一个 NameMatchTransactionAttributeSource对象。 + +在 Pointcut 的 matches()判断过程 中,会用到 TransactionAttributeSource 对象,这个 TransactionAttributeSource 对象 是在对 TransactionInterceptor 进行依赖注入时就配置好的。它的设置是在 TransactionInterceptor 的基类 TransactionAspectSupport 中完成的,配置的是一个 NameMatchTransactionAttributeSource 对象。 + ```java public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { /** @@ -206,15 +219,17 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } ``` -在以上的代码实现中可以看到,NameMatchTransactionAttributeSource 作为 TransactionAttributeSource 的具体实现,是实际完成事务处理属性读入和匹配的地方。在对 事务属性TransactionAttributes 进行设置时,会从事务处理属性配置中读取事务方法名和配置属性,在得到配置的事务方法名和属性以后,会把它们作为键值对加入到一个 nameMap 中。 -在应用调用目标方法的时候,因为这个目标方法已经被 TransactionProxyFactoryBean 代理,所以 TransactionProxyFactoryBean 需要判断这个调用方法是否是事务方法。这个判断的实现,是通过在 NameMatchTransactionAttributeSource 中能否为这个调用方法返回事务属性来完成的。具体的实现过程是这样的:首先,以调用方法名为索引在 nameMap 中查找相应的事务处理属性值,如果能够找到,那么就说明该调用方法和事务方法是直接对应的,如果找不到,那么就会遍历整个 nameMap,对保存在 nameMap 中的每一个方法名,使用 PatternMatchUtils的simpleMatch()方法 进行命名模式上的匹配。这里使用 PatternMatchUtils 进行匹配的原因是,在设置事务方法的时候,可以不需要为事务方法设置一个完整的方法名,而可以通过设置方法名的命名模式来完成,比如可以通过对 通配符* 的使用等。所以,如果直接通过方法名没能够匹配上,而通过方法名的命名模式能够匹配上,这个方法也是需要进行事务处理的,相对应地,它所配置的事务处理属性也会从 nameMap 中取出来,从而触发事务处理拦截器的拦截。 +在以上的代码实现中可以看到,NameMatchTransactionAttributeSource 作为 TransactionAttributeSource 的具体实现,是实际完成事务处理属性读入和匹配的地方。在对 事务属性 TransactionAttributes 进行设置时,会从事务处理属性配置中读取事务方法名和配置属性,在得到配置的事务方法名和属性以后,会把它们作为键值对加入到一个 nameMap 中。 + +在应用调用目标方法的时候,因为这个目标方法已经被 TransactionProxyFactoryBean 代理,所以 TransactionProxyFactoryBean 需要判断这个调用方法是否是事务方法。这个判断的实现,是通过在 NameMatchTransactionAttributeSource 中能否为这个调用方法返回事务属性来完成的。具体的实现过程是这样的:首先,以调用方法名为索引在 nameMap 中查找相应的事务处理属性值,如果能够找到,那么就说明该调用方法和事务方法是直接对应的,如果找不到,那么就会遍历整个 nameMap,对保存在 nameMap 中的每一个方法名,使用 PatternMatchUtils 的 simpleMatch()方法 进行命名模式上的匹配。这里使用 PatternMatchUtils 进行匹配的原因是,在设置事务方法的时候,可以不需要为事务方法设置一个完整的方法名,而可以通过设置方法名的命名模式来完成,比如可以通过对 通配符\* 的使用等。所以,如果直接通过方法名没能够匹配上,而通过方法名的命名模式能够匹配上,这个方法也是需要进行事务处理的,相对应地,它所配置的事务处理属性也会从 nameMap 中取出来,从而触发事务处理拦截器的拦截。 + ```java public class NameMatchTransactionAttributeSource implements TransactionAttributeSource, Serializable { /** key 是方法名,value 是事务属性 */ private Map nameMap = new HashMap(); - + /** * 将给定 属性transactionAttributes 解析为 <名称, 属性> 的Map对象。以 方法名称 为 key, * 字符串属性定义 为 value,可通过 TransactionAttributeEditor 解析为 TransactionAttribute实例。 @@ -230,7 +245,7 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute addTransactionalMethod(methodName, attr); } } - + /** * 为事务方法添加属性 */ @@ -240,7 +255,7 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute } this.nameMap.put(methodName, attr); } - + /** * 对调用的方法进行判断,判断它是否是事务方法,如果是事务方法,则取出相应的事务配置属性 */ @@ -248,7 +263,7 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute // 直接通过 方法名 匹配 String methodName = method.getName(); TransactionAttribute attr = this.nameMap.get(methodName); - + if (attr == null) { // 查找最具体的名称匹配 String bestNameMatch = null; @@ -260,10 +275,10 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute } } } - + return attr; } - + /** * 如果给定的方法名与映射的名称匹配,则返回 */ @@ -272,14 +287,17 @@ public class NameMatchTransactionAttributeSource implements TransactionAttribute } } ``` -通过以上过程可以得到与目标对象调用方法相关的 TransactionAttribute对象,在这个对象中,封装了事务处理的配置。具体来说,在前面的匹配过程中,如果匹配返回的结果是 null,那么说明当前的调用方法不是一个事务方法,不需要纳入 Spring 统一的事务管理中,因为它并没有配置在 TransactionProxyFactoryBean 的事务处理设置中。如果返回的 TransactionAttribute对象 不是 null,那么这个返回的 TransactionAttribute对象 就已经包含了对事务方法的配置信息,对应这个事务方法的具体事务配置也已经读入到 TransactionAttribute对象 中了,为 TransactionInterceptor 做好了对调用的目标方法添加事务处理的准备。 + +通过以上过程可以得到与目标对象调用方法相关的 TransactionAttribute 对象,在这个对象中,封装了事务处理的配置。具体来说,在前面的匹配过程中,如果匹配返回的结果是 null,那么说明当前的调用方法不是一个事务方法,不需要纳入 Spring 统一的事务管理中,因为它并没有配置在 TransactionProxyFactoryBean 的事务处理设置中。如果返回的 TransactionAttribute 对象 不是 null,那么这个返回的 TransactionAttribute 对象 就已经包含了对事务方法的配置信息,对应这个事务方法的具体事务配置也已经读入到 TransactionAttribute 对象 中了,为 TransactionInterceptor 做好了对调用的目标方法添加事务处理的准备。 ### 2.3 事务处理拦截器的设计与实现 -在完成以上的准备工作后,经过 TransactionProxyFactoryBean 的 AOP包装, 此时如果对目标对象进行方法调用,起作用的对象实际上是一个 Proxy代理对象,对目标对象方法的调用,不会直接作用在 TransactionProxyFactoryBean 设置的目标对象上,而会被设置的事务处理拦截器拦截。而在 TransactionProxyFactoryBean 的 AOP实现 中,获取 Proxy对象 的过程并不复杂,TransactionProxyFactoryBean 作为一个 FactoryBean,对这个 Bean 的对象的引用是通过调用其父类 AbstractSingletonProxyFactoryBean 的 getObject()方法 来得到的。 + +在完成以上的准备工作后,经过 TransactionProxyFactoryBean 的 AOP 包装, 此时如果对目标对象进行方法调用,起作用的对象实际上是一个 Proxy 代理对象,对目标对象方法的调用,不会直接作用在 TransactionProxyFactoryBean 设置的目标对象上,而会被设置的事务处理拦截器拦截。而在 TransactionProxyFactoryBean 的 AOP 实现 中,获取 Proxy 对象 的过程并不复杂,TransactionProxyFactoryBean 作为一个 FactoryBean,对这个 Bean 的对象的引用是通过调用其父类 AbstractSingletonProxyFactoryBean 的 getObject()方法 来得到的。 + ```java public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanClassLoaderAware, InitializingBean { - + private Object proxy; // 返回的是一个 proxy代理对象,这个 proxy 是 ProxyFactory 生成的 AOP代理, // 已经封装了对事务处理的拦截器配置 @@ -291,14 +309,16 @@ public abstract class AbstractSingletonProxyFactoryBean extends ProxyConfig } } ``` -InvocationHandler 的实现类中有一个非常重要的方法 invoke(),该方法是 proxy代理对象 的回调方法,在调用 proxy对象 的代理方法时触发这个回调。事务处理拦截器TransactionInterceptor 中实现了 InvocationHandler 的 invoke()方法,其过程是,首先获得调用方法的事务处理配置;在得到事务处理配置以后,会取得配置的 PlatformTransactionManager,由这个事务处理器来实现事务的创建、提交、回滚操作。 + +InvocationHandler 的实现类中有一个非常重要的方法 invoke(),该方法是 proxy 代理对象 的回调方法,在调用 proxy 对象 的代理方法时触发这个回调。事务处理拦截器 TransactionInterceptor 中实现了 InvocationHandler 的 invoke()方法,其过程是,首先获得调用方法的事务处理配置;在得到事务处理配置以后,会取得配置的 PlatformTransactionManager,由这个事务处理器来实现事务的创建、提交、回滚操作。 + ```java public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable { public Object invoke(final MethodInvocation invocation) throws Throwable { // 得到代理的目标对象,并将事务属性传递给目标对象 Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); - + // 在其父类 TransactionAspectSupport 中进行后续的事务处理 return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { public Object proceedWithInvocation() throws Throwable { @@ -312,15 +332,15 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init private static final ThreadLocal transactionInfoHolder = new NamedThreadLocal("Current aspect-driven transaction"); - + protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation) throws Throwable { - + // 获取事务属性,如果属性为空,则该方法是非事务性的 final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass); - + // 这里区分不同类型的 PlatformTransactionManager,因为他们的调用方式不同, // 对 CallbackPreferringPlatformTransactionManager 来说,需要回调函数 // 来实现事务的创建和提交,而非 CallbackPreferringPlatformTransactionManager @@ -377,7 +397,7 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } }); - + // Check result: It might indicate a Throwable to rethrow. if (result instanceof ThrowableHolder) { throw ((ThrowableHolder) result).getThrowable(); @@ -391,22 +411,22 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } } - + /** * 用于保存事务信息的不透明对象。子类必须将其传递回该类上的方法,但看不到其内部。 */ protected final class TransactionInfo { - + private final PlatformTransactionManager transactionManager; - + private final TransactionAttribute transactionAttribute; - + private final String joinpointIdentification; - + private TransactionStatus transactionStatus; - + private TransactionInfo oldTransactionInfo; - + public TransactionInfo(PlatformTransactionManager transactionManager, TransactionAttribute transactionAttribute, String joinpointIdentification) { this.transactionManager = transactionManager; @@ -416,9 +436,10 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init } } ``` -以事务提交为例,简要的说明下该过程。在调用代理的事务方法时,因为前面已经完成了一系列 AOP配置,对事务方法的调用,最终启动 -TransactionInterceptor拦截器 的 invoke()方法。在这个方法中,首先会读取该事务方法的事务属性配置,然后根据事务属性配置以及具体事务处理器的配置来决定采用哪一个事务处理器,这个事务处理器实际上是一个 PlatformTransactionManager。在确定好具体的事务处理器之后,会根据事务的运行情况和事务配置来决定是不是需要创建新的事务。 -对于 Spring 而言,事务的管理实际上是通过一个 TransactionInfo对象 来完成的,在该对象中,封装了事务对象和事务处理的状态信息,这是事务处理的抽象。在这一步完成以后,会对拦截器链进行处理,因为有可能在该事务对象中还配置了除事务处理 AOP 之外的其他拦截器。在结束对拦截器链处理之后,会对 TransactionInfo 中的信息进行更新,以反映最近的事务处理情况,在这个时候,也就完成了事务提交的准备,通过调用 事务处理器PlatformTransactionManager 的 commitTransactionAfterReturning()方法 来完成事务的提交。这个提交的处理过程已经封装在 PlatformTransactionManager 的事务处理器中了,而与具体数据源相关的处理过程,最终委托给相关的具体事务处理器来完成,比如 DataSourceTransactionManager、Hibermate'TransactionManager 等。 +以事务提交为例,简要的说明下该过程。在调用代理的事务方法时,因为前面已经完成了一系列 AOP 配置,对事务方法的调用,最终启动 +TransactionInterceptor 拦截器 的 invoke()方法。在这个方法中,首先会读取该事务方法的事务属性配置,然后根据事务属性配置以及具体事务处理器的配置来决定采用哪一个事务处理器,这个事务处理器实际上是一个 PlatformTransactionManager。在确定好具体的事务处理器之后,会根据事务的运行情况和事务配置来决定是不是需要创建新的事务。 + +对于 Spring 而言,事务的管理实际上是通过一个 TransactionInfo 对象 来完成的,在该对象中,封装了事务对象和事务处理的状态信息,这是事务处理的抽象。在这一步完成以后,会对拦截器链进行处理,因为有可能在该事务对象中还配置了除事务处理 AOP 之外的其他拦截器。在结束对拦截器链处理之后,会对 TransactionInfo 中的信息进行更新,以反映最近的事务处理情况,在这个时候,也就完成了事务提交的准备,通过调用 事务处理器 PlatformTransactionManager 的 commitTransactionAfterReturning()方法 来完成事务的提交。这个提交的处理过程已经封装在 PlatformTransactionManager 的事务处理器中了,而与具体数据源相关的处理过程,最终委托给相关的具体事务处理器来完成,比如 DataSourceTransactionManager、Hibermate'TransactionManager 等。 -在这个 invoke()方法 的实现中,可以看到整个事务处理在 AOP拦截器 中实现的全过程。同时,它也是 Spring 采用 AOP 封装事务处理和实现声明式事务处理的核心部分。这部分实现是一个桥梁,它胶合了具体的事务处理和 Spring AOP框架,可以看成是一个 Spring AOP应用,在这个桥梁搭建完成以后,Spring事务处理 的实现就开始了。 \ No newline at end of file +在这个 invoke()方法 的实现中,可以看到整个事务处理在 AOP 拦截器 中实现的全过程。同时,它也是 Spring 采用 AOP 封装事务处理和实现声明式事务处理的核心部分。这部分实现是一个桥梁,它胶合了具体的事务处理和 Spring AOP 框架,可以看成是一个 Spring AOP 应用,在这个桥梁搭建完成以后,Spring 事务处理 的实现就开始了。 diff --git "a/docs/Spring/Spring\346\225\264\344\275\223\350\204\211\347\273\234/16\345\274\240\345\233\276\350\247\243\351\224\201Spring\347\232\204\346\225\264\344\275\223\350\204\211\347\273\234.md" "b/docs/Spring/Spring\346\225\264\344\275\223\350\204\211\347\273\234/16\345\274\240\345\233\276\350\247\243\351\224\201Spring\347\232\204\346\225\264\344\275\223\350\204\211\347\273\234.md" new file mode 100644 index 00000000..bcb302b8 --- /dev/null +++ "b/docs/Spring/Spring\346\225\264\344\275\223\350\204\211\347\273\234/16\345\274\240\345\233\276\350\247\243\351\224\201Spring\347\232\204\346\225\264\344\275\223\350\204\211\347\273\234.md" @@ -0,0 +1,261 @@ +作者: [Java4ye](https://github.com/Java4ye) + +### 概览 + +本文将讲解 Spring 的原理,看看一个 Bean 是怎么被创建出来的,中间经历过那几道工序加工,它的生命周期是怎样的,以及有哪些扩展点,后置处理器可以使用,让你对 Spring 多一些了解! + +### 目录 + +本文会先大概介绍下这些知识点 👇 + +![image-20211213224509864](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213224509864.png) + +### 印象中的 Spring + +脑海中有这么一条公式: + +👉 IOC = 工厂模式 + XML + 反射 + +👉 而 DI , AOP , **事务** 等也都在 XML 中很直观的表现出来 + +虽然我们现在大部分用这个注解来代替,但是原理还是基本一样的 🐖 + +注解使用起来很方便,但是学习的话,还是建议先通过这个 XML ,毕竟结构性的文档,有层次感,可以留下更深的印象~ 😄 + +### 小小 Spring + +把 Spring 浓缩一下,就有了这么一点小东西 🐖 + +![image-20211213224920994](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213224920994.png) + +想了下,我们用 Spring ,其中最主要的一点,就是用它来帮我们管理,创建这个 Bean 。 + +那么先从源头看起 —— Bean 从哪来 (@\_@;) + +### Bean 解析流程 + +![image-20211213225044814](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225044814.png) + +如图所示,就是通过 **解析器**,对我们的 XML 文件或者注解进行解析,最后将这些信息封装在 BeanDefinition 类中,并通过 BeanDefinitionRegistry 接口将这些信息 **注册** 起来,放在 beanDefinitionMap 变量中, key : beanName , value :BeanDefinition 。 + +简单看看 BeanDefinition 中的属性叭 + +### BeanDefinition + +- beanClass : bean 的类型 ,实例化时用的 🐖 +- scope : 作用范围有 singleton,prototype + +- isLazy : **懒加载** ,true 的话 会在 getBean 时生成,而且 scope 的 prototype 无效,false 在 Spring 启动过程中直接生成 +- initMethodName : 初始化方法,当然是初始化时调用 🐖 +- primary : 主要的,有多个 Bean 时使用它 +- dependsOn : 依赖的 Bean,必须等依赖 Bean 创建好才可以创建 + +> PS: @Component ,@Bean , 都会被解析成 BeanDefinition + +### 反射 + +有了原料后呢,咋们再来看看这个 **工厂** BeanFactory + +先简单想一想这个工厂要怎么创建这个 Bean 呢? + +没错,肯定就是这个 **反射** 啦 😄 + +那么,结合我们从原料中获取的重要属性之一的 beanClass ,我们可以画出这么一张图 👇 + +![image-20211213225124831](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225124831.png) + +那么我们再来看看这个 BeanFactory 叭 😄 + +### BeanFactory + +先来看看 作为 IOC 容器的**根接口** 的 BeanFactory 提供了什么方法吧 👇 + +![image-20210904162844126](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904162844126.png) + +主要是这个 getBean 方法,以及 **别名获取**,**类型获取** 方法和其他一些判断方法如 :**单例**,**多例**,**类型匹配**,**包含 bean** + +我们来简单看看它的子接口都有哪些叭~😄 + +这里分享个小技巧叭 🐖 + +看源码的时候,一般就直接看这个**默认**接口 如这里的 DefaultListableBeanFactory + +![image-20210904161436139](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904161436139.png) + +基本上看个类名就知道大概作用了,那么先对号入座下 👇 + +**ListableBeanFactory** + +> 👉 遍历 bean + +**HierarchicalBeanFactory** + +> 👉 提供 父子关系,可以获取上一级的 BeanFactory + +**ConfigurableBeanFactory** + +> 👉 实现了 SingletonBeanRegistry ,主要是 单例 Bean 的注册,生成 + +**AutowireCapableBeanFactory** + +> 👉 和自动装配有关的 + +**AbstractBeanFactory** + +> 👉 单例缓存,以及 FactoryBean 相关的 + +**ConfigurableListableBeanFactory** + +> 👉 预实例化单例 Bean,分析,修改 BeanDefinition + +**AbstractAutowireCapableBeanFactory** + +> 👉 创建 Bean ,属性注入,实例化,调用初始化方法 等等 + +**DefaultListableBeanFactory** + +> 👉 支持单例 Bean ,Bean 别名 ,父子 BeanFactory,Bean 类型转化 ,Bean 后置处理,FactoryBean,自动装配等 + +是不是非常丰富 😄 + +### FactoryBean + +FactoryBean ,它本身就是个 Bean,算是小工厂 ,归 BeanFactory 这个大工厂管理的。 + +![image-20210904174616712](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904174616712.png) + +可以看到它就只有三个方法 + +1. `getObject()` 获取对象 +2. `isSingleton()` 单例对象 +3. `getObjectType()` 返回的是 Bean 对象的类型 + +相比大工厂 BeanFactory 少了特别多东西,没有严格的 Bean 生命周期流程 😄 + +FactoryBean 对象本身也是一个 Bean,是一个小工厂,可以生产另外的 Bean + +BeanFactory 是 Spring 容器的根接口,是大工厂,生产各种各样的 Bean + +beanName 就是正常对象 + +“&”+beanName , 获取的是实现了该接口的 FactoryBean 工厂对象 + +大致如下 👇 + +![image-20211213225330193](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225330193.png) + +### ApplicationContext + +我们再来看看这个 ApplicationContext + +![image-20210904161808341](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904161808341.png) + +可以看到它扩展了很多功能,除了 BeanFactory ,它还可以**创建 , 获取 Bean**,以及处理**国际化**,**事件**,**获取资源**等 + +- EnvironmentCapable 获取 环境变量 的功能,可以获取到 **操作系统变量** 和 **JVM 环境变量** +- ListableBeanFactory 获取所有 BeanNames,判断某个 BeanName 是否存在 BeanDefinition 对象,统计 BeanDefinition 对象,获取某个类型对应的所有 beanNames 等功能 +- HierarchicalBeanFactory 获取父 BeanFactory ,判断某个 name 是否存在 bean 对象的功能 +- MessageSource **国际化功能**,获取某个国际化资源 +- ApplicationEventPublisher **事件发布功能**(重点) +- ResourcePatternResolver **加载,获取资源的功能**,这里的资源可能是文件,图片 等某个 URL 资源都可以 + +还有这三个重要的类 👇,就不一一介绍先啦 😄 + +1. ClassPathXmlApplicationContext +2. AnnotationConfigApplicationContext +3. FileSystemXmlApplicationContext + +赶紧来看看这个核心叭! + +### IOC 容器 + +当然,这时候出场的肯定是 IOC 啦。 + +我们都知道 IOC 是 **控制反转** ,但是别忘了 **容器** 这个词,比如 **容器的根接口** BeanFactory ,**容器的实现** 👇 + +1. ClassPathXmlApplicationContext +2. AnnotationConfigApplicationContext +3. FileSystemXmlApplicationContext + +同时我们要注意这里无处不在的 **后置处理器** xxxPostProcessor 🐷 + +这个是 Spring 中扩展性强的原因了! + +我们可以在各个过程中合理应用这些 PostProcessor 来扩展,或者修改 Bean 定义信息等等 + +![image-20211213225748030](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225748030.png) + +可以看到在这个容器中,完成了 Bean 的初始化,而这个过程还有很多细节 ,请往下看看 👇 + +DI 到时写 **属性填充** 时再介绍 🐷 + +### BeanFactory 后置处理器 + +作为 IOC 容器根接口的 BeanFactory ,有着非常高的扩展性,比如最开始获取原料 BeanDefinition 时,就出现了两个针对 BeanFactory 工厂的后置处理器 👇 + +BeanDefinitionRegistryPostProcessor + +> 通过该接口,我们可以自己掌控我们的 **原料**,通过 BeanDefinitionRegistry 接口去 **新增**,**删除**,**获取**我们这个 BeanDefinition + +BeanFactoryPostProcessor + +> 通过该接口,可以在 **实例化对象前**,对 BeanDefinition 进行修改 ,**冻结** ,**预实例化单例 Bean** 等 + +经过上面层层阻碍后,我们最终会来到目标方法 getBean ,将原料投入生产,最终获取一个个 Bean 对象出来 + +那么随之而来的就是这个 Bean 的生命周期啦 😄 + +### Bean 生命周期 + +Bean 的创建和管理有**标准化的流程**! + +这里在我们的工厂 BeanFactory 中写得很清楚 👇 + +![image-20210902072224002](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210902072224002.png) + +总共 **14** 个步骤,是不是一下子就清晰多了 😄 + +![image-20211213225831583](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225831583.png) + +在看这部分的源码时,要多注意两个英文单词 😝 + +1. **实例化** 👉 **Instantiation** +2. **初始化** 👉 **Initialization** + +ps: 别看快搞错了 哈哈 😝 + +仔细阅读上面这 14 个步骤,会发现前面 **8** 个都是 Aware 接口,而他们的作用也很简单,就是获取 xxAware 这个单词的前缀 xx 😄 + +比如事件发布器 ApplicationEventPublisher ,只要你实现了 ApplicationEventPublisherAware 接口,就可以**获取** 事件发布器 ApplicationEventPublisher ! + +### Bean 后置处理器 + +在实例化 和 初始化流程中,把这个 Bean 的后置处理器 BeanPostProcessor 安排上,就得到下图啦 👇 + +![image-20211213225953964](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225953964.png) + +这里留意下 **实例化** 有扩展点 InstantiationAwareBeanPostProcessor , **初始化** 扩展点 BeanPostProcessor 就非常多啦,我们主要来关注下这个 AOP + +### AOP + +那么 AOP 是在哪个步骤代理对象的呢?👇 + +![image-20211213230042502](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213230042502.png) + +可以在 AbstractAutoProxyCreator 类中看到 👇 + +![image-20210903080803199](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210903080803199.png) + +### 总结 + +本文就先介绍到这里啦 🐖 + +主要介绍了 Spring 里面的这些脉络,方便小伙伴们对它有个整体的印象先~ + +再介绍其中的一些扩展点,比如从源材料开始的 BeanFactoryPostprocessor ,到产物 Bean 的 BeanPostprocessor 。 + +实例化,初始化的顺序,Bean 的生命周期,以及 BeanFactory 及子类扩展的功能,再到 ApplicationContext 的功能。 + +还有这个核心机制: **工厂+XML+反射**,以及 AOP **发生的地方**。😋 + +![image-20211213230212297](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213230212297.png) diff --git "a/docs/Spring/Spring\346\272\220\347\240\201\346\225\205\344\272\213\357\274\210\347\236\216\347\274\226\347\211\210\357\274\211/\351\235\242\347\255\213\345\223\245IoC\345\256\271\345\231\250\347\232\204\344\270\200\345\244\251(\344\270\212).md" "b/docs/Spring/Spring\346\272\220\347\240\201\346\225\205\344\272\213\357\274\210\347\236\216\347\274\226\347\211\210\357\274\211/\351\235\242\347\255\213\345\223\245IoC\345\256\271\345\231\250\347\232\204\344\270\200\345\244\251(\344\270\212).md" index 0ef5a706..4922f6df 100644 --- "a/docs/Spring/Spring\346\272\220\347\240\201\346\225\205\344\272\213\357\274\210\347\236\216\347\274\226\347\211\210\357\274\211/\351\235\242\347\255\213\345\223\245IoC\345\256\271\345\231\250\347\232\204\344\270\200\345\244\251(\344\270\212).md" +++ "b/docs/Spring/Spring\346\272\220\347\240\201\346\225\205\344\272\213\357\274\210\347\236\216\347\274\226\347\211\210\357\274\211/\351\235\242\347\255\213\345\223\245IoC\345\256\271\345\231\250\347\232\204\344\270\200\345\244\251(\344\270\212).md" @@ -1,29 +1,62 @@ -引言:庞大的代码量让人心生怠倦,有趣的故事让技术也疯狂。 +# 引言:庞大的代码量让人心生怠倦,有趣的故事让技术也疯狂 -大家好,我是 IoC 容器家族的第 17 代传人,我们家族世世代代在 spring 商业街上卖烤面筋,大家都叫我“面筋哥”,另外我爹还给我起了个高大上的英文名字,叫“FileSystemXmlApplicationContext”,但有群臭猴子嫌麻烦,就天天叫我的外号,害得我差点忘了自己的本名。不过无所谓咯,只要生意兴隆,这都是小事。 +大家好,我是 IoC 容器家族的第 17 代传人,我们家族世世代代在 spring 商业街上卖烤面筋,大家都叫我“面筋哥”,另外我爹还给我起了个高大上的英文名字,叫 `FileSystemXmlApplicationContext`,但有群臭猴子嫌麻烦,就天天叫我的外号,害得我差点忘了自己的本名。不过无所谓咯,只要生意兴隆,这都是小事。 -前几天出摊卖烤面筋时,灵感大作,即兴唱了一首“我的烤面筋”,被网友拍下来传到某站上 成了网红,现在我要趁势而上,把自己祖传的烤面筋工艺宣传出去,让我那个臭弟弟“ClassPathXmlApplicationContext”知道,谁才是 IoC 容器的正统传人! +前几天出摊卖烤面筋时,灵感大作,即兴唱了一首“我的烤面筋”,被网友拍下来传到某站上成了网红,现在我要趁势而上,把自己祖传的烤面筋工艺宣传出去,让我那个臭弟弟 `ClassPathXmlApplicationContext` 知道,谁才是 IoC 容器的正统传人! ## 第一阶段:BeanDefinition 资源定位(Reader,beanDefinitionReader,documentReader) -新的一天从 new 开始,但我却还躺在床上各种伸懒腰,毕竟我现在也是个小老板了,很多杂七杂八的活雇几个小弟干就行咯。我拿起我的 iBanana11 看了看商业街董事(某程序员)发的“精选优质面筋批发市场地址”,然后深吸一口气 refresh(),闭上眼 obtainFreshBeanFactory(),气沉丹田 refreshBeanFactory(),大喊一声: -“loadBeanDefinitions()!” -我虎背熊腰的小弟“beanDefinitionReader” 破门而入,尖声细语地问道: -“老板有何吩咐 ~ ?” + +新的一天从 `new` 开始,但我却还躺在床上各种伸懒腰,毕竟我现在也是个小老板了,很多杂七杂八的活雇几个小弟干就行咯。我拿起我的 iBanana11 看了看商业街董事(某程序员)发的“精选优质面筋批发市场地址”,然后深吸一口气 `refresh()`,闭上眼 `obtainFreshBeanFactory()`,气沉丹田 `refreshBeanFactory()`,大喊一声: + +> loadBeanDefinitions()! + +我虎背熊腰的小弟 `beanDefinitionReader` 破门而入,尖声细语地问道: + +> 老板有何吩咐 ~? + 我起身叮嘱了他几件事后,把自己的联系方式(引用)、面筋批发市场的地址(spring 配置文件地址)交给他,就又躺回去盯着天花板上的钻石吊灯继续发呆。 -Reader 家有一对兄妹,哥哥 beanDefinitionReader 虎背熊腰大老粗,却尖声细语;妹妹 documentReader 心灵手巧,可惜比较宅,我们几乎没怎么见过。兄妹俩相互配合把上午的准备工作做了大半。 -不要看我天天躺着,彗星晒屁股了还眯着眼,ta 们兄妹俩在几点几分打个喷嚏我都能算到,毕竟我基因里都写满了“烤面筋工艺完整详细流程”。 -哥哥现在肯定在开着小面包车拿着我给他的地址(locations)到处找面筋等原材料,然后把找到的面筋打包进 Document 对象,拉回来交给妹妹 documentReader 进行精心处理,连同 Document 给她的还有一个“神秘人”的联系方式。 -妹妹会打开 Document 取出其中最大的几个箱子(<beans>、<import>、<alias> 等一级标签),分别进行处理。其中 beans 箱最为重要,里面放满了夜市的主角,烤面筋的核心材料。 + +Reader 家有一对兄妹,哥哥 `beanDefinitionReader` 虎背熊腰大老粗,却尖声细语;妹妹 `documentReader` 心灵手巧,可惜比较宅,我们几乎没怎么见过。兄妹俩相互配合把上午的准备工作做了大半。 + +不要看我天天躺着,彗星晒屁股了还眯着眼,他们兄妹俩在几点几分打个喷嚏我都能算到,毕竟我基因里都写满了“烤面筋工艺完整详细流程”。 + +哥哥现在肯定在开着小面包车拿着我给他的地址(`locations`)到处找面筋等原材料,然后把找到的面筋打包进 `Document` 对象,拉回来交给妹妹 `documentReader` 进行精心处理,连同 `Document` 给她的还有一个“神秘人”的联系方式。 + +妹妹会打开 `Document` 取出其中最大的几个箱子(``、``、`` 等一级标签),分别进行处理。其中 `beans` 箱最为重要,里面放满了夜市的主角,烤面筋的核心材料。 ## 第二阶段:将 bean 解析封装成 BeanDefinitionHolder(BeanDefinitionParserDelegate) -之后妹妹会拿起我们 IoC 家族祖传的面筋处理神器 BeanDefinitionParserDelegate,从 beans 箱里面一个一个取出形态各异的面筋 bean 分别进行加工处理。刚拿出来的面筋 bean 是不会直接烤了卖的,我们会将 bean 用神器 ParserDelegate 进行九九八十一道细致处理,所以我们家烤出来的面筋才会如此劲道美味,世世代代延绵不断。 - 不过处理程序再怎么细致复杂,也不过就是分为两大部分:第一,处理 bean 的属性信息,如 id,class,scope 等;第二,处理 bean 的子元素,主要是 标签,而 标签又有属性和子元素,且子元素类型更加丰富复杂,可能是<map>,<set>,<list>,<array> 等。所以如果你们想学我家的祖传秘方,开个同样的摊子干倒我,也不是这么容易的哦。 - 经过上面的步骤,一个配置文件中的面筋 bean 就被处理包装成了半成品 BeanDefinitionHolder。 + +之后妹妹会拿起我们 IoC 家族祖传的面筋处理神器 `BeanDefinitionParserDelegate`,从 `beans` 箱里面一个一个取出形态各异的面筋 bean 分别进行加工处理。 + +刚拿出来的面筋 bean 是不会直接烤了卖的,我们会将 bean 用神器 `ParserDelegate` 进行九九八十一道细致处理,所以我们家烤出来的面筋才会如此劲道美味,世世代代延绵不断。 + +不过处理程序再怎么细致复杂,也不过就是分为两大部分: + +1. 处理 bean 的属性信息,如 `id`、`class`、`scope` 等; +2. 处理 bean 的子元素,主要是 `` 标签,而 `` 标签又有属性和子元素,且子元素类型更加丰富复杂,可能是 ``、``、``、`` 等。 + +所以如果你们想学我家的祖传秘方,开个同样的摊子干倒我,也不是这么容易的哦。 + +经过上面的步骤,一个配置文件中的面筋 bean 就被处理包装成了半成品 `BeanDefinitionHolder`。 ## 第三阶段:将 BeanDefinition 注册进 IoC 容器(BeanDefinitionReaderUtils) -妹妹在用神器 BeanDefinitionParserDelegate 经过一顿疯狂操作之后,将包装好的半成品 BeanDefinitionHolder 扔进传输机 BeanDefinitionReaderUtils,并且输入哥哥给她的神秘人地址,就继续处理下一个面筋 bean 咯。 -之后,传输机将 BeanDefinitionHolder 的包装打开,分别取出 beanName(面筋的唯一标识)和 BeanDefinition(面筋本筋),传输的目的地是 BeanDefinitionRegistry 的工作室(这就是我前面给哥哥 beanDefinitionReader 的地址)。 -这家工作室的 BeanDefinitionRegistry 其实就是我的影分身之一,因为我的祖先实现了这个接口。影分身 Registry 检查一下传输过来的 beanName(面筋的唯一标识)和 BeanDefinition(面筋本筋),如果没什么问题,就把它们用根绳子系在一起扔进我的“王之面筋宝库”,一个 ConcurrentHashMap(64),也有人把我的“面筋宝库”称作“IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。 -就这样,每一种取出来的面筋都会经过这些处理。等到所有的面筋处理完了,也差不多到了傍晚,每到这时我就会拿起梳子和发油,对着镶满钻石的镜子,梳理整齐与徐峥同款的明星发型,唱着魔性的“我的烤面筋 ~”,骑着小车车,出摊咯 ~ -面筋等原材料基本上都已经处理完毕,但把这些原材料变成程序员手中的“烤面筋”也是一门复杂而精细的手艺,老铁们记得 watch、star、fork,素质三连一波,下一期我将带领你们走进 spring 商业街的夜市,一起烤出香喷喷的面筋,成为这条 gai 上最亮的仔! \ No newline at end of file +妹妹在用神器 `BeanDefinitionParserDelegate` 经过一顿疯狂操作之后,将包装好的半成品 `BeanDefinitionHolder` 扔进传输机 `BeanDefinitionReaderUtils`,并且输入哥哥给她的神秘人地址,就继续处理下一个面筋 bean 咯。 + +之后,传输机将 `BeanDefinitionHolder` 的包装打开,分别取出 `beanName`(面筋的唯一标识)和 `BeanDefinition`(面筋本筋),传输的目的地是 `BeanDefinitionRegistry` 的工作室(这就是我前面给哥哥 `beanDefinitionReader` 的地址)。 + +这家工作室的 `BeanDefinitionRegistry` 其实就是我的影分身之一,因为我的祖先实现了这个接口。 + +影分身 `Registry` 检查一下传输过来的 `beanName`(面筋的唯一标识)和 `BeanDefinition`(面筋本筋),如果没什么问题,就把它们用根绳子系在一起扔进我的“王之面筋宝库”,一个 `ConcurrentHashMap(64)`,也有人把我的“面筋宝库”称作 “IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。 + +就这样,每一种取出来的面筋都会经过这些处理。 + +等到所有的面筋处理完了,也差不多到了傍晚,每到这时我就会拿起梳子和发油,对着镶满钻石的镜子,梳理整齐与徐峥同款的明星发型,唱着魔性的: + +> 我的烤面筋 ~ + +骑着小车车,出摊咯 ~ + +--- + +面筋等原材料基本上都已经处理完毕,但把这些原材料变成程序员手中的“烤面筋”也是一门复杂而精细的手艺,老铁们记得 `watch`、`star`、`fork`,素质三连一波,下一期我将带领你们走进 spring 商业街的夜市,一起烤出香喷喷的面筋,成为这条 `gai` 上最亮的仔! diff --git a/docs/Spring/TX/Spring-transaction.md b/docs/Spring/TX/Spring-transaction.md new file mode 100644 index 00000000..e7cee6d7 --- /dev/null +++ b/docs/Spring/TX/Spring-transaction.md @@ -0,0 +1,1795 @@ +# Spring 事务 + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) + +## 声明式事务 + +### Propagation + +- 事务传播 + +```java +public enum Propagation { + + /** + * 有事务则加入,没有则新建 + */ + REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), + + /** + * 有事务就用,如果没有就不开启(继承关系) + * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization + */ + SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), + + /** + * 必须在已有事务中 + */ + MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), + + /** + * 不管是否已有事务,都要开启新事务,老事务挂起 + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), + + /** + * 不开启事务 + * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager + */ + NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), + + /** + * 必须在没有事务的方法中调用,否则抛出异常 + */ + NEVER(TransactionDefinition.PROPAGATION_NEVER), + + /** + * 果已有事务,则嵌套执行,如果没有,就新建(和REQUIRED类似,和REQUIRES_NEW容易混淆) + * @see org.springframework.jdbc.datasource.DataSourceTransactionManager + */ + NESTED(TransactionDefinition.PROPAGATION_NESTED); + + + private final int value; + + + Propagation(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + +} +``` + +### Isolation + +- 事务级别 + +```java +public enum Isolation { + + /** + * @see java.sql.Connection + */ + DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), + + /** + * 读未提交 + * + * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED + */ + READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), + + /** + * 读已提交 + * + * @see java.sql.Connection#TRANSACTION_READ_COMMITTED + */ + READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), + + /** + * 可重复读 + * + * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ + */ + REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), + + /** + * 可串行化 + * + * @see java.sql.Connection#TRANSACTION_SERIALIZABLE + */ + SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); + + + private final int value; + + + Isolation(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + +} +``` + +### EnableTransactionManagement + +- 下面代码是一个注解方式的事务配置使用 `EnableTransactionManagement`来开启事务支持 + +```java +@ComponentScan(basePackages = "org.source.hot.spring.overview.ioc.tx.declarative") +@EnableTransactionManagement +public class TxConfig { + + @Bean // 数据源 + public DataSource dataSource() { + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUsername(""); + dataSource.setPassword(""); + dataSource.setUrl(""); + dataSource.setDriverClassName(com.mysql.jdbc.Driver.class.getName()); + return dataSource; + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean //事务管理器 + public PlatformTransactionManager platformTransactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + +} +``` + +- 注解源码如下,关注于`@Import(TransactionManagementConfigurationSelector.class)` + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(TransactionManagementConfigurationSelector.class) +public @interface EnableTransactionManagement { + + boolean proxyTargetClass() default false; + + AdviceMode mode() default AdviceMode.PROXY; + + int order() default Ordered.LOWEST_PRECEDENCE; + +} +``` + +```java +public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector { + + @Override + protected String[] selectImports(AdviceMode adviceMode) { + // 根据切面类型进行初始化 + switch (adviceMode) { + case PROXY: + // 默认值 + return new String[] {AutoProxyRegistrar.class.getName(), + ProxyTransactionManagementConfiguration.class.getName()}; + case ASPECTJ: + return new String[] {determineTransactionAspectClass()}; + default: + return null; + } + } + + private String determineTransactionAspectClass() { + return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? + TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : + TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); + } + +} +``` + +### ProxyTransactionManagementConfiguration + +```java +@Configuration(proxyBeanMethods = false) +public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + + + /** + * 事务切面 + * @param transactionAttributeSource + * @param transactionInterceptor + * @return + */ + @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( + TransactionAttributeSource transactionAttributeSource, + TransactionInterceptor transactionInterceptor) { + // 事务切面 + BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + // 事务属性 + advisor.setTransactionAttributeSource(transactionAttributeSource); + advisor.setAdvice(transactionInterceptor); + if (this.enableTx != null) { + // 执行顺序 + advisor.setOrder(this.enableTx.getNumber("order")); + } + return advisor; + } + + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionAttributeSource transactionAttributeSource() { + return new AnnotationTransactionAttributeSource(); + } + + /*** + * 事务拦截器 + * @param transactionAttributeSource + * @return + */ + @Bean + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) + public TransactionInterceptor transactionInterceptor( + TransactionAttributeSource transactionAttributeSource) { + TransactionInterceptor interceptor = new TransactionInterceptor(); + interceptor.setTransactionAttributeSource(transactionAttributeSource); + if (this.txManager != null) { + // 事务管理器注入 + interceptor.setTransactionManager(this.txManager); + } + return interceptor; + } + +} +``` + +### TransactionInterceptor + +![image-20200729144622440](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729144622440.png) + +- 实现了`org.aopalliance.intercept.MethodInterceptor`接口的方法 + +```java +@Override +@Nullable +public Object invoke(MethodInvocation invocation) throws Throwable { + // Work out the target class: may be {@code null}. + // The TransactionAttributeSource should be passed the target class + // as well as the method, which may be from an interface. + Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); + + // Adapt to TransactionAspectSupport's invokeWithinTransaction... + return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); +} +``` + +- 这段代码会在具有`Transactional` 的注解方法上生效 + +```java +@Service +public class IssueServiceImpl { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Transactional() + public boolean insertIssue() throws Exception { + jdbcTemplate.execute("INSERT INTO `scrum`.`issue`() VALUES ()"); + + throw new Exception("a"); + } + +} + + +public class DeclarativeTransactionTest { + + public static void main(String[] args) throws Exception { + + AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext( + TxConfig.class); + IssueServiceImpl bean = applicationContext.getBean(IssueServiceImpl.class); + bean.insertIssue(); + System.out.println(); + applicationContext.close(); + } + + +} +``` + +![image-20200729145518089](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729145518089.png) + +断点开始进行查阅. 再断点后执行一步会直接进入 cglib 代理对象 + +`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept` 具体不展开,继续往下执行 + +![image-20200729145637688](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729145637688.png) + +走到`invoke`方法了 + +入参对象查看 + +![image-20200729145835608](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729145835608.png) + +- 获取事务属性 + + ```java + @Override + @Nullable + public TransactionAttribute getTransactionAttribute(Method method, + @Nullable Class targetClass) { + if (method.getDeclaringClass() == Object.class) { + return null; + } + + // First, see if we have a cached value. + // 尝试缓存中获取 + Object cacheKey = getCacheKey(method, targetClass); + TransactionAttribute cached = this.attributeCache.get(cacheKey); + if (cached != null) { + // Value will either be canonical value indicating there is no transaction attribute, + // or an actual transaction attribute. + if (cached == NULL_TRANSACTION_ATTRIBUTE) { + return null; + } else { + return cached; + } + } else { + // We need to work it out. + // 自行构建一个事务属性 + TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass); + // Put it in the cache. + if (txAttr == null) { + this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); + } else { + String methodIdentification = ClassUtils + .getQualifiedMethodName(method, targetClass); + if (txAttr instanceof DefaultTransactionAttribute) { + ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification); + } + if (logger.isTraceEnabled()) { + logger.trace("Adding transactional method '" + methodIdentification + + "' with attribute: " + txAttr); + } + this.attributeCache.put(cacheKey, txAttr); + } + return txAttr; + } + } + + + protected Object getCacheKey(Method method, @Nullable Class targetClass) { + return new MethodClassKey(method, targetClass); + } + + ``` + +![image-20200729162023837](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729162023837.png) + +- 此处方法已经获取到了这个方法就是后面的一个切面 + +- 确定事务管理器 + + ```java + @Nullable + protected TransactionManager determineTransactionManager( + @Nullable TransactionAttribute txAttr) { + // Do not attempt to lookup tx manager if no tx attributes are set + // 空判断返回一个事务管理器 + if (txAttr == null || this.beanFactory == null) { + return getTransactionManager(); + } + + // 属性是否有别名 + String qualifier = txAttr.getQualifier(); + // 如果有 + if (StringUtils.hasText(qualifier)) { + // 从 ioc 容器中根据类型和名称获取事务管理器 + return determineQualifiedTransactionManager(this.beanFactory, qualifier); + } else if (StringUtils.hasText(this.transactionManagerBeanName)) { + // 从 ioc 容器中根据类型和名称获取事务管理器 + return determineQualifiedTransactionManager(this.beanFactory, + this.transactionManagerBeanName); + } else { + // 通过get方法获取 + TransactionManager defaultTransactionManager = getTransactionManager(); + // 如果没有 + if (defaultTransactionManager == null) { + // 尝试从缓存中获取 + defaultTransactionManager = this.transactionManagerCache + .get(DEFAULT_TRANSACTION_MANAGER_KEY); + // 缓存里面没有从 ioc 容器中获取并且设置缓存 + if (defaultTransactionManager == null) { + defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class); + this.transactionManagerCache.putIfAbsent( + DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager); + } + } + return defaultTransactionManager; + } + } + ``` + +![image-20200729160650401](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729160650401.png) + +- 类型转换 + + ```java + @Nullable + private PlatformTransactionManager asPlatformTransactionManager( + @Nullable Object transactionManager) { + if (transactionManager == null + || transactionManager instanceof PlatformTransactionManager) { + return (PlatformTransactionManager) transactionManager; + } else { + throw new IllegalStateException( + "Specified transaction manager is not a PlatformTransactionManager: " + + transactionManager); + } + } + ``` + +- 获取方法切面 + + ```java + private String methodIdentification(Method method, @Nullable Class targetClass, + @Nullable TransactionAttribute txAttr) { + + String methodIdentification = methodIdentification(method, targetClass); + if (methodIdentification == null) { + if (txAttr instanceof DefaultTransactionAttribute) { + // 直接就获取了.方法签名. + methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor(); + } + if (methodIdentification == null) { + methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); + } + } + return methodIdentification; + } + ``` + +![image-20200729161647214](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729161647214.png) + +- 创建一个新的事务根据事务传播性 + + ```java + @SuppressWarnings("serial") + protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, + @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { + + // If no name specified, apply method identification as transaction name. + // 把切面的地址放进去 + if (txAttr != null && txAttr.getName() == null) { + txAttr = new DelegatingTransactionAttribute(txAttr) { + @Override + public String getName() { + return joinpointIdentification; + } + }; + } + + TransactionStatus status = null; + if (txAttr != null) { + if (tm != null) { + // 事务状态 + // 获取事务 + status = tm.getTransaction(txAttr); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + + "] because no transaction manager has been configured"); + } + } + } + // 处理出一个 TransactionInfo + return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); + } + + ``` + +![image-20200729163303000](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729163303000.png) + +- `tm.getTransaction` + + ```java + @Override + public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) + throws TransactionException { + + // Use defaults if no transaction definition given. + // 获取事务的定义 + TransactionDefinition def = (definition != null ? definition + : TransactionDefinition.withDefaults()); + + // 获取事务 + Object transaction = doGetTransaction(); + boolean debugEnabled = logger.isDebugEnabled(); + + // 是否存在事务 + if (isExistingTransaction(transaction)) { + // Existing transaction found -> check propagation behavior to find out how to behave. + // 存在事务后处理什么操作 + return handleExistingTransaction(def, transaction, debugEnabled); + } + + // Check definition settings for new transaction. + // 超时的校验. 小于默认值抛出异常 + if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { + throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout()); + } + + // No existing transaction found -> check propagation behavior to find out how to proceed. + // 没有事务抛出异常 + if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { + throw new IllegalTransactionStateException( + "No existing transaction found for transaction marked with propagation 'mandatory'"); + } else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || + def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || + def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + SuspendedResourcesHolder suspendedResources = suspend(null); + if (debugEnabled) { + logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); + } + try { + boolean newSynchronization = (getTransactionSynchronization() + != SYNCHRONIZATION_NEVER); + DefaultTransactionStatus status = newTransactionStatus( + def, transaction, true, newSynchronization, debugEnabled, + suspendedResources); + doBegin(transaction, def); + prepareSynchronization(status, def); + return status; + } catch (RuntimeException | Error ex) { + resume(null, suspendedResources); + throw ex; + } + } else { + // Create "empty" transaction: no actual transaction, but potentially synchronization. + if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger + .isWarnEnabled()) { + logger.warn( + "Custom isolation level specified but no actual transaction initiated; " + + "isolation level will effectively be ignored: " + def); + } + boolean newSynchronization = (getTransactionSynchronization() + == SYNCHRONIZATION_ALWAYS); + return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, + null); + } + } + ``` + + - `org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction` + + - `org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction` + + ```java + @Override + protected Object doGetTransaction() { + DataSourceTransactionObject txObject = new DataSourceTransactionObject(); + txObject.setSavepointAllowed(isNestedTransactionAllowed()); + // 数据库链接对象 + // 从事务管理器中获取数据库链接对象 + ConnectionHolder conHolder = + (ConnectionHolder) TransactionSynchronizationManager + .getResource(obtainDataSource()); + txObject.setConnectionHolder(conHolder, false); + return txObject; + } + ``` + + - `org.springframework.transaction.support.AbstractPlatformTransactionManager#suspend` + + ```java + @Nullable + protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) + throws TransactionException { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + List suspendedSynchronizations = doSuspendSynchronization(); + try { + Object suspendedResources = null; + if (transaction != null) { + suspendedResources = doSuspend(transaction); + } + // 线程名称 + String name = TransactionSynchronizationManager.getCurrentTransactionName(); + // 同步方法中设置 + TransactionSynchronizationManager.setCurrentTransactionName(null); + // 只读设置 + boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); + // 同步方法中设置 + TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); + // 隔离级别 + Integer isolationLevel = TransactionSynchronizationManager + .getCurrentTransactionIsolationLevel(); + // 同步方法中设置 + TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); + // 是否活跃 + boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); + TransactionSynchronizationManager.setActualTransactionActive(false); + return new SuspendedResourcesHolder( + suspendedResources, suspendedSynchronizations, name, readOnly, + isolationLevel, wasActive); + } catch (RuntimeException | Error ex) { + // doSuspend failed - original transaction is still active... + doResumeSynchronization(suspendedSynchronizations); + throw ex; + } + } else if (transaction != null) { + // Transaction active but no synchronization active. + Object suspendedResources = doSuspend(transaction); + return new SuspendedResourcesHolder(suspendedResources); + } else { + // Neither transaction nor synchronization active. + return null; + } + } + ``` + +- `prepareTransactionInfo`简单的`new`对象并且绑定线程 + + ```java + protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm, + @Nullable TransactionAttribute txAttr, String joinpointIdentification, + @Nullable TransactionStatus status) { + + // 初始化 + TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification); + if (txAttr != null) { + // We need a transaction for this method... + if (logger.isTraceEnabled()) { + logger.trace( + "Getting transaction for [" + txInfo.getJoinpointIdentification() + "]"); + } + // The transaction manager will flag an error if an incompatible tx already exists. + txInfo.newTransactionStatus(status); + } else { + // The TransactionInfo.hasTransaction() method will return false. We created it only + // to preserve the integrity of the ThreadLocal stack maintained in this class. + if (logger.isTraceEnabled()) { + logger.trace("No need to create transaction for [" + joinpointIdentification + + "]: This method is not transactional."); + } + } + + // We always bind the TransactionInfo to the thread, even if we didn't create + // a new transaction here. This guarantees that the TransactionInfo stack + // will be managed correctly even if no transaction was created by this aspect. + // 和线程绑定 + txInfo.bindToThread(); + return txInfo; + } + ``` + +- `retVal = invocation.proceedWithInvocation();` + + - 这里走的是 CGLIB 的方法直接会执行结果将结果返回具体方法在 + + `org.springframework.aop.framework.CglibAopProxy.CglibMethodInvocation#proceed` + + ```java + @Override + @Nullable + public Object proceed() throws Throwable { + try { + return super.proceed(); + } + catch (RuntimeException ex) { + throw ex; + } + catch (Exception ex) { + if (ReflectionUtils.declaresException(getMethod(), ex.getClass())) { + throw ex; + } + else { + throw new UndeclaredThrowableException(ex); + } + } + } + + ``` + +- 如果没有异常就直接处理完成返回了 + +- 我们现在是有异常的 + + ```java + try { + // This is an around advice: Invoke the next interceptor in the chain. + // This will normally result in a target object being invoked. + // 回调方法 + retVal = invocation.proceedWithInvocation(); + } catch (Throwable ex) { + // target invocation exception + // 回滚异常 + completeTransactionAfterThrowing(txInfo, ex); + throw ex; + } finally { + // 消息清理 + cleanupTransactionInfo(txInfo); + } + ``` + +- `completeTransactionAfterThrowing`回滚异常的处理方法 + + ```java + protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, + Throwable ex) { + if (txInfo != null && txInfo.getTransactionStatus() != null) { + if (logger.isTraceEnabled()) { + logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + + "] after exception: " + ex); + } + if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { + try { + // 做回滚 + txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); + } catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by rollback exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + logger.error("Application exception overridden by rollback exception", ex); + throw ex2; + } + } else { + // We don't roll back on this exception. + // Will still roll back if TransactionStatus.isRollbackOnly() is true. + try { + // org.springframework.transaction.support.AbstractPlatformTransactionManager.commit 的方法 + txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); + } catch (TransactionSystemException ex2) { + logger.error("Application exception overridden by commit exception", ex); + ex2.initApplicationException(ex); + throw ex2; + } catch (RuntimeException | Error ex2) { + logger.error("Application exception overridden by commit exception", ex); + throw ex2; + } + } + } + } + ``` + + - 整理一下这里的流程 + + 1. 有异常走回滚 + + `txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus())` + + 2. 没有异常直接提交 + + `txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())` + + - **注意: 这里的异常如果是 exception 不会走回滚** + +- 判断是否需要回滚 + + ``` + txInfo.transactionAttribute.rollbackOn + ``` + + - 链路 + + - `org.springframework.transaction.interceptor.DelegatingTransactionAttribute#rollbackOn` + + - `org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn` + + ```java + @Override + public boolean rollbackOn(Throwable ex) { + if (logger.isTraceEnabled()) { + logger.trace( + "Applying rules to determine whether transaction should rollback on " + ex); + } + + RollbackRuleAttribute winner = null; + int deepest = Integer.MAX_VALUE; + + if (this.rollbackRules != null) { + for (RollbackRuleAttribute rule : this.rollbackRules) { + int depth = rule.getDepth(ex); + if (depth >= 0 && depth < deepest) { + deepest = depth; + winner = rule; + } + } + } + + if (logger.isTraceEnabled()) { + logger.trace("Winning rollback rule is: " + winner); + } + + // User superclass behavior (rollback on unchecked) if no rule matches. + if (winner == null) { + logger.trace("No relevant rollback rule found: applying default rules"); + return super.rollbackOn(ex); + } + + return !(winner instanceof NoRollbackRuleAttribute); + } + ``` + + - `org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn` + + ```java + @Override + public boolean rollbackOn(Throwable ex) { + return (ex instanceof RuntimeException || ex instanceof Error); + } + ``` + + - 这就是我们的异常判断是否需要回滚 + +- `cleanupTransactionInfo` + + 数据清理 + + ```java + protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) { + if (txInfo != null) { + txInfo.restoreThreadLocalStatus(); + } + } + ``` + + ```java + private void restoreThreadLocalStatus() { + // Use stack to restore old transaction TransactionInfo. + // Will be null if none was set. + transactionInfoHolder.set(this.oldTransactionInfo); + } + ``` + +## 编程式事务 + +### DefaultTransactionDefinition + +- 默认的事务定义 + - 常见属性 + 1. timeout + 2. readOnly + 3. .... + +### PlatformTransactionManager + +```java +// 获取事务 +TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException; +// 提交事务 +void commit(TransactionStatus status) throws TransactionException; +// 回滚事务 +void rollback(TransactionStatus status) throws TransactionException; +``` + +- 贴出一部分 + +![image-20200728105926218](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200728105926218.png) + +- AbstractPlatformTransactionManager 定义了一些基础属性 以及一些需要子类实现的方法 + +```java +// 属性 +defaultTimeout +nestedTransactionAllowed +validateExistingTransaction +globalRollbackOnParticipationFailure +failEarlyOnGlobalRollbackOnly +rollbackOnCommitFailure +// 方法 +doGetTransaction +isExistingTransaction +useSavepointForNestedTransaction +doBegin +doSuspend +doResume +shouldCommitOnGlobalRollbackOnly +prepareForCommit +doCommit +doRollback +doSetRollbackOnly +registerAfterCompletionWithExistingTransaction +doCleanupAfterCompletion + +``` + +### DataSourceTransactionManager + +- xml 配置如下 + +```xml + + + + + + + + + + + + +``` + +- 两个属性,通常我们会配置 datasource + + ```java + @Nullable + private DataSource dataSource; + + private boolean enforceReadOnly = false; + ``` + + - bean 的属性注入就不具体描述了 + +![image-20200728133037075](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200728133037075.png) + +- `InitializingBean` + + - ```java + @Override + public void afterPropertiesSet() { + if (getDataSource() == null) { + throw new IllegalArgumentException("Property 'dataSource' is required"); + } + } + ``` + + - 如果`dataSource`为空会抛出异常 + - 默认单例会注册到 ioc 容器中.后续注册流程不具体描述 + +- 方法注释 + +```java + /** + * 获取datasource + */ + protected DataSource obtainDataSource() { + DataSource dataSource = getDataSource(); + Assert.state(dataSource != null, "No DataSource set"); + return dataSource; + } + + /** + * 创建事务 + * + * @return 事务对象 + */ + @Override + protected Object doGetTransaction() { + DataSourceTransactionObject txObject = new DataSourceTransactionObject(); + txObject.setSavepointAllowed(isNestedTransactionAllowed()); + // 数据库链接对象 + // 从事务管理器中获取数据库链接对象 + ConnectionHolder conHolder = + (ConnectionHolder) TransactionSynchronizationManager + .getResource(obtainDataSource()); + txObject.setConnectionHolder(conHolder, false); + return txObject; + } + + /** + * 是否存在事务 + * + * @param transaction transaction object returned by doGetTransaction + * @return + */ + @Override + protected boolean isExistingTransaction(Object transaction) { + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + return (txObject.hasConnectionHolder() && txObject.getConnectionHolder() + .isTransactionActive()); + } + + + /** + * This implementation sets the isolation level but ignores the timeout. 事务的开始方法 + */ + @Override + protected void doBegin(Object transaction, TransactionDefinition definition) { + // 拿出事务 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + // 链接对象 + Connection con = null; + + try { + if (!txObject.hasConnectionHolder() || + txObject.getConnectionHolder().isSynchronizedWithTransaction()) { + // 数据库链接对象 + Connection newCon = obtainDataSource().getConnection(); + if (logger.isDebugEnabled()) { + logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); + } + // 设置数据库连接 + txObject.setConnectionHolder(new ConnectionHolder(newCon), true); + } + // 拿出链接对象并且设置同步事务 + txObject.getConnectionHolder().setSynchronizedWithTransaction(true); + // 链接对象赋值 + con = txObject.getConnectionHolder().getConnection(); + + // 获取事务级别 + Integer previousIsolationLevel = DataSourceUtils + .prepareConnectionForTransaction(con, definition); + // 设置事务隔离级别 + txObject.setPreviousIsolationLevel(previousIsolationLevel); + // 设置只读 + txObject.setReadOnly(definition.isReadOnly()); + + // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, + // so we don't want to do it unnecessarily (for example if we've explicitly + // configured the connection pool to set it already). + // 判断是否自动提交 + if (con.getAutoCommit()) { + txObject.setMustRestoreAutoCommit(true); + if (logger.isDebugEnabled()) { + logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); + } + con.setAutoCommit(false); + } + + // 事务链接准备 + prepareTransactionalConnection(con, definition); + // 事务激活 + txObject.getConnectionHolder().setTransactionActive(true); + + // 超时时间获取 + int timeout = determineTimeout(definition); + // 默认超时时间设置 + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + txObject.getConnectionHolder().setTimeoutInSeconds(timeout); + } + + // Bind the connection holder to the thread. + // 将链接和当前线程绑定 + if (txObject.isNewConnectionHolder()) { + // k: datasource v: connectionHolder + TransactionSynchronizationManager + .bindResource(obtainDataSource(), txObject.getConnectionHolder()); + } + } catch (Throwable ex) { + if (txObject.isNewConnectionHolder()) { + // 释放链接 + DataSourceUtils.releaseConnection(con, obtainDataSource()); + txObject.setConnectionHolder(null, false); + } + throw new CannotCreateTransactionException( + "Could not open JDBC Connection for transaction", ex); + } + } + + + /** + * 挂起事务 + * + * @param transaction transaction object returned by {@code doGetTransaction} + * @return 移除的链接 + */ + @Override + protected Object doSuspend(Object transaction) { + // 获取事务对象 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + // 连接置空 + txObject.setConnectionHolder(null); + // 解除资源绑定 + return TransactionSynchronizationManager.unbindResource(obtainDataSource()); + } + + + /** + * 恢复事务 + * + * @param transaction transaction object returned by {@code doGetTransaction} + * @param suspendedResources the object that holds suspended resources, as returned by + * doSuspend + */ + @Override + protected void doResume(@Nullable Object transaction, Object suspendedResources) { + // 资源绑定 + TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources); + } + + + /** + * 做提交 + * + * @param status the status representation of the transaction + */ + @Override + protected void doCommit(DefaultTransactionStatus status) { + // 事务对象 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status + .getTransaction(); + // 获取链接 + Connection con = txObject.getConnectionHolder().getConnection(); + if (status.isDebug()) { + logger.debug("Committing JDBC transaction on Connection [" + con + "]"); + } + try { + // 链接提交 + con.commit(); + } catch (SQLException ex) { + throw new TransactionSystemException("Could not commit JDBC transaction", ex); + } + } + + + /** + * 事务回滚 + * + * @param status the status representation of the transaction + */ + @Override + protected void doRollback(DefaultTransactionStatus status) { + + // 事务对象 + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status + .getTransaction(); + // 链接对象 + Connection con = txObject.getConnectionHolder().getConnection(); + if (status.isDebug()) { + logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); + } + try { + // 回滚方法 + con.rollback(); + } catch (SQLException ex) { + throw new TransactionSystemException("Could not roll back JDBC transaction", ex); + } + } + + + /** + * 设置回滚 + * + * @param status the status representation of the transaction + */ + @Override + protected void doSetRollbackOnly(DefaultTransactionStatus status) { + DataSourceTransactionObject txObject = (DataSourceTransactionObject) status + .getTransaction(); + if (status.isDebug()) { + logger.debug( + "Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + + "] rollback-only"); + } + txObject.setRollbackOnly(); + } + + + /** + * 清除 + * + * @param transaction transaction object returned by {@code doGetTransaction} + */ + @Override + protected void doCleanupAfterCompletion(Object transaction) { + DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; + + // Remove the connection holder from the thread, if exposed. + if (txObject.isNewConnectionHolder()) { + // 释放datasource绑定的资源 + TransactionSynchronizationManager.unbindResource(obtainDataSource()); + } + + // Reset connection. + Connection con = txObject.getConnectionHolder().getConnection(); + try { + if (txObject.isMustRestoreAutoCommit()) { + con.setAutoCommit(true); + } + // 重置链接 + DataSourceUtils.resetConnectionAfterTransaction( + con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly()); + } catch (Throwable ex) { + logger.debug("Could not reset JDBC Connection after transaction", ex); + } + + if (txObject.isNewConnectionHolder()) { + if (logger.isDebugEnabled()) { + logger.debug("Releasing JDBC Connection [" + con + "] after transaction"); + } + DataSourceUtils.releaseConnection(con, this.dataSource); + } + + txObject.getConnectionHolder().clear(); + } + + + /** + * + * 事务准备 + */ + protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition) + throws SQLException { + + if (isEnforceReadOnly() && definition.isReadOnly()) { + try (Statement stmt = con.createStatement()) { + // 执行sql 类似事务隔离级别 + stmt.executeUpdate("SET TRANSACTION READ ONLY"); + } + } + } +``` + +#### 内部类 DataSourceTransactionObject + +```java + private static class DataSourceTransactionObject extends JdbcTransactionObjectSupport { + + /** + * 是否有新的链接 + */ + private boolean newConnectionHolder; + + /** + * 是否自动提交 + */ + private boolean mustRestoreAutoCommit; + } +``` + +### AbstractPlatformTransactionManager + +- abstract 修饰具体定义的方法不具体展开。主要关注实现`org.springframework.transaction.PlatformTransactionManager`的几个方法 + +#### commit 方法 + +```java +@Override +public final void commit(TransactionStatus status) throws TransactionException { + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + // 事务状态 + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + + if (defStatus.isLocalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug("Transactional code has requested rollback"); + } + // 处理回滚 + processRollback(defStatus, false); + return; + } + + if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { + if (defStatus.isDebug()) { + logger.debug( + "Global transaction is marked as rollback-only but transactional code requested commit"); + } + // 处理回滚 + processRollback(defStatus, true); + return; + } + // 真正的处理提交 + processCommit(defStatus); +} +``` + +```java +private void processCommit(DefaultTransactionStatus status) throws TransactionException { + try { + boolean beforeCompletionInvoked = false; + + try { + boolean unexpectedRollback = false; + // + prepareForCommit(status); + triggerBeforeCommit(status); + triggerBeforeCompletion(status); + // 前置任务是否已经执行 + beforeCompletionInvoked = true; + + // 嵌套事务. 是否有保存点 + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Releasing transaction savepoint"); + } + unexpectedRollback = status.isGlobalRollbackOnly(); + status.releaseHeldSavepoint(); + } else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction commit"); + } + unexpectedRollback = status.isGlobalRollbackOnly(); + doCommit(status); + } else if (isFailEarlyOnGlobalRollbackOnly()) { + unexpectedRollback = status.isGlobalRollbackOnly(); + } + + // Throw UnexpectedRollbackException if we have a global rollback-only + // marker but still didn't get a corresponding exception from commit. + if (unexpectedRollback) { + throw new UnexpectedRollbackException( + "Transaction silently rolled back because it has been marked as rollback-only"); + } + } catch (UnexpectedRollbackException ex) { + // can only be caused by doCommit + // 事务的同步状态: 回滚 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + throw ex; + } catch (TransactionException ex) { + // can only be caused by doCommit + // 提交失败 做回滚 + if (isRollbackOnCommitFailure()) { + doRollbackOnCommitException(status, ex); + } else { + // 事务的同步状态: 未知 + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + } + throw ex; + } catch (RuntimeException | Error ex) { + if (!beforeCompletionInvoked) { + triggerBeforeCompletion(status); + } + doRollbackOnCommitException(status, ex); + throw ex; + } + + // Trigger afterCommit callbacks, with an exception thrown there + // propagated to callers but the transaction still considered as committed. + try { + triggerAfterCommit(status); + } finally { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); + } + + } finally { + // 完成后清理 + cleanupAfterCompletion(status); + } +} +``` + +#### rollback 方法 + +```java +@Override +public final void rollback(TransactionStatus status) throws TransactionException { + // 是否已完成 + if (status.isCompleted()) { + throw new IllegalTransactionStateException( + "Transaction is already completed - do not call commit or rollback more than once per transaction"); + } + + DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + // 执行回滚 + processRollback(defStatus, false); +} +``` + +```java +private void processRollback(DefaultTransactionStatus status, boolean unexpected) { + try { + boolean unexpectedRollback = unexpected; + + try { + triggerBeforeCompletion(status); + + // 嵌套事务 + if (status.hasSavepoint()) { + if (status.isDebug()) { + logger.debug("Rolling back transaction to savepoint"); + } + // 回滚保存点 + status.rollbackToHeldSavepoint(); + } + // 独立事务 + else if (status.isNewTransaction()) { + if (status.isDebug()) { + logger.debug("Initiating transaction rollback"); + } + // 执行回滚 + doRollback(status); + } else { + // Participating in larger transaction + if (status.hasTransaction()) { + if (status.isLocalRollbackOnly() + || isGlobalRollbackOnParticipationFailure()) { + if (status.isDebug()) { + logger.debug( + "Participating transaction failed - marking existing transaction as rollback-only"); + } + // 设置回滚 + doSetRollbackOnly(status); + } else { + if (status.isDebug()) { + logger.debug( + "Participating transaction failed - letting transaction originator decide on rollback"); + } + } + } else { + logger.debug( + "Should roll back transaction but cannot - no transaction available"); + } + // Unexpected rollback only matters here if we're asked to fail early + if (!isFailEarlyOnGlobalRollbackOnly()) { + unexpectedRollback = false; + } + } + } catch (RuntimeException | Error ex) { + triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); + throw ex; + } + + triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); + + // Raise UnexpectedRollbackException if we had a global rollback-only marker + if (unexpectedRollback) { + throw new UnexpectedRollbackException( + "Transaction rolled back because it has been marked as rollback-only"); + } + } finally { + cleanupAfterCompletion(status); + } +} +``` + +### TransactionSynchronizationManager + +- 事务同步管理器 + +- 一些基本属性 + +```java + /** + * 资源 + */ + private static final ThreadLocal> resources = + new NamedThreadLocal<>("Transactional resources"); + /** + * 同步器 + */ + private static final ThreadLocal> synchronizations = + new NamedThreadLocal<>("Transaction synchronizations"); + + /** + * 事务名称 + */ + private static final ThreadLocal currentTransactionName = + new NamedThreadLocal<>("Current transaction name"); + + /** + * 是否只读 + */ + private static final ThreadLocal currentTransactionReadOnly = + new NamedThreadLocal<>("Current transaction read-only status"); + + /** + * 事务隔离级别 + */ + private static final ThreadLocal currentTransactionIsolationLevel = + new NamedThreadLocal<>("Current transaction isolation level"); + + /** + * 事务激活状态 + */ + private static final ThreadLocal actualTransactionActive = + new NamedThreadLocal<>("Actual transaction active"); +``` + +#### 资源方法 + +##### 获取资源 + +```java +public static Map getResourceMap() { + // 线程变量中获取 + Map map = resources.get(); + // 判空 如果为空给个空map如果有就返回 + return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap()); +} +``` + +##### 判断是否存在资源 + +```java +public static boolean hasResource(Object key) { + // 资源key获取 + // 通过 unwrapResourceIfNecessary 会走一次资源对象转换. + // 1. InfrastructureProxy + // 2. ScopedObject + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Object value = doGetResource(actualKey); + return (value != null); +} +``` + +- `unwrapResourceIfNecessary`方法会将资源具体化到接口,从接口中调用方法获取具体的资源 + + ```java + static Object unwrapResourceIfNecessary(Object resource) { + Assert.notNull(resource, "Resource must not be null"); + Object resourceRef = resource; + // unwrap infrastructure proxy + if (resourceRef instanceof InfrastructureProxy) { + resourceRef = ((InfrastructureProxy) resourceRef).getWrappedObject(); + } + if (aopAvailable) { + // now unwrap scoped proxy + resourceRef = ScopedProxyUnwrapper.unwrapIfNecessary(resourceRef); + } + return resourceRef; + } + + private static class ScopedProxyUnwrapper { + + public static Object unwrapIfNecessary(Object resource) { + if (resource instanceof ScopedObject) { + return ((ScopedObject) resource).getTargetObject(); + } else { + return resource; + } + } + } + + ``` + +- `doGetResource` 方法去获取资源 + + ```java + @Nullable + private static Object doGetResource(Object actualKey) { + Map map = resources.get(); + if (map == null) { + return null; + } + Object value = map.get(actualKey); + // Transparently remove ResourceHolder that was marked as void... + // 如果资源是下面两种的其中一个就删除这个资源 + if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { + map.remove(actualKey); + // Remove entire ThreadLocal if empty... + if (map.isEmpty()) { + resources.remove(); + } + value = null; + } + return value; + } + ``` + +##### 资源绑定 + +```java +public static void bindResource(Object key, Object value) throws IllegalStateException { + // 将资源转换为正真的key + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + Assert.notNull(value, "Value must not be null"); + Map map = resources.get(); + // set ThreadLocal Map if none found + // 资源对象为空初始化 + if (map == null) { + map = new HashMap<>(); + resources.set(map); + } + // 原来的值 + Object oldValue = map.put(actualKey, value); + // Transparently suppress a ResourceHolder that was marked as void... + // 如果原来的值是下面的两种 抛出异常 + if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) { + oldValue = null; + } + if (oldValue != null) { + throw new IllegalStateException("Already value [" + oldValue + "] for key [" + + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]"); + } + if (logger.isTraceEnabled()) { + logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + + Thread.currentThread().getName() + "]"); + } +} +``` + +- debug 使用的是 druid 的数据源 + +![image-20200729090322058](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729090322058.png) + +- `unwrapResourceIfNecessary` 方法 + +```java +static Object unwrapResourceIfNecessary(Object resource) { + Assert.notNull(resource, "Resource must not be null"); + Object resourceRef = resource; + // unwrap infrastructure proxy + if (resourceRef instanceof InfrastructureProxy) { + resourceRef = ((InfrastructureProxy) resourceRef).getWrappedObject(); + } + if (aopAvailable) { + // now unwrap scoped proxy + resourceRef = ScopedProxyUnwrapper.unwrapIfNecessary(resourceRef); + } + return resourceRef; +} +``` + +显然`com.alibaba.druid.pool.DruidDataSource`不是`InfrastructureProxy` + +- `aopAvailable` + + ```java + private static final boolean aopAvailable = ClassUtils.isPresent( + "org.springframework.aop.scope.ScopedObject", + TransactionSynchronizationUtils.class.getClassLoader()); + ``` + + ```java + public static boolean isPresent(String className, @Nullable ClassLoader classLoader) { + try { + forName(className, classLoader); + return true; + } + catch (IllegalAccessError err) { + throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + + className + "]: " + err.getMessage(), err); + } + catch (Throwable ex) { + // Typically ClassNotFoundException or NoClassDefFoundError... + return false; + } + } + ``` + + 看是否可以解析如果解析成功返回`true` 解析失败返回`false` + +- `ScopedProxyUnwrapper.unwrapIfNecessary` + + ```java + private static class ScopedProxyUnwrapper { + + public static Object unwrapIfNecessary(Object resource) { + if (resource instanceof ScopedObject) { + return ((ScopedObject) resource).getTargetObject(); + } else { + return resource; + } + } + } + ``` + + - `com.alibaba.druid.pool.DruidDataSource`不是`ScopedObject` 直接返回 + +后续就是一个`map`的`put`方法不具体展开 + +##### 解除资源绑定 + +```java +public static Object unbindResource(Object key) throws IllegalStateException { + // 获取真正的资源对象 + Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); + // map 移除key + Object value = doUnbindResource(actualKey); + if (value == null) { + throw new IllegalStateException( + "No value for key [" + actualKey + "] bound to thread [" + Thread + .currentThread().getName() + "]"); + } + return value; +} + + + @Nullable + private static Object doUnbindResource(Object actualKey) { + Map map = resources.get(); + if (map == null) { + return null; + } + Object value = map.remove(actualKey); + // Remove entire ThreadLocal if empty... + if (map.isEmpty()) { + resources.remove(); + } + // Transparently suppress a ResourceHolder that was marked as void... + if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { + value = null; + } + if (value != null && logger.isTraceEnabled()) { + logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" + + Thread.currentThread().getName() + "]"); + } + return value; + } + +``` + +map 对象的 remove 操作 + +#### 其他 + +- 其他几个都是使用`ThreadLocal`进行数据设置操作即可. + +--- + +### TransactionTemplate + +- 属性 + + ```java + @Nullable + private PlatformTransactionManager transactionManager; + + ``` + + 前文说到 `DataSourceTransactionManager` 实现了 `PlatformTransactionManager` 因此配置的时候我们有如下片段 + + ```xml + + + + ``` + +- 事务操作模板类图 + + ![image-20200728094658684](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200728094658684.png) + +- `org.springframework.beans.factory.InitializingBean`接口的实现 + + ```java + @Override + public void afterPropertiesSet() { + if (this.transactionManager == null) { + throw new IllegalArgumentException("Property 'transactionManager' is required"); + } + } + ``` + +#### execute + +```java + @Override + @Nullable + public T execute(TransactionCallback action) throws TransactionException { + Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); + + // 事务管理是否是 xxx接口 + if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { +// 强转执行 + return ((CallbackPreferringPlatformTransactionManager) this.transactionManager) + .execute(this, action); + } else { + // 获取事务状态 + TransactionStatus status = this.transactionManager.getTransaction(this); + // 返回结果 + T result; + try { + // 事务回调执行 + result = action.doInTransaction(status); + } catch (RuntimeException | Error ex) { + // Transactional code threw application exception -> rollback + // 回滚异常 + rollbackOnException(status, ex); + throw ex; + } catch (Throwable ex) { + // Transactional code threw unexpected exception -> rollback + // 回滚异常 + rollbackOnException(status, ex); + throw new UndeclaredThrowableException(ex, + "TransactionCallback threw undeclared checked exception"); + } + // 提交 + this.transactionManager.commit(status); + return result; + } + } +``` diff --git a/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md b/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md new file mode 100644 index 00000000..444300f2 --- /dev/null +++ b/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md @@ -0,0 +1,27 @@ +# Spring PlaceholderResolver + +- 类全路径: `org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver` + +- 类作用将占位符中的内容替换成属性值. + - 假设现有属性表: user.dir = c:\home + 传入参数 user.dir 会获得 c:\home + +```java + + @FunctionalInterface + public interface PlaceholderResolver { + + /** + * Resolve the supplied placeholder name to the replacement value. + * @param placeholderName the name of the placeholder to resolve + * @return the replacement value, or {@code null} if no replacement is to be made + */ + @Nullable + String resolvePlaceholder(String placeholderName); + } + +``` + +- 类图如下 + +![PropertyPlaceholderConfigurerResolver](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyPlaceholderConfigurerResolver.png) diff --git a/docs/Spring/clazz/PlaceholderResolver/Spring-PropertyPlaceholderConfigurerResolver.md b/docs/Spring/clazz/PlaceholderResolver/Spring-PropertyPlaceholderConfigurerResolver.md new file mode 100644 index 00000000..9edc0d9e --- /dev/null +++ b/docs/Spring/clazz/PlaceholderResolver/Spring-PropertyPlaceholderConfigurerResolver.md @@ -0,0 +1,72 @@ +# Spring PropertyPlaceholderConfigurerResolver + +- 类全路径: `org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.PropertyPlaceholderConfigurerResolver` + +- 这个类是从 Properties 中获取属性 + +```java + private final class PropertyPlaceholderConfigurerResolver implements PlaceholderResolver { + + private final Properties props; + + private PropertyPlaceholderConfigurerResolver(Properties props) { + this.props = props; + } + + @Override + @Nullable + public String resolvePlaceholder(String placeholderName) { + return PropertyPlaceholderConfigurer.this.resolvePlaceholder(placeholderName, + this.props, systemPropertiesMode); + } + } + +``` + +- 详细方法如下 + +```java + + @Nullable + protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) { + String propVal = null; + if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) { + propVal = resolveSystemProperty(placeholder); + } + if (propVal == null) { + propVal = resolvePlaceholder(placeholder, props); + } + if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) { + propVal = resolveSystemProperty(placeholder); + } + return propVal; + } + +``` + +```java + @Nullable + protected String resolvePlaceholder(String placeholder, Properties props) { + return props.getProperty(placeholder); + } +``` + +```java + @Nullable + protected String resolveSystemProperty(String key) { + try { + String value = System.getProperty(key); + if (value == null && this.searchSystemEnvironment) { + value = System.getenv(key); + } + return value; + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not access system property '" + key + "': " + ex); + } + return null; + } + } + +``` diff --git a/docs/Spring/clazz/PlaceholderResolver/Spring-ServletContextPlaceholderResolver.md b/docs/Spring/clazz/PlaceholderResolver/Spring-ServletContextPlaceholderResolver.md new file mode 100644 index 00000000..2acae56f --- /dev/null +++ b/docs/Spring/clazz/PlaceholderResolver/Spring-ServletContextPlaceholderResolver.md @@ -0,0 +1,42 @@ +# Spring ServletContextPlaceholderResolver + +- 类全路径: `org.springframework.web.util.ServletContextPropertyUtils.ServletContextPlaceholderResolver` + +```java + private static class ServletContextPlaceholderResolver + implements PropertyPlaceholderHelper.PlaceholderResolver { + + private final String text; + + private final ServletContext servletContext; + + public ServletContextPlaceholderResolver(String text, ServletContext servletContext) { + this.text = text; + this.servletContext = servletContext; + } + + @Override + @Nullable + public String resolvePlaceholder(String placeholderName) { + try { + // servlet 上下文获取 + String propVal = this.servletContext.getInitParameter(placeholderName); + if (propVal == null) { + // Fall back to system properties. + propVal = System.getProperty(placeholderName); + if (propVal == null) { + // Fall back to searching the system environment. + propVal = System.getenv(placeholderName); + } + } + return propVal; + } + catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + + this.text + "] as ServletContext init-parameter or system property: " + ex); + return null; + } + } + } + +``` diff --git a/docs/Spring/clazz/PlaceholderResolver/Spring-SystemPropertyPlaceholderResolver.md b/docs/Spring/clazz/PlaceholderResolver/Spring-SystemPropertyPlaceholderResolver.md new file mode 100644 index 00000000..f000b72e --- /dev/null +++ b/docs/Spring/clazz/PlaceholderResolver/Spring-SystemPropertyPlaceholderResolver.md @@ -0,0 +1,34 @@ +# Spring SystemPropertyPlaceholderResolver + +- 类全路径: `org.springframework.util.SystemPropertyUtils.SystemPropertyPlaceholderResolver` + +```java + private static class SystemPropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver { + + private final String text; + + public SystemPropertyPlaceholderResolver(String text) { + this.text = text; + } + + @Override + @Nullable + public String resolvePlaceholder(String placeholderName) { + try { + String propVal = System.getProperty(placeholderName); + if (propVal == null) { + // Fall back to searching the system environment. + // 获取系统属性 + propVal = System.getenv(placeholderName); + } + return propVal; + } + catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + + this.text + "] as system property: " + ex); + return null; + } + } + } + +``` diff --git a/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png b/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png new file mode 100644 index 00000000..4e34347e Binary files /dev/null and b/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png differ diff --git a/docs/Spring/clazz/PropertySource/Spring-CommandLinePropertySource.md b/docs/Spring/clazz/PropertySource/Spring-CommandLinePropertySource.md new file mode 100644 index 00000000..b2d280fb --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-CommandLinePropertySource.md @@ -0,0 +1,116 @@ +# Spring CommandLinePropertySource + +- 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 extends EnumerablePropertySource { + + public static final String COMMAND_LINE_PROPERTY_SOURCE_NAME = "commandLineArgs"; + + public static final String DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME = "nonOptionArgs"; + + + private String nonOptionArgsPropertyName = DEFAULT_NON_OPTION_ARGS_PROPERTY_NAME; + + + + public CommandLinePropertySource(T source) { + // 命令行参数, 属性值 + super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source); + } + + public CommandLinePropertySource(String name, T source) { + // 参数名称, 参数值 + super(name, source); + } + + + public void setNonOptionArgsPropertyName(String nonOptionArgsPropertyName) { + this.nonOptionArgsPropertyName = nonOptionArgsPropertyName; + } + + @Override + public final boolean containsProperty(String name) { + // 输入值是否等于nonOptionArgs + if (this.nonOptionArgsPropertyName.equals(name)) { + // 等于后判断参数列表是否为空 + return !this.getNonOptionArgs().isEmpty(); + } + // 是否存在 name 属性 + return this.containsOption(name); + } + + @Override + @Nullable + public final String getProperty(String name) { + if (this.nonOptionArgsPropertyName.equals(name)) { + // 获取 非可选项参数列表 + Collection nonOptionArguments = this.getNonOptionArgs(); + if (nonOptionArguments.isEmpty()) { + return null; + } + else { + // 可选参数命令行参数 + return StringUtils.collectionToCommaDelimitedString(nonOptionArguments); + } + } + Collection optionValues = this.getOptionValues(name); + if (optionValues == null) { + return null; + } + else { + // 命令行参数 + return StringUtils.collectionToCommaDelimitedString(optionValues); + } + } + + + /** + * 是否存在 name 的命令行参数 + */ + protected abstract boolean containsOption(String name); + + /** + * 获取参数列表集合 + */ + @Nullable + protected abstract List getOptionValues(String name); + + /** + * 获取 non-option 参数列表 + */ + protected abstract List getNonOptionArgs(); + +} +``` + +## getOptionValues + +```java +/** + * Return the collection of values associated with the command line option having the + * given name. + *
    + *
  • if the option is present and has no argument (e.g.: "--foo"), return an empty + * collection ({@code []})
  • + *
  • if the option is present and has a single value (e.g. "--foo=bar"), return a + * collection having one element ({@code ["bar"]})
  • + *
  • if the option is present and the underlying command line parsing library + * supports multiple arguments (e.g. "--foo=bar --foo=baz"), return a collection + * having elements for each value ({@code ["bar", "baz"]})
  • + *
  • if the option is not present, return {@code null}
  • + *
+ * + * 获取参数列表集合 + */ +@Nullable +protected abstract List getOptionValues(String name); +``` + +阅读注释可以知道该方法可以获取命令行参数的列表. + +- 如 `--foo`作为开头当输入命令行为 `--foo=bar --foo=baz` 在输入参数名称 `foo` 会得到数据`bar,baz` diff --git a/docs/Spring/clazz/PropertySource/Spring-ComparisonPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-ComparisonPropertySource.md new file mode 100644 index 00000000..87c5a147 --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-ComparisonPropertySource.md @@ -0,0 +1,43 @@ +# Spring ComparisonPropertySource + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 整体代码如下. + - 下面几个调用方法会直接抛出异常 + 1. getSource + 1. containsProperty + 1. getProperty + +```java + static class ComparisonPropertySource extends StubPropertySource { + + // 异常信息 + private static final String USAGE_ERROR = + "ComparisonPropertySource instances are for use with collection comparison only"; + + public ComparisonPropertySource(String name) { + super(name); + } + + @Override + public Object getSource() { + // 抛异常 + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + public boolean containsProperty(String name) { + // 抛异常 + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + @Nullable + public String getProperty(String name) { + // 抛异常 + throw new UnsupportedOperationException(USAGE_ERROR); + } + } + +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-CompositePropertySource.md b/docs/Spring/clazz/PropertySource/Spring-CompositePropertySource.md new file mode 100644 index 00000000..49a7420f --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-CompositePropertySource.md @@ -0,0 +1,106 @@ +# 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 { + + /** + * set 集合 + */ + private final Set> propertySources = new LinkedHashSet<>(); + + + /** + * Create a new {@code CompositePropertySource}. + * @param name the name of the property source + */ + public CompositePropertySource(String name) { + super(name); + } + + + @Override + @Nullable + public Object getProperty(String name) { + // 循环 + for (PropertySource propertySource : this.propertySources) { + // 获取存储内容 + Object candidate = propertySource.getProperty(name); + if (candidate != null) { + return candidate; + } + } + return null; + } + + @Override + public boolean containsProperty(String name) { + for (PropertySource propertySource : this.propertySources) { + // 是否存在name + if (propertySource.containsProperty(name)) { + return true; + } + } + return false; + } + + @Override + public String[] getPropertyNames() { + Set names = new LinkedHashSet<>(); + for (PropertySource propertySource : this.propertySources) { + // 类型不同抛出异常 + if (!(propertySource instanceof EnumerablePropertySource)) { + throw new IllegalStateException( + "Failed to enumerate property names due to non-enumerable property source: " + propertySource); + } + // 批量添加 + names.addAll(Arrays.asList(((EnumerablePropertySource) propertySource).getPropertyNames())); + } + // 转换成 array + return StringUtils.toStringArray(names); + } + + + /** + * Add the given {@link PropertySource} to the end of the chain. + * @param propertySource the PropertySource to add + */ + public void addPropertySource(PropertySource propertySource) { + this.propertySources.add(propertySource); + } + + /** + * Add the given {@link PropertySource} to the start of the chain. + * @param propertySource the PropertySource to add + * @since 4.1 + */ + public void addFirstPropertySource(PropertySource propertySource) { + // 头插 + List> existing = new ArrayList<>(this.propertySources); + this.propertySources.clear(); + this.propertySources.add(propertySource); + this.propertySources.addAll(existing); + } + + /** + * Return all property sources that this composite source holds. + * @since 4.1.1 + */ + public Collection> getPropertySources() { + return this.propertySources; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + " {name='" + this.name + "', propertySources=" + this.propertySources + "}"; + } + +} +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-EnumerablePropertySource.md b/docs/Spring/clazz/PropertySource/Spring-EnumerablePropertySource.md new file mode 100644 index 00000000..945d010a --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-EnumerablePropertySource.md @@ -0,0 +1,48 @@ +# Spring EnumerablePropertySource + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 全路径: `org.springframework.core.env.EnumerablePropertySource` +- 在这个类中定义了一个抽象方法`getPropertyNames` 用来获取所有的 property 的名称 + +```java + public abstract String[] getPropertyNames(); +``` + +- 整体代码如下 + +```java +public abstract class EnumerablePropertySource extends PropertySource { + + public EnumerablePropertySource(String name, T source) { + super(name, source); + } + + protected EnumerablePropertySource(String name) { + super(name); + } + + + /** + * Return whether this {@code PropertySource} contains a property with the given name. + *

This implementation checks for the presence of the given name within the + * {@link #getPropertyNames()} array. + * + * 在属性列表中是否存在 properties + * @param name the name of the property to find + */ + @Override + public boolean containsProperty(String name) { + return ObjectUtils.containsElement(getPropertyNames(), name); + } + + /** + * Return the names of all properties contained by the + * 获取所有的 properties 名称 + * {@linkplain #getSource() source} object (never {@code null}). + */ + public abstract String[] getPropertyNames(); + +} +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-MapPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-MapPropertySource.md new file mode 100644 index 00000000..18c9a56d --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-MapPropertySource.md @@ -0,0 +1,41 @@ +# Spring MapPropertySource + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 类全路径: `org.springframework.core.env.MapPropertySource` +- 内部数据结构是一个`Map` + 这是一个对 map 的操作. +- 整体代码如下. + +```java + +public class MapPropertySource extends EnumerablePropertySource> { + + public MapPropertySource(String name, Map source) { + super(name, source); + } + + + @Override + @Nullable + public Object getProperty(String name) { + // 从map中获取 name 对应的value + return this.source.get(name); + } + + @Override + public boolean containsProperty(String name) { + // 判断是否存在 name 属性 + return this.source.containsKey(name); + } + + @Override + public String[] getPropertyNames() { + // 互殴去 map 的所有key + return StringUtils.toStringArray(this.source.keySet()); + } + +} + +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-MockPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-MockPropertySource.md new file mode 100644 index 00000000..b3d5ceef --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-MockPropertySource.md @@ -0,0 +1,96 @@ +# 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) { + this.setProperty(name, value); + return this; +} +``` + +## setProperty + +```java +public void setProperty(String name, Object value) { + this.source.put(name, value); +} +``` + +## 完整代码 + +```java +public class MockPropertySource extends PropertiesPropertySource { + + /** + * {@value} is the default name for {@link MockPropertySource} instances not + * otherwise given an explicit name. + * @see #MockPropertySource() + * @see #MockPropertySource(String) + */ + public static final String MOCK_PROPERTIES_PROPERTY_SOURCE_NAME = "mockProperties"; + + /** + * Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME} + * that will maintain its own internal {@link Properties} instance. + */ + public MockPropertySource() { + this(new Properties()); + } + + /** + * Create a new {@code MockPropertySource} with the given name that will + * maintain its own internal {@link Properties} instance. + * @param name the {@linkplain #getName() name} of the property source + */ + public MockPropertySource(String name) { + this(name, new Properties()); + } + + /** + * Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME} + * and backed by the given {@link Properties} object. + * @param properties the properties to use + */ + public MockPropertySource(Properties properties) { + this(MOCK_PROPERTIES_PROPERTY_SOURCE_NAME, properties); + } + + /** + * Create a new {@code MockPropertySource} with the given name and backed by the given + * {@link Properties} object. + * @param name the {@linkplain #getName() name} of the property source + * @param properties the properties to use + */ + public MockPropertySource(String name, Properties properties) { + super(name, properties); + } + + /** + * Set the given property on the underlying {@link Properties} object. + */ + public void setProperty(String name, Object value) { + // map 操作 + this.source.put(name, value); + } + + /** + * Convenient synonym for {@link #setProperty} that returns the current instance. + * Useful for method chaining and fluent-style use. + * 设置属性名称和属性值 + * @return this {@link MockPropertySource} instance + */ + public MockPropertySource withProperty(String name, Object value) { + this.setProperty(name, value); + return this; + } + +} +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-PropertiesPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-PropertiesPropertySource.md new file mode 100644 index 00000000..28e55abe --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-PropertiesPropertySource.md @@ -0,0 +1,33 @@ +# 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 的方法了 + - map.keySet() + +```java +public class PropertiesPropertySource extends MapPropertySource { + + @SuppressWarnings({"rawtypes", "unchecked"}) + public PropertiesPropertySource(String name, Properties source) { + super(name, (Map) source); + } + + protected PropertiesPropertySource(String name, Map source) { + super(name, source); + } + + + @Override + public String[] getPropertyNames() { + synchronized (this.source) { + return super.getPropertyNames(); + } + } + +} +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-ResourcePropertySource.md b/docs/Spring/clazz/PropertySource/Spring-ResourcePropertySource.md new file mode 100644 index 00000000..745df151 --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-ResourcePropertySource.md @@ -0,0 +1,223 @@ +# 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 结构 + +## getNameForResource + +```java +private static String getNameForResource(Resource resource) { + // 获取 resource 的介绍 + String name = resource.getDescription(); + if (!StringUtils.hasText(name)) { + // 短类名+@+hashcode + name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource); + } + return name; +} +``` + +## withName + +- 创建 ResourcePropertySource 对象, 根据 name 属性 + +```java +public ResourcePropertySource withName(String name) { + if (this.name.equals(name)) { + return this; + } + // Store the original resource name if necessary... + if (this.resourceName != null) { + if (this.resourceName.equals(name)) { + return new ResourcePropertySource(this.resourceName, null, this.source); + } + else { + return new ResourcePropertySource(name, this.resourceName, this.source); + } + } + else { + // Current name is resource name -> preserve it in the extra field... + return new ResourcePropertySource(name, this.name, this.source); + } +} +``` + +## 构造函数 + +- 通过 location 字符串读取 resource + +```java +public ResourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException { + // 默认资源读取器读取 location 转换成 resource + this(name, new DefaultResourceLoader(classLoader).getResource(location)); +} +``` + +- 读取 resource 信息进行存储 + +```java +public ResourcePropertySource(String name, EncodedResource resource) throws IOException { + // 设置 name + map 对象 + // map 对象是 资源信息 + super(name, PropertiesLoaderUtils.loadProperties(resource)); + // 获取 resource name + this.resourceName = getNameForResource(resource.getResource()); +} +``` + +## 完整代码 + +```java +public class ResourcePropertySource extends PropertiesPropertySource { + + /** The original resource name, if different from the given name. */ + @Nullable + private final String resourceName; + + + /** + * Create a PropertySource having the given name based on Properties + * loaded from the given encoded resource. + */ + public ResourcePropertySource(String name, EncodedResource resource) throws IOException { + // 设置 name + map 对象 + // map 对象是 资源信息 + super(name, PropertiesLoaderUtils.loadProperties(resource)); + // 获取 resource name + this.resourceName = getNameForResource(resource.getResource()); + } + + /** + * Create a PropertySource based on Properties loaded from the given resource. + * The name of the PropertySource will be generated based on the + * {@link Resource#getDescription() description} of the given resource. + */ + public ResourcePropertySource(EncodedResource resource) throws IOException { + // 设置 key: name, resource 的 name + // 设置 value: resource 资源信息 + super(getNameForResource(resource.getResource()), PropertiesLoaderUtils.loadProperties(resource)); + this.resourceName = null; + } + + /** + * Create a PropertySource having the given name based on Properties + * loaded from the given encoded resource. + */ + public ResourcePropertySource(String name, Resource resource) throws IOException { + super(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource))); + this.resourceName = getNameForResource(resource); + } + + /** + * Create a PropertySource based on Properties loaded from the given resource. + * The name of the PropertySource will be generated based on the + * {@link Resource#getDescription() description} of the given resource. + */ + public ResourcePropertySource(Resource resource) throws IOException { + super(getNameForResource(resource), PropertiesLoaderUtils.loadProperties(new EncodedResource(resource))); + this.resourceName = null; + } + + /** + * Create a PropertySource having the given name based on Properties loaded from + * the given resource location and using the given class loader to load the + * resource (assuming it is prefixed with {@code classpath:}). + */ + public ResourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException { + // 默认资源读取器读取 location 转换成 resource + this(name, new DefaultResourceLoader(classLoader).getResource(location)); + } + + /** + * Create a PropertySource based on Properties loaded from the given resource + * location and use the given class loader to load the resource, assuming it is + * prefixed with {@code classpath:}. The name of the PropertySource will be + * generated based on the {@link Resource#getDescription() description} of the + * resource. + */ + public ResourcePropertySource(String location, ClassLoader classLoader) throws IOException { + this(new DefaultResourceLoader(classLoader).getResource(location)); + } + + /** + * Create a PropertySource having the given name based on Properties loaded from + * the given resource location. The default thread context class loader will be + * used to load the resource (assuming the location string is prefixed with + * {@code classpath:}. + */ + public ResourcePropertySource(String name, String location) throws IOException { + this(name, new DefaultResourceLoader().getResource(location)); + } + + /** + * Create a PropertySource based on Properties loaded from the given resource + * location. The name of the PropertySource will be generated based on the + * {@link Resource#getDescription() description} of the resource. + */ + public ResourcePropertySource(String location) throws IOException { + this(new DefaultResourceLoader().getResource(location)); + } + + private ResourcePropertySource(String name, @Nullable String resourceName, Map source) { + super(name, source); + this.resourceName = resourceName; + } + + /** + * Return the description for the given Resource; if the description is + * empty, return the class name of the resource plus its identity hash code. + * @see org.springframework.core.io.Resource#getDescription() + */ + private static String getNameForResource(Resource resource) { + // 获取 resource 的介绍 + String name = resource.getDescription(); + if (!StringUtils.hasText(name)) { + // 短类名+@+hashcode + name = resource.getClass().getSimpleName() + "@" + System.identityHashCode(resource); + } + return name; + } + + /** + * Return a potentially adapted variant of this {@link ResourcePropertySource}, + * overriding the previously given (or derived) name with the specified name. + * @since 4.0.4 + */ + public ResourcePropertySource withName(String name) { + if (this.name.equals(name)) { + return this; + } + // Store the original resource name if necessary... + if (this.resourceName != null) { + if (this.resourceName.equals(name)) { + return new ResourcePropertySource(this.resourceName, null, this.source); + } + else { + return new ResourcePropertySource(name, this.resourceName, this.source); + } + } + else { + // Current name is resource name -> preserve it in the extra field... + return new ResourcePropertySource(name, this.name, this.source); + } + } + + /** + * Return a potentially adapted variant of this {@link ResourcePropertySource}, + * overriding the previously given name (if any) with the original resource name + * (equivalent to the name generated by the name-less constructor variants). + * @since 4.1 + */ + public ResourcePropertySource withResourceName() { + if (this.resourceName == null) { + return this; + } + return new ResourcePropertySource(this.resourceName, null, this.source); + } + +} +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-ServletConfigPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-ServletConfigPropertySource.md new file mode 100644 index 00000000..37d44b2d --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-ServletConfigPropertySource.md @@ -0,0 +1,34 @@ +# Spring ServletConfigPropertySource + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 类全路径: `org.springframework.web.context.support.ServletConfigPropertySource` +- 内部数据结构是 `ServletConfig` + +- 整体代码如下 + +```java + +public class ServletConfigPropertySource extends EnumerablePropertySource { + + public ServletConfigPropertySource(String name, ServletConfig servletConfig) { + super(name, servletConfig); + } + + @Override + public String[] getPropertyNames() { + // javax.servlet.ServletConfig.getInitParameterNames + return StringUtils.toStringArray(this.source.getInitParameterNames()); + } + + @Override + @Nullable + public String getProperty(String name) { + // javax.servlet.ServletConfig.getInitParameter + return this.source.getInitParameter(name); + } + +} + +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-ServletContextPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-ServletContextPropertySource.md new file mode 100644 index 00000000..e1b34f6a --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-ServletContextPropertySource.md @@ -0,0 +1,33 @@ +# 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 { + + public ServletContextPropertySource(String name, ServletContext servletContext) { + super(name, servletContext); + } + + @Override + public String[] getPropertyNames() { + // javax.servlet.ServletContext.getInitParameterNames 方法调用 + return StringUtils.toStringArray(this.source.getInitParameterNames()); + } + + @Override + @Nullable + public String getProperty(String name) { + // javax.servlet.ServletContext.getInitParameter + return this.source.getInitParameter(name); + } + +} + +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-SimpleCommandLineArgsParser.md b/docs/Spring/clazz/PropertySource/Spring-SimpleCommandLineArgsParser.md new file mode 100644 index 00000000..5fa5c1a1 --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-SimpleCommandLineArgsParser.md @@ -0,0 +1,53 @@ +# 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 { + + /** + * Parse the given {@code String} array based on the rules described {@linkplain + * SimpleCommandLineArgsParser above}, returning a fully-populated + * {@link CommandLineArgs} object. + * @param args command line arguments, typically from a {@code main()} method + */ + public CommandLineArgs parse(String... args) { + CommandLineArgs commandLineArgs = new CommandLineArgs(); + for (String arg : args) { + if (arg.startsWith("--")) { + String optionText = arg.substring(2, arg.length()); + String optionName; + String optionValue = null; + if (optionText.contains("=")) { + optionName = optionText.substring(0, optionText.indexOf('=')); + optionValue = optionText.substring(optionText.indexOf('=') + 1, optionText.length()); + } + else { + optionName = optionText; + } + if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) { + throw new IllegalArgumentException("Invalid argument syntax: " + arg); + } + commandLineArgs.addOptionArg(optionName, optionValue); + } + else { + commandLineArgs.addNonOptionArg(arg); + } + } + return commandLineArgs; + } + +} + +``` + +- 处理流程 + 1. 循环命令行参数列表 + 2. 去掉 `--` 和 `=` 获取参数名称和参数值放入结果集合 diff --git a/docs/Spring/clazz/PropertySource/Spring-SimpleCommandLinePropertySource.md b/docs/Spring/clazz/PropertySource/Spring-SimpleCommandLinePropertySource.md new file mode 100644 index 00000000..1ad7df93 --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-SimpleCommandLinePropertySource.md @@ -0,0 +1,152 @@ +# Spring SimpleCommandLinePropertySource + +- 全路径: `org.springframework.core.env.SimpleCommandLinePropertySource` + +```java +public class SimpleCommandLinePropertySource extends CommandLinePropertySource {} +``` + +- SimpleCommandLinePropertySource 的 source 类型是 CommandLineArgs 具体解释请看下面分析 + +## CommandLineArgs + +两个内部属性 + +```java +class CommandLineArgs { + /** + * 选项参数列表 + */ + private final Map> optionArgs = new HashMap<>(); + + /** + * 非选项参数列表 + */ + private final List nonOptionArgs = new ArrayList<>(); + +} +``` + +### addOptionArg + +添加 选项参数 + +```java +public void addOptionArg(String optionName, @Nullable String optionValue) { + if (!this.optionArgs.containsKey(optionName)) { + this.optionArgs.put(optionName, new ArrayList<>()); + } + if (optionValue != null) { + this.optionArgs.get(optionName).add(optionValue); + } +} +``` + +### getOptionNames + +- 获取选项参数列表 + +```java +public Set getOptionNames() { + return Collections.unmodifiableSet(this.optionArgs.keySet()); +} +``` + +- 其他方法不具体描述了,各位可以查看下面的代码 + +```java +class CommandLineArgs { + + /** + * 选项参数列表 + */ + private final Map> optionArgs = new HashMap<>(); + + /** + * 非选项参数列表 + */ + private final List nonOptionArgs = new ArrayList<>(); + + /** + * Add an option argument for the given option name and add the given value to the + * list of values associated with this option (of which there may be zero or more). + * The given value may be {@code null}, indicating that the option was specified + * without an associated value (e.g. "--foo" vs. "--foo=bar"). + * + * 添加 选项参数 + */ + public void addOptionArg(String optionName, @Nullable String optionValue) { + if (!this.optionArgs.containsKey(optionName)) { + this.optionArgs.put(optionName, new ArrayList<>()); + } + if (optionValue != null) { + this.optionArgs.get(optionName).add(optionValue); + } + } + + /** + * Return the set of all option arguments present on the command line. + * 获取选项参数列表 + */ + public Set getOptionNames() { + return Collections.unmodifiableSet(this.optionArgs.keySet()); + } + + /** + * Return whether the option with the given name was present on the command line. + */ + public boolean containsOption(String optionName) { + return this.optionArgs.containsKey(optionName); + } + + /** + * Return the list of values associated with the given option. {@code null} signifies + * that the option was not present; empty list signifies that no values were associated + * with this option. + */ + @Nullable + public List getOptionValues(String optionName) { + return this.optionArgs.get(optionName); + } + + /** + * Add the given value to the list of non-option arguments. + */ + public void addNonOptionArg(String value) { + this.nonOptionArgs.add(value); + } + + /** + * Return the list of non-option arguments specified on the command line. + */ + public List getNonOptionArgs() { + return Collections.unmodifiableList(this.nonOptionArgs); + } + +} +``` + +在了解 CommandLineArgs 类后再来看 SimpleCommandLinePropertySource 会相对容易. 内部的几个方法就是调用 CommandLineArgs 所提供的方法 + +```java +@Override +public String[] getPropertyNames() { + return StringUtils.toStringArray(this.source.getOptionNames()); +} + +@Override +protected boolean containsOption(String name) { + return this.source.containsOption(name); +} + +@Override +@Nullable +protected List getOptionValues(String name) { + return this.source.getOptionValues(name); +} + +@Override +protected List getNonOptionArgs() { + return this.source.getNonOptionArgs(); +} +``` diff --git a/docs/Spring/clazz/PropertySource/Spring-StubPropertySource.md b/docs/Spring/clazz/PropertySource/Spring-StubPropertySource.md new file mode 100644 index 00000000..675fd00e --- /dev/null +++ b/docs/Spring/clazz/PropertySource/Spring-StubPropertySource.md @@ -0,0 +1,26 @@ +# Spring StubPropertySource + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 整体代码如下. + - 通过 StubPropertySource 的 getProperty 方法永远返回 null + +```java + public static class StubPropertySource extends PropertySource { + + public StubPropertySource(String name) { + super(name, new Object()); + } + + /** + * Always returns {@code null}. + */ + @Override + @Nullable + public String getProperty(String name) { + return null; + } + } + +``` diff --git a/docs/Spring/clazz/Spring-AnnotationUtils.md b/docs/Spring/clazz/Spring-AnnotationUtils.md index eed992cc..e26feb98 100644 --- a/docs/Spring/clazz/Spring-AnnotationUtils.md +++ b/docs/Spring/clazz/Spring-AnnotationUtils.md @@ -1,214 +1,224 @@ # Spring AnnotationUtils + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - `org.springframework.core.annotation.AnnotationUtils`提供了注解相关的方法 - 1. getAnnotation: 获取注解 - 1. findAnnotation: 寻找注解 - 1. getValue: 获取属性值 - 1. getDefaultValue: 获取默认值 - - + 1. getAnnotation: 获取注解 + 1. findAnnotation: 寻找注解 + 1. getValue: 获取属性值 + 1. getDefaultValue: 获取默认值 + ## getAnnotation + - 测试用例如下 + ```java - @Test - public void findMethodAnnotationOnLeaf() throws Exception { - Method m = Leaf.class.getMethod("annotatedOnLeaf"); - assertNotNull(m.getAnnotation(Order.class)); - assertNotNull(getAnnotation(m, Order.class)); - assertNotNull(findAnnotation(m, Order.class)); - } +@Test +public void findMethodAnnotationOnLeaf() throws Exception { + Method m = Leaf.class.getMethod("annotatedOnLeaf"); + assertNotNull(m.getAnnotation(Order.class)); + assertNotNull(getAnnotation(m, Order.class)); + assertNotNull(findAnnotation(m, Order.class)); +} ``` + - `org.springframework.core.annotation.AnnotationUtils.getAnnotation(java.lang.reflect.Method, java.lang.Class)` + ```java - /** - * Get a single {@link Annotation} of {@code annotationType} from the - * supplied {@link Method}, where the annotation is either present - * or meta-present on the method. - *

Correctly handles bridge {@link Method Methods} generated by the compiler. - *

Note that this method supports only a single level of meta-annotations. - * For support for arbitrary levels of meta-annotations, use - * {@link #findAnnotation(Method, Class)} instead. - * - * @param method the method to look for annotations on - * 被检查的函数 - * @param annotationType the annotation type to look for - * 需要检测的注解类型 - * @return the first matching annotation, or {@code null} if not found - * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) - * @see #getAnnotation(AnnotatedElement, Class) - */ - @Nullable - public static A getAnnotation(Method method, Class annotationType) { - // 函数 - Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - // 强制转换 - return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); - } +/** + * Get a single {@link Annotation} of {@code annotationType} from the + * supplied {@link Method}, where the annotation is either present + * or meta-present on the method. + *

Correctly handles bridge {@link Method Methods} generated by the compiler. + *

Note that this method supports only a single level of meta-annotations. + * For support for arbitrary levels of meta-annotations, use + * {@link #findAnnotation(Method, Class)} instead. + * + * @param method the method to look for annotations on + * 被检查的函数 + * @param annotationType the annotation type to look for + * 需要检测的注解类型 + * @return the first matching annotation, or {@code null} if not found + * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) + * @see #getAnnotation(AnnotatedElement, Class) + */ +@Nullable +public static A getAnnotation(Method method, Class annotationType) { + // 函数 + Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); + // 强制转换 + return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); +} ``` -- method - ![image-20200116085344737](../../../images/spring/image-20200116085344737.png) +- method + +![image-20200116085344737](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116085344737.png) - annotationType - ![image-20200116085423073](../../../images/spring/image-20200116085423073.png) +![image-20200116085423073](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116085423073.png) ```java - @Nullable - public static A getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - try { - // 获取注解 - A annotation = annotatedElement.getAnnotation(annotationType); - if (annotation == null) { - for (Annotation metaAnn : annotatedElement.getAnnotations()) { - annotation = metaAnn.annotationType().getAnnotation(annotationType); - if (annotation != null) { - break; - } +@Nullable +public static A getAnnotation(AnnotatedElement annotatedElement, Class annotationType) { + try { + // 获取注解 + A annotation = annotatedElement.getAnnotation(annotationType); + if (annotation == null) { + for (Annotation metaAnn : annotatedElement.getAnnotations()) { + annotation = metaAnn.annotationType().getAnnotation(annotationType); + if (annotation != null) { + break; } } - return (annotation != null ? synthesizeAnnotation(annotation, annotatedElement) : null); - } catch (Throwable ex) { - handleIntrospectionFailure(annotatedElement, ex); - return null; } + return (annotation != null ? synthesizeAnnotation(annotation, annotatedElement) : null); + } catch (Throwable ex) { + handleIntrospectionFailure(annotatedElement, ex); + return null; } +} ``` + - `org.springframework.core.annotation.AnnotationUtils.synthesizeAnnotation(A, java.lang.reflect.AnnotatedElement)` + ```java - public static A synthesizeAnnotation( - A annotation, @Nullable AnnotatedElement annotatedElement) { +public static A synthesizeAnnotation( + A annotation, @Nullable AnnotatedElement annotatedElement) { - return synthesizeAnnotation(annotation, (Object) annotatedElement); - } + return synthesizeAnnotation(annotation, (Object) annotatedElement); +} ``` + ```java - /** - * 注解是否存在别名,没有直接返回 - * - * @param annotation 注解 - * @param annotatedElement 函数 - * @param - * @return - */ - @SuppressWarnings("unchecked") - static A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) { - if (annotation instanceof SynthesizedAnnotation || hasPlainJavaAnnotationsOnly(annotatedElement)) { - return annotation; - } - // 具体的注解 - Class annotationType = annotation.annotationType(); - if (!isSynthesizable(annotationType)) { - return annotation; - } +/** + * 注解是否存在别名,没有直接返回 + * + * @param annotation 注解 + * @param annotatedElement 函数 + * @param + * @return + */ +@SuppressWarnings("unchecked") +static A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) { + if (annotation instanceof SynthesizedAnnotation || hasPlainJavaAnnotationsOnly(annotatedElement)) { + return annotation; + } + // 具体的注解 + Class annotationType = annotation.annotationType(); + if (!isSynthesizable(annotationType)) { + return annotation; + } - DefaultAnnotationAttributeExtractor attributeExtractor = - new DefaultAnnotationAttributeExtractor(annotation, annotatedElement); - InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); + DefaultAnnotationAttributeExtractor attributeExtractor = + new DefaultAnnotationAttributeExtractor(annotation, annotatedElement); + InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); - // Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a - // synthesizable annotation before (which needs to declare @AliasFor from the same package) - Class[] exposedInterfaces = new Class[]{annotationType, SynthesizedAnnotation.class}; - return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler); - } + // Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a + // synthesizable annotation before (which needs to declare @AliasFor from the same package) + Class[] exposedInterfaces = new Class[]{annotationType, SynthesizedAnnotation.class}; + return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler); +} ``` + -`org.springframework.core.annotation.AnnotationUtils.isSynthesizable` + ```java - @SuppressWarnings("unchecked") - private static boolean isSynthesizable(Class annotationType) { - if (hasPlainJavaAnnotationsOnly(annotationType)) { - return false; - } - // 从缓存中获取当前注解,不存在null - Boolean synthesizable = synthesizableCache.get(annotationType); - if (synthesizable != null) { - return synthesizable; - } +@SuppressWarnings("unchecked") +private static boolean isSynthesizable(Class annotationType) { + if (hasPlainJavaAnnotationsOnly(annotationType)) { + return false; + } + // 从缓存中获取当前注解,不存在null + Boolean synthesizable = synthesizableCache.get(annotationType); + if (synthesizable != null) { + return synthesizable; + } - synthesizable = Boolean.FALSE; - for (Method attribute : getAttributeMethods(annotationType)) { - if (!getAttributeAliasNames(attribute).isEmpty()) { + synthesizable = Boolean.FALSE; + for (Method attribute : getAttributeMethods(annotationType)) { + if (!getAttributeAliasNames(attribute).isEmpty()) { + synthesizable = Boolean.TRUE; + break; + } + // 获取返回值类型 + Class returnType = attribute.getReturnType(); + + // 根据返回值做不同处理 + if (Annotation[].class.isAssignableFrom(returnType)) { + Class nestedAnnotationType = + (Class) returnType.getComponentType(); + if (isSynthesizable(nestedAnnotationType)) { synthesizable = Boolean.TRUE; break; } - // 获取返回值类型 - Class returnType = attribute.getReturnType(); - - // 根据返回值做不同处理 - if (Annotation[].class.isAssignableFrom(returnType)) { - Class nestedAnnotationType = - (Class) returnType.getComponentType(); - if (isSynthesizable(nestedAnnotationType)) { - synthesizable = Boolean.TRUE; - break; - } - } else if (Annotation.class.isAssignableFrom(returnType)) { - Class nestedAnnotationType = (Class) returnType; - if (isSynthesizable(nestedAnnotationType)) { - synthesizable = Boolean.TRUE; - break; - } + } else if (Annotation.class.isAssignableFrom(returnType)) { + Class nestedAnnotationType = (Class) returnType; + if (isSynthesizable(nestedAnnotationType)) { + synthesizable = Boolean.TRUE; + break; } } - - synthesizableCache.put(annotationType, synthesizable); - return synthesizable; } + synthesizableCache.put(annotationType, synthesizable); + return synthesizable; +} + ``` - `org.springframework.core.annotation.AnnotationUtils#getAttributeMethods` ```java - static List getAttributeMethods(Class annotationType) { - List methods = attributeMethodsCache.get(annotationType); - if (methods != null) { - return methods; - } +static List getAttributeMethods(Class annotationType) { + List methods = attributeMethodsCache.get(annotationType); + if (methods != null) { + return methods; + } - methods = new ArrayList<>(); - // annotationType.getDeclaredMethods() 获取注解中的方法 - for (Method method : annotationType.getDeclaredMethods()) { - if (isAttributeMethod(method)) { - ReflectionUtils.makeAccessible(method); - methods.add(method); - } + methods = new ArrayList<>(); + // annotationType.getDeclaredMethods() 获取注解中的方法 + for (Method method : annotationType.getDeclaredMethods()) { + if (isAttributeMethod(method)) { + ReflectionUtils.makeAccessible(method); + methods.add(method); } - - // 缓存 key:注解,value:函数列表 - attributeMethodsCache.put(annotationType, methods); - // 函数列表 - return methods; } + // 缓存 key:注解,value:函数列表 + attributeMethodsCache.put(annotationType, methods); + // 函数列表 + return methods; +} + ``` -- `org.springframework.core.annotation.AnnotationUtils#isAttributeMethod` +- `org.springframework.core.annotation.AnnotationUtils#isAttributeMethod` ```java - /** - * Determine if the supplied {@code method} is an annotation attribute method. - *

- * 做3个判断 - *

    - *
  1. 函数不为空(method != null)
  2. - *
  3. 参数列表是不是空(method.getParameterCount() == 0)
  4. - *
  5. 返回类型不是void(method.getReturnType() != void.class)
  6. - *
- * - * @param method the method to check - * @return {@code true} if the method is an attribute method - * @since 4.2 - */ - static boolean isAttributeMethod(@Nullable Method method) { - return (method != null && method.getParameterCount() == 0 && method.getReturnType() != void.class); - } +/** + * Determine if the supplied {@code method} is an annotation attribute method. + *

+ * 做3个判断 + *

    + *
  1. 函数不为空(method != null)
  2. + *
  3. 参数列表是不是空(method.getParameterCount() == 0)
  4. + *
  5. 返回类型不是void(method.getReturnType() != void.class)
  6. + *
+ * + * @param method the method to check + * @return {@code true} if the method is an attribute method + * @since 4.2 + */ +static boolean isAttributeMethod(@Nullable Method method) { + return (method != null && method.getParameterCount() == 0 && method.getReturnType() !=void.class); +} ``` @@ -216,24 +226,24 @@ ```java @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Method method) { - // 1. 方法修饰符是不是public - // 2. 注解是不是public - // 3. 是否重写 - if ((!Modifier.isPublic(method.getModifiers()) || - !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { - method.setAccessible(true); - } +public static void makeAccessible(Method method) { + // 1. 方法修饰符是不是public + // 2. 注解是不是public + // 3. 是否重写 + if ((!Modifier.isPublic(method.getModifiers()) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { + method.setAccessible(true); } +} ``` 处理结果 -![image-20200116085726577](../../../images/spring/image-20200116085726577.png) +![image-20200116085726577](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116085726577.png) -![image-20200116085737632](../../../images/spring/image-20200116085737632.png) +![image-20200116085737632](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116085737632.png) -处理结果和Order定义相同 +处理结果和 Order 定义相同 ```java @Retention(RetentionPolicy.RUNTIME) @@ -253,266 +263,227 @@ public @interface Order { } ``` - - 最终返回 -![image-20200116085927359](../../../images/spring/image-20200116085927359.png) - +![image-20200116085927359](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116085927359.png) +## findAnnotation +- `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.Method, java.lang.Class
)` +```java +@SuppressWarnings("unchecked") +@Nullable +public static A findAnnotation(Method method, @Nullable Class annotationType) { + Assert.notNull(method, "Method must not be null"); + if (annotationType == null) { + return null; + } + // 创建注解缓存,key:被扫描的函数,value:注解 + AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType); + // 从findAnnotationCache获取缓存 + A result = (A) findAnnotationCache.get(cacheKey); + if (result == null) { + Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); + // 寻找注解 + result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType); + if (result == null) { + result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces()); + } + Class clazz = method.getDeclaringClass(); + while (result == null) { + clazz = clazz.getSuperclass(); + if (clazz == null || clazz == Object.class) { + break; + } + Set annotatedMethods = getAnnotatedMethodsInBaseType(clazz); + if (!annotatedMethods.isEmpty()) { + for (Method annotatedMethod : annotatedMethods) { + if (isOverride(method, annotatedMethod)) { + Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod); + result = findAnnotation((AnnotatedElement) resolvedSuperMethod, annotationType); + if (result != null) { + break; + } + } + } + } + if (result == null) { + result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); + } + } + if (result != null) { + // 处理注解 + result = synthesizeAnnotation(result, method); + // 添加缓存 + findAnnotationCache.put(cacheKey, result); + } + } + // 返回 + return result; +} +``` -## findAnnotation +- `org.springframework.core.annotation.AnnotationUtils.AnnotationCacheKey` -- `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.Method, java.lang.Class)` +```java +private static final class AnnotationCacheKey implements Comparable { - ```java - @SuppressWarnings("unchecked") - @Nullable - public static A findAnnotation(Method method, @Nullable Class annotationType) { - Assert.notNull(method, "Method must not be null"); - if (annotationType == null) { - return null; - } - // 创建注解缓存,key:被扫描的函数,value:注解 - AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType); - // 从findAnnotationCache获取缓存 - A result = (A) findAnnotationCache.get(cacheKey); - - if (result == null) { - Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - // 寻找注解 - result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType); - if (result == null) { - result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces()); - } - - Class clazz = method.getDeclaringClass(); - while (result == null) { - clazz = clazz.getSuperclass(); - if (clazz == null || clazz == Object.class) { - break; - } - Set annotatedMethods = getAnnotatedMethodsInBaseType(clazz); - if (!annotatedMethods.isEmpty()) { - for (Method annotatedMethod : annotatedMethods) { - if (isOverride(method, annotatedMethod)) { - Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod); - result = findAnnotation((AnnotatedElement) resolvedSuperMethod, annotationType); - if (result != null) { - break; - } - } - } - } - if (result == null) { - result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); - } - } - - if (result != null) { - // 处理注解 - result = synthesizeAnnotation(result, method); - // 添加缓存 - findAnnotationCache.put(cacheKey, result); - } - } - // 返回 - return result; - } - - ``` - - + /** + * 带有注解的函数或者类 + */ + private final AnnotatedElement element; -- `org.springframework.core.annotation.AnnotationUtils.AnnotationCacheKey` + /** + * 注解 + */ + private final Class annotationType; - ```java - private static final class AnnotationCacheKey implements Comparable { - - /** - * 带有注解的函数或者类 - */ - private final AnnotatedElement element; - - /** - * 注解 - */ - private final Class annotationType; - - public AnnotationCacheKey(AnnotatedElement element, Class annotationType) { - this.element = element; - this.annotationType = annotationType; - } - + public AnnotationCacheKey(AnnotatedElement element, Class annotationType) { + this.element = element; + this.annotationType = annotationType; } - ``` - +} +``` - `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class)` - ```java - @Nullable - public static A findAnnotation( - AnnotatedElement annotatedElement, @Nullable Class annotationType) { - // 注解类型不为空 - if (annotationType == null) { - return null; - } - - // Do NOT store result in the findAnnotationCache since doing so could break - // findAnnotation(Class, Class) and findAnnotation(Method, Class). - // 寻找注解 - A ann = findAnnotation(annotatedElement, annotationType, new HashSet<>()); - return (ann != null ? synthesizeAnnotation(ann, annotatedElement) : null); - } - - ``` - - +```java +@Nullable +public static A findAnnotation( + AnnotatedElement annotatedElement, @Nullable Class annotationType) { + // 注解类型不为空 + if (annotationType == null) { + return null; + } + // Do NOT store result in the findAnnotationCache since doing so could break + // findAnnotation(Class, Class) and findAnnotation(Method, Class). + // 寻找注解 + A ann = findAnnotation(annotatedElement, annotationType, new HashSet<>()); + return (ann != null ? synthesizeAnnotation(ann, annotatedElement) : null); +} +``` - `org.springframework.core.annotation.AnnotationUtils#findAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class, java.util.Set)` - ```java - @Nullable - private static A findAnnotation( - AnnotatedElement annotatedElement, Class annotationType, Set visited) { - try { - // 直接获取注解 - A annotation = annotatedElement.getDeclaredAnnotation(annotationType); - if (annotation != null) { - return annotation; - } - // 多级注解 - for (Annotation declaredAnn : getDeclaredAnnotations(annotatedElement)) { - Class declaredType = declaredAnn.annotationType(); - // 注解是否 由java.lang.annotation提供 - if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) { - annotation = findAnnotation((AnnotatedElement) declaredType, annotationType, visited); - if (annotation != null) { - return annotation; - } - } - } - } catch (Throwable ex) { - handleIntrospectionFailure(annotatedElement, ex); - } - return null; - } - - ``` - - - -![image-20200116092259944](../../../images/spring/image-20200116092259944.png) - -- `synthesizeAnnotation`方法就不再重复一遍了可以看上文 - - - +```java +@Nullable +private static A findAnnotation( + AnnotatedElement annotatedElement, Class annotationType, Set visited) { + try { + // 直接获取注解 + A annotation = annotatedElement.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return annotation; + } + // 多级注解 + for (Annotation declaredAnn : getDeclaredAnnotations(annotatedElement)) { + Class declaredType = declaredAnn.annotationType(); + // 注解是否 由java.lang.annotation提供 + if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) { + annotation = findAnnotation((AnnotatedElement) declaredType, annotationType, visited); + if (annotation != null) { + return annotation; + } + } + } + } catch (Throwable ex) { + handleIntrospectionFailure(annotatedElement, ex); + } + return null; +} +``` +![image-20200116092259944](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116092259944.png) +- `synthesizeAnnotation`方法就不再重复一遍了可以看上文 ## getValue - 测试用例 - ```java - @Test - public void getValueFromAnnotation() throws Exception { - Method method = SimpleFoo.class.getMethod("something", Object.class); - Order order = findAnnotation(method, Order.class); - - assertEquals(1, getValue(order, VALUE)); - assertEquals(1, getValue(order)); - } - ``` - +```java +@Test +public void getValueFromAnnotation() throws Exception { + Method method = SimpleFoo.class.getMethod("something", Object.class); + Order order = findAnnotation(method, Order.class); + assertEquals(1, getValue(order, VALUE)); + assertEquals(1, getValue(order)); +} +``` - `org.springframework.core.annotation.AnnotationUtils#getValue(java.lang.annotation.Annotation, java.lang.String)` ```java - @Nullable - public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) { - if (annotation == null || !StringUtils.hasText(attributeName)) { - return null; - } - try { - // 根据attributeName获取注解对应函数 - Method method = annotation.annotationType().getDeclaredMethod(attributeName); - ReflectionUtils.makeAccessible(method); - // 反射执行方法 - return method.invoke(annotation); - } catch (NoSuchMethodException ex) { - return null; - } catch (InvocationTargetException ex) { - rethrowAnnotationConfigurationException(ex.getTargetException()); - throw new IllegalStateException("Could not obtain value for annotation attribute '" + - attributeName + "' in " + annotation, ex); - } catch (Throwable ex) { - handleIntrospectionFailure(annotation.getClass(), ex); - return null; - } +@Nullable +public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) { + if (annotation == null || !StringUtils.hasText(attributeName)) { + return null; + } + try { + // 根据attributeName获取注解对应函数 + Method method = annotation.annotationType().getDeclaredMethod(attributeName); + ReflectionUtils.makeAccessible(method); + // 反射执行方法 + return method.invoke(annotation); + } catch (NoSuchMethodException ex) { + return null; + } catch (InvocationTargetException ex) { + rethrowAnnotationConfigurationException(ex.getTargetException()); + throw new IllegalStateException("Could not obtain value for annotation attribute '" + + attributeName + "' in " + annotation, ex); + } catch (Throwable ex) { + handleIntrospectionFailure(annotation.getClass(), ex); + return null; } +} ``` - - ```java - @Nullable - public static Object getValue(Annotation annotation) { - return getValue(annotation, VALUE); - } +@Nullable +public static Object getValue(Annotation annotation) { + return getValue(annotation, VALUE); +} ``` - - - - - - ## getDefaultValue - `org.springframework.core.annotation.AnnotationUtils#getDefaultValue(java.lang.annotation.Annotation)` - - ```java - @Nullable - public static Object getDefaultValue(Annotation annotation) { - return getDefaultValue(annotation, VALUE); - } +@Nullable +public static Object getDefaultValue(Annotation annotation) { + return getDefaultValue(annotation, VALUE); +} ``` ```java - @Nullable - public static Object getDefaultValue( - @Nullable Class annotationType, @Nullable String attributeName) { +@Nullable +public static Object getDefaultValue( + @Nullable Class annotationType, @Nullable String attributeName) { - if (annotationType == null || !StringUtils.hasText(attributeName)) { - return null; - } - try { - // 直接获取defaultValue - return annotationType.getDeclaredMethod(attributeName).getDefaultValue(); - } catch (Throwable ex) { - handleIntrospectionFailure(annotationType, ex); - return null; - } + if (annotationType == null || !StringUtils.hasText(attributeName)) { + return null; + } + try { + // 直接获取defaultValue + return annotationType.getDeclaredMethod(attributeName).getDefaultValue(); + } catch (Throwable ex) { + handleIntrospectionFailure(annotationType, ex); + return null; } +} ``` - diff --git a/docs/Spring/clazz/Spring-ApplicationListener.md b/docs/Spring/clazz/Spring-ApplicationListener.md index ab09612a..9f3b5a1a 100644 --- a/docs/Spring/clazz/Spring-ApplicationListener.md +++ b/docs/Spring/clazz/Spring-ApplicationListener.md @@ -1,9 +1,8 @@ # Spring initApplicationEventMulticaster + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - - ## demo ```java @@ -21,8 +20,6 @@ public class DemoApplicationListener implements ApplicationListener { ``` - - ```XML ``` - - -```JAVA +```java public class ListenerSourceCode { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("Listener-demo.xml"); @@ -43,12 +38,9 @@ public class ListenerSourceCode { } ``` - - ## 初始化入口 -- `org.springframework.context.support.AbstractApplicationContext.refresh`中的`initApplicationEventMulticaster`方法 - +- `org.springframework.context.support.AbstractApplicationContext.refresh`中的`initApplicationEventMulticaster`方法 ```java protected void initApplicationEventMulticaster() { @@ -74,11 +66,9 @@ public class ListenerSourceCode { ``` - - ## 注册 -```JAVA +```java protected void registerListeners() { // Register statically specified listeners first. // 读取 ApplicationListener @@ -112,9 +102,7 @@ public class ListenerSourceCode { ``` -![image-20200119163638222](../../../images/spring/image-20200119163638222.png) - - +![image-20200119163638222](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119163638222.png) ## finishRefresh 发布 @@ -142,10 +130,6 @@ public class ListenerSourceCode { - `org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent)` - `org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)` - - - - ```java protected void publishEvent(Object event, @Nullable ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); @@ -186,13 +170,9 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { } ``` - - - 执行监听方法 -![image-20200119164149650](../../../images/spring/image-20200119164149650.png) - - +![image-20200119164149650](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119164149650.png) ```java protected void invokeListener(ApplicationListener listener, ApplicationEvent event) { @@ -212,8 +192,6 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { ``` - - ```java @SuppressWarnings({"rawtypes", "unchecked"}) private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { @@ -239,8 +217,6 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { ``` +![image-20200119164402137](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119164402137.png) - -![image-20200119164402137](../../../images/spring/image-20200119164402137.png) - -![image-20200119164410301](../../../images/spring/image-20200119164410301.png) \ No newline at end of file +![image-20200119164410301](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119164410301.png) diff --git a/docs/Spring/clazz/Spring-BeanDefinitionParserDelegate.md b/docs/Spring/clazz/Spring-BeanDefinitionParserDelegate.md index d31d8a3b..059a45c6 100644 --- a/docs/Spring/clazz/Spring-BeanDefinitionParserDelegate.md +++ b/docs/Spring/clazz/Spring-BeanDefinitionParserDelegate.md @@ -1,905 +1,1344 @@ -# Spring-BeanDefinitionParserDelegate +# Spring BeanDefinitionParserDelegate + - Author: [HuiFer](https://github.com/huifer) -- 源码路径: `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate` -- 注意: 贴出的代码为当前类(`BeanDefinitionParserDelegate`)没有将其他的调用代码贴出,详细请看[huifer-srping](https://github.com/huifer/spring-framework). -- 该类的作用是将标签解析 +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 全路径`org.springframework.beans.factory.xml.BeanDefinitionParserDelegate` +- 解析 xml 中标签的委托类 -- 下面这段代码是这个类的入口 +- 在这个类中定义常量如下,为后续解析提供帮助 ```java - @Nullable - public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { - // 获取id 属性 - String id = ele.getAttribute(ID_ATTRIBUTE); - // 获取 name 属性 - String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); - - // 别名列表 - List aliases = new ArrayList<>(); - if (StringUtils.hasLength(nameAttr)) { - String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); - aliases.addAll(Arrays.asList(nameArr)); - } - - // beanName = 中id的属性值 - String beanName = id; - if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) { - beanName = aliases.remove(0); - if (logger.isTraceEnabled()) { - logger.trace("No XML 'id' specified - using '" + beanName + - "' as bean name and " + aliases + " as aliases"); - } - } - - if (containingBean == null) { - checkNameUniqueness(beanName, aliases, ele); - } - - // 创建对象 - AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean); - if (beanDefinition != null) { - if (!StringUtils.hasText(beanName)) { - try { - if (containingBean != null) { - beanName = BeanDefinitionReaderUtils.generateBeanName( - beanDefinition, this.readerContext.getRegistry(), true); - } - else { - beanName = this.readerContext.generateBeanName(beanDefinition); - // Register an alias for the plain bean class name, if still possible, - // if the generator returned the class name plus a suffix. - // This is expected for Spring 1.2/2.0 backwards compatibility. - String beanClassName = beanDefinition.getBeanClassName(); - if (beanClassName != null && - beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && - !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) { - aliases.add(beanClassName); - } - } - if (logger.isTraceEnabled()) { - logger.trace("Neither XML 'id' nor 'name' specified - " + - "using generated bean name [" + beanName + "]"); - } - } - catch (Exception ex) { - error(ex.getMessage(), ele); - return null; - } - } - String[] aliasesArray = StringUtils.toStringArray(aliases); - return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); - } + public static final String BEANS_NAMESPACE_URI = "http://www.springframework.org/schema/beans"; - return null; - } -``` + public static final String MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; "; -![image-20191231142829639](/image/spring/image-20191231142829639.png) + public static final String TRUE_VALUE = "true"; -该类大量的`parseXXX`开头的代码,这些方法都是对标签进行解析转换成具体的实体 + public static final String FALSE_VALUE = "false"; + public static final String DEFAULT_VALUE = "default"; + public static final String DESCRIPTION_ELEMENT = "description"; -### 测试用例 + public static final String AUTOWIRE_NO_VALUE = "no"; -```xml - - - - + public static final String AUTOWIRE_BY_NAME_VALUE = "byName"; -``` + public static final String AUTOWIRE_BY_TYPE_VALUE = "byType"; + public static final String AUTOWIRE_CONSTRUCTOR_VALUE = "constructor"; + public static final String AUTOWIRE_AUTODETECT_VALUE = "autodetect"; -## parseBeanDefinitionAttributes + public static final String NAME_ATTRIBUTE = "name"; -- 解析属性`scope`,`abstract`,`lazy-init` + public static final String BEAN_ELEMENT = "bean"; -```java -public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, - @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { - - if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { - error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); - } - else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { - bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); - } - else if (containingBean != null) { - // Take default from containing bean in case of an inner bean definition. - bd.setScope(containingBean.getScope()); - } - - if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { - bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); - } - - String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); - if (isDefaultValue(lazyInit)) { - lazyInit = this.defaults.getLazyInit(); - } - bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); - - String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); - bd.setAutowireMode(getAutowireMode(autowire)); - - if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { - String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); - bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); - } - - String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); - if (isDefaultValue(autowireCandidate)) { - String candidatePattern = this.defaults.getAutowireCandidates(); - if (candidatePattern != null) { - String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); - bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); - } - } - else { - bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); - } + public static final String META_ELEMENT = "meta"; - if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { - bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); - } + public static final String ID_ATTRIBUTE = "id"; - if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { - String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); - bd.setInitMethodName(initMethodName); - } - else if (this.defaults.getInitMethod() != null) { - bd.setInitMethodName(this.defaults.getInitMethod()); - bd.setEnforceInitMethod(false); - } + public static final String PARENT_ATTRIBUTE = "parent"; - if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { - String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); - bd.setDestroyMethodName(destroyMethodName); - } - else if (this.defaults.getDestroyMethod() != null) { - bd.setDestroyMethodName(this.defaults.getDestroyMethod()); - bd.setEnforceDestroyMethod(false); - } + public static final String CLASS_ATTRIBUTE = "class"; - if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { - bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); - } - if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { - bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); - } + public static final String ABSTRACT_ATTRIBUTE = "abstract"; - return bd; - } -``` + public static final String SCOPE_ATTRIBUTE = "scope"; + public static final String LAZY_INIT_ATTRIBUTE = "lazy-init"; -![image-20191231162505748](/image/spring/image-20191231162505748.png) + public static final String AUTOWIRE_ATTRIBUTE = "autowire"; -其他的标签也同样的方式**`getAttribute`**获取 + public static final String AUTOWIRE_CANDIDATE_ATTRIBUTE = "autowire-candidate"; -思路: + public static final String PRIMARY_ATTRIBUTE = "primary"; -1. 判断是否有这个标签 - 1. 有直接获取,设置值 - 2. 没有跳过 + public static final String DEPENDS_ON_ATTRIBUTE = "depends-on"; + public static final String INIT_METHOD_ATTRIBUTE = "init-method"; + public static final String DESTROY_METHOD_ATTRIBUTE = "destroy-method"; + public static final String FACTORY_METHOD_ATTRIBUTE = "factory-method"; -## parseMetaElements + public static final String FACTORY_BEAN_ATTRIBUTE = "factory-bean"; -```java - /** - * 解析Meta 元素 - * {@code } => {@link BeanMetadataAttribute} - * Parse the meta elements underneath the given element, if any. - */ - public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { - NodeList nl = ele.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) { - Element metaElement = (Element) node; - // 获取 key 属性值 - String key = metaElement.getAttribute(KEY_ATTRIBUTE); - // 获取 value 属性值 - String value = metaElement.getAttribute(VALUE_ATTRIBUTE); - BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); - attribute.setSource(extractSource(metaElement)); - attributeAccessor.addMetadataAttribute(attribute); - } - } - } + public static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg"; -``` + public static final String INDEX_ATTRIBUTE = "index"; -![image-20191231164622063](/image/spring/image-20191231164622063.png) + public static final String TYPE_ATTRIBUTE = "type"; + public static final String VALUE_TYPE_ATTRIBUTE = "value-type"; + public static final String KEY_TYPE_ATTRIBUTE = "key-type"; + public static final String PROPERTY_ELEMENT = "property"; + public static final String REF_ATTRIBUTE = "ref"; -## parseLookupOverrideSubElements + public static final String VALUE_ATTRIBUTE = "value"; -```java - /** - * 解析{@code } - * Parse lookup-override sub-elements of the given bean element. - */ - public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { - NodeList nl = beanEle.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { - Element ele = (Element) node; - String methodName = ele.getAttribute(NAME_ATTRIBUTE); - String beanRef = ele.getAttribute(BEAN_ELEMENT); - // 转换成JAVA对象 - LookupOverride override = new LookupOverride(methodName, beanRef); - override.setSource(extractSource(ele)); - overrides.addOverride(override); - } - } - } + public static final String LOOKUP_METHOD_ELEMENT = "lookup-method"; -``` + public static final String REPLACED_METHOD_ELEMENT = "replaced-method"; -### 测试用例 + public static final String REPLACER_ATTRIBUTE = "replacer"; -```xml - - - - - - - - -``` + public static final String ARG_TYPE_ELEMENT = "arg-type"; -![image-20191231165638975](/image/spring/image-20191231165638975.png) + public static final String ARG_TYPE_MATCH_ATTRIBUTE = "match"; + public static final String REF_ELEMENT = "ref"; + public static final String IDREF_ELEMENT = "idref"; + public static final String BEAN_REF_ATTRIBUTE = "bean"; + public static final String PARENT_REF_ATTRIBUTE = "parent"; + public static final String VALUE_ELEMENT = "value"; + public static final String NULL_ELEMENT = "null"; -## parseReplacedMethodSubElements + public static final String ARRAY_ELEMENT = "array"; -```java - /** - * {@code } - * Parse replaced-method sub-elements of the given bean element. - */ - public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { - NodeList nl = beanEle.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { - Element replacedMethodEle = (Element) node; - String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); - String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); - // 转换成JAVA对象 - ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); - // Look for arg-type match elements. - // 参数解析 解析 - List argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); - for (Element argTypeEle : argTypeEles) { - String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); - match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); - if (StringUtils.hasText(match)) { - replaceOverride.addTypeIdentifier(match); - } - } - replaceOverride.setSource(extractSource(replacedMethodEle)); - overrides.addOverride(replaceOverride); - } - } - } + public static final String LIST_ELEMENT = "list"; + + public static final String SET_ELEMENT = "set"; + + public static final String MAP_ELEMENT = "map"; + + public static final String ENTRY_ELEMENT = "entry"; + + public static final String KEY_ELEMENT = "key"; + + public static final String KEY_ATTRIBUTE = "key"; + public static final String KEY_REF_ATTRIBUTE = "key-ref"; + + public static final String VALUE_REF_ATTRIBUTE = "value-ref"; + + public static final String PROPS_ELEMENT = "props"; + + public static final String PROP_ELEMENT = "prop"; + + public static final String MERGE_ATTRIBUTE = "merge"; + + public static final String QUALIFIER_ELEMENT = "qualifier"; + + public static final String QUALIFIER_ATTRIBUTE_ELEMENT = "attribute"; + + public static final String DEFAULT_LAZY_INIT_ATTRIBUTE = "default-lazy-init"; + + public static final String DEFAULT_MERGE_ATTRIBUTE = "default-merge"; + + public static final String DEFAULT_AUTOWIRE_ATTRIBUTE = "default-autowire"; + + public static final String DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE = "default-autowire-candidates"; + + public static final String DEFAULT_INIT_METHOD_ATTRIBUTE = "default-init-method"; + + public static final String DEFAULT_DESTROY_METHOD_ATTRIBUTE = "default-destroy-method"; + + private static final String SINGLETON_ATTRIBUTE = "singleton"; ``` -### 测试用例 +## populateDefaults - 编写方法`dis` +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#populateDefaults`方法解析属性赋值给`DocumentDefaultsDefinition`对象 + +- 代码逻辑如下 + 1. 读取属性 + 2. 判断是否默认值 + 3. 判断是否存在属性 + 4. 赋值 ```java -public class Person { - private String name; - private Apple apple; - private Integer age; - - public void dis() { - System.out.println("dis"); - } +protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) { + // 获取 default-lazy-init 属性值 + String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE); + // 判断是否是默认值 + if (isDefaultValue(lazyInit)) { + // Potentially inherited from outer sections, otherwise falling back to false. + lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE); + } + defaults.setLazyInit(lazyInit); + + String merge = root.getAttribute(DEFAULT_MERGE_ATTRIBUTE); + if (isDefaultValue(merge)) { + // Potentially inherited from outer sections, otherwise falling back to false. + merge = (parentDefaults != null ? parentDefaults.getMerge() : FALSE_VALUE); + } + defaults.setMerge(merge); + + String autowire = root.getAttribute(DEFAULT_AUTOWIRE_ATTRIBUTE); + if (isDefaultValue(autowire)) { + // Potentially inherited from outer sections, otherwise falling back to 'no'. + autowire = (parentDefaults != null ? parentDefaults.getAutowire() : AUTOWIRE_NO_VALUE); + } + defaults.setAutowire(autowire); + + if (root.hasAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)) { + defaults.setAutowireCandidates(root.getAttribute(DEFAULT_AUTOWIRE_CANDIDATES_ATTRIBUTE)); + } + else if (parentDefaults != null) { + defaults.setAutowireCandidates(parentDefaults.getAutowireCandidates()); + } + + if (root.hasAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)) { + defaults.setInitMethod(root.getAttribute(DEFAULT_INIT_METHOD_ATTRIBUTE)); + } + else if (parentDefaults != null) { + defaults.setInitMethod(parentDefaults.getInitMethod()); + } + + if (root.hasAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)) { + defaults.setDestroyMethod(root.getAttribute(DEFAULT_DESTROY_METHOD_ATTRIBUTE)); + } + else if (parentDefaults != null) { + defaults.setDestroyMethod(parentDefaults.getDestroyMethod()); + } + + defaults.setSource(this.readerContext.extractSource(root)); } ``` -​ 编写一个替换`dis`方法的类 +### DocumentDefaultsDefinition + +- 全路径:`org.springframework.beans.factory.xml.DocumentDefaultsDefinition` +- 下面放出类的属性标记 ```java -public class Rc implements MethodReplacer { - @Override - public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { - System.out.println("替换原来的方法"); - return null; - } +public class DocumentDefaultsDefinition implements DefaultsDefinition { + + /** + * true or false + */ + @Nullable + private String lazyInit; + + /** + * true or false + */ + @Nullable + private String merge; + + /** + * no or byName or byType + */ + @Nullable + private String autowire; + + /** + * default-autowire-candidates 属性值 + */ + @Nullable + private String autowireCandidates; + + /** + * 实例化方法 + */ + @Nullable + private String initMethod; + + /** + * 摧毁方法 + */ + @Nullable + private String destroyMethod; + + @Nullable + private Object source; } ``` -xml 配置 +## checkNameUniqueness -```java - - +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#checkNameUniqueness` +- 判断 beanName 是否被使用, bean 别名是否被使用 + +```java +/** + * Validate that the specified bean name and aliases have not been used already + * within the current level of beans element nesting. + * + * 判断 beanName 是否被使用, bean 别名是否被使用 + */ +protected void checkNameUniqueness(String beanName, List aliases, Element beanElement) { + // 当前寻找的name + String foundName = null; + + // 是否有 beanName + // 使用过的name中是否存在 + if (StringUtils.hasText(beanName) && this.usedNames.contains(beanName)) { + foundName = beanName; + } + if (foundName == null) { + // 寻找匹配的第一个 + foundName = CollectionUtils.findFirstMatch(this.usedNames, aliases); + } + // 抛出异常 + if (foundName != null) { + error("Bean name '" + foundName + "' is already used in this element", beanElement); + } + + // 加入使用队列 + this.usedNames.add(beanName); + this.usedNames.addAll(aliases); +} ``` -![image-20200101093742238](/image/spring/image-20200101093742238.png) +## createBeanDefinition + +- `org.springframework.beans.factory.support.BeanDefinitionReaderUtils#createBeanDefinition` +- 创建具有基本信息的**BeanDefinition** + 1. parent bean name + 2. bean clsss + 3. bean class name +```java +public static AbstractBeanDefinition createBeanDefinition( + @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { + + GenericBeanDefinition bd = new GenericBeanDefinition(); + // 设置 父bean + bd.setParentName(parentName); + if (className != null) { + if (classLoader != null) { + // 设置 class + // 内部是通过反射创建 class + bd.setBeanClass(ClassUtils.forName(className, classLoader)); + } + else { + // 设置 class name + bd.setBeanClassName(className); + } + } + return bd; +} +``` +## parseBeanDefinitionElement +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)` +- 该方法用来解析 `` 标签信息 +## -## parseConstructorArgElements +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)` ```java - public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { - NodeList nl = beanEle.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { - parseConstructorArgElement((Element) node, bd); - } - } - } - +@Nullable +public AbstractBeanDefinition parseBeanDefinitionElement( + Element ele, String beanName, @Nullable BeanDefinition containingBean) { + + this.parseState.push(new BeanEntry(beanName)); + + String className = null; + // 是否包含属性 class + if (ele.hasAttribute(CLASS_ATTRIBUTE)) { + className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); + } + String parent = null; + // 是否包含属性 parent + if (ele.hasAttribute(PARENT_ATTRIBUTE)) { + parent = ele.getAttribute(PARENT_ATTRIBUTE); + } + + try { + // 创建 bean definition + AbstractBeanDefinition bd = createBeanDefinition(className, parent); + + // bean definition 属性设置 + parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); + bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); + // 元信息设置 + parseMetaElements(ele, bd); + // lookup-override 标签解析 + parseLookupOverrideSubElements(ele, bd.getMethodOverrides()); + // replaced-method sub-elements 标签解析 + parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); + + // constructor arg 标签解析 + parseConstructorArgElements(ele, bd); + // property 标签解析 + parsePropertyElements(ele, bd); + // qualifier 标签解析 + parseQualifierElements(ele, bd); + // 资源设置 + bd.setResource(this.readerContext.getResource()); + // source 设置 + bd.setSource(extractSource(ele)); + + return bd; + } + catch (ClassNotFoundException ex) { + error("Bean class [" + className + "] not found", ele, ex); + } + catch (NoClassDefFoundError err) { + error("Class that bean class [" + className + "] depends on not found", ele, err); + } + catch (Throwable ex) { + error("Unexpected failure during bean definition parsing", ele, ex); + } + finally { + this.parseState.pop(); + } + + return null; +} ``` -```java - /** - * 解析 constructor-arg 元素 - * {@code } - * Parse a constructor-arg element. - */ - public void parseConstructorArgElement(Element ele, BeanDefinition bd) { - String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); - String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); - String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); - // 判断是否存在 index 属性 - if (StringUtils.hasLength(indexAttr)) { - try { - int index = Integer.parseInt(indexAttr); - if (index < 0) { - error("'index' cannot be lower than 0", ele); - } - else { - try { - // ConstructorArgumentEntry 插入parseState - this.parseState.push(new ConstructorArgumentEntry(index)); - Object value = parsePropertyValue(ele, bd, null); - // 转换成JAVA对象 - ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); - if (StringUtils.hasLength(typeAttr)) { - valueHolder.setType(typeAttr); - } - if (StringUtils.hasLength(nameAttr)) { - valueHolder.setName(nameAttr); - } - valueHolder.setSource(extractSource(ele)); - // 不允许重复指定相同参数 - if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { - error("Ambiguous constructor-arg entries for index " + index, ele); - } - else { - bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); - } - } - finally { - this.parseState.pop(); - } - } - } - catch (NumberFormatException ex) { - error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); - } - } - else { - try { - this.parseState.push(new ConstructorArgumentEntry()); - Object value = parsePropertyValue(ele, bd, null); - ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); - if (StringUtils.hasLength(typeAttr)) { - valueHolder.setType(typeAttr); - } - if (StringUtils.hasLength(nameAttr)) { - valueHolder.setName(nameAttr); - } - valueHolder.setSource(extractSource(ele)); - bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); - } - finally { - this.parseState.pop(); - } - } - } - -``` - -```JAVA - /** - * Get the value of a property element. May be a list etc. - * Also used for constructor arguments, "propertyName" being null in this case. - */ - @Nullable - public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { - String elementName = (propertyName != null ? - " element for property '" + propertyName + "'" : - " element"); - - // Should only have one child element: ref, value, list, etc. - NodeList nl = ele.getChildNodes(); - Element subElement = null; - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - // 不处理 element 节点名称 = description 和 meta - if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && - !nodeNameEquals(node, META_ELEMENT)) { - // Child element is what we're looking for. - if (subElement != null) { - error(elementName + " must not contain more than one sub-element", ele); - } - else { - subElement = (Element) node; - } - } - } - - // 判断是否存在 ref 属性 - boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); - // 判断是否 value 属性 - boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); - // 判断1: ref属性存在 value 属性 或者 ref 和 value 有一个存在并且有 下级节点 抛出异常 - if ((hasRefAttribute && hasValueAttribute) || - ((hasRefAttribute || hasValueAttribute) && subElement != null)) { - error(elementName + - " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); - } - - // 判断2: 存在 ref 返回 ref 的值 - if (hasRefAttribute) { - String refName = ele.getAttribute(REF_ATTRIBUTE); - if (!StringUtils.hasText(refName)) { - error(elementName + " contains empty 'ref' attribute", ele); - } - RuntimeBeanReference ref = new RuntimeBeanReference(refName); - ref.setSource(extractSource(ele)); - return ref; - } - // 判断3: 存在 value 返回 value 的值 - else if (hasValueAttribute) { - TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); - valueHolder.setSource(extractSource(ele)); - return valueHolder; - } - else if (subElement != null) { - // 下级标签解析 - return parsePropertySubElement(subElement, bd); - } - else { - // 没有抛出异常 - // Neither child element nor "ref" or "value" attribute found. - error(elementName + " must specify a ref or value", ele); - return null; - } - } +### parseBeanDefinitionAttributes + +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionAttributes` +- 将 xml 标签的数据读取到内存中设置给`AbstractBeanDefinition` + +```java +public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, + @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) { + + // 是否存在 singleton 属性 + if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { + error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele); + } + // 是否存在 scope 属性 + else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { + // 设置 scope 属性 + bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); + } + // bean 定义是否为空 + else if (containingBean != null) { + // Take default from containing bean in case of an inner bean definition. + // 设置 bean definition 中的 scope + bd.setScope(containingBean.getScope()); + } + + // 是否存在 abstract 属性 + if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { + // 设置 abstract 属性 + bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); + } + + // 获取 lazy-init 属性 + String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); + // 是否是默认的 lazy-init 属性 + if (isDefaultValue(lazyInit)) { + // 获取默认值 + lazyInit = this.defaults.getLazyInit(); + } + // 设置 lazy-init 属性 + bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); + + // 获取注入方式 + String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); + // 设置注入方式 + bd.setAutowireMode(getAutowireMode(autowire)); + + // 依赖的bean + if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { + String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); + bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); + } + + // autowire-candidate 是否自动注入判断 + String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); + if (isDefaultValue(autowireCandidate)) { + String candidatePattern = this.defaults.getAutowireCandidates(); + if (candidatePattern != null) { + String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); + // * 匹配 设置数据 + bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); + } + } + else { + bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); + } + + // 获取 primary 书信 + if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) { + bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); + } + + // 获取 init-method 属性 + if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) { + String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); + bd.setInitMethodName(initMethodName); + } + // 没有 init-method 的情况处理 + else if (this.defaults.getInitMethod() != null) { + bd.setInitMethodName(this.defaults.getInitMethod()); + bd.setEnforceInitMethod(false); + } + + // 获取 destroy-method 属性 + if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) { + String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); + bd.setDestroyMethodName(destroyMethodName); + } + // 没有 destroy-method 的情况处理 + else if (this.defaults.getDestroyMethod() != null) { + bd.setDestroyMethodName(this.defaults.getDestroyMethod()); + bd.setEnforceDestroyMethod(false); + } + + // 获取 factory-method 属性 + if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) { + bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); + } + // 获取 factory-bean 属性 + if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) { + bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); + } + + return bd; +} ``` +### parseMetaElements +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseMetaElements` -### 测试用例 +- 设置元数据. -- 编辑构造函数 + 标签`meta`的解析 - ```java - public class Person { - private String name; - private Apple apple; - private Integer age; - - public Person() { - } - - public Person(Integer age) { - this.age = age; +```java +public void parseMetaElements(Element ele, BeanMetadataAttributeAccessor attributeAccessor) { + // 获取下级标签 + NodeList nl = ele.getChildNodes(); + // 循环子标签 + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + // 设置数据 + if (isCandidateElement(node) && nodeNameEquals(node, META_ELEMENT)) { + Element metaElement = (Element) node; + // 获取 key 属性 + String key = metaElement.getAttribute(KEY_ATTRIBUTE); + // 获取 value 属性 + String value = metaElement.getAttribute(VALUE_ATTRIBUTE); + // 元数据对象设置 + BeanMetadataAttribute attribute = new BeanMetadataAttribute(key, value); + // 设置 source + attribute.setSource(extractSource(metaElement)); + // 信息添加 + attributeAccessor.addMetadataAttribute(attribute); } - - ``` + } +} +``` -- 添加配置 +使用案例 - ```xml - - - ``` +```xml + + + +``` +```java +ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/beans/spring-lookup-method.xml"); +ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); +BeanDefinition apple = beanFactory.getBeanDefinition("apple"); +Object attribute = apple.getAttribute("meta-key"); +System.out.println(attribute); +``` -![image-20200101100906778](/image/spring/image-20200101100906778.png) +### parseLookupOverrideSubElements +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseLookupOverrideSubElements` +- 解析标签 -## parsePropertyElements + `lookup-method` ```java -public void parsePropertyElements(Element beanEle, BeanDefinition bd) { - NodeList nl = beanEle.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) { - parsePropertyElement((Element) node, bd); - } - } - } +public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) { + // 获取子标签 + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + // 是否有 lookup-method 属性 + if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) { + Element ele = (Element) node; + // 获取 name 属性 + String methodName = ele.getAttribute(NAME_ATTRIBUTE); + // 获取 bean 属性 + String beanRef = ele.getAttribute(BEAN_ELEMENT); + // 创建 覆盖依赖 + LookupOverride override = new LookupOverride(methodName, beanRef); + // 设置 source + override.setSource(extractSource(ele)); + overrides.addOverride(override); + } + } +} ``` -```java - /** - * 解析 {@code } - * Parse a property element. - */ - public void parsePropertyElement(Element ele, BeanDefinition bd) { - String propertyName = ele.getAttribute(NAME_ATTRIBUTE); - if (!StringUtils.hasLength(propertyName)) { - error("Tag 'property' must have a 'name' attribute", ele); - return; - } - this.parseState.push(new PropertyEntry(propertyName)); - try { - if (bd.getPropertyValues().contains(propertyName)) { - error("Multiple 'property' definitions for property '" + propertyName + "'", ele); - return; - } - Object val = parsePropertyValue(ele, bd, propertyName); - // 转换JAVA 对象 - PropertyValue pv = new PropertyValue(propertyName, val); - parseMetaElements(ele, pv); - pv.setSource(extractSource(ele)); - bd.getPropertyValues().addPropertyValue(pv); - } - finally { - this.parseState.pop(); - } - } - -``` +使用案例 -### 测试用例 +```xml + + + -xml + + + -```xml - - - ``` +```java +public class LookupMain { + public static void main(String[] args) { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/beans/spring-lookup-method.xml"); + Shop shop = context.getBean("shop", Shop.class); + System.out.println(shop.getFruits().getName()); + } +} +``` +### parseReplacedMethodSubElements -![image-20200101111755022](/image/spring/image-20200101111755022.png) - +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseReplacedMethodSubElements` +- 解析标签 -## parseQualifierElements + `replaced-method` ```java -public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) { - NodeList nl = beanEle.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) { - parseQualifierElement((Element) node, bd); +public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) { + // 子节点获取 + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + // 是否包含 replaced-method 属性 + if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) { + Element replacedMethodEle = (Element) node; + // 获取 name 属性 + String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE); + // 获取 replacer + String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE); + // 对象组装 + ReplaceOverride replaceOverride = new ReplaceOverride(name, callback); + // Look for arg-type match elements. + // 子节点属性 + // 处理 arg-type 标签 + List argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT); + + for (Element argTypeEle : argTypeEles) { + // 获取 match 数据值 + String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE); + // match 信息设置 + match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle)); + if (StringUtils.hasText(match)) { + // 添加类型标识 + replaceOverride.addTypeIdentifier(match); } - } - } + } + // 设置 source + replaceOverride.setSource(extractSource(replacedMethodEle)); + // 重载列表添加 + overrides.addOverride(replaceOverride); + } + } +} ``` -```java - /** - * Parse a qualifier element. - */ - public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) { - String typeName = ele.getAttribute(TYPE_ATTRIBUTE); - // 判断是否有类型 - if (!StringUtils.hasLength(typeName)) { - error("Tag 'qualifier' must have a 'type' attribute", ele); - return; - } - // 创建并且放入 parseState - this.parseState.push(new QualifierEntry(typeName)); - try { - AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName); - qualifier.setSource(extractSource(ele)); - String value = ele.getAttribute(VALUE_ATTRIBUTE); - // 判断是否有值 - if (StringUtils.hasLength(value)) { - qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value); - } - // 下级节点 - NodeList nl = ele.getChildNodes(); - for (int i = 0; i < nl.getLength(); i++) { - Node node = nl.item(i); - if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) { - Element attributeEle = (Element) node; - // 获取 下级biao'qia - String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); - String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); - // 判断是否有 key 和 value - if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) { - // 转换成JAVA对象 - BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue); - attribute.setSource(extractSource(attributeEle)); - qualifier.addMetadataAttribute(attribute); - } - else { - // 没有抛出异常 - error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle); - return; - } - } - } - bd.addQualifier(qualifier); - } - finally { - this.parseState.pop(); - } - } +- 使用案例 -``` +```xml + + + + + String + + + -### 测试用例 + -- 编写一个注解 + +``` ```java -/** - * 在spring-config.xml中配置 ,使用时通过 status + quality 来引入具体的bean - */ -@Target({ElementType.FIELD, ElementType.METHOD, - ElementType.TYPE, ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -@Qualifier -public @interface PersonQualifier { +public class MethodReplacerApple implements MethodReplacer { + @Override + public Object reimplement(Object obj, Method method, Object[] args) throws Throwable { + System.out.println("方法替换"); + return obj; + } +} +``` - String status(); +**replacer 需要使用 MethodReplacer 实现类** - String quality(); -} -``` +### parseConstructorArgElements -```JAVA -public class PersonS { - private String personName; +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElements` - public String getPersonName() { - return personName; - } +- 解析`constructor-arg`标签 - public void setPersonName(String personName) { - this.personName = personName; - } -} +``` +public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) { + // 获取 + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) { + // 解析 constructor-arg 下级标签 + parseConstructorArgElement((Element) node, bd); + } + } +} ``` -```JAVA +```java +public void parseConstructorArgElement(Element ele, BeanDefinition bd) { + // 获取 index 属性 + String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE); + // 获取 type 属性 + String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE); + // 获取 name 属性 + String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); + if (StringUtils.hasLength(indexAttr)) { + try { + // 构造参数的所以未知 + int index = Integer.parseInt(indexAttr); + if (index < 0) { + error("'index' cannot be lower than 0", ele); + } + else { + try { + this.parseState.push(new ConstructorArgumentEntry(index)); + // 解析 property 标签 + Object value = parsePropertyValue(ele, bd, null); + // 创建 构造函数的 属性控制类 + ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); + if (StringUtils.hasLength(typeAttr)) { + // 类型设置 + valueHolder.setType(typeAttr); + } + if (StringUtils.hasLength(nameAttr)) { + // 名称设置 + valueHolder.setName(nameAttr); + } + // 源设置 + valueHolder.setSource(extractSource(ele)); + if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) { + error("Ambiguous constructor-arg entries for index " + index, ele); + } + else { + // 添加 构造函数信息 + bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder); + } + } + finally { + this.parseState.pop(); + } + } + } + catch (NumberFormatException ex) { + error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele); + } + } + else { + try { + this.parseState.push(new ConstructorArgumentEntry()); + // 解析 property 标签 + Object value = parsePropertyValue(ele, bd, null); + // 创建 构造函数的 属性控制类 + ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value); + if (StringUtils.hasLength(typeAttr)) { + // 类型设置 + valueHolder.setType(typeAttr); + } + if (StringUtils.hasLength(nameAttr)) { + // 名称设置 + valueHolder.setName(nameAttr); + } + // 源设置 + valueHolder.setSource(extractSource(ele)); + // 添加 构造函数信息 + bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder); + } + finally { + this.parseState.pop(); + } + } +} +``` -import org.springframework.beans.factory.annotation.Autowired; +### parseConstructorArgElement -public class PersonService { - @Autowired -// @PersonQualifier(status = "status_teacher", quality = "quality_teacher") - @PersonQualifier(status = "status_student", quality = "quality_student") - private PersonS personS; +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElement` - public PersonS getPerson() { - return personS; - } +- 解析 constructor-arg 下级标签 - public void setPerson(PersonS person) { - this.personS = person; - } -} +```java +@Nullable +public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) { + String elementName = (propertyName != null ? + " element for property '" + propertyName + "'" : + " element"); + + // Should only have one child element: ref, value, list, etc. + NodeList nl = ele.getChildNodes(); + Element subElement = null; + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) && + !nodeNameEquals(node, META_ELEMENT)) { + // Child element is what we're looking for. + if (subElement != null) { + error(elementName + " must not contain more than one sub-element", ele); + } + else { + subElement = (Element) node; + } + } + } + + // ref 属性是否存在 + boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE); + // value 属性是否存在 + boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE); + if ((hasRefAttribute && hasValueAttribute) || + ((hasRefAttribute || hasValueAttribute) && subElement != null)) { + error(elementName + + " is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele); + } + + if (hasRefAttribute) { + // 获取 ref 属性值 + String refName = ele.getAttribute(REF_ATTRIBUTE); + if (!StringUtils.hasText(refName)) { + error(elementName + " contains empty 'ref' attribute", ele); + } + // 创建 连接对象 + RuntimeBeanReference ref = new RuntimeBeanReference(refName); + + ref.setSource(extractSource(ele)); + return ref; + } + else if (hasValueAttribute) { + // 获取 value + TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); + valueHolder.setSource(extractSource(ele)); + return valueHolder; + } + else if (subElement != null) { + return parsePropertySubElement(subElement, bd); + } + else { + // Neither child element nor "ref" or "value" attribute found. + error(elementName + " must specify a ref or value", ele); + return null; + } +} ``` -```JAVA -public class Student extends PersonS { - private String stdLocation; +### parsePropertySubElement - public String getStdLocation() { - return stdLocation; - } +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)` - public void setStdLocation(String stdLocation) { - this.stdLocation = stdLocation; - } +```java +@Nullable +public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) { + // 解析 property 下级标签 + return parsePropertySubElement(ele, bd, null); } ``` -```JAVA -public class Teacher extends PersonS { - private String subject; +### parsePropertySubElement - public String getSubject() { - return subject; - } +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition, java.lang.String)` - public void setSubject(String subject) { - this.subject = subject; - } -} ``` - -```JAVA -public class QualifierSourceCode { - public static void main(String[] args) { - AbstractApplicationContext context = new ClassPathXmlApplicationContext("QualifierSourceCode-beans.xml"); - PersonService service = context.getBean(PersonService.class); - System.out.println(service.getPerson().getPersonName()); - context.close(); - } +@Nullable +public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) { + if (!isDefaultNamespace(ele)) { + // 嵌套分析 + return parseNestedCustomElement(ele, bd); + } + else if (nodeNameEquals(ele, BEAN_ELEMENT)) { + // 解析 bean 标签 + BeanDefinitionHolder nestedBd = parseBeanDefinitionElement(ele, bd); + if (nestedBd != null) { + // 装饰 bean define + nestedBd = decorateBeanDefinitionIfRequired(ele, nestedBd, bd); + } + return nestedBd; + } + // ref 名称判断 + else if (nodeNameEquals(ele, REF_ELEMENT)) { + // A generic reference to any name of any bean. + // 获取 ref 属性 + String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); + boolean toParent = false; + if (!StringUtils.hasLength(refName)) { + // A reference to the id of another bean in a parent context. + // 获取 parent 属性 + refName = ele.getAttribute(PARENT_REF_ATTRIBUTE); + toParent = true; + if (!StringUtils.hasLength(refName)) { + error("'bean' or 'parent' is required for element", ele); + return null; + } + } + if (!StringUtils.hasText(refName)) { + error(" element contains empty target attribute", ele); + return null; + } + // bean 连接对象创建 + RuntimeBeanReference ref = new RuntimeBeanReference(refName, toParent); + ref.setSource(extractSource(ele)); + return ref; + } + else if (nodeNameEquals(ele, IDREF_ELEMENT)) { + return parseIdRefElement(ele); + } + else if (nodeNameEquals(ele, VALUE_ELEMENT)) { + return parseValueElement(ele, defaultValueType); + } + else if (nodeNameEquals(ele, NULL_ELEMENT)) { + // It's a distinguished null value. Let's wrap it in a TypedStringValue + // object in order to preserve the source location. + TypedStringValue nullHolder = new TypedStringValue(null); + nullHolder.setSource(extractSource(ele)); + return nullHolder; + } + else if (nodeNameEquals(ele, ARRAY_ELEMENT)) { + return parseArrayElement(ele, bd); + } + else if (nodeNameEquals(ele, LIST_ELEMENT)) { + return parseListElement(ele, bd); + } + else if (nodeNameEquals(ele, SET_ELEMENT)) { + return parseSetElement(ele, bd); + } + else if (nodeNameEquals(ele, MAP_ELEMENT)) { + return parseMapElement(ele, bd); + } + else if (nodeNameEquals(ele, PROPS_ELEMENT)) { + return parsePropsElement(ele); + } + else { + error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele); + return null; + } } - ``` -```XML - - - - - - - - - - - - - - - - - - - - - - - +#### parseIdRefElement + +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseIdRefElement` + +```java +@Nullable +public Object parseIdRefElement(Element ele) { + // A generic reference to any name of any bean. + // 获取 bean 属性 + String refName = ele.getAttribute(BEAN_REF_ATTRIBUTE); + if (!StringUtils.hasLength(refName)) { + error("'bean' is required for element", ele); + return null; + } + if (!StringUtils.hasText(refName)) { + error(" element contains empty target attribute", ele); + return null; + } + // 设置 bean 链接对象 + RuntimeBeanNameReference ref = new RuntimeBeanNameReference(refName); + // 设置原 + ref.setSource(extractSource(ele)); + return ref; +} ``` -![image-20200101155539501](/image/spring/image-20200101155539501.png) +#### parseValueElement +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseValueElement` +```java + public Object parseValueElement(Element ele, @Nullable String defaultTypeName) { + // It's a literal value. + // 获取 xml 中的文本变量 + String value = DomUtils.getTextValue(ele); + // 获取 type 属性 + String specifiedTypeName = ele.getAttribute(TYPE_ATTRIBUTE); + // 类型 + String typeName = specifiedTypeName; + if (!StringUtils.hasText(typeName)) { + typeName = defaultTypeName; + } + try { + // 创建类型值 + TypedStringValue typedValue = buildTypedStringValue(value, typeName); + typedValue.setSource(extractSource(ele)); + typedValue.setSpecifiedTypeName(specifiedTypeName); + return typedValue; + } + catch (ClassNotFoundException ex) { + error("Type class [" + typeName + "] not found for element", ele, ex); + return value; + } + } +``` +##### buildTypedStringValue -### Bean解析结果 +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#buildTypedStringValue` +- 构造对象, 没有创建对象 -![image-20200102083512005](/image/spring/image-20200102083512005.png) +```java +protected TypedStringValue buildTypedStringValue(String value, @Nullable String targetTypeName) + throws ClassNotFoundException { + // class loader + ClassLoader classLoader = this.readerContext.getBeanClassLoader(); + TypedStringValue typedValue; + if (!StringUtils.hasText(targetTypeName)) { + typedValue = new TypedStringValue(value); + } + else if (classLoader != null) { + // 目标类 + Class targetType = ClassUtils.forName(targetTypeName, classLoader); + // 构造 + typedValue = new TypedStringValue(value, targetType); + } + else { + // 构造 + typedValue = new TypedStringValue(value, targetTypeName); + } + return typedValue; +} +``` +#### parseArrayElement +```java +public Object parseArrayElement(Element arrayEle, @Nullable BeanDefinition bd) { + // 获取 value-type 属性 + String elementType = arrayEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + // 子节点 + NodeList nl = arrayEle.getChildNodes(); + // 合并 array 的类 + ManagedArray target = new ManagedArray(elementType, nl.getLength()); + target.setSource(extractSource(arrayEle)); + target.setElementTypeName(elementType); + target.setMergeEnabled(parseMergeAttribute(arrayEle)); + // 处理 collection 节点 + parseCollectionElements(nl, target, bd, elementType); + return target; +} +``` +#### parseListElement +```java +public List parseListElement(Element collectionEle, @Nullable BeanDefinition bd) { + String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + NodeList nl = collectionEle.getChildNodes(); + ManagedList target = new ManagedList<>(nl.getLength()); + target.setSource(extractSource(collectionEle)); + target.setElementTypeName(defaultElementType); + target.setMergeEnabled(parseMergeAttribute(collectionEle)); + parseCollectionElements(nl, target, bd, defaultElementType); + return target; +} +``` -## importBeanDefinitionResource +#### parseSetElement -```JAVA - /** - * 解析{@code } 标签 - * Parse an "import" element and load the bean definitions - * from the given resource into the bean factory. - */ - protected void importBeanDefinitionResource(Element ele) { - // 获取 resource 属性 - String location = ele.getAttribute(RESOURCE_ATTRIBUTE); - // 是否有 resource 属性 - if (!StringUtils.hasText(location)) { - getReaderContext().error("Resource location must not be empty", ele); - return; - } +```java +public Set parseSetElement(Element collectionEle, @Nullable BeanDefinition bd) { + String defaultElementType = collectionEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + NodeList nl = collectionEle.getChildNodes(); + ManagedSet target = new ManagedSet<>(nl.getLength()); + target.setSource(extractSource(collectionEle)); + target.setElementTypeName(defaultElementType); + target.setMergeEnabled(parseMergeAttribute(collectionEle)); + parseCollectionElements(nl, target, bd, defaultElementType); + return target; +} +``` - // Resolve system properties: e.g. "${user.dir}" - // 获取系统环境,解析路径 - /** - * 1. getReaderContext() 获取{@link XmlReaderContext} - * 2. getEnvironment() 获取环境 - * 3. resolveRequiredPlaceholders(location) 解析占位符${...} - */ - location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location); +##### parseCollectionElements - // 资源存放集合 - Set actualResources = new LinkedHashSet<>(4); +- `parseArrayElement`、`parseListElement`、`parseSetElement` 都围绕者下面这个方法进行数据合并 - // Discover whether the location is an absolute or relative URI - // 相对路径 绝对路径的判断 - boolean absoluteLocation = false; - try { - // 判断是相对路径还是绝对路径 - absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute(); - } - catch (URISyntaxException ex) { - // cannot convert to an URI, considering the location relative - // unless it is the well-known Spring prefix "classpath*:" - } +```java +protected void parseCollectionElements( + NodeList elementNodes, Collection target, @Nullable BeanDefinition bd, String defaultElementType) { + + for (int i = 0; i < elementNodes.getLength(); i++) { + Node node = elementNodes.item(i); + if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT)) { + // 处理子节点 + target.add(parsePropertySubElement((Element) node, bd, defaultElementType)); + } + } +} +``` - // Absolute or relative? - if (absoluteLocation) { - try { - // 获取import中的数量 - int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources); - if (logger.isTraceEnabled()) { - logger.trace("Imported " + importCount + " bean definitions from URL location [" + location + "]"); - } - } - catch (BeanDefinitionStoreException ex) { - getReaderContext().error( - "Failed to import bean definitions from URL location [" + location + "]", ele, ex); - } - } - else { - // No URL -> considering resource location as relative to the current file. - try { - int importCount; - // 本地地址 - Resource relativeResource = getReaderContext().getResource().createRelative(location); - if (relativeResource.exists()) { - // 此处调用方式和加载一个xml文件相同 - importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource); - - actualResources.add(relativeResource); - } - else { - String baseLocation = getReaderContext().getResource().getURL().toString(); - importCount = getReaderContext().getReader().loadBeanDefinitions( - StringUtils.applyRelativePath(baseLocation, location), actualResources); - } - if (logger.isTraceEnabled()) { - logger.trace("Imported " + importCount + " bean definitions from relative location [" + location + "]"); - } - } - catch (IOException ex) { - getReaderContext().error("Failed to resolve current resource location", ele, ex); +#### parseMapElement + +```java +public Map parseMapElement(Element mapEle, @Nullable BeanDefinition bd) { + // key-type 属性获取 + String defaultKeyType = mapEle.getAttribute(KEY_TYPE_ATTRIBUTE); + // value-type 属性互殴去 + String defaultValueType = mapEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + + // entry 标签获取 + List entryEles = DomUtils.getChildElementsByTagName(mapEle, ENTRY_ELEMENT); + // 合并 map 对象 + ManagedMap map = new ManagedMap<>(entryEles.size()); + map.setSource(extractSource(mapEle)); + map.setKeyTypeName(defaultKeyType); + map.setValueTypeName(defaultValueType); + map.setMergeEnabled(parseMergeAttribute(mapEle)); + + // 循环 entry 节点 + for (Element entryEle : entryEles) { + // Should only have one value child element: ref, value, list, etc. + // Optionally, there might be a key child element. + NodeList entrySubNodes = entryEle.getChildNodes(); + Element keyEle = null; + Element valueEle = null; + for (int j = 0; j < entrySubNodes.getLength(); j++) { + Node node = entrySubNodes.item(j); + if (node instanceof Element) { + Element candidateEle = (Element) node; + // 节点名称是否为 key + if (nodeNameEquals(candidateEle, KEY_ELEMENT)) { + if (keyEle != null) { + error(" element is only allowed to contain one sub-element", entryEle); + } + else { + keyEle = candidateEle; + } } - catch (BeanDefinitionStoreException ex) { - getReaderContext().error( - "Failed to import bean definitions from relative location [" + location + "]", ele, ex); + else { + // Child element is what we're looking for. + if (nodeNameEquals(candidateEle, DESCRIPTION_ELEMENT)) { + // the element is a -> ignore it + } + else if (valueEle != null) { + error(" element must not contain more than one value sub-element", entryEle); + } + else { + valueEle = candidateEle; + } } - } - // 转换数组 - Resource[] actResArray = actualResources.toArray(new Resource[0]); - /*** - * fireImportProcessed()触发import事件, - * 并且通过{@link org.springframework.beans.factory.parsing.ReaderEventListener#importProcessed(ImportDefinition)} 宣布处理结果 - */ - getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele)); - } + } + } + + // Extract key from attribute or sub-element. + Object key = null; + // key 属性 + boolean hasKeyAttribute = entryEle.hasAttribute(KEY_ATTRIBUTE); + // key-ref 属性 + boolean hasKeyRefAttribute = entryEle.hasAttribute(KEY_REF_ATTRIBUTE); + if ((hasKeyAttribute && hasKeyRefAttribute) || + (hasKeyAttribute || hasKeyRefAttribute) && keyEle != null) { + error(" element is only allowed to contain either " + + "a 'key' attribute OR a 'key-ref' attribute OR a sub-element", entryEle); + } + if (hasKeyAttribute) { + // TypedStringValue 构建 + key = buildTypedStringValueForMap(entryEle.getAttribute(KEY_ATTRIBUTE), defaultKeyType, entryEle); + } + else if (hasKeyRefAttribute) { + // key-ref 属性获取 + String refName = entryEle.getAttribute(KEY_REF_ATTRIBUTE); + if (!StringUtils.hasText(refName)) { + error(" element contains empty 'key-ref' attribute", entryEle); + } + // 创建 bean 连接对象 + RuntimeBeanReference ref = new RuntimeBeanReference(refName); + ref.setSource(extractSource(entryEle)); + key = ref; + } + else if (keyEle != null) { + // 获取 key 数据 + key = parseKeyElement(keyEle, bd, defaultKeyType); + } + else { + error(" element must specify a key", entryEle); + } + + // Extract value from attribute or sub-element. + Object value = null; + // value 属性是否存在 + boolean hasValueAttribute = entryEle.hasAttribute(VALUE_ATTRIBUTE); + // value-ref 属性是否存在 + boolean hasValueRefAttribute = entryEle.hasAttribute(VALUE_REF_ATTRIBUTE); + // 是否存在 value-type 属性 + boolean hasValueTypeAttribute = entryEle.hasAttribute(VALUE_TYPE_ATTRIBUTE); + if ((hasValueAttribute && hasValueRefAttribute) || + (hasValueAttribute || hasValueRefAttribute) && valueEle != null) { + error(" element is only allowed to contain either " + + "'value' attribute OR 'value-ref' attribute OR sub-element", entryEle); + } + if ((hasValueTypeAttribute && hasValueRefAttribute) || + (hasValueTypeAttribute && !hasValueAttribute) || + (hasValueTypeAttribute && valueEle != null)) { + error(" element is only allowed to contain a 'value-type' " + + "attribute when it has a 'value' attribute", entryEle); + } + if (hasValueAttribute) { + // 获取 value-type 属性 + String valueType = entryEle.getAttribute(VALUE_TYPE_ATTRIBUTE); + if (!StringUtils.hasText(valueType)) { + // 设置默认value-type + valueType = defaultValueType; + } + // 创建 TypedStringValue + value = buildTypedStringValueForMap(entryEle.getAttribute(VALUE_ATTRIBUTE), valueType, entryEle); + } + else if (hasValueRefAttribute) { + // 获取 value-ref 属性 + String refName = entryEle.getAttribute(VALUE_REF_ATTRIBUTE); + if (!StringUtils.hasText(refName)) { + error(" element contains empty 'value-ref' attribute", entryEle); + } + // 创建 bean 链接对象 + RuntimeBeanReference ref = new RuntimeBeanReference(refName); + ref.setSource(extractSource(entryEle)); + value = ref; + } + else if (valueEle != null) { + value = parsePropertySubElement(valueEle, bd, defaultValueType); + } + else { + error(" element must specify a value", entryEle); + } + // Add final key and value to the Map. + map.put(key, value); + } + + return map; +} ``` +#### parsePropsElement +### parsePropertyElement -![image-20200102085031641](/image/spring/image-20200102085031641.png) +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyElement` -![image-20200102091421516](/image/spring/image-20200102091421516.png) +```java +public void parsePropertyElement(Element ele, BeanDefinition bd) { + String propertyName = ele.getAttribute(NAME_ATTRIBUTE); + if (!StringUtils.hasLength(propertyName)) { + error("Tag 'property' must have a 'name' attribute", ele); + return; + } + this.parseState.push(new PropertyEntry(propertyName)); + try { + if (bd.getPropertyValues().contains(propertyName)) { + error("Multiple 'property' definitions for property '" + propertyName + "'", ele); + return; + } + // 解析 property 标签 + Object val = parsePropertyValue(ele, bd, propertyName); + // 构造 PropertyValue 对象 + PropertyValue pv = new PropertyValue(propertyName, val); + // 解析元信息 + parseMetaElements(ele, pv); + pv.setSource(extractSource(ele)); + // 添加 pv 结构 + bd.getPropertyValues().addPropertyValue(pv); + } + finally { + this.parseState.pop(); + } +} +``` +### parseQualifierElements +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseQualifierElements` +- 解析 qualifier 标签和下级标签 +```java +public void parseQualifierElements(Element beanEle, AbstractBeanDefinition bd) { + NodeList nl = beanEle.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ELEMENT)) { + // 单个解析 + parseQualifierElement((Element) node, bd); + } + } +} +``` + +### parseQualifierElement + +- `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseQualifierElement` + +```java +public void parseQualifierElement(Element ele, AbstractBeanDefinition bd) { + // 获取 type 属性 + String typeName = ele.getAttribute(TYPE_ATTRIBUTE); + if (!StringUtils.hasLength(typeName)) { + error("Tag 'qualifier' must have a 'type' attribute", ele); + return; + } + this.parseState.push(new QualifierEntry(typeName)); + try { + // 自动注入对象创建 + AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(typeName); + // 设置源 + qualifier.setSource(extractSource(ele)); + // 获取 value 属性 + String value = ele.getAttribute(VALUE_ATTRIBUTE); + if (StringUtils.hasLength(value)) { + // 设置 属性 value , value + qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, value); + } + NodeList nl = ele.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) { + Node node = nl.item(i); + if (isCandidateElement(node) && nodeNameEquals(node, QUALIFIER_ATTRIBUTE_ELEMENT)) { + Element attributeEle = (Element) node; + // 获取 key 属性 + String attributeName = attributeEle.getAttribute(KEY_ATTRIBUTE); + // 获取 value 属性 + String attributeValue = attributeEle.getAttribute(VALUE_ATTRIBUTE); + if (StringUtils.hasLength(attributeName) && StringUtils.hasLength(attributeValue)) { + // key value 属性映射 + BeanMetadataAttribute attribute = new BeanMetadataAttribute(attributeName, attributeValue); + attribute.setSource(extractSource(attributeEle)); + // 添加 qualifier 属性值 + qualifier.addMetadataAttribute(attribute); + } + else { + error("Qualifier 'attribute' tag must have a 'name' and 'value'", attributeEle); + return; + } + } + } + // 添加 qualifier + bd.addQualifier(qualifier); + } + finally { + this.parseState.pop(); + } +} +``` diff --git a/docs/Spring/clazz/Spring-BeanDefinitionReaderUtils.md b/docs/Spring/clazz/Spring-BeanDefinitionReaderUtils.md new file mode 100644 index 00000000..5375c0eb --- /dev/null +++ b/docs/Spring/clazz/Spring-BeanDefinitionReaderUtils.md @@ -0,0 +1,131 @@ +# 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` + +```java +public static AbstractBeanDefinition createBeanDefinition( + @Nullable String parentName, @Nullable String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException { + + GenericBeanDefinition bd = new GenericBeanDefinition(); + // 设置 父bean + bd.setParentName(parentName); + if (className != null) { + if (classLoader != null) { + // 设置 class + // 内部是通过反射创建 class + bd.setBeanClass(ClassUtils.forName(className, classLoader)); + } + else { + // 设置 class name + bd.setBeanClassName(className); + } + } + return bd; +} +``` + +## generateBeanName + +- `org.springframework.beans.factory.support.BeanDefinitionReaderUtils.generateBeanName(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry, boolean)` + +```java + public static String generateBeanName( + BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) + throws BeanDefinitionStoreException { + + // 获取 bean class 的名称 + // Class.getName() + String generatedBeanName = definition.getBeanClassName(); + if (generatedBeanName == null) { + // 父类名称是否存在 + if (definition.getParentName() != null) { + generatedBeanName = definition.getParentName() + "$child"; + } + // 工厂 beanName 是否为空 + else if (definition.getFactoryBeanName() != null) { + generatedBeanName = definition.getFactoryBeanName() + "$created"; + } + } + if (!StringUtils.hasText(generatedBeanName)) { + throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name"); + } + + String id = generatedBeanName; + if (isInnerBean) { + // Inner bean: generate identity hashcode suffix. + // 组装名称 + // 生成名称 + # + 16 进制的一个字符串 + id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition); + } + else { + // Top-level bean: use plain class name with unique suffix if necessary. + // 唯一beanName设置 + // // beanName + # + 序号 + return uniqueBeanName(generatedBeanName, registry); + } + return id; + } +``` + +## uniqueBeanName + +```java +public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) { + String id = beanName; + int counter = -1; + + // Increase counter until the id is unique. + while (counter == -1 || registry.containsBeanDefinition(id)) { + counter++; + // beanName + # + 序号 + id = beanName + GENERATED_BEAN_NAME_SEPARATOR + counter; + } + return id; +} +``` + +## registerBeanDefinition + +```java +public static void registerBeanDefinition( + BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + // Register bean definition under primary name. + // 获取 beanName + String beanName = definitionHolder.getBeanName(); + // 注册bean definition + registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); + + // Register aliases for bean name, if any. + // 别名列表 + String[] aliases = definitionHolder.getAliases(); + // 注册别名列表 + if (aliases != null) { + for (String alias : aliases) { + registry.registerAlias(beanName, alias); + } + } +} +``` + +## registerWithGeneratedName + +```java +public static String registerWithGeneratedName( + AbstractBeanDefinition definition, BeanDefinitionRegistry registry) + throws BeanDefinitionStoreException { + + // 生成一个 beanName + String generatedName = generateBeanName(definition, registry, false); + // 注册 bean Definition + registry.registerBeanDefinition(generatedName, definition); + return generatedName; +} +``` diff --git a/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md b/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md index e04a7421..36a9392c 100644 --- a/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md +++ b/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md @@ -1,10 +1,11 @@ # Spring BeanFactoryPostProcessor + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 作用: 定制或修改`BeanDefinition`的属性 - ## Demo + ```java public class ChangeAttrBeanPostProcessor implements BeanFactoryPostProcessor { private Set attr; @@ -75,20 +76,18 @@ public class BeanFactoryPostProcessorSourceCode { ``` - - ## 初始化 - `org.springframework.context.support.AbstractApplicationContext#refresh` - ```JAVA + ```java invokeBeanFactoryPostProcessors(beanFactory); ``` - ```JAVA + ```java protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); - + // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor) if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { @@ -96,15 +95,15 @@ public class BeanFactoryPostProcessorSourceCode { beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } } - + ``` - `org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List)` - ```JAVA + ```java public static void invokeBeanFactoryPostProcessors( ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) { - + // Invoke BeanDefinitionRegistryPostProcessors first, if any. Set processedBeans = new HashSet<>(); // 判断是否为BeanDefinitionRegistry类 @@ -114,7 +113,7 @@ public class BeanFactoryPostProcessorSourceCode { List regularPostProcessors = new ArrayList<>(); // 存放 BeanDefinitionRegistryPostProcessor List registryProcessors = new ArrayList<>(); - + // 2.首先处理入参中的beanFactoryPostProcessors for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) { // 判断是否是BeanDefinitionRegistryPostProcessor @@ -133,13 +132,13 @@ public class BeanFactoryPostProcessorSourceCode { regularPostProcessors.add(postProcessor); } } - + // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // Separate between BeanDefinitionRegistryPostProcessors that implement // PriorityOrdered, Ordered, and the rest. List currentRegistryProcessors = new ArrayList<>(); - + // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered. /** * 调用实现{@link PriorityOrdered}\{@link BeanDefinitionRegistryPostProcessor} @@ -159,7 +158,7 @@ public class BeanFactoryPostProcessorSourceCode { registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); - + // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { @@ -172,7 +171,7 @@ public class BeanFactoryPostProcessorSourceCode { registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); - + // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. boolean reiterate = true; while (reiterate) { @@ -190,7 +189,7 @@ public class BeanFactoryPostProcessorSourceCode { invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); } - + // Now, invoke the postProcessBeanFactory callback of all processors handled so far. invokeBeanFactoryPostProcessors(registryProcessors, beanFactory); invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory); @@ -198,13 +197,13 @@ public class BeanFactoryPostProcessorSourceCode { // Invoke factory processors registered with the context instance. invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory); } - + // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the bean factory post-processors apply to them! // 配置文件中的 BeanFactoryPostProcessor 处理 String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false); - + // Separate between BeanFactoryPostProcessors that implement PriorityOrdered, // Ordered, and the rest. List priorityOrderedPostProcessors = new ArrayList<>(); @@ -222,11 +221,11 @@ public class BeanFactoryPostProcessorSourceCode { nonOrderedPostProcessorNames.add(ppName); } } - + // First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered. sortPostProcessors(priorityOrderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory); - + // Next, invoke the BeanFactoryPostProcessors that implement Ordered. List orderedPostProcessors = new ArrayList<>(); for (String postProcessorName : orderedPostProcessorNames) { @@ -234,7 +233,7 @@ public class BeanFactoryPostProcessorSourceCode { } sortPostProcessors(orderedPostProcessors, beanFactory); invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory); - + // Finally, invoke all other BeanFactoryPostProcessors. // 配置文件中自定义的 BeanFactoryPostProcessor 注册 List nonOrderedPostProcessors = new ArrayList<>(); @@ -242,22 +241,16 @@ public class BeanFactoryPostProcessorSourceCode { nonOrderedPostProcessors.add(beanFactory.getBean(postProcessorName, BeanFactoryPostProcessor.class)); } invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory); - + // Clear cached merged bean definitions since the post-processors might have // modified the original metadata, e.g. replacing placeholders in values... beanFactory.clearMetadataCache(); } ``` - - -![image-20200119085346675](../../../images/spring/image-20200119085346675.png) - -![image-20200119085655734](../../../images/spring/image-20200119085655734.png) - - - +![image-20200119085346675](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119085346675.png) +![image-20200119085655734](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119085655734.png) ## InstantiationAwareBeanPostProcessor @@ -267,8 +260,6 @@ public class BeanFactoryPostProcessorSourceCode { } ``` - - ```java public static void registerBeanPostProcessors( ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { @@ -356,9 +347,7 @@ public class BeanFactoryPostProcessorSourceCode { ``` - - -- 测试用Bean +- 测试用 Bean ```java public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor { @@ -370,35 +359,27 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa } ``` +- 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序 Bean +![image-20200119101026726](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119101026726.png) -- 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序Bean - - ![image-20200119101026726](../../../images/spring/image-20200119101026726.png) - - - -![image-20200119101017989](../../../images/spring/image-20200119101017989.png) +![image-20200119101017989](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119101017989.png) - 注册方法信息截图 -![image-20200119101107820](../../../images/spring/image-20200119101107820.png) - - - - +![image-20200119101107820](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119101107820.png) ### 使用阶段(调用阶段) 在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])`中有如下代码 -```JAVA +```java Object bean = resolveBeforeInstantiation(beanName, mbdToUse); ``` - `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation` -```JAVA +```java @Nullable protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; @@ -424,7 +405,7 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa - `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation` -```JAVA +```java @Nullable protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) { for (BeanPostProcessor bp : getBeanPostProcessors()) { @@ -444,4 +425,4 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa 这个地方已经可以看到`InstantiationAwareBeanPostProcessor`出现了,并且调用了方法`postProcessBeforeInstantiation`,此处就可以调用我们的自定义方法了 -![image-20200119101516591](../../../images/spring/image-20200119101516591.png) \ No newline at end of file +![image-20200119101516591](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119101516591.png) diff --git a/docs/Spring/clazz/Spring-BeanNameGenerator.md b/docs/Spring/clazz/Spring-BeanNameGenerator.md new file mode 100644 index 00000000..90748cef --- /dev/null +++ b/docs/Spring/clazz/Spring-BeanNameGenerator.md @@ -0,0 +1,121 @@ +# 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 { + + /** + * Generate a bean name for the given bean definition. + * 生成 beanName + * @param definition the bean definition to generate a name for + * @param registry the bean definition registry that the given definition + * is supposed to be registered with + * @return the generated bean name + */ + String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry); + +} +``` + +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/BeanNameGenerator.png) + +## DefaultBeanNameGenerator + +- `org.springframework.beans.factory.support.DefaultBeanNameGenerator` + +- 调用工具类方法进行生成 + +```java +@Override +public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { + return BeanDefinitionReaderUtils.generateBeanName(definition, registry); +} +``` + +1. ClassName + # + 十六进制字符 +2. parentName + \$child + # + 十六进制字符 +3. factoryBeanName +\$created+# + 十六进制字符 +4. beanName + # + 序号 + +```java +public static String generateBeanName( + BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) + throws BeanDefinitionStoreException { + + // 获取 bean class 的名称 + // Class.getName() + String generatedBeanName = definition.getBeanClassName(); + if (generatedBeanName == null) { + // 父类名称是否存在 + if (definition.getParentName() != null) { + generatedBeanName = definition.getParentName() + "$child"; + } + // 工厂 beanName 是否为空 + else if (definition.getFactoryBeanName() != null) { + generatedBeanName = definition.getFactoryBeanName() + "$created"; + } + } + if (!StringUtils.hasText(generatedBeanName)) { + throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither " + + "'class' nor 'parent' nor 'factory-bean' - can't generate bean name"); + } + + String id = generatedBeanName; + if (isInnerBean) { + // Inner bean: generate identity hashcode suffix. + // 组装名称 + // 生成名称 + # + 16 进制的一个字符串 + id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition); + } + else { + // Top-level bean: use plain class name with unique suffix if necessary. + // 唯一beanName设置 + // // beanName + # + 序号 + return uniqueBeanName(generatedBeanName, registry); + } + return id; +} +``` + +## AnnotationBeanNameGenerator + +1. 获取注解的 value 作为 beanName +2. 类名首字母小写 + +```java +@Override +public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { + if (definition instanceof AnnotatedBeanDefinition) { + // 从注解中获取 beanName + // 获取注解的value属性值 + String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition); + if (StringUtils.hasText(beanName)) { + // Explicit bean name found. + // 如果存在直接返回 + return beanName; + } + } + // Fallback: generate a unique default bean name. + // 默认beanName + // 类名,首字母小写 + return buildDefaultBeanName(definition, registry); +} +``` + +## FullyQualifiedAnnotationBeanNameGenerator + +- 全类名 + +```java +@Override +protected String buildDefaultBeanName(BeanDefinition definition) { + String beanClassName = definition.getBeanClassName(); + Assert.state(beanClassName != null, "No bean class name set"); + return beanClassName; +} +``` diff --git a/docs/Spring/clazz/Spring-Conditional.md b/docs/Spring/clazz/Spring-Conditional.md new file mode 100644 index 00000000..716ade0f --- /dev/null +++ b/docs/Spring/clazz/Spring-Conditional.md @@ -0,0 +1,258 @@ +# Spring Conditional + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +## Conditional + +```java +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Conditional { + + /** + * 多个匹配器接口 + */ + Class[] value(); + +} +``` + +## Condition + +``` +@FunctionalInterface +public interface Condition { + + /** + * 匹配,如果匹配返回true进行初始化,返回false跳过初始化 + */ + boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); + +} +``` + +- ConditionContext 上下文 +- AnnotatedTypeMetadata 注解信息 + +### ConditionContext + +``` +public interface ConditionContext { + + /** + * bean的定义 + */ + BeanDefinitionRegistry getRegistry(); + + /** + * bean 工厂 + */ + @Nullable + ConfigurableListableBeanFactory getBeanFactory(); + + /** + * 环境 + */ + Environment getEnvironment(); + + /** + * 资源加载器 + */ + ResourceLoader getResourceLoader(); + + /** + * 类加载器 + */ + @Nullable + ClassLoader getClassLoader(); + +} +``` + +- 唯一实现 : `org.springframework.context.annotation.ConditionEvaluator.ConditionContextImpl` + +- 构造方法 + +```java +public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry, + @Nullable Environment environment, @Nullable ResourceLoader resourceLoader) { + + this.registry = registry; + this.beanFactory = deduceBeanFactory(registry); + this.environment = (environment != null ? environment : deduceEnvironment(registry)); + this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry)); + this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory); +} +``` + +- 在构造方法中加载了一些变量, 这些变量是根据一定规则转换后得到. 具体规则不展开. + +### AnnotatedTypeMetadata + +- 元数据接口 + +```java +public interface AnnotatedTypeMetadata { + + /** + * 获取所有注解 + */ + MergedAnnotations getAnnotations(); + + /** + * 是否有注解 + */ + default boolean isAnnotated(String annotationName) { + return getAnnotations().isPresent(annotationName); + } + + /** + * 获取注解的属性 + */ + @Nullable + default Map getAnnotationAttributes(String annotationName) { + return getAnnotationAttributes(annotationName, false); + } + +} +``` + +## 源码分析 + +- 对应测试类`org.springframework.context.annotation.ConfigurationClassWithConditionTests` + +```java +@Test +public void conditionalOnMissingBeanMatch() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class); + ctx.refresh(); + assertThat(ctx.containsBean("bean1")).isTrue(); + assertThat(ctx.containsBean("bean2")).isFalse(); + assertThat(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration")).isFalse(); +} + + + + @Configuration + static class BeanOneConfiguration { + + @Bean + public ExampleBean bean1() { + return new ExampleBean(); + } + } + + @Configuration + @Conditional(NoBeanOneCondition.class) + static class BeanTwoConfiguration { + + @Bean + public ExampleBean bean2() { + return new ExampleBean(); + } + } + + + static class NoBeanOneCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !context.getBeanFactory().containsBeanDefinition("bean1"); + } + } + +``` + +- `org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean` + +### shouldSkip + +```java +public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) { + if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) { + return false; + } + + if (phase == null) { + if (metadata instanceof AnnotationMetadata && + ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) { + return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); + } + return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN); + } + + List conditions = new ArrayList<>(); + // 获取注解 Conditional 的属性值 + for (String[] conditionClasses : getConditionClasses(metadata)) { + for (String conditionClass : conditionClasses) { + // 序列化成注解 + Condition condition = getCondition(conditionClass, this.context.getClassLoader()); + // 插入注解列表 + conditions.add(condition); + } + } + + AnnotationAwareOrderComparator.sort(conditions); + + for (Condition condition : conditions) { + ConfigurationPhase requiredPhase = null; + if (condition instanceof ConfigurationCondition) { + requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); + } + + // matches 进行验证 + if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { + return true; + } + } + + return false; +} +``` + +- 读取注解信息, 并且执行注解对应的类的方法 + + 用例如下. 实例化`BeanTwoConfiguration`对象的时候会去执行`NoBeanOneCondition`方法 + + ```java + @Configuration + @Conditional(NoBeanOneCondition.class) + static class BeanTwoConfiguration { + + @Bean + public ExampleBean bean2() { + return new ExampleBean(); + } + } + + + static class NoBeanOneCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return !context.getBeanFactory().containsBeanDefinition("bean1"); + } + } + ``` + +在开发中可以自定义 matches 规则 + +这也是在注册的时候第一个方法 + +```java +private void doRegisterBean(Class beanClass, @Nullable String name, + @Nullable Class[] qualifiers, @Nullable Supplier supplier, + @Nullable BeanDefinitionCustomizer[] customizers) { + + AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass); + // 和条件注解相关的函数 + if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) { + return; + } + + // 省略其他 +} +``` diff --git a/docs/Spring/clazz/Spring-Custom-attribute-resolver.md b/docs/Spring/clazz/Spring-Custom-attribute-resolver.md index af097a42..7281c6cd 100644 --- a/docs/Spring/clazz/Spring-Custom-attribute-resolver.md +++ b/docs/Spring/clazz/Spring-Custom-attribute-resolver.md @@ -1,8 +1,10 @@ # Spring 自定义属性解析器 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) ## 用例 + ```xml requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) { @@ -111,7 +112,7 @@ public class DatePropertyEditor extends PropertyEditorSupport { - `PropertyEditorRegistrySupport` - ![image-20200117111131406](../../../images/spring/image-20200117111131406.png) + ![image-20200117111131406](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117111131406.png) 此处对象是通过`DatePropertyRegister`传递的 @@ -166,9 +167,7 @@ public class DatePropertyEditor extends PropertyEditorSupport { - 在`AbstractBeanFactory`中查看变量 -![image-20200117110115741](../../../images/spring/image-20200117110115741.png) - - +![image-20200117110115741](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117110115741.png) - 为什么最后结果变成`com.huifer.source.spring.bean.DatePropertyEditor` @@ -181,10 +180,10 @@ public class DatePropertyEditor extends PropertyEditorSupport { - + ``` - - 对应的set方法 + - 对应的 set 方法 ```java public void setCustomEditors(Map, Class> customEditors) { @@ -192,36 +191,26 @@ public class DatePropertyEditor extends PropertyEditorSupport { } ``` - ![image-20200117110846256](../../../images/spring/image-20200117110846256.png) - - - - - - - - + ![image-20200117110846256](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117110846256.png) ## applyPropertyValues - 应用属性值 - - ```java protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { if (pvs.isEmpty()) { return; } - + if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) { ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); } - + MutablePropertyValues mpvs = null; // 没有解析的属性 List original; - + if (pvs instanceof MutablePropertyValues) { mpvs = (MutablePropertyValues) pvs; if (mpvs.isConverted()) { @@ -248,7 +237,7 @@ public class DatePropertyEditor extends PropertyEditorSupport { } // 创建BeanDefinitionValueResolver BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); - + // Create a deep copy, resolving any references for values. // 解析后的对象集合 List deepCopy = new ArrayList<>(original.size()); @@ -303,7 +292,7 @@ public class DatePropertyEditor extends PropertyEditorSupport { // 转换成功的标记方法 mpvs.setConverted(); } - + // Set our (possibly massaged) deep copy. try { bw.setPropertyValues(new MutablePropertyValues(deepCopy)); @@ -313,36 +302,24 @@ public class DatePropertyEditor extends PropertyEditorSupport { mbd.getResourceDescription(), beanName, "Error setting property values", ex); } } - - ``` - - - - ![image-20200117133325461](../../../images/spring/image-20200117133325461.png) - - - - - -![image-20200117141309038](../../../images/spring/image-20200117141309038.png) - - - -![image-20200117141519123](../../../images/spring/image-20200117141519123.png) + ``` + ![image-20200117133325461](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117133325461.png) +![image-20200117141309038](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117141309038.png) +![image-20200117141519123](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117141519123.png) - 属性值解析 - ![image-20200117142800671](../../../images/spring/image-20200117142800671.png) + ![image-20200117142800671](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117142800671.png) - ```JAVA + ```java @Nullable private Object convertForProperty( @Nullable Object value, String propertyName, BeanWrapper bw, TypeConverter converter) { - + if (converter instanceof BeanWrapperImpl) { return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName); } @@ -352,12 +329,10 @@ public class DatePropertyEditor extends PropertyEditorSupport { return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam); } } - - ``` - + ``` -```JAVA +```java private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) { try { editor.setValue(oldValue); @@ -375,11 +350,9 @@ public class DatePropertyEditor extends PropertyEditorSupport { ``` - - - 调用用例编写的方法 - ```JAVA + ```java @Override public void setAsText(String text) throws IllegalArgumentException { System.out.println(text); @@ -389,15 +362,12 @@ public class DatePropertyEditor extends PropertyEditorSupport { this.setValue(date); } catch (Exception e) { e.printStackTrace(); - + } } - - ``` - + ``` -![image-20200117143022827](../../../images/spring/image-20200117143022827.png) +![image-20200117143022827](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117143022827.png) 该值也是这个方法的返回`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class, org.springframework.core.convert.TypeDescriptor)` - diff --git a/docs/Spring/clazz/Spring-Custom-label-resolution.md b/docs/Spring/clazz/Spring-Custom-label-resolution.md index ddf0baab..233323a9 100644 --- a/docs/Spring/clazz/Spring-Custom-label-resolution.md +++ b/docs/Spring/clazz/Spring-Custom-label-resolution.md @@ -1,15 +1,16 @@ # Spring 自定义标签解析 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 与自定义标签解析相关的类 - 1. `org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser` - 2. `org.springframework.beans.factory.xml.NamespaceHandlerSupport` - + 1. `org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser` + 2. `org.springframework.beans.factory.xml.NamespaceHandlerSupport` - 开始源码之前先搭建一个环境 - ## 环境搭建 + - 创建对象 + ```java public class UserXtd { private String userName; @@ -32,7 +33,9 @@ public class UserXtd { } } ``` + - 创建 xsd 文件 + ```xml ``` + - 创建 namespaceHandler + ```java public class UserNamespaceHandler extends NamespaceHandlerSupport { @Override @@ -57,7 +62,9 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { } } ``` + - 创建 beanDefinitionParser + ```java public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { /** @@ -88,15 +95,21 @@ public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser } ``` + - 创建 resource/META-INF/spring.handlers + ```text http\://www.huifer.com/schema/user=com.huifer.source.spring.parser.UserNamespaceHandler ``` + - 创建 resource/META-INF/spring.schemas + ```text http\://www.huifer.com/schema/user.xsd=META-INF/spring-test.xsd ``` -- 创建测试用例xml + +- 创建测试用例 xml + ```xml ``` + - 创建 Java 运行方法 + ```java /** * 自定义标签测试用例 @@ -123,10 +138,13 @@ public class XSDDemo { } } ``` + - 这里我们希望输出结果是`huifer97@163.com`,运行后结果也确实是`huifer97@163.com` ## 解析 DefaultNamespaceHandlerResolver + - 入口方法`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions` + ```java protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { @@ -152,10 +170,11 @@ public class XSDDemo { } ``` + - 调用链路 - `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element)` - - `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)` - + - `org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)` + ```java /** * Parse a custom element (outside of the default namespace). @@ -185,18 +204,16 @@ public class XSDDemo { ``` -![image-20200109084131415](../../../images/spring/image-20200109084131415.png) +![image-20200109084131415](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109084131415.png) -- `http://www.huifer.com/schema/user`和我们定义的xsd文件中的url相同,如何找到对应的NamespaceHandler,在`META-INF/spring.handlers`中有定义, +- `http://www.huifer.com/schema/user`和我们定义的 xsd 文件中的 url 相同,如何找到对应的 NamespaceHandler,在`META-INF/spring.handlers`中有定义, `http\://www.huifer.com/schema/user=com.huifer.source.spring.parser.UserNamespaceHandler` - `NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);`这行代码就是获取`spring.handlers`中的定义 + `NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);`这行代码就是获取`spring.handlers`中的定义 - 处理方法`org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver.resolve` - - ```java /** * Locate the {@link NamespaceHandler} for the supplied namespace URI @@ -252,11 +269,9 @@ public class XSDDemo { ``` - - - `org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#getHandlerMappings`跟踪这个方法 -```JAVA +```java /** * Load the specified NamespaceHandler mappings lazily. * @@ -267,15 +282,13 @@ public class XSDDemo { } ``` - - -![image-20200109085606240](../../../images/spring/image-20200109085606240.png) +![image-20200109085606240](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109085606240.png) - 这里直接存在数据了,他是从什么时候加载的? - `org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions` - 这个方法在注册bean定义的时候调用 + 这个方法在注册 bean 定义的时候调用 ```java public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { @@ -286,7 +299,7 @@ public class XSDDemo { documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; } - + ``` - 继续跟踪`createReaderContext` @@ -300,7 +313,7 @@ public class XSDDemo { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); } - + ``` - 继续跟踪`getNamespaceHandlerResolver` @@ -314,7 +327,7 @@ public class XSDDemo { } return this.namespaceHandlerResolver; } - + ``` - 继续跟踪`createDefaultNamespaceHandlerResolver` @@ -328,8 +341,6 @@ public class XSDDemo { } ``` - - - 继续跟踪`DefaultNamespaceHandlerResolver` `org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver` @@ -338,16 +349,16 @@ public class XSDDemo { public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) { this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION); } - + ``` 他回到了我们之前疑问的地方 `handlerMappings` 如何出现的 断点 - ![image-20200109090456547](../../../images/spring/image-20200109090456547.png) + ![image-20200109090456547](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109090456547.png) - ```JAVA + ```java public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) { this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION); } @@ -355,25 +366,23 @@ public class XSDDemo { `public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";` - ![image-20200109090655157](../../../images/spring/image-20200109090655157.png) + ![image-20200109090655157](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109090655157.png) 此时还是空 走完 - ![image-20200109091216505](../../../images/spring/image-20200109091216505.png) + ![image-20200109091216505](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109091216505.png) ```java @Override public String toString() { return "NamespaceHandlerResolver using mappings " + getHandlerMappings(); } - - ``` - + ``` -```JAVA +```java /** * Load the specified NamespaceHandler mappings lazily. * @@ -413,13 +422,7 @@ public class XSDDemo { ``` -![image-20200109094032421](../../../images/spring/image-20200109094032421.png) - - - - - - +![image-20200109094032421](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109094032421.png) ## org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve @@ -495,8 +498,6 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { ``` - - - 方法走完,回到开始的方法 ```java @@ -526,16 +527,10 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { // 自定义处理器 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } - - ``` - - - -![image-20200109092801572](../../../images/spring/image-20200109092801572.png) - - + ``` +![image-20200109092801572](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109092801572.png) ## org.springframework.beans.factory.xml.NamespaceHandler#parse @@ -548,7 +543,7 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { BeanDefinitionParser parser = findParserForElement(element, parserContext); return (parser != null ? parser.parse(element, parserContext) : null); } - + ``` ### org.springframework.beans.factory.xml.NamespaceHandlerSupport#findParserForElement @@ -571,7 +566,7 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { ``` -![image-20200109093242494](../../../images/spring/image-20200109093242494.png) +![image-20200109093242494](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109093242494.png) ### org.springframework.beans.factory.xml.BeanDefinitionParser#parse @@ -583,8 +578,8 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { * {@link AbstractSingleBeanDefinitionParser#parseInternal(org.w3c.dom.Element, org.springframework.beans.factory.xml.ParserContext)} */ AbstractBeanDefinition definition = parseInternal(element, parserContext); - - + + } ``` @@ -629,13 +624,11 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { } ``` -![image-20200109094654409](../../../images/spring/image-20200109094654409.png) - - +![image-20200109094654409](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109094654409.png) 执行`com.huifer.source.spring.parser.UserBeanDefinitionParser#doParse` -```JAVA +```java @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { // 获取 userName 标签属性值 @@ -651,8 +644,3 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { } } ``` - - - - - diff --git a/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md b/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md index e9a13686..83e5e0bb 100644 --- a/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md +++ b/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md @@ -1,28 +1,29 @@ # DefaultSingletonBeanRegistry + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - 源码路径: `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry` - 官方提供的测试类: `org.springframework.beans.factory.support.DefaultSingletonBeanRegistryTests` 类图 -![image-20200110093044672](../../../images/spring/image-20200110093044672.png) +![image-20200110093044672](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200110093044672.png) + ## 注册方法解析 + - 从名字可以看出这是一个单例对象的注册类 - `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.registerSingleton` - - - 测试用例出发 ```java @Test public void testSingletons() { DefaultSingletonBeanRegistry beanRegistry = new DefaultSingletonBeanRegistry(); - + TestBean tb = new TestBean(); beanRegistry.registerSingleton("tb", tb); assertSame(tb, beanRegistry.getSingleton("tb")); - + TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", new ObjectFactory() { @Override public Object getObject() throws BeansException { @@ -30,7 +31,7 @@ } }); assertSame(tb2, beanRegistry.getSingleton("tb2")); - + assertSame(tb, beanRegistry.getSingleton("tb")); assertSame(tb2, beanRegistry.getSingleton("tb2")); assertEquals(2, beanRegistry.getSingletonCount()); @@ -38,15 +39,13 @@ assertEquals(2, names.length); assertEquals("tb", names[0]); assertEquals("tb2", names[1]); - + beanRegistry.destroySingletons(); assertEquals(0, beanRegistry.getSingletonCount()); assertEquals(0, beanRegistry.getSingletonNames().length); } - - ``` - + ``` - 第一个关注的方法`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerSingleton` 注册单例对象 @@ -76,6 +75,7 @@ } ``` + ```java /** * Add the given singleton object to the singleton cache of this factory. @@ -95,7 +95,9 @@ } } ``` + - 这些变量是什么 + ```java /** * 单例对象的缓存: beanName -> Object @@ -153,7 +155,9 @@ - 注册方法至此结束 ## 获取方法解析 + - `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(java.lang.String)` + ```java @Override @Nullable @@ -190,9 +194,10 @@ ``` -- 获取单例对象的本质就是从map中获取 ObjectFactory 进而执行 getObject() -`ObjectFactory singletonFactory = this.singletonFactories.get(beanName);` +- 获取单例对象的本质就是从 map 中获取 ObjectFactory 进而执行 getObject() + `ObjectFactory singletonFactory = this.singletonFactories.get(beanName);` - 测试方法 + ```java TestBean tb2 = (TestBean) beanRegistry.getSingleton("tb2", new ObjectFactory() { @@ -275,4 +280,4 @@ new ObjectFactory() { } ``` -通过`getObject`就可以获取当前对象 \ No newline at end of file +通过`getObject`就可以获取当前对象 diff --git a/docs/Spring/clazz/Spring-EntityResolver.md b/docs/Spring/clazz/Spring-EntityResolver.md index 38cfda85..b6b0e10a 100644 --- a/docs/Spring/clazz/Spring-EntityResolver.md +++ b/docs/Spring/clazz/Spring-EntityResolver.md @@ -1,10 +1,13 @@ # EntityResolver + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework) -- 源码路径: `org.xml.sax.EntityResolver`,非Spring类 +- 源码路径: `org.xml.sax.EntityResolver`,非 Spring 类 ## DelegatingEntityResolver#resolveEntity + - org.springframework.beans.factory.xml.DelegatingEntityResolver.resolveEntity + ```java @Override @Nullable @@ -25,16 +28,21 @@ } ``` -- 上述这段代码是针对xml进行校验 + +- 上述这段代码是针对 xml 进行校验 + ```xml ``` + - 如上所示以`.xsd`结尾,应该执行` return this.schemaResolver.resolveEntity(publicId, systemId);`方法 -`http://www.springframework.org/schema/beans/spring-beans.xsd` + `http://www.springframework.org/schema/beans/spring-beans.xsd` - `org.springframework.beans.factory.xml.PluggableSchemaResolver.resolveEntity` + ## PluggableSchemaResolver#resolveEntity + ```java @Override @Nullable @@ -79,15 +87,15 @@ ``` -![image-20200108081404857](../../../images/spring//image-20200108081404857.png) +![image-20200108081404857](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring//image-20200108081404857.png) -![image-20200108081623427](../../../images/spring//image-20200108081623427.png) +![image-20200108081623427](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring//image-20200108081623427.png) 得到本地路径,后续直接返回读取资源 ## BeansDtdResolver#resolveEntity -创建一个Dtd的约束文件 +创建一个 Dtd 的约束文件 ```xml @@ -141,18 +149,14 @@ ``` - - - - systemId `https://www.springframework.org/dtd/spring-beans-2.0.dtd` -![image-20200108082335031](../../../images/spring//image-20200108082335031.png) - +![image-20200108082335031](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring//image-20200108082335031.png) ## 总结 -- DelegatingEntityResolver#resolveEntity,是对xml文档的校验前置步骤,根据`dtd`和`xsd`加载本地资源文件 - `spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.dtd` - `spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.xsd` +- DelegatingEntityResolver#resolveEntity,是对 xml 文档的校验前置步骤,根据`dtd`和`xsd`加载本地资源文件 + `spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.dtd` + `spring-framework\spring-beans\src\main\resources\org\springframework\beans\factory\xml\spring-beans.xsd` - `PluggableSchemaResolver`负责加载`xsd`文件 -- `BeansDtdResolver`负责加载`dtd`文件 \ No newline at end of file +- `BeansDtdResolver`负责加载`dtd`文件 diff --git a/docs/Spring/clazz/Spring-Import.md b/docs/Spring/clazz/Spring-Import.md index 30be05f2..d4ff0c74 100644 --- a/docs/Spring/clazz/Spring-Import.md +++ b/docs/Spring/clazz/Spring-Import.md @@ -1,9 +1,12 @@ -# Spring Import +# Spring Import + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) ## 分析 + - org.springframework.context.annotation.Import + ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -19,9 +22,12 @@ public @interface Import { Class[] value(); } ``` + ### ImportBeanDefinitionRegistrar -- 注册Import Bean + +- 注册 Import Bean - `org.springframework.context.annotation.ImportBeanDefinitionRegistrar` + ```java public interface ImportBeanDefinitionRegistrar { @@ -40,11 +46,13 @@ public interface ImportBeanDefinitionRegistrar { } ``` + - 两个实现类 - 1. `org.springframework.context.annotation.AutoProxyRegistrar` - 2. `org.springframework.context.annotation.AspectJAutoProxyRegistrar` + 1. `org.springframework.context.annotation.AutoProxyRegistrar` + 2. `org.springframework.context.annotation.AspectJAutoProxyRegistrar` #### AutoProxyRegistrar + ```java public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @@ -95,4 +103,4 @@ public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { } } -``` \ No newline at end of file +``` diff --git a/docs/Spring/clazz/Spring-MessageSource.md b/docs/Spring/clazz/Spring-MessageSource.md index 882827b3..35af8eb1 100644 --- a/docs/Spring/clazz/Spring-MessageSource.md +++ b/docs/Spring/clazz/Spring-MessageSource.md @@ -1,11 +1,11 @@ # Spring MessageSource + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) ## 初始化入口 -- `org.springframework.context.support.AbstractApplicationContext.refresh`方法有`initMessageSource()`方法进行了`MessageSource`初始化 - +- `org.springframework.context.support.AbstractApplicationContext.refresh`方法有`initMessageSource()`方法进行了`MessageSource`初始化 ```java protected void initMessageSource() { @@ -43,19 +43,9 @@ ``` +读取 xml 配置文件 - - - -读取xml配置文件 - -![image-20200119141937915](../../../images/spring/image-20200119141937915.png) - - - - - - +![image-20200119141937915](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119141937915.png) ## getMessage @@ -86,7 +76,7 @@ } throw new NoSuchMessageException(code, locale); } - + ``` - 两个方法 @@ -102,14 +92,14 @@ } return null; } - + ``` - - 返回code本身或者`null` + - 返回 code 本身或者`null` 2. `org.springframework.context.support.AbstractMessageSource#getMessageInternal` - ```JAVA + ```java @Nullable protected String getMessageInternal(@Nullable String code, @Nullable Object[] args, @Nullable Locale locale) { if (code == null) { @@ -120,7 +110,7 @@ locale = Locale.getDefault(); } Object[] argsToUse = args; - + if (!isAlwaysUseMessageFormat() && ObjectUtils.isEmpty(args)) { // Optimized resolution: no arguments to apply, // therefore no MessageFormat needs to be involved. @@ -131,13 +121,13 @@ return message; } } - + else { // Resolve arguments eagerly, for the case where the message // is defined in a parent MessageSource but resolvable arguments // are defined in the child MessageSource. argsToUse = resolveArguments(args, locale); - + MessageFormat messageFormat = resolveCode(code, locale); if (messageFormat != null) { synchronized (messageFormat) { @@ -145,7 +135,7 @@ } } } - + // Check locale-independent common messages for the given message code. Properties commonMessages = getCommonMessages(); if (commonMessages != null) { @@ -154,18 +144,16 @@ return formatMessage(commonMessage, args, locale); } } - + // Not found -> check parent, if any. return getMessageFromParent(code, argsToUse, locale); } - - ``` - + ``` - `org.springframework.context.support.ResourceBundleMessageSource#resolveCodeWithoutArguments` - ```JAVA + ```java @Override protected String resolveCodeWithoutArguments(String code, Locale locale) { Set basenames = getBasenameSet(); @@ -182,29 +170,17 @@ } return null; } - - ``` - - - - - - - -![image-20200119143046066](../../../images/spring/image-20200119143046066.png) + ``` +![image-20200119143046066](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119143046066.png) - 加载后截图 - 获取方法`String result = getStringOrNull(bundle, code);`就是map获取 - -![image-20200119144019171](../../../images/spring/image-20200119144019171.png) - - - + 获取方法`String result = getStringOrNull(bundle, code);`就是 map 获取 +![image-20200119144019171](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119144019171.png) - 没有配置文件的情况 - ![image-20200119145138205](../../../images/spring/image-20200119145138205.png) \ No newline at end of file + ![image-20200119145138205](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119145138205.png) diff --git a/docs/Spring/clazz/Spring-Metadata.md b/docs/Spring/clazz/Spring-Metadata.md new file mode 100644 index 00000000..adc1422f --- /dev/null +++ b/docs/Spring/clazz/Spring-Metadata.md @@ -0,0 +1,872 @@ +# Spring 元信息 + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) + +## ClassMetadata + +```java +public interface ClassMetadata { + + /** + * 类名 + */ + String getClassName(); + + /** + * 是否是接口 + */ + boolean isInterface(); + + /** + * 是否是注解 + */ + boolean isAnnotation(); + + /** + * 是否是超类 + */ + boolean isAbstract(); + + /** + * 是否允许创建,实例化 + */ + default boolean isConcrete() { + return !(isInterface() || isAbstract()); + } + + /** + * 是否有final修饰 + */ + boolean isFinal(); + + /** + * 是否独立 + */ + boolean isIndependent(); + + /** + * 是否有内部类 + */ + default boolean hasEnclosingClass() { + return (getEnclosingClassName() != null); + } + + /** + * 是否是基础类 + */ + @Nullable + String getEnclosingClassName(); + + /** + * 是否有父类 + */ + default boolean hasSuperClass() { + return (getSuperClassName() != null); + } + + /** + * 父类名称 + */ + @Nullable + String getSuperClassName(); + + /** + * 实现类名称列表 + */ + String[] getInterfaceNames(); + + /** + * 成员列表 + * @since 3.1 + */ + String[] getMemberClassNames(); + +} +``` + +![image-20200824094154847](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200824094154847.png) + +## AnnotatedTypeMetadata + +```java +public interface AnnotatedTypeMetadata { + + /** + * 获取所有注解 + */ + MergedAnnotations getAnnotations(); + + /** + * 是否有注解 + */ + default boolean isAnnotated(String annotationName) { + return getAnnotations().isPresent(annotationName); + } + + /** + * 获取注解的属性 + */ + @Nullable + default Map getAnnotationAttributes(String annotationName) { + return getAnnotationAttributes(annotationName, false); + } + // 省略其他 + +} +``` + +## AnnotationMetadata + +```java +public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata { + + /** + * 获取注解名称,全类名 + */ + default Set getAnnotationTypes() { + return getAnnotations().stream() + .filter(MergedAnnotation::isDirectlyPresent) + .map(annotation -> annotation.getType().getName()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * 注解全类名 + */ + default Set getMetaAnnotationTypes(String annotationName) { + MergedAnnotation annotation = getAnnotations().get(annotationName, MergedAnnotation::isDirectlyPresent); + if (!annotation.isPresent()) { + return Collections.emptySet(); + } + return MergedAnnotations.from(annotation.getType(), SearchStrategy.INHERITED_ANNOTATIONS).stream() + .map(mergedAnnotation -> mergedAnnotation.getType().getName()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + /** + * 是否包含某个注解 + */ + default boolean hasAnnotation(String annotationName) { + return getAnnotations().isDirectlyPresent(annotationName); + } + + /** + * 是否被某个注解标记过 + */ + default boolean hasMetaAnnotation(String metaAnnotationName) { + return getAnnotations().get(metaAnnotationName, + MergedAnnotation::isMetaPresent).isPresent(); + } + + /** + * 是否有注解,类里面有一个注解就返回true + */ + default boolean hasAnnotatedMethods(String annotationName) { + return !getAnnotatedMethods(annotationName).isEmpty(); + } + + /** + * 获取包含注解的方法 + */ + Set getAnnotatedMethods(String annotationName); + + + /** + * 通过反射创建一个注解的元信息 + */ + static AnnotationMetadata introspect(Class type) { + return StandardAnnotationMetadata.from(type); + } + +} +``` + +## MethodMetadata + +```java +public interface MethodMetadata extends AnnotatedTypeMetadata { + + /** + * 方法名称 + */ + String getMethodName(); + + /** + * 方法全路径 + */ + String getDeclaringClassName(); + + /** + * 返回值类型 + */ + String getReturnTypeName(); + + /** + * 是不是abstrac修饰 + */ + boolean isAbstract(); + + /** + * 是否 static 修饰 + */ + boolean isStatic(); + + /** + * 是否 final 修饰 + */ + boolean isFinal(); + + /** + * 是否重载 + */ + boolean isOverridable(); + +} +``` + +## MetadataReader + +```java +public interface MetadataReader { + + /** + * Return the resource reference for the class file. + * + * 获取资源 + */ + Resource getResource(); + + /** + * Read basic class metadata for the underlying class. + * 获取类的元信息 + */ + ClassMetadata getClassMetadata(); + + /** + * Read full annotation metadata for the underlying class, + * including metadata for annotated methods. + * + * 获取注解的元信息 + */ + AnnotationMetadata getAnnotationMetadata(); + +} +``` + +## MetadataReaderFactory + +- 用来创建 MetadataReader + +```java +public interface MetadataReaderFactory { + + /** + * Obtain a MetadataReader for the given class name. + * @param className the class name (to be resolved to a ".class" file) + * @return a holder for the ClassReader instance (never {@code null}) + * @throws IOException in case of I/O failure + */ + MetadataReader getMetadataReader(String className) throws IOException; + + /** + * Obtain a MetadataReader for the given resource. + * @param resource the resource (pointing to a ".class" file) + * @return a holder for the ClassReader instance (never {@code null}) + * @throws IOException in case of I/O failure + */ + MetadataReader getMetadataReader(Resource resource) throws IOException; + +} +``` + +- 接口解释的差不多了.接下来看一些实现 + +## StandardClassMetadata + +- 通过 JAVA 反射来获取类的元信息 + +- 这个类采用的方式是 Java class 的方法,通过构造方法来填写一个 Class 对象. 之后使用这个对象的方法 + +```java +public class StandardClassMetadata implements ClassMetadata { + + private final Class introspectedClass; + + @Deprecated + public StandardClassMetadata(Class introspectedClass) { + Assert.notNull(introspectedClass, "Class must not be null"); + this.introspectedClass = introspectedClass; + } + + /** + * Return the underlying Class. + */ + public final Class getIntrospectedClass() { + return this.introspectedClass; + } + + + @Override + public String getClassName() { + return this.introspectedClass.getName(); + } + + @Override + public boolean isInterface() { + return this.introspectedClass.isInterface(); + } + + @Override + public boolean isAnnotation() { + return this.introspectedClass.isAnnotation(); + } + + @Override + public boolean isAbstract() { + return Modifier.isAbstract(this.introspectedClass.getModifiers()); + } + + @Override + public boolean isFinal() { + return Modifier.isFinal(this.introspectedClass.getModifiers()); + } + + @Override + public boolean isIndependent() { + return (!hasEnclosingClass() || + (this.introspectedClass.getDeclaringClass() != null && + Modifier.isStatic(this.introspectedClass.getModifiers()))); + } + + @Override + @Nullable + public String getEnclosingClassName() { + Class enclosingClass = this.introspectedClass.getEnclosingClass(); + return (enclosingClass != null ? enclosingClass.getName() : null); + } + + @Override + @Nullable + public String getSuperClassName() { + Class superClass = this.introspectedClass.getSuperclass(); + return (superClass != null ? superClass.getName() : null); + } + + @Override + public String[] getInterfaceNames() { + Class[] ifcs = this.introspectedClass.getInterfaces(); + String[] ifcNames = new String[ifcs.length]; + for (int i = 0; i < ifcs.length; i++) { + ifcNames[i] = ifcs[i].getName(); + } + return ifcNames; + } + + @Override + public String[] getMemberClassNames() { + LinkedHashSet memberClassNames = new LinkedHashSet<>(4); + for (Class nestedClass : this.introspectedClass.getDeclaredClasses()) { + memberClassNames.add(nestedClass.getName()); + } + return StringUtils.toStringArray(memberClassNames); + } + +} +``` + +## StandardMethodMetadata + +- 通过 java 反射获取方法的元信息 +- 构造方法传递一个 method + - 如果这个方法有注解,会进行注解的解析 + +```java +public class StandardMethodMetadata implements MethodMetadata { + + private final Method introspectedMethod; + + private final boolean nestedAnnotationsAsMap; + + private final MergedAnnotations mergedAnnotations; + + @Deprecated + public StandardMethodMetadata(Method introspectedMethod) { + this(introspectedMethod, false); + } + + + @Deprecated + public StandardMethodMetadata(Method introspectedMethod, boolean nestedAnnotationsAsMap) { + Assert.notNull(introspectedMethod, "Method must not be null"); + this.introspectedMethod = introspectedMethod; + this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; + this.mergedAnnotations = MergedAnnotations.from( + introspectedMethod, SearchStrategy.DIRECT, RepeatableContainers.none()); + } + + + @Override + public MergedAnnotations getAnnotations() { + return this.mergedAnnotations; + } + + /** + * Return the underlying Method. + */ + public final Method getIntrospectedMethod() { + return this.introspectedMethod; + } + + @Override + public String getMethodName() { + return this.introspectedMethod.getName(); + } + + @Override + public String getDeclaringClassName() { + return this.introspectedMethod.getDeclaringClass().getName(); + } + + @Override + public String getReturnTypeName() { + return this.introspectedMethod.getReturnType().getName(); + } + + @Override + public boolean isAbstract() { + return Modifier.isAbstract(this.introspectedMethod.getModifiers()); + } + + @Override + public boolean isStatic() { + return Modifier.isStatic(this.introspectedMethod.getModifiers()); + } + + @Override + public boolean isFinal() { + return Modifier.isFinal(this.introspectedMethod.getModifiers()); + } + + @Override + public boolean isOverridable() { + return !isStatic() && !isFinal() && !isPrivate(); + } + + private boolean isPrivate() { + return Modifier.isPrivate(this.introspectedMethod.getModifiers()); + } + + @Override + @Nullable + public Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { + if (this.nestedAnnotationsAsMap) { + return MethodMetadata.super.getAnnotationAttributes(annotationName, classValuesAsString); + } + return AnnotatedElementUtils.getMergedAnnotationAttributes(this.introspectedMethod, + annotationName, classValuesAsString, false); + } + + /** + * 获取所有注解的信息 + */ + @Override + @Nullable + public MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) { + if (this.nestedAnnotationsAsMap) { + return MethodMetadata.super.getAllAnnotationAttributes(annotationName, classValuesAsString); + } + return AnnotatedElementUtils.getAllAnnotationAttributes(this.introspectedMethod, + annotationName, classValuesAsString, false); + } + +} +``` + +## StandardAnnotationMetadata + +- StandardAnnotationMetadata 是 StandardClassMetadata 的子类 + +- 还是一个基于 JAVA 反射做的一个类 + +- 获取注解属性 map + +```java +@Override +@Nullable +public Map getAnnotationAttributes(String annotationName, boolean classValuesAsString) { + if (this.nestedAnnotationsAsMap) { + return AnnotationMetadata.super.getAnnotationAttributes(annotationName, classValuesAsString); + } + return AnnotatedElementUtils.getMergedAnnotationAttributes( + getIntrospectedClass(), annotationName, classValuesAsString, false); +} +``` + +- `org.springframework.core.annotation.AnnotatedElementUtils#getMergedAnnotationAttributes(java.lang.reflect.AnnotatedElement, java.lang.String, boolean, boolean)` + - `org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes` + - `org.springframework.core.annotation.MergedAnnotation#asAnnotationAttributes` + +```java +@Nullable +public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, + String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + MergedAnnotation mergedAnnotation = getAnnotations(element) + .get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared()); + return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap); +} +``` + +- 查看这个方法的源码借助测试类`org.springframework.core.annotation.AnnotatedElementUtilsTests#getMergedAnnotationAttributesOnClassWithLocalAnnotation` +- getAnnotations() 方法 + +### getAnnotations + +- `org.springframework.core.annotation.MergedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers)` + + - `org.springframework.core.annotation.TypeMappedAnnotations#from(java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.RepeatableContainers, org.springframework.core.annotation.AnnotationFilter)` + +- 最终我们找到的代码如下 + +```java +static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy, + RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) { + + if (AnnotationsScanner.isKnownEmpty(element, searchStrategy)) { + return NONE; + } + return new TypeMappedAnnotations(element, searchStrategy, repeatableContainers, annotationFilter); +} +``` + +- 判断是否为空. 为空返回 None,不会为空构造出一个对象 org.springframework.core.annotation.TypeMappedAnnotations + +### MergedAnnotations + +```java +public interface MergedAnnotations extends Iterable> { + + //确定注解是否存在 + boolean isPresent(Class annotationType); + //注解是否直接存在 + boolean isDirectlyPresent(Class annotationType); + // 获取匹配的注解 + MergedAnnotation get(Class annotationType); + // 省略其他 + +} +``` + +- 这个接口中还有两个方法 + + 1. `of` + 将多个`MergedAnnotations`合并 + + 2. `from` + + 将多个注解合并 + +### SearchStrategy + +```java +enum SearchStrategy { + + /** + * Find only directly declared annotations, without considering + * {@link Inherited @Inherited} annotations and without searching + * superclasses or implemented interfaces. + * + * 直接查找 + */ + DIRECT, + + /** + * Find all directly declared annotations as well as any + * {@link Inherited @Inherited} superclass annotations. This strategy + * is only really useful when used with {@link Class} types since the + * {@link Inherited @Inherited} annotation is ignored for all other + * {@linkplain AnnotatedElement annotated elements}. This strategy does + * not search implemented interfaces. + * + * 继承查找 + */ + INHERITED_ANNOTATIONS, + + /** + * Find all directly declared and superclass annotations. This strategy + * is similar to {@link #INHERITED_ANNOTATIONS} except the annotations + * do not need to be meta-annotated with {@link Inherited @Inherited}. + * This strategy does not search implemented interfaces. + * 查找当前类和父类的注解 + */ + SUPERCLASS, + + /** + * Perform a full search of the entire type hierarchy, including + * superclasses and implemented interfaces. Superclass annotations do + * not need to be meta-annotated with {@link Inherited @Inherited}. + */ + TYPE_HIERARCHY, + + /** + * Perform a full search of the entire type hierarchy on the source + * and any enclosing classes. This strategy is similar to + * {@link #TYPE_HIERARCHY} except that {@linkplain Class#getEnclosingClass() + * enclosing classes} are also searched. Superclass annotations do not + * need to be meta-annotated with {@link Inherited @Inherited}. When + * searching a {@link Method} source, this strategy is identical to + * {@link #TYPE_HIERARCHY}. + */ + TYPE_HIERARCHY_AND_ENCLOSING_CLASSES +} +``` + +- `org.springframework.core.annotation.TypeMappedAnnotations#get(java.lang.String, java.util.function.Predicate>, org.springframework.core.annotation.MergedAnnotationSelector)` + +```java +@Override +public MergedAnnotation get(String annotationType, + @Nullable Predicate> predicate, + @Nullable MergedAnnotationSelector selector) { + // 匹配校验 + if (this.annotationFilter.matches(annotationType)) { + return MergedAnnotation.missing(); + } + MergedAnnotation result = scan(annotationType, + new MergedAnnotationFinder<>(annotationType, predicate, selector)); + return (result != null ? result : MergedAnnotation.missing()); +} +``` + +#### Scan + +`org.springframework.core.annotation.AnnotationsScanner#scan(C, java.lang.reflect.AnnotatedElement, org.springframework.core.annotation.MergedAnnotations.SearchStrategy, org.springframework.core.annotation.AnnotationsProcessor, java.util.function.BiPredicate>)` + +```java +@Nullable +static R scan(C context, AnnotatedElement source, SearchStrategy searchStrategy, + AnnotationsProcessor processor, @Nullable BiPredicate> classFilter) { + + R result = process(context, source, searchStrategy, processor, classFilter); + return processor.finish(result); +} +``` + +在这个里面重点关注`PROCESS`方法 + +```java +@Nullable +private static R process(C context, AnnotatedElement source, + SearchStrategy searchStrategy, AnnotationsProcessor processor, + @Nullable BiPredicate> classFilter) { + + if (source instanceof Class) { + return processClass(context, (Class) source, searchStrategy, processor, classFilter); + } + if (source instanceof Method) { + return processMethod(context, (Method) source, searchStrategy, processor, classFilter); + } + return processElement(context, source, processor, classFilter); +} +``` + +测试类 + +```java + @Transactional("TxConfig") + static class TxConfig { + } +``` + +显然这是一个类他会走`processClass`方法 + +- 根据扫描方式进行扫描 + +```java +@Nullable +private static R processClass(C context, Class source, + SearchStrategy searchStrategy, AnnotationsProcessor processor, + @Nullable BiPredicate> classFilter) { + + switch (searchStrategy) { + case DIRECT: + return processElement(context, source, processor, classFilter); + case INHERITED_ANNOTATIONS: + return processClassInheritedAnnotations(context, source, searchStrategy, processor, classFilter); + case SUPERCLASS: + return processClassHierarchy(context, source, processor, classFilter, false, false); + case TYPE_HIERARCHY: + return processClassHierarchy(context, source, processor, classFilter, true, false); + case TYPE_HIERARCHY_AND_ENCLOSING_CLASSES: + return processClassHierarchy(context, source, processor, classFilter, true, true); + } + throw new IllegalStateException("Unsupported search strategy " + searchStrategy); +} +``` + +- 扫描的形式就不贴出完整代码了 + +`finish`就包装一下返回. + +- 此时`org.springframework.core.annotation.AnnotatedElementUtils#getMergedAnnotationAttributes(java.lang.reflect.AnnotatedElement, java.lang.String, boolean, boolean)`这个方法走到了最后一步`org.springframework.core.annotation.AnnotatedElementUtils#getAnnotationAttributes` + +- 最后的组装 map 方法 + + `org.springframework.core.annotation.TypeMappedAnnotation#asMap(java.util.function.Function,T>, org.springframework.core.annotation.MergedAnnotation.Adapt...)` + +```java +@Override +public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) { + return asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()), adaptations); +} +``` + +```java +@Override +public > T asMap(Function, T> factory, Adapt... adaptations) { + T map = factory.apply(this); + Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null"); + AttributeMethods attributes = this.mapping.getAttributes(); + for (int i = 0; i < attributes.size(); i++) { + Method attribute = attributes.get(i); + Object value = (isFiltered(attribute.getName()) ? null : + getValue(i, getTypeForMapOptions(attribute, adaptations))); + if (value != null) { + map.put(attribute.getName(), + adaptValueForMapOptions(attribute, value, map.getClass(), factory, adaptations)); + } + } + return map; +} +``` + +- 获取属性列表,循环, 放入 map 返回. + + map + + ​ key: 注解的函数 + + ​ value: 函数对应的值 + +```java +@Transactional("TxConfig") +``` + +```java +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Inherited +@interface Transactional { + + String value() default ""; + + String qualifier() default "transactionManager"; + + boolean readOnly() default false; +} +``` + +如果是上面这样的结构那么返回值为 + +```json +value:TxConfig +qulifiter:transactionManager +readOnlay:false +``` + +![image-20200824104529315](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200824104529315.png) + +## SimpleMetadataReader + +- 构造方法传递三个参数直接使用 + + ```java + final class SimpleMetadataReader implements MetadataReader { + + private static final int PARSING_OPTIONS = ClassReader.SKIP_DEBUG + | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES; + + private final Resource resource; + + private final AnnotationMetadata annotationMetadata; + + + SimpleMetadataReader(Resource resource, @Nullable ClassLoader classLoader) throws IOException { + SimpleAnnotationMetadataReadingVisitor visitor = new SimpleAnnotationMetadataReadingVisitor(classLoader); + getClassReader(resource).accept(visitor, PARSING_OPTIONS); + this.resource = resource; + this.annotationMetadata = visitor.getMetadata(); + } + + private static ClassReader getClassReader(Resource resource) throws IOException { + try (InputStream is = new BufferedInputStream(resource.getInputStream())) { + try { + return new ClassReader(is); + } + catch (IllegalArgumentException ex) { + throw new NestedIOException("ASM ClassReader failed to parse class file - " + + "probably due to a new Java class file version that isn't supported yet: " + resource, ex); + } + } + } + + + @Override + public Resource getResource() { + return this.resource; + } + + @Override + public ClassMetadata getClassMetadata() { + return this.annotationMetadata; + } + + @Override + public AnnotationMetadata getAnnotationMetadata() { + return this.annotationMetadata; + } + + } + ``` + +## SimpleMetadataReaderFactory + +- 关注点为如何获取`MetadataReader` + 1. 通过资源直接 new 出来 + 2. 通过 className 转换成资源地址, + 3. 将资源地址转换成`Resource`对象 + 4. new 出来 + +```java +@Override +public MetadataReader getMetadataReader(String className) throws IOException { + try { + String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX; + Resource resource = this.resourceLoader.getResource(resourcePath); + return getMetadataReader(resource); + } + catch (FileNotFoundException ex) { + // Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here... + // ClassUtils.forName has an equivalent check for resolution into Class references later on. + int lastDotIndex = className.lastIndexOf('.'); + if (lastDotIndex != -1) { + String innerClassName = + className.substring(0, lastDotIndex) + '$' + className.substring(lastDotIndex + 1); + String innerClassResourcePath = ResourceLoader.CLASSPATH_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX; + Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath); + if (innerClassResource.exists()) { + return getMetadataReader(innerClassResource); + } + } + throw ex; + } +} + +@Override +public MetadataReader getMetadataReader(Resource resource) throws IOException { + return new SimpleMetadataReader(resource, this.resourceLoader.getClassLoader()); +} +``` diff --git a/docs/Spring/clazz/Spring-MethodOverride.md b/docs/Spring/clazz/Spring-MethodOverride.md new file mode 100644 index 00000000..c8f3a854 --- /dev/null +++ b/docs/Spring/clazz/Spring-MethodOverride.md @@ -0,0 +1,242 @@ +# 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.MethodOverrides` + +## MethodOverride + +- MethodOverride 方法重载类 + +在`MethodOverride`定义了下面三个属性 + +1. 方法名称 +2. 是否重载 +3. 源 + +```java +public abstract class MethodOverride implements BeanMetadataElement { + + /** + * 方法名称 + */ + private final String methodName; + + /** + * 是否重载 + */ + private boolean overloaded = true; + + /** + * 源 + */ + @Nullable + private Object source; +} +``` + +- 定义了一个抽象方法, 交由子类实现 + +```java +public abstract boolean matches(Method method); +``` + +类图 + +![MethodOverride](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/MethodOverride.png) + +- 在 Spring 中有两种可以重写的机制(XML) + + 1. `lookup-method` 标签 + + ```xml + + ``` + + 2. `replaced-method` 标签 + + ```xml + + ``` + +相对应的两个类如类图所示 + +## LookupOverride + +- `org.springframework.beans.factory.support.LookupOverride` +- lookup-method 标签对应的实体对象 + +属性列表 + +1. beanName +2. method + +```java +@Nullable +private final String beanName; + +@Nullable +private Method method; +``` + +### matches + +比较方法 + +1. method 是否直接相等 +1. method 名称是否相同 +1. 是否需要重载 +1. 是不是 ABSTRACT 方法 +1. 参数列表长度是否等于 0 + +```java + @Override + public boolean matches(Method method) { + if (this.method != null) { + // 通过 equals 判断 + return method.equals(this.method); + } + else { + // 1. method 名称是否相同 + // 2. 是否需要重载 + // 3. 是不是 ABSTRACT 方法 + // 4. 参数列表长度是否等于0 + return (method.getName().equals(getMethodName()) && (!isOverloaded() || + Modifier.isAbstract(method.getModifiers()) || method.getParameterCount() == 0)); + } + } + +``` + +## ReplaceOverride + +- `org.springframework.beans.factory.support.ReplaceOverride` + +```java +/** + * 实现 MethodReplacer 接口的bean name + * @see MethodReplacer + */ +private final String methodReplacerBeanName; + +/** + * 标签 arg-type 数据 + */ +private final List typeIdentifiers = new LinkedList<>(); +``` + +- 一个例子 + +```XML + + + + + String + + + + + + + + + +``` + +methodReplacerBeanName 对应`org.springframework.beans.factory.support.MethodReplacer` 的实现类 + +typeIdentifiers 对应标签 arg-type 的属性值 + +构造方法 + +```java +public ReplaceOverride(String methodName, String methodReplacerBeanName) { + super(methodName); + Assert.notNull(methodName, "Method replacer bean name must not be null"); + this.methodReplacerBeanName = methodReplacerBeanName; +} +``` + +methodName 通过父类进行设置 + +### matches + +```java +@Override +public boolean matches(Method method) { + // 方法名称是否相同 + if (!method.getName().equals(getMethodName())) { + return false; + } + // 是否重载 + if (!isOverloaded()) { + // Not overloaded: don't worry about arg type matching... + return true; + } + // If we get here, we need to insist on precise argument matching... + // 类型标识数量是否和参数列表是否不相同 + if (this.typeIdentifiers.size() != method.getParameterCount()) { + return false; + } + // 获取参数类型列表 + Class[] parameterTypes = method.getParameterTypes(); + for (int i = 0; i < this.typeIdentifiers.size(); i++) { + String identifier = this.typeIdentifiers.get(i); + // 判断 方法参数的类型是否在类型标识列表中 + if (!parameterTypes[i].getName().contains(identifier)) { + return false; + } + } + return true; +} +``` + +## MethodOverrides + +- `org.springframework.beans.factory.support.MethodOverrides` + +- 重载方法对象 + +- 存储所有重载的方法列表(set 结构) + +```java + private final Set 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); + } + } + ``` + +1. 获取 MethodOverride + + ```java + @Nullable + public MethodOverride getOverride(Method method) { + MethodOverride match = null; + for (MethodOverride candidate : this.overrides) { + if (candidate.matches(method)) { + match = candidate; + } + } + return match; + } + ``` diff --git a/docs/Spring/clazz/Spring-MultiValueMap.md b/docs/Spring/clazz/Spring-MultiValueMap.md new file mode 100644 index 00000000..b7d76435 --- /dev/null +++ b/docs/Spring/clazz/Spring-MultiValueMap.md @@ -0,0 +1,132 @@ +# Spring MultiValueMap + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +- 类路径: `org.springframework.util.MultiValueMap` + +```java +public interface MultiValueMap extends Map> { + + /** + * 获取value的第一 + */ + @Nullable + V getFirst(K key); + + /** + * 添加元素 + */ + void add(K key, @Nullable V value); + + /** + * 添加所有元素 + */ + void addAll(K key, List values); + + /** + * 添加要给 {@link MultiValueMap} 对象 + */ + void addAll(MultiValueMap values); + + + default void addIfAbsent(K key, @Nullable V value) { + if (!containsKey(key)) { + add(key, value); + } + } + + /** + * 设置数据 + */ + void set(K key, @Nullable V value); + + /** + * 设置一个map数据 + */ + void setAll(Map values); + + /** + * 转换成 map 结构 + */ + Map toSingleValueMap(); + +} +``` + +- 但从接口定义上可以明确 value 是一个 list 结构 + +类图 + +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/MultiValueMap.png) + +## LinkedMultiValueMap + +```java +public class LinkedMultiValueMap implements MultiValueMap, Serializable, Cloneable { + + @Override + @Nullable + public V getFirst(K key) { + // 获取list + List values = this.targetMap.get(key); + // 获取 list 的第一个 + return (values != null && !values.isEmpty() ? values.get(0) : null); + } + + @Override + public void add(K key, @Nullable V value) { + // 从当前内存中获取key对应的list. + List values = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + // 将value 插入到values中 + values.add(value); + } + + @Override + public void addAll(K key, List values) { + // 从当前内存中获取key对应的list. + List currentValues = this.targetMap.computeIfAbsent(key, k -> new LinkedList<>()); + // 将value 插入到values中 + currentValues.addAll(values); + } + + @Override + public void addAll(MultiValueMap values) { + for (Entry> entry : values.entrySet()) { + addAll(entry.getKey(), entry.getValue()); + } + } + + @Override + public void set(K key, @Nullable V value) { + // 构造list + List values = new LinkedList<>(); + // 添加 + values.add(value); + // 添加 + this.targetMap.put(key, values); + } + + @Override + public void setAll(Map values) { + // 循环执行 set 方法 + values.forEach(this::set); + } + + @Override + public Map toSingleValueMap() { + // 返回结果定义 + LinkedHashMap singleValueMap = new LinkedHashMap<>(this.targetMap.size()); + // 循环 + this.targetMap.forEach((key, values) -> { + if (values != null && !values.isEmpty()) { + // value 获取原来list中的第一个元素 + singleValueMap.put(key, values.get(0)); + } + }); + return singleValueMap; + } +} +``` + +- 其他实现类也基本和这个类相同, 不做具体展开 diff --git a/docs/Spring/clazz/Spring-OrderComparator.md b/docs/Spring/clazz/Spring-OrderComparator.md index b4273736..81855606 100644 --- a/docs/Spring/clazz/Spring-OrderComparator.md +++ b/docs/Spring/clazz/Spring-OrderComparator.md @@ -1,8 +1,8 @@ # Spring OrderComparator + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - ```java private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) { boolean p1 = (o1 instanceof PriorityOrdered); @@ -50,6 +50,7 @@ ``` - 测试用例 + ```java @Test public void compareWithSourceProviderArray() { @@ -60,9 +61,7 @@ ``` -![image-20200116141838601](../../../images/spring/image-20200116141838601.png) - - +![image-20200116141838601](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116141838601.png) ```java @Nullable @@ -91,8 +90,6 @@ ``` -![image-20200116141932486](../../../images/spring/image-20200116141932486.png) - - +![image-20200116141932486](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116141932486.png) -最终`Integer.compare(i1, i2)`比较返回 OK ! \ No newline at end of file +最终`Integer.compare(i1, i2)`比较返回 OK ! diff --git a/docs/Spring/clazz/Spring-OrderUtils.md b/docs/Spring/clazz/Spring-OrderUtils.md index c171d153..c9a6e3d0 100644 --- a/docs/Spring/clazz/Spring-OrderUtils.md +++ b/docs/Spring/clazz/Spring-OrderUtils.md @@ -1,10 +1,10 @@ # Spring OrderUtils + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) - `org.springframework.core.annotation.OrderUtils`主要方法如下 - 1. getOrder - 1. getPriority - + 1. getOrder + 1. getPriority - 测试类`org.springframework.core.annotation.OrderUtilsTests` ```java @@ -33,8 +33,7 @@ } ``` - - + ```java @Nullable public static Integer getPriority(Class type) { @@ -59,4 +58,4 @@ return result; } -``` \ No newline at end of file +``` diff --git a/docs/Spring/clazz/Spring-Property.md b/docs/Spring/clazz/Spring-Property.md new file mode 100644 index 00000000..67896f23 --- /dev/null +++ b/docs/Spring/clazz/Spring-Property.md @@ -0,0 +1,312 @@ +# Spring Property + +- 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` + +- 类图如下 + + ![images](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyValues.png) + +- 在 Spring IoC 中,**非 Web 工程**,使用 xml 或者注解进行配置主要使用到的是 `PropertyValues` ,`PropertyValue` ,`MutablePropertyValues` 三个 + + 其中 `PropertyValues` 是继承迭代器,具体实现在`MutablePropertyValues` 他们处理的对象是`PropertyValues` + + 关系就是这样. + +- 开始类的解析了 + +## PropertyValue + +- `org.springframework.beans.PropertyValue` + +- 类图 + + ![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyValue.png) + +- 这个类暂时只关注两个属性 + + 1. name: 属性名称 + 2. value: 属性值 + + 对应标签`` + + 属性值一一对应填入. + +## MutablePropertyValues + +- `org.springframework.beans.MutablePropertyValues` + +- 属性 + 1. `propertyValueList`:属性列表, key:参数名称,value:具体数据 + 2. `processedProperties`: 已经处理的属性名称 + 3. `converted`: 是否转换 + +```java +public class MutablePropertyValues implements PropertyValues, Serializable { + /** + * 属性列表, key:参数名称,value:具体数据 + */ + private final List propertyValueList; + + /** + * 已经处理的属性名称 + */ + @Nullable + private Set processedProperties; + + /** + * 是否转换 + */ + private volatile boolean converted = false; +} +``` + +### 构造器 + +- `MutablePropertyValues` 的一个构造器. 其他构造器的方式原理实现差不多. 核心是将构造参数转换成`PropertyValue`对象在放入`propertyValueList`中 + +```java +public MutablePropertyValues(@Nullable PropertyValues original) { + // We can optimize this because it's all new: + // There is no replacement of existing property values. + if (original != null) { + // 从列表中获取所有可能指 + PropertyValue[] pvs = original.getPropertyValues(); + this.propertyValueList = new ArrayList<>(pvs.length); + for (PropertyValue pv : pvs) { + // 循环插入 property values + this.propertyValueList.add(new PropertyValue(pv)); + } + } + else { + this.propertyValueList = new ArrayList<>(0); + } +} +``` + +### PropertyValue 的构造方法 + +```java + public PropertyValue(PropertyValue original) { + Assert.notNull(original, "Original must not be null"); + this.name = original.getName(); + this.value = original.getValue(); + this.optional = original.isOptional(); + this.converted = original.converted; + this.convertedValue = original.convertedValue; + this.conversionNecessary = original.conversionNecessary; + this.resolvedTokens = original.resolvedTokens; + setSource(original.getSource()); + copyAttributesFrom(original); + } + +``` + +- 除了最后一行是一个复杂调用. 前面几行代码都是属性赋值操作. + - 最后一行代码会调用`AttributeAccessor`接口上的方法. + +## AttributeAccessor + +- `org.springframework.core.AttributeAccessor` + +- 完整的方法列表及作用注释 + +```java +public interface AttributeAccessor { + + /** + * 设置属性值 + * @param name 属性值名称 + * @param value 属性值 + */ + void setAttribute(String name, @Nullable Object value); + + /** + * 通过属性名称获取属性值 + * + * @param name 属性值名称 + * @return 属性值 + */ + @Nullable + Object getAttribute(String name); + + /** + * 移除指定属性名称的值,返回移除的属性值 + * + * @param name 属性值名称 + * @return 移除的属性值 + */ + @Nullable + Object removeAttribute(String name); + + /** + * 是否包含属性名称 + * @param 属性名称 + */ + boolean hasAttribute(String name); + + /** + * 属性名称列表 + */ + String[] attributeNames(); + +} +``` + +- 回到`org.springframework.core.AttributeAccessorSupport#copyAttributesFrom`方法 + +```java +protected void copyAttributesFrom(AttributeAccessor source) { + Assert.notNull(source, "Source must not be null"); + // 获取属性名称列表 + String[] attributeNames = source.attributeNames(); + // 循环属性名称列表 + for (String attributeName : attributeNames) { + // 设置属性 + // name: 属性名称,value: 从入参中获取属性名称对应的属性值 + setAttribute(attributeName, source.getAttribute(attributeName)); + } +} +``` + +### setAttribute + +- 一个 map 操作 + +```java +@Override +public void setAttribute(String name, @Nullable Object value) { + Assert.notNull(name, "Name must not be null"); + if (value != null) { + this.attributes.put(name, value); + } + else { + removeAttribute(name); + } +} +``` + +## addPropertyValue + +- `org.springframework.beans.MutablePropertyValues#addPropertyValue(org.springframework.beans.PropertyValue)` + +```java + public MutablePropertyValues addPropertyValue(PropertyValue pv) { + // 循环获取 属性对象 + for (int i = 0; i < this.propertyValueList.size(); i++) { + // 正在处理的 属性对象 + PropertyValue currentPv = this.propertyValueList.get(i); + // 正在处理的属性对象名称和添加的属性对象名称比较 + // 如果相同会做一个合并操作 + if (currentPv.getName().equals(pv.getName())) { + // 合并属性 + pv = mergeIfRequired(pv, currentPv); + // 重新设置 + setPropertyValueAt(pv, i); + return this; + } + } + // 放入 list 集合 + this.propertyValueList.add(pv); + return this; + } + +``` + +## mergeIfRequired + +- `org.springframework.beans.MutablePropertyValues#mergeIfRequired` + +- 这段代码会取舍新老数据. + 1. 如果是`Mergeable`类型会做合并操作 + 2. 直接返回新数据 + +```java + private PropertyValue mergeIfRequired(PropertyValue newPv, PropertyValue currentPv) { + Object value = newPv.getValue(); + if (value instanceof Mergeable) { + Mergeable mergeable = (Mergeable) value; + if (mergeable.isMergeEnabled()) { + // 获取合并的结果,放入对象 + Object merged = mergeable.merge(currentPv.getValue()); + // 创建新的 属性对象 + return new PropertyValue(newPv.getName(), merged); + } + } + return newPv; + } + +``` + +- 配合测试代码,跟容易看懂. + + ```java + @Test + public void testAddOrOverride() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.addPropertyValue(new PropertyValue("forname", "Tony")); + pvs.addPropertyValue(new PropertyValue("surname", "Blair")); + pvs.addPropertyValue(new PropertyValue("age", "50")); + doTestTony(pvs); + PropertyValue addedPv = new PropertyValue("rod", "Rod"); + pvs.addPropertyValue(addedPv); + assertThat(pvs.getPropertyValue("rod").equals(addedPv)).isTrue(); + PropertyValue changedPv = new PropertyValue("forname", "Greg"); + pvs.addPropertyValue(changedPv); + assertThat(pvs.getPropertyValue("forname").equals(changedPv)).isTrue(); + } + ``` + +## Mergeable + +新的接口`Mergeable` + +- `org.springframework.beans.Mergeable` + +```java +public interface Mergeable { + + /** + * 是否需要合并 + */ + boolean isMergeEnabled(); + + /** + * 合并方法 + */ + Object merge(@Nullable Object parent); + +} +``` + +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/Mergeable.png) + +- 看一下 List 怎么实现`merge` + +```java +@Override +@SuppressWarnings("unchecked") +public List merge(@Nullable Object parent) { + if (!this.mergeEnabled) { + throw new IllegalStateException("Not allowed to merge when the 'mergeEnabled' property is set to 'false'"); + } + if (parent == null) { + return this; + } + if (!(parent instanceof List)) { + throw new IllegalArgumentException("Cannot merge with object of type [" + parent.getClass() + "]"); + } + List merged = new ManagedList<>(); + merged.addAll((List) parent); + merged.addAll(this); + return merged; +} +``` + +- 在 list 视线中就是讲两个结果合并. 事实上其他的几个都是这个操作. 这里就不贴所有的代码了 diff --git a/docs/Spring/clazz/Spring-PropertyPlaceholderHelper.md b/docs/Spring/clazz/Spring-PropertyPlaceholderHelper.md new file mode 100644 index 00000000..faa769a5 --- /dev/null +++ b/docs/Spring/clazz/Spring-PropertyPlaceholderHelper.md @@ -0,0 +1,131 @@ +# Spring PropertyPlaceholderHelper + +- 类全路径: `org.springframework.util.PropertyPlaceholderHelper` + +## parseStringValue + +- `org.springframework.util.PropertyPlaceholderHelper#parseStringValue` 这个方法是主要方法 + +```java +protected String parseStringValue( + String value, PlaceholderResolver placeholderResolver, @Nullable Set visitedPlaceholders) { + + // 占位符所在位置 + int startIndex = value.indexOf(this.placeholderPrefix); + if (startIndex == -1) { + return value; + } + + // 返回值 + StringBuilder result = new StringBuilder(value); + while (startIndex != -1) { + // 寻找结尾占位符 + int endIndex = findPlaceholderEndIndex(result, startIndex); + if (endIndex != -1) { + // 返回值切分留下中间内容 + String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); + String originalPlaceholder = placeholder; + if (visitedPlaceholders == null) { + visitedPlaceholders = new HashSet<>(4); + } + if (!visitedPlaceholders.add(originalPlaceholder)) { + throw new IllegalArgumentException( + "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); + } + // Recursive invocation, parsing placeholders contained in the placeholder key. + // 递归获取占位符内容 + placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); + // Now obtain the value for the fully resolved key... + // 解析占位符内容获得真正的属性值 + String propVal = placeholderResolver.resolvePlaceholder(placeholder); + if (propVal == null && this.valueSeparator != null) { + int separatorIndex = placeholder.indexOf(this.valueSeparator); + if (separatorIndex != -1) { + String actualPlaceholder = placeholder.substring(0, separatorIndex); + String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length()); + propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder); + if (propVal == null) { + propVal = defaultValue; + } + } + } + if (propVal != null) { + // Recursive invocation, parsing placeholders contained in the + // previously resolved placeholder value. + propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders); + result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal); + if (logger.isTraceEnabled()) { + logger.trace("Resolved placeholder '" + placeholder + "'"); + } + startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length()); + } + else if (this.ignoreUnresolvablePlaceholders) { + // Proceed with unprocessed value. + startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length()); + } + else { + throw new IllegalArgumentException("Could not resolve placeholder '" + + placeholder + "'" + " in value \"" + value + "\""); + } + visitedPlaceholders.remove(originalPlaceholder); + } + else { + startIndex = -1; + } + } + return result.toString(); +} +``` + +在这里还需要关注一个接口 + +- 占位符解析. + +```java +@FunctionalInterface +public interface PlaceholderResolver { + + /** + * Resolve the supplied placeholder name to the replacement value. + * @param placeholderName the name of the placeholder to resolve + * @return the replacement value, or {@code null} if no replacement is to be made + */ + @Nullable + String resolvePlaceholder(String placeholderName); +} +``` + +占位符解析请查看: [PlaceholderResolver](PlaceholderResolver) + +## findPlaceholderEndIndex + +- 寻找结尾占位符索引 + +```java +/** + * 寻找结尾占位符索引 + */ +private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + this.placeholderPrefix.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + this.placeholderSuffix.length(); + } + else { + return index; + } + } + else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { + withinNestedPlaceholder++; + index = index + this.simplePrefix.length(); + } + else { + index++; + } + } + return -1; +} +``` diff --git a/docs/Spring/clazz/Spring-PropertySources.md b/docs/Spring/clazz/Spring-PropertySources.md new file mode 100644 index 00000000..9511c20c --- /dev/null +++ b/docs/Spring/clazz/Spring-PropertySources.md @@ -0,0 +1,487 @@ +# Spring PropertySources + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +## MutablePropertySources + +- 全路径: `org.springframework.core.env.MutablePropertySources` + +- `MutablePropertySources`类内部存储了`List>`对象,主要是针对`List>` 进行的操作.换句话说就是对 list 操作的实现 + +- 类注解如下 + +```java +public class MutablePropertySources implements PropertySources { + + private final List> propertySourceList = new CopyOnWriteArrayList<>(); + + + /** + * Create a new {@link MutablePropertySources} object. + * + * 构造方法 + */ + public MutablePropertySources() { + } + + /** + * Create a new {@code MutablePropertySources} from the given propertySources + * object, preserving the original order of contained {@code PropertySource} objects. + * 构造方法, 传递一个集合, 将集合中的数据放入 {@code propertySourceList}. + */ + public MutablePropertySources(PropertySources propertySources) { + this(); + // PropertySources 是一个迭代器接口的实现,通过循环取出信息放入到 propertySourceList 中 + for (PropertySource propertySource : propertySources) { + // 放入方法 + addLast(propertySource); + } + } + + + /** + * 获取迭代器对象 + */ + @Override + public Iterator> iterator() { + return this.propertySourceList.iterator(); + } + + /** + * 获取 Spliterator 对象 + */ + @Override + public Spliterator> spliterator() { + return Spliterators.spliterator(this.propertySourceList, 0); + } + + /** + * 获取流 + */ + @Override + public Stream> stream() { + return this.propertySourceList.stream(); + } + + /** + * 判断是否存在 name + * @param name the {@linkplain PropertySource#getName() name of the property source} to find + */ + @Override + public boolean contains(String name) { + return this.propertySourceList.contains(PropertySource.named(name)); + } + + /** + * 获取 PropertySource 信息 + * @param name the {@linkplain PropertySource#getName() name of the property source} to find + * @return + */ + @Override + @Nullable + public PropertySource get(String name) { + // 获取 name 所在的索引位置 + int index = this.propertySourceList.indexOf(PropertySource.named(name)); + // get方法获取结果 + return (index != -1 ? this.propertySourceList.get(index) : null); + } + + + /** + * Add the given property source object with highest precedence. + * + * 头插数据 + */ + public void addFirst(PropertySource propertySource) { + removeIfPresent(propertySource); + this.propertySourceList.add(0, propertySource); + } + + /** + * Add the given property source object with lowest precedence. + * + * 尾插数据 + */ + public void addLast(PropertySource propertySource) { + removeIfPresent(propertySource); + this.propertySourceList.add(propertySource); + } + + /** + * Add the given property source object with precedence immediately higher + * than the named relative property source. + * + * 在relativePropertySourceName的索引位置前添加数据 + */ + public void addBefore(String relativePropertySourceName, PropertySource propertySource) { + assertLegalRelativeAddition(relativePropertySourceName, propertySource); + removeIfPresent(propertySource); + int index = assertPresentAndGetIndex(relativePropertySourceName); + addAtIndex(index, propertySource); + } + + /** + * Add the given property source object with precedence immediately lower + * than the named relative property source. + * 在relativePropertySourceName的索引位置后添加数据 + */ + public void addAfter(String relativePropertySourceName, PropertySource propertySource) { + assertLegalRelativeAddition(relativePropertySourceName, propertySource); + // 删除存在的数据 + removeIfPresent(propertySource); + // 获取所有 + int index = assertPresentAndGetIndex(relativePropertySourceName); + // 在索引+1出添加数据 + addAtIndex(index + 1, propertySource); + } + + /** + * Return the precedence of the given property source, {@code -1} if not found. + * 获取索引位置 + */ + public int precedenceOf(PropertySource propertySource) { + return this.propertySourceList.indexOf(propertySource); + } + + /** + * Remove and return the property source with the given name, {@code null} if not found. + * 删除索引位置 + * @param name the name of the property source to find and remove + */ + @Nullable + public PropertySource remove(String name) { + // 获取索引 + int index = this.propertySourceList.indexOf(PropertySource.named(name)); + // 删除索引上的数据 + return (index != -1 ? this.propertySourceList.remove(index) : null); + } + + /** + * Replace the property source with the given name with the given property source object. + * 替换 name 的信息 + * @param name the name of the property source to find and replace + * @param propertySource the replacement property source + * @throws IllegalArgumentException if no property source with the given name is present + * @see #contains + */ + public void replace(String name, PropertySource propertySource) { + // 获取索引位置 + int index = assertPresentAndGetIndex(name); + // 设置具体所应位置的值 + this.propertySourceList.set(index, propertySource); + } + + /** + * Return the number of {@link PropertySource} objects contained. + * 数量 + */ + public int size() { + return this.propertySourceList.size(); + } + + @Override + public String toString() { + return this.propertySourceList.toString(); + } + + /** + * Ensure that the given property source is not being added relative to itself. + * 确保两个 PropertySource 的 name不相同 + */ + protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource propertySource) { + // 获取 PropertySource 的名字 + String newPropertySourceName = propertySource.getName(); + // 历史名字和新的名字是否相同 + if (relativePropertySourceName.equals(newPropertySourceName)) { + throw new IllegalArgumentException( + "PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself"); + } + } + + /** + * Remove the given property source if it is present. + * 删除已存在的数据 + */ + protected void removeIfPresent(PropertySource propertySource) { + this.propertySourceList.remove(propertySource); + } + + /** + * Add the given property source at a particular index in the list. + * 指定索引位置插入数据 + */ + private void addAtIndex(int index, PropertySource propertySource) { + removeIfPresent(propertySource); + this.propertySourceList.add(index, propertySource); + } + + /** + * Assert that the named property source is present and return its index. + * 获取 name 所在的索引位置 + * @param name {@linkplain PropertySource#getName() name of the property source} to find + * @throws IllegalArgumentException if the named property source is not present + */ + private int assertPresentAndGetIndex(String name) { + int index = this.propertySourceList.indexOf(PropertySource.named(name)); + if (index == -1) { + throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist"); + } + return index; + } + +} +``` + +## PropertySources + +- 类路径: `org.springframework.core.env.PropertySources` + +- 详细说明如下 + +```java +public interface PropertySources extends Iterable> { + + /** + * Return a sequential {@link Stream} containing the property sources. + * 获取流 + * @since 5.1 + */ + default Stream> stream() { + return StreamSupport.stream(spliterator(), false); + } + + /** + * Return whether a property source with the given name is contained. + * 判断是否存在 name + * @param name the {@linkplain PropertySource#getName() name of the property source} to find + */ + boolean contains(String name); + + /** + * Return the property source with the given name, {@code null} if not found. + * 获取 PropertySource + * @param name the {@linkplain PropertySource#getName() name of the property source} to find + */ + @Nullable + PropertySource get(String name); + +} +``` + +## PropertySource + +- 类路径: `org.springframework.core.env.PropertySource` + +- 存有两个子类 + 1. StubPropertySource + 2. ComparisonPropertySource 3. 调用`getSource`、`containsProperty`、`getProperty` 都会直接异常 + +```java +public abstract class PropertySource { + + protected final Log logger = LogFactory.getLog(getClass()); + + /** + * 属性名称 + */ + protected final String name; + + /** + * 值 + */ + protected final T source; + + + /** + * Create a new {@code PropertySource} with the given name and source object. + */ + public PropertySource(String name, T source) { + Assert.hasText(name, "Property source name must contain at least one character"); + Assert.notNull(source, "Property source must not be null"); + this.name = name; + this.source = source; + } + + /** + * Create a new {@code PropertySource} with the given name and with a new + * {@code Object} instance as the underlying source. + *

Often useful in testing scenarios when creating anonymous implementations + * that never query an actual source but rather return hard-coded values. + */ + @SuppressWarnings("unchecked") + public PropertySource(String name) { + this(name, (T) new Object()); + } + + /** + * Return a {@code PropertySource} implementation intended for collection comparison purposes only. + *

Primarily for internal use, but given a collection of {@code PropertySource} objects, may be + * used as follows: + *

+	 * {@code List> sources = new ArrayList>();
+	 * sources.add(new MapPropertySource("sourceA", mapA));
+	 * sources.add(new MapPropertySource("sourceB", mapB));
+	 * assert sources.contains(PropertySource.named("sourceA"));
+	 * assert sources.contains(PropertySource.named("sourceB"));
+	 * assert !sources.contains(PropertySource.named("sourceC"));
+	 * }
+ * The returned {@code PropertySource} will throw {@code UnsupportedOperationException} + * if any methods other than {@code equals(Object)}, {@code hashCode()}, and {@code toString()} + * are called. + * @param name the name of the comparison {@code PropertySource} to be created and returned. + */ + public static PropertySource named(String name) { + return new ComparisonPropertySource(name); + } + + /** + * Return the name of this {@code PropertySource}. + */ + public String getName() { + return this.name; + } + + /** + * Return the underlying source object for this {@code PropertySource}. + */ + public T getSource() { + return this.source; + } + + /** + * Return whether this {@code PropertySource} contains the given name. + *

This implementation simply checks for a {@code null} return value + * from {@link #getProperty(String)}. Subclasses may wish to implement + * a more efficient algorithm if possible. + * @param name the property name to find + */ + public boolean containsProperty(String name) { + // getProperty 抽象方法子类实现 + return (getProperty(name) != null); + } + + /** + * Return the value associated with the given name, + * or {@code null} if not found. + * // getProperty 抽象方法子类实现 + * @param name the property to find + * @see PropertyResolver#getRequiredProperty(String) + */ + @Nullable + public abstract Object getProperty(String name); + + /** + * This {@code PropertySource} object is equal to the given object if: + *

    + *
  • they are the same instance + *
  • the {@code name} properties for both objects are equal + *
+ *

No properties other than {@code name} are evaluated. + */ + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof PropertySource && + ObjectUtils.nullSafeEquals(this.name, ((PropertySource) other).name))); + } + + /** + * Return a hash code derived from the {@code name} property + * of this {@code PropertySource} object. + */ + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(this.name); + } + + /** + * Produce concise output (type and name) if the current log level does not include + * debug. If debug is enabled, produce verbose output including the hash code of the + * PropertySource instance and every name/value property pair. + *

This variable verbosity is useful as a property source such as system properties + * or environment variables may contain an arbitrary number of property pairs, + * potentially leading to difficult to read exception and log messages. + * @see Log#isDebugEnabled() + */ + @Override + public String toString() { + if (logger.isDebugEnabled()) { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + " {name='" + this.name + "', properties=" + this.source + "}"; + } + else { + return getClass().getSimpleName() + " {name='" + this.name + "'}"; + } + } + + /** + * {@code PropertySource} to be used as a placeholder in cases where an actual + * property source cannot be eagerly initialized at application context + * creation time. For example, a {@code ServletContext}-based property source + * must wait until the {@code ServletContext} object is available to its enclosing + * {@code ApplicationContext}. In such cases, a stub should be used to hold the + * intended default position/order of the property source, then be replaced + * during context refresh. + * @see org.springframework.context.support.AbstractApplicationContext#initPropertySources() + * @see org.springframework.web.context.support.StandardServletEnvironment + * @see org.springframework.web.context.support.ServletContextPropertySource + */ + public static class StubPropertySource extends PropertySource { + + public StubPropertySource(String name) { + super(name, new Object()); + } + + /** + * Always returns {@code null}. + */ + @Override + @Nullable + public String getProperty(String name) { + return null; + } + } + + + /** + * A {@code PropertySource} implementation intended for collection comparison + * purposes. + * + * @see PropertySource#named(String) + */ + static class ComparisonPropertySource extends StubPropertySource { + + // 异常信息 + private static final String USAGE_ERROR = + "ComparisonPropertySource instances are for use with collection comparison only"; + + public ComparisonPropertySource(String name) { + super(name); + } + + @Override + public Object getSource() { + // 抛异常 + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + public boolean containsProperty(String name) { + // 抛异常 + throw new UnsupportedOperationException(USAGE_ERROR); + } + + @Override + @Nullable + public String getProperty(String name) { + // 抛异常 + throw new UnsupportedOperationException(USAGE_ERROR); + } + } + +} +``` + +类图 + +![PropertySource.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertySource.png) diff --git a/docs/Spring/clazz/Spring-Scheduling.md b/docs/Spring/clazz/Spring-Scheduling.md index c948d802..69676c66 100644 --- a/docs/Spring/clazz/Spring-Scheduling.md +++ b/docs/Spring/clazz/Spring-Scheduling.md @@ -1,9 +1,12 @@ # Spring 定时任务 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) ## EnableScheduling + - 首先关注的类为启动定时任务的注解`@EnableScheduling` + ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -15,7 +18,9 @@ public @interface EnableScheduling { ``` ## SchedulingConfiguration + - 注册定时任务相关信息 + ```java @Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @@ -36,7 +41,9 @@ public class SchedulingConfiguration { ``` ## ScheduledAnnotationBeanPostProcessor -- 关注application事件,以及spring生命周期相关的接口实现 + +- 关注 application 事件,以及 spring 生命周期相关的接口实现 + ```java /** * application 事件 @@ -96,6 +103,7 @@ public class SchedulingConfiguration { ``` - 处理定时任务注解 + ```java protected void processScheduled(Scheduled scheduled, Method method, Object bean) { try { @@ -241,18 +249,21 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) } ``` - ## 定时任务 + - CronTask - - cron定时任务 + - cron 定时任务 - FixedDelayTask - - 间隔时间的定时任务 + - 间隔时间的定时任务 - FixedRateTask - - 调用频率的定时任务 + - 调用频率的定时任务 - ScheduledTask - - 定时任务对象 + - 定时任务对象 + ### cron 表达式解析 + - `org.springframework.scheduling.support.CronSequenceGenerator.doParse` + ```java private void doParse(String[] fields) { setNumberHits(this.seconds, fields[0], 0, 60); @@ -272,8 +283,10 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) ``` ### 执行定时任务 + - 这里以 CronTask 任务进行分析,其他定时任务同理 - - `org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleCronTask` + - `org.springframework.scheduling.config.ScheduledTaskRegistrar.scheduleCronTask` + ```java @Nullable public ScheduledTask scheduleCronTask(CronTask task) { @@ -299,4 +312,4 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean) return (newTask ? scheduledTask : null); } -``` \ No newline at end of file +``` diff --git a/docs/Spring/clazz/Spring-SimpleAliasRegistry.md b/docs/Spring/clazz/Spring-SimpleAliasRegistry.md index 144c88a3..006728d8 100644 --- a/docs/Spring/clazz/Spring-SimpleAliasRegistry.md +++ b/docs/Spring/clazz/Spring-SimpleAliasRegistry.md @@ -1,9 +1,12 @@ # Spring-SimpleAliasRegistry + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [huifer-spring](https://github.com/huifer/spring-framework) ## AliasRegistry -- `SimpleAliasRegistry`继承`org.springframework.core.AliasRegistry` + +- `SimpleAliasRegistry`继承`org.springframework.core.AliasRegistry` + ```java public interface AliasRegistry { @@ -50,7 +53,9 @@ public interface AliasRegistry { } ``` -## SimpleAliasRegistry + +## SimpleAliasRegistry + ```java /** * Simple implementation of the {@link AliasRegistry} interface. @@ -304,4 +309,4 @@ public class SimpleAliasRegistry implements AliasRegistry { } -``` \ No newline at end of file +``` diff --git a/docs/Spring/clazz/Spring-SpringFactoriesLoader.md b/docs/Spring/clazz/Spring-SpringFactoriesLoader.md new file mode 100644 index 00000000..09e565e2 --- /dev/null +++ b/docs/Spring/clazz/Spring-SpringFactoriesLoader.md @@ -0,0 +1,127 @@ +# SpringFactoriesLoader + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) + +- 全路径 : `org.springframework.core.io.support.SpringFactoriesLoader` +- 测试类 : `org.springframework.core.io.support.SpringFactoriesLoaderTests` + +## loadFactories + +- **加载并实例化工厂** + +```java +public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader) { + Assert.notNull(factoryType, "'factoryType' must not be null"); + ClassLoader classLoaderToUse = classLoader; + if (classLoaderToUse == null) { + classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); + } + // 工厂实现类名称 + List factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse); + if (logger.isTraceEnabled()) { + logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames); + } + List result = new ArrayList<>(factoryImplementationNames.size()); + for (String factoryImplementationName : factoryImplementationNames) { + // 将实例化的工厂放入结果集合 + result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse)); + } + // 排序 + AnnotationAwareOrderComparator.sort(result); + return result; + } +``` + +## loadSpringFactories + +- 获取接口的实现类名 + +```java + private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { + MultiValueMap result = cache.get(classLoader); + if (result != null) { + return result; + } + + try { + // 找 META-INF/spring.factories + Enumeration urls = (classLoader != null ? + classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : + ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); + result = new LinkedMultiValueMap<>(); + while (urls.hasMoreElements()) { + // 获取 路由地址 + URL url = urls.nextElement(); + // url 解析 + UrlResource resource = new UrlResource(url); + // Properties 解析 + Properties properties = PropertiesLoaderUtils.loadProperties(resource); + // 循环解析结果 + for (Map.Entry entry : properties.entrySet()) { + String factoryTypeName = ((String) entry.getKey()).trim(); + for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { + // 放入list + result.add(factoryTypeName, factoryImplementationName.trim()); + } + } + } + // 放入缓存 + cache.put(classLoader, result); + return result; + } + catch (IOException ex) { + throw new IllegalArgumentException("Unable to load factories from location [" + + FACTORIES_RESOURCE_LOCATION + "]", ex); + } + } + +``` + +- 存放在 测试目录下的`META-INF/spring.factories` + + ```properties + org.springframework.core.io.support.DummyFactory =\ + org.springframework.core.io.support.MyDummyFactory2, \ + org.springframework.core.io.support.MyDummyFactory1 + + java.lang.String=\ + org.springframework.core.io.support.MyDummyFactory1 + + org.springframework.core.io.support.DummyPackagePrivateFactory=\ + org.springframework.core.io.support.DummyPackagePrivateFactory + + ``` + +- `Enumeration urls ` 变量存放的是 扫描到的`META-INF/spring.factories` 路径 + +- while 代码简单描述 + 1. 获取文件路径 + 2. 文件路径解析 + 3. 读取文件 Properties 解析 + 4. 放入返回结果 + 5. 放入缓存 + +## instantiateFactory + +```java +@SuppressWarnings("unchecked") +private static T instantiateFactory(String factoryImplementationName, Class factoryType, ClassLoader classLoader) { + try { + Class factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader); + if (!factoryType.isAssignableFrom(factoryImplementationClass)) { + throw new IllegalArgumentException( + "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]"); + } + return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance(); + } + catch (Throwable ex) { + throw new IllegalArgumentException( + "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", + ex + ); + } +} +``` + +- 反射创建 diff --git a/docs/Spring/clazz/Spring-StopWatch.md b/docs/Spring/clazz/Spring-StopWatch.md index fd68d090..dcb6879b 100644 --- a/docs/Spring/clazz/Spring-StopWatch.md +++ b/docs/Spring/clazz/Spring-StopWatch.md @@ -1,9 +1,12 @@ # Spring StopWatch + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 全路径: `org.springframework.util.StopWatch` + ## 属性 + - taskList: 任务信息列表 - keepTaskList: 是否保留任务信息列表 - startTimeMillis: 任务开始的时间 @@ -13,7 +16,9 @@ - totalTimeMillis: 总共花费的时间 ## 方法 + - `org.springframework.util.StopWatch.start(java.lang.String)` + ```java public void start(String taskName) throws IllegalStateException { if (this.currentTaskName != null) { @@ -23,7 +28,9 @@ this.startTimeMillis = System.currentTimeMillis(); } ``` + - `org.springframework.util.StopWatch.stop` + ```java public void stop() throws IllegalStateException { if (this.currentTaskName == null) { @@ -41,4 +48,4 @@ this.currentTaskName = null; } -``` \ No newline at end of file +``` diff --git a/docs/Spring/clazz/Spring-SystemPropertyUtils.md b/docs/Spring/clazz/Spring-SystemPropertyUtils.md new file mode 100644 index 00000000..7291fec6 --- /dev/null +++ b/docs/Spring/clazz/Spring-SystemPropertyUtils.md @@ -0,0 +1,83 @@ +# Spring SystemPropertyUtils + +- spring 中获取系统属性的工具类 + +- 内部属性 + +```java +/** + * + * Prefix for system property placeholders: "${". + * 前缀占位符 + * */ +public static final String PLACEHOLDER_PREFIX = "${"; + +/** + * Suffix for system property placeholders: "}". + * 后缀占位符 + * */ +public static final String PLACEHOLDER_SUFFIX = "}"; + +/** + * Value separator for system property placeholders: ":". + * 值分割符号 + * */ +public static final String VALUE_SEPARATOR = ":"; + + +/** + * 占位符解析类 + */ +private static final PropertyPlaceholderHelper strictHelper = + new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false); + +/** + * 占位符解析类 + */ +private static final PropertyPlaceholderHelper nonStrictHelper = + new PropertyPlaceholderHelper(PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true); +``` + +## resolvePlaceholders + +- 解析属性 + +![SystemPropertyUtils-resolvePlaceholders.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/SystemPropertyUtils-resolvePlaceholders.png) + +时序图因为有递归所以看着有点长, 其核心方法最后会指向 PlaceholderResolver + +通过 PlaceholderResolver 获取属性值 + +在 `SystemPropertyUtils` 内部有 `PlaceholderResolver ` 实现 + +- 最终通过下面的类来获取具体的属性值 + +```java +private static class SystemPropertyPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver { + + private final String text; + + public SystemPropertyPlaceholderResolver(String text) { + this.text = text; + } + + @Override + @Nullable + public String resolvePlaceholder(String placeholderName) { + try { + String propVal = System.getProperty(placeholderName); + if (propVal == null) { + // Fall back to searching the system environment. + // 获取系统属性 + propVal = System.getenv(placeholderName); + } + return propVal; + } + catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholderName + "' in [" + + this.text + "] as system property: " + ex); + return null; + } + } +} +``` diff --git a/docs/Spring/clazz/Spring-beanFactory.md b/docs/Spring/clazz/Spring-beanFactory.md new file mode 100644 index 00000000..de903d19 --- /dev/null +++ b/docs/Spring/clazz/Spring-beanFactory.md @@ -0,0 +1,2680 @@ +# Spring BeanFactory + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) + +## BeanFactory 概述 + +- `org.springframework.beans.factory.BeanFactory` + +### 类图 + +![beanFactory](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/BeanFactory.png) + +### 方法列表 + +- 贴出部分代码. 仅表示方法作用 + +```java +public interface BeanFactory { + // 从容器中根据beanname获取 + Object getBean(String name) throws BeansException; + // 延迟加载对象 + ObjectProvider getBeanProvider(Class requiredType); + // 是否存在beanName + boolean containsBean(String name); + // 这个 beanName 是否是单例的. 映射成 bean + boolean isSingleton(String name) throws NoSuchBeanDefinitionException; + // 是否多例. + boolean isPrototype(String name) throws NoSuchBeanDefinitionException; + // 类型是否匹配 + boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException; + // 获取bean的类型 + Class getType(String name) throws NoSuchBeanDefinitionException; + // 获取别名 + String[] getAliases(String name); +} +``` + +## 解析 + +### 用例 + +bean 的实例化有如下几种方法 + +1. 静态方法 +2. 工厂方法创建 +3. FactoryBean 接口创建 + +### 代码部分 + +```java +public class UserBean { + + private String name; + private Integer age; + + public static UserBean createInstance() { + UserBean userBean = new UserBean(); + userBean.setAge(18); + userBean.setName("zhangsan"); + + return userBean; + } + // get set 忽略 +} +``` + +```java +public interface UserBeanFactory { + UserBean factory(); +} + +public class UserBeanFactoryImpl implements + UserBeanFactory { + + + @Override + public UserBean factory() { + return UserBean.createInstance(); + } +} + +``` + +```java +public class UserFactoryBean implements FactoryBean { + + @Override + public boolean isSingleton() { + return true; + } + + @Override + public UserBean getObject() throws Exception { + return UserBean.createInstance(); + } + + @Override + public Class getObjectType() { + return UserBean.class; + } +} +``` + +```xml + + + + + + + + + + + + + + + +``` + +```java +public class SpringBeanInstantiation { + + public static void main(String[] args) { + BeanFactory context = new ClassPathXmlApplicationContext( + "META-INF/beans/spring-bean-instantiation.xml"); + + UserBean staticMethodBean = context.getBean("static-method-user", UserBean.class); + UserBean factoryUser = context.getBean("factory-use", UserBean.class); + UserBean factoryBean = context.getBean("factory-bean-user", UserBean.class); + System.out.println(); + + } +} +``` + +### 分析 + +- 对下面代码进行分析 + +```java + UserBean staticMethodBean = context.getBean("static-method-user", UserBean.class); +``` + +- `org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String, java.lang.Class)` + +```java +@Override + public T getBean(String name, Class requiredType) throws BeansException { + // 判断 beanFactory 是否存活 + assertBeanFactoryActive(); + + // 1. 获取 beanFactory + // 2. 根据 beanName + class 获取 Bean + return getBeanFactory().getBean(name, requiredType); + } +``` + +- 从方法参数 + - name: beanName + - requiredType: 唯一的类型. 对象类型 + +### assertBeanFactoryActive + +- beanFactory 是否存活判断 + +```java +protected void assertBeanFactoryActive() { + // 是否存活 + if (!this.active.get()) { + // 是否关闭 + if (this.closed.get()) { + throw new IllegalStateException(getDisplayName() + " has been closed already"); + } + else { + throw new IllegalStateException(getDisplayName() + " has not been refreshed yet"); + } + } + } +``` + +### getBeanFactory + +- 获取 beanFactory + + - 获取方法是一个抽象方法 + + ```java + public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException; + ``` + + - 子类实现 + + `org.springframework.context.support.AbstractRefreshableApplicationContext#getBeanFactory` + + ```java + @Override + public final ConfigurableListableBeanFactory getBeanFactory() { + synchronized (this.beanFactoryMonitor) { + if (this.beanFactory == null) { + throw new IllegalStateException("BeanFactory not initialized or already closed - " + + "call 'refresh' before accessing beans via the ApplicationContext"); + } + return this.beanFactory; + } + } + ``` + + - `org.springframework.context.support.GenericApplicationContext#getBeanFactory` + + ```java + @Override + public final ConfigurableListableBeanFactory getBeanFactory() { + return this.beanFactory; + } + ``` + +- 获取到的对象是`org.springframework.beans.factory.support.DefaultListableBeanFactory` + +![image-20200902102912716](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902102912716.png) + +- 整体类图 + +![image-20200902103154580](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902103154580.png) + +### doGetBean + +- `org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean` + + 获取 bean 的核心 + +#### transformedBeanName + +```java +protected String transformedBeanName(String name) { + // 转换 beanName . + // 1. 通过·BeanFactoryUtils.transformedBeanName· 求beanName + // 2. 如果是有别名的(方法参数是别名) . 会从别名列表中获取对应的 beanName + return canonicalName(BeanFactoryUtils.transformedBeanName(name)); +} +``` + +```java +public static String transformedBeanName(String name) { + Assert.notNull(name, "'name' must not be null"); + // 名字不是 & 开头直接返回 + if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { + return name; + } + // 截取字符串 在返回 + return transformedBeanNameCache.computeIfAbsent(name, beanName -> { + do { + beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); + } + while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); + return beanName; + }); +} +``` + +```java +public String canonicalName(String name) { + String canonicalName = name; + // Handle aliasing... + String resolvedName; + do { + // 别名的获取 + resolvedName = this.aliasMap.get(canonicalName); + if (resolvedName != null) { + canonicalName = resolvedName; + } + } + while (resolvedName != null); + return canonicalName; +} +``` + +别名对象 + +```java +private final Map aliasMap = new ConcurrentHashMap<>(16); +``` + +```java + + + +``` + +aliasMap 和 别名标签的对应关系 + +![image-20200902105454958](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902105454958.png) + +alias 标签的 alias 值作为别名的 key , alias 标签的 name 值作为 value + +#### getSingleton + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)` + +```java +@Override +@Nullable +public Object getSingleton(String beanName) { + return getSingleton(beanName, true); +} +``` + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)` + +```java +@Nullable +protected Object getSingleton(String beanName, boolean allowEarlyReference) { + // 尝试从单例缓存中获取 + Object singletonObject = this.singletonObjects.get(beanName); + // 单例对象是否null + // 这个 beanName 是否正在创建 + if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { + // 锁 + synchronized (this.singletonObjects) { + // 从延迟加载的map中获取 + singletonObject = this.earlySingletonObjects.get(beanName); + // 对象是否空 , 是否允许提前应用 + if (singletonObject == null && allowEarlyReference) { + // 从对象工厂map中获取对象工厂 + ObjectFactory singletonFactory = this.singletonFactories.get(beanName); + if (singletonFactory != null) { + // 对象获取后设置 + singletonObject = singletonFactory.getObject(); + this.earlySingletonObjects.put(beanName, singletonObject); + this.singletonFactories.remove(beanName); + } + } + } + } + return singletonObject; +} +``` + +- 相关属性值 + +```java +/** + * Cache of singleton objects: bean name to bean instance. + * + * 单例对象容器, key: beanName , value: bean实例 + * */ +private final Map singletonObjects = new ConcurrentHashMap<>(256); + + + /** + * Cache of singleton factories: bean name to ObjectFactory. + * key: beanName + * value: 对象工厂 + * */ + private final Map> singletonFactories = new HashMap<>(16); + + + + /** + * Names of beans that are currently in creation. + * + * 当前正在实例化的beanName + * + * */ + private final Set singletonsCurrentlyInCreation = + Collections.newSetFromMap(new ConcurrentHashMap<>(16)); +``` + +#### getObjectForBeanInstance + +- `org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance` + +```java +protected Object getObjectForBeanInstance( + Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) { + + // Don't let calling code try to dereference the factory if the bean isn't a factory. + // 判断 beanName 是不是 bean 工厂 + if (BeanFactoryUtils.isFactoryDereference(name)) { + // 类型判断 + if (beanInstance instanceof NullBean) { + return beanInstance; + } + if (!(beanInstance instanceof FactoryBean)) { + throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass()); + } + if (mbd != null) { + mbd.isFactoryBean = true; + } + // 返回实例 + return beanInstance; + } + + // Now we have the bean instance, which may be a normal bean or a FactoryBean. + // If it's a FactoryBean, we use it to create a bean instance, unless the + // caller actually wants a reference to the factory. + // 判断是否是 factoryBean + if (!(beanInstance instanceof FactoryBean)) { + return beanInstance; + } + + Object object = null; + if (mbd != null) { + mbd.isFactoryBean = true; + } + else { + // 缓存中获取 + object = getCachedObjectForFactoryBean(beanName); + } + if (object == null) { + // Return bean instance from factory. + // 如果还是 null 从 factory bean 中创建 + FactoryBean factory = (FactoryBean) beanInstance; + // Caches object obtained from FactoryBean if it is a singleton. + if (mbd == null && containsBeanDefinition(beanName)) { + mbd = getMergedLocalBeanDefinition(beanName); + } + boolean synthetic = (mbd != null && mbd.isSynthetic()); + // 从 FactoryBean 中获取bean实例 + object = getObjectFromFactoryBean(factory, beanName, !synthetic); + } + return object; + } +``` + +#### getObjectFromFactoryBean + +- `org.springframework.beans.factory.support.FactoryBeanRegistrySupport#getObjectFromFactoryBean` + +- 从 FactoryBean 中获取对象 + +```java + protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) { + // 是否单例 是否已经包含 + if (factory.isSingleton() && containsSingleton(beanName)) { + synchronized (getSingletonMutex()) { + // 从工厂bean的缓存中获取 + Object object = this.factoryBeanObjectCache.get(beanName); + if (object == null) { + + // 从 factoryBean 接口中获取 + object = doGetObjectFromFactoryBean(factory, beanName); + // Only post-process and store if not put there already during getObject() call above + // (e.g. because of circular reference processing triggered by custom getBean calls) + // 从缓存map中获取 + Object alreadyThere = this.factoryBeanObjectCache.get(beanName); + if (alreadyThere != null) { + // 如果缓存中获取有值 + // object 覆盖 + object = alreadyThere; + } + else { + if (shouldPostProcess) { + if (isSingletonCurrentlyInCreation(beanName)) { + // Temporarily return non-post-processed object, not storing it yet.. + return object; + } + // 单例创建前的验证 + beforeSingletonCreation(beanName); + try { + // 从 FactoryBean 接口创建的 后置处理 + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, + "Post-processing of FactoryBean's singleton object failed", ex); + } + finally { + // 单例bean创建之后 + afterSingletonCreation(beanName); + } + } + // 是否包含bean name + if (containsSingleton(beanName)) { + // 插入缓存 + // 后续使用的时候可以直接获取 + this.factoryBeanObjectCache.put(beanName, object); + } + } + } + return object; + } + } + else { + Object object = doGetObjectFromFactoryBean(factory, beanName); + if (shouldPostProcess) { + try { + object = postProcessObjectFromFactoryBean(object, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex); + } + } + return object; + } + } + +``` + +#### beforeSingletonCreation + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation` + +- 单例创建前的验证 + +```java +protected void beforeSingletonCreation(String beanName) { + // 排除的单例beanName 是否包含当前beanName + // 添加当前正在初始化的beanName 是否正确 + if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } +} +``` + +#### postProcessObjectFromFactoryBean + +- 两种实现 + + - `org.springframework.beans.factory.support.FactoryBeanRegistrySupport#postProcessObjectFromFactoryBean` + + ```java + protected Object postProcessObjectFromFactoryBean(Object object, String beanName) throws BeansException { + return object; + } + ``` + + 直接返回 object + + - `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#postProcessObjectFromFactoryBean` 调用 `BeanPostProcessor` + + ```java + @Override + protected Object postProcessObjectFromFactoryBean(Object object, String beanName) { + return applyBeanPostProcessorsAfterInitialization(object, beanName); + } + + + @Override + public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (BeanPostProcessor processor : getBeanPostProcessors()) { + Object current = processor.postProcessAfterInitialization(result, beanName); + if (current == null) { + return result; + } + result = current; + } + return result; + } + + ``` + +- 两个方法军返回 `Bean` 对象 . 一种是直接返回 。 另一种是执行接口 `BeanPostProcessor` 接口返回 + +#### afterSingletonCreation + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#afterSingletonCreation` + +```java +protected void afterSingletonCreation(String beanName) { + // 排除的单例beanName 是否包含当前beanName + // 移除当前正在初始化的beanName 是否正确 + if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) { + throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation"); + } +} +``` + +- 代码现在进入的很深了,回到 doGetBean +- `org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean` + +```java + protected T doGetBean(final String name, @Nullable final Class requiredType, + @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { + // 转换beanName + final String beanName = transformedBeanName(name); + Object bean; + + // Eagerly check singleton cache for manually registered singletons. + // 获取单例对象 + Object sharedInstance = getSingleton(beanName); + // 单例对象是否存在 参数是否为空 + if (sharedInstance != null && args == null) { + if (logger.isTraceEnabled()) { + if (isSingletonCurrentlyInCreation(beanName)) { + logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + + "' that is not fully initialized yet - a consequence of a circular reference"); + } + else { + logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); + } + } + // 实例化bean + bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); + } + + // 省略后续内容 + } +``` + +- 目前未知`doGetBean`的第一个`if`分支已经分析完毕. 接下来看下面的代码 + +- 下面这段代码就简单说一下就跳过了。 + - 从 容器中获取,最后还是回到 doGetBean 方法中. 来进行 bean 创建 这里不进行展开。 + +```java +else { + // Fail if we're already creating this bean instance: + // We're assumably within a circular reference. + // 循环依赖的问题 + if (isPrototypeCurrentlyInCreation(beanName)) { + throw new BeanCurrentlyInCreationException(beanName); + } + + // Check if bean definition exists in this factory. + BeanFactory parentBeanFactory = getParentBeanFactory(); + if (parentBeanFactory != null && !containsBeanDefinition(beanName)) { + // Not found -> check parent. + String nameToLookup = originalBeanName(name); + if (parentBeanFactory instanceof AbstractBeanFactory) { + return ((AbstractBeanFactory) parentBeanFactory).doGetBean( + nameToLookup, requiredType, args, typeCheckOnly); + } + else if (args != null) { + // Delegation to parent with explicit args. + return (T) parentBeanFactory.getBean(nameToLookup, args); + } + else if (requiredType != null) { + // No args -> delegate to standard getBean method. + return parentBeanFactory.getBean(nameToLookup, requiredType); + } + else { + return (T) parentBeanFactory.getBean(nameToLookup); + } + } +``` + +#### markBeanAsCreated + +- `org.springframework.beans.factory.support.AbstractBeanFactory#markBeanAsCreated` + +- 方法作用将 bean 标记为已创建 + +``` +protected void markBeanAsCreated(String beanName) { + // 已创建的beanName 是否包含当前beanName + if (!this.alreadyCreated.contains(beanName)) { + synchronized (this.mergedBeanDefinitions) { + if (!this.alreadyCreated.contains(beanName)) { + // Let the bean definition get re-merged now that we're actually creating + // the bean... just in case some of its metadata changed in the meantime. + // 将属性stale设置true + clearMergedBeanDefinition(beanName); + // 放入已创建集合中 + this.alreadyCreated.add(beanName); + } + } + } +} +``` + +```java +protected void clearMergedBeanDefinition(String beanName) { + RootBeanDefinition bd = this.mergedBeanDefinitions.get(beanName); + if (bd != null) { + bd.stale = true; + } +} +``` + +- stale 的解释 + + ```java + /** + * Determines if the definition needs to be re-merged. + * 是否需要重新合并定义 + * */ + volatile boolean stale; + ``` + +- 属性值 已创建的 beanName + + ```java + private final Set alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); + ``` + +#### getMergedLocalBeanDefinition + +- `org.springframework.beans.factory.support.AbstractBeanFactory#getMergedLocalBeanDefinition` + +- 这个方法获取一个`RootBeanDefinition`对象 , 这个对象也是 bean 的一种定义。 +- 从目前的几个方法名称来看,暂且认为这是一个合并了多个 `BeanDefinition`的对象吧 + +![rootBeanDefinition](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/RootBeanDefinition.png) + +```java +protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { + // Quick check on the concurrent map first, with minimal locking. + // 缓存中获取 + RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName); + if (mbd != null && !mbd.stale) { + return mbd; + } + // 合并的 bean 定义 + return getMergedBeanDefinition(beanName, getBeanDefinition(beanName)); +} + + + protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd) + throws BeanDefinitionStoreException { + + return getMergedBeanDefinition(beanName, bd, null); + } + +``` + +#### getBeanDefinition + +- 获取 `beanDefinition ` +- `org.springframework.beans.factory.support.DefaultListableBeanFactory#getBeanDefinition` + +```java +@Override +public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException { + BeanDefinition bd = this.beanDefinitionMap.get(beanName); + if (bd == null) { + if (logger.isTraceEnabled()) { + logger.trace("No bean named '" + beanName + "' found in " + this); + } + throw new NoSuchBeanDefinitionException(beanName); + } + return bd; +} +``` + +- 从 beanDefinition map 中获取 + +- 相关属性 + + ```java + /** + * Map of bean definition objects, keyed by bean name. + * + * key: beanName + * value: BeanDefinition + * + * */ + private final Map beanDefinitionMap = new ConcurrentHashMap<>(256); + ``` + +#### getMergedBeanDefinition + +- 获取`RootBeanDefinition` + +- `org.springframework.beans.factory.support.AbstractBeanFactory#getMergedBeanDefinition(java.lang.String, org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.config.BeanDefinition)` + +- 第一部分代码 + - map 中获取 RootBeanDefinition + - 是否存在父名称 + - 类型是否是 `RootBeanDefinition` + - 是: 拷贝 + - 否: 将 `BeanDefinition` 转换成 `RootBeanDefinition` + +```java +protected RootBeanDefinition getMergedBeanDefinition( + String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) + throws BeanDefinitionStoreException { + + synchronized (this.mergedBeanDefinitions) { + RootBeanDefinition mbd = null; + RootBeanDefinition previous = null; + + // Check with full lock now in order to enforce the same merged instance. + if (containingBd == null) { + // 从缓存中获取 + mbd = this.mergedBeanDefinitions.get(beanName); + } + + if (mbd == null || mbd.stale) { + previous = mbd; + // 是否存在父名称 + if (bd.getParentName() == null) { + // Use copy of given root bean definition. + // 类型是否等于RootBeanDefinition + if (bd instanceof RootBeanDefinition) { + // 做一次对象拷贝 + mbd = ((RootBeanDefinition) bd).cloneBeanDefinition(); + } + else { + // 将 beanDefinition 创建成 RootBeanDefinition + mbd = new RootBeanDefinition(bd); + } + } + + // 省略其他 + } +``` + +- 相关属性 + + ```java + /** + * Map from bean name to merged RootBeanDefinition. + * key: beanName + * value: RootBeanDefinition + * */ + private final Map mergedBeanDefinitions = new ConcurrentHashMap<>(256); + ``` + +- 克隆 方法 + + ```java + /** + * 克隆 BeanDefinition + * @return + */ + @Override + public RootBeanDefinition cloneBeanDefinition() { + return new RootBeanDefinition(this); + } + ``` + +- 第二部分代码 + +```java +{ + // Child bean definition: needs to be merged with parent. + // 父BeanDefinition + BeanDefinition pbd; + try { + // 父类beanName + String parentBeanName = transformedBeanName(bd.getParentName()); + // 当前beanName是否等于父的beanName + if (!beanName.equals(parentBeanName)) { + // 存在父 beanName + // 父 beanDefinition + // 递归调用 + pbd = getMergedBeanDefinition(parentBeanName); + } + else { + // 获取父 beanFactory + BeanFactory parent = getParentBeanFactory(); + // beanFactory 类型判断 + if (parent instanceof ConfigurableBeanFactory) { + // ConfigurableBeanFactory 的获取方式 + pbd = ((ConfigurableBeanFactory) parent).getMergedBeanDefinition(parentBeanName); + } + else { + throw new NoSuchBeanDefinitionException(parentBeanName, + "Parent name '" + parentBeanName + "' is equal to bean name '" + beanName + + "': cannot be resolved without an AbstractBeanFactory parent"); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanName, + "Could not resolve parent bean definition '" + bd.getParentName() + "'", ex); + } + // Deep copy with overridden values. + // 将 父 BeanDefinition 对象拷贝 + mbd = new RootBeanDefinition(pbd); + // 覆盖 beanDefinition + mbd.overrideFrom(bd); + } +``` + +#### overrideFrom + +- 覆盖方法 + +- `org.springframework.beans.factory.support.AbstractBeanDefinition#overrideFrom` + +- 最后一段 + +```java + // Set default singleton scope, if not configured before. + // 作用域设置 + if (!StringUtils.hasLength(mbd.getScope())) { + // 没有设置作用域直接给单例类型 + mbd.setScope(SCOPE_SINGLETON); + } + + // A bean contained in a non-singleton bean cannot be a singleton itself. + // Let's correct this on the fly here, since this might be the result of + // parent-child merging for the outer bean, in which case the original inner bean + // definition will not have inherited the merged outer bean's singleton status. + // 修正 作用域 + if (containingBd != null && !containingBd.isSingleton() && mbd.isSingleton()) { + mbd.setScope(containingBd.getScope()); + } + + // Cache the merged bean definition for the time being + // (it might still get re-merged later on in order to pick up metadata changes) + if (containingBd == null && isCacheBeanMetadata()) { + // 放入缓存 + this.mergedBeanDefinitions.put(beanName, mbd); + } +} +if (previous != null) { + copyRelevantMergedBeanDefinitionCaches(previous, mbd); +} +return mbd; +``` + +#### checkMergedBeanDefinition + +- `org.springframework.beans.factory.support.AbstractBeanFactory#checkMergedBeanDefinition` + + ```java + protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args) + throws BeanDefinitionStoreException { + + if (mbd.isAbstract()) { + throw new BeanIsAbstractException(beanName); + } + } + ``` + + - 判断是否 abstract 标记的情况 + +- 继续回到 `doGetBean` 方法 + +```java +// 需要依赖的bean +String[] dependsOn = mbd.getDependsOn(); +if (dependsOn != null) { + for (String dep : dependsOn) { + if (isDependent(beanName, dep)) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); + } + // 注册依赖bean + registerDependentBean(dep, beanName); + try { + getBean(dep); + } + catch (NoSuchBeanDefinitionException ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "'" + beanName + "' depends on missing bean '" + dep + "'", ex); + } + } +} +``` + +#### isDependent + +- 是否存在依赖关系 + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent(java.lang.String, java.lang.String, java.util.Set)` + +```java +private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set alreadySeen) { + if (alreadySeen != null && alreadySeen.contains(beanName)) { + return false; + } + // 别名 + String canonicalName = canonicalName(beanName); + // 依赖列表中获取 + Set dependentBeans = this.dependentBeanMap.get(canonicalName); + if (dependentBeans == null) { + return false; + } + if (dependentBeans.contains(dependentBeanName)) { + return true; + } + for (String transitiveDependency : dependentBeans) { + if (alreadySeen == null) { + alreadySeen = new HashSet<>(); + } + alreadySeen.add(beanName); + if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) { + return true; + } + } + return false; +} +``` + +- 相关属性 + + ```java + /** + * Map between dependent bean names: bean name to Set of dependent bean names. + * + * key: bean + * value: 依赖列表 + * */ + private final Map> dependentBeanMap = new ConcurrentHashMap<>(64); + ``` + +- 一个用例 + +```xml + + + +``` + +![image-20200903091759451](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903091759451.png) + +#### registerDependentBean + +- 注册依赖关系 +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerDependentBean` + - 在前文调用 `isDependent` 方法的的时候我们找到了一个依赖映射`dependentBeanMap` ,在这个方法中会将依赖关系放入`dependentBeanMap` + +```java +public void registerDependentBean(String beanName, String dependentBeanName) { + // 别名 + String canonicalName = canonicalName(beanName); + + synchronized (this.dependentBeanMap) { + // 向依赖关系中放入数据 + Set dependentBeans = + this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8)); + if (!dependentBeans.add(dependentBeanName)) { + return; + } + } + + synchronized (this.dependenciesForBeanMap) { + Set dependenciesForBean = + this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8)); + dependenciesForBean.add(canonicalName); + } +} +``` + +- 再回到 `doGetBean` + +- 接下来就是实例化的过程了. + +```java +if (mbd.isSingleton()) { + sharedInstance = getSingleton(beanName, () -> { + try { + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); +} +``` + +#### getSingleton + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory)` +- 获取单例对象 + + 1. 从单例对象的 map 缓存中获取 + 2. 从 ObjectFactory 中获取 + +- 周边方法 + + - `beforeSingletonCreation` + + - `afterSingletonCreation` + - `addSingleton` + +```java +public Object getSingleton(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(beanName, "Bean name must not be null"); + synchronized (this.singletonObjects) { + // 从单例对象缓存中获取 + Object singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + if (this.singletonsCurrentlyInDestruction) { + throw new BeanCreationNotAllowedException(beanName, + "Singleton bean creation not allowed while singletons of this factory are in destruction " + + "(Do not request a bean from a BeanFactory in a destroy method implementation!)"); + } + if (logger.isDebugEnabled()) { + logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); + } + // 单例创建前的验证 + beforeSingletonCreation(beanName); + boolean newSingleton = false; + boolean recordSuppressedExceptions = (this.suppressedExceptions == null); + if (recordSuppressedExceptions) { + this.suppressedExceptions = new LinkedHashSet<>(); + } + try { + // 从 ObjectFactory 中获取 + singletonObject = singletonFactory.getObject(); + newSingleton = true; + } + catch (IllegalStateException ex) { + // Has the singleton object implicitly appeared in the meantime -> + // if yes, proceed with it since the exception indicates that state. + singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + throw ex; + } + } + catch (BeanCreationException ex) { + if (recordSuppressedExceptions) { + for (Exception suppressedException : this.suppressedExceptions) { + ex.addRelatedCause(suppressedException); + } + } + throw ex; + } + finally { + if (recordSuppressedExceptions) { + this.suppressedExceptions = null; + } + // 创建单例对象后的验证 + afterSingletonCreation(beanName); + } + if (newSingleton) { + // 添加到 单例容器中 + addSingleton(beanName, singletonObject); + } + } + return singletonObject; + } +} +``` + +- 回到 doGetBean 方法中 + + ```java + if (mbd.isSingleton()) { + // 判断是否是单例 + sharedInstance = getSingleton(beanName, () -> { + try { + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + ``` + + 这里又要给 `createBean`方法, 从 `getSingleton` 的参数看可以知道 ,第二个匿名函数是`ObjectFactory`接口实现. + + ```java + @FunctionalInterface + public interface ObjectFactory { + + /** + * Return an instance (possibly shared or independent) + * of the object managed by this factory. + * 获取对象 + * @return the resulting instance + * @throws BeansException in case of creation errors + */ + T getObject() throws BeansException; + + } + ``` + + - createBean 返回的就是单例 bean 对象的实例 + +##### createBean + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[])` + +- 两个核心方法 + +```java +// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. +Object bean = resolveBeforeInstantiation(beanName, mbdToUse); +Object beanInstance = doCreateBean(beanName, mbdToUse, args); +``` + +###### resolveBeforeInstantiation + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation` + +- 方法概述: + + 获取`BeanPostProcessor`接口的实现列表 + + - `applyBeanPostProcessorsBeforeInstantiation` 前置方法执行 + - `applyBeanPostProcessorsAfterInitialization`后置方法执行 + +```java +@Nullable +protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { + Object bean = null; + if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { + // Make sure bean class is actually resolved at this point. + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + Class targetType = determineTargetType(beanName, mbd); + if (targetType != null) { + /** + * 主要实现{@link org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation(java.lang.Class, java.lang.String)} + */ + bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); + if (bean != null) { + bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); + } + } + } + mbd.beforeInstantiationResolved = (bean != null); + } + return bean; +} +``` + +###### doCreateBean + +- 创建 bean +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean` + +```java + // Instantiate the bean. + BeanWrapper instanceWrapper = null; + if (mbd.isSingleton()) { + // beanFactory 移除当前创建的beanName + instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); + } + // beanWrapper 是否存在 + if (instanceWrapper == null) { + // 创建 bean 实例 + instanceWrapper = createBeanInstance(beanName, mbd, args); + } +``` + +###### createBeanInstance + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance` +- 创建 bean 实例 + +```java +protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) { + // Make sure bean class is actually resolved at this point. + // 获取 bean class + Class beanClass = resolveBeanClass(mbd, beanName); + + if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Bean class isn't public, and non-public access not allowed: " + beanClass.getName() + ); + } + + // 返回一个用来创建bean实例的回调接口 + // Supplier get 直接获取bean对象 + Supplier instanceSupplier = mbd.getInstanceSupplier(); + if (instanceSupplier != null) { + return obtainFromSupplier(instanceSupplier, beanName); + } + + if (mbd.getFactoryMethodName() != null) { + // 通过工厂方法创建 + return instantiateUsingFactoryMethod(beanName, mbd, args); + } + + // Shortcut when re-creating the same bean... + boolean resolved = false; + boolean autowireNecessary = false; + if (args == null) { + synchronized (mbd.constructorArgumentLock) { + if (mbd.resolvedConstructorOrFactoryMethod != null) { + resolved = true; + autowireNecessary = mbd.constructorArgumentsResolved; + } + } + } + if (resolved) { + if (autowireNecessary) { + // 自动构造 bean + return autowireConstructor(beanName, mbd, null, null); + } + else { + // 实例化bean + return instantiateBean(beanName, mbd); + } + } + + // Candidate constructors for autowiring? + Constructor[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); + if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || + mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { + return autowireConstructor(beanName, mbd, ctors, args); + } + + // Preferred constructors for default construction? + ctors = mbd.getPreferredConstructors(); + if (ctors != null) { + return autowireConstructor(beanName, mbd, ctors, null); + } + + // No special handling: simply use no-arg constructor. + return instantiateBean(beanName, mbd); +} +``` + +###### resolveBeanClass + +- `org.springframework.beans.factory.support.AbstractBeanFactory#resolveBeanClass` +- 获取 bean 的 class + +```java +@Nullable +protected Class resolveBeanClass(final RootBeanDefinition mbd, String beanName, final Class... typesToMatch) + throws CannotLoadBeanClassException { + + try { + // 是否包含 bean 类型 + if (mbd.hasBeanClass()) { + // 直接返回 + return mbd.getBeanClass(); + } + if (System.getSecurityManager() != null) { + return AccessController.doPrivileged((PrivilegedExceptionAction>) () -> + doResolveBeanClass(mbd, typesToMatch), getAccessControlContext()); + } + else { + // 从 bean definition 中获取 + return doResolveBeanClass(mbd, typesToMatch); + } + } + catch (PrivilegedActionException pae) { + ClassNotFoundException ex = (ClassNotFoundException) pae.getException(); + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + } + catch (ClassNotFoundException ex) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex); + } + catch (LinkageError err) { + throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err); + } +} +``` + +###### doResolveBeanClass + +- `org.springframework.beans.factory.support.AbstractBeanFactory#doResolveBeanClass` + +- 第一段 + + ```java + ClassLoader beanClassLoader = getBeanClassLoader(); + ClassLoader dynamicLoader = beanClassLoader; + boolean freshResolve = false; + + // 判断 typesToMatch 是否为空 + if (!ObjectUtils.isEmpty(typesToMatch)) { + // When just doing type checks (i.e. not creating an actual instance yet), + // use the specified temporary class loader (e.g. in a weaving scenario). + // 获取临时类加载器 + ClassLoader tempClassLoader = getTempClassLoader(); + if (tempClassLoader != null) { + dynamicLoader = tempClassLoader; + freshResolve = true; + // 类型比较 + if (tempClassLoader instanceof DecoratingClassLoader) { + DecoratingClassLoader dcl = (DecoratingClassLoader) tempClassLoader; + for (Class typeToMatch : typesToMatch) { + // 添加排除的类 + dcl.excludeClass(typeToMatch.getName()); + } + } + } + } + ``` + +- 第二段 + + ```java + if (className != null) { + // bean 属性值 + Object evaluated = evaluateBeanDefinitionString(className, mbd); + if (!className.equals(evaluated)) { + // A dynamically resolved expression, supported as of 4.2... + if (evaluated instanceof Class) { + return (Class) evaluated; + } + else if (evaluated instanceof String) { + className = (String) evaluated; + freshResolve = true; + } + else { + throw new IllegalStateException("Invalid class name expression result: " + evaluated); + } + } + ``` + +###### evaluateBeanDefinitionString + +```java +@Nullable +protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) { + // 占位符解析 + if (this.beanExpressionResolver == null) { + return value; + } + + Scope scope = null; + if (beanDefinition != null) { + // 获取 scope + String scopeName = beanDefinition.getScope(); + if (scopeName != null) { + // scope 转换成 接口值 + scope = getRegisteredScope(scopeName); + } + } + // 返回对象 + return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope)); +} +``` + +###### evaluate + +- `org.springframework.context.expression.StandardBeanExpressionResolver#evaluate` + +```java + @Override + @Nullable + public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException { + if (!StringUtils.hasLength(value)) { + return value; + } + try { + Expression expr = this.expressionCache.get(value); + if (expr == null) { + // el表达式解析 + expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext); + // 解析结果放入缓存 + this.expressionCache.put(value, expr); + } + // spring 中默认的表达式上下文 + StandardEvaluationContext sec = this.evaluationCache.get(evalContext); + if (sec == null) { + // 设置属性 + sec = new StandardEvaluationContext(evalContext); + sec.addPropertyAccessor(new BeanExpressionContextAccessor()); + sec.addPropertyAccessor(new BeanFactoryAccessor()); + sec.addPropertyAccessor(new MapAccessor()); + sec.addPropertyAccessor(new EnvironmentAccessor()); + sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory())); + sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader())); + ConversionService conversionService = evalContext.getBeanFactory().getConversionService(); + if (conversionService != null) { + sec.setTypeConverter(new StandardTypeConverter(conversionService)); + } + customizeEvaluationContext(sec); + this.evaluationCache.put(evalContext, sec); + } + // 把值获取 + return expr.getValue(sec); + } + catch (Throwable ex) { + throw new BeanExpressionException("Expression parsing failed", ex); + } + } + +``` + +- 类图 + +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/TemplateAwareExpressionParser.png) + +###### BeanExpressionContext + +- 两个属性 + +```java +private final ConfigurableBeanFactory beanFactory; + +@Nullable +private final Scope scope; +``` + +- 几个方法 + +```java +public boolean containsObject(String key) { + return (this.beanFactory.containsBean(key) || + (this.scope != null && this.scope.resolveContextualObject(key) != null)); +} + +@Nullable +public Object getObject(String key) { + if (this.beanFactory.containsBean(key)) { + return this.beanFactory.getBean(key); + } + else if (this.scope != null) { + return this.scope.resolveContextualObject(key); + } + else { + return null; + } +} +``` + +beanName 是否存在 + +根据 beanName 获取 bean 实例 + +- 回到解析方法 + +###### parseExpression + +```java +@Override +public Expression parseExpression(String expressionString, @Nullable ParserContext context) throws ParseException { + if (context != null && context.isTemplate()) { + // 是否使用 template 解析 + return parseTemplate(expressionString, context); + } + else { + // 自定义的解析规则 + return doParseExpression(expressionString, context); + } +} +``` + +- doParseExpression + + - spring 中的两种解析方式 + - `org.springframework.expression.spel.standard.InternalSpelExpressionParser#doParseExpression ` + - `org.springframework.expression.spel.standard.SpelExpressionParser#doParseExpression` + +- parseTemplate 方法 + - `org.springframework.expression.common.TemplateAwareExpressionParser#parseTemplate` + +```java +private Expression parseTemplate(String expressionString, ParserContext context) throws ParseException { + // 表达式为空 + if (expressionString.isEmpty()) { + // 创建空的 LiteralExpression + return new LiteralExpression(""); + } + + // 表达式解析成接口 + Expression[] expressions = parseExpressions(expressionString, context); + if (expressions.length == 1) { + return expressions[0]; + } + else { + // 返回字符串的表达式 + return new CompositeStringExpression(expressionString, expressions); + } +} +``` + +![image-20200903111128603](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903111128603.png) + +- `parseExpressions` + + - `org.springframework.expression.common.TemplateAwareExpressionParser#parseExpressions` + - 说简单一些这个地方就是拿出表达式的值 + +- 回到 `evaluate` 方法 + +```java +StandardEvaluationContext sec = this.evaluationCache.get(evalContext); +if (sec == null) { + // 设置属性 + sec = new StandardEvaluationContext(evalContext); + sec.addPropertyAccessor(new BeanExpressionContextAccessor()); + sec.addPropertyAccessor(new BeanFactoryAccessor()); + sec.addPropertyAccessor(new MapAccessor()); + sec.addPropertyAccessor(new EnvironmentAccessor()); + sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory())); + sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader())); + ConversionService conversionService = evalContext.getBeanFactory().getConversionService(); + if (conversionService != null) { + sec.setTypeConverter(new StandardTypeConverter(conversionService)); + } + customizeEvaluationContext(sec); + this.evaluationCache.put(evalContext, sec); +} +// 把值获取 +return expr.getValue(sec); +``` + +- 最后一句 `getValue` + + - `org.springframework.expression.common.LiteralExpression#getValue(org.springframework.expression.EvaluationContext)` + + 刚才流程中我们可以看到 `expr` 是`LiteralExpression` + + ```java + @Override + public String getValue(EvaluationContext context) { + return this.literalValue; + } + ``` + + 直接返回字符串. 这个字符串就是刚才放进去的 el 表达式 + +往外跳 找到方法 `doResolveBeanClass` + +```java +if (className != null) { + // bean 属性值 + Object evaluated = evaluateBeanDefinitionString(className, mbd); + if (!className.equals(evaluated)) { + // A dynamically resolved expression, supported as of 4.2... + if (evaluated instanceof Class) { + return (Class) evaluated; + } + else if (evaluated instanceof String) { + className = (String) evaluated; + freshResolve = true; + } + else { + throw new IllegalStateException("Invalid class name expression result: " + evaluated); + } + } + if (freshResolve) { + // When resolving against a temporary class loader, exit early in order + // to avoid storing the resolved Class in the bean definition. + if (dynamicLoader != null) { + try { + return dynamicLoader.loadClass(className); + } + catch (ClassNotFoundException ex) { + if (logger.isTraceEnabled()) { + logger.trace("Could not load class [" + className + "] from " + dynamicLoader + ": " + ex); + } + } + } + return ClassUtils.forName(className, dynamicLoader); + } +} + +``` + +- 目前为止我们解析了 第一句话 `Object evaluated = evaluateBeanDefinitionString(className, mbd);` 接下来往下走看一下具体的 class 返回对象 + +1. 类型等于 class 直接返回 +2. 类型等于 String 的两种返回方式 + 1. ClassLoader.loadClass 返回 + 2. ClassUtils.forName 返回 + 1. 底层方法为 `java.lang.Class#forName(java.lang.String, boolean, java.lang.ClassLoader)` + +###### resolveBeanClass + +- 回到`doResolveBeanClass`方法中.最后一行 + + ```java + // Resolve regularly, caching the result in the BeanDefinition... + return mbd.resolveBeanClass(beanClassLoader); + ``` + +```java +@Nullable +public Class resolveBeanClass(@Nullable ClassLoader classLoader) throws ClassNotFoundException { + // 获取beanClassName + String className = getBeanClassName(); + if (className == null) { + return null; + } + // 加载类 + Class resolvedClass = ClassUtils.forName(className, classLoader); + this.beanClass = resolvedClass; + // 返回 + return resolvedClass; +} +``` + +- 获取 beanClassName + +```java +@Override +@Nullable +public String getBeanClassName() { + Object beanClassObject = this.beanClass; + if (beanClassObject instanceof Class) { + return ((Class) beanClassObject).getName(); + } + else { + return (String) beanClassObject; + } +} +``` + +- 回到`createBeanInstance` + - `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance` + +```java +// 返回一个用来创建bean实例的回调接口 +// Supplier get 直接获取bean对象 +Supplier instanceSupplier = mbd.getInstanceSupplier(); +if (instanceSupplier != null) { + return obtainFromSupplier(instanceSupplier, beanName); +} +``` + +###### obtainFromSupplier + +```java +protected BeanWrapper obtainFromSupplier(Supplier instanceSupplier, String beanName) { + Object instance; + + // 获取当前的bean实例 + String outerBean = this.currentlyCreatedBean.get(); + // 设置当前处理的beanName + this.currentlyCreatedBean.set(beanName); + try { + // 从 Supplier 中获取 + instance = instanceSupplier.get(); + } + finally { + if (outerBean != null) { + // 如果 currentlyCreatedBean 取不到设置 + this.currentlyCreatedBean.set(outerBean); + } + else { + // 移除 + this.currentlyCreatedBean.remove(); + } + } + + if (instance == null) { + // supplier 中获取不到, 将实例设置为 NullBean + instance = new NullBean(); + } + // beanWrapper 包装 + BeanWrapper bw = new BeanWrapperImpl(instance); + // beanWrapper 实例化后的操作 + initBeanWrapper(bw); + return bw; +} +``` + +- `Supplier` 代码如下 + +```java +@FunctionalInterface +public interface Supplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); +} +``` + +###### initBeanWrapper + +```java +protected void initBeanWrapper(BeanWrapper bw) { + // 设置转换服务 + bw.setConversionService(getConversionService()); + // 注册自定义属性编辑器 + registerCustomEditors(bw); +} +``` + +###### registerCustomEditors + +```java +protected void registerCustomEditors(PropertyEditorRegistry registry) { + PropertyEditorRegistrySupport registrySupport = + (registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null); + if (registrySupport != null) { + registrySupport.useConfigValueEditors(); + } + if (!this.propertyEditorRegistrars.isEmpty()) { + for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) { + try { + // 属性编辑器,注册自定义属性编辑器 + registrar.registerCustomEditors(registry); + } + catch (BeanCreationException ex) { + Throwable rootCause = ex.getMostSpecificCause(); + if (rootCause instanceof BeanCurrentlyInCreationException) { + BeanCreationException bce = (BeanCreationException) rootCause; + String bceBeanName = bce.getBeanName(); + if (bceBeanName != null && isCurrentlyInCreation(bceBeanName)) { + if (logger.isDebugEnabled()) { + logger.debug("PropertyEditorRegistrar [" + registrar.getClass().getName() + + "] failed because it tried to obtain currently created bean '" + + ex.getBeanName() + "': " + ex.getMessage()); + } + onSuppressedException(ex); + continue; + } + } + throw ex; + } + } + } + if (!this.customEditors.isEmpty()) { + this.customEditors.forEach((requiredType, editorClass) -> + registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))); + } +} +``` + +- 最后调用 + + `org.springframework.beans.support.ResourceEditorRegistrar#registerCustomEditors` + +###### registerCustomEditors + +```java +@Override +public void registerCustomEditors(PropertyEditorRegistry registry) { + ResourceEditor baseEditor = new ResourceEditor(this.resourceLoader, this.propertyResolver); + doRegisterEditor(registry, Resource.class, baseEditor); + doRegisterEditor(registry, ContextResource.class, baseEditor); + doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor)); + doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor)); + doRegisterEditor(registry, File.class, new FileEditor(baseEditor)); + doRegisterEditor(registry, Path.class, new PathEditor(baseEditor)); + doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor)); + doRegisterEditor(registry, URL.class, new URLEditor(baseEditor)); + + ClassLoader classLoader = this.resourceLoader.getClassLoader(); + doRegisterEditor(registry, URI.class, new URIEditor(classLoader)); + doRegisterEditor(registry, Class.class, new ClassEditor(classLoader)); + doRegisterEditor(registry, Class[].class, new ClassArrayEditor(classLoader)); + + if (this.resourceLoader instanceof ResourcePatternResolver) { + doRegisterEditor(registry, Resource[].class, + new ResourceArrayPropertyEditor((ResourcePatternResolver) this.resourceLoader, this.propertyResolver)); + } +} +``` + +###### doRegisterEditor + +```java +private void doRegisterEditor(PropertyEditorRegistry registry, Class requiredType, PropertyEditor editor) { + if (registry instanceof PropertyEditorRegistrySupport) { + // 属性编辑器覆盖默认的编辑器 + ((PropertyEditorRegistrySupport) registry).overrideDefaultEditor(requiredType, editor); + } + else { + // 注册自定义的属性编辑器 + registry.registerCustomEditor(requiredType, editor); + } +} +``` + +覆盖默认编辑器 + +```java +public void overrideDefaultEditor(Class requiredType, PropertyEditor propertyEditor) { + if (this.overriddenDefaultEditors == null) { + this.overriddenDefaultEditors = new HashMap<>(); + } + this.overriddenDefaultEditors.put(requiredType, propertyEditor); +} +``` + +- `registerCustomEditor` + +```java +@Override +public void registerCustomEditor(@Nullable Class requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) { + if (requiredType == null && propertyPath == null) { + throw new IllegalArgumentException("Either requiredType or propertyPath is required"); + } + if (propertyPath != null) { + if (this.customEditorsForPath == null) { + this.customEditorsForPath = new LinkedHashMap<>(16); + } + this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType)); + } + else { + if (this.customEditors == null) { + this.customEditors = new LinkedHashMap<>(16); + } + // 放入 customEditors map对象中 + this.customEditors.put(requiredType, propertyEditor); + this.customEditorCache = null; + } +} +``` + +到这里 `createBeanInstance` 流程已经完毕 + +回到`doCreateBean` 方法 + +```java +// beanWrapper 是否存在 +if (instanceWrapper == null) { + // 创建 bean 实例 + instanceWrapper = createBeanInstance(beanName, mbd, args); +} +// 获取 实例 +final Object bean = instanceWrapper.getWrappedInstance(); +// beanWrapper中存储的实例.class +Class beanType = instanceWrapper.getWrappedClass(); +if (beanType != NullBean.class) { + mbd.resolvedTargetType = beanType; +} +``` + +紧接着两行代码 获取 bean 实例 和 beanType + +###### applyMergedBeanDefinitionPostProcessors + +```java +synchronized (mbd.postProcessingLock) { + if (!mbd.postProcessed) { + try { + // 后置方法执行 BeanPostProcessor + applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); + } + catch (Throwable ex) { + throw new BeanCreationException(mbd.getResourceDescription(), beanName, + "Post-processing of merged bean definition failed", ex + ); + } + mbd.postProcessed = true; + } +} +``` + +- `applyMergedBeanDefinitionPostProcessors` 方法会执行所有的后置方法. + +```java +protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class beanType, String beanName) { + for (BeanPostProcessor bp : getBeanPostProcessors()) { + if (bp instanceof MergedBeanDefinitionPostProcessor) { + MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp; + bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); + } + } +} +``` + +###### addSingletonFactory + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory` + +- 继续回到 doCreateBean + +```java +// Eagerly cache singletons to be able to resolve circular references +// even when triggered by lifecycle interfaces like BeanFactoryAware. +boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && + isSingletonCurrentlyInCreation(beanName)); + +// 单例对象暴露 +if (earlySingletonExposure) { + if (logger.isTraceEnabled()) { + logger.trace("Eagerly caching bean '" + beanName + + "' to allow for resolving potential circular references"); + } + addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); +} +``` + +- `org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory` + + 添加单例工厂 + +```java +protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) { + Assert.notNull(singletonFactory, "Singleton factory must not be null"); + synchronized (this.singletonObjects) { + if (!this.singletonObjects.containsKey(beanName)) { + // 添加单例对象工厂 + this.singletonFactories.put(beanName, singletonFactory); + // 删除单例BeanName + this.earlySingletonObjects.remove(beanName); + // 注册单例beanName + this.registeredSingletons.add(beanName); + } + } +} +``` + +###### getEarlyBeanReference + +- `org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference` + +```java +@Override +public Object getEarlyBeanReference(Object bean, String beanName) { + // 尝试获取缓存 + Object cacheKey = getCacheKey(bean.getClass(), beanName); + // 加入缓存 + this.earlyProxyReferences.put(cacheKey, bean); + // 代理对象 + return wrapIfNecessary(bean, beanName, cacheKey); +} +``` + +- wrapIfNecessary + +```java +protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { + // 这个bean是否处理过 + if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { + return bean; + } + // 这个bean是否需要代理 + if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { + return bean; + } + // 1.bean.class是否是Spring接口类型 2. 是否为 AutowireCapableBeanFactory 接口 + if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { + // 向代理集合中插入值 + this.advisedBeans.put(cacheKey, Boolean.FALSE); + return bean; + } + + // Create proxy if we have advice. + // 增强方法获取 + Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); + // 增强方法不为空 + if (specificInterceptors != DO_NOT_PROXY) { + // 向代理集合中插入值 + this.advisedBeans.put(cacheKey, Boolean.TRUE); + // 创建代理 + Object proxy = createProxy( + bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); + // 代理类型 + this.proxyTypes.put(cacheKey, proxy.getClass()); + return proxy; + } + + this.advisedBeans.put(cacheKey, Boolean.FALSE); + return bean; +} +``` + +- 回到下面代码中 + + ```java + if (earlySingletonExposure) { + if (logger.isTraceEnabled()) { + logger.trace("Eagerly caching bean '" + beanName + + "' to allow for resolving potential circular references"); + } + // 添加单例工厂 + addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); + } + ``` + + - 上述方法就是将结果 bean 放入 + +###### populateBean + +```java +// Initialize the bean instance. +Object exposedObject = bean; +try { + populateBean(beanName, mbd, instanceWrapper); + exposedObject = initializeBean(beanName, exposedObject, mbd); +} +``` + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean` +- 设置属性值 + +- 概述一下方法 + + - 自动注入的两种实现 + + 1. 根据类型 + 2. 根据名称 + + - xml 中的属性标签设置 + + ```xml + + + + ``` + + ```java + { + if (bw == null) { + if (mbd.hasPropertyValues()) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance"); + } + else { + // Skip property population phase for null instance. + return; + } + } + + // Give any InstantiationAwareBeanPostProcessors the opportunity to modify the + // state of the bean before properties are set. This can be used, for example, + // to support styles of field injection. + if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { + for (BeanPostProcessor bp : getBeanPostProcessors()) { + if (bp instanceof InstantiationAwareBeanPostProcessor) { + InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; + if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { + return; + } + } + } + } + + PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null); + // 获取自动注入的值 + int resolvedAutowireMode = mbd.getResolvedAutowireMode(); + // 自动注入 + if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) { + MutablePropertyValues newPvs = new MutablePropertyValues(pvs); + // Add property values based on autowire by name if applicable. + if (resolvedAutowireMode == AUTOWIRE_BY_NAME) { + // 按照名称注入 + autowireByName(beanName, mbd, bw, newPvs); + } + // Add property values based on autowire by type if applicable. + if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) { + // 按照类型注入 + autowireByType(beanName, mbd, bw, newPvs); + } + pvs = newPvs; + } + + boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors(); + boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE); + + PropertyDescriptor[] filteredPds = null; + if (hasInstAwareBpps) { + if (pvs == null) { + pvs = mbd.getPropertyValues(); + } + for (BeanPostProcessor bp : getBeanPostProcessors()) { + if (bp instanceof InstantiationAwareBeanPostProcessor) { + InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; + PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); + if (pvsToUse == null) { + if (filteredPds == null) { + filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); + } + pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName); + if (pvsToUse == null) { + return; + } + } + pvs = pvsToUse; + } + } + } + if (needsDepCheck) { + if (filteredPds == null) { + filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching); + } + // 以来检查 + checkDependencies(beanName, mbd, filteredPds, pvs); + } + + if (pvs != null) { + // 应用属性 + applyPropertyValues(beanName, mbd, bw, pvs); + } + } + ``` + +pvs 属性如下 + +![image-20200903150738285](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903150738285.png) + +###### applyPropertyValues + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyPropertyValues` +- 属性设置 + +```java +protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) { + if (pvs.isEmpty()) { + return; + } + + if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) { + ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext()); + } + + MutablePropertyValues mpvs = null; + // 没有解析的属性 + List original; + + if (pvs instanceof MutablePropertyValues) { + mpvs = (MutablePropertyValues) pvs; + if (mpvs.isConverted()) { + // Shortcut: use the pre-converted values as-is. + try { + bw.setPropertyValues(mpvs); + return; + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } + } + original = mpvs.getPropertyValueList(); + } + else { + original = Arrays.asList(pvs.getPropertyValues()); + } + + // 自定义转换器 + TypeConverter converter = getCustomTypeConverter(); + if (converter == null) { + converter = bw; + } + // 创建BeanDefinitionValueResolver + BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter); + + // Create a deep copy, resolving any references for values. + // 解析后的对象集合 + List deepCopy = new ArrayList<>(original.size()); + boolean resolveNecessary = false; + for (PropertyValue pv : original) { + // 解析过的属性 + if (pv.isConverted()) { + deepCopy.add(pv); + } + // 没有解析过的属性 + else { + // 属性名称 + String propertyName = pv.getName(); + // 属性值,直接读取到的 + Object originalValue = pv.getValue(); + if (originalValue == AutowiredPropertyMarker.INSTANCE) { + Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod(); + if (writeMethod == null) { + throw new IllegalArgumentException("Autowire marker for property without write method: " + pv); + } + originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true); + } + // 解析值 + Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); + Object convertedValue = resolvedValue; + + /** + * 1. isWritableProperty: 属性可写 + * 2. isNestedOrIndexedProperty: 是否循环嵌套 + */ + boolean convertible = bw.isWritableProperty(propertyName) && + !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName); + if (convertible) { + // 转换器解析 + convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter); + } + // Possibly store converted value in merged bean definition, + // in order to avoid re-conversion for every created bean instance. + if (resolvedValue == originalValue) { + if (convertible) { + pv.setConvertedValue(convertedValue); + } + deepCopy.add(pv); + } + // 类型解析 + else if (convertible && originalValue instanceof TypedStringValue && + !((TypedStringValue) originalValue).isDynamic() && + !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { + pv.setConvertedValue(convertedValue); + deepCopy.add(pv); + } + else { + resolveNecessary = true; + deepCopy.add(new PropertyValue(pv, convertedValue)); + } + } + } + if (mpvs != null && !resolveNecessary) { + mpvs.setConverted(); + } + + // Set our (possibly massaged) deep copy. + try { + bw.setPropertyValues(new MutablePropertyValues(deepCopy)); + } + catch (BeansException ex) { + throw new BeanCreationException( + mbd.getResourceDescription(), beanName, "Error setting property values", ex); + } +} +``` + +属性设置后跳出方法回到 `doCreateBean` + +```java +try { + populateBean(beanName, mbd, instanceWrapper); + exposedObject = initializeBean(beanName, exposedObject, mbd); +} +``` + +![image-20200903150930186](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903150930186.png) + +###### initializeBean + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition)` + +- 我们可以看一下整个代码的流程 + 1. aware 接口的执行 + 2. BeanPostProcessor 前置方法执行 + 3. bean 实例化 + 4. BeanPostProcessor 后置方法执行 + 5. 返回 bean + +```java +protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + invokeAwareMethods(beanName, bean); + return null; + }, getAccessControlContext()); + } + else { + // aware 接口执行 + invokeAwareMethods(beanName, bean); + } + + Object wrappedBean = bean; + if (mbd == null || !mbd.isSynthetic()) { + // BeanPostProcessor 前置方法执行 + wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); + } + + try { + // 执行实例化函数 + invokeInitMethods(beanName, wrappedBean, mbd); + } + catch (Throwable ex) { + throw new BeanCreationException( + (mbd != null ? mbd.getResourceDescription() : null), + beanName, "Invocation of init method failed", ex + ); + } + if (mbd == null || !mbd.isSynthetic()) { + // BeanPostProcessor 后置方法执行 + wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); + } + + return wrappedBean; +} +``` + +- Aware 接口的执行 + +```java +private void invokeAwareMethods(final String beanName, final Object bean) { + if (bean instanceof Aware) { + if (bean instanceof BeanNameAware) { + ((BeanNameAware) bean).setBeanName(beanName); + } + if (bean instanceof BeanClassLoaderAware) { + ClassLoader bcl = getBeanClassLoader(); + if (bcl != null) { + ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl); + } + } + if (bean instanceof BeanFactoryAware) { + ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this); + } + } +}j +``` + +- 前置方法执行 + + ```java + @Override + public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (BeanPostProcessor processor : getBeanPostProcessors()) { + Object current = processor.postProcessBeforeInitialization(result, beanName); + if (current == null) { + return result; + } + result = current; + } + return result; + } + ``` + +- 后置方法执行 + + ```java + @Override + public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException { + + Object result = existingBean; + for (BeanPostProcessor processor : getBeanPostProcessors()) { + // 执行 spring 容器中 BeanPostProcessor + Object current = processor.postProcessAfterInitialization(result, beanName); + if (current == null) { + return result; + } + result = current; + } + return result; + } + ``` + +###### invokeInitMethods + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeInitMethods` +- 初始化方法重点看一下 + +```java +protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) + throws Throwable { + + + // 是否是 InitializingBean + boolean isInitializingBean = (bean instanceof InitializingBean); + // 是否存在方法 "afterPropertiesSet" + if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { + if (logger.isTraceEnabled()) { + logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); + } + if (System.getSecurityManager() != null) { + try { + // 执行 afterPropertiesSet + AccessController.doPrivileged((PrivilegedExceptionAction) () -> { + ((InitializingBean) bean).afterPropertiesSet(); + return null; + }, getAccessControlContext()); + } + catch (PrivilegedActionException pae) { + throw pae.getException(); + } + } + else { + ((InitializingBean) bean).afterPropertiesSet(); + } + } + + if (mbd != null && bean.getClass() != NullBean.class) { + String initMethodName = mbd.getInitMethodName(); + if (StringUtils.hasLength(initMethodName) && + !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && + !mbd.isExternallyManagedInitMethod(initMethodName)) { + // 自定义的 init method + invokeCustomInitMethod(beanName, bean, mbd); + } + } +} +``` + +![image-20200903153057321](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153057321.png) + +我们现在的 bean 不是`InitializingBean` 会走自定义的`init-mthod`方法 + +- 做一下改造实体对象 + + ```java + public void initMethod() { + this.name = "abc"; + this.age = 10; + } + ``` + +```xml + + + +``` + +- 观察 `initMethodName` 会变成 标签属性`init-method` 的内容. 接下来就是通过反射执行方法 + +![image-20200903153432559](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153432559.png) + +- 在执行方法前将 bean 的信息先做一次截图 + + ![image-20200903153533141](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153533141.png) + +- 如果按照我们代码中的编写方式 bean 的属性会被覆盖 + + ![image-20200903153617353](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153617353.png) + +###### invokeCustomInitMethod + +- `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeCustomInitMethod` +- 执行 自定义的`init-method` 方法 + +```java +protected void invokeCustomInitMethod(String beanName, final Object bean, RootBeanDefinition mbd) + throws Throwable { + + // 获取 initMethod 名称 + String initMethodName = mbd.getInitMethodName(); + Assert.state(initMethodName != null, "No init method set"); + // 反射获取方法 + Method initMethod = (mbd.isNonPublicAccessAllowed() ? + BeanUtils.findMethod(bean.getClass(), initMethodName) : + ClassUtils.getMethodIfAvailable(bean.getClass(), initMethodName)); + + // 方法是否存在判断 + if (initMethod == null) { + if (mbd.isEnforceInitMethod()) { + throw new BeanDefinitionValidationException("Could not find an init method named '" + + initMethodName + "' on bean with name '" + beanName + "'"); + } + else { + if (logger.isTraceEnabled()) { + logger.trace("No default init method named '" + initMethodName + + "' found on bean with name '" + beanName + "'"); + } + // Ignore non-existent default lifecycle methods. + return; + } + } + + if (logger.isTraceEnabled()) { + logger.trace("Invoking init method '" + initMethodName + "' on bean with name '" + beanName + "'"); + } + // 尝试获取接口方法 + Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod); + + if (System.getSecurityManager() != null) { + AccessController.doPrivileged((PrivilegedAction) () -> { + ReflectionUtils.makeAccessible(methodToInvoke); + return null; + }); + try { + // 反射调用 + AccessController.doPrivileged((PrivilegedExceptionAction) () -> + methodToInvoke.invoke(bean), getAccessControlContext()); + } + catch (PrivilegedActionException pae) { + InvocationTargetException ex = (InvocationTargetException) pae.getException(); + throw ex.getTargetException(); + } + } + else { + try { + // 反射调用 + ReflectionUtils.makeAccessible(methodToInvoke); + methodToInvoke.invoke(bean); + } + catch (InvocationTargetException ex) { + throw ex.getTargetException(); + } + } +} +``` + +###### getInterfaceMethodIfPossible + +- `org.springframework.util.ClassUtils#getInterfaceMethodIfPossible` + +```java +public static Method getInterfaceMethodIfPossible(Method method) { + // 是不是 public + // 是不是 接口 + if (!Modifier.isPublic(method.getModifiers()) || method.getDeclaringClass().isInterface()) { + return method; + } + // 放入init-method 缓存 + return interfaceMethodCache.computeIfAbsent(method, key -> { + Class current = key.getDeclaringClass(); + while (current != null && current != Object.class) { + // 当前类的 接口列表 + Class[] ifcs = current.getInterfaces(); + for (Class ifc : ifcs) { + try { + // 从接口中获取方法 + return ifc.getMethod(key.getName(), key.getParameterTypes()); + } + catch (NoSuchMethodException ex) { + // ignore + } + } + current = current.getSuperclass(); + } + return key; + }); +} +``` + +- 跳出这个方法`initializeBean` 回到下面代码 + + ```java + try { + populateBean(beanName, mbd, instanceWrapper); + exposedObject = initializeBean(beanName, exposedObject, mbd); + } + ``` + + - `org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean` + + 其实到此 bean 已经创建完成可以直接返回了. + +- 再往外层跳 + + `org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean` + + ```javascript + if (mbd.isSingleton()) { + // 判断是否是单例 + sharedInstance = getSingleton(beanName, () -> { + try { + return createBean(beanName, mbd, args); + } + catch (BeansException ex) { + // Explicitly remove instance from singleton cache: It might have been put there + // eagerly by the creation process, to allow for circular reference resolution. + // Also remove any beans that received a temporary reference to the bean. + destroySingleton(beanName); + throw ex; + } + }); + bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); + } + ``` + + - 单例对象的创建 bean 已经完成啦... + +- 其他的两种创建,其本质还是 `createBean` 方法的调用. + +```java +// 原型模式创建 +else if (mbd.isPrototype()) { + // It's a prototype -> create a new instance. + Object prototypeInstance = null; + try { + beforePrototypeCreation(beanName); + prototypeInstance = createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); +} + +else { + String scopeName = mbd.getScope(); + final Scope scope = this.scopes.get(scopeName); + if (scope == null) { + throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'"); + } + try { + Object scopedInstance = scope.get(beanName, () -> { + beforePrototypeCreation(beanName); + try { + return createBean(beanName, mbd, args); + } + finally { + afterPrototypeCreation(beanName); + } + }); + bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd); + } + catch (IllegalStateException ex) { + throw new BeanCreationException(beanName, + "Scope '" + scopeName + "' is not active for the current thread; consider " + + "defining a scoped proxy for this bean if you intend to refer to it from a singleton", + ex); + } +} +``` + +- 再往外面跳一层 回到 getBean 方法. + +- 终于 getBean 方法底层调用分析结束. diff --git a/docs/Spring/clazz/Spring-scan.md b/docs/Spring/clazz/Spring-scan.md index 042cc3f3..b89fbb04 100644 --- a/docs/Spring/clazz/Spring-scan.md +++ b/docs/Spring/clazz/Spring-scan.md @@ -1,15 +1,19 @@ # Spring scan + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read) ## 解析 + - Spring 注解形式使用有下面两种方式 - 1. 通过`AnnotationConfigApplicationContext`参数:扫描包 - 2. 通过xml配置`context:component-scan`属性`base-package` + 1. 通过`AnnotationConfigApplicationContext`参数:扫描包 + 2. 通过 xml 配置`context:component-scan`属性`base-package` + ```java AnnotationConfigApplicationContext aac = new AnnotationConfigApplicationContext("com.huifer.source.spring.ann"); ``` + ```xml @@ -17,6 +21,7 @@ - 目标明确开始找入口方法 - `AnnotationConfigApplicationContext`直接点进去看就找到了 + ```java public AnnotationConfigApplicationContext(String... basePackages) { this(); @@ -25,7 +30,9 @@ public AnnotationConfigApplicationContext(String... basePackages) { refresh(); } ``` + - `context:component-scan`寻找方式:冒号`:`钱+NamespaceHandler 或者全文搜索`component-scan`,最终找到`org.springframework.context.config.ContextNamespaceHandler` + ```java public class ContextNamespaceHandler extends NamespaceHandlerSupport { @@ -46,9 +53,10 @@ public class ContextNamespaceHandler extends NamespaceHandlerSupport { ### org.springframework.context.annotation.ComponentScanBeanDefinitionParser -![image-20200115093602651](../../../images/spring/image-20200115093602651.png) +![image-20200115093602651](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115093602651.png) - 实现`BeanDefinitionParser`直接看`parse`方法 + ```java @Override @Nullable @@ -75,7 +83,9 @@ public class ContextNamespaceHandler extends NamespaceHandlerSupport { ``` - 回过头看`AnnotationConfigApplicationContext` + ### org.springframework.context.annotation.AnnotationConfigApplicationContext + ```java public AnnotationConfigApplicationContext(String... basePackages) { this(); @@ -84,6 +94,7 @@ public AnnotationConfigApplicationContext(String... basePackages) { refresh(); } ``` + ```java private final ClassPathBeanDefinitionScanner scanner; @@ -94,7 +105,9 @@ public AnnotationConfigApplicationContext(String... basePackages) { } ``` + - `org.springframework.context.annotation.ClassPathBeanDefinitionScanner.scan` + ```java public int scan(String... basePackages) { @@ -112,12 +125,10 @@ public int scan(String... basePackages) { } ``` -- 这个地方`doScan`似曾相识,他就是`org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse`中的`doScan`,下一步解析doScan +- 这个地方`doScan`似曾相识,他就是`org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse`中的`doScan`,下一步解析 doScan ### org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan - - ```java protected Set doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); @@ -159,8 +170,6 @@ public int scan(String... basePackages) { ``` - - #### org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents ```java @@ -176,8 +185,6 @@ public int scan(String... basePackages) { ``` - - ```java /** * 扫描当前包路径下的资源 @@ -247,12 +254,8 @@ public int scan(String... basePackages) { ``` - - #### org.springframework.context.annotation.ScopeMetadataResolver#resolveScopeMetadata - - ```java /** * 生命周期设置 @@ -299,13 +302,11 @@ public int scan(String... basePackages) { ``` -![image-20200115141708702](../../../images/spring/image-20200115141708702.png) +![image-20200115141708702](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115141708702.png) #### org.springframework.beans.factory.support.BeanNameGenerator#generateBeanName -- 创建beanName `org.springframework.context.annotation.AnnotationBeanNameGenerator#generateBeanName` - - +- 创建 beanName `org.springframework.context.annotation.AnnotationBeanNameGenerator#generateBeanName` ```java @Override @@ -362,18 +363,12 @@ public class DemoService { } ``` - - -![image-20200115143315633](../../../images/spring/image-20200115143315633.png) - - +![image-20200115143315633](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115143315633.png) - `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)` - `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition)` - - -```JAVA +```java protected String buildDefaultBeanName(BeanDefinition definition) { // 获取bean class name String beanClassName = definition.getBeanClassName(); @@ -386,7 +381,7 @@ public class DemoService { ``` -```JAVA +```java @Configuration public class BeanConfig { @Scope(value =ConfigurableBeanFactory.SCOPE_PROTOTYPE) @@ -398,15 +393,11 @@ public class BeanConfig { ``` -![image-20200115143456554](../../../images/spring/image-20200115143456554.png) - - - - +![image-20200115143456554](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115143456554.png) #### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition -- 这个方法没什么难点,直接是set方法 +- 这个方法没什么难点,直接是 set 方法 ```java protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) { @@ -472,16 +463,10 @@ static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, Anno } ``` - - - 方法思路: 1. 获取注解的属性值 2. 设置注解属性 - - - - #### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate - 重复检查 @@ -509,15 +494,9 @@ static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, Anno ``` - - - - #### org.springframework.context.annotation.AnnotationConfigUtils#applyScopedProxyMode - - -```JAVA +```java static BeanDefinitionHolder applyScopedProxyMode( ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { @@ -531,4 +510,3 @@ static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, Anno } ``` - diff --git a/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md b/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md new file mode 100644 index 00000000..52bc9d32 --- /dev/null +++ b/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md @@ -0,0 +1,62 @@ +# Spring DateTimeFormatAnnotationFormatterFactory + +- 类全路径: `org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory` + +- 类图 + ![EmbeddedValueResolutionSupport](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/DateTimeFormatAnnotationFormatterFactory.png) + +```java +public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport + implements AnnotationFormatterFactory { + + /** + * 字段类型 + */ + private static final Set> FIELD_TYPES; + + @Override + public Set> getFieldTypes() { + return FIELD_TYPES; + } + + @Override + public Printer getPrinter(DateTimeFormat annotation, Class fieldType) { + return getFormatter(annotation, fieldType); + } + + @Override + public Parser getParser(DateTimeFormat annotation, Class fieldType) { + return getFormatter(annotation, fieldType); + } + + protected Formatter getFormatter(DateTimeFormat annotation, Class fieldType) { + DateFormatter formatter = new DateFormatter(); + // style + String style = resolveEmbeddedValue(annotation.style()); + // 判断时间格式是否村子啊 + if (StringUtils.hasLength(style)) { + formatter.setStylePattern(style); + } + // iso 设置 + formatter.setIso(annotation.iso()); + // date time pattern + String pattern = resolveEmbeddedValue(annotation.pattern()); + // 设置 + if (StringUtils.hasLength(pattern)) { + formatter.setPattern(pattern); + } + return formatter; + } + + static { + Set> fieldTypes = new HashSet<>(4); + // 加入字段类型 + fieldTypes.add(Date.class); + fieldTypes.add(Calendar.class); + fieldTypes.add(Long.class); + FIELD_TYPES = Collections.unmodifiableSet(fieldTypes); + } + +} + +``` diff --git a/docs/Spring/clazz/format/Parser/Spring-DateTimeParser.md b/docs/Spring/clazz/format/Parser/Spring-DateTimeParser.md new file mode 100644 index 00000000..a708c361 --- /dev/null +++ b/docs/Spring/clazz/format/Parser/Spring-DateTimeParser.md @@ -0,0 +1,30 @@ +# Spring DateTimeParser + +- 类全路径: `org.springframework.format.datetime.joda.DateTimeParser` + +- 代码如下 + +```java +public final class DateTimeParser implements Parser { + + private final DateTimeFormatter formatter; + + + /** + * Create a new DateTimeParser. + * @param formatter the Joda DateTimeFormatter instance + */ + public DateTimeParser(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + + @Override + public DateTime parse(String text, Locale locale) throws ParseException { + // DateTimeFormatter 转换字符串事件类型 + return JodaTimeContextHolder.getFormatter(this.formatter, locale).parseDateTime(text); + } + +} + +``` diff --git a/docs/Spring/clazz/format/Printer/Spring-MillisecondInstantPrinter.md b/docs/Spring/clazz/format/Printer/Spring-MillisecondInstantPrinter.md new file mode 100644 index 00000000..2e664d05 --- /dev/null +++ b/docs/Spring/clazz/format/Printer/Spring-MillisecondInstantPrinter.md @@ -0,0 +1,28 @@ +# Spring MillisecondInstantPrinter + +- 类全路径: `org.springframework.format.datetime.joda.MillisecondInstantPrinter` + +```java +public final class MillisecondInstantPrinter implements Printer { + + private final DateTimeFormatter formatter; + + + /** + * Create a new ReadableInstantPrinter. + * @param formatter the Joda DateTimeFormatter instance + */ + public MillisecondInstantPrinter(DateTimeFormatter formatter) { + this.formatter = formatter; + } + + + @Override + public String print(Long instant, Locale locale) { + // DateTimeFormatter .print + return JodaTimeContextHolder.getFormatter(this.formatter, locale).print(instant); + } + +} + +``` diff --git a/docs/Spring/clazz/format/Spring-AnnotationFormatterFactory.md b/docs/Spring/clazz/format/Spring-AnnotationFormatterFactory.md new file mode 100644 index 00000000..6d650ab0 --- /dev/null +++ b/docs/Spring/clazz/format/Spring-AnnotationFormatterFactory.md @@ -0,0 +1,41 @@ +# Spring AnnotationFormatterFactory + +- 类全路径: `org.springframework.format.AnnotationFormatterFactory` + +```java + +public interface AnnotationFormatterFactory { + + /** + * The types of fields that may be annotated with the <A> annotation. + * 字段类型 + */ + Set> getFieldTypes(); + + /** + * Get the Printer to print the value of a field of {@code fieldType} annotated with + * {@code annotation}. + *

If the type T the printer accepts is not assignable to {@code fieldType}, a + * coercion from {@code fieldType} to T will be attempted before the Printer is invoked. + * 通过注解和字段类型获取输出接口 + * @param annotation the annotation instance + * @param fieldType the type of field that was annotated + * @return the printer + */ + Printer getPrinter(A annotation, Class fieldType); + + /** + * Get the Parser to parse a submitted value for a field of {@code fieldType} + * annotated with {@code annotation}. + *

If the object the parser returns is not assignable to {@code fieldType}, + * a coercion to {@code fieldType} will be attempted before the field is set. + * 通过注解和字段类型获取解析接口 + * @param annotation the annotation instance + * @param fieldType the type of field that was annotated + * @return the parser + */ + Parser getParser(A annotation, Class fieldType); + +} + +``` diff --git a/docs/Spring/clazz/format/Spring-Formatter.md b/docs/Spring/clazz/format/Spring-Formatter.md new file mode 100644 index 00000000..9e9b1cc6 --- /dev/null +++ b/docs/Spring/clazz/format/Spring-Formatter.md @@ -0,0 +1,12 @@ +# Spring Formatter + +- 类全路径: `org.springframework.format.Formatter` + +```java +public interface Formatter extends Printer, Parser { + +} +``` + +- 该接口继承了 printer 和 parser 两个接口. +- 比较常见的有: `DateFormatter` 就是继承这个接口. diff --git a/docs/Spring/clazz/format/Spring-Parser.md b/docs/Spring/clazz/format/Spring-Parser.md new file mode 100644 index 00000000..9625774e --- /dev/null +++ b/docs/Spring/clazz/format/Spring-Parser.md @@ -0,0 +1,27 @@ +# Spring Parser + +- 类全路径: `org.springframework.format.Parser` +- 类作用: 字符串准换成 java 对象 + +```java + +@FunctionalInterface +public interface Parser { + + /** + * Parse a text String to produce a T. + * 将字符串转换成对象 + * @param text the text string + * @param locale the current user locale + * @return an instance of T + * @throws ParseException when a parse exception occurs in a java.text parsing library + * @throws IllegalArgumentException when a parse exception occurs + */ + T parse(String text, Locale locale) throws ParseException; + +} +``` + +- 类图 + +![Parser](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/Parser.png) diff --git a/docs/Spring/clazz/format/Spring-Printer.md b/docs/Spring/clazz/format/Spring-Printer.md new file mode 100644 index 00000000..c90c1b9f --- /dev/null +++ b/docs/Spring/clazz/format/Spring-Printer.md @@ -0,0 +1,20 @@ +# Spring Printer + +- 类全路径: `org.springframework.format.Printer` +- 类作用: 对象转换成字符串 + +```java +@FunctionalInterface +public interface Printer { + + /** + * Print the object of type T for display. + * 打印对象 + * @param object the instance to print + * @param locale the current user locale + * @return the printed text string + */ + String print(T object, Locale locale); + +} +``` diff --git a/docs/Spring/message/Spring-EnableJms.md b/docs/Spring/message/Spring-EnableJms.md index 9ea710ff..c6c49d82 100644 --- a/docs/Spring/message/Spring-EnableJms.md +++ b/docs/Spring/message/Spring-EnableJms.md @@ -1,9 +1,11 @@ # Spring EnableJms 注解 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码路径: `org.springframework.jms.annotation.EnableJms` ## 源码分析 + ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -15,7 +17,6 @@ public @interface EnableJms { - 该类的切入点在`@Import(JmsBootstrapConfiguration.class)` , 直接看`JmsBootstrapConfiguration`就可以了 - ```java @Configuration @Role(BeanDefinition.ROLE_INFRASTRUCTURE) @@ -48,17 +49,17 @@ public class JmsBootstrapConfiguration { 类图 -![image-20200304085303580](../../../images/springmessage/image-20200304085303580.png) +![image-20200304085303580](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springmessage/image-20200304085303580.png) - - -- 主要关注 +- 主要关注 1. **afterSingletonsInstantiated** + 2. **postProcessAfterInitialization** -#### afterSingletonsInstantiated -```JAVA +#### afterSingletonsInstantiated + +```java @Override public void afterSingletonsInstantiated() { // Remove resolved singleton classes from cache @@ -107,12 +108,12 @@ public class JmsBootstrapConfiguration { - 关注最后一行`this.registrar.afterPropertiesSet()` - ```JAVA + ```java @Override public void afterPropertiesSet() { registerAllEndpoints(); } - + protected void registerAllEndpoints() { Assert.state(this.endpointRegistry != null, "No JmsListenerEndpointRegistry set"); synchronized (this.mutex) { @@ -128,13 +129,9 @@ public class JmsBootstrapConfiguration { - 注册监听在下面分析会讲详见下文 - - - - #### postProcessAfterInitialization -```JAVA +```java @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof AopInfrastructureBean || bean instanceof JmsListenerContainerFactory || @@ -172,9 +169,7 @@ public class JmsBootstrapConfiguration { ``` - - -```JAVA +```java protected void processJmsListener(JmsListener jmsListener, Method mostSpecificMethod, Object bean) { Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass()); @@ -243,19 +238,17 @@ public class JmsBootstrapConfiguration { ``` - - - `org.springframework.jms.config.JmsListenerEndpointRegistry#registerListenerContainer(org.springframework.jms.config.JmsListenerEndpoint, org.springframework.jms.config.JmsListenerContainerFactory, boolean)` ```java public void registerListenerContainer(JmsListenerEndpoint endpoint, JmsListenerContainerFactory factory, boolean startImmediately) { - + Assert.notNull(endpoint, "Endpoint must not be null"); Assert.notNull(factory, "Factory must not be null"); String id = endpoint.getId(); Assert.hasText(id, "Endpoint id must be set"); - + synchronized (this.listenerContainers) { if (this.listenerContainers.containsKey(id)) { throw new IllegalStateException("Another endpoint is already registered with id '" + id + "'"); @@ -271,8 +264,6 @@ public class JmsBootstrapConfiguration { } ``` - - - `org.springframework.jms.config.JmsListenerEndpointRegistry#createListenerContainer` ```java @@ -309,15 +300,11 @@ public class JmsBootstrapConfiguration { ``` - - - - - 关键接口`JmsListenerContainerFactory` - ```JAVA + ```java public interface JmsListenerContainerFactory { - + /** * Create a {@link MessageListenerContainer} for the given {@link JmsListenerEndpoint}. * 创建肩痛容器 @@ -325,13 +312,11 @@ public class JmsBootstrapConfiguration { * @return the created container */ C createListenerContainer(JmsListenerEndpoint endpoint); - + } ``` - ![image-20200304092154712](../../../images/springmessage/image-20200304092154712.png) - - + ![image-20200304092154712](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springmessage/image-20200304092154712.png) - 注册完成后是否立即启动 @@ -341,25 +326,19 @@ public class JmsBootstrapConfiguration { // 启动消息监听容器 startIfNecessary(container); } - + private void startIfNecessary(MessageListenerContainer listenerContainer) { if (this.contextRefreshed || listenerContainer.isAutoStartup()) { listenerContainer.start(); } } - + ``` - - 具体实现: `org.springframework.jms.listener.AbstractJmsListeningContainer#start` + - 具体实现: `org.springframework.jms.listener.AbstractJmsListeningContainer#start` - 执行完`start`方法就结束了`processJmsListener`的调用链路, `postProcessAfterInitialization` 也结束了 - - - - - - ### JmsListenerEndpointRegistry - 这个类辅助**JmsListenerAnnotationBeanPostProcessor** 处理 @@ -408,4 +387,3 @@ public class JmsBootstrapConfiguration { } ``` - diff --git a/docs/Spring/message/Spring-JmsTemplate.md b/docs/Spring/message/Spring-JmsTemplate.md index e9f1a0da..e38058a7 100644 --- a/docs/Spring/message/Spring-JmsTemplate.md +++ b/docs/Spring/message/Spring-JmsTemplate.md @@ -1,10 +1,11 @@ # Spring JmsTemplate + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码路径: `org.springframework.jms.core.JmsTemplate` - ## 源码分析 + ### send 发送消息 ```java @@ -59,6 +60,7 @@ ``` - 最后`action.doInJms(sessionToUse)`的操作 + ```java Destination destination = resolveDestinationName(session, destinationName); doSend(session, destination, messageCreator); @@ -66,6 +68,7 @@ ``` - `doSend`真正做的发送方法 + ```java protected void doSend(Session session, Destination destination, MessageCreator messageCreator) throws JMSException { @@ -94,8 +97,10 @@ } ``` + 1. `createProducer`中通过`javax.jms.Session.createProducer`创建`MessageProducer`,第三方消息中间件独立实现 2. `createMessage` + ```java @Override public javax.jms.Message createMessage(Session session) throws JMSException { @@ -107,8 +112,11 @@ public javax.jms.Message createMessage(Session session) throws JMSException { } } ``` + - 消息转换后续在更新 + 3. `doSend` 这里也是第三方消息中间件实现 + ```java protected void doSend(MessageProducer producer, Message message) throws JMSException { if (this.deliveryDelay >= 0) { @@ -122,7 +130,9 @@ protected void doSend(MessageProducer producer, Message message) throws JMSExcep } } ``` + 4. `closeMessageProducer` 这个方法特别,直接关闭 + ```java public static void closeMessageProducer(@Nullable MessageProducer producer) { if (producer != null) { @@ -140,6 +150,7 @@ public static void closeMessageProducer(@Nullable MessageProducer producer) { ``` ### receive 接收消息 + ```java @Override @Nullable @@ -198,4 +209,4 @@ public static void closeMessageProducer(@Nullable MessageProducer producer) { } } -``` \ No newline at end of file +``` diff --git a/docs/Spring/message/Spring-MessageConverter.md b/docs/Spring/message/Spring-MessageConverter.md index fbf53b8b..68959a5f 100644 --- a/docs/Spring/message/Spring-MessageConverter.md +++ b/docs/Spring/message/Spring-MessageConverter.md @@ -1,41 +1,40 @@ # Spring MessageConverter + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) - 源码路径: `org.springframework.messaging.converter.MessageConverter` + ## MessageConverter + - 消息转换接口 - 类图如下 -![image-20200305085013723](../../../images/springmessage/image-20200305085013723.png) + ![image-20200305085013723](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springmessage/image-20200305085013723.png) - 两个方法 -1. fromMessage: 从消息转换到Object + +1. fromMessage: 从消息转换到 Object + ```java Object fromMessage(Message message, Class targetClass); ``` -2. toMessage: 从Object转换到消息 + +2. toMessage: 从 Object 转换到消息 + ```java Message toMessage(Object payload, @Nullable MessageHeaders headers); ``` - - - - -| 序号 | class | 作用 | -| ---- | ------------------------------- | -------------------- | -| 1 | ByteArrayMessageConverter | byte数组消息转换器 | -| 2 | MappingJackson2MessageConverter | jackson2的消息转换器 | -| 3 | MarshallingMessageConverter | xml的消息转换器 | -| 4 | StringMessageConverter | 字符串消息转换器 | - - - - +| 序号 | class | 作用 | +| ---- | ------------------------------- | --------------------- | +| 1 | ByteArrayMessageConverter | byte 数组消息转换器 | +| 2 | MappingJackson2MessageConverter | jackson2 的消息转换器 | +| 3 | MarshallingMessageConverter | xml 的消息转换器 | +| 4 | StringMessageConverter | 字符串消息转换器 | ## AbstractMessageConverter -类图: +类图: -![image-20200305085845017](../../../images/springmessage/image-20200305085845017.png) +![image-20200305085845017](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springmessage/image-20200305085845017.png) ### fromMessage @@ -68,7 +67,7 @@ - 真正的转换过程 -```JAVA +```java @Override protected Object convertFromInternal(Message message, Class targetClass, @Nullable Object conversionHint) { Charset charset = getContentTypeCharset(getMimeType(message.getHeaders())); @@ -78,11 +77,9 @@ ``` - - ### toMessage -```JAVA +```java @Override @Nullable public final Message toMessage(Object payload, @Nullable MessageHeaders headers) { @@ -91,9 +88,7 @@ ``` - - -```JAVA +```java @Override @Nullable public final Message toMessage(Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { @@ -133,12 +128,12 @@ - `org.springframework.messaging.converter.StringMessageConverter#convertToInternal` - ```JAVA + ```java @Override @Nullable protected Object convertToInternal( Object payload, @Nullable MessageHeaders headers, @Nullable Object conversionHint) { - + if (byte[].class == getSerializedPayloadClass()) { // 获取编码 Charset charset = getContentTypeCharset(getMimeType(headers)); @@ -147,14 +142,12 @@ } return payload; } - - ``` - + ``` - 创建**Message**对象 - ```JAVA + ```java @SuppressWarnings("unchecked") public static Message createMessage(@Nullable T payload, MessageHeaders messageHeaders) { Assert.notNull(payload, "Payload must not be null"); @@ -166,12 +159,10 @@ return new GenericMessage<>(payload, messageHeaders); } } - - ``` - + ``` - ```JAVA + ```java @SuppressWarnings("unchecked") public Message build() { if (this.originalMessage != null && !this.headerAccessor.isModified()) { @@ -185,13 +176,11 @@ return new GenericMessage<>(this.payload, headersToUse); } } - - ``` - + ``` - 两种创建方式基本相同,如果出现异常组装异常消息对象`ErrorMessage`,成功创建`GenericMessage` -![image-20200305090846313](../../../images/springmessage/image-20200305090846313.png) +![image-20200305090846313](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springmessage/image-20200305090846313.png) -从类图上看`ErrorMessage`是`GenericMessage`的子类 \ No newline at end of file +从类图上看`ErrorMessage`是`GenericMessage`的子类 diff --git a/docs/Spring/mvc/Spring-MVC-HandlerMapping.md b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md new file mode 100644 index 00000000..b70dac49 --- /dev/null +++ b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md @@ -0,0 +1,673 @@ +# Spring HandlerMapping + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [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; +// 其他静态变量省略 +} +``` + +![image](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/HandlerMapping.png) + +```java +@Override +@Nullable +public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { + // 转换成handler + Object handler = getHandlerInternal(request); + if (handler == null) { + // 获取默认的 handler + handler = getDefaultHandler(); + } + if (handler == null) { + return null; + } + // Bean name or resolved handler? + if (handler instanceof String) { + // handler 是beanName 直接从容器中获取 + String handlerName = (String) handler; + handler = obtainApplicationContext().getBean(handlerName); + } + + HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); + + if (logger.isTraceEnabled()) { + logger.trace("Mapped to " + handler); + } + else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) { + logger.debug("Mapped to " + executionChain.getHandler()); + } + + if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { + CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null); + CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); + config = (config != null ? config.combine(handlerConfig) : handlerConfig); + executionChain = getCorsHandlerExecutionChain(request, executionChain, config); + } + + return executionChain; +} +``` + +- `getHandlerInternal`方法是一个抽象方法 + + ```java + @Nullable + protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception; + ``` + + 存在的实现方法 + + ![image-20200915135933146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/image-20200915135933146.png) + +- 先看`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`方法是怎么一回事. + +```java + @Override + protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { + // 获取当前请求路径 + String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); + // 设置属性 + request.setAttribute(LOOKUP_PATH, lookupPath); + // 上锁 + this.mappingRegistry.acquireReadLock(); + try { + // 寻找 handler method + HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); + return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); + } + finally { + // 释放锁 + this.mappingRegistry.releaseReadLock(); + } + } + +``` + +## UrlPathHelper + +- 全路径:`org.springframework.web.util.UrlPathHelper` + +- 几个属性 + + ```java + /** + * 是否全路径标记 + */ + 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 + String contextPath = getContextPath(request); + // 获取 uri + String requestUri = getRequestUri(request); + String path = getRemainingPath(requestUri, contextPath, true); + if (path != null) { + // Normal case: URI contains context path. + return (StringUtils.hasText(path) ? path : "/"); + } + else { + return requestUri; + } +} +``` + +1. 从 request 中获取 context-path + 1. 从属性中直接获取 + 2. 从 request 中调用 getContextPath 获取 + 3. 判断是否是**`/`** + 4. decode request string +2. 从 request 中虎丘 request-uri + 1. 从属性中获取 + 2. 从 request 中调用 getRequestURI 获取 + 3. decode +3. 获取剩余路径 + +### getContextPath + +- 获取 context-path 地址 + +```java +public String getContextPath(HttpServletRequest request) { + // 从 request 获取 context path + String contextPath = (String) request.getAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE); + if (contextPath == null) { + contextPath = request.getContextPath(); + } + if ("/".equals(contextPath)) { + // Invalid case, but happens for includes on Jetty: silently adapt it. + contextPath = ""; + } + // decode context path + return decodeRequestString(request, contextPath); +} +``` + +### decodeRequestString + +- 判断是否需要编码, 需要编码就做编码操作,不需要就直接返回 + +```java +public String decodeRequestString(HttpServletRequest request, String source) { + // 判断是否需要编码 + if (this.urlDecode) { + // 进行编码 + return decodeInternal(request, source); + } + return source; +} +``` + +### decodeInternal + +- 编码方法 + +```java +@SuppressWarnings("deprecation") +private String decodeInternal(HttpServletRequest request, String source) { + // 确定编码方式 + String enc = determineEncoding(request); + try { + // 将 source 编译成 enc 的编码方式 + return UriUtils.decode(source, enc); + } + catch (UnsupportedCharsetException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Could not decode request string [" + source + "] with encoding '" + enc + + "': falling back to platform default encoding; exception message: " + ex.getMessage()); + } + // 直接编码,JDK底层编码 + return URLDecoder.decode(source); + } +} +``` + +### determineEncoding + +- 确认编码 + +```java +protected String determineEncoding(HttpServletRequest request) { + // 从 request 中获取编码方式 + String enc = request.getCharacterEncoding(); + if (enc == null) { + // 默认编码 + enc = getDefaultEncoding(); + } + return enc; +} +``` + +### getRequestUri + +- 获取 uri 地址 + +```java + public String getRequestUri(HttpServletRequest request) { + // 从属性中获取 + String uri = (String) request.getAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE); + if (uri == null) { + // 调用方法获取 + uri = request.getRequestURI(); + } + //编码和清理数据 + return decodeAndCleanUriString(request, uri); + } + +``` + +### decodeAndCleanUriString + +- 编码和清理数据 + +```java +private String decodeAndCleanUriString(HttpServletRequest request, String uri) { + // 去掉分号 + uri = removeSemicolonContent(uri); + // decoding + uri = decodeRequestString(request, uri); + // 去掉 // 双斜杠 + uri = getSanitizedPath(uri); + return uri; +} +``` + +### shouldRemoveTrailingServletPathSlash + +- 是否删除 servlet path 后的斜杠 + +- 默认是 false . +- 代码流程 + 1. 通过 classLoader 加载 `"com.ibm.ws.webcontainer.WebContainer"` + 2. 调用方法 `"getWebContainerProperties"` + 3. 从方法结果中取`"getWebContainerProperties"` + +```java +private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request) { + if (request.getAttribute(WEBSPHERE_URI_ATTRIBUTE) == null) { + // Regular servlet container: behaves as expected in any case, + // so the trailing slash is the result of a "/" url-pattern mapping. + // Don't remove that slash. + return false; + } + Boolean flagToUse = websphereComplianceFlag; + if (flagToUse == null) { + ClassLoader classLoader = UrlPathHelper.class.getClassLoader(); + String className = "com.ibm.ws.webcontainer.WebContainer"; + String methodName = "getWebContainerProperties"; + String propName = "com.ibm.ws.webcontainer.removetrailingservletpathslash"; + boolean flag = false; + try { + Class cl = classLoader.loadClass(className); + Properties prop = (Properties) cl.getMethod(methodName).invoke(null); + flag = Boolean.parseBoolean(prop.getProperty(propName)); + } + catch (Throwable ex) { + if (logger.isDebugEnabled()) { + logger.debug("Could not introspect WebSphere web container properties: " + ex); + } + } + flagToUse = flag; + websphereComplianceFlag = flag; + } + // Don't bother if WebSphere is configured to be fully Servlet compliant. + // However, if it is not compliant, do remove the improper trailing slash! + return !flagToUse; +} +``` + +### decodeMatrixVariables + +- 编码修改方法 + +```java +public MultiValueMap decodeMatrixVariables( + HttpServletRequest request, MultiValueMap vars) { + + // 判断是否需要重写编码 + if (this.urlDecode) { + return vars; + } + else { + // 需要重写编码的情况 + MultiValueMap decodedVars = new LinkedMultiValueMap<>(vars.size()); + // 循环, 将 value 调用decodeInternal写到结果map返回 + vars.forEach((key, values) -> { + for (String value : values) { + decodedVars.add(key, decodeInternal(request, value)); + } + }); + return decodedVars; + } +} +``` + +- 与这个方法对应的还有`decodePathVariables` + +### decodePathVariables + +```java +public Map decodePathVariables(HttpServletRequest request, Map vars) { + // 判断是否需要重写编码 + if (this.urlDecode) { + return vars; + } + else { + Map decodedVars = new LinkedHashMap<>(vars.size()); + // 虚幻 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 { + List matches = new ArrayList<>(); + // 从 MultiValueMap 获取 + List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); + // 如果不为空 + if (directPathMatches != null) { + // 添加匹配映射 + addMatchingMappings(directPathMatches, matches, request); + } + if (matches.isEmpty()) { + // No choice but to go through all mappings... + // 添加匹配映射 + addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); + } + + //... +} +``` + +- 创建一个匹配 list,将匹配结果放入 + + ``` + List matches = new ArrayList<>(); + ``` + +- 从 map 中获取数据 + + ``` + List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); + ``` + + ```java + @Nullable + public List getMappingsByUrl(String urlPath) { + return this.urlLookup.get(urlPath); + } + ``` + + urlLookup 是`MultiValueMap`接口. + + key:url value:mapping + +- addMatchingMappings 方法 + + ```java + if (directPathMatches != null) { + // 添加匹配映射 + addMatchingMappings(directPathMatches, matches, request); + } + if (matches.isEmpty()) { + // No choice but to go through all mappings... + // 添加匹配映射 + addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request); + } + ``` + + ```java + private void addMatchingMappings(Collection mappings, List matches, HttpServletRequest request) { + for (T mapping : mappings) { + // 抽象方法 + // 通过抽象方法获取 match 结果 + T match = getMatchingMapping(mapping, request); + // 是否为空 + if (match != null) { + // 从 mappingLookup 获取结果并且插入到matches中 + matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping))); + } + } + } + ``` + +- `getMatchingMapping` 方法是一个抽象方法 + + ```java + protected abstract T getMatchingMapping(T mapping, HttpServletRequest request); + ``` + +- `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#getMatchingMapping` + + ```java + @Override + protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) { + return info.getMatchingCondition(request); + } + ``` + +- 第二部分 + +```java +if (!matches.isEmpty()) { + // 比较对象 + Comparator comparator = new MatchComparator(getMappingComparator(request)); + // 排序 + matches.sort(comparator); + // 获取第一个 match 对象 + Match bestMatch = matches.get(0); + if (matches.size() > 1) { + if (logger.isTraceEnabled()) { + logger.trace(matches.size() + " matching mappings: " + matches); + } + + if (CorsUtils.isPreFlightRequest(request)) { + return PREFLIGHT_AMBIGUOUS_MATCH; + } + Match secondBestMatch = matches.get(1); + if (comparator.compare(bestMatch, secondBestMatch) == 0) { + // 拿出 handlerMethod 进行比较 + Method m1 = bestMatch.handlerMethod.getMethod(); + Method m2 = secondBestMatch.handlerMethod.getMethod(); + String uri = request.getRequestURI(); + throw new IllegalStateException( + "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); + } + } + request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); + handleMatch(bestMatch.mapping, lookupPath, request); + return bestMatch.handlerMethod; +} +else { + return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); +} +``` + +- 一行行开始分析 + +```java +Comparator comparator = new MatchComparator(getMappingComparator(request)); +``` + +- 抽象方法`getMappingComparator` + +```java +protected abstract Comparator getMappingComparator(HttpServletRequest request); +``` + +- 实现方法 + + ```java + @Override + protected Comparator getMappingComparator(final HttpServletRequest request) { + return (info1, info2) -> info1.compareTo(info2, request); + } + ``` + + 内部定义了 compareTo 方法 + +- 执行完成比较方法后创建对象`MatchComparator` +- 对象创建后进行排序,排序后取出第一个元素作为后续操作的基准对象 + +```java +// 排序 +matches.sort(comparator); +// 获取第一个 match 对象 +Match bestMatch = matches.get(0); +``` + +```java +if (matches.size() > 1) { + if (logger.isTraceEnabled()) { + logger.trace(matches.size() + " matching mappings: " + matches); + } + + // 是否跨域请求 + if (CorsUtils.isPreFlightRequest(request)) { + return PREFLIGHT_AMBIGUOUS_MATCH; + } + // 取出第二个元素. + Match secondBestMatch = matches.get(1); + // 如果比较结果相同 + if (comparator.compare(bestMatch, secondBestMatch) == 0) { + // 第二个元素和第一个元素的比较过程 + // 拿出 handlerMethod 进行比较 + Method m1 = bestMatch.handlerMethod.getMethod(); + Method m2 = secondBestMatch.handlerMethod.getMethod(); + String uri = request.getRequestURI(); + throw new IllegalStateException( + "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); + } +} +``` + +- 取出第一个元素和第二个元素进行比较. 如果两个 match 相同, 出现异常 + +最后两个方法 + +```java + // 设置属性 + request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod); + // 处理匹配的结果 + handleMatch(bestMatch.mapping, lookupPath, request); + return bestMatch.handlerMethod; +} +else { + // 处理没有匹配的结果 + return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); +} +``` + +- `handleMatch` + + ```java + protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) { + request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath); + } + ``` + + 设置一次属性 + + 这个方法子类会继续实现 + + - `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleMatch` + +```java +@Override +protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) { + super.handleMatch(info, lookupPath, request); + + String bestPattern; + Map uriVariables; + + // 匹配器 + Set patterns = info.getPatternsCondition().getPatterns(); + // 如果空设置基本数据 + if (patterns.isEmpty()) { + bestPattern = lookupPath; + uriVariables = Collections.emptyMap(); + } + else { + // 取出一个匹配器 + bestPattern = patterns.iterator().next(); + + // 地址匹配器比较 路由地址和匹配器比较 + uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath); + } + + request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern); + + if (isMatrixVariableContentAvailable()) { + // 处理多层参数, 带有;分号的处理 + Map> matrixVars = extractMatrixVariables(request, uriVariables); + request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars); + } + + // 编码url参数 + Map decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables); + + if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) { + // 获取 media type + Set mediaTypes = info.getProducesCondition().getProducibleMediaTypes(); + request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes); + } +} +``` + +- `handleNoMatch` 也是同类型操作 + - `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#handleNoMatch` + - `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping#handleNoMatch` + +```java +@Override +protected HandlerMethod handleNoMatch( + Set infos, String lookupPath, HttpServletRequest request) throws ServletException { + + // 创建对象 PartialMatchHelper + PartialMatchHelper helper = new PartialMatchHelper(infos, request); + if (helper.isEmpty()) { + return null; + } + + // 函数是否匹配 + if (helper.hasMethodsMismatch()) { + Set methods = helper.getAllowedMethods(); + // 请求方式比较 + if (HttpMethod.OPTIONS.matches(request.getMethod())) { + // handler 转换 + HttpOptionsHandler handler = new HttpOptionsHandler(methods); + // 构建 handler method + return new HandlerMethod(handler, HTTP_OPTIONS_HANDLE_METHOD); + } + throw new HttpRequestMethodNotSupportedException(request.getMethod(), methods); + } + + if (helper.hasConsumesMismatch()) { + Set mediaTypes = helper.getConsumableMediaTypes(); + MediaType contentType = null; + if (StringUtils.hasLength(request.getContentType())) { + try { + // 字符串转换成对象 + contentType = MediaType.parseMediaType(request.getContentType()); + } + catch (InvalidMediaTypeException ex) { + throw new HttpMediaTypeNotSupportedException(ex.getMessage()); + } + } + throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes)); + } + + if (helper.hasProducesMismatch()) { + Set mediaTypes = helper.getProducibleMediaTypes(); + throw new HttpMediaTypeNotAcceptableException(new ArrayList<>(mediaTypes)); + } + + if (helper.hasParamsMismatch()) { + List conditions = helper.getParamConditions(); + throw new UnsatisfiedServletRequestParameterException(conditions, request.getParameterMap()); + } + + return null; +} +``` diff --git a/docs/Spring/mvc/Spring-mvc-MappingRegistry.md b/docs/Spring/mvc/Spring-mvc-MappingRegistry.md new file mode 100644 index 00000000..e57e6ca7 --- /dev/null +++ b/docs/Spring/mvc/Spring-mvc-MappingRegistry.md @@ -0,0 +1,305 @@ +# MappingRegistry + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring](https://github.com/SourceHot/spring-framework-read) +- 源码路径: `org.springframework.jms.annotation.EnableJms` + +- 类全路径 +- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry` +- 基本属性 + + ```java + class MappingRegistry { + + /** + * key:mapping + * value: mapping registration + */ + private final Map> registry = new HashMap<>(); + + /** + * key: mapping + * value: handlerMethod + */ + private final Map mappingLookup = new LinkedHashMap<>(); + + /** + * key: url + * value: list mapping + */ + private final MultiValueMap urlLookup = new LinkedMultiValueMap<>(); + + /** + * key: name + * value: handler method + */ + private final Map> nameLookup = new ConcurrentHashMap<>(); + + /** + * key:handler method + * value: 跨域配置 + */ + private final Map corsLookup = new ConcurrentHashMap<>(); + + /** + * 读写锁 + */ + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + } + ``` + +- 写一个简单的 controller 来进行解析 + +```java +@RestController +@RequestMapping("/demo") +public class DemoController { + @GetMapping("/do") + public Object go() { + return "fff"; + } +} +``` + +- 前置链路追踪 + + - `org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#registerHandlerMethod` + + ```java + protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { + super.registerHandlerMethod(handler, method, mapping); + this.updateConsumesCondition(mapping, method); + } + ``` + + - `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#registerHandlerMethod` + + ```java + protected void registerHandlerMethod(Object handler, Method method, T mapping) { + this.mappingRegistry.register(mapping, handler, method); + } + ``` + + - `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register` + + 本文重点的方法 + +先将对象截图出来方便后续理解 + +![image-20200918130340555](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200918130340555.png) + +## createHandlerMethod + +- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#createHandlerMethod` + +```java +protected HandlerMethod createHandlerMethod(Object handler, Method method) { + // 是否是字符串 + if (handler instanceof String) { + // 创建对象 + return new HandlerMethod((String) handler, + obtainApplicationContext().getAutowireCapableBeanFactory(), method); + } + return new HandlerMethod(handler, method); +} +``` + +- HandlerMethod 构造函数 + + ```java + public HandlerMethod(String beanName, BeanFactory beanFactory, Method method){} + + public HandlerMethod(Object bean, Method method) {} + ``` + +## HandlerMethod + +- 成员变量 + +```java +public class HandlerMethod { + + /** Logger that is available to subclasses. */ + protected final Log logger = LogFactory.getLog(getClass()); + + /** + * beanName 或者 bean 实例 + */ + private final Object bean; + + /** + * 上下文 + */ + @Nullable + private final BeanFactory beanFactory; + + /** + * bean 类型 + */ + private final Class beanType; + + /** + * 处理方法 + */ + private final Method method; + + private final Method bridgedMethod; + + /** + * 方法参数 + */ + private final MethodParameter[] parameters; +} +``` + +## validateMethodMapping + +- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#validateMethodMapping` + +HandlerMethod 进行验证 + +```java +private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) { + // Assert that the supplied mapping is unique. + // 从缓存中获取 + HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping); + // 是否为空 , 是否相同 + if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) { + throw new IllegalStateException( + "Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" + + handlerMethod + "\nto " + mapping + ": There is already '" + + existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped."); + } +} +``` + +## getDirectUrls + +- 找到 mapping 匹配的 url + +- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getDirectUrls` + +```java +private List getDirectUrls(T mapping) { + List urls = new ArrayList<>(1); + // mapping.getPatternsCondition().getPatterns() + for (String path : getMappingPathPatterns(mapping)) { + // 是否匹配 + if (!getPathMatcher().isPattern(path)) { + urls.add(path); + } + } + return urls; +} +``` + +## handlerMethod 和 name 绑定 + +```java +String name = null; +if (getNamingStrategy() != null) { + // 获取名字 + // 类名#方法名 + name = getNamingStrategy().getName(handlerMethod, mapping); + // 设置 handlerMethod + name 的关系 + addMappingName(name, handlerMethod); +} +``` + +- `org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrategy#getName` + +```java +@Override +public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) { + if (mapping.getName() != null) { + return mapping.getName(); + } + StringBuilder sb = new StringBuilder(); + // 短类名 + String simpleTypeName = handlerMethod.getBeanType().getSimpleName(); + for (int i = 0; i < simpleTypeName.length(); i++) { + if (Character.isUpperCase(simpleTypeName.charAt(i))) { + sb.append(simpleTypeName.charAt(i)); + } + } + // 组装名称 + // 类名+#+方法名称 + sb.append(SEPARATOR).append(handlerMethod.getMethod().getName()); + return sb.toString(); +} +``` + +## initCorsConfiguration + +- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initCorsConfiguration` + +```java +@Override +protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) { + // 创建 handlerMethod + HandlerMethod handlerMethod = createHandlerMethod(handler, method); + // 获取 beanType + Class beanType = handlerMethod.getBeanType(); + // 获取跨域注解 CrossOrigin + CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class); + CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class); + + if (typeAnnotation == null && methodAnnotation == null) { + return null; + } + + // 跨域信息配置 + CorsConfiguration config = new CorsConfiguration(); + // 更新跨域配置 + updateCorsConfig(config, typeAnnotation); + updateCorsConfig(config, methodAnnotation); + + if (CollectionUtils.isEmpty(config.getAllowedMethods())) { + // 跨域配置赋给方法 + for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) { + config.addAllowedMethod(allowedMethod.name()); + } + } + // 应用跨域 + return config.applyPermitDefaultValues(); +} +``` + +## unregister + +- `org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#unregister` + + 移除 mapping 信息 + +- 执行 map , list 相关的移除方法. + +```java +public void unregister(T mapping) { + this.readWriteLock.writeLock().lock(); + try { + MappingRegistration definition = this.registry.remove(mapping); + if (definition == null) { + return; + } + + this.mappingLookup.remove(definition.getMapping()); + + for (String url : definition.getDirectUrls()) { + List list = this.urlLookup.get(url); + if (list != null) { + list.remove(definition.getMapping()); + if (list.isEmpty()) { + this.urlLookup.remove(url); + } + } + } + + removeMappingName(definition); + + this.corsLookup.remove(definition.getHandlerMethod()); + } + finally { + this.readWriteLock.writeLock().unlock(); + } +} +``` diff --git a/docs/SpringBoot/Spring-Boot-Run.md b/docs/SpringBoot/Spring-Boot-Run.md index 08436bb9..a989fa17 100644 --- a/docs/SpringBoot/Spring-Boot-Run.md +++ b/docs/SpringBoot/Spring-Boot-Run.md @@ -1,8 +1,12 @@ # SpringBoot 启动方法 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) + ## 入口 -- 通常一个简单的SpringBoot基础项目我们会有如下代码 + +- 通常一个简单的 SpringBoot 基础项目我们会有如下代码 + ```java @SpringBootApplication @RestController @@ -18,7 +22,9 @@ public class Application { ``` - 值得关注的有`SpringApplication.run`以及注解`@SpringBootApplication` -### run方法 + +### run 方法 + ```java public ConfigurableApplicationContext run(String... args) { // 秒表 @@ -79,8 +85,6 @@ public class Application { - 获取监听器 - - ```java private SpringApplicationRunListeners getRunListeners(String[] args) { Class[] types = new Class[] { SpringApplication.class, String[].class }; @@ -107,69 +111,63 @@ public class Application { ### createSpringFactoriesInstances - ```java - @SuppressWarnings("unchecked") - private List createSpringFactoriesInstances(Class type, Class[] parameterTypes, - ClassLoader classLoader, Object[] args, Set names) { - // 初始化 - List instances = new ArrayList<>(names.size()); - for (String name : names) { - try { - // 通过名字创建类的class对象 - Class instanceClass = ClassUtils.forName(name, classLoader); - Assert.isAssignable(type, instanceClass); - // 构造器获取 - Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes); - // 创建具体实例 - T instance = (T) BeanUtils.instantiateClass(constructor, args); - // 加入实例表中 - instances.add(instance); - } - catch (Throwable ex) { - throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); - } +```java +@SuppressWarnings("unchecked") +private List createSpringFactoriesInstances(Class type, Class[] parameterTypes, + ClassLoader classLoader, Object[] args, Set names) { + // 初始化 + List instances = new ArrayList<>(names.size()); + for (String name : names) { + try { + // 通过名字创建类的class对象 + Class instanceClass = ClassUtils.forName(name, classLoader); + Assert.isAssignable(type, instanceClass); + // 构造器获取 + Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes); + // 创建具体实例 + T instance = (T) BeanUtils.instantiateClass(constructor, args); + // 加入实例表中 + instances.add(instance); + } + catch (Throwable ex) { + throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } - return instances; } + return instances; +} - ``` +``` -- `SpringFactoriesLoader.loadFactoryNames(type, classLoader)` 是spring提供的方法,主要目的是读取`spring.factories`文件 +- `SpringFactoriesLoader.loadFactoryNames(type, classLoader)` 是 spring 提供的方法,主要目的是读取`spring.factories`文件 - 读取需要创建的内容 -![image-20200318080601725](../../../images/SpringBoot/image-20200318080601725.png) +![image-20200318080601725](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318080601725.png) - 创建完成 - ![image-20200318080901881](../../../images/SpringBoot/image-20200318080901881.png) - - + ![image-20200318080901881](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318080901881.png) - `AnnotationAwareOrderComparator.sort(instances)`排序 - - 通过spring的源码我们知道这个方法是根据`order`的数字大小进行排序,观察 + - 通过 spring 的源码我们知道这个方法是根据`order`的数字大小进行排序,观察 `SharedMetadataReaderFactoryContextInitializer` - ![image-20200318081112670](../../../images/SpringBoot/image-20200318081112670.png) + ![image-20200318081112670](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081112670.png) - 同样的再找一个`DelegatingApplicationContextInitializer` - ![image-20200318081322781](../../../images/SpringBoot/image-20200318081322781.png) - -- 下图中的所有类都有Order数值返回 + ![image-20200318081322781](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081322781.png) - 排序前: - - ![image-20200318081352639](../../../images/SpringBoot/image-20200318081352639.png) - - 排序后: - -![image-20200318081458019](../../../images/SpringBoot/image-20200318081458019.png) +- 下图中的所有类都有 Order 数值返回 + 排序前: +![image-20200318081352639](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081352639.png) +排序后: +![image-20200318081458019](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081458019.png) ### listeners.starting() @@ -177,28 +175,24 @@ public class Application { ``` class SpringApplicationRunListeners { - + private final List listeners; SpringApplicationRunListeners(Log log, Collection listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } - + void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } } - + } ``` - 这里主要是启动`org.springframework.boot.SpringApplicationRunListener#starting`方法,只有一个实现`org.springframework.boot.context.event.EventPublishingRunListener#starting` - - - - ### prepareEnvironment ```java @@ -224,8 +218,6 @@ public class Application { ``` - - ### configureIgnoreBeanInfo - 获取`spring.beaninfo.ignore`并且设置到环境信息中 @@ -239,10 +231,6 @@ public class Application { } ``` - - - - ### printBanner ```java @@ -275,18 +263,18 @@ public class Application { - 最终输出内容类:`org.springframework.boot.SpringBootBanner` - ```JAVA + ```java class SpringBootBanner implements Banner { - + private static final String[] BANNER = { "", " . ____ _ __ _ _", " /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\", " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /", " =========|_|==============|___/=/_/_/_/" }; - + private static final String SPRING_BOOT = " :: Spring Boot :: "; - + private static final int STRAP_LINE_SIZE = 42; - + @Override public void printBanner(Environment environment, Class sourceClass, PrintStream printStream) { for (String line : BANNER) { @@ -298,18 +286,18 @@ public class Application { while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) { padding.append(" "); } - + printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding.toString(), AnsiStyle.FAINT, version)); printStream.println(); } - + } ``` ### createApplicationContext -```JAVA +```java protected ConfigurableApplicationContext createApplicationContext() { // 获取上下文类 Class contextClass = this.applicationContextClass; @@ -337,11 +325,9 @@ public class Application { ``` - - - `this.applicationContextClass` 初始化方法 -```JAVA +```java public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); @@ -356,7 +342,7 @@ public class Application { - `org.springframework.boot.WebApplicationType#deduceFromClasspath` -```JAVA +```java static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { @@ -372,21 +358,13 @@ public class Application { ``` - - - - ### exceptionReporters -![image-20200318085243888](../../../images/SpringBoot/image-20200318085243888.png) - - - - +![image-20200318085243888](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318085243888.png) ### prepareContext -```JAVA +```java private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 上下文中设置环境 @@ -428,8 +406,6 @@ public class Application { - `set`方法就不说了 - - ### postProcessApplicationContext ```java @@ -457,23 +433,15 @@ public class Application { ``` -- 看一下最终设置完成后的context +- 看一下最终设置完成后的 context ```java context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); ``` - - -![image-20200318090128983](../../../images/SpringBoot/image-20200318090128983.png) - -![image-20200318090312626](../../../images/SpringBoot/image-20200318090312626.png) - - - - - +![image-20200318090128983](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090128983.png) +![image-20200318090312626](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090312626.png) ### applyInitializers @@ -492,28 +460,16 @@ public class Application { ``` +- 初始化 `List> listeners`: `setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));` - -- 初始化 `List> listeners`: `setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));` - - - -- 获取 `List> listeners`: `public Set> getListeners() { return asUnmodifiableOrderedSet(this.listeners);}` - - +- 获取 `List> listeners`: `public Set> getListeners() { return asUnmodifiableOrderedSet(this.listeners);}` - 数据结果 -![image-20200318090935285](../../../images/SpringBoot/image-20200318090935285.png) +![image-20200318090935285](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090935285.png) - 子类的具体实现不展开了 - - - - - - ### getAllSources ```java @@ -530,21 +486,13 @@ public class Application { ``` - - - `primarySources` 就是我们的项目启动类,在`SpringApplication`的构造器中有`this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources))` -![image-20200318091558233](../../../images/SpringBoot/image-20200318091558233.png) - - - - - - +![image-20200318091558233](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318091558233.png) ### load -- 加载bean到应用上下文 +- 加载 bean 到应用上下文 ```java protected void load(ApplicationContext context, Object[] sources) { @@ -571,8 +519,6 @@ public class Application { ``` - - ```java int load() { int count = 0; @@ -584,8 +530,6 @@ public class Application { ``` - - ```java private int load(Object source) { Assert.notNull(source, "Source must not be null"); @@ -606,11 +550,9 @@ private int load(Object source) { ``` -- 通过前文我们已经知道 `source`就是一个class - - ![image-20200318092027020](../../../images/SpringBoot/image-20200318092027020.png) - +- 通过前文我们已经知道 `source`就是一个 class + ![image-20200318092027020](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318092027020.png) ```java private int load(Class source) { @@ -629,26 +571,16 @@ private int load(Object source) { ``` -- 我们的启动类是一个组件,直接注册完成返回1 - - - - +- 我们的启动类是一个组件,直接注册完成返回 1 ### listeners.contextLoaded(context) - 监听器行为: 在上下文资源加载后做一些事情 - - - - ### refreshContext - 上下文刷新 - - ```java private void refreshContext(ConfigurableApplicationContext context) { refresh(context); @@ -675,13 +607,7 @@ private int load(Object source) { } ``` - - -- 最终来到了`org.springframework.context.support.AbstractApplicationContext#refresh`方法,此方法是spring的一个方法,此处不在阐述 - - - - +- 最终来到了`org.springframework.context.support.AbstractApplicationContext#refresh`方法,此方法是 spring 的一个方法,此处不在阐述 ### afterRefresh @@ -690,33 +616,22 @@ private int load(Object source) { ``` protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { } - - ``` - - - + ``` ### stopWatch.stop() - 秒表结束 - - ### listeners.started(context) - 各类监听器启动 - - - ### callRunners -- 两种runner启动`ApplicationRunner` 和 `CommandLineRunner` - - +- 两种 runner 启动`ApplicationRunner` 和 `CommandLineRunner` -```JAVA +```java private void callRunners(ApplicationContext context, ApplicationArguments args) { List runners = new ArrayList<>(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); @@ -734,11 +649,7 @@ private int load(Object source) { ``` - - - - -```JAVA +```java private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { (runner).run(args); @@ -749,12 +660,6 @@ private void callRunner(ApplicationRunner runner, ApplicationArguments args) { } ``` - - - - - - ### listeners.running(context) -- 监听器正式开始工作 \ No newline at end of file +- 监听器正式开始工作 diff --git a/docs/SpringBoot/SpringBoot-ConditionalOnBean.md b/docs/SpringBoot/SpringBoot-ConditionalOnBean.md new file mode 100644 index 00000000..f816a150 --- /dev/null +++ b/docs/SpringBoot/SpringBoot-ConditionalOnBean.md @@ -0,0 +1,599 @@ +# SpringBoot ConditionalOnBean + +- Author: [HuiFer](https://github.com/huifer) +- 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) + +- 在 SpringBoot 中有下列当 XXX 存在或不存的时候执行初始化 + - ConditionalOnBean + ConditionalOnClass + ConditionalOnCloudPlatform + ConditionalOnExpression + ConditionalOnJava + ConditionalOnJndi + ConditionalOnMissingBean + ConditionalOnMissingClass + ConditionalOnNotWebApplication + ConditionalOnProperty + ConditionalOnResource + ConditionalOnSingleCandidate + ConditionalOnWebApplication + +## ConditionalOnBean + +```java +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Conditional(OnBeanCondition.class) +public @interface ConditionalOnBean { + + /** + * 需要匹配的 bean 类型 + */ + Class[] value() default {}; + + /** + * 需要匹配的 bean 类型 + */ + String[] type() default {}; + + /** + * 匹配的 bean 注解 + */ + Class[] annotation() default {}; + + /** + * 需要匹配的 beanName + */ + String[] name() default {}; + + /** + * 搜索策略 + */ + SearchStrategy search() default SearchStrategy.ALL; + + /** + */ + Class[] parameterizedContainer() default {}; + +} +``` + +## SearchStrategy + +```java +public enum SearchStrategy { + + /** + * 当前 上下文 + */ + CURRENT, + + /** + * 找所有的父容器 + */ + ANCESTORS, + + /** + * 当前上下文+父容器 + */ + ALL + +} +``` + +## OnBeanCondition + +- org.springframework.boot.autoconfigure.condition.OnBeanCondition + +- 这个类是一个条件类,相关的还有 + + ```properties + org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ + org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ + org.springframework.boot.autoconfigure.condition.OnClassCondition,\ + org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition + ``` + +- 类图 + + ![image-20200824085726621](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200824085726621.png) + +在看这部分源码之前需要先了解 `Conditional`和`Condition`的源码 + +- 简单描述 + + 通过实现`Condition` 来确认是否初始化 bean + +- 从类图上我们可以看到 `condition` 的继承关系. 在这里需要去找到`SpringBootCondition` + +- `org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)` + + ```java + @Override + public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + // 类名或者方法名标记 + String classOrMethodName = getClassOrMethodName(metadata); + try { + // 比较类,子类实现 + ConditionOutcome outcome = getMatchOutcome(context, metadata); + // 日志输出 + logOutcome(classOrMethodName, outcome); + // 报告记录 + recordEvaluation(context, classOrMethodName, outcome); + // 返回匹配结果 + return outcome.isMatch(); + } + catch (NoClassDefFoundError ex) { + throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + + ex.getMessage() + " not found. Make sure your own configuration does not rely on " + + "that class. This can also happen if you are " + + "@ComponentScanning a springframework package (e.g. if you " + + "put a @ComponentScan in the default package by mistake)", ex); + } + catch (RuntimeException ex) { + throw new IllegalStateException("Error processing condition on " + getName(metadata), ex); + } + } + ``` + +- `getOutcomes` 子类实现 + + `org.springframework.boot.autoconfigure.condition.OnBeanCondition#getOutcomes` + + ```java + String[] autoConfigurationClasses, + AutoConfigurationMetadata autoConfigurationMetadata + ``` + + - 第一个参数: 需要自动配置的类 + - 配置注解信息 + +### ConditionOutcome 和 ConditionMessage + +```java +public class ConditionOutcome { + + /** + * 是否匹配 + */ + private final boolean match; + /** + * 条件信息 + */ + private final ConditionMessage message; +} + + +public final class ConditionMessage { + + private String message; +} +``` + +- 造一个对象用来进行 debug + +```java + +@Component +public class Beans { + + + @Bean + public A a() { + return new A(); + } + + + @Bean + @ConditionalOnBean(value = A.class) + public B b() { + return new B(); + } +} + +``` + +## getMatchOutcome + +```java +@Override +public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + // 条件信息 + ConditionMessage matchMessage = ConditionMessage.empty(); + // 获取注解求和 + MergedAnnotations annotations = metadata.getAnnotations(); + // 注解是否匹配 + if (annotations.isPresent(ConditionalOnBean.class)) { + // 搜索 ConditionalOnBean 注解 + Spec spec = new Spec<>(context, metadata, annotations, + ConditionalOnBean.class); + // 匹配结果 + MatchResult matchResult = getMatchingBeans(context, spec); + if (!matchResult.isAllMatched()) { + String reason = createOnBeanNoMatchReason(matchResult); + return ConditionOutcome.noMatch(spec.message().because(reason)); + } + // 把注解解析出来获得文本 + matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE, + matchResult.getNamesOfAllMatches()); + } + if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { + Spec spec = new SingleCandidateSpec(context, metadata, annotations); + MatchResult matchResult = getMatchingBeans(context, spec); + if (!matchResult.isAllMatched()) { + return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll()); + } + else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(), + spec.getStrategy() == SearchStrategy.ALL)) { + return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans") + .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); + } + matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE, + matchResult.getNamesOfAllMatches()); + } + if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { + Spec spec = new Spec<>(context, metadata, annotations, + ConditionalOnMissingBean.class); + MatchResult matchResult = getMatchingBeans(context, spec); + if (matchResult.isAnyMatched()) { + String reason = createOnMissingBeanNoMatchReason(matchResult); + return ConditionOutcome.noMatch(spec.message().because(reason)); + } + matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll(); + } + return ConditionOutcome.match(matchMessage); +} +``` + +- 开始方法分析 + +### getMatchingBeans + +- `org.springframework.boot.autoconfigure.condition.OnBeanCondition#getMatchingBeans` + +```java +protected final MatchResult getMatchingBeans(ConditionContext context, Spec spec) { + // 获取上下文 + ClassLoader classLoader = context.getClassLoader(); + // 获取 IOC 容器 + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + // 扫描方式比较是否为当前上下文 + boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT; + + Set> parameterizedContainers = spec.getParameterizedContainers(); + if (spec.getStrategy() == SearchStrategy.ANCESTORS) { + BeanFactory parent = beanFactory.getParentBeanFactory(); + Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, + "Unable to use SearchStrategy.ANCESTORS"); + beanFactory = (ConfigurableListableBeanFactory) parent; + } + // 结果对象初始化 + MatchResult result = new MatchResult(); + Set beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy, + spec.getIgnoredTypes(), parameterizedContainers); + for (String type : spec.getTypes()) { + // 通过类型获取 beanName + Collection typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, + parameterizedContainers); + typeMatches.removeAll(beansIgnoredByType); + if (typeMatches.isEmpty()) { + result.recordUnmatchedType(type); + } + else { + result.recordMatchedType(type, typeMatches); + } + } + for (String annotation : spec.getAnnotations()) { + Set annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation, + considerHierarchy); + annotationMatches.removeAll(beansIgnoredByType); + if (annotationMatches.isEmpty()) { + result.recordUnmatchedAnnotation(annotation); + } + else { + result.recordMatchedAnnotation(annotation, annotationMatches); + } + } + for (String beanName : spec.getNames()) { + if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) { + result.recordMatchedName(beanName); + } + else { + result.recordUnmatchedName(beanName); + } + } + return result; +} +``` + +- 在`MatchResult result = new MatchResult()` 之前的代码作用是确认 ioc 容器 + +#### getNamesOfBeansIgnoredByType + +```java + /** + * 获取忽略的beans(返回对象是 beanName) + * 循环,忽略的类型, 将类型从 beanFactory 获取,返回 + */ + private Set getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory, + boolean considerHierarchy, Set ignoredTypes, Set> parameterizedContainers) { + Set result = null; + for (String ignoredType : ignoredTypes) { + // 从 beanFactory 中获取忽略的beanNames + Collection ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, + ignoredType, parameterizedContainers); + result = addAll(result, ignoredNames); + } + return (result != null) ? result : Collections.emptySet(); + } + +``` + +#### getBeanNamesForType + +```java +/** + * 通过类型获取 beanName + */ +private Set getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy, + ListableBeanFactory beanFactory, String type, Set> parameterizedContainers) throws LinkageError { + try { + // 从beanFactory 中获取忽略的类 返回beanNanme + return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader), + parameterizedContainers); + } + catch (ClassNotFoundException | NoClassDefFoundError ex) { + return Collections.emptySet(); + } +} +``` + +#### getBeanNamesForType + +```java +/** + * 通过类型获取 beanName + */ +private Set getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class type, + Set> parameterizedContainers) { + // 获取beanName + Set result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers, + null); + return (result != null) ? result : Collections.emptySet(); +} +``` + +#### collectBeanNamesForType + +- 这里最终回到了 spring beanFactory 的方法 getBeanNamesForType + +```java +private Set collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, + Class type, Set> parameterizedContainers, Set result) { + result = addAll(result, beanFactory.getBeanNamesForType(type, true, false)); + for (Class container : parameterizedContainers) { + ResolvableType generic = ResolvableType.forClassWithGenerics(container, type); + result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false)); + } + if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) { + BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(); + if (parent instanceof ListableBeanFactory) { + result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type, + parameterizedContainers, result); + } + } + return result; +} +``` + +到这里需要忽略的 beanName 就全部找出来了 + +```java +// 匹配类型在移除 +for (String type : spec.getTypes()) { + // 通过类型获取 beanName + Collection typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type, + parameterizedContainers); + typeMatches.removeAll(beansIgnoredByType); + if (typeMatches.isEmpty()) { + result.recordUnmatchedType(type); + } + else { + result.recordMatchedType(type, typeMatches); + } +} + + + // 注解匹配删除忽略的beanname + for (String annotation : spec.getAnnotations()) { + Set annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation, + considerHierarchy); + annotationMatches.removeAll(beansIgnoredByType); + if (annotationMatches.isEmpty()) { + result.recordUnmatchedAnnotation(annotation); + } + else { + result.recordMatchedAnnotation(annotation, annotationMatches); + } + } +``` + +- 在忽略 bean 找到之后做一个类型移除的操作. + +![image-20200825140750035](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825140750035.png) + +### 返回值 + +- 在返回之前做一堆判断条件. 一旦符合条件这个地方会做一个 noMatch 的一个对象(`ConditionOutcome`) ,通过返回 match 对象`ConditionOutcome` + +```java +public static ConditionOutcome noMatch(ConditionMessage message) { + return new ConditionOutcome(false, message); +} +``` + +```java + if (!matchResult.isAllMatched()) { + String reason = createOnBeanNoMatchReason(matchResult); + return ConditionOutcome.noMatch(spec.message().because(reason)); + } + // 把注解解析出来获得文本 + matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE, + matchResult.getNamesOfAllMatches()); + } + if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) { + Spec spec = new SingleCandidateSpec(context, metadata, annotations); + MatchResult matchResult = getMatchingBeans(context, spec); + if (!matchResult.isAllMatched()) { + return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll()); + } + else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(), + spec.getStrategy() == SearchStrategy.ALL)) { + return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans") + .items(Style.QUOTE, matchResult.getNamesOfAllMatches())); + } + matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE, + matchResult.getNamesOfAllMatches()); + } + if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) { + Spec spec = new Spec<>(context, metadata, annotations, + ConditionalOnMissingBean.class); + MatchResult matchResult = getMatchingBeans(context, spec); + if (matchResult.isAnyMatched()) { + String reason = createOnMissingBeanNoMatchReason(matchResult); + return ConditionOutcome.noMatch(spec.message().because(reason)); + } + matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll(); + } + return ConditionOutcome.match(matchMessage); +``` + +![image-20200825141506531](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825141506531.png) + +- 到此结果封装完毕.回到方法`org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)` 继续进行 + - 再往后就继续执行 spring 的 bean 初始化咯 + +## MessageSourceAutoConfiguration + +- 启动阶段的一个类运行解读 + +- `org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration` + + ```java + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT) + @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) + @Conditional(ResourceBundleCondition.class) + @EnableConfigurationProperties + public class MessageSourceAutoConfiguration {} + + ``` + + - 根据类的注解信息我们可以找到有`ResourceBundleCondition` + + ![image-20200825092343271](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825092343271.png) + +- 获取类名或者方法名的结果是`MessageSourceAutoConfiguration`全路径 + +- 继续往下是一个比较的方法(是否符合 match) + + `org.springframework.boot.autoconfigure.condition.SpringBootCondition#getMatchOutcome`这个方法是一个抽象方法子类实现 + +- 上图中红框内标注的类为`org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition` + + 同时继承`org.springframework.boot.autoconfigure.condition.SpringBootCondition` + + 并且重写了方法`getMatchOutcome` + + ```java + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { + // 从 容器中获取 + String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages"); + // 从缓存中获取条件信息 + ConditionOutcome outcome = cache.get(basename); + if (outcome == null) { + // 生成条件信息对象 + outcome = getMatchOutcomeForBasename(context, basename); + // 放入缓存 + cache.put(basename, outcome); + } + return outcome; + } + ``` + + 这个方法主要将比较信息放入, + +- 后续的行为依然是判断是否匹配,匹配就创建. + +## Spring Boot 启动阶段的自动注入 + +```java +org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#filter +``` + +```java +private List filter(List configurations, AutoConfigurationMetadata autoConfigurationMetadata) { + long startTime = System.nanoTime(); + String[] candidates = StringUtils.toStringArray(configurations); + boolean[] skip = new boolean[candidates.length]; + boolean skipped = false; + // 获取 AutoConfigurationImportFilter 相关配置 + for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { + // 执行 aware 相关接口 + invokeAwareMethods(filter); + // 是否可以初始化的结果 + boolean[] match = filter.match(candidates, autoConfigurationMetadata); + for (int i = 0; i < match.length; i++) { + if (!match[i]) { + // 是否跳过 + skip[i] = true; + candidates[i] = null; + skipped = true; + } + } + } + if (!skipped) { + return configurations; + } + List result = new ArrayList<>(candidates.length); + // 处理最终需要的类 + for (int i = 0; i < candidates.length; i++) { + if (!skip[i]) { + result.add(candidates[i]); + } + } + if (logger.isTraceEnabled()) { + int numberFiltered = configurations.size() - result.size(); + logger.trace("Filtered " + numberFiltered + " auto configuration class in " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); + } + return new ArrayList<>(result); +} +``` + +- 在这里有一个关注点 循环方法`getAutoConfigurationImportFilters()` + +```java +protected List getAutoConfigurationImportFilters() { + return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); +} +``` + +在`spring.factories`文件中找到`AutoConfigurationImportFilter`后面的值 + +```properties +org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ +org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ +org.springframework.boot.autoconfigure.condition.OnClassCondition,\ +org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition +``` + +- 此时我们可以和前文的源码分析连接起来有一个完整的认识了 + + ![image-20200825142332485](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825142332485.png) + +- 最后来看整体类图 + + ![image-20200825142418115](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825142418115.png) diff --git a/docs/SpringBoot/SpringBoot-ConfigurationProperties.md b/docs/SpringBoot/SpringBoot-ConfigurationProperties.md index 59cb4f04..8d3ba5ed 100644 --- a/docs/SpringBoot/SpringBoot-ConfigurationProperties.md +++ b/docs/SpringBoot/SpringBoot-ConfigurationProperties.md @@ -1,4 +1,5 @@ # SpringBoot ConfigurationProperties + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) @@ -15,13 +16,11 @@ * @see EnableConfigurationProperties ``` - - 看到`ConfigurationPropertiesScan` 去看看这个 ## ConfigurationPropertiesScan -```JAVA +```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @@ -32,19 +31,15 @@ public @interface ConfigurationPropertiesScan {} - 熟悉的**Import**注解 - - ## ConfigurationPropertiesScanRegistrar -![image-20200323094446756](../../../images/SpringBoot/image-20200323094446756.png) - -- debug没有抓到后续补充 - +![image-20200323094446756](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323094446756.png) +- debug 没有抓到后续补充 ## EnableConfigurationProperties -```JAVA +```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @@ -53,10 +48,6 @@ public @interface EnableConfigurationProperties { } ``` - - - - ## EnableConfigurationPropertiesRegistrar - 该类会读取**spring.factories** @@ -81,10 +72,6 @@ public @interface EnableConfigurationProperties { org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration ``` - - - - ```java @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { @@ -99,11 +86,9 @@ public @interface EnableConfigurationProperties { ``` - - ### registerInfrastructureBeans -```JAVA +```java static void registerInfrastructureBeans(BeanDefinitionRegistry registry) { // 属性绑定后置处理器 ConfigurationPropertiesBindingPostProcessor.register(registry); @@ -113,11 +98,11 @@ public @interface EnableConfigurationProperties { } ``` -- 此处操作逻辑基本相同,是否存在这个beanName 存在直接注册,不存在补充 +- 此处操作逻辑基本相同,是否存在这个 beanName 存在直接注册,不存在补充 #### ConfigurationPropertiesBindingPostProcessor.register(registry) -```JAVA +```java public static void register(BeanDefinitionRegistry registry) { Assert.notNull(registry, "Registry must not be null"); // 是否存在 @@ -132,11 +117,9 @@ public @interface EnableConfigurationProperties { ``` - - #### ConfigurationPropertiesBeanDefinitionValidator.register(registry) -```JAVA +```java static void register(BeanDefinitionRegistry registry) { Assert.notNull(registry, "Registry must not be null"); if (!registry.containsBeanDefinition(BEAN_NAME)) { @@ -150,27 +133,19 @@ public @interface EnableConfigurationProperties { ``` - - ### getTypes(metadata).forEach(beanRegistrar::register) - 先看输入参数 **metadata** -![image-20200323134135926](../../../images/SpringBoot/image-20200323134135926.png) - - - -- getTypes结果 - -![image-20200323134325955](../../../images/SpringBoot/image-20200323134325955.png) - - +![image-20200323134135926](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323134135926.png) +- getTypes 结果 +![image-20200323134325955](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323134325955.png) - 源码开始,先找出刚才的对象`org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration` - ```JAVA + ```java @Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @@ -183,9 +158,7 @@ public @interface EnableConfigurationProperties { public class ServletWebServerFactoryAutoConfiguration {} ``` - - -```JAVA +```java /** * 找出 {@link EnableConfigurationProperties} 注解标记的中的属性值,并且返回值不是void * @param metadata @@ -204,8 +177,6 @@ public @interface EnableConfigurationProperties { - 这里我们可以直接知道返回的是`@EnableConfigurationProperties(ServerProperties.class)` 的数据值: `ServerProperties.class` - - 循环注册 ```java @@ -217,27 +188,15 @@ public @interface EnableConfigurationProperties { ``` - - - - - - - - --- - - ## ConfigurationPropertiesBindingPostProcessor -![image-20200323095626953](../../../images/SpringBoot/image-20200323095626953.png) - - +![image-20200323095626953](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323095626953.png) ### postProcessBeforeInitialization -```JAVA +```java @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // 绑定 @@ -247,8 +206,6 @@ public @interface EnableConfigurationProperties { ``` - - - get ```java @@ -258,10 +215,8 @@ public @interface EnableConfigurationProperties { // 创建 ConfigurationPropertiesBean return create(beanName, bean, bean.getClass(), factoryMethod); } - - ``` - + ``` ```java private static Method findFactoryMethod(ConfigurableListableBeanFactory beanFactory, String beanName) { @@ -284,8 +239,6 @@ public @interface EnableConfigurationProperties { ``` - - ```java private static Method findFactoryMethodUsingReflection(ConfigurableListableBeanFactory beanFactory, BeanDefinition beanDefinition) { @@ -315,15 +268,11 @@ public @interface EnableConfigurationProperties { ``` - - - - ### create - `org.springframework.boot.context.properties.ConfigurationPropertiesBean#create` -```JAVA +```java private static ConfigurationPropertiesBean create(String name, Object instance, Class type, Method factory) { // 找注解 ConfigurationProperties annotation = findAnnotation(instance, type, factory, ConfigurationProperties.class); @@ -348,46 +297,35 @@ public @interface EnableConfigurationProperties { ``` - - - 第一个需要做的类: `org.springframework.boot.autoconfigure.web.ServerProperties` - - - `annotation` -![image-20200323104711545](../../../images/SpringBoot/image-20200323104711545.png) +![image-20200323104711545](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323104711545.png) - `bindType` -![image-20200323104815305](../../../images/SpringBoot/image-20200323104815305.png) +![image-20200323104815305](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323104815305.png) - 返回对象 -![image-20200323105053757](../../../images/SpringBoot/image-20200323105053757.png) +![image-20200323105053757](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105053757.png) - 此时数据还没有进去 - - - - #### bind - 数据绑定 直接看结果 -![image-20200323105155998](../../../images/SpringBoot/image-20200323105155998.png) +![image-20200323105155998](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105155998.png) - 上述配置和我在配置文件中写的配置一致 - - ```yml server: port: 9999 - ``` - 具体方法: `org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor#bind` @@ -410,8 +348,6 @@ server: ``` - - ```java BindResult bind(ConfigurationPropertiesBean propertiesBean) { // 最后的结果 @@ -420,18 +356,16 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { ConfigurationProperties annotation = propertiesBean.getAnnotation(); // 获取处理器 BindHandler bindHandler = getBindHandler(target, annotation); - // + // return getBinder().bind(annotation.prefix(), target, bindHandler); } ``` -![image-20200323105830138](../../../images/SpringBoot/image-20200323105830138.png) - - +![image-20200323105830138](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105830138.png) ##### findProperty -```JAVA +```java private ConfigurationProperty findProperty(ConfigurationPropertyName name, Context context) { if (name.isEmpty()) { return null; @@ -448,21 +382,19 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { ``` - - - `org.springframework.boot.context.properties.source.SpringConfigurationPropertySource#getConfigurationProperty` - ```JAVA + ```java @Override public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) { PropertyMapping[] mappings = getMapper().map(name); return find(mappings, name); } - + ``` - ```JAVA - + ```java + protected final ConfigurationProperty find(PropertyMapping[] mappings, ConfigurationPropertyName name) { for (PropertyMapping candidate : mappings) { if (candidate.isApplicable(name)) { @@ -474,10 +406,10 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { } return null; } - + ``` - ```JAVA + ```java private ConfigurationProperty find(PropertyMapping mapping) { // 需要读取的配置信息的key String propertySourceName = mapping.getPropertySourceName(); @@ -492,28 +424,14 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { // 包装返回 return ConfigurationProperty.of(configurationPropertyName, value, origin); } - - ``` - - - - - - - -![image-20200323115408877](../../../images/SpringBoot/image-20200323115408877.png) - - - -![image-20200323115701118](../../../images/SpringBoot/image-20200323115701118.png) - - - -![image-20200323115711826](../../../images/SpringBoot/image-20200323115711826.png) + ``` +![image-20200323115408877](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115408877.png) +![image-20200323115701118](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115701118.png) +![image-20200323115711826](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115711826.png) ##### getBindHandler @@ -546,11 +464,9 @@ private BindHandler getBindHandler(Bindable target, ConfigurationProperti - 最终获取得到的处理器 -![image-20200323110603959](../../../images/SpringBoot/image-20200323110603959.png) - +![image-20200323110603959](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323110603959.png) - -- 最后的bind +- 最后的 bind ```java private Object bindObject(ConfigurationPropertyName name, Bindable target, BindHandler handler, @@ -582,17 +498,8 @@ private BindHandler getBindHandler(Bindable target, ConfigurationProperti ``` - - - - -![image-20200323112945449](../../../images/SpringBoot/image-20200323112945449.png) - - - - +![image-20200323112945449](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323112945449.png) 配置信息到此绑定成功,关于如何处理集合相关的配置请各位读者自行学习 ----- - +--- diff --git a/docs/SpringBoot/SpringBoot-LogSystem.md b/docs/SpringBoot/SpringBoot-LogSystem.md index 85cc2579..9da37e9e 100644 --- a/docs/SpringBoot/SpringBoot-LogSystem.md +++ b/docs/SpringBoot/SpringBoot-LogSystem.md @@ -1,149 +1,131 @@ # SpringBoot 日志系统 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) - 包路径: `org.springframework.boot.logging` ## 日志级别 -- 日志级别: `org.springframework.boot.logging.LogLevel` -```java -public enum LogLevel { - TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF -} -``` - - +- 日志级别: `org.springframework.boot.logging.LogLevel` + ```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](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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 { - - /** - * key : SpringBoot 中定义的日志级别, value: 其他日志框架的日志级别 - */ - private final Map systemToNative; - - /** - * key : 其他日志框架的日志级别 , value: springBoot 中定义中定义的日志级别 - */ - private final Map nativeToSystem; - } + protected static class LogLevels { + /** + * key : SpringBoot 中定义的日志级别, value: 其他日志框架的日志级别 + */ + private final Map systemToNative; + /** + * key : 其他日志框架的日志级别 , value: springBoot 中定义中定义的日志级别 + */ + private final Map nativeToSystem; + } ``` - - ## LoggingSystem - 抽象类 - `org.springframework.boot.logging.LoggingSystem` -- 一个map对象: `SYSTEMS` - -```JAVA - /** - * key: 第三方日志框架的类 value: springBoot 中的处理类 - */ - private static final Map SYSTEMS; - - static { - Map 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); - } - -``` - +- 一个 map 对象: `SYSTEMS` + ```java + /** + * key: 第三方日志框架的类 value: springBoot 中的处理类 + */ + private static final Map SYSTEMS; + + static { + Map 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); + } +} ``` - - - - -![image-20200323151409473](../../../images/SpringBoot/image-20200323151409473.png) - - +![image-20200323151409473](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323151409473.png) - 默认日志: `org.springframework.boot.logging.logback.LogbackLoggingSystem` @@ -151,361 +133,313 @@ public static LoggingSystem get(ClassLoader classLoader) { - 初始化之前 - ![image-20200323154205484](../../../images/SpringBoot/image-20200323154205484.png) + ![image-20200323154205484](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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`**方法 + 1. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationEvent` + 2. `org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent` + 3. `org.springframework.boot.logging.LoggingSystem#beforeInitialize` - ```JAVA - @Override - public void beforeInitialize() { - // 日志上下文 - LoggerContext loggerContext = getLoggerContext(); - // 是否初始化 - if (isAlreadyInitialized(loggerContext)) { - return; - } - // 父类方法 - super.beforeInitialize(); - // 添加过滤器 - loggerContext.getTurboFilterList().add(FILTER); +- 因为前文中我们已知对象是:`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); + } ``` - - - - - 初始化之前的的操作完成了初始化方法开始 ### initialize - `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` + ```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); + } + ``` -标记`markAsInitialized` - -```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` 这个方法定义了默认配置文件有哪些 - -```java - @Override - protected String[] getStandardConfigLocations() { - return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" }; - } - -``` - - - - - -- 切回`org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions`方法 +- `getStandardConfigLocations` 这个方法定义了默认配置文件有哪些。 + ```java + @Override + protected String[] getStandardConfigLocations() { + return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" }; + } + ``` +- 切回 `org.springframework.boot.logging.AbstractLoggingSystem#initializeWithConventions` 方法 - 添加依赖 -```XML - - org.springframework.boot - spring-boot-starter-logging - ${revision} - + ```XML + + org.springframework.boot + spring-boot-starter-logging + ${revision} + -``` + ``` - 添加配置文件 -![image-20200323161442058](../../../images/SpringBoot/image-20200323161442058.png) - + ![image-20200323161442058](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323161442058.png) + ![image-20200323161522570](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323161522570.png) +- 此时配置文件地址出现了 + ```java + protected String getSelfInitializationConfig() { + // 寻找配置文件 + return findConfig(getStandardConfigLocations()); + } + ``` -![image-20200323161522570](../../../images/SpringBoot/image-20200323161522570.png) + ```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 - protected String getSelfInitializationConfig() { - // 寻找配置文件 - return findConfig(getStandardConfigLocations()); - } +#### reinitialize +```java +@Override +protected void reinitialize(LoggingInitializationContext initializationContext) { + // 日志上下文重新设置 + getLoggerContext().reset(); + getLoggerContext().getStatusManager().clear(); + // 加载配置文件 + loadConfiguration(initializationContext, getSelfInitializationConfig(), null); +} ``` -```JAVA - @Override - protected String[] getStandardConfigLocations() { - return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", "logback.xml" }; +```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)); } - -``` - -```JAVA - private String findConfig(String[] locations) { - for (String location : locations) { - ClassPathResource resource = new ClassPathResource(location, this.classLoader); - if (resource.exists()) { - return "classpath:" + location; - } + catch (Exception ex) { + throw new IllegalStateException("Could not initialize Logback logging from " + location, ex); + } + List 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()); } - return null; } - -``` - -- 此时自定义配置文件如何获取的已经明了 - - - -#### reinitialize - -```JAVA - @Override - protected void reinitialize(LoggingInitializationContext initializationContext) { - // 日志上下文重新设置 - getLoggerContext().reset(); - getLoggerContext().getStatusManager().clear(); - // 加载配置文件 - loadConfiguration(initializationContext, getSelfInitializationConfig(), null); + 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 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); } - -``` - - - -```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); - } + else { + new ContextInitializer(loggerContext).configureByResource(url); } +} ``` -- 执行配置属于logback 操作源码不在此进行分析 \ No newline at end of file +执行配置属于 logback 操作源码不在此进行分析。 diff --git a/docs/SpringBoot/SpringBoot-application-load.md b/docs/SpringBoot/SpringBoot-application-load.md index 81ad082c..06c60b45 100644 --- a/docs/SpringBoot/SpringBoot-application-load.md +++ b/docs/SpringBoot/SpringBoot-application-load.md @@ -1,34 +1,25 @@ # Spring Boot application 文件加载 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) - - - - ## 如何找到这个加载的过程 1. 创建配置文件`application.yml` -2. 全局搜索yml +2. 全局搜索 yml - ![image-20200319083048849](../../../images/SpringBoot/image-20200319083048849.png) + ![image-20200319083048849](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083048849.png) 3. 换成`properties`搜索 - ![image-20200319083140225](../../../images/SpringBoot/image-20200319083140225.png) + ![image-20200319083140225](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083140225.png) 4. 我们以`yml`为例打上断点开始源码追踪 - - - - 看到调用堆栈 -![image-20200319083345067](../../../images/SpringBoot/image-20200319083345067.png) - - +![image-20200319083345067](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083345067.png) - 一步一步回上去看如何调用具体方法的 @@ -36,15 +27,11 @@ - 配置文件监听器 - - ### 调用过程 -![image-20200319082131146](../../../images/SpringBoot/image-20200319082131146.png) - -![image-20200319082544653](../../../images/SpringBoot/image-20200319082544653.png) - +![image-20200319082131146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319082131146.png) +![image-20200319082544653](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319082544653.png) `org.springframework.boot.context.config.ConfigFileApplicationListener#addPropertySources` @@ -56,18 +43,10 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL } ``` - - - - - - -### Loader +### Loader - 配置资源加载器 - - 构造方法 ```java @@ -85,28 +64,20 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL ``` - - -- 熟悉的老朋友`this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader())`, 看看**`spring.factories`**有什么 +- 熟悉的老朋友`this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader())`, 看看**`spring.factories`**有什么 - 搜索目标: `org.springframework.boot.env.PropertySourceLoader` - ![image-20200319084141748](../../../images/SpringBoot/image-20200319084141748.png) - + ![image-20200319084141748](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084141748.png) +![image-20200319084151997](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084151997.png) -![image-20200319084151997](../../../images/SpringBoot/image-20200319084151997.png) +观察发现里面有一个`YamlPropertySourceLoader`和我们之前找 yml 字符串的时候找到的类是一样的。说明搜索方式没有什么问题。 -观察发现里面有一个`YamlPropertySourceLoader`和我们之前找yml字符串的时候找到的类是一样的。说明搜索方式没有什么问题。 - -![image-20200319084357652](../../../images/SpringBoot/image-20200319084357652.png) +![image-20200319084357652](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084357652.png) 初始化完成,后续进行解析了 - - - - ### load 方法 ```java @@ -139,19 +110,13 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL ### initializeProfiles - 初始化`private Deque profiles;` 属性 -- ![image-20200319084902957](../../../images/SpringBoot/image-20200319084902957.png) - - - - +- ![image-20200319084902957](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084902957.png) ### load - `org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#load(org.springframework.boot.context.config.ConfigFileApplicationListener.Profile, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentFilterFactory, org.springframework.boot.context.config.ConfigFileApplicationListener.DocumentConsumer)` - - -```JAVA +```java private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { getSearchLocations().forEach( // 本地路径 @@ -170,14 +135,10 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document - 资源路径可能性 -![image-20200319085446640](../../../images/SpringBoot/image-20200319085446640.png) - - +![image-20200319085446640](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319085446640.png) 该方法采用循环每个路径下面都去尝试一遍 - - - 中间过程省略,我们直接看最后的加载行为 - `org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments` @@ -201,13 +162,7 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document ``` - - -此处的`loader.load()`调用具体的loader实现类进行执行方法 - - - - +此处的`loader.load()`调用具体的 loader 实现类进行执行方法 ### yml 解析 @@ -235,21 +190,13 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document ``` - - -![image-20200319090446231](../../../images/SpringBoot/image-20200319090446231.png) - - - - +![image-20200319090446231](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319090446231.png) - `PropertiesPropertySourceLoader`解析同理不在次展开描述了 - - ### asDocuments -```JAVA +```java /** * 将 {@link PropertySource} 转换成 {@link Document} * @param loaded @@ -276,5 +223,4 @@ private void load(Profile profile, DocumentFilterFactory filterFactory, Document ``` ------ - +--- diff --git "a/docs/SpringBoot/SpringBoot-\350\207\252\345\212\250\350\243\205\351\205\215.md" "b/docs/SpringBoot/SpringBoot-\350\207\252\345\212\250\350\243\205\351\205\215.md" index 7c3496ab..48754bd2 100644 --- "a/docs/SpringBoot/SpringBoot-\350\207\252\345\212\250\350\243\205\351\205\215.md" +++ "b/docs/SpringBoot/SpringBoot-\350\207\252\345\212\250\350\243\205\351\205\215.md" @@ -1,9 +1,10 @@ # Spring Boot 自动装配 + - Author: [HuiFer](https://github.com/huifer) - 源码阅读仓库: [SourceHot-spring-boot](https://github.com/SourceHot/spring-boot-read) - - `org.springframework.boot.autoconfigure.SpringBootApplication` + ```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -44,7 +45,7 @@ public @interface SpringBootApplication { @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { - + } ``` @@ -52,11 +53,11 @@ public @interface EnableAutoConfiguration { - 类图 -![image-20200320150642022](../../../images/SpringBoot/image-20200320150642022.png) +![image-20200320150642022](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320150642022.png) ## getAutoConfigurationMetadata() -```JAVA +```java @Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, @@ -86,10 +87,10 @@ public @interface EnableAutoConfiguration { - `org.springframework.boot.autoconfigure.AutoConfigurationMetadataLoader#loadMetadata(java.lang.ClassLoader)` - ```JAVA + ```java static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) { try { - + // 获取资源路径 Enumeration urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path); @@ -103,12 +104,10 @@ public @interface EnableAutoConfiguration { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex); } } - - ``` - - ![image-20200320160423991](../../../images/SpringBoot/image-20200320160423991.png) + ``` + ![image-20200320160423991](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320160423991.png) - `protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";` @@ -116,8 +115,6 @@ public @interface EnableAutoConfiguration { 相关 Issues : https://github.com/spring-projects/spring-boot/issues/11282 - - - 自动装配 `spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories` @@ -134,43 +131,31 @@ public @interface EnableAutoConfiguration { org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ ``` - - - - -![image-20200320162835665](../../../images/SpringBoot/image-20200320162835665.png) - -同样找一下redis - -![image-20200320163001728](../../../images/SpringBoot/image-20200320163001728.png) - - +![image-20200320162835665](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320162835665.png) +同样找一下 redis +![image-20200320163001728](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320163001728.png) - 仔细看`org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration`类 - - 先说注解 -```JAVA +```java @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class }) ``` - - ### EnableConfigurationProperties `自动映射一个POJO到Spring Boot配置文件(默认是application.properties文件)的属性集。` - `org.springframework.boot.autoconfigure.data.redis.RedisProperties` -- 部分redis配置属性 +- 部分 redis 配置属性 -```JAVA +```java @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { @@ -215,47 +200,35 @@ public class RedisProperties { */ private String clientName; - + } ``` - - - 找到一个我们用相同方式去寻找到别的一些属性处理如`org.springframework.boot.autoconfigure.jdbc.JdbcProperties` 具体展开请各位读者自行了解了 - - - - - - ### AnnotationMetadata 回过头继续我们的主要流程 - `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process` - ![image-20200320163806852](../../../images/SpringBoot/image-20200320163806852.png) + ![image-20200320163806852](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320163806852.png) 再此之前我们看过了`getAutoConfigurationMetadata()`的相关操作 -关注 `AnnotationMetadata annotationMetadata` 存储了一些什么 +关注 `AnnotationMetadata annotationMetadata` 存储了一些什么 -![image-20200320164145286](../../../images/SpringBoot/image-20200320164145286.png) +![image-20200320164145286](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320164145286.png) 这里简单理解 -1. mergedAnnotations 类相关的注解信息 +1. mergedAnnotations 类相关的注解信息 2. annotationTypes 在启动类上的注解列表 - - - - ### getAutoConfigurationEntry -```JAVA +```java protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { @@ -283,10 +256,6 @@ public class RedisProperties { ``` - - - - ### getAttributes ```java @@ -302,11 +271,7 @@ public class RedisProperties { ``` - - -![image-20200320171138431](../../../images/SpringBoot/image-20200320171138431.png) - - +![image-20200320171138431](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320171138431.png) ### getCandidateConfigurations @@ -324,7 +289,7 @@ public class RedisProperties { ``` -![image-20200320171734270](../../../images/SpringBoot/image-20200320171734270.png) +![image-20200320171734270](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320171734270.png) - 第一个是我自己写的一个测试用 @@ -333,30 +298,20 @@ public class RedisProperties { org.sourcehot.service.HelloServiceAutoConfiguration ``` - - - - - - ### removeDuplicates - new 两个对象直接做数据转换,去重 -```JAVA +```java protected final List removeDuplicates(List list) { return new ArrayList<>(new LinkedHashSet<>(list)); } ``` +### getExclusions - - - -### getExclusions - -```JAVA +```java protected Set getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { Set excluded = new LinkedHashSet<>(); // 获取属性 exclude 值转换成list @@ -370,11 +325,9 @@ public class RedisProperties { ``` - - ### getExcludeAutoConfigurationsProperty -```JAVA +```java private List getExcludeAutoConfigurationsProperty() { if (getEnvironment() instanceof ConfigurableEnvironment) { Binder binder = Binder.get(getEnvironment()); @@ -388,26 +341,20 @@ public class RedisProperties { ``` - - -![image-20200323080611527](../../../images/SpringBoot/image-20200323080611527.png) - - +![image-20200323080611527](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323080611527.png) - 修改启动类 - ```JAVA + ```java @SpringBootApplication(excludeName = { "org.sourcehot.service.HelloServiceAutoConfiguration" }) - + ``` - ![image-20200323081009823](../../../images/SpringBoot/image-20200323081009823.png) + ![image-20200323081009823](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323081009823.png) ### checkExcludedClasses - - -```JAVA +```java private void checkExcludedClasses(List configurations, Set exclusions) { List invalidExcludes = new ArrayList<>(exclusions.size()); for (String exclusion : exclusions) { @@ -424,15 +371,13 @@ public class RedisProperties { ``` - - - `configurations.removeAll(exclusions)` 移除忽略的类 ### filter -```JAVA +```java private List filter(List configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); @@ -471,39 +416,21 @@ public class RedisProperties { ``` - - - `getAutoConfigurationImportFilters()` 从`spring.factories` 获取 `AutoConfigurationImportFilter`的接口 - - -![image-20200323081903145](../../../images/SpringBoot/image-20200323081903145.png) - - +![image-20200323081903145](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323081903145.png) - 循环内执行`Aware`系列接口 - - `match`方法: `org.springframework.boot.autoconfigure.AutoConfigurationImportFilter#match` - `filter.match(candidates, autoConfigurationMetadata)` 比较判断哪些是需要自动注入的类 - - - - -![image-20200323082553595](../../../images/SpringBoot/image-20200323082553595.png) - - - - - - +![image-20200323082553595](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323082553595.png) ### fireAutoConfigurationImportEvents -```JAVA +```java private void fireAutoConfigurationImportEvents(List configurations, Set exclusions) { // 获取自动配置的监听器列表 List listeners = getAutoConfigurationImportListeners(); @@ -521,17 +448,15 @@ public class RedisProperties { ``` -![image-20200323083149737](../../../images/SpringBoot/image-20200323083149737.png) - - +![image-20200323083149737](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083149737.png) - `AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);` -![image-20200323083247061](../../../images/SpringBoot/image-20200323083247061.png) +![image-20200323083247061](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083247061.png) - `org.springframework.boot.autoconfigure.AutoConfigurationImportListener#onAutoConfigurationImportEvent` 在执行自动配置时触发 , 实现类只有 **`ConditionEvaluationReportAutoConfigurationImportListener`** - ```JAVA + ```java @Override public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) { if (this.beanFactory != null) { @@ -542,34 +467,22 @@ public class RedisProperties { report.recordExclusions(event.getExclusions()); } } - - ``` - - - -![image-20200323083656670](../../../images/SpringBoot/image-20200323083656670.png) - - + ``` +![image-20200323083656670](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083656670.png) - 初始化完 - - - - ## process - `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process` - - -![image-20200323084922159](../../../images/SpringBoot/image-20200323084922159.png) +![image-20200323084922159](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323084922159.png) - 后续的一些行为相对简单,直接放个源码了. -```JAVA +```java @Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, @@ -590,12 +503,6 @@ public class RedisProperties { } ``` - - - - - - ## selectImports ```java @@ -624,8 +531,4 @@ public class RedisProperties { ``` - - - - -后续由spring进行不再继续跟踪 \ No newline at end of file +后续由 spring 进行不再继续跟踪 diff --git a/docs/SpringCloud/spring-cloud-commons-source-note.md b/docs/SpringCloud/spring-cloud-commons-source-note.md new file mode 100644 index 00000000..659debd4 --- /dev/null +++ b/docs/SpringCloud/spring-cloud-commons-source-note.md @@ -0,0 +1,1004 @@ +# 说明 + +Author: [haitaoss](https://github.com/haitaoss) + +源码阅读仓库: [spring-cloud-commons](https://github.com/haitaoss/spring-cloud-commons) + +参考资料和需要掌握的知识: + +- [SpringBoot 源码分析](https://github.com/haitaoss/spring-boot/blob/source-v2.7.8/note/springboot-source-note.md) +- [Spring 源码分析](https://github.com/haitaoss/spring-framework) +- [Spring Cloud 官网文档](https://docs.spring.io/spring-cloud/docs/2021.0.5/reference/html/) +- [Spring Cloud Commons 官网文档](https://docs.spring.io/spring-cloud-commons/docs/3.1.5/reference/html/) + +# Spring Cloud 介绍 + +SpringCloud 是在 SpringBoot 的基础上构建的。Spring Cloud 以两个库的形式提供了许多特性:Spring Cloud Context 和 Spring Cloud Commons。Spring Cloud Context 为 SpringCloud 应用程序的 ApplicationContext 提供扩展机制(引导上下文、加密、刷新属性和环境端点)。Spring Cloud Commons 是一组抽象(服务注册、负载均衡、熔断器等 API) 和 通用类,用于不同的 Spring Cloud 实现(例如 Spring Cloud Netflix 和 Spring Cloud Consul) + +是基于 Spring Boot 的自动装配原理实现的,其实就是定义了很多自动配置类,所以在 SpringCloud 的环境下 启动 SpringBoot 程序 会有很多功能。 + +# 核心功能源码分析 + +## BootstrapApplicationListener (bootstrap.properties 读取原理) + +[前置知识:SprinBoot 加载 application.yml 的原理](https://github.com/haitaoss/spring-boot/blob/source-v2.7.8/note/springboot-source-note.md#%E5%B1%9E%E6%80%A7%E6%96%87%E4%BB%B6%E7%9A%84%E5%8A%A0%E8%BD%BD%E9%A1%BA%E5%BA%8F) + +示例代码 + +```java +@EnableAutoConfiguration +public class Main { + + public static void main(String[] args) { + // 是否创建 bootstrapContext + System.setProperty("spring.cloud.bootstrap.enabled", "true"); + // 设置 bootstrapContext 中属性文件的搜索目录 或者是 属性文件 + System.setProperty("spring.cloud.bootstrap.location", ""); + System.setProperty("spring.cloud.bootstrap.additional-location", + "optional:classpath:/config/haitao/,classpath:/haitao.properties"); + // 设置 bootstrapContext 默认属性文件的名字 + // System.setProperty("spring.cloud.bootstrap.name", "bootstrap-haitao"); + // 设置 profile + // System.setProperty("spring.profiles.active", "haitao"); + // 测试读取属性 + ConfigurableApplicationContext context = SpringApplication.run(Main.class, args); + ConfigurableEnvironment environment = context.getEnvironment(); + Stream.iterate(1, i -> i + 1).limit(5).map(i -> "p" + i).forEach( + name -> System.out.println(String.format("key:%s \t valus: %s", name, environment.getProperty(name)))); + } + +} +``` + +BootstrapApplicationListener 是用于完成 SpringCloud 的接入的,主要是完成 bootstrapContext 的创建、bootstrap 属性的加载、设置 bootstrapContext 为父容器。下面是 BootstrapApplicationListener 被触发的入口和核心逻辑 + +```java +/** + * BootstrapApplicationListener 是用于完成 SpringCloud 的接入的,主要是完成 bootstrapContext的创建、bootstrap属性的加载、设置bootstrapContext为父容器。 + * 下面是 BootstrapApplicationListener 被触发的入口和核心逻辑 + * + * + * SpringBoot 启动的生命周期的配置Environment阶段,会发布 ApplicationEnvironmentPreparedEvent 事件,所以 BootstrapApplicationListener 会收到事件 + * {@link SpringApplication#run(String...)} + * {@link SpringApplication#prepareEnvironment(SpringApplicationRunListeners, DefaultBootstrapContext, ApplicationArguments)} + * {@link EventPublishingRunListener#environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)} + * {@link EnvironmentPostProcessorApplicationListener#onApplicationEvent(ApplicationEvent)} + * {@link BootstrapApplicationListener#onApplicationEvent(org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent)} + * + * 注:spring-cloud-context.jar!/META-INF/spring.factories 中声明了 BootstrapApplicationListener + * + * BootstrapApplicationListener#onApplicationEvent 的核心逻辑 + * 1. 属性 spring.cloud.bootstrap.enabled == false 就直接 return 不做处理 + * + * 2. 构造出 bootstrap context, 拷贝 PropertySource、ApplicationContextInitializer 给当前 SpringApplication + * 2.1 构造一个 bootstrapEnvironment,主要是设置这三个属性 + * 由 ${spring.cloud.bootstrap.name:bootstrap} 设置 spring.config.name 属性的值 + * 由 ${spring.cloud.bootstrap.location} 设置 spring.config.location 属性的值 + * 由 ${spring.cloud.bootstrap.additional-location} 设置 spring.config.additional-location 属性的值 + * + * Tips:这三个属性是为了指定SpringBoot启动时应该读取那些目录下的属性文件,从而实现扩展 Environment + * + * 2.2 配置 SpringApplicationBuilder,最主要是设置 BootstrapImportSelectorConfiguration 作为源配置类 + * `SpringApplicationBuilder builder = new SpringApplicationBuilder() + * .environment(bootstrapEnvironment) + * .sources(BootstrapImportSelectorConfiguration.class);` + * + * 注:BootstrapImportSelectorConfiguration 这个类会 @Import(BootstrapImportSelector.class),其作用是读取 META-INF/spring.factories 文件 + * 获取key为`BootstrapConfiguration.class.getName()`的值 和 属性 spring.cloud.bootstrap.sources 的值作为配置类导入到 BeanFactory 中 + * + * 2.3 使用 SpringApplicationBuilder 构造出 Context,也就是又通过 SpringBoot 创建一个 context , 说白了就是利用 SpringBoot 加载 application.yml 的逻辑来加载 bootstrap.yml + * `ConfigurableApplicationContext bootstrapContext = builder.run();` + * + * 2.4 将 bootstrapContext 中的 Environment 追加到 event.getSpringApplication() 中,从而将 bootstrap.properties 属性内容 扩展到 event.getSpringApplication() 中 + * + * 2.5 将 bootstrapContext 中的 ApplicationContextInitializer 追加到 event.getSpringApplication() 中 + * - 有一个 PropertySourceBootstrapConfiguration ,这个是用来添加 PropertySource 到Environment中的,具体有哪些 PropertySource, + * 可以注册 PropertySourceLocator bean来自定义逻辑(比如 本地文件、网络资源 ) + * + * - 有一个 AncestorInitializer , 其作用是设置 bootstrapContext 作为 application 的父容器 + * + * Tips:说白了 bootstrapContext 的目的就是 加载bootstrap属性 和 生成 ApplicationContextInitializer,这两个东西都会设置给 + * SpringApplication,从而实现对 SpringBoot 应用的定制化。可以把 bootstrapContext 理解成父容器,因为会通过 AncestorInitializer + * 将 bootstrapContext 设置为IOC容器的父容器。 + * */ +``` + +### BootstrapImportSelectorConfiguration + +用来扩展 bootstrapContext 中的配置类 + +```java +// 类的声明如下 +@Configuration(proxyBeanMethods = false) +@Import(BootstrapImportSelector.class) +public class BootstrapImportSelectorConfiguration {} +``` + +```java +/** + * BootstrapImportSelectorConfiguration 会通过 @Import 导入 {@link BootstrapImportSelector} + * 其回调方法 {@link BootstrapImportSelector#selectImports(AnnotationMetadata)} 的逻辑是 + * 1. 读取 META-INF/spring.factories 获取key为BootstrapConfiguration的值 + * 2. 获取属性 spring.cloud.bootstrap.sources 的值 + * 3. 合并第一第二的值,然后排序 + * 4. 会将值注册到容器中,作为容器的配置类 + * + * 而 + * spring-cloud-context.jar!/META-INF/spring.factories 中定义了 + * org.springframework.cloud.bootstrap.BootstrapConfiguration = org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration + * + * 也就是 {@link PropertySourceBootstrapConfiguration} 会注册到 bootstrapContext 中, + * 它是 ApplicationContextInitializer 类型的,最终会用来初始化 context + * */ +``` + +### PropertySourceBootstrapConfiguration + +示例代码 + +```java +public class MyPropertySourceLocator implements PropertySourceLocator { + + public MyPropertySourceLocator() { + System.out.println("MyPropertySourceLocator...构造器"); + } + + @Resource + private ApplicationContext applicationContext; + + @Value("${dynamicConfigFile}") + private String filePath; + + @Override + public PropertySource locate(Environment environment) { + PropertySource propertySource; + try { + // 也可以改成网络资源 + propertySource = new YamlPropertySourceLoader() + .load("haitao-propertySource", applicationContext.getResource(filePath)).get(0); + } catch (IOException e) { + throw new RuntimeException(e); + } + return propertySource; + } + +} +``` + +`META-INF/spring.factories` + +```properties +org.springframework.cloud.bootstrap.BootstrapConfiguration=\ + cn.haitaoss.BootstrapProperties.BootstrapConfiguration.MyPropertySourceLocator +``` + +```java +/** + * PropertySourceBootstrapConfiguration 是用来初始化IOC容器的,其初始化逻辑是扩展IOC容器的Environment, + * 可以自定义 PropertySourceLocator 用来扩展 Environment + * + * {@link PropertySourceBootstrapConfiguration#initialize(ConfigurableApplicationContext)} + * + * 1. 通过依赖注入对属性赋值 + * @Autowired(required = false) + * private List propertySourceLocators = new ArrayList<>(); + * + * 2. 排序 + * AnnotationAwareOrderComparator.sort(this.propertySourceLocators); + * + * 3. 遍历 propertySourceLocators , 然后回调方法得到 PropertySource 收集起来 + * for (PropertySourceLocator locator : this.propertySourceLocators) { + * Collection> source = locator.locateCollection(environment); + * sourceList.addAll(source); + * } + * + * 4. 根据属性值决定插入到Environment的顺序 + * spring.cloud.config.overrideSystemProperties 默认是 true + * spring.cloud.config.allowOverride 默认是 true + * spring.cloud.config.overrideNone 默认是 false + * + * if !allowOverride || (!overrideNone && overrideSystemProperties) + * 通过 PropertySourceLocator 得到的 PropertySource 会添加到最前面,也就是优先生效 + * else if overrideNone + * 通过 PropertySourceLocator 得到的 PropertySource 会添加到最后面,也就是兜底生效 + * else if !overrideSystemProperties + * 通过 PropertySourceLocator 得到的 PropertySource 会放在 systemEnvironment 的后面 + * else if overrideSystemProperties + * 通过 PropertySourceLocator 得到的 PropertySource 会放在 systemEnvironment 的前面 + * else + * 通过 PropertySourceLocator 得到的 PropertySource 会添加到最后面,也就是兜底生效 + * + * 注:也就是可以通过这三个属性值,决定最终 Environment 属性的读取顺序 + * + * */ +``` + +## @RefreshScope 和 @ConfigurationProperties bean 的更新 + +示例代码 + +```java +@SpringBootApplication +public class Main { + + /** + * 总结用法: + * + * 可以通过属性 spring.cloud.refresh.refreshable spring.cloud.refresh.extraRefreshable + * 代替 @RefreshScope + * + * 可以设置属性 spring.cloud.refresh.enabled=false 取消 @RefreshScope 的自动注入 是 + * spring.cloud.refresh.never-refreshable 属性记录的类就不重会新绑定属性 + */ + public static void main(String[] args) { + // TODOHAITAO: 2023/4/6 访问验证属性更新 GET http://127.0.0.1:8080/actuator/refresh + // 启用 bootstrap 属性的加载 + System.setProperty("spring.cloud.bootstrap.enabled", "true"); + + // 通过配置属性的方式,扩展bean为 refresh scope 的 + System.setProperty("spring.cloud.refresh.refreshable", + Arrays.asList(RefreshScopeBean1.class.getName(), RefreshScopeBean2.class.getName()).stream() + .collect(Collectors.joining(","))); + System.setProperty("spring.cloud.refresh.extraRefreshable", + Arrays.asList(Object.class.getName()).stream().collect(Collectors.joining(","))); + + // 设置 bootstrapContext 会默认加载的 bean + System.setProperty("spring.cloud.bootstrap.sources","cn.haitaoss.RefreshScope.config.MyPropertySourceLocator"); + } + +} +``` + +```java +/** + * 只是列举了我觉得比较重要的,并不是全部内容 + * spring-cloud-context.jar!/META-INF/spring.factories + * + * org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + * org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\ + * org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\ + * org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration + * + * org.springframework.context.ApplicationListener=\ + * org.springframework.cloud.bootstrap.BootstrapApplicationListener + * + * org.springframework.cloud.bootstrap.BootstrapConfiguration=\ + * org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration + * */ +``` + +### ConfigurationPropertiesRebinderAutoConfiguration + +1. 记录 @ConfigurationProperties 的 bean +2. 接收 EnvironmentChangeEvent 事件,对记录的 bean 进行重新初始化从而完成属性的更新 + +```java +/** + * ConfigurationPropertiesRebinderAutoConfiguration + * 注册 ConfigurationPropertiesBeans : + * - 是 BeanPostProcessor 其作用是记录有 @ConfigurationProperties注解 标注的bean + * + * 注册 ConfigurationPropertiesRebinder : + * - 实现了 ApplicationListener 接口,还依赖 ConfigurationPropertiesBeans。 + * 收到事件的处理逻辑是,遍历 ConfigurationPropertiesBeans 记录的 bean 对其进行重新绑定。其实就是 回调bean的销毁方法,然后对bean重新初始化而已, + * 而不是直接从BeanFactory中删除bean。 + * + * 注: + * 1. 会过滤掉是 refresh scope 的bean,因为 refresh 作用域的bean由其他类进行刷新 + * 2. 是 spring.cloud.refresh.never-refreshable 属性记录的类就不要重新绑定属性 + * + * 实现了 SmartInitializingSingleton ,会在 单例bean实例化完后被回调,其回调方法的逻辑是使用 ConfigurationPropertiesRebinder 对父容器中的bean进行重新绑定 + * + * */ +``` + +### RefreshAutoConfiguration + +1. 注册 refresh scope 到 BeanFactory 中 +2. 接收 RefreshEvent 事件,更新 Environment 和 清空 refresh scope 中记录的 bean +3. 使用 @RefreshScope 标注的 bean,最终生成的是代理对象,每次执行代理对象的方法,都会从 refresh scope 中获取 bean 得到调用方法的对象,从而能保证更新之后,获取的对象也是新的 + +```java +/** + * RefreshAutoConfiguration + * 可以设置属性 spring.cloud.refresh.enabled=false 让这个自动配置类不生效 + * + * 注册 RefreshScope : + * - 实现 ApplicationListener 接口,收到事件的处理逻辑是对 refresh scope 的bean进行实例化(非懒加载的) + * - 实现 BeanFactoryPostProcessor 接口,接口方法的实现逻辑是 将当前scope注册到 BeanFactory 中 + * - 实现 BeanDefinitionRegistryPostProcessor 接口,接口方法的实现逻辑是过滤出 beanClass 是 ScopedProxyFactoryBean 改成 LockedScopedProxyFactoryBean + * 注:LockedScopedProxyFactoryBean 是用来生成代理对象的工具类,会默认添加一个 MethodInterceptor,该拦截器是先加 读锁 再执行方法, + * 其目的是因为 RefreshScope 的刷新方法,会遍历域中的所有对象 上写锁之后在销毁bean,从而保证如果scope刷新时,方法的执行会被堵塞, + * 而bean的创建是通过 synchronized 保证一致性。 + * + * 注:@RefreshScope 标注的bean,会在解析BeanDefinition时,设置其beanClass为 ScopedProxyFactoryBean + * + * 注册 LoggingRebinder : + * - 实现 ApplicationListener 接口,收到事件的处理逻辑是获取属性前缀 logging.level 重新设置日志级别 + * + * 注册 LegacyContextRefresher 或 ConfigDataContextRefresher : + * - 是 ContextRefresher 类型的,这两个bean是互斥的只会注册一个。 + * 1. 刷新 Environment。更新原理是 重新启动一个SpringBoot 从而实现属性文件的加载,然后将新生成 PropertySource 替换或者追加到 当前context的Environment中 + * 更新完会发布 EnvironmentChangeEvent 事件 + * 2. 刷新 refresh Scope 中的bean,其实就是情况作用域中的bean,然后会发布 RefreshScopeRefreshedEvent 事件 + * + * + * 注册 RefreshEventListener : + * - 其实现了 SmartApplicationListener 接口, 会接收 RefreshEvent 事件,收到事件的处理逻辑是回调 ContextRefresher#refresh 来 + * 更新 Environment,刷新 refresh 作用域 + * + * 注册 RefreshScopeBeanDefinitionEnhancer : + * - 实现 BeanDefinitionRegistryPostProcessor 接口,接口的实现逻辑是过滤出 beanClass 在属性 + * spring.cloud.refresh.refreshable 或者 spring.cloud.refresh.extraRefreshable 中 + * 就设置为 refresh scope ,并修改其beanClass为 ScopedProxyFactoryBean。也就是省略 @RefreshScope 的一种方式 + * + * Tips:要想实现自定义 Environment 和 refresh bean 的更新逻辑,可以自定注册 ContextRefresher + * */ +``` + +### RefreshEndpointAutoConfiguration + +```java +/** + * RefreshEndpointAutoConfiguration + * 注册 RefreshEndpoint : + * 只定义了一个 @WriteOperation , 该方法的逻辑是会执行 ContextRefresher#refresh 方法,从而 更新 Environment,刷新 refresh 作用域 + * POST http://localhost:8080/actuator/refresh 可以触发该操作 + * + * 注:得导入 spring-boot-starters-actuator 才会生效 + * + * ...还注册了很多 bean , 暂时不看了 , 用到再看吧 + * */ +``` + +## @EnableDiscoveryClient + +```java +/** + * {@link EnableDiscoveryClient} + * @EnableDiscoveryClient(autoRegister=true) + * 1. 的目的是将 META-INF/spring.factories 中 key 是 `EnableDiscoveryClient.class.getName()` 的类注册到 BeanFactory 中 + * 2. 若 autoRegister==true , 还会注册 AutoServiceRegistrationConfiguration 到 BeanFactory 中, + * 这个类的目的很简单,就是注册 AutoServiceRegistrationProperties 到BeanFactory中 + * 3. 若 autoRegister==false, 会设置属性 spring.cloud.service-registry.auto-registration.enabled 为 false, + * 这个为 false 就会导致 AutoServiceRegistrationAutoConfiguration 的 @conditional注解不匹配,从而不会生效 + * + * 总结:注册 AutoServiceRegistrationProperties 和 META-INF/spring.factories 中的类 到容器中 + * + * + * 其实 @EnableDiscoveryClient 没啥用了,因为 spring-cloud-commons.jar!/META-INF/spring.factories 中声明了 + * org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + * org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration + * 也就是 AutoServiceRegistrationAutoConfiguration 会自动注入。但是需要 DiscoveryClient 的bean能被发现, + * 必须得自己注册到BeanFactory中,不能使用 META-INF/spring.factories 设置 key 是 `EnableDiscoveryClient.class.getName()` + * + * {@link AutoServiceRegistrationAutoConfiguration} + * 通过 @PostConstruct 的方式让校验方法被调用,是用来校验 AutoServiceRegistration 类型的bean是否存在,但是默认是不会报错的 + * 可以设置 spring.cloud.service-registry.auto-registration.failFast=true 的方式,启用校验。 + * + * 总结:@EnableDiscoveryClient 的核心功能是可以将声明在 META-INF/spring.factories 中的类注册到容器中(key是 `EnableDiscoveryClient.class.getName()`) + * */ +``` + +## @LoadBalanced + +```java +@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface LoadBalanced {} +``` + +```java +/** + * @LoadBalanced 的作用 + * 1. 其本质是一个 @Qualifier 。在依赖注入的过滤候选bean时会校验 @Qualifier 的值 + * 2. 作为标记注解,比如 LoadBalancerWebClientBuilderBeanPostProcessor 会过滤出有 @LoadBalanced 的bean进行处理 + * */ +``` + +```java +public class Config { + + @Bean + @LoadBalanced + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public RestTemplate restTemplate2() { + return new RestTemplate(); + } +} + +public class LoadBalancerAutoConfiguration { + + // 只会注入 restTemplate 不会注入 restTemplate2 + @LoadBalanced + @Autowired(required = false) + private List restTemplates = Collections.emptyList(); + +} +``` + +## @LoadBalancerClient 和@LoadBalancerClients + +@LoadBalancerClient 映射成 LoadBalancerClientSpecification 然后注册到 BeanFactory 中。 + +@LoadBalancerClients 其实就是多个 @LoadBalancerClient + +```java +@Configuration(proxyBeanMethods = false) +@Import(LoadBalancerClientConfigurationRegistrar.class) +public @interface LoadBalancerClient { + + @AliasFor("name") + String value() default ""; + + @AliasFor("value") + String name() default ""; + + Class[] configuration() default {}; +} +``` + +```java +/** + * LoadBalancerClientConfigurationRegistrar 是 ImportBeanDefinitionRegistrar 的实现类,所以IOC容器解析到 @Import(LoadBalancerClientConfigurationRegistrar.class) + * 会回调其方法 + * {@link LoadBalancerClientConfigurationRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)} + * 1. 获取注解的元数据信息 + * Map client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true); + * + * 2. 获取name,获取的是注解的value属性值或者是注解的name属性值 + * String name = getClientName(client); + * + * 3. 映射成BeanDefinition然后注册到BeanFactory中 + * + * BeanDefinitionBuilder builder = BeanDefinitionBuilder + * .genericBeanDefinition(LoadBalancerClientSpecification.class); + * builder.addConstructorArgValue(name); + * builder.addConstructorArgValue(client.get("configuration")); + * registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition()); + * + * 注:LoadBalancerClientSpecification 是构造 LoadBalancerClientFactory 依赖的bean + * */ +``` + +## DiscoveryClient + +`spring-cloud-commons.jar!/META-INF/spring.factories`的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration,\ + org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration,\ +``` + +DiscoveryClient 是用来获取注册中心注册了多少实例,单独看是没用的得结合 [负载均衡的实现逻辑](#LoadBalancerClient) 才能明白。 + +```java +/** + * SimpleDiscoveryClientAutoConfiguration + * 注册 SimpleDiscoveryProperties , 通过 @ConfigurationProperties 将配置文件中的信息绑定到属性中 + * 注册 SimpleDiscoveryClient 是 DiscoveryClient 接口的实现类, 其依赖 SimpleDiscoveryProperties , 其职责是根据 serviceId 返回 List + * + * CompositeDiscoveryClientAutoConfiguration + * 注册 CompositeDiscoveryClient 是 DiscoveryClient 的实现类,其作用是用来聚合 List 的 + **/ +``` + +## LoadBalancerAutoConfiguration + +`spring-cloud-commons.jar!/META-INF/spring.factories`的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\ +``` + +> 这个配置类主要是定义了 LoadBalancerInterceptor 用来拦截 RestTemplate 的执行,其拦截逻辑是委托给 +> +> [LoadBalancerClient](#LoadBalancerClient) 来做。又定义了 LoadBalancerRequestFactory 用于生成 LoadBalancerClient 的参数, +> +> 而 LoadBalancerRequestFactory 会使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强, +> +> 所以我们可以自定义 **LoadBalancerRequestTransformer** 的 bean 对 负载均衡的请求 进行修改。 + +```java +/** + * org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration + * 满足这个条件 @ConditionalOnBean(LoadBalancerClient.class) 配置类才会生效,而 LoadBalancerClient 唯一的实现在 + * spring-cloud-loadbalancer.jar 中。LoadBalancerClient 是用来执行请求的,让请求变成负载均衡的方式 + * + * 注册 SmartInitializingSingleton,其逻辑是使用 RestTemplateCustomizer 对 @LoadBalanced的RestTemplate 进行自定义 + * + * 注册 LoadBalancerRequestFactory,其依赖 LoadBalancerClient 和 List + * 是用来生成 LoadBalancerRequest 的(其实就是使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强) + * `LoadBalancerRequest request = requestFactory.createRequest(rawRequest, body, execution); + * LoadBalancerClient.execute(serviceName, request);` + * + * 注册 LoadBalancerInterceptor 是 ClientHttpRequestInterceptor 接口的实现类,其依赖于 LoadBalancerClient、LoadBalancerRequestFactory + * ClientHttpRequestInterceptor 是用来拦截 RestTemplate 执行请求的 + * + * 注册 RestTemplateCustomizer 依赖 LoadBalancerInterceptor , 会将 LoadBalancerInterceptor 设置给 RestTemplate + * List list = new ArrayList<>(restTemplate.getInterceptors()); + * list.add(loadBalancerInterceptor); + * restTemplate.setInterceptors(list); + **/ +``` + +## LoadBalancerClient + +示例代码 + +```java +@EnableAutoConfiguration +@RestController +@Import({ LoadBalancerClientConfig.class, LoadBalancerOtherConfig.class }) +public class Main extends BaseApp { + + public static void main(String[] args) { + /** + * TODOHAITAO: 2023/4/7 验证方式 运行 Main、Client1、Client2 然后访问: + * - 堵塞式 GET http://localhost:8080/s1 + * - 响应式 GET http://localhost:8080/2/s1 + */ + // 采用那种方式对 RestTemplate 进行增强,看 + // org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration + System.setProperty("spring.cloud.loadbalancer.retry.enabled", "false"); + System.setProperty("spring.profiles.active", "loadbalance"); + ConfigurableApplicationContext context = SpringApplication.run(Main.class); + } + +} +``` + +负载均衡会使用 LoadBalancerClient 来执行请求的,大致逻辑是通过 DiscoveryClient 得到 serviceId 有哪些实例,再通过负载均衡策略的逻辑筛选出唯一的实例,然后根据这个实例的 url 执行请求。 + +`spring-cloud-loadbalancer.jar!/META-INF/spring.factories`的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\ + org.springframework.cloud.loadbalancer.config.LoadBalancerStatsAutoConfiguration,\ + org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration +``` + +### LoadBalancerAutoConfiguration + +[提供了@LoadBalancerClient 用于简易的注册 LoadBalancerClientSpecification](#@LoadBalancerClient) + +```java +/** + * org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration + * + * 注册 LoadBalancerZoneConfig , ZonePreferenceServiceInstanceListSupplier 会依赖这个bean来过滤出 List + * + * 注册 LoadBalancerClientFactory 其继承 NamedContextFactory。 + * 会根据 name 创建一个IOC容器,IOC容器默认有两个配置类:PropertyPlaceholderAutoConfiguration、LoadBalancerClientConfiguration + * 并依赖 LoadBalancerClientSpecification 用来扩展配置类,创建的IOC容器会缓存到Map中。 + * + * LoadBalancerClientConfiguration 其目的是注册了 ServiceInstanceListSupplier、ReactorLoadBalancer + * 这两个bean是用来实现负载均衡策略得到唯一的 ServiceInstance 的。而且都有 @ConditionalOnMissingBean 条件,若我们想自定义 + * 可以设置 LoadBalancerClientSpecification 扩展配置类。 + * + * 可以使用 @LoadBalancerClient 或者直接注册 LoadBalancerClientSpecification 类型的bean到容器中, + **/ +``` + +### LoadBalancerStatsAutoConfiguration + +一般来说 LoadBalancerClient 执行负载均衡请求时会 回调 LoadBalancerLifecycle 接口的方法 + +```java +/** + * LoadBalancerStatsAutoConfiguration + * 注册 MicrometerStatsLoadBalancerLifecycle 其实现了 LoadBalancerLifecycle 接口。比如 BlockingLoadBalancerClient + * 执行请求时会回调 LoadBalancerLifecycle 的方法做通知 + * */ +``` + +### BlockingLoadBalancerClientAutoConfiguration + +注册 LoadBalancerClient 的实现类到容器中 + +```java +/** + * BlockingLoadBalancerClientAutoConfiguration + * 注册 BlockingLoadBalancerClient 其实现了LoadBalancerClient接口,依赖 LoadBalancerClientFactory 和 properties + * BlockingLoadBalancerClient 的核心逻辑是接收 HttpRequest 解析 Uri 得到 serviceId ,然后使用 serviceId 负载均衡得到唯一的 serviceInstance + * 然后再执行 HttpRequest + * + * */ +``` + +### 负载均衡的 RestTemplate 执行请求的流程 + +使用 restTemplate 发送请求,最终会委托给 ClientHttpRequestInterceptor 执行请求 + +```properties +# 大致是这么个流程,可以细看下面的代码分析 +RestTemplate -> (RetryLoadBalancerInterceptor|LoadBalancerInterceptor) + -> LoadBalancerClientFactory -> BlockingLoadBalancerClient + -> ReactorLoadBalancer -> ServiceInstanceListSupplier -> DiscoveryClient + -> LoadBalancerRequestTransformer + -> 执行请求 +``` + +```java +/** + * 例如: + * restTemplate.getForEntity("http://serviceName/xx", String.class) + * + * {@link RestTemplate#getForEntity(String, Class, Object...)} + * {@link RestTemplate#execute(String, HttpMethod, RequestCallback, ResponseExtractor, Object...)} + * {@link RestTemplate#doExecute(URI, HttpMethod, RequestCallback, ResponseExtractor)} + * {@link AbstractClientHttpRequest#execute()} + * {@link AbstractBufferingClientHttpRequest#executeInternal(HttpHeaders)} + * {@link InterceptingClientHttpRequest#executeInternal(HttpHeaders, byte[])} + * {@link InterceptingClientHttpRequest.InterceptingRequestExecution#execute(HttpRequest, byte[])} + * 在这里回调 ClientHttpRequestInterceptor 的方法,从而实现拦截请求的执行 + **/ +``` + +```java +public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { + if (this.iterator.hasNext()) { + ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); + // 委托给迭代器执行逻辑。比如 LoadBalancerInterceptor 其实就是修改 request 然后又递归回调该方法 + return nextInterceptor.intercept(request, body, this); + } + else { + HttpMethod method = request.getMethod(); + Assert.state(method != null, "No standard HTTP method"); + ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method); + // 拷贝请求头内容 + request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value)); + // 执行请求 + return delegate.execute(); + } +} +``` + +### LoadBalancerInterceptor + +默认注入的是 [RetryLoadBalancerInterceptor](#RetryLoadBalancerInterceptor) 而不是 LoadBalancerInterceptor。可以设置 `spring.cloud.loadbalancer.retry.enabled=false` 让 LoadBalancerInterceptor 生效 + +```java +/** + * LoadBalancerInterceptor 的执行逻辑 + * {@link LoadBalancerInterceptor#intercept(HttpRequest, byte[], ClientHttpRequestExecution)} + * 1. 获取serviceName + * final URI originalUri = request.getURI(); + * String serviceName = originalUri.getHost(); + * + * 2. 使用 LoadBalancerRequestFactory 构造出 LoadBalancerRequest,构造逻辑其实就是使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强 + * LoadBalancerRequest lbRequest = requestFactory.createRequest(request, body, execution); + * + * 3. 委托给 LoadBalancerClient 执行请求 + * loadBalancerClient.execute(serviceName, lbRequest) + * 默认是这个实现类的方法 {@link BlockingLoadBalancerClient#execute(String, LoadBalancerRequest)} + * + **/ +``` + +### RetryLoadBalancerInterceptor + +整体逻辑和 [LoadBalancerInterceptor](#LoadBalancerInterceptor) 是一样的,只不过是使用 [RetryTemplate](https://github.com/spring-projects/spring-retry) 来执行,根据重试策略重复执行而已。 + +会使用 **LoadBalancedRetryFactory** 来生成 LoadBalancedRetryPolicy、BackOffPolicy、RetryListener 这三个东西是用来决定该如何重试,默认是有一个 BlockingLoadBalancedRetryPolicy 会根据属性信息生成 LoadBalancedRetryPolicy、BackOffPolicy,若我们有需要可以自定义 LoadBalancedRetryFactory bean 注册到容器中,因为 + +```java +@Configuration +public class BlockingLoadBalancerRetryConfig { + @Bean + @ConditionalOnMissingBean + LoadBalancedRetryFactory loadBalancedRetryFactory(LoadBalancerProperties properties) { + return new BlockingLoadBalancedRetryFactory(properties); + } +} +``` + +```properties +# 指示应在同一ServiceInstance 上重试请求的次数(对每个选定实例单独计数) +spring.cloud.loadbalancer.retry.maxRetriesOnSameServiceInstance=1 +# 指示新选择的 ServiceInstance 应重试请求的次数 +spring.cloud.loadbalancer.retry.maxRetriesOnNextServiceInstance=1 +# 总是重试失败请求的状态代码 +spring.cloud.loadbalancer.retry.retryableStatusCodes=1 +# 设置最小回退持续时间(默认为5毫秒) +spring.cloud.loadbalancer.retry.backoff.minBackoff=1 +# 设置最大回退持续时间(默认为最大长值毫秒) +spring.cloud.loadbalancer.retry.backoff.maxBackoff=1 +# 设置用于计算每个调用的实际回退持续时间的抖动(默认为0.5) +spring.cloud.loadbalancer.retry.backoff.jitter=1 +``` + +```java +/** + * RetryLoadBalancerInterceptor 的执行逻辑 + * {@link RetryLoadBalancerInterceptor#intercept(HttpRequest, byte[], ClientHttpRequestExecution)} + * + * 1. 使用 LoadBalancedRetryFactory 生成重试策略(默认是根据配置信息) + * final LoadBalancedRetryPolicy retryPolicy = lbRetryFactory.createRetryPolicy(serviceName, loadBalancer); + * + * 2. 构造出 RetryTemplate + * RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy); + * + * 2.1 使用 LoadBalancedRetryFactory 生成重试策略 + * RetryTemplate template = new RetryTemplate(); + * BackOffPolicy backOffPolicy = lbRetryFactory.createBackOffPolicy(serviceName); + * template.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy); + * + * 2.2 使用 LoadBalancedRetryFactory 生成重试监听器 + * RetryListener[] retryListeners = lbRetryFactory.createRetryListeners(serviceName); + * template.setListeners(retryListeners); + * ... + * + * 3. 使用 RetryTemplate 执行 + * return template.execute(context -> { + * + * 1. 从 loadBalancerClientFactory 中获取 LoadBalancerLifecycle 类型的bean + * Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator + * .getSupportedLifecycleProcessors( + * loadBalancerFactory.getInstances(serviceName, LoadBalancerLifecycle.class), + * RetryableRequestContext.class, ResponseData.class, ServiceInstance.class); + * + * 2. 回调 LoadBalancerLifecycle#onStart 生命周期方法 + * supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); + * + * 3. 通过负载均衡策略选择出唯一的 serviceInstance + * serviceInstance = loadBalancerClient.choose(serviceName, lbRequest); + * + * 4. 执行请求 + * ClientHttpResponse response = loadBalancer.execute(serviceName, + * serviceInstance, lbRequest); + * + * }) + * + * */ +``` + +### BlockingLoadBalancerClient#execute + +1. 执行请求过程中会回调 LoadBalancerLifecycle 生命周期方法 +2. 负载均衡得到 serviceInstance 构造出 HttpRequest 后,会使用 LoadBalancerRequestTransformer 对 HttpRequest 进行增强 + +> 注:不建议将 LoadBalancerLifecycle、ServiceInstanceListSupplier、ReactorLoadBalancer 注册到应用程序中,而是通过 LoadBalancerClientSpecification 的方式为每一个 serviceInstance 设置独立的bean,从而实现不同的 serviceInstance 使用不同的 负载均衡策略 + +```java +/** + * BlockingLoadBalancerClient 执行请求 + * {@link BlockingLoadBalancerClient#execute(String, LoadBalancerRequest)} + * + * 1. 根据 serviceId 获取配置的 hint 值,默认是 default。可以设置 spring.cloud.loadbalancer.hint.serviceName=hint1 来设置该值 + * String hint = getHint(serviceId); + * + * 2. 装饰成 LoadBalancerRequestAdapter + * LoadBalancerRequestAdapter lbRequest = new LoadBalancerRequestAdapter<>(request, + * new DefaultRequestContext(request, hint)); + * + * 3. 从 loadBalancerClientFactory 中获取 LoadBalancerLifecycle 类型的bean + * Set supportedLifecycleProcessors = loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class) + * 注:LoadBalancerClientFactory 继承 NamedContextFactory , 会根据 serviceId 创建一个IOC容器,再从这个指定的IOC容器中获取bean,创建的IOC容器会存到Map中 + * + * 4. 回调 LoadBalancerLifecycle#onStart 生命周期方法 + * supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); + * + * 5. 负载均衡选择出唯一的 serviceInstance + * 5.1 通过 loadBalancerClientFactory 获取 ReactiveLoadBalancer 实例。 + * ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance(serviceId); + * + * 5.2 选择出 ServiceInstance + * Response loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block(); + * ServiceInstance serviceInstance = loadBalancerResponse.getServer(); + * + * 注:ReactorLoadBalancer 依赖 ServiceInstanceListSupplier 得到 List 然后根据其负载均衡策略得到唯一的 serviceInstance + * 而 ServiceInstanceListSupplier 默认是通过获取 DiscoveryClient 得到 List,然后根据 ServiceInstanceListSupplier + * 的逻辑过滤掉一些 + * + * 6. 若 serviceInstance 是空,先回调生命周期方法然后报错 + * supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete( + * new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse()))); + * throw new IllegalStateException("No instances available for " + serviceId); + * + * 7. 装饰一下 serviceInstance + * DefaultResponse defaultResponse = new DefaultResponse(serviceInstance); + * + * 8. 回调 LoadBalancerLifecycle#onStartRequest 生命周期方法 + * supportedLifecycleProcessors + * .forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance))); + * + * 9. 执行请求,其实就是回调RestTemplate的拦截方法 + * T response = request.apply(serviceInstance); + * + * 9.1 构造出 HttpRequest,其目的是会根据 instance 生成 uri + * HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer); + * + * 9.2 遍历 LoadBalancerRequestTransformer 对 serviceRequest 进行增强 + * for (LoadBalancerRequestTransformer transformer : this.transformers) { + * serviceRequest = transformer.transformRequest(serviceRequest, instance); + * } + * + * 9.3 放行请求,最终会发送Http请求 + * execution.execute(serviceRequest, body); + * + * + * 10. 回调 LoadBalancerLifecycle#onComplete 生命周期方法 + * supportedLifecycleProcessors + * .forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, + * lbRequest, defaultResponse, clientResponse))); + * */ +``` + +### ReactorLoadBalancer + +示例代码 + +```java +@LoadBalancerClient(name = "s1", configuration = { MyLoadBalancer.class, MyServiceInstanceListSupplier.class }) +@LoadBalancerClients({ @LoadBalancerClient(name = "s2", configuration = MyRandomLoadBalancer.class), + @LoadBalancerClient(name = "s3", configuration = MyRoundRobinLoadBalancer.class), }) +public class LoadBalancerClientConfig { + +} +``` + +```java +/** + * ReactorLoadBalancer 是一个负载均衡器,会根据其负载均衡逻辑从 ServiceInstanceListSupplier 返回的 List 中筛选出唯一的 ServiceInstance。 + * + * 再执行负载均衡请求时会用到 ReactorLoadBalancer + * {@link BlockingLoadBalancerClient#execute(String, LoadBalancerRequest)} + * ReactiveLoadBalancer loadBalancer = loadBalancerClientFactory.getInstance(serviceId); + * + * 而 LoadBalancerClientFactory 继承 NamedContextFactory,获取实例的特点是 每个serviceId对应一个IOC容器,实例是从对应的IOC容器中得到的, + * LoadBalancerClientFactory 构造的IOC容器默认会注册配置类 LoadBalancerClientConfiguration, + * 而 LoadBalancerClientConfiguration 其目的是注册了 ServiceInstanceListSupplier、ReactorLoadBalancer + * 而且都有 @ConditionalOnMissingBean 条件,若我们想自定义可以给IOC容器设置配置类,从而让 @ConditionalOnMissingBean 不匹配。 + * 可以使用 @LoadBalancerClient 或者直接注册 LoadBalancerClientSpecification 类型的bean到容器中。 + * */ +``` + +```java +public interface ReactorLoadBalancer extends ReactiveLoadBalancer { + + /** + * Choose the next server based on the load balancing algorithm. + * @param request - an input request + * @return - mono of response + */ + @SuppressWarnings("rawtypes") + Mono> choose(Request request); + + default Mono> choose() { + return choose(REQUEST); + } + +} +``` + +```java +// 这只是伪代码 +@Configuration(proxyBeanMethods = false) +@ConditionalOnDiscoveryEnabled +public class LoadBalancerClientConfiguration { + + @Bean + @ConditionalOnMissingBean + public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, + LoadBalancerClientFactory loadBalancerClientFactory) { + String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); + return new RoundRobinLoadBalancer( + loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); + } + + @Bean + @ConditionalOnMissingBean + public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( + ConfigurableApplicationContext context) { + return ServiceInstanceListSupplier.builder().withDiscoveryClient().withCaching().build(context); + } + +} +``` + +### ServiceInstanceListSupplier + +示例代码 + +```java +public class MyServiceInstanceListSupplier { + + @Bean + public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( + ConfigurableApplicationContext context) { + return ServiceInstanceListSupplier.builder() + //.withDiscoveryClient() // 通过 ReactiveDiscoveryClient 获取 List + .withBlockingDiscoveryClient() // 通过 DiscoveryClient 获取 List + // 下面配置的是通过什么方式 过滤 List + // .withZonePreference() // spring.cloud.loadbalancer.zone" 属性值与 serviceInstance.getMetadata().get("zone") 进行匹配 + // .withBlockingHealthChecks() // spring.cloud.loadbalancer.healthCheck.* 属性定义的的规则来过滤 + // .withRequestBasedStickySession() spring.cloud.loadbalancer.stickySession.instanceIdCookieName 属性值过滤 serviceInstance.getInstanceId() + // .withSameInstancePreference() + .withCaching() // 会使用到 LoadBalancerCacheManager 缓存 List + .build(context); + } + +} +``` + +```java +/** + * ServiceInstanceListSupplier 是用来返回 List 的。一般是根据 ReactiveDiscoveryClient 或者 DiscoveryClient 得到 serviceId 注册的 List, + * 然后根据其逻辑过滤掉不满足的,再返回最终的 List + * + * 提供了Builder快速构建 + * 响应式(获取 ReactiveDiscoveryClient 的bean得到有哪些服务):ServiceInstanceListSupplier.builder().withDiscoveryClient().build(context); + * 非响应式(获取 DiscoveryClient 的bean得到有哪些服务):ServiceInstanceListSupplier.builder().withBlockingDiscoveryClient().build(context); + * + * + * 这个 LoadBalancerClientConfiguration 配置类,定义了很多种 ServiceInstanceListSupplier。可以通过设置属性值决定应用哪种 + * spring.cloud.loadbalancer.configurations=[default | zone-preference | health-check | request-based-sticky-session | same-instance-preference] + * */ +``` + +```java +public interface ServiceInstanceListSupplier extends Supplier>> { + + String getServiceId(); + + default Flux> get(Request request) { + return get(); + } + + static ServiceInstanceListSupplierBuilder builder() { + return new ServiceInstanceListSupplierBuilder(); + } + +} +``` + +### WebClient.Builder 实现负载均衡 + +WebClient.Builder 是执行响应式请求的工具类。下面是让 WebClient.Builder 具有负载均衡能力的实现逻辑。 + +`spring-cloud-commons.jar!/META-INF/spring.factories`的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\ + org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration +``` + +```java +/** + * {@link LoadBalancerBeanPostProcessorAutoConfiguration} + * 注册 LoadBalancerWebClientBuilderBeanPostProcessor 其依赖 DeferringLoadBalancerExchangeFilterFunction。 + * 其是一个后置处理器,会在 postProcessBeforeInitialization 时过滤类型是 WebClient.Builder 且有 @LoadBalanced 的bean ,为 bean 增加filter + * `((WebClient.Builder) bean).filter(DeferringLoadBalancerExchangeFilterFunction)` + * + * 注册 DeferringLoadBalancerExchangeFilterFunction 其依赖 LoadBalancedExchangeFilterFunction,没啥用具体逻辑是委托给 LoadBalancedExchangeFilterFunction 执行的。 + * + * {@link ReactorLoadBalancerClientAutoConfiguration} + * 注册 ReactorLoadBalancerExchangeFilterFunction 其依赖 ReactiveLoadBalancer.Factory、LoadBalancerProperties。 + * 其定义了负载均衡的实现逻辑,比如要回调 LoadBalancerLifecycle 的方法,要通过 ReactiveLoadBalancer.Factory 负载均衡得到 ServiceInstance + * + * Tips:LoadBalancerClientFactory 是 ReactiveLoadBalancer.Factory 的实现类。 + * + * 注册 RetryableLoadBalancerExchangeFilterFunction 其依赖 ReactiveLoadBalancer.Factory、LoadBalancerProperties、LoadBalancerRetryPolicy。 + * 大致逻辑和 ReactorLoadBalancerExchangeFilterFunction 一样,只不过增加了重试的实现 + * + * 注册 LoadBalancerRetryPolicy 其依赖 LoadBalancerProperties。 + * 就是重试策略,没啥特别的 + * + * + * 总结:通过 LoadBalancerWebClientBuilderBeanPostProcessor 给 WebClient.Builder 增加 Filter,所以使用 WebClient.Builder 执行请求时会执行 Filter 的逻辑。 + * DeferringLoadBalancerExchangeFilterFunction 的逻辑是 回调 LoadBalancerLifecycle 的方法,使用 LoadBalancerClientFactory 生成的负载均衡器得到唯一的 ServiceInstance, + * 根据 ServiceInstance 的信息 修改请求的信息,从而实现负载均衡请求。 + * + * WebClient.Builder -> ReactorLoadBalancerExchangeFilterFunction -> LoadBalancerLifecycle -> LoadBalancerClientFactory + * */ +``` diff --git a/docs/SpringCloud/spring-cloud-gateway-source-note.md b/docs/SpringCloud/spring-cloud-gateway-source-note.md new file mode 100644 index 00000000..e1697ac5 --- /dev/null +++ b/docs/SpringCloud/spring-cloud-gateway-source-note.md @@ -0,0 +1,1312 @@ +# 说明 + +Author: [haitaoss](https://github.com/haitaoss) + +源码阅读仓库: [spring-cloud-gateway](https://github.com/haitaoss/spring-cloud-gateway) + +参考资料和需要掌握的知识: + +- [Spring WebFlux 源码分析](https://github.com/haitaoss/spring-framework/blob/source-v5.3.10/note/springwebflux-source-note.md) +- [Spring Cloud Circuit Breaker](https://github.com/haitaoss/spring-cloud-circuitbreaker/blob/source-v2.1.5/note/spring-cloud-circuitbreaker-source-note.md) +- [Spring Cloud Commons](https://github.com/haitaoss/spring-cloud-commons/blob/source-v3.1.5/note/spring-cloud-commons-source-note.md#blockingloadbalancerclientexecute) +- [Spring Cloud Gateway 官网文档](https://docs.spring.io/spring-cloud-gateway/docs/3.1.5/reference/html/) + +# Spring Cloud Gateway 介绍 + +功能:**接收请求**并根据匹配的**路由**进行**转发**。 + +术语: + +- **Route**: 是路由规则的描述。它由 ID、目标 URI、**Predicate 集合**和**Filter 集合**组成。如果 Predicate 为真,则路由匹配。 +- **Predicate**: 这是一个 Java 8 函数接口。输入类型是 `ServerWebExchange` ,所以可以匹配 HTTP 请求中的任何内容,例如 Header 或参数。 +- **Filter**: 这些是使用**特定工厂**构建的 `GatewayFilter` 的实例。使用这个可以在发送下游请求之前或之后修改请求和响应。 + +Spring Cloud Gateway 是基于 Spring WebFlux 实现的,是通过注册 WebFlux 的生命周期组件实现控制请求执行。 + +```properties +# Spring WebFlux 处理请求的生命周期 +客户端请求 -> WebFlux服务 -> WebFilter -> DispatcherHandler -> HandlerMapping -> HandlerAdapter -> 执行Handler方法 +``` + +Gateway 通过注册 [RoutePredicateHandlerMapping](#RoutePredicateHandlerMapping) 实现核心逻辑 + +# Gateway 自动装配 + +`spring-cloud-gateway-server.jar!META-INF/spring.factories`的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\ + org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\ + org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration,\ + org.springframework.cloud.gateway.config.GatewayReactiveOAuth2AutoConfiguration +``` + +`spring-cloud-gateway-webflux.jar!META-INF/spring.factories`的内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.gateway.webflux.config.ProxyResponseAutoConfiguration + +org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebFlux=\ +org.springframework.cloud.gateway.webflux.config.ProxyResponseAutoConfiguration +``` + +`spring-cloud-gateway-mvc.jar!META-INF/spring.factories`的内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.gateway.mvc.config.ProxyResponseAutoConfiguration + +org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\ +org.springframework.cloud.gateway.mvc.config.ProxyResponseAutoConfiguration +``` + +## GatewayClassPathWarningAutoConfiguration + +作用:检验启动环境不能是 SpringMVC + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) +public class GatewayClassPathWarningAutoConfiguration { + + @Configuration(proxyBeanMethods = false) + // SpringMVC 会存在这个类,所以条件会满足,这个类就会注册到BeanFactory中 + @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet") + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) + protected static class SpringMvcFoundOnClasspathConfiguration { + + public SpringMvcFoundOnClasspathConfiguration() { + // 实例化就直接抛出异常 + throw new MvcFoundOnClasspathException(); + } + + } +} +``` + +## GatewayAutoConfiguration + +### 类图 + +> RouteLocator 是为了得到`Flux`,可以使用 RouteLocatorBuilder 很方便的生成 RouteLocator。 +> +> RouteDefinitionRouteLocator 是会根据 RouteDefinition 生成 Route ,而 RouteDefinition 是由 RouteDefinitionLocator 生成的。 +> +> Route 是由 AsyncPredicate 和 GatewayFilter 组成的。而 AsyncPredicate 由 RoutePredicateFactory 生成,GatewayF 创建 ilter 由 GatewayFilterFactory + +![RouteLocator](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png) + +> RoutePredicateHandlerMapping 通过 RouteLocator 得到的 `Flux` ,遍历执行`Route.getPredicate().apply(ServerWebExchange)` 返回`true`说明命中了路由规则,将命中的 Route 存到 ServerWebExchange 中,然后执行 FilteringWebHandler 。 +> +> FilteringWebHandler 的逻辑就是执行 GlobalFilter + GatewayFilter + +![Route](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png) + +### 源码 + +可以自定义这些类型的 bean 实现功能的扩展:**RouteLocator**、**HttpHeaderFilter**、**GlobalFilter** 、**GatewayFilterFactory**、**RoutePredicateFactory** + +默认通过 @Bean 注册了很多的 GlobalFilter、GatewayFilterFactory、RoutePredicateFactory 且都是有条件注解的,可以通过设置属性不进行默认注册。主要是有这[三个条件注解](#conditionalonenabledglobalfilterconditionalonenabledfilterconditionalonenabledpredicate) + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) +@ConditionalOnClass(DispatcherHandler.class) +public class GatewayAutoConfiguration { + + /** + * 是工具类,可用来构造出 RouteLocator 实例。若想使用编码的方式配置 Route,推荐使用这个 RouteLocatorBuilder。 + */ + @Bean + public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) { + return new RouteLocatorBuilder(context); + } + + /** + * 实现 RouteDefinitionLocator 接口,其特点是根据 GatewayProperties(配置文件中定义的route) 的内容返回 List + */ + @Bean + @ConditionalOnMissingBean + public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) { + return new PropertiesRouteDefinitionLocator(properties); + } + + /** + * 实现 RouteDefinitionRepository 接口,定义如何 save、delete RouteDefinition + * 实现 RouteDefinitionLocator 接口,其特点是从缓存(Map、Redis等等)中得到 List + */ + @Bean + @ConditionalOnMissingBean(RouteDefinitionRepository.class) + public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() { + return new InMemoryRouteDefinitionRepository(); + } + + /** + * 聚合所有的 RouteDefinitionLocator + */ + @Bean + @Primary + public RouteDefinitionLocator routeDefinitionLocator(List routeDefinitionLocators) { + return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators)); + } + + /** + * 是一个工具类,可用来 实例化类、属性绑定和属性校验(JSR303) + * GatewayFilterFactory、RoutePredicateFactory 会使用 ConfigurationService 生成 Config 实例,并完成属性绑定和属性校验(JSR303) + */ + @Bean + public ConfigurationService gatewayConfigurationService(BeanFactory beanFactory, + @Qualifier("webFluxConversionService") ObjectProvider conversionService, + ObjectProvider validator) { + return new ConfigurationService(beanFactory, conversionService, validator); + } + + /** + * RouteLocator 接口是用来生成 Flux 的。 + * + * 依赖 RouteDefinitionLocator 得到 RouteDefinition , 而 RouteDefinition 中定义了 FilterDefinition、PredicateDefinition, + * 会使用 GatewayFilterFactory、RoutePredicateFactory 生成 GatewayFilter、Predicate ,然后配置给 Route 实例 + * 而 GatewayFilterFactory、RoutePredicateFactory 继承这两个接口 ShortcutConfigurable、Configurable,这两个接口是为了得到 Config 。 + * 会使用 ConfigurationService 生成 Config 实例,并完成属性绑定和属性校验(JSR303)。 + * GatewayFilterFactory、RoutePredicateFactory 会根据 Config 来生成 GatewayFilter、Predicate + */ + @Bean + public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, + List gatewayFilters, List predicates, + RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) { + return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties, + configurationService); + } + + /** + * 聚合所有的 RouteLocator 。所以我们可以自定义 RouteLocator 自定义路由 + */ + @Bean + @Primary + @ConditionalOnMissingBean(name = "cachedCompositeRouteLocator") + public RouteLocator cachedCompositeRouteLocator(List routeLocators) { + return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators))); + } + + /** + * 实现 ApplicationListener 接口, + * 收到关心的事件(ContextRefreshedEvent、RefreshScopeRefreshedEvent、InstanceRegisteredEvent、ParentHeartbeatEvent、HeartbeatEvent) + * 就会 发布一个 RefreshRoutesEvent 事件 + */ + @Bean + @ConditionalOnClass(name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor") + public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) { + return new RouteRefreshListener(publisher); + } + + /** + * FilteringWebHandler 实现 WebHandler 接口,可以理解成 SpringMVC 中的 handler, + * RoutePredicateHandlerMapping.getHandler() 返回的就是 FilteringWebHandler, + * FilteringWebHandler 就是遍历执行 GlobalFilter + Route配置的WebFilter + */ + @Bean + public FilteringWebHandler filteringWebHandler(List globalFilters) { + return new FilteringWebHandler(globalFilters); + } + + /** + * RoutePredicateHandlerMapping 实现 HandlerMapping 接口。 + * + * RoutePredicateHandlerMapping#getHandler 是根据 RouteLocator 得到的 List 遍历执行 Route.getPredicate().apply(ServerWebExchange) + * 为 true 就说明匹配,会返回 FilteringWebHandler + */ + @Bean + @ConditionalOnMissingBean + public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler, + RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) { + return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment); + } + + // 生成 Predicate 的工厂 + @Bean + @ConditionalOnEnabledPredicate + public AfterRoutePredicateFactory afterRoutePredicateFactory() { + return new AfterRoutePredicateFactory(); + } + + // 生成 GatewayFilter 的 + @Bean + @ConditionalOnEnabledFilter + public AddRequestHeaderGatewayFilterFactory addRequestHeaderGatewayFilterFactory() { + return new AddRequestHeaderGatewayFilterFactory(); + } + + // 实现 HttpHeadersFilter 接口。 NettyRoutingFilter、WebsocketRoutingFilter 会依赖这种类型的bean,用来对 Header 进行修改 + @Bean + @ConditionalOnProperty(name = "spring.cloud.gateway.x-forwarded.enabled", matchIfMissing = true) + public XForwardedHeadersFilter xForwardedHeadersFilter() { + return new XForwardedHeadersFilter(); + } + + // 会使用这个执行 Http、Https 请求,同时依赖 HttpHeadersFilter 用来对 Header 进行修改 + @Bean + @ConditionalOnEnabledGlobalFilter + public NettyRoutingFilter routingFilter(HttpClient httpClient, + ObjectProvider> headersFilters, HttpClientProperties properties) { + return new NettyRoutingFilter(httpClient, headersFilters, properties); + } + // HttpHeaderFilter beans ... + // GlobalFilter beans ... + // Predicate Factory beans ... + // GatewayFilter Factory beans ... + // GatewayActuatorConfiguration 会注册 Endpoint 用于查看、新增、更新、删除 RouteDefinition +} +``` + +## GatewayResilience4JCircuitBreakerAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) +@ConditionalOnClass({ DispatcherHandler.class, ReactiveResilience4JAutoConfiguration.class, + ReactiveCircuitBreakerFactory.class, ReactiveResilience4JCircuitBreakerFactory.class }) +public class GatewayResilience4JCircuitBreakerAutoConfiguration { + + /** + * SpringCloudCircuitBreakerResilience4JFilterFactory 实现 GatewayFilterFactory 接口, + * 其核心逻辑是使用 ReactiveCircuitBreaker 来执行业务逻辑,当 出现异常 或者 路由请求返回的状态码是期望值 就 + * 直接使用 DispatcherHandler 来执行 fallbackUrl,可以理解成使用 fallbackUrl 重新执行一次请求。 + * 并且会往 ServerWebExchange 设置一个key记录异常对象。 + */ + @Bean + @ConditionalOnBean(ReactiveResilience4JCircuitBreakerFactory.class) + @ConditionalOnEnabledFilter + public SpringCloudCircuitBreakerResilience4JFilterFactory springCloudCircuitBreakerResilience4JFilterFactory( + ReactiveResilience4JCircuitBreakerFactory reactiveCircuitBreakerFactory, + ObjectProvider dispatcherHandler) { + return new SpringCloudCircuitBreakerResilience4JFilterFactory(reactiveCircuitBreakerFactory, dispatcherHandler); + } + + /** + * FallbackHeadersGatewayFilterFactory 实现 GatewayFilterFactory 接口, + * 其核心逻辑:如果请求是 fallbackUrl 执行的(通过异常key判断),那就设置一些请求头 + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledFilter + public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() { + return new FallbackHeadersGatewayFilterFactory(); + } + +} +``` + +## GatewayNoLoadBalancerClientAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnMissingClass("org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer") +@ConditionalOnMissingBean(ReactiveLoadBalancer.class) +@EnableConfigurationProperties(GatewayLoadBalancerProperties.class) +@AutoConfigureAfter(GatewayReactiveLoadBalancerClientAutoConfiguration.class) +public class GatewayNoLoadBalancerClientAutoConfiguration { + + /** + * NoLoadBalancerClientFilter 实现 GlobalFilter 接口,也就是每个 Route 的请求都会执行。 + * 功能:路由的Url 有 lb 前缀 就直接抛出异常,也就是说不支持 负载均衡的路由 + * + * BeanFactory 中没有 ReactiveLoadBalancerClientFilter 才会生效。 + */ + @Bean + @ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class) + public NoLoadBalancerClientFilter noLoadBalancerClientFilter(GatewayLoadBalancerProperties properties) { + return new NoLoadBalancerClientFilter(properties.isUse404()); + } + + protected static class NoLoadBalancerClientFilter implements GlobalFilter, Ordered { + + private final boolean use404; + + public NoLoadBalancerClientFilter(boolean use404) { + this.use404 = use404; + } + + @Override + public int getOrder() { + return LOAD_BALANCER_CLIENT_FILTER_ORDER; + } + + @Override + @SuppressWarnings("Duplicates") + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); + String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); + // url 没有 lb 前缀 就放行 + if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) { + return chain.filter(exchange); + } + // 不能处理 lb:// 所以 直接报错 + throw NotFoundException.create(use404, "Unable to find instance for " + url.getHost()); + } + + } + +} +``` + +## GatewayMetricsAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".enabled", matchIfMissing = true) +@EnableConfigurationProperties(GatewayMetricsProperties.class) +@ConditionalOnClass({ DispatcherHandler.class, MeterRegistry.class, MetricsAutoConfiguration.class }) +public class GatewayMetricsAutoConfiguration { + + // 会从 ServerWebExchange 中得到 请求的Method、响应的状态码等 + @Bean + public GatewayHttpTagsProvider gatewayHttpTagsProvider() { + return new GatewayHttpTagsProvider(); + } + + // 会从 ServerWebExchange 中得到 匹配的路由地址 + @Bean + @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.tags.path.enabled") + public GatewayPathTagsProvider gatewayPathTagsProvider() { + return new GatewayPathTagsProvider(); + } + + // 会从 ServerWebExchange 中得到 routId、route uri + @Bean + public GatewayRouteTagsProvider gatewayRouteTagsProvider() { + return new GatewayRouteTagsProvider(); + } + + // 将 GatewayMetricsProperties 的信息映射成 Tags + @Bean + public PropertiesTagsProvider propertiesTagsProvider(GatewayMetricsProperties properties) { + return new PropertiesTagsProvider(properties.getTags()); + } + + /** + * GatewayMetricsFilter 实现 GlobalFilter 接口, + * 将 List 返回的信息记录到 MeterRegistry 中 + */ + @Bean + @ConditionalOnBean(MeterRegistry.class) + @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.enabled", matchIfMissing = true) + public GatewayMetricsFilter gatewayMetricFilter(MeterRegistry meterRegistry, + List tagsProviders, GatewayMetricsProperties properties) { + return new GatewayMetricsFilter(meterRegistry, tagsProviders, properties.getPrefix()); + } + + /** + * RouteDefinitionMetrics 实现 ApplicationListener 接口, + * 收到事件的逻辑是 RouteDefinitionLocator.getRouteDefinitions().count() 记录到 MeterRegistry 中 + */ + @Bean + @ConditionalOnBean(MeterRegistry.class) + @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.enabled", matchIfMissing = true) + public RouteDefinitionMetrics routeDefinitionMetrics(MeterRegistry meterRegistry, + RouteDefinitionLocator routeDefinitionLocator, GatewayMetricsProperties properties) { + return new RouteDefinitionMetrics(meterRegistry, routeDefinitionLocator, properties.getPrefix()); + } + +} +``` + +## GatewayRedisAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnBean(ReactiveRedisTemplate.class) +@ConditionalOnClass({ RedisTemplate.class, DispatcherHandler.class }) +@ConditionalOnProperty(name = "spring.cloud.gateway.redis.enabled", matchIfMissing = true) +class GatewayRedisAutoConfiguration { + + /** + * RedisRouteDefinitionRepository 实现 RouteDefinitionRepository 接口。 + * 使用 redis 作为缓存层,存储 RouteDefinition + */ + @Bean + @ConditionalOnProperty(value = "spring.cloud.gateway.redis-route-definition-repository.enabled", + havingValue = "true") + @ConditionalOnClass(ReactiveRedisTemplate.class) + public RedisRouteDefinitionRepository redisRouteDefinitionRepository( + ReactiveRedisTemplate reactiveRedisTemplate) { + return new RedisRouteDefinitionRepository(reactiveRedisTemplate); + } + + // ... +} +``` + +## GatewayDiscoveryClientAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) +@ConditionalOnClass({ DispatcherHandler.class, CompositeDiscoveryClientAutoConfiguration.class }) +@EnableConfigurationProperties +public class GatewayDiscoveryClientAutoConfiguration { + + /** + * 这是一个 PathRoutePredicateFactory,根据 serviceId 进行路由 + * @return + */ + public static List initPredicates() { + ArrayList definitions = new ArrayList<>(); + // TODO: add a predicate that matches the url at /serviceId? + + // add a predicate that matches the url at /serviceId/** + PredicateDefinition predicate = new PredicateDefinition(); + predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class)); + predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'"); + definitions.add(predicate); + return definitions; + } + + /** + * 这是一个 RewritePathGatewayFilterFactory,移除 serviceId 路径前缀 + * @return + */ + public static List initFilters() { + ArrayList definitions = new ArrayList<>(); + + // add a filter that removes /serviceId by default + FilterDefinition filter = new FilterDefinition(); + filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class)); + String regex = "'/' + serviceId + '/?(?.*)'"; + String replacement = "'/${remaining}'"; + filter.addArg(REGEXP_KEY, regex); + filter.addArg(REPLACEMENT_KEY, replacement); + definitions.add(filter); + + return definitions; + } + + /** + * DiscoveryLocatorProperties 类上标注了 @ConfigurationProperties("spring.cloud.gateway.discovery.locator") + * 也就是可以通过配置属性的方式设置属性值,下面的逻辑是设置默认值的意思。 + * DiscoveryClientRouteDefinitionLocator 会使用这两个属性会作为生成 RouteDefinition 的 Predicate 和 Filter + * @return + */ + @Bean + public DiscoveryLocatorProperties discoveryLocatorProperties() { + DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties(); + // 默认就设置 PathRoutePredicateFactory + properties.setPredicates(initPredicates()); + // 默认就设置 RewritePathGatewayFilterFactory + properties.setFilters(initFilters()); + return properties; + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled", matchIfMissing = true) + public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration { + + /** + * DiscoveryClientRouteDefinitionLocator 实现 RouteDefinitionLocator。 + * 会根据 ReactiveDiscoveryClient.getServices() 返回的 Flux 生成 Flux + * 每个 RouteDefinition 是由 ServiceInstance + DiscoveryLocatorProperties 的内容 配置 路由Uri、Predicate、Filter + * 大部分属性值是通过解析 SPEL 表达式得到的,其中根对象是 ServiceInstance,所以说 编写的 SPEL 表达式可以引用 ServiceInstance 中的属性 + * + * @param discoveryClient + * @param properties + * @return + */ + @Bean + @ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled") + public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator( + ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) { + return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties); + } + + } + +} +``` + +## SimpleUrlHandlerMappingGlobalCorsAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(SimpleUrlHandlerMapping.class) +@ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping", + matchIfMissing = false) +public class SimpleUrlHandlerMappingGlobalCorsAutoConfiguration { + + @Autowired + private GlobalCorsProperties globalCorsProperties; + + @Autowired + private SimpleUrlHandlerMapping simpleUrlHandlerMapping; + + /** + * 为 SimpleUrlHandlerMapping 配置 跨域配置信息 + */ + @PostConstruct + void config() { + simpleUrlHandlerMapping.setCorsConfigurations(globalCorsProperties.getCorsConfigurations()); + } + +} +``` + +## GatewayReactiveLoadBalancerClientAutoConfiguration + +```java +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass({ ReactiveLoadBalancer.class, LoadBalancerAutoConfiguration.class, DispatcherHandler.class }) +@EnableConfigurationProperties(GatewayLoadBalancerProperties.class) +public class GatewayReactiveLoadBalancerClientAutoConfiguration { + + /** + * ReactiveLoadBalancerClientFilter 实现 GlobalFilter 接口。 + * 作用:url 没有 lb 协议 就放行,有 lb 就使用 LoadBalancerClientFactory 得到负载均衡后的 uri,修改 ServerWebExchange 放行filter + */ + @Bean + @ConditionalOnBean(LoadBalancerClientFactory.class) + @ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class) + @ConditionalOnEnabledGlobalFilter + public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, + GatewayLoadBalancerProperties properties) { + return new ReactiveLoadBalancerClientFilter(clientFactory, properties); + } + + /** + * LoadBalancerServiceInstanceCookieFilter 实现 GlobalFilter 接口 + * 作用:是负载均衡的路由,就添加一个Cookie键值对 + */ + @Bean + @ConditionalOnBean({ ReactiveLoadBalancerClientFilter.class, LoadBalancerClientFactory.class }) + @ConditionalOnMissingBean + @ConditionalOnEnabledGlobalFilter + public LoadBalancerServiceInstanceCookieFilter loadBalancerServiceInstanceCookieFilter( + LoadBalancerClientFactory loadBalancerClientFactory) { + return new LoadBalancerServiceInstanceCookieFilter(loadBalancerClientFactory); + } + +} +``` + +## GatewayReactiveOAuth2AutoConfiguration + +这是 Spring Security 的组件,没研究过,不知道具体的作用是啥,暂时不管了。 + +## ProxyResponseAutoConfiguration + +ProxyResponseAutoConfiguration 是 HandlerMethodArgumentResolver 接口的实现类,是用来解析方法参数的。它支持解析 `ProxyExchange` 类型的参数。`ProxyExchange` 可用来执行 Http 请求,感觉好鸡肋... + +又因为 SpringWebFlux 和 SpringMVC 的执行流程是类似的,定义的类名也是一样的(包不同),所以就搞了两套。 + +注:HandlerMethodArgumentResolver 是 SpringMVC 、SpringWebFlux 的内容,不细说了。 + +# 核心源码 + +## @ConditionalOnEnabledGlobalFilter、@ConditionalOnEnabledFilter、@ConditionalOnEnabledPredicate + +`类的定义` + +```java +@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD }) +@Conditional(OnEnabledGlobalFilter.class) +public @interface ConditionalOnEnabledGlobalFilter { + Class value() default OnEnabledGlobalFilter.DefaultValue.class; +} + +@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD }) +@Conditional(OnEnabledFilter.class) +public @interface ConditionalOnEnabledFilter { + Class> value() default OnEnabledFilter.DefaultValue.class; +} + +@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE, ElementType.METHOD }) +@Conditional(OnEnabledPredicate.class) +public @interface ConditionalOnEnabledPredicate { + Class> value() default OnEnabledPredicate.DefaultValue.class; +} +``` + +因为 @ConditionalOnEnabledGlobalFilter 上标注了 @Conditional,所以在 [ConfigurationClassPostProcessor](https://github.com/haitaoss/spring-framework/blob/source-v5.3.10/note/spring-source-note.md#conditional) 解析配置类时,会执行 `OnEnabledGlobalFilter#matches(ConditionContext,AnnotatedTypeMetadata)` 结果是`true`才会将 bean 注册到 BeanFactory 中 + +![OnEnabledComponent](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png) + +```java +/** + * {@link Condition#matches(ConditionContext, AnnotatedTypeMetadata)} + * {@link SpringBootCondition#matches(ConditionContext, AnnotatedTypeMetadata)} + * {@link OnEnabledComponent#getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)} + * 1. 拿到类型。若注解的 value 不是默认值就返回value值,否则就拿到方法的返回值类型。 + * Class candidate = getComponentType(annotationClass(), context, metadata); + * 2. 确定匹配结果。前缀 + 类处理后的值 + 后缀 作为key,从 Environment 获取值,值是false则不匹配,否则匹配 + * determineOutcome(candidate, context.getEnvironment()) + * + * Tips: OnEnabledComponent 定义了三个抽象方法,由子类决定返回值是啥 + * normalizeComponentName() 得到 类处理后的值 + * annotationClass() 得到 注解 + * defaultValueClass() 得到 默认值 + * */ +``` + +可以通过这种方式让默认注入的失效。 + +```properties +spring.cloud.gateway.global-filter.XX.enabled=false +spring.cloud.gateway.filter.XX.enabled=false +spring.cloud.gateway.predicate.XX.enabled=false +``` + +## NettyRoutingFilter + +是非常重要的 GlobalFilter。Gateway 是通过它执行 http、https 协议的请求,依赖 HttpClient 执行请求。 + +```java +public class NettyRoutingFilter implements GlobalFilter, Ordered { + + @Override + public int getOrder() { + return Integer.MAX_VALUE; // 最大值,说明这是最后要执行的 GatewayFilter + } + + @Override + @SuppressWarnings("Duplicates") + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + + String scheme = requestUrl.getScheme(); + // 已经路由 或者 不是 http、https 就放行 + if (isAlreadyRouted(exchange) || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) { + return chain.filter(exchange); + } + // 设置一个属性,标记 已经路由了 + setAlreadyRouted(exchange); + + /** + * 遍历执行所有的 HttpHeadersFilter 得到 HttpHeaders。 + * 也就是说可以对最终要执行的 请求头 进行加工 + * + * 注:HttpHeadersFilter 是从BeanFactory中获取的,所以我们可以自定义 HttpHeadersFilter 达到扩展的目的 + * */ + HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange); + + // 根据 Route 的元数据构造 HttpClient 然后执行请求 + Flux responseFlux = getHttpClient(route, exchange) + .headers() + .responseConnection((res, connection) -> {}); + + // 放行 + return responseFlux.then(chain.filter(exchange)); + } + +} +``` + +## WebsocketRoutingFilter + +是非常重要的 GlobalFilter。Gateway 是通过它执行 ws、wss 协议的请求,依赖 WebSocketService 执行请求。 + +```java +public class WebsocketRoutingFilter implements GlobalFilter, Ordered { + @Override + public int getOrder() { + // 在 NettyRoutingFilter 之前执行 + return Integer.MAX_VALUE - 1; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + // 有请求头 upgrade=WebSocket ,那就将 http、https 转成 ws、wss 协议 + changeSchemeIfIsWebSocketUpgrade(exchange); + + URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + String scheme = requestUrl.getScheme(); + + // 已经路由过 或者 不是 ws、wss 协议 就放行 + if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) { + return chain.filter(exchange); + } + // 标记路由了 + setAlreadyRouted(exchange); + + HttpHeaders headers = exchange.getRequest().getHeaders(); + /** + * 遍历执行所有的 HttpHeadersFilter 得到 HttpHeaders。 + * 也就是说可以对最终要执行的 请求头 进行加工 + * + * 注:HttpHeadersFilter 是从BeanFactory中获取的,所以我们可以自定义 HttpHeadersFilter 达到扩展的目的 + * */ + HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange); + + List protocols = getProtocols(headers); + + // 使用 webSocketService 执行请求。且不在执行后续的filter + return this.webSocketService.handleRequest(exchange, + new ProxyWebSocketHandler(requestUrl, this.webSocketClient, filtered, protocols)); + } +} +``` + +## RoutePredicateHandlerMapping + +RoutePredicateHandlerMapping 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的。 + +**大致流程**:由 [RouteLocator](#RouteLocator) 得到 `Flux` ,遍历执行 `Route.getPredicate().apply(exchange) == true` + +就将 Route 存到 exchange 中然后返回 [FilteringWebHandler](#FilteringWebHandler),最后会由 HandlerAdapter 执行 [FilteringWebHandler](#FilteringWebHandler) 。 + +```java +public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { + /** + * 通过依赖注入得到这些bean + */ + public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, + GlobalCorsProperties globalCorsProperties, Environment environment) { + this.webHandler = webHandler; + this.routeLocator = routeLocator; + /** + * 获取属性作为 order 值,默认是1。从而决定是 DispatcherHandler 使用的第几个 HandlerMapping, + * 因为 HandlerMapping 的特点是能处理就使用,不在使用其他的 HandlerMapping,所以优先级是很重要的。 + * */ + setOrder(environment.getProperty(GatewayProperties.PREFIX + ".handler-mapping.order", Integer.class, 1)); + // 设置同源配置信息 + setCorsConfigurations(globalCorsProperties.getCorsConfigurations()); + } + + /** + * 返回值不是 Mono.empty() 就表示 RoutePredicateHandlerMapping 命中了, + * 就会执行 HandlerAdapter.handle(serverWebExchange,webHandler) + */ + @Override + protected Mono getHandlerInternal(ServerWebExchange exchange) { + // 设置一个属性 + exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName()); + /** + * 使用 routeLocator 得到 List 遍历 + * */ + return lookupRoute(exchange) + .flatMap((Function>) r -> { + exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); + /** + * 将 Route 设置到 exchange 中 + * + * 后续流程会用到 + * {@link FilteringWebHandler#handle(ServerWebExchange)} + * */ + exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); + /** + * 会由 SimpleHandlerAdapter 处理 + * */ + return Mono.just(webHandler); + }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { + exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); + }))); + } + + protected Mono lookupRoute(ServerWebExchange exchange) { + /** + * 得到 Route,根据 Route 的 Predicate 决定是否匹配 + * */ + return this.routeLocator.getRoutes() + /** + * concatMap 的特点是 返回的内容不是 Mono.empty() 、Flux.empty() 才收集到 Flux 中 + * */ + .concatMap(route -> Mono.just(route).filterWhen(r -> { + // exchange 中存一下 routeId + exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); + /** + * 执行谓词 + * {@link AsyncPredicate.AndAsyncPredicate#apply(Object)} + * */ + return r.getPredicate().apply(exchange); + }).onErrorResume(e -> Mono.empty())) + // 拿到第一个。所以 Route 的顺序会决定最终的方法的执行 + .next(); + } + +} +``` + +## RouteLocator + +RouteLocator 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的,因为标注了 @Primary,所以 [RoutePredicateHandlerMapping](#RoutePredicateHandlerMapping) 通过依赖注入拿到的是 CachingRouteLocator。 + +CachingRouteLocator 是对 CompositeRouteLocator 的代理。功能: + +- 使用 ConcurrentHashMap 缓存结果。 +- 它实现 `ApplicationListener `接口,接收事件的处理逻辑是 **更新缓存结果**,然后发布 RefreshRoutesResultEvent 事件 + +CompositeRouteLocator 是用来聚合容器中所有的 RouteLocator 的,默认的 RouteLocator 是 [RouteDefinitionRouteLocator](#RouteDefinitionRouteLocator) + +Tips:若想通过编码的方式生成 RouteLocator 可以使用 RouteLocatorBuilder。 + +```java +public class CachingRouteLocator + implements Ordered, RouteLocator, ApplicationListener, ApplicationEventPublisherAware { + + private final RouteLocator delegate; + private final Map cache = new ConcurrentHashMap<>(); + + private ApplicationEventPublisher applicationEventPublisher; + + public CachingRouteLocator(RouteLocator delegate) { + // 是这个 CompositeRouteLocator + this.delegate = delegate; + // 使用 ConcurrentHashMap 缓存 Route,缓存中没有就执行 fetch 方法得到 + routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch); + } + + private Flux fetch() { + // 通过 delegate 得到,然后再对 Route 进行排序 + return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE); + } + + @Override + public Flux getRoutes() { + return this.routes; + } + + public Flux refresh() { + // 清空缓存 + this.cache.clear(); + return this.routes; + } + + @Override + public void onApplicationEvent(RefreshRoutesEvent event) { + // 收到事件,就执行 fetch() 也就是会会重新解析得到 List + fetch().collect(Collectors.toList()).subscribe( + list -> Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe(signals -> { + // 发布 RefreshRoutesResultEvent 事件 + applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this)); + // 重新设置缓存内容 + cache.put(CACHE_KEY, signals); + }, this::handleRefreshError), this::handleRefreshError); + } + +} +``` + +## RouteDefinitionRouteLocator + +RouteDefinitionRouteLocator 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的。它依赖 [RouteDefinitionLocator](#RouteDefinitionLocator) 得到 `Flux`。根据 RouteDefinition 记录的 PredicateDefinition 的 name 得到 `RoutePredicateFactory`, 使用 ConfigurationService 用来 实例化、属性绑定、属性校验得到泛型 Config 的实例对象,最后 RoutePredicateFactory 根据 Config 生成 AsyncPredicate。 + +根据 RouteDefinition 记录的 FilterDefinition 的 name 得到 `GatewayFilterFactory`, 使用 ConfigurationService 用来 实例化、属性绑定、属性校验得到泛型 Config 的实例对象,最后 GatewayFilterFactory 根据 Config 生成 GatewayFilter。 + +然后使用 AsyncPredicate、GatewayFilter 构造出 Route 实例 + +```java +public class RouteDefinitionRouteLocator implements RouteLocator { + /** + * 通过依赖注入得到 + */ + public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, + List predicates, List gatewayFilterFactories, + GatewayProperties gatewayProperties, ConfigurationService configurationService) { + this.routeDefinitionLocator = routeDefinitionLocator; + this.configurationService = configurationService; + /** + * 将 List 转成 Map,key 是执行 {@link RoutePredicateFactory#name()} 得到的。 + * 默认的逻辑是 类名去除 RoutePredicateFactory + * 比如 AddHeadRoutePredicateFactory 的key是 AddHead + * */ + initFactories(predicates); + /** + * 逻辑同上 {@link GatewayFilterFactory#name()} + * 默认的逻辑是 类名去除 GatewayFilterFactory + * 比如 AddRequestHeaderGatewayFilterFactory 的key是 AddRequestHeader + * */ + gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory)); + this.gatewayProperties = gatewayProperties; + } + + @Override + public Flux getRoutes() { + /** + * 通过 RouteDefinitionLocator 得到 RouteDefinition ,然后根据 RouteDefinition 转成 Route + * */ + Flux routes = this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute); + return routes; + } + + private Route convertToRoute(RouteDefinition routeDefinition) { + /** + * 会根据定义 predicates 的顺序,遍历处理。根据 predicate.getName() 找到 RoutePredicateFactory, + * 再使用 factory 生成 AsyncPredicate + * */ + AsyncPredicate predicate = combinePredicates(routeDefinition); + /** + * 先处理通过属性定义的 默认Filter(spring.cloud.gateway.defaultFilters),再根据定义 filters 的顺序,遍历处理。根据 filter.getName() 找到 GatewayFilterFactory, + * 再使用 factory 生成 GatewayFilter + * + * 最后会根据 order 进行排序。 + * */ + List gatewayFilters = getFilters(routeDefinition); + + // 构造出 Route + return Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters).build(); + } + + @SuppressWarnings("unchecked") + List loadGatewayFilters(String id, List filterDefinitions) { + ArrayList ordered = new ArrayList<>(filterDefinitions.size()); + for (int i = 0; i < filterDefinitions.size(); i++) { + FilterDefinition definition = filterDefinitions.get(i); + // 根据 definition.getName() 拿到 GatewayFilterFactory + GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName()); + if (factory == null) { + throw new IllegalArgumentException( + "Unable to find GatewayFilterFactory with name " + definition.getName()); + } + + /** + * 使用 configurationService 生成 configuration + * + * 和这个是一样的的,看这里就知道了 + * {@link RouteDefinitionRouteLocator#lookup(RouteDefinition, PredicateDefinition)} + * */ + // @formatter:off + Object configuration = this.configurationService.with(factory) + .name(definition.getName()) + .properties(definition.getArgs()) + .eventFunction((bound, properties) -> new FilterArgsEvent( + // TODO: why explicit cast needed or java compile fails + RouteDefinitionRouteLocator.this, id, (Map) properties)) + .bind(); + // @formatter:on + + if (configuration instanceof HasRouteId) { + HasRouteId hasRouteId = (HasRouteId) configuration; + // 设置 routeId + hasRouteId.setRouteId(id); + } + + // factory 根据 configuration 生成 GatewayFilter + GatewayFilter gatewayFilter = factory.apply(configuration); + if (gatewayFilter instanceof Ordered) { + ordered.add(gatewayFilter); + } + else { + // 默认的 order 值 就是 定义 filter 的顺序 + ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1)); + } + } + + return ordered; + } + + private List getFilters(RouteDefinition routeDefinition) { + List filters = new ArrayList<>(); + + if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { + /** + * 先添加通过属性定义的默认Filter + * spring.cloud.gateway.defaultFilters=[f1,f2] + * */ + filters.addAll(loadGatewayFilters(routeDefinition.getId(), + new ArrayList<>(this.gatewayProperties.getDefaultFilters()))); + } + + final List definitionFilters = routeDefinition.getFilters(); + if (!CollectionUtils.isEmpty(definitionFilters)) { + // 再添加 RouteDefinition 定义的 filter + filters.addAll(loadGatewayFilters(routeDefinition.getId(), definitionFilters)); + } + + // 排序 + AnnotationAwareOrderComparator.sort(filters); + return filters; + } + + private AsyncPredicate combinePredicates(RouteDefinition routeDefinition) { + List predicates = routeDefinition.getPredicates(); + // routeDefinition 没有定义 predicate ,就设置一个返回 ture 的 AsyncPredicate + if (predicates == null || predicates.isEmpty()) { + // this is a very rare case, but possible, just match all + return AsyncPredicate.from(exchange -> true); + } + + /** + * 获取 AsyncPredicate。 + * + * 会根据 predicate.getName() 拿到 RoutePredicateFactory,执行 RoutePredicateFactory.apply(config) 生成 AsyncPredicate + * */ + AsyncPredicate predicate = lookup(routeDefinition, predicates.get(0)); + // 遍历剩下的 predicate + for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { + AsyncPredicate found = lookup(routeDefinition, andPredicate); + /** + * and 的连接多个 predicate。返回的是这个类型 AndAsyncPredicate + * + * 其实就是不断的套娃。 + * */ + predicate = predicate.and(found); + } + + return predicate; + } + + @SuppressWarnings("unchecked") + private AsyncPredicate lookup(RouteDefinition route, PredicateDefinition predicate) { + /** + * predicates 是根据 BeanFactory 中 RoutePredicateFactory 类型的 bean 生成的。 + * 所以可以理解成是从 BeanFactory 中得到 RoutePredicateFactory。 + * */ + RoutePredicateFactory factory = this.predicates.get(predicate.getName()); + if (factory == null) { + throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName()); + } + if (logger.isDebugEnabled()) { + logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to " + + predicate.getName()); + } + + /** + * factory 实现 ShortcutConfigurable 接口,规定如何生成的 属性绑定的Map + * factory 实现 Configurable 接口,规定使用 config 是啥 + * + * configurationService 会依赖 factory 生成 属性绑定的Map 得到 Config 的类型 + * 然后使用 属性绑定的Map + ConversionsService + Validator 实例化 Config ,并且会对 Config 进行属性绑定和属性校验(JSR303) + * */ + // @formatter:off + Object config = this.configurationService.with(factory) + .name(predicate.getName()) + // 设置属性。会根据这个生成用于属性绑定的Map + .properties(predicate.getArgs()) + // 定义事件。对 config 完成属性绑定完后,会发布这个事件 + .eventFunction((bound, properties) -> new PredicateArgsEvent( + RouteDefinitionRouteLocator.this, route.getId(), properties)) + .bind(); + // @formatter:on + + // 根据 config 使用 factory 生成 AsyncPredicate + return factory.applyAsync(config); + } + +} +``` + +## RouteDefinitionLocator + +RouteDefinitionLocator 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的,因为标注了 @Primary,所以 [RouteDefinitionRouteLocator](#RouteDefinitionRouteLocator) 通过依赖注入拿到的是 CompositeRouteDefinitionLocator。 + +CompositeRouteDefinitionLocator 的作用是聚合容器中所有的 RouteDefinitionLocator。默认是注册了 [PropertiesRouteDefinitionLocator](#PropertiesRouteDefinitionLocator) 和 [InMemoryRouteDefinitionRepository](#InMemoryRouteDefinitionRepository) + +## PropertiesRouteDefinitionLocator + +PropertiesRouteDefinitionLocator 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的。 + +```java +public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator { + public PropertiesRouteDefinitionLocator(GatewayProperties properties) { + /** + * 依赖注入得到的 + * + * GatewayProperties 标注了 @ConfigurationProperties("spring.cloud.gateway") + * 所以会通过属性绑定设置值 + * */ + this.properties = properties; + } + + @Override + public Flux getRouteDefinitions() { + // 直接返回 properties.getRoutes() + return Flux.fromIterable(this.properties.getRoutes()); + } +} +``` + +看 PredicateDefinition、FilterDefinition 的构造器,就能明白属性文件为啥可以写 `Weight=group1,8` + +![image-20230428141218057](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png) + +## InMemoryRouteDefinitionRepository + +InMemoryRouteDefinitionRepository 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的。它主要是实现了 RouteDefinitionRepository 接口,而 RouteDefinitionRepository 继承 RouteDefinitionLocator,RouteDefinitionWriter 接口。 + +RouteDefinitionRepository 的职责是通过缓存的方式记录 RouteDefinition,而不是通过属性 映射成 RouteDefinition。而 [AbstractGatewayControllerEndpoint](#AbstractGatewayControllerEndpoint) 会依赖 RouteDefinitionWriter 的实例,用来缓存通过接口方式注册的 RouteDefinition。 + +![RouteDefinitionRepository](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png) + +```java +public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { + // 线程安全的 + private final Map routes = synchronizedMap(new LinkedHashMap()); + + @Override + public Mono save(Mono route) { + /** + * Gateway Endpoint 会依赖 RouteDefinitionRepository 类型的bean 记录通过 Endpoint 动态添加的 RouteDefinition + * + * 源码在这里 + * {@link AbstractGatewayControllerEndpoint#save(String, RouteDefinition)} + * */ + return route.flatMap(r -> { + if (ObjectUtils.isEmpty(r.getId())) { + return Mono.error(new IllegalArgumentException("id may not be empty")); + } + // 存到缓存中 + routes.put(r.getId(), r); + return Mono.empty(); + }); + } + + @Override + public Mono delete(Mono routeId) { + return routeId.flatMap(id -> { + if (routes.containsKey(id)) { + // 从缓存中移除 + routes.remove(id); + return Mono.empty(); + } + return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId))); + }); + } + + @Override + public Flux getRouteDefinitions() { + // 返回缓存中的值 + Map routesSafeCopy = new LinkedHashMap<>(routes); + return Flux.fromIterable(routesSafeCopy.values()); + } + +} +``` + +## AbstractGatewayControllerEndpoint + +GatewayControllerEndpoint 和 GatewayLegacyControllerEndpoint 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的,默认是注册 GatewayControllerEndpoint ,可以设置属性`spring.cloud.gateway.actuator.verbose.enabled=false` 变成让 GatewayLegacyControllerEndpoint 生效。 + +主要是提供这些功能:查看 RouteDefinitions、Routes、GlobalFilters、routefilters、routepredicates、更新或者新增 RouteDefinition、刷新 RouteDefinition。 + +更新或新增 RouteDefinition 是依赖 [RouteDefinitionRepository](#InMemoryRouteDefinitionRepository) 进行缓存。 + +刷新 RouteDefinition 是会发布 RefreshRoutesEvent 事件,该事件会有 [CachingRouteLocator](#RouteLocator) 处理 + +![AbstractGatewayControllerEndpoint](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png) + +## RouteRefreshListener + +RouteRefreshListener 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的 + +```java +public class RouteRefreshListener implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationEvent event) { + // 这是 IOC 容器 refresh 的最后阶段会发布的事件 + if (event instanceof ContextRefreshedEvent) { + ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event; + // 不是 + if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) { + /** + * 重设 + * + * 其实就是发布一个 RefreshRoutesEvent 事件, + * 该事件会由 CachingRouteLocator 接收,会对 List 进行缓存 + * {@link CachingRouteLocator#onApplicationEvent(RefreshRoutesEvent)} + * */ + reset(); + } + } + else if (event instanceof RefreshScopeRefreshedEvent || event instanceof InstanceRegisteredEvent) { + reset(); + } + else if (event instanceof ParentHeartbeatEvent) { + ParentHeartbeatEvent e = (ParentHeartbeatEvent) event; + resetIfNeeded(e.getValue()); + } + else if (event instanceof HeartbeatEvent) { + HeartbeatEvent e = (HeartbeatEvent) event; + resetIfNeeded(e.getValue()); + } + } + + private void resetIfNeeded(Object value) { + if (this.monitor.update(value)) { + reset(); + } + } + + private void reset() { + this.publisher.publishEvent(new RefreshRoutesEvent(this)); + } + +} +``` + +## FilteringWebHandler + +FilteringWebHandler 是由 [GatewayAutoConfiguration](#GatewayAutoConfiguration) 注册的。 + +拿到容器中的 `List + Route.getFilters()` 对 Filter 进行排序,紧接着按顺序执行所有的 GatewayFilter。这么说是不准确的,只有每个 filter 都执行`chain.fiter` 才会执行所有的 GatewayFilter,这其实就是责任链模式。 + +优先级最高(最后执行) 的 Filter 是 [NettyRoutingFilter](#NettyRoutingFilter) ,它是用来执行 http、https 请求的,也就是完成路由的职责。 + +```java +public class FilteringWebHandler implements WebHandler { + public FilteringWebHandler(List globalFilters) { + // 这是依赖注入得到的 + this.globalFilters = loadFilters(globalFilters); + } + + private static List loadFilters(List filters) { + return filters.stream().map(filter -> { + // 装饰成 GatewayFilter 类型 + GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter); + if (filter instanceof Ordered) { + int order = ((Ordered) filter).getOrder(); + return new OrderedGatewayFilter(gatewayFilter, order); + } + return gatewayFilter; + }).collect(Collectors.toList()); + } + @Override + public Mono handle(ServerWebExchange exchange) { + /** + * 拿到 Route 实例。这个是在前一个步骤设置的 + * {@link RoutePredicateHandlerMapping#getHandlerInternal(ServerWebExchange)} + * */ + Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); + // 拿到 Route 的 GatewayFilter + List gatewayFilters = route.getFilters(); + + // 先添加 globalFilter + List combined = new ArrayList<>(this.globalFilters); + // 再添加 route 定义的 Filter + combined.addAll(gatewayFilters); + + // 排序 + AnnotationAwareOrderComparator.sort(combined); + + /** + * 装饰成 DefaultGatewayFilterChain 执行。 + * + * 其实就是遍历执行所有的 GatewayFilter + * */ + return new DefaultGatewayFilterChain(combined).filter(exchange); + } + + private static class DefaultGatewayFilterChain implements GatewayFilterChain { + @Override + public Mono filter(ServerWebExchange exchange) { + return Mono.defer(() -> { + if (this.index < filters.size()) { + GatewayFilter filter = filters.get(this.index); + // 套娃行为 + DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1); + // 执行 + return filter.filter(exchange, chain); + } + else { + return Mono.empty(); // complete + } + }); + } + + } +} +``` diff --git a/docs/SpringCloud/spring-cloud-openfeign-source-note.md b/docs/SpringCloud/spring-cloud-openfeign-source-note.md new file mode 100644 index 00000000..97d8c8f7 --- /dev/null +++ b/docs/SpringCloud/spring-cloud-openfeign-source-note.md @@ -0,0 +1,557 @@ +# 说明 + +Author: [haitaoss](https://github.com/haitaoss) + +源码阅读仓库: [spring-cloud-openfeign](https://github.com/haitaoss/spring-cloud-openfeign) + +参考资料和需要掌握的知识: + +- [SpringBoot 源码分析](https://github.com/haitaoss/spring-boot/blob/source-v2.7.8/note/springboot-source-note.md) +- [Spring 源码分析](https://github.com/haitaoss/spring-framework) +- [Spring Cloud 官网文档](https://docs.spring.io/spring-cloud/docs/2021.0.5/reference/html/) +- [Spring Cloud Commons 官网文档](https://docs.spring.io/spring-cloud-commons/docs/3.1.5/reference/html/) +- [Spring Cloud OpenFeign 官网文档](https://docs.spring.io/spring-cloud-openfeign/docs/3.1.5/reference/html/) +- [Feign 官方文档](https://github.com/OpenFeign/feign#readme) + +# Spring Cloud OpenFeign 介绍 + +[Feign](https://github.com/haitaoss/feign) 是一个声明式的 Web 服务客户端,它使 Java 编写 Web 服务客户端变得更加容易。其实就是通过 JDK 代理生成接口的代理对象,方法的执行就是执行 Http 请求。而 OpenFeign 的作用是通过自动装配将 Feign 集成到应用程序中。主要是有这几个特性: + +1. 整合 [Spring Cache](https://github.com/haitaoss/spring-framework/blob/source-v5.3.10/note/spring-source-note.md#cacheinterceptorinvoke) ,代理 FeignClient 接口方法,增加上缓存相关的逻辑。 +2. 整合 CircuitBreaker ,代理 FeignClient 接口方法,方法的执行委托给 CircuitBreaker 控制 +3. 整合 [spring-cloud-loadbalancer](https://github.com/haitaoss/spring-cloud-commons/blob/source-v3.0.1/note/spring-cloud-commons-source-note.md#loadbalancerclient) ,让 Feign 使用负载均衡的 HTTP 客户端 发送请求 + +# 核心功能源码分析 + +## OpenFeign 自动装配原理 + +`spring-cloud-openfeign-core.jar!META-INF/spring.factories` 的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.openfeign.FeignAutoConfiguration,\ + org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\ + org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration,\ + org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration +``` + +### FeignAutoConfiguration + +主要是注册了 FeignContext、Targeter、CachingCapability、Client: + +- FeignContext 是用来隔离不同 FeignClient 的容器,每个 FeignClient 有单独的 IOC 容器,容器中默认注册了 FeignClient 需要的 bean。 +- Targeter 是用来生成 FeignClient 接口实现类的,只是配置而已。生成接口代理类的逻辑是由 Feign 实现的。 +- CachingCapability 是用来配置 Feign.Builder 的,主要是对 InvocationHandlerFactory 进行增强,而 InvocationHandlerFactory 是用来生成 InvocationHandler 从而让方法的执行委托给 [CacheInterceptor](https://github.com/haitaoss/spring-framework/blob/source-v5.3.10/note/spring-source-note.md#cacheinterceptorinvoke) 执行,这是属于 SpringCache 的内容了,不展开说了。 +- Client 是执行 HTTP 请求的工具,比如 ApacheHttpClient、OkHttpClient。 + +```java +/** + * FeignAutoConfiguration + * 注册三个绑定属性的bean @EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class, FeignEncoderProperties.class}) + * FeignClientProperties:记录 FeignClient 的配置信息,比如 RequestInterceptor 等等 + * FeignHttpClientProperties:记录 HttpClient 的配置信息,最大连接数、连接的ttl等等 + * FeignEncoderProperties: 是否从响应头 Content-Type 获取响应体的编码,会使用这个编码对响应体解码成字符串(默认是UTF-8) + * + * 注册 HasFeatures 是用来描述系统有 Feign 的功能,它是给 FeaturesEndpoint 使用的。 + * + * 注册 FeignContext 其继承 NamedContextFactory。 + * 其作用是会根据 name 创建单独的IOC容器,获取bean是从单独的IOC容器中拿。IOC容器默认有两个配置类:PropertyPlaceholderAutoConfiguration、FeignClientsConfiguration + * 并依赖 FeignClientSpecification 用来扩展配置类,创建的IOC容器会缓存到Map中。 + * + * FeignClientsConfiguration 其目的是注册 Decoder、Encoder、Encoder、Contract、FormattingConversionService、 + * Retryer、FeignLoggerFactory、FeignClientConfigurer、Feign.Builder 这些bean 而且都有 @ConditionalOnMissingBean 条件,若我们想自定义 + * 这些bean,可以设置 FeignClientSpecification 扩展配置类,从而让 @ConditionalOnMissingBean 不满足,也就不会使用这些默认的bean + * + * 而 FeignClientSpecification 可以通过这两个注解快速配置 + * @EnableFeignClients(defaultConfiguration={A.class}) // 这样子是注册全局的,FeignContext 创建的所有IOC容器都会使用这个配置类 + * @FeignClient(contextId="f1",name="serviceName",configuration={A.class}) // FeignContext 为 f1 创建的IOC容器 会使用这个配置类 + * 注:contextId 为空 就会使用 name 作为缺省值 + * + * 注册 CachingCapability 其实现 Capability 接口,依赖 CacheInterceptor。CachingCapability 是用来增强 Feign.Builder 设置的 InvocationHandlerFactory 的 + * 让方法的执行委托给 CacheInterceptor 执行,也就是支持 Spring Cache 的功能 + * + * 注册 PageJacksonModule、SortJacksonModule 都是 Module 类型的,这两个东西是用来扩展 jackson 扩展序列化规则的,是为了支持 spring data + * + * 注册 Targeter 类型的bean,默认是 DefaultTargeter , 如果容器中有 CircuitBreakerFactory 类型的bean,那就会注册 FeignCircuitBreakerTargeter + * Targeter 是用来聚合 FeignClientFactoryBean、Feign.Builder、FeignContext、Target.HardCodedTarget, + * 定义了如何生成 Target.HardCodedTarget 泛型的实例 + * + * 注册 CircuitBreakerNameResolver 是用来生成断路器name的,FeignCircuitBreakerTargeter 会依赖这个bean + * + * 注册 HttpClientConnectionManager 会依赖 FeignHttpClientProperties 来设置连接相关参数 + * + * 注册 CloseableHttpClient 是 HttpClient 的实现类,其依赖 HttpClientConnectionManager、FeignHttpClientProperties 设置一些参数 + * + * 注册 ApacheHttpClient 是 feign.Client 的实现类,依赖 HttpClient 来执行HTTP请求 + * + * 属性 feign.okhttp.enabled == true 会注册 + * 注册 ConnectionPool 会依赖 FeignHttpClientProperties 来设置连接相关参数 + * 注册 okhttp3.OkHttpClient 其依赖 ConnectionPool、FeignHttpClientProperties 设置一些参数 + * 注册 OkHttpClient 是 feign.Client 的实现类,依赖 okhttp3.OkHttpClient 来执行HTTP请求 + * */ +``` + +### FeignAcceptGzipEncodingAutoConfiguration + +```java +/** + * FeignAcceptGzipEncodingAutoConfiguration + * 注册 FeignAcceptGzipEncodingInterceptor , 它是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Accept-Encoding=gzip,deflate + * 注:请求头 Accept-Encoding=gzip,deflate 是告诉服务器 客户端支持 gzip,deflate 压缩 + **/ +``` + +```yml +feign: + compression: + # 设置请求头 Accept-Encoding=gzip,deflate 用于告诉服务器 客户端支持 gzip,deflate 压缩 + response: + enabled: true +``` + +### FeignContentGzipEncodingAutoConfiguration + +```java +/** + * FeignContentGzipEncodingAutoConfiguration + * 注册 FeignContentGzipEncodingInterceptor , 它是 RequestInterceptor 的实现类,其目的是给 Request 增加请求头 Content-Encoding=gzip,deflate + * 满足这两点才需要增加请求头: + * 1. Content-Type 是属性 feign.compression.request.mimeTypes 包含的值 + * 2. Content-Length 大于 属性 feign.compression.request.minRequestSize 的值 + * + * 请求头有 Content-Encoding=gzip,deflate 会对请求体进行编码后(压缩)再发送给到服务器(这得看你用的Client是否支持) + * */ +``` + +```yml +feign: + compression: + request: + enabled: true + # FeignClient 执行HTTP请求时,Content-Type 、Content-Length 满足这两个条件,就设置请求头 Content-Encoding=gzip,deflate。 + # 设置了请求头后 在发送前会对请求体进行压缩 + mimeTypes: + - 'text/xml' + - 'application/xml' + - 'application/json' + minRequestSize: 100 +``` + +### FeignLoadBalancerAutoConfiguration + +```java +@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class, + DefaultFeignLoadBalancerConfiguration.class }) +public class FeignLoadBalancerAutoConfiguration {} +``` + +```java +/** + * FeignLoadBalancerAutoConfiguration + * + * 目的都是注册 Client 的实现类,根据条件会注册 FeignBlockingLoadBalancerClient 或者是 RetryableFeignBlockingLoadBalancerClient + * 导入的三个配置类的区别在于: + * - HttpClientFeignLoadBalancerConfiguration 依赖 HttpClient 执行HTTP请求 + * - OkHttpFeignLoadBalancerConfiguration 依赖 okhttp3.OkHttpClient 执行HTTP请求 + * - DefaultFeignLoadBalancerConfiguration 依赖 Client.Default 执行HTTP请求 + * */ +``` + +## @EnableFeignClients 和 @FeignClient + +@EnableFeignClients 是用来扫描得到标注了 @FeignClient 的类,将类的信息映射成 BeanDefinition,然后注册到 BeanFactory 中。而需要注意的是这个 bean 的实例化是 `FeignClientFactoryBean.getObject()` 得到的。@FeignClient 的注解值主要是映射给`FeignClientFactoryBean`。所以要想知道 `@FeignClient` 是如何实现生成接口代理对象的还得看`FeignClientFactoryBean.getObjec()` + +```java +@Import(FeignClientsRegistrar.class) +public @interface EnableFeignClients { + + String[] value() default {}; // 要扫描的包 + + String[] basePackages() default {}; // 要扫描的包 + + Class[] basePackageClasses() default {}; // 类所在的包 + + Class[] defaultConfiguration() default {}; // FeignClient Context 会用到的默认配置类 + + Class[] clients() default {}; // 指定 FeignClient,若指定就不会根据包路径扫描 +} +``` + +```java +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface FeignClient { + + String contextId() default ""; // FeignClient Context 的ID,没指定会使用 name 或者 value 的值。支持占位符解析 + + @AliasFor("name") + String value() default ""; // 服务名。支持占位符解析 + + @AliasFor("value") + String name() default ""; // 服务名。支持占位符解析 + + String qualifier() default ""; // 别名 + + String url() default ""; // 若指定这个值,那就不会使用 name,也就不会变成负载均衡请求了。支持占位符解析 + + boolean decode404() default false; + + Class[] configuration() default {}; // 给 FeignClient Context 设置默认配置类 + + Class fallback() default void.class; // 使用 {@link FeignCircuitBreaker.Builder} 构造的 FeignClient 才会用到这个属性,是用来给 CircuitBreaker 使用的,用于在执行HTTP请求时出错后 的兜底策略。需要注册到容器中才行 + + Class fallbackFactory() default void.class; // 和 {@link FeignClient#fallback()} 的用法类似,只不过这个是用来创建 fallback的。如果指定了 fallback ,那么这个属性就没用了。需要注册到容器中才行 + + String path() default ""; // 访问路径是 url + path 。支持占位符解析 + + boolean primary() default true; // bean是否是 @Primary 的 + +} +``` + +```java +/** + * + * 举例: + * @EnableFeignClients(defaultConfiguration={A.class},clients={A.class}) + * @FeignClient(name="s1",configuration={A.class}) + * public class Config{} + * + * @EnableFeignClients 上有 @Import(FeignClientsRegistrar.class) 所以解析配置类解析到这个注解时会将 FeignClientsRegistrar + * 注册到BeanFactory中,而 FeignClientsRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,所以其接口方法会被回调。 + * {@link FeignClientsRegistrar#registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry)} + * + * 1. 注册 FeignClient 默认配置 + * 获取 defaultConfiguration 注解值映射成 BeanDefinition 注册到 BeanFactory 中 + * BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class); + * builder.addConstructorArgValue("default."+Config.class.getName()); + * builder.addConstructorArgValue(defaultConfiguration); + * registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), + * builder.getBeanDefinition()); + * + * Tips: FeignContext 继承 NamedContextFactory, 会依赖 FeignClientSpecification 类型的bean 用来配置要生成的IOC容器。 + * FeignContext 会使用 beanName是 "default." 前缀的 FeignClientSpecification 作为默认项,用来配置要生成的IOC容器 + * + * 2. 注册 FeignClient + * 2.1 记录候选的组件 candidateComponents + * 设置 clients 值那就只使用这些值作为 candidateComponents, 没有设置 clients 值,那就扫描包下的类,只会收集有 @FeignClient 的类。 + * value + basePackages + basePackageClasses 的值作为要扫描的包路径,若这三个注解值都没设置, + * 那就用 @EnableFeignClients 注解所在的配置类的包作为要扫描的包路径 + * + * 2.2 遍历 candidateComponents 挨个映射成 BeanDefinition 注册到 BeanFactory 中 + * - 校验 @FeignClient 标注的类 不是接口就报错 + * - 注册 FeignClient 配置 + * String name = getClientName(attributes); // 为空就依次获取属性 contextId -> value -> name -> serviceId 都没设置就报错 + * registerClientConfiguration(registry, name, attributes.get("configuration")); // 同上映射成 FeignClientSpecification,但是没有 "default." 前缀 + * + * - 将注解的值映射到 FeignClientFactoryBean ,然后装饰成 BeanDefinition 注册到BeanFactory中 + * String contextId = getContextId(beanFactory, attributes); // 获取属性值,值为空就依次获取: contextId -> serviceId -> name -> value + * String name = getName(attributes); // 同上,只不过获取的是:serviceId -> name -> value + * + * // 定义 FeignClientFactoryBean + * FeignClientFactoryBean factoryBean = new FeignClientFactoryBean(); + * factoryBean.setBeanFactory(beanFactory); + * factoryBean.setName(name); + * factoryBean.setContextId(contextId); + * factoryBean.setType(clazz); + * // 根据 属性 feign.client.refresh-enabled 设置 + * factoryBean.setRefreshableClient(isClientRefreshEnabled()); + * + * // 提供 Supplier ,BeanFactory实例化会调用 Supplier 得到bean对象 + * BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> { + * // 根据 @FeignClient(url="")的值来设置,会解析占位符,还会补全http:// + * factoryBean.setUrl(getUrl(beanFactory, attributes)); + * + * // 获取 @FeignClient(path="")的值来设置,会解析占位符, 会补上前缀/,移除后缀/ + * factoryBean.setPath(getPath(beanFactory, attributes)); + * + * // 剩下的就是简单读取值然后设置给factoryBean + * factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404")))); + * factoryBean.setFallback(attributes.get("fallback")); + * factoryBean.setFallbackFactory(attributes.get("fallbackFactory")); + * return factoryBean.getObject(); + * }); + * + * // 将BeanDefinition注册到BeanFactory中 + * registry.registerBeanDefinition(beanName, definition.getBeanDefinition()); + * + * 如果 feign.client.refresh-enabled 是true那就多注册 OptionsFactoryBean 到容器中,而且是 refresh 作用域的 + * 当 FeignClientFactoryBean.getObject() 时会拿到 OptionsFactoryBean 用来配置 Feign.Builder + * + * Tips:因为每次实例化bean都会重新设置 url、path 的值且支持使用占位符,所以我们可以 + * 将 bean 设置成 refresh 作用域的,然后就能实现 url、path 的动态更新 + * */ +``` + +## FeignClientFactoryBean + +```java +/** + * @FeignClient 注解修饰的接口会注册到BeanFactory中,这种bean的实例化是执行 FeignClientFactoryBean.getObject() 得到。 + * + * FeignClientFactoryBean 继承 FactoryBean 实现 InitializingBean + * {@link FeignClientFactoryBean#afterPropertiesSet()} 会校验属性 contextId、name 都不能为空 + * {@link FeignClientFactoryBean#getType()} 返回的其实就是 @FeignClient 标注的接口类型 + * {@link FeignClientFactoryBean#getObject()} 这个才是关键,看这里才能知道是得到接口代理对象的 + * */ +``` + +## FeignClientFactoryBean#getObject + +其目的是配置 `Feign.Builder`,配置的参数有啥作用去看 [Feign](https://github.com/haitaoss/feign) 就明白了,最后将 `Feign.Builder` 交给 [Targeter](#Targeter) ,由 Targeter 使用 `Feign.Bduiler` 生成接口代理对象。我们可以自定义 Targeter 注册到容器中,让默认注册的失效。 + +可以使用 FeignClientSpecification、FeignBuilderCustomizer 来配置 `Feign.Builder` 需要的参数 + +```java +/** + * {@link FeignClientFactoryBean#getObject()} + * + * 1. 从容器中获取 FeignContext + * FeignContext context = beanFactory.getBean(FeignContext.class) + * + * 2. 获取 Feign.Builder 并对其进行配置 + * Feign.Builder builder = feign(context); + * 2.1 根据 contextId 从 FeignContext 中获取 Feign.Builder + * 2.2 根据 contextId 从 FeignContext 中获取 FeignLoggerFactory、Encoder、Decoder、Contract... 设置给 Feign.Builder + * 2.3 可以设置 feign.client.config.contextId.xx 属性 和使用 FeignBuilderCustomizer 用来对 Feign.Builder 进行配置 + * 会设置很多东西:Logger.Level、Retryer、ErrorDecoder、FeignErrorDecoderFactory、Options、RequestInterceptor、QueryMapEncoder、Contract、Encoder、Decoder、ExceptionPropagationPolicy、Capability + * + * 注:FeignContext 是 NamedContextFactory 不同的 name 会有单独的IOC容器,IOC容器默认会加载的配置类是 FeignClientsConfiguration + * + * 3. url 没有值,那就使用 name + path 拼接成 url,然后使用 Target 得到接口的代理对象 + * Client client = getOptional(context, Client.class); + * builder.client(client); + * Targeter targeter = get(context, Targeter.class); + * + * return targeter.target(this, builder, context, target); + * + * 4. url 有值,那就使用 url + path 拼接成 url,然后使用 Target 得到接口的代理对象(这里会对Client进行解构,使用非负载均衡的Client) + * Client client = getOptional(context, Client.class); + * if (client instanceof FeignBlockingLoadBalancerClient) { + * // 因为提供了Url所以不需要负载均衡的Client,所以这里解构拿到 非负载均衡的Client + * client = ((FeignBlockingLoadBalancerClient) client).getDelegate(); + * } + * builder.client(client); + * Targeter targeter = get(context, Targeter.class); + * return targeter.target(this, builder, context, target); + * + * */ +``` + +## FeignClientBuilder + +这是 OpenFeign 提供的工具类,用于快速生成 FeignClient 接口的代理对象。其本质是通过配置 FeignClientFactoryBean 然后执行 [getObject](#FeignClientFactoryBean#getObject) 得到代理对象。 + +## Targeter + +```java +public interface Targeter { + T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, + Target.HardCodedTarget target); +} +``` + +就是接口规范而已,其目的是规定使用 Feign 的步骤。最终的目的是使用 `Feign.Builder` 为 `Target.HardCodedTarget `描述的接口生成代理对象。 + +我们可以注册 Targeter 到容器中,自定义逻辑,比如 [FeignCircuitBreakerTargeter](#FeignCircuitBreakerTargeter) + +## FeignCircuitBreakerTargeter + +断路器可以看: + +- [spring-cloud-circuitbreaker](https://github.com/haitaoss/spring-cloud-circuitbreaker) +- [Sentinel](https://github.com/haitaoss/Sentinel) + +`spring-cloud-openfeign-core.jar!META-INF/spring.factories` 的部分内容 + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + org.springframework.cloud.openfeign.FeignAutoConfiguration,\ +``` + +属性`feign.circuitbreaker.enabled`是 true 且 容器中有 CircuitBreakerFactory 类型的 bean,就会注册 FeignCircuitBreakerTargeter 到容器中 + +```java +public class FeignAutoConfiguration { + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty("feign.circuitbreaker.enabled") + protected static class CircuitBreakerPresentFeignTargeterConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(CircuitBreakerFactory.class) + public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) { + return new FeignCircuitBreakerTargeter(circuitBreakerFactory); + } + + } +} +``` + +FeignCircuitBreakerTargeter 的核心逻辑是为 Feign.Builder 配置 **invocationHandlerFactory** 属性,从而能够将方法的执行委托给 **CircuitBreaker** 执行。 + +```java +// 伪代码如下 +Feign.builder().invocationHandlerFactory( + (target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory, + feignClientName, target, dispatch, nullableFallbackFactory + )); +``` + +```java +class FeignCircuitBreakerTargeter implements Targeter { + + @Override + public T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context, + Target.HardCodedTarget target) { + // 不是 FeignCircuitBreaker 类型的就不做处理 + if (!(feign instanceof FeignCircuitBreaker.Builder)) { + return feign.target(target); + } + FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign; + String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId(); + /** + * 前置知识:Feign 其实是通过JDK动态代理 为接口创建出代理对象,所以要想拦截方法的执行只需要配置 InvocationHandler 即可。 + * + * 下面的几行代码的最终目都是设置 FeignCircuitBreakerInvocationHandler 作为代理对象的 InvocationHandler, + * 所以关键还得看 FeignCircuitBreakerInvocationHandler + * + * {@link FeignCircuitBreakerInvocationHandler#invoke(Object, Method, Object[])} + * 大致流程是方法的执行交给 CircuitBreaker 执行 {@link CircuitBreaker#run(Supplier, Function)} + * CircuitBreaker 可以拿到 fallback 或者 fallbackFactory。可以决定什么时候回调 fallback 的逻辑 + * */ + Class fallback = factory.getFallback(); + if (fallback != void.class) { + // 存在 fallback 的情况 + return targetWithFallback(name, context, target, builder, fallback); + } + Class fallbackFactory = factory.getFallbackFactory(); + if (fallbackFactory != void.class) { + // 存在 fallbackFactory 的情况 + return targetWithFallbackFactory(name, context, target, builder, fallbackFactory); + } + return builder(name, builder).target(target); + } +} +``` + +## FeignCircuitBreakerInvocationHandler + +[FeignCircuitBreakerTargeter](#FeignCircuitBreakerTargeter) 会配置 FeignCircuitBreakerInvocationHandler 作为 FeignClient 接口代理对象的 InvocationHandler,从而将 方法的执行 和 fallback 的执行 交给 CircuitBreaker 来决定,比如方法执行出错了就执行 fallback。 + +```java +class FeignCircuitBreakerInvocationHandler implements InvocationHandler { + + @Override + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + String circuitName = this.feignClientName + "_" + method.getName(); + // 通过 CircuitBreakerFactory 得到 CircuitBreaker 实例 + CircuitBreaker circuitBreaker = this.factory.create(circuitName); + // 定义方法的执行 + Supplier supplier = asSupplier(method, args); + /** + * 存在 nullableFallbackFactory 就使用 + * + * 比如这两种情况 nullableFallbackFactory 才会有值 + * @FeignClient(fallback=A.class) + * @FeignClient(fallbackFactory=A.class) + * @FeignClient(fallback=A.class, fallbackFactory=A.class) // 两个都有的情况 只会使用 fallback + * */ + if (this.nullableFallbackFactory != null) { + // 使用 nullableFallbackFactory 构造出 fallbackFunction + Function fallbackFunction = throwable -> { + // 通过 nullableFallbackFactory 得到 fallback + Object fallback = this.nullableFallbackFactory.create(throwable); + try { + // 使用 fallback 执行当前出错的方法 + return this.fallbackMethodMap.get(method).invoke(fallback, args); + } + catch (Exception e) { + throw new IllegalStateException(e); + } + }; + // 使用 circuitBreaker 执行方法 + return circuitBreaker.run(supplier, fallbackFunction); + } + // 使用 circuitBreaker 执行方法 + return circuitBreaker.run(supplier); + } + + private Supplier asSupplier(final Method method, final Object[] args) { + return () -> { + try { + return this.dispatch.get(method).invoke(args); + } + catch (RuntimeException throwable) { + throw throwable; + } + catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + }; + } +} +``` + +## FeignClientsConfiguration + +FeignContext 中为 name 创建的 IOC 容器都会使用 FeignClientsConfiguration 作为默认的配置类,这个配置类中定义了配置 Feign.Builder 的参数,其实就是对 Feign 的功能做实现,让 Feign 支持 SpringMVC 的注解等等。 + +**列举最关键的几个参数对象,并不是全部的代码** + +```java +@Configuration(proxyBeanMethods = false) +public class FeignClientsConfiguration { + + /** + * 依赖IOC容器配置的 List ,其作用是将 执行 FeignClient 接口的响应体 转成 方法的参数类型 + * @return + */ + @Bean + @ConditionalOnMissingBean + public Decoder feignDecoder() { + return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters))); + } + + /** + * 依赖IOC容器配置的 List ,其作用是将 FeignClient 接口的参数 设置到请求体中 + * @param formWriterProvider + * @return + */ + @Bean + @ConditionalOnMissingBean + @ConditionalOnMissingClass("org.springframework.data.domain.Pageable") + public Encoder feignEncoder(ObjectProvider formWriterProvider) { + return springEncoder(formWriterProvider); + } + + /** + * 扩展 FeignClient 接口 支持的注解。其作用是在执行接口方法时 将特殊注解标注的内容 映射到Request对象中, + * 比如设置 请求头、请求路径、查询参数、请求体 等等 + * @param feignConversionService + * @return + */ + @Bean + @ConditionalOnMissingBean + public Contract feignContract(ConversionService feignConversionService) { + boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash(); + return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash); + } + + /** + * 生成 FeignClient 的 Builder 对象。FeignCircuitBreaker 是 OpenFeign 定义的, + * 用来使用 FeignCircuitBreaker 来执行 FeignClient 接口的方法 + * @return + */ + @Bean + @Scope("prototype") + @ConditionalOnMissingBean + @ConditionalOnBean(CircuitBreakerFactory.class) + public Feign.Builder circuitBreakerFeignBuilder() { + return FeignCircuitBreaker.builder(); + } + +} +``` diff --git "a/docs/SpringSecurity/SpringSecurity\346\265\201\347\250\213\350\241\245\345\205\205.md" "b/docs/SpringSecurity/SpringSecurity\346\265\201\347\250\213\350\241\245\345\205\205.md" new file mode 100644 index 00000000..e69de29b diff --git "a/docs/SpringSecurity/SpringSecurity\350\207\252\345\256\232\344\271\211\347\224\250\346\210\267\350\256\244\350\257\201.md" "b/docs/SpringSecurity/SpringSecurity\350\207\252\345\256\232\344\271\211\347\224\250\346\210\267\350\256\244\350\257\201.md" new file mode 100644 index 00000000..e69de29b diff --git "a/docs/SpringSecurity/SpringSecurity\350\257\267\346\261\202\345\205\250\350\277\207\347\250\213\350\247\243\346\236\220.md" "b/docs/SpringSecurity/SpringSecurity\350\257\267\346\261\202\345\205\250\350\277\207\347\250\213\350\247\243\346\236\220.md" new file mode 100644 index 00000000..c7d0f160 --- /dev/null +++ "b/docs/SpringSecurity/SpringSecurity\350\257\267\346\261\202\345\205\250\350\277\207\347\250\213\350\247\243\346\236\220.md" @@ -0,0 +1,139 @@ +# Spring Security 请求全过程解析 + +Spring Security 是一款基于 Spring 的安全框架,主要包含认证和授权两大安全模块,和另外一款流行的安全框架 Apache Shiro 相比,它拥有更为强大的功能。Spring Security 也可以轻松的自定义扩展以满足各种需求,并且对常见的 Web 安全攻击提供了防护支持。如果你的 Web 框架选择的是 Spring,那么在安全方面 Spring Security 会是一个不错的选择。 + +这里我们使用 Spring Boot 来集成 Spring Security,Spring Boot 版本为**_2.5.3_**,Spring Security 版本为**_5.5.1_**。 + +## 开启 Spring Security + +使用 IDEA 创建一个 Spring Boot 项目,然后引入**_spring-boot-starter-security_**: + +```java +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.projectlombok:lombok:1.18.8' + annotationProcessor 'org.projectlombok:lombok:1.18.8' + providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' +} +``` + +接下来我们创建一个**_HelloController_**,对外提供一个/hello服务: + +```java +@RestController +public class HelloController { + @GetMapping("hello") + public String hello() { + return "hello world"; + } +} +``` + +这时候我们直接启动项目,访问http://localhost:8080/hello,可以看到页面跳转到一个登陆页面: + +![image-20210811091508157](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091508157.png) + +默认的用户名为 user,密码由 Sping Security 自动生成,回到 IDEA 的控制台,可以找到密码信息: + +```java +Using generated security password: 4f06ba04-37e9-4bdd-a085-3305260da0d6 +``` + +输入用户名 user,密码 4f06ba04-37e9-4bdd-a085-3305260da0d6 后,我们便可以成功访问/hello接口。 + +## 基本原理 + +Spring Security 默认为我们开启了一个简单的安全配置,下面让我们来了解其原理。 + +当 Spring Boot 项目配置了 Spring Security 后,Spring Security 的整个加载过程如下图所示: + +![image-20210811091633434](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091633434.png) + +而当我们访问http://localhost:8080/hello时,代码的整个执行过程如下图所示: + +![image-20210811091659121](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091659121.png) + +如上图所示,Spring Security 包含了众多的过滤器,这些过滤器形成了一条链,所有请求都必须通过这些过滤器后才能成功访问到资源。 + +下面我们通过 debug 来验证这个过程: + +首先,通过前面可以知道,当有请求来到时,最先由**_DelegatingFilterProxy_**负责接收,因此在**_DelegatingFilterProxy_**的doFilter()的首行打上断点: + +![image-20210811091719470](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091719470.png) + +接着**_DelegatingFilterProxy_**会将请求委派给**_FilterChainProxy_**进行处理,在**_FilterChainProxy_**的首行打上断点: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png) + +**_FilterChainProxy_**会在doFilterInternal()中生成一个内部类**_VirtualFilterChain_**的实例,以此来调用 Spring Security 的整条过滤器链,在**_VirtualFilterChain_**的doFilter()首行打上断点: + +![image-20210811091755498](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091755498.png) + +接下来**_VirtualFilterChain_**会通过**_currentPosition_**依次调用存在**_additionalFilters_**中的过滤器,其中比较重要的几个过滤器有:**_UsernamePasswordAuthenticationFilter_**、**_DefaultLoginPageGeneratingFilter_**、**_AnonymousAuthenticationFilter_**、**_ExceptionTranslationFilter_**、**_FilterSecurityInterceptor_**,我们依次在这些过滤器的doFilter()的首行打上断点: + +![image-20210811091815473](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091815473.png) + +准备完毕后,我们启动项目,然后访问http://localhost:8080/hello,程序首先跳转到**_DelegatingFilterProxy_**的断点上: + +![image-20210811091833065](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091833065.png) + +此时**_delegate_**还是 null 的,接下来依次执行代码,可以看到**_delegate_**最终被赋值一个**_FilterChainProxy_**的实例: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png) + +接下来程序依次跳转到**_FilterChainProxy_**的doFilter()和**_VirtualFilterChain_**的doFilter()中: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png) + +![image-20210811092048784](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811092048784.png) + +接着程序跳转到**_AbstractAuthenticationProcessingFilter_**(**_UsernamePasswordAuthenticationFilter_**的父类)的doFilter()中,通过requiresAuthentication()判定为 false(是否是 POST 请求): + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png) + +接着程序跳转到**_DefaultLoginPageGeneratingFilter_**的doFilter()中,通过isLoginUrlRequest()判定为 false(请求路径是否是/login): + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png) + +接着程序跳转到**_AnonymousAuthenticationFilter_**的doFilter()中,由于是首次请求,此时SecurityContextHolder.getContext().getAuthentication()为 null,因此会生成一个**_AnonymousAuthenticationToken_**的实例: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png) + +接着程序跳转到**_ExceptionTranslationFilter_**的doFilter()中,**_ExceptionTranslationFilter_**负责处理**_FilterSecurityInterceptor_**抛出的异常,我们在 catch 代码块的首行打上断点: + +**![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png)** + +接着程序跳转到**_FilterSecurityInterceptor_**的doFilter()中,依次执行代码后程序停留在其父类(**_AbstractSecurityInterceptor_**)的attemptAuthorization()中: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png) + +**_accessDecisionManager_**是**_AccessDecisionManager_**(访问决策器)的实例,**_AccessDecisionManager_**主要有 3 个实现类:**_AffirmativeBased_**(一票通过),**ConsensusBased**(少数服从多数)、UnanimousBased(一票否决),此时**_AccessDecisionManager_**的的实现类是**_AffirmativeBased_**,我们可以看到程序进入**_AffirmativeBased_**的decide()中: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png) + +从上图可以看出,决策的关键在voter.vote(authentication, object, configAttributes)这句代码上,通过跟踪调试,程序最终进入**_AuthenticationTrustResolverImpl_**的isAnonymous()中: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png) + +isAssignableFrom()判断前者是否是后者的父类,而**_anonymousClass_**被固定为**_AnonymousAuthenticationToken.class_**,参数**_authentication_**由前面**_AnonymousAuthenticationFilter_**可以知道是**_AnonymousAuthenticationToken_**的实例,因此isAnonymous()返回 true,**_FilterSecurityInterceptor_**抛出**_AccessDeniedException_**异常,程序返回**_ExceptionTranslationFilter_**的 catch 块中: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png) + +接着程序会依次进入**_DelegatingAuthenticationEntryPoint_**、**_LoginUrlAuthenticationEntryPoint_**中,最后由**_LoginUrlAuthenticationEntryPoint_**的commence()决定重定向到/login: + +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png) + +后续对/login的请求同样会经过之前的执行流程,在**_DefaultLoginPageGeneratingFilter_**的doFilter()中,通过isLoginUrlRequest()判定为 true(请求路径是否是/login),直接返回**_login.html_**,也就是我们开头看到的登录页面。 + +当我们输入用户名和密码,点击**_Sign in_**,程序来到**_AbstractAuthenticationProcessingFilter_**的doFilter()中,通过requiresAuthentication()判定为 true(是否是 POST 请求),因此交给其子类**_UsernamePasswordAuthenticationFilter_**进行处理,**_UsernamePasswordAuthenticationFilter_**会将用户名和密码封装成一个**_UsernamePasswordAuthenticationToken_**的实例并进行校验,当校验通过后会将请求重定向到我们一开始请求的路径:/hello。 + +后续对/hello的请求经过过滤器链时就可以一路开绿灯直到最终交由**_HelloController_**返回"Hello World"。 + +## 参考 + +1. [Spring Security Reference](https://docs.spring.io/spring-security/site/docs/current/reference/html5/) + +2. [Spring Boot 中开启 Spring Security](https://mrbird.cc/Spring-Boot&Spring-Security.html) diff --git "a/docs/Tomcat/servlet-api\346\272\220\347\240\201\350\265\217\346\236\220.md" "b/docs/Tomcat/servlet-api\346\272\220\347\240\201\350\265\217\346\236\220.md" index 3965b023..f1c86273 100644 --- "a/docs/Tomcat/servlet-api\346\272\220\347\240\201\350\265\217\346\236\220.md" +++ "b/docs/Tomcat/servlet-api\346\272\220\347\240\201\350\265\217\346\236\220.md" @@ -1,59 +1,70 @@ ## Servlet 基础 + ### Servlet 简介 -Servlet(Server Applet)是J2EE的内容之一,由 Java 编写的服务器端小程序。它是web请求的入口,主要功能在于交互式地(Request 和 Response)浏览和修改数据,生成动态 Web内容。Servlet 运行于支持 Java的应用服务器中,如 Tomcat。从实现上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP协议 的 Web服务器。servlet 的工作模式如下: -- 客户端发送请求至 WEB服务器; -- 服务器根据请求的URL调用相应的 servlet对象进行处理,获取到 servlet对象的处理结果; + +Servlet(Server Applet)是 J2EE 的内容之一,由 Java 编写的服务器端小程序。它是 web 请求的入口,主要功能在于交互式地(Request 和 Response)浏览和修改数据,生成动态 Web 内容。Servlet 运行于支持 Servlet 的 WEB 容器中,如 Tomcat。从实现上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议 的 Web 服务器。servlet 的工作模式如下: + +- 客户端发送请求至 WEB 服务器; +- 服务器根据请求的 URL 调用相应的 servlet 对象进行处理,获取到 servlet 对象的处理结果; - 服务器将 响应内容 返回给客户端; -另外,由于各种MVC框架的兴起,现在几乎没人会直接使用 servlet 来处理请求咯,往往都是把 SpringMVC 在 Tomcat 中一配,各种请求都交由 DispatcherServlet 来分发。 +另外,由于各种 MVC 框架的兴起,现在几乎没人会直接使用 servlet 来处理请求咯,往往都是把 SpringMVC 在 Tomcat 中一配,各种请求都交由 DispatcherServlet 来分发。 -### Servlet生命周期 -- 加载 : 客户端第一次访问该 Servlet 时,Tomcat 会创建该 Servlet 的实例,一般只创建1次,所以 servlet对象 在 Tomcat 是单例的; +### Servlet 生命周期 + +- 加载 : 客户端第一次访问该 Servlet 时,Tomcat 会创建该 Servlet 的实例,一般只创建 1 次,所以 servlet 对象 在 Tomcat 是单例的; - 初始化 : Tomcat 调用 该 Servlet 的 init()方法 进行初始化; -- 服务 : 每当客户端访问 该Servlet 时,Tomcat 就会调用一次该 Servlet 的 service()方法 处理请求; -- 销毁 : Tomcat关闭时,会调用 这些servlet 的 destroy()方法,让该实例释放掉所占的资源。 +- 服务 : 每当客户端访问 该 Servlet 时,Tomcat 就会调用一次该 Servlet 的 service()方法 处理请求; +- 销毁 : Tomcat 关闭时,会调用 这些 servlet 的 destroy()方法,让该实例释放掉所占的资源。 简单总结一下就是:只要访问 Servlet,service()方法 就会被调用,init()方法 只有第一次访问 Servlet 的时候才会被调用,destroy()方法 会在 Tomcat 关闭的时候被调用。 ### <load-on-startup> -在 web.xml 中配置 Servlet 时有个属性 <load-on-startup>1</load-on-startup>。翻译过来就是 “在启动时加载”,其作用如下: -1. load-on-startup 元素标记容器是否应该在 web应用程序 启动的时候就加载这个 Servlet,实例化并调用其 init()方法; + +在 web.xml 中配置 Servlet 时有个属性 <load-on-startup>1</load-on-startup>。翻译过来就是 “在启动时加载”,其作用如下: + +1. load-on-startup 元素标记容器是否应该在 web 应用程序 启动的时候就加载这个 Servlet,实例化并调用其 init()方法; 2. 它的值必须是一个整数,表示 Servlet 被加载的先后顺序; -3. 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个 Servlet,值越小,Servlet 的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载; +3. 如果值为正整数或者 0 时,表示容器在应用启动时就加载并初始化这个 Servlet,值越小,Servlet 的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载; 4. 如果该元素的值为负数或者没有设置,则容器会在 Servlet 被请求时才加载。 -### Servlet的多线程并发问题 -servlet对象 在 Tomcat服务器中 是 单实例-多线程并发访问的,比如 DispatcherServlet对象 只会被创建一次,但多个请求同时过来时,Tomcat线程池 的多个工作线程就会并发地访问该 DispatcherServlet对象。所以,若一个servlet对象中存在被并发修改的共享数据(成员变量 等),又没有加锁控制并发安全,就很可能会出现线程安全问题。 +### Servlet 的多线程并发问题 + +servlet 对象 在 Tomcat 服务器中 是 单实例-多线程并发访问的,比如 DispatcherServlet 对象 只会被创建一次,但多个请求同时过来时,Tomcat 线程池 的多个工作线程就会并发地访问该 DispatcherServlet 对象。所以,若一个 servlet 对象中存在被并发修改的共享数据(成员变量 等),又没有加锁控制并发安全,就很可能会出现线程安全问题。 -解决方案: -1. 把可能会并发修改的共享数据的代码块进行同步(使用synchronized 或 Lock对象); -2. 建议在 Servlet类 中尽量不要使用成员变量。若使用成员变量,则必须同步,并尽量缩小同步代码块的范围,以避免因为同步而导致并发效率降低。 +解决方案: + +1. 把可能会并发修改的共享数据的代码块进行同步(使用 synchronized 或 Lock 对象); +2. 建议在 Servlet 类 中尽量不要使用成员变量。若使用成员变量,则必须同步,并尽量缩小同步代码块的范围,以避免因为同步而导致并发效率降低。 ### Servlet 实现请求和响应 -对于每次客户端请求,Web容器 都会创建一个新的 HttpServletRequest请求对象 和 一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给相应的 Servlet对象 的 service()方法,service()方法 再根据请求方式分别调用 doGet()/doPost()/doXXX()方法。经过一系列业务层处理,最后将结果封装到 response对象中,交由 Socket对象传输响应给客户端。 + +对于每次客户端请求,Web 容器 都会创建一个新的 HttpServletRequest 请求对象 和 一个新的 HttpServletResponse 响应对象,然后将这两个对象作为参数传递给相应的 Servlet 对象 的 service()方法,service()方法 再根据请求方式分别调用 doGet()/doPost()/doXXX()方法。经过一系列业务层处理,最后将结果封装到 response 对象中,交由 Socket 对象传输响应给客户端。 ## Servlet 源码解析 -javax.servlet 包对 Servlet规范 的一些主要行为和接口进行了定义和简单实现,它是 Servlet规范 的体现,具体的实现交由下游厂商或开发者(如:Tomcat / Jetty),就像体现了JDBC规范的 java.sql包 一样,主要负责定义标准和规范。其源码和注释如下。 + +javax.servlet 包对 Servlet 规范 的一些主要行为和接口进行了定义和简单实现,它是 Servlet 规范 的体现,具体的实现交由下游厂商或开发者(如:Tomcat / Jetty),就像体现了 JDBC 规范的 java.sql 包 一样,主要负责定义标准和规范。其源码和注释如下。 + ```java public interface Servlet { /** * 初始化servlet */ public abstract void init(ServletConfig servletconfig) throws ServletException; - + /** * 提供服务 */ public abstract void service(ServletRequest servletrequest, ServletResponse servletresponse) throws ServletException, IOException; - + /** * 销毁本servlet - */ + */ public abstract void destroy(); - + public abstract ServletConfig getServletConfig(); - + public abstract String getServletInfo(); } @@ -63,11 +74,11 @@ public interface Servlet { public interface ServletConfig { public abstract String getServletName(); - + public abstract ServletContext getServletContext(); - + public abstract String getInitParameter(String s); - + public abstract Enumeration getInitParameterNames(); } @@ -77,41 +88,41 @@ public interface ServletConfig { public interface ServletContext { public abstract ServletContext getContext(String s); - + public abstract Servlet getServlet(String s) throws ServletException; - + public abstract Enumeration getServlets(); - + public abstract Enumeration getServletNames(); - + public abstract int getMajorVersion(); - + public abstract Set getResourcePaths(String s); - + public abstract URL getResource(String s) throws MalformedURLException; - + public abstract InputStream getResourceAsStream(String s); - + public abstract RequestDispatcher getRequestDispatcher(String s); - + public abstract RequestDispatcher getNamedDispatcher(String s); - + public abstract String getRealPath(String s); - + public abstract String getServerInfo(); - + public abstract String getInitParameter(String s); - + public abstract Enumeration getInitParameterNames(); - + public abstract Object getAttribute(String s); - + public abstract Enumeration getAttributeNames(); - + public abstract void setAttribute(String s, Object obj); - + public abstract void removeAttribute(String s); - + public abstract String getServletContextName(); } @@ -121,51 +132,51 @@ public interface ServletContext { public interface ServletRequest { public abstract Object getAttribute(String s); - + public abstract Enumeration getAttributeNames(); - + public abstract String getCharacterEncoding(); - + public abstract void setCharacterEncoding(String s) throws UnsupportedEncodingException; - + public abstract int getContentLength(); - + public abstract String getContentType(); - + public abstract ServletInputStream getInputStream() throws IOException; - + public abstract String getParameter(String s); - + public abstract Enumeration getParameterNames(); - + public abstract String[] getParameterValues(String s); - + public abstract Map getParameterMap(); - + public abstract String getProtocol(); - + public abstract String getScheme(); - + public abstract String getServerName(); - + public abstract int getServerPort(); - + public abstract BufferedReader getReader() throws IOException; - + public abstract String getRemoteAddr(); - + public abstract String getRemoteHost(); - + public abstract void setAttribute(String s, Object obj); - + public abstract void removeAttribute(String s); - + public abstract Locale getLocale(); - + public abstract Enumeration getLocales(); - + public abstract boolean isSecure(); - + public abstract RequestDispatcher getRequestDispatcher(String s); } @@ -175,37 +186,39 @@ public interface ServletRequest { public interface ServletResponse { public abstract String getCharacterEncoding(); - + public abstract ServletOutputStream getOutputStream() throws IOException; - + public abstract PrintWriter getWriter() throws IOException; - + public abstract void setContentLength(int i); - + public abstract void setContentType(String s); - + public abstract void setBufferSize(int i); - + public abstract int getBufferSize(); - + public abstract void flushBuffer() throws IOException; - + public abstract void resetBuffer(); - + public abstract boolean isCommitted(); - + public abstract void reset(); - + public abstract void setLocale(Locale locale); - + public abstract Locale getLocale(); } ``` + 其主要部分的类图 如下。 -![avatar](images/Tomcat/Servlet主要类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Tomcat/Servlet主要类图.png) 下面看一下 javax.servlet.http 包下的内容,它提供了很多 我经常用到的类和接口,比如:HttpServlet、HttpServletRequest、HttpServletResponse。其源码如下。 + ```java public abstract class HttpServlet extends GenericServlet implements Serializable { @@ -220,7 +233,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable private static final String HEADER_LASTMOD = "Last-Modified"; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings"); - + /** * 将 请求及响应 转换成 HttpServletRequest 及 HttpServletResponse, * 再调用 service() 的重载方法 @@ -236,7 +249,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable } service(request, response); } - + /** * 对HTTP协议 各种类型的请求分别进行处理 */ @@ -277,7 +290,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable resp.sendError(501, errMsg); } } - + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_get_not_supported"); @@ -286,17 +299,17 @@ public abstract class HttpServlet extends GenericServlet implements Serializable else resp.sendError(400, msg); } - + protected long getLastModified(HttpServletRequest req) { return -1L; } - + protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { NoBodyResponse response = new NoBodyResponse(resp); doGet(req, response); response.setContentLength(); } - + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_post_not_supported"); @@ -305,7 +318,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable else resp.sendError(400, msg); } - + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_put_not_supported"); @@ -314,7 +327,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable else resp.sendError(400, msg); } - + protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String protocol = req.getProtocol(); String msg = lStrings.getString("http.method_delete_not_supported"); @@ -323,7 +336,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable else resp.sendError(400, msg); } - + private Method[] getAllDeclaredMethods(Class c) { if (c.getName().equals("javax.servlet.http.HttpServlet")) return null; @@ -336,16 +349,16 @@ public abstract class HttpServlet extends GenericServlet implements Serializable allMethods[i] = parentMethods[i]; j = i; } - + for (int i = ++j; i < thisMethods.length + j; i++) allMethods[i] = thisMethods[i - j]; - + return allMethods; } else { return thisMethods; } } - + protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Method methods[] = getAllDeclaredMethods(getClass()); boolean ALLOW_GET = false; @@ -368,7 +381,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable if (m.getName().equals("doDelete")) ALLOW_DELETE = true; } - + String allow = null; if (ALLOW_GET && allow == null) allow = "GET"; @@ -404,7 +417,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable allow = allow + ", OPTIONS"; resp.setHeader("Allow", allow); } - + protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String CRLF = "\r\n"; String responseString = "TRACE " + req.getRequestURI() + " " + req.getProtocol(); @@ -412,7 +425,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable String headerName = (String) reqHeaderEnum.nextElement(); responseString = responseString + CRLF + headerName + ": " + req.getHeader(headerName); } - + responseString = responseString + CRLF; int responseLength = responseString.length(); resp.setContentType("message/http"); @@ -421,7 +434,7 @@ public abstract class HttpServlet extends GenericServlet implements Serializable out.print(responseString); out.close(); } - + private void maybeSetLastModified(HttpServletResponse resp, long lastModified) { if (resp.containsHeader("Last-Modified")) return; @@ -439,53 +452,53 @@ public interface HttpServletRequest extends ServletRequest { public static final String FORM_AUTH = "FORM"; public static final String CLIENT_CERT_AUTH = "CLIENT_CERT"; public static final String DIGEST_AUTH = "DIGEST"; - + public abstract String getAuthType(); - + public abstract Cookie[] getCookies(); - + public abstract long getDateHeader(String s); - + public abstract String getHeader(String s); - + public abstract Enumeration getHeaders(String s); - + public abstract Enumeration getHeaderNames(); - + public abstract int getIntHeader(String s); - + public abstract String getMethod(); - + public abstract String getPathInfo(); - + public abstract String getPathTranslated(); - + public abstract String getContextPath(); - + public abstract String getQueryString(); - + public abstract String getRemoteUser(); - + public abstract boolean isUserInRole(String s); - + public abstract Principal getUserPrincipal(); - + public abstract String getRequestedSessionId(); - + public abstract String getRequestURI(); - + public abstract StringBuffer getRequestURL(); - + public abstract String getServletPath(); - + public abstract HttpSession getSession(boolean flag); - + public abstract HttpSession getSession(); - + public abstract boolean isRequestedSessionIdValid(); - + public abstract boolean isRequestedSessionIdFromCookie(); - + public abstract boolean isRequestedSessionIdFromURL(); } @@ -534,33 +547,33 @@ public interface HttpServletResponse extends ServletResponse { public static final int SC_SERVICE_UNAVAILABLE = 503; public static final int SC_GATEWAY_TIMEOUT = 504; public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505; - + public abstract void addCookie(Cookie cookie); - + public abstract boolean containsHeader(String s); - + public abstract String encodeURL(String s); - + public abstract String encodeRedirectURL(String s); - + public abstract void sendError(int i, String s) throws IOException; - + public abstract void sendError(int i) throws IOException; - + public abstract void sendRedirect(String s) throws IOException; - + public abstract void setDateHeader(String s, long l); - + public abstract void addDateHeader(String s, long l); - + public abstract void setHeader(String s, String s1); - + public abstract void addHeader(String s, String s1); - + public abstract void setIntHeader(String s, int i); - + public abstract void addIntHeader(String s, int i); - + public abstract void setStatus(int i); } -``` \ No newline at end of file +``` diff --git "a/docs/Tomcat/servlet\345\256\271\345\231\250\350\257\246\350\247\243.md" "b/docs/Tomcat/servlet\345\256\271\345\231\250\350\257\246\350\247\243.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Tomcat/servlet\345\256\271\345\231\250\350\257\246\350\247\243.md" +++ "b/docs/Tomcat/servlet\345\256\271\345\231\250\350\257\246\350\247\243.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Web\346\234\215\345\212\241\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" "b/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Web\346\234\215\345\212\241\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" index fcb5dbe4..b3787c53 100644 --- "a/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Web\346\234\215\345\212\241\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" +++ "b/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204Web\346\234\215\345\212\241\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" @@ -1 +1 @@ -努力编写中... \ No newline at end of file +努力编写中... diff --git "a/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204servlet\345\256\271\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" "b/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204servlet\345\256\271\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" index fcb5dbe4..fe1b2afd 100644 --- "a/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204servlet\345\256\271\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" +++ "b/docs/Tomcat/\344\270\200\344\270\252\347\256\200\345\215\225\347\232\204servlet\345\256\271\345\231\250\344\273\243\347\240\201\350\256\276\350\256\241.md" @@ -1 +1,217 @@ -努力编写中... \ No newline at end of file +# 一个简单的 Servlet 容器代码设计 + +Servlet 算是 Java Web 开发请求链路调用栈中底层的一个技术,当客户端发起一个请求后,到达服务器内部,就会先进入 Servlet(这里不讨论更底层的链路),SpringMVC 的请求分发核心也是一个 Servlet,名叫`DispatcherServlet`,一个请求首先会进入到这个 Servlet,然后在通过 SpringMVC 的机制去分发到对应的 Controller 下。 + +但是再往上一层说,普通的开发人员可能不会关心 Servlet 是怎么被调用的,我们只要写一个`@WebServlet`注解在 Servlet 的类上,运行后,客户端的请求就会自动进入到相应的 Servlet 中,而做这些事的叫 Servlet 容器,Servlet 容器一定是一个 Web 服务器,但 Web 服务器反过来可不一定是 Servlet 容器哦。 + +而了解一个 Servlet 容器的实现有助于更好的理解 JavaWeb 开发。 + +## Github 地址 + +项目最后的实现在 Github 上可以查看到 + +https://github.com/houxinlin/jerrycat + +## 容器的实现 + +在 JavaWeb 的开发世界,有很多都要遵守规范,JDBC 也是,Servlet 容器也是,Java 很多不去做实现,只做接口,具体的实现留给各大厂商去做,而 Servlet 容器其中一个实现就是 Tomcat。 + +Tomcat 的实现还是很复杂的,这里也不做研究,我们只搞清楚一个小型的 Servlet 容器实现的步骤即可。 + +我们起一个容器名,叫 JerryCat 吧,他的实现功能只有一个,将请求交给对应的 Servlet,并将其处理结果返回给客户端,因为这才是核心,而实现他的具体步骤如下。 + +1. 解压 war 文件 +2. 收集 Servlet 信息 +3. 启动 web 服务器 +4. 请求映射 & 返回结果 + +## 解压 war 文件 + +当你在 Tomcat 的 webapps 目录下放入一个 war 文件,启动 tomcat 后,tomcat 会自动把这个 war 文件解压了,后续所有的操作将会针对这个解压后的目录,而解压一个 war 文件很简单,代码如下。 + +```java + +public static void unzipWar(String warFilePath, String outputFolder) throws IOException { + byte[] buffer = new byte[1024]; + try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(Paths.get(warFilePath)))) { + ZipEntry zipEntry; + while ((zipEntry = zis.getNextEntry()) != null) { + String entryName = zipEntry.getName(); + File newFile = new File(outputFolder + File.separator + entryName); + if (zipEntry.isDirectory()) { + newFile.mkdirs(); + } else { + new File(newFile.getParent()).mkdirs(); + try (FileOutputStream fos = new FileOutputStream(newFile)) { + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + } + zis.closeEntry(); + } + } +} +``` + +## 收集 Servlet 信息 + +这一步是一个核心,因为 Servlet 容器一定要知道一个 war 项目中所有的 Servlet 信息,也就是要知道开发人员定义的请求路径和具体 Servlet 的映射关系,当请求进来的时候,才能根据这个映射关系调用到对应的 Servlet 下。 + +在 Servlet 3.0 规范以前,所有的映射关系需要在 web.xml 中去配置,比如下面这样,这个配置用来告诉容器将`/hello`的请求映射到`com.example.HelloServlet`下,容器只需要读取一个配置即可。 + +```xml + + HelloServlet + com.example.HelloServlet + + + + HelloServlet + /hello + + +``` + +但是自从规范 3.0 开始,增加了`@WebServlet`等注解,如下,这也是告诉容器,这个类的请求路径是`/hello`。 + +```java +@WebServlet("/hello") +public class HelloServlet extends HttpServlet {} +``` + +那么容器的实现就会增加负担,因为要遍历所有的 class,找出标有`@WebServlet`的类,并做收集,那问题是怎么找到这些符合的类呢? 首先不能通过反射,因为有两个问题。 + +第一个问题是类加载器的问题(这里假设你已经了解了类加载器的概念),因为容器的类加载器是不能加载 war 项目中的 class 的,即使能加载,你要通过`Class.forName()`去加载类时,在这个收集信息阶段,容器是不可能知道有那些类名称的,虽然可以通过在 web.xml 直接告诉容器,但说回来,尝试`Class.forName()`时会抛出`ClassNotFoundException`,而真正的容器实现都会自定义一个 ClassLoader,专门去加载项目的 class 和资源。 + +那么就算有了自定义的 ClassLoader,可以加载到项目的 class,那么`Class.forName`会触发 static 代码块,如果项目中的 Servlet 正好写了 static 代码快,则会调用,虽然最终这个代码块都会被调用,但不应该在这个时候,会出一些问题。 + +而正确的做法是直接读取二进制 class 文件,从 class 文件规范中找到这个 class 是不是有`@WebServlet`注解,这是唯一的办法,Spring 扫描注解的时候也是这样做的,而 Tomcat 也是这样,[Tomcat 解析 class 文件的类可以点击我查看](https://github.com/apache/tomcat/blob/main/java/org/apache/tomcat/util/bcel/classfile/ClassParser.java)。 + +Tomcat 是纯自己手撸出一个解析器,如果熟悉 class 文件格式后,还是比较容易的,所以这里我们依靠一个框架,比如用`org.ow2.asm`这个库,额外的知识:Spring 也是靠第三方库来读取的。 + +具体例子如下 + +```java +private void collectorServlet() { + try { + final Set classFileSet = new HashSet<>(); + Files.walkFileTree(Paths.get(this.webProjectPath, WEB_CLASSES_PATH), new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (file.toString().endsWith(".class")) classFileSet.add(file.toString()); + return super.visitFile(file, attrs); + } + }); + ClassNode classNode = new ClassNode(); + for (String classFile : classFileSet) { + ClassReader classReader = new ClassReader(Files.newInputStream(Paths.get(classFile))); + classReader.accept(classNode, ClassReader.EXPAND_FRAMES); + List visibleAnnotations = classNode.visibleAnnotations; + for (AnnotationNode visibleAnnotation : visibleAnnotations) { + if ("Ljavax/servlet/annotation/WebServlet;".equalsIgnoreCase(visibleAnnotation.desc)) { + Map annotationValues = ClassUtils.getAnnotationValues(visibleAnnotation.values); + Object o = loaderClass(classReader.getClassName()); + servletMap.put(annotationValues.get("value").toString(), ((HttpServlet) o)); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } +} + + + private Object loaderClass(String name) { + try { + Class aClass = appClassloader.loadClass(name); + return aClass.newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } +``` + +主要就是遍历`/WEB-INF/classes/`目录,使用 ClassReader 类解析这个 class 文件,并判断是不是标有 WebServlet 注解,如果存在,则通过自定义的类加载器加载并实例化他,而这个类加载器主要作用就是根据给定的类名,从`/WEB-INF/classes/`加载类,如果给定的类不存在,则交给父类加载器。 + +但向 Tomcat 都有一些公共的类区域,可以把所有项目所用到的共同库提取出来,放到一个目录下,另外 war 规范中,`/WEB-INF/lib`目录用来存放第三方的 jar 文件库,类加载器也需要考虑这个目录。 + +那么这个类加载器加载路径依次如下: + +1. /WEB-INF/classes/目录 +2. /WEB-INF/lib 目录 +3. 公共区域 +4. 父类加载器 + +如果最后一个也加载不到,则抛出异常,拥有一个公共区域其实是很有必要的,通常来说我们都会依赖大量的第三方库,可能自己的代码和资源都不到 10M,但是大量的第三方库可能占到上百 M,部署传输起来可能不方便,正确的做法应该是把用到的第三方库一次性上传到公共区域,部署时只传自己的代码。 + +并且类加载器还需要重写 getResource、getResourceAsStream 等这些方法用来在项目的类路径下查找资源。 + +## 启动 web 服务器 + +上面说到,Servlet 容器也是一个 Web 服务器,只有启动一个 Web 服务器后,收到请求,才能传递给 Servlet,并且,他还能处理静态资源,实现一个 Web 服务器重要的是解析 HTTP 报文,并且根据响应结果生成 HTTP 报文。 + +这部分我们可以使用一个 Java 提供的现成库,如下。 + +```java +HttpServer httpServer = HttpServer.create(new InetSocketAddress(4040), 10); +``` + +1. `HttpServer`:是 Java 中用于创建 HTTP 服务器的类。它是 Java SE 6 引入的,用于支持简单的 HTTP 服务端功能。 +1. `HttpServer.create`:用于创建一个新的 HTTP 服务器实例。 +1. `new InetSocketAddress(4040)`:`InetSocketAddress`表示 IP 地址和端口号的类。这里的`4040`是端口号,表示 HTTP 服务器将在本地计算机的 4040 端口上监听传入的 HTTP 请求。 +1. `10`:这是服务器的等待队列的最大长度。当 HTTP 服务器在处理传入的请求时,如果同时有更多请求到达,它们将被放入等待队列。这里的`10`表示等待队列的最大长度为 10,即最多允许同时有 10 个请求在等待处理。 + +## 请求映射 & 返回结果 + +这里有一点比较麻烦,我们知道 doGet 和 doPost 的参数是`HttpServletRequest`、`HttpServletResponse`,容器需要实现这两个接口,提供请求参数,这里我们偷个懒,使用`mockito`这个库来构造一个请求。 + +下面代码中,`createContext`用来监听某个请求路径,当有请求过来时,HttpServer 会把请求对象封装为`HttpExchange`,而我们做的事是把他转换为`HttpServletRequest`。 + +当调用 service 时,`javax.servlet.http.HttpServlet`会自动根据请求访问,调用 doGet 或者是 doPost 等。 + +```java +try { + HttpServer httpServer = HttpServer.create(new InetSocketAddress(4040), 10); + httpServer.createContext("/", httpExchange -> { + Servlet servlet = servletMap.get(httpExchange.getRequestURI().toString()); + JerryCatHttpServletResponse httpServletResponse = new JerryCatHttpServletResponse(Mockito.mock(HttpServletResponse.class)); + HttpServletRequest httpServletRequest = createHttpServletRequest(httpExchange); + if (servlet != null) { + try { + servlet.service(httpServletRequest, httpServletResponse); + byte[] responseByte = httpServletResponse.getResponseByte(); + httpExchange.sendResponseHeaders(200, responseByte.length); + httpExchange.getResponseBody().write(responseByte); + httpExchange.getResponseBody().flush(); + } catch (ServletException e) { + e.printStackTrace(); + } + } + }); + httpServer.start(); +} catch (IOException e) { + throw new RuntimeException(e); +} +``` + +到这里就结束容器的任务了,只需要等待 Servlet 处理完成,将结果返回给客户端即可。 + +但这里,请求映射显的有点简单,因为我们少了处理通配符的情况。 + +## 其余规范 + +其他特性我们不说,但属于 Servlet 规范的容器一定要实现,其余规范还有如 ServletContainerInitializer、Filter 等这里我们都没有实现,ServletContainerInitializer 是一个很有用的东西,SpringBoot 打包成 war 后,就依靠它去启动。 + +Filter 同样的做法,也是通过 ClassReader 读取,在调用 service 前一步,先调用 Filter。 + +## 结束 + +这里只实现了一个容器的雏形中的核心,一个完整的容器,至少要做到提供完整的`HttpServletRequest`的实现,还有`HttpServletResponse`,这里只做演示,没有做太多处理,比如最重要的 Cookie 管理、Session 管理,否则应用程序就无法实现用户登录状态维护。 + +`HttpServletRequest`是继承`ServletRequest`的,他们定义的方法加起来共有 70 多个,需要一一去实现,才能给用户提供一个完整的请求信息供给,否则用户想拿一个请求头都拿不到,也没办法继续开发。 + +有完整的信息提供后,就可以做额外的功能开发了,比如 WebSocket,当请求过来时候,发现是一个 WebSocket 握手请求,那么相应的要做一个协议升级,转换为 WebSocket 协议。 + +另外,一个容器进程是可以加载多个 war 项目的,就像 tomcat,久而久之,支持的东西多了,就成了真正的容器。 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..696a7324 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,45 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Source Code Hunter" + text: "互联网公司常用框架源码赏析" + tagline: Doocs 技术社区出品 + actions: + - theme: brand + text: 开始阅读 + link: /Spring/IoC/1、BeanDefinition的资源定位过程 + - theme: alt + text: GitHub + link: https://github.com/doocs/source-code-hunter + +features: + - title: Spring 系列 + details: 深入解析 Spring IoC、AOP、MVC、事务、Boot、Cloud 等源码机制 + + - title: MyBatis + details: 详解 MyBatis 支撑层与核心组件源码,理解 ORM 框架底层实现 + + - title: Netty + details: 网络通信核心框架,涵盖 NIO、ByteBuf、ChannelPipeline 等底层剖析 + + - title: Dubbo + details: 探索 Dubbo 的架构设计、远程通信与注册中心实现机制 + + - title: Tomcat + details: 揭秘 Servlet 容器实现原理,深入 Web 服务运行机制 + + - title: Redis + details: 深挖 Redis 数据结构实现,如 SDS、跳表等底层源码 + + - title: JDK 1.8 + details: Java 核心类库、集合与并发源码全景解读 + + - title: 学习心得 + details: 总结源码学习心得,涵盖设计模式、并发编程与成长感悟 + + - title: Nacos / Sentinel / RocketMQ + details: 分布式系统中的注册、限流、消息中间件核心源码拆解 +--- + diff --git a/docs/nacos/nacos-discovery.md b/docs/nacos/nacos-discovery.md new file mode 100644 index 00000000..e72a14d1 --- /dev/null +++ b/docs/nacos/nacos-discovery.md @@ -0,0 +1,688 @@ +# Nacos 服务注册 + +- nacos-spring-boot-project 中有关服务注册的几个项目 + - nacos-discovery-spring-boot-actuator + nacos-discovery-spring-boot-autoconfigure + nacos-discovery-spring-boot-starter + +```properties +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + com.alibaba.boot.nacos.discovery.autoconfigure.NacosDiscoveryAutoConfiguration +``` + +找到类 `NacosDiscoveryAutoConfiguration` + +```java +@ConditionalOnProperty(name = NacosDiscoveryConstants.ENABLED, matchIfMissing = true) +@ConditionalOnMissingBean(name = DISCOVERY_GLOBAL_NACOS_PROPERTIES_BEAN_NAME) +@EnableNacosDiscovery +@EnableConfigurationProperties(value = NacosDiscoveryProperties.class) +@ConditionalOnClass(name = "org.springframework.boot.context.properties.bind.Binder") + public class NacosDiscoveryAutoConfiguration { + + @Bean + public NacosDiscoveryAutoRegister discoveryAutoRegister() { + return new NacosDiscoveryAutoRegister(); + } + +} +``` + +- 注解:`EnableNacosDiscovery` + +```java +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Import(NacosDiscoveryBeanDefinitionRegistrar.class) +public @interface EnableNacosDiscovery {} +``` + +- import 类 :`NacosDiscoveryBeanDefinitionRegistrar` + +```java +public class NacosDiscoveryBeanDefinitionRegistrar + implements ImportBeanDefinitionRegistrar, EnvironmentAware { + + private Environment environment; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + AnnotationAttributes attributes = AnnotationAttributes + .fromMap(importingClassMetadata + .getAnnotationAttributes(EnableNacosDiscovery.class.getName())); + // Register Global Nacos Properties Bean + registerGlobalNacosProperties(attributes, registry, environment, + DISCOVERY_GLOBAL_NACOS_PROPERTIES_BEAN_NAME); + registerGlobalNacosProperties(attributes, registry, environment, + MAINTAIN_GLOBAL_NACOS_PROPERTIES_BEAN_NAME); + // Register Nacos Common Beans + registerNacosCommonBeans(registry); + // Register Nacos Discovery Beans + registerNacosDiscoveryBeans(registry); + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } +} +``` + +- 两个流程 + + 1. 将注解`EnableNacosDiscovery`的属性读取,放入到 nacos 的全局属性配置中 + + 2. bean 注入 + +## nacos 全局配置属性 + +- `com.alibaba.nacos.spring.context.annotation.discovery.NacosDiscoveryBeanDefinitionRegistrar#registerBeanDefinitions` + - `com.alibaba.nacos.spring.util.NacosBeanUtils#registerGlobalNacosProperties(org.springframework.core.annotation.AnnotationAttributes, org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.core.env.PropertyResolver, java.lang.String)` + +```java +public static void registerGlobalNacosProperties(AnnotationAttributes attributes, + BeanDefinitionRegistry registry, PropertyResolver propertyResolver, + String beanName) { + if (attributes == null) { + return; // Compatible with null + } + AnnotationAttributes globalPropertiesAttributes = attributes + .getAnnotation("globalProperties"); + registerGlobalNacosProperties((Map) globalPropertiesAttributes, registry, + propertyResolver, beanName); +} +``` + +- 贴出注解上的信息 + +```java + NacosProperties globalProperties() default @NacosProperties(username = USERNAME_PLACEHOLDER, password = PASSWORD_PLACEHOLDER, endpoint = ENDPOINT_PLACEHOLDER, namespace = NAMESPACE_PLACEHOLDER, accessKey = ACCESS_KEY_PLACEHOLDER, secretKey = SECRET_KEY_PLACEHOLDER, serverAddr = SERVER_ADDR_PLACEHOLDER, contextPath = CONTEXT_PATH_PLACEHOLDER, clusterName = CLUSTER_NAME_PLACEHOLDER, encode = ENCODE_PLACEHOLDER); + +``` + +- 通过下面这段代码会将注解信息获取到对象`AnnotationAttributes globalPropertiesAttributes`中 + + ```java + AnnotationAttributes globalPropertiesAttributes = attributes + .getAnnotation("globalProperties"); + ``` + +- 下一段代码是将属性换算出来 + + ```java + registerGlobalNacosProperties((Map) globalPropertiesAttributes, registry, + propertyResolver, beanName) + + + public static void registerGlobalNacosProperties(Map globalPropertiesAttributes, + BeanDefinitionRegistry registry, PropertyResolver propertyResolver, + String beanName) { + // 占位符解析成具体的配置信息 + Properties globalProperties = resolveProperties(globalPropertiesAttributes, + propertyResolver); + // 单例注册 + registerSingleton(registry, beanName, globalProperties); + } + + ``` + +![image-20200821111938485](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821111938485.png) + +## registerNacosCommonBeans + +``` +public static void registerNacosCommonBeans(BeanDefinitionRegistry registry) { + // Register NacosApplicationContextHolder Bean + registerNacosApplicationContextHolder(registry); + // Register AnnotationNacosInjectedBeanPostProcessor Bean + registerAnnotationNacosInjectedBeanPostProcessor(registry); +} +``` + +- 主要方法: registerInfrastructureBean + 1. 定义出 bean + 2. 设置构造参数 + 3. 注册对象 + +```java +public static void registerInfrastructureBean(BeanDefinitionRegistry registry, + String beanName, Class beanClass, Object... constructorArgs) { + // Build a BeanDefinition for NacosServiceFactory class + // 定义出 bean 根据类型 + BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(beanClass); + for (Object constructorArg : constructorArgs) { + beanDefinitionBuilder.addConstructorArgValue(constructorArg); + } + // ROLE_INFRASTRUCTURE + beanDefinitionBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + // Register + registry.registerBeanDefinition(beanName, + beanDefinitionBuilder.getBeanDefinition()); +} +``` + +## @EnableConfigurationProperties(value = NacosDiscoveryProperties.class) + +属性读取,从 application 配置文件中读取数据转换成 java 对象。 + +![image-20200821132413628](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821132413628.png) + +## NacosDiscoveryAutoRegister + +```java +public class NacosDiscoveryAutoRegister + implements ApplicationListener {} +``` + +- 处理一个`WebServerInitializedEvent` 事件的方法 + +- 重写方法如下,主要工作内容 + 1. 把服务发现配置读取出来 + 2. 设置一些数据值 + 3. 调用服务注册接口 + +```java +@Override +public void onApplicationEvent(WebServerInitializedEvent event) { + + if (!discoveryProperties.isAutoRegister()) { + return; + } + + Register register = discoveryProperties.getRegister(); + + if (StringUtils.isEmpty(register.getIp())) { + register.setIp(NetUtils.localIP()); + } + + if (register.getPort() == 0) { + register.setPort(event.getWebServer().getPort()); + } + + register.getMetadata().put("preserved.register.source", "SPRING_BOOT"); + + register.setInstanceId(""); + + String serviceName = register.getServiceName(); + + if (StringUtils.isEmpty(serviceName)){ + if (StringUtils.isEmpty(applicationName)){ + throw new AutoRegisterException("serviceName notNull"); + } + serviceName = applicationName; + } + + try { + namingService.registerInstance(serviceName, register.getGroupName(), + register); + logger.info("Finished auto register service : {}, ip : {}, port : {}", + serviceName, register.getIp(), register.getPort()); + } catch (NacosException e) { + throw new AutoRegisterException(e); + } +} +``` + +- 注册的参数 + + ![image-20200821133350982](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821133350982.png) + +## 服务注册 + +![image-20200821133445090](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821133445090.png) + +- 注册一个实例 + 1. 将 instance 对象转换成 BeatInfo 对象 + 2. 注册实例 + +```java + @Override + public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException { + + if (instance.isEphemeral()) { + // 实例信息转换 + BeatInfo beatInfo = new BeatInfo(); + beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName)); + beatInfo.setIp(instance.getIp()); + beatInfo.setPort(instance.getPort()); + beatInfo.setCluster(instance.getClusterName()); + beatInfo.setWeight(instance.getWeight()); + beatInfo.setMetadata(instance.getMetadata()); + beatInfo.setScheduled(false); + beatInfo.setPeriod(instance.getInstanceHeartBeatInterval()); + // 插入这条实例的信息 + beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), + beatInfo); + } + + serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance); + } + +``` + +- addBeatInfo + - 创建了一个定时任务 BeatTask + +```java +public void addBeatInfo(String serviceName, BeatInfo beatInfo) { + NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo); + String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()); + BeatInfo existBeat = null; + //fix #1733 + if ((existBeat = dom2Beat.remove(key)) != null) { + existBeat.setStopped(true); + } + dom2Beat.put(key, beatInfo); + executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS); + MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size()); +} +``` + +### BeatTask + +``` +class BeatTask implements Runnable { + + BeatInfo beatInfo; + + public BeatTask(BeatInfo beatInfo) { + this.beatInfo = beatInfo; + } + + @Override + public void run() { + if (beatInfo.isStopped()) { + return; + } + long nextTime = beatInfo.getPeriod(); + try { + // 与nacos进行一次rest请求交互 + JSONObject result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled); + long interval = result.get(CLIENT_BEAT_INTERVAL_FIELD).asLong(); + boolean lightBeatEnabled = false; + if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) { + lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean(); + } + BeatReactor.this.lightBeatEnabled = lightBeatEnabled; + if (interval > 0) { + nextTime = interval; + } + int code = NamingResponseCode.OK; + if (result.has(CommonParams.CODE)) { + code = result.get(CommonParams.CODE).asInt(); + } + // 如果nacos找不到当前实例, + if (code == NamingResponseCode.RESOURCE_NOT_FOUND) { + Instance instance = new Instance(); + instance.setPort(beatInfo.getPort()); + instance.setIp(beatInfo.getIp()); + instance.setWeight(beatInfo.getWeight()); + instance.setMetadata(beatInfo.getMetadata()); + instance.setClusterName(beatInfo.getCluster()); + instance.setServiceName(beatInfo.getServiceName()); + instance.setInstanceId(instance.getInstanceId()); + instance.setEphemeral(true); + try { + // 执行注册服务 + serverProxy.registerService(beatInfo.getServiceName(), + NamingUtils.getGroupName(beatInfo.getServiceName()), instance); + } catch (Exception ignore) { + } + } + } catch (NacosException ne) { + NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}", + JSON.toJSONString(beatInfo), ne.getErrCode(), ne.getErrMsg()); + + } catch (Exception unknownEx) { + NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, unknown exception msg: {}", + JacksonUtils.toJson(beatInfo), unknownEx.getMessage(), unknownEx); + } finally { + executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS); + } + } +} +``` + +- 定时任务说明 + + 1. 和 nacos 进行一次交互,根据交互结果的 code 判断,如果不在 nacos 会执行注册. + +- 发送请求的方法 + + ```java + public String reqAPI(String api, Map params, String body, List servers, String method) throws NacosException { + + params.put(CommonParams.NAMESPACE_ID, getNamespaceId()); + + if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) { + throw new NacosException(NacosException.INVALID_PARAM, "no server available"); + } + + NacosException exception = new NacosException(); + + if (serverListManager.isDomain()) { + String nacosDomain = serverListManager.getNacosDomain(); + for (int i = 0; i < maxRetry; i++) { + try { + return callServer(api, params, body, nacosDomain, method); + } catch (NacosException e) { + exception = e; + if (NAMING_LOGGER.isDebugEnabled()) { + NAMING_LOGGER.debug("request {} failed.", nacosDomain, e); + } + } + } + } else { + Random random = new Random(System.currentTimeMillis()); + int index = random.nextInt(servers.size()); + + for (int i = 0; i < servers.size(); i++) { + String server = servers.get(index); + try { + return callServer(api, params, body, server, method); + } catch (NacosException e) { + exception = e; + if (NAMING_LOGGER.isDebugEnabled()) { + NAMING_LOGGER.debug("request {} failed.", server, e); + } + } + index = (index + 1) % servers.size(); + } + } + + NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(), + exception.getErrMsg()); + + throw new NacosException(exception.getErrCode(), + "failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage()); + ``` + +**学习点** + +- 这里采用随机值作为第一个 server 的获取,主要目的是为了将请求随机分配给不同的 nacos 服务 + + 如果直接使用 for 循环的索引那第一台 nacos 服务会收到所有的请求,直到这台服务坏了才会请求第二台 + + Random random = new Random(System.currentTimeMillis()); + int index = random.nextInt(servers.size()); + + for (int i = 0; i < servers.size(); i++) { + // 获取nacos所在的ip+port地址 + String server = servers.get(index); + try { + // 进行请求 + return callServer(api, params, body, server, method); + } catch (NacosException e) { + exception = e; + if (NAMING_LOGGER.isDebugEnabled()) { + NAMING_LOGGER.debug("request {} failed.", server, e); + } + } + index = (index + 1) % servers.size(); + } + + } + +### registerService + +- 注册方法就是请求一次接口,将数据发送给 nacos 就完成了 + +```java +public void registerService(String serviceName, String groupName, Instance instance) throws NacosException { + + NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", + namespaceId, serviceName, instance); + String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName); + if (instance.isEphemeral()) { + BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance); + beatReactor.addBeatInfo(groupedServiceName, beatInfo); + } + + final Map params = new HashMap(32); + params.put(CommonParams.NAMESPACE_ID, namespaceId); + params.put(CommonParams.SERVICE_NAME, groupedServiceName); + params.put(CommonParams.GROUP_NAME, groupName); + params.put(CommonParams.CLUSTER_NAME, instance.getClusterName()); + params.put(IP_PARAM, instance.getIp()); + params.put(PORT_PARAM, String.valueOf(instance.getPort())); + params.put(WEIGHT_PARAM, String.valueOf(instance.getWeight())); + params.put(REGISTER_ENABLE_PARAM, String.valueOf(instance.isEnabled())); + params.put(HEALTHY_PARAM, String.valueOf(instance.isHealthy())); + params.put(EPHEMERAL_PARAM, String.valueOf(instance.isEphemeral())); + params.put(META_PARAM, JacksonUtils.toJson(instance.getMetadata())); + + reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST); + +} +``` + +- 服务注册的接口 + + - `/nacos/v1/ns/instance` + - `/nacos/v1/ns/instance/beat` + +- 接下来去寻找这两个接口的实现 + + `com.alibaba.nacos.naming.controllers.InstanceController` + +## nacos 服务端 + +### 实例注册 + +```java +public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException { + // 创建空服务 + createEmptyService(namespaceId, serviceName, instance.isEphemeral()); + + Service service = getService(namespaceId, serviceName); + + if (service == null) { + throw new NacosException(NacosException.INVALID_PARAM, + "service not found, namespace: " + namespaceId + ", service: " + serviceName); + } + + addInstance(namespaceId, serviceName, instance.isEphemeral(), instance); +} +``` + +- 创建空服务的流程 + + - 获取服务对象 + + nacos 的服务信息存储在 + + `com.alibaba.nacos.naming.core.ServiceManager#serviceMap` + + ```java + private Map> serviceMap = new ConcurrentHashMap<>(); + ``` + +```java +public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster) throws NacosException { + // 获取服务信息 + Service service = getService(namespaceId, serviceName); + if (service == null) { + + Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName); + service = new Service(); + service.setName(serviceName); + service.setNamespaceId(namespaceId); + service.setGroupName(NamingUtils.getGroupName(serviceName)); + // now validate the service. if failed, exception will be thrown + service.setLastModifiedMillis(System.currentTimeMillis()); + service.recalculateChecksum(); + if (cluster != null) { + cluster.setService(service); + service.getClusterMap().put(cluster.getName(), cluster); + } + service.validate(); + + putServiceAndInit(service); + if (!local) { + addOrReplaceService(service); + } + } +} +``` + +- 在了解 map 结构后不难理解下面这个获取 Service 的方法了 + +```java +public Service getService(String namespaceId, String serviceName) { + if (serviceMap.get(namespaceId) == null) { + return null; + } + return chooseServiceMap(namespaceId).get(serviceName); +} +``` + +```java +private void putServiceAndInit(Service service) throws NacosException { + putService(service); + service.init(); + consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service); + consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service); + Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJSON()); +} +``` + +- 把服务加入 map 对象 + +```java +public void putService(Service service) { + if (!serviceMap.containsKey(service.getNamespaceId())) { + synchronized (putServiceLock) { + if (!serviceMap.containsKey(service.getNamespaceId())) { + serviceMap.put(service.getNamespaceId(), new ConcurrentHashMap<>(16)); + } + } + } + serviceMap.get(service.getNamespaceId()).put(service.getName(), service); +} +``` + +- init 方法设置了一个数据验证的任务 , 并且在集群中设置 service 信息 + +```java +public void init() { + + HealthCheckReactor.scheduleCheck(clientBeatCheckTask); + + for (Map.Entry entry : clusterMap.entrySet()) { + entry.getValue().setService(this); + entry.getValue().init(); + } +} +``` + +- 再往后添加两个 key 的监听 + +- addInstance 方法 + +```java +public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException { + + String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral); + + Service service = getService(namespaceId, serviceName); + + synchronized (service) { + List instanceList = addIpAddresses(service, ephemeral, ips); + + Instances instances = new Instances(); + instances.setInstanceList(instanceList); + + consistencyService.put(key, instances); + } +} +``` + +- 简单理解 consistencyService 结构信息 + - key: 定义的一个名字 + - value : 实例的列表 + +### 实例健康检查 + +- 获取实例独享, 从 service 中根据集群名称获取实例列表 , 再根据 ip + 端口 返回实例对象 + +```java +Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port); +``` + +```java +public Instance getInstance(String namespaceId, String serviceName, String cluster, String ip, int port) { + Service service = getService(namespaceId, serviceName); + if (service == null) { + return null; + } + + List clusters = new ArrayList<>(); + clusters.add(cluster); + + List ips = service.allIPs(clusters); + if (ips == null || ips.isEmpty()) { + return null; + } + + for (Instance instance : ips) { + if (instance.getIp().equals(ip) && instance.getPort() == port) { + return instance; + } + } + + return null; +} +``` + +- 实例健康检查接口做的事件 + 1. 获取实例 + 1. 实例不存在注册实例 + 2. 获取服务 + 1. 服务不存在抛出异常 + 2. 服务存在执行一个心跳方法 + 3. 组装结果返回 + +```java +@CanDistro +@PutMapping("/beat") +@Secured(action = ActionTypes.WRITE) +public ObjectNode beat(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, + @RequestParam String serviceName, + @RequestParam(defaultValue = StringUtils.EMPTY) String ip, + @RequestParam(defaultValue = UtilsAndCommons.DEFAULT_CLUSTER_NAME) String clusterName, + @RequestParam(defaultValue = "0") Integer port, + @RequestParam(defaultValue = StringUtils.EMPTY) String beat)throws Exception { + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval()); + RsInfo clientBeat = null; + if (StringUtils.isNotBlank(beat)) { + clientBeat = JacksonUtils.toObj(beat, RsInfo.class); + } + if (clientBeat != null) { + if (StringUtils.isNotBlank(clientBeat.getCluster())) { + clusterName = clientBeat.getCluster(); + } else { + // fix #2533 + clientBeat.setCluster(clusterName); + } + ip = clientBeat.getIp(); + port = clientBeat.getPort(); + } + + NamingUtils.checkServiceNameFormat(serviceName); + Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}, namespaceId: {}", clientBeat, + serviceName, namespaceId); + BeatInfoInstanceBuilder builder = BeatInfoInstanceBuilder.newBuilder(); + int resultCode = instanceServiceV2 + .handleBeat(namespaceId, serviceName, ip, port, clusterName, clientBeat, builder); + result.put(CommonParams.CODE, resultCode); + result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, + instanceServiceV2.getHeartBeatInterval(namespaceId, serviceName, ip, port, clusterName)); + result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled()); + return result; +} +``` diff --git a/docs/rocketmq/rocketmq-commitlog.md b/docs/rocketmq/rocketmq-commitlog.md new file mode 100644 index 00000000..316f0b5f --- /dev/null +++ b/docs/rocketmq/rocketmq-commitlog.md @@ -0,0 +1,272 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ CommitLog 详解 + +commitlog 目录主要存储消息,为了保证性能,顺序写入,每一条消息的长度都不相同,每条消息的前面四个字节存储该条消息的总长度,每个文件大小默认为 1G,文件的命名是以 commitLog 起始偏移量命名的,可以通过修改 broker 配置文件中 mappedFileSizeCommitLog 属性改变文件大小 + +1、获取最小偏移量 + +org.apache.rocketmq.store.CommitLog#getMinOffset + +```java +public long getMinOffset() { + MappedFile mappedFile = this.mappedFileQueue.getFirstMappedFile(); + if (mappedFile != null) { + if (mappedFile.isAvailable()) { + return mappedFile.getFileFromOffset(); + } else { + return this.rollNextFile(mappedFile.getFileFromOffset()); + } + } + + return -1; +} +``` + +获取目录下的第一个文件 + +```java +public MappedFile getFirstMappedFile() { + MappedFile mappedFileFirst = null; + + if (!this.mappedFiles.isEmpty()) { + try { + mappedFileFirst = this.mappedFiles.get(0); + } catch (IndexOutOfBoundsException e) { + //ignore + } catch (Exception e) { + log.error("getFirstMappedFile has exception.", e); + } + } + + return mappedFileFirst; +} +``` + +如果该文件可用返回文件的起始偏移量,否则返回下一个文件的 起始偏移量 + +```java +public long rollNextFile(final long offset) { + int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); + return offset + mappedFileSize - offset % mappedFileSize; +} +``` + +2、根据偏移量和消息长度查找消息 + +org.apache.rocketmq.store.CommitLog#getMessage + +```java +public SelectMappedBufferResult getMessage(final long offset, final int size) { + int mappedFileSize = this.defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(); + MappedFile mappedFile = this.mappedFileQueue.findMappedFileByOffset(offset, offset == 0); + if (mappedFile != null) { + int pos = (int) (offset % mappedFileSize); + return mappedFile.selectMappedBuffer(pos, size); + } + return null; +} +``` + +首先获取 commitLog 文件大小,默认 1G + +`private int mappedFileSizeCommitLog = 1024 * 1024 * 1024;` + +获取偏移量所在的 MappedFile + +org.apache.rocketmq.store.MappedFileQueue#findMappedFileByOffset(long, boolean) + +获取第一个 MappedFile 和最后一个 MappedFile,校验偏移量是否在这两个 MappedFile 之间,计算当前偏移量所在 MappedFiles 索引值为当前偏移量的索引减去第一个文件的索引值 + +```java +if (firstMappedFile != null && lastMappedFile != null) { + if (offset < firstMappedFile.getFileFromOffset() || offset >= lastMappedFile.getFileFromOffset() + this.mappedFileSize) { + LOG_ERROR.warn("Offset not matched. Request offset: {}, firstOffset: {}, lastOffset: {}, mappedFileSize: {}, mappedFiles count: {}", + offset, + firstMappedFile.getFileFromOffset(), + lastMappedFile.getFileFromOffset() + this.mappedFileSize, + this.mappedFileSize, + this.mappedFiles.size()); + } else { + int index = (int) ((offset / this.mappedFileSize) - (firstMappedFile.getFileFromOffset() / this.mappedFileSize)); + MappedFile targetFile = null; + try { + targetFile = this.mappedFiles.get(index); + } catch (Exception ignored) { + } + + if (targetFile != null && offset >= targetFile.getFileFromOffset() + && offset < targetFile.getFileFromOffset() + this.mappedFileSize) { + return targetFile; + } + + for (MappedFile tmpMappedFile : this.mappedFiles) { + if (offset >= tmpMappedFile.getFileFromOffset() + && offset < tmpMappedFile.getFileFromOffset() + this.mappedFileSize) { + return tmpMappedFile; + } + } + } + + if (returnFirstOnNotFound) { + return firstMappedFile; + } +} +``` + +根据在文件内的偏移量和消息长度获取消息内容 + +```java +public SelectMappedBufferResult selectMappedBuffer(int pos, int size) { + int readPosition = getReadPosition(); + if ((pos + size) <= readPosition) { + if (this.hold()) { + ByteBuffer byteBuffer = this.mappedByteBuffer.slice(); + byteBuffer.position(pos); + ByteBuffer byteBufferNew = byteBuffer.slice(); + byteBufferNew.limit(size); + return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this); + } else { + log.warn("matched, but hold failed, request pos: " + pos + ", fileFromOffset: " + + this.fileFromOffset); + } + } else { + log.warn("selectMappedBuffer request pos invalid, request pos: " + pos + ", size: " + size + + ", fileFromOffset: " + this.fileFromOffset); + } + + return null; +} +``` + +3、Broker 正常停止文件恢复 + +org.apache.rocketmq.store.CommitLog#recoverNormally + +首先查询消息是否验证 CRC + +`boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();` + +从倒数第 3 个文件开始恢复,如果不足 3 个文件,则从第一个文件开始恢复 + +```java +int index = mappedFiles.size() - 3; +if (index < 0) + index = 0; +``` + +循环遍历 CommitLog 文件,每次取出一条消息 + +`DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);` + +如果查找结果为 true 并且消息的长度大于 0,表示消息正确,mappedFileOffset 指针向前移动本条消息的长度; + +```java +if (dispatchRequest.isSuccess() && size > 0) { + mappedFileOffset += size; +} +``` + +如果查找结果为 true 并且结果等于 0,表示已到文件 的末尾,如果还有下一个文件,则重置 processOffset、mappedOffset 并重复上述步骤,否则跳出循环; + +```java +else if (dispatchRequest.isSuccess() && size == 0) { + index++; + if (index >= mappedFiles.size()) { + // Current branch can not happen + log.info("recover last 3 physics file over, last mapped file " + mappedFile.getFileName()); + break; + } else { + mappedFile = mappedFiles.get(index); + byteBuffer = mappedFile.sliceByteBuffer(); + processOffset = mappedFile.getFileFromOffset(); + mappedFileOffset = 0; + log.info("recover next physics file, " + mappedFile.getFileName()); + } +} +``` + +如果查找结果为 false,则表示消息没有填满该文件,跳出循环,结束遍历 + +```java +else if (!dispatchRequest.isSuccess()) { + log.info("recover physics file end, " + mappedFile.getFileName()); + break; +} +``` + +更新 committedPosition 和 flushedWhere 指针 + +```java +this.mappedFileQueue.setFlushedWhere(processOffset); +this.mappedFileQueue.setCommittedWhere(processOffset); +``` + +删除 offset 之后的所有文件。遍历目录下面的所有文件,如果文件尾部偏移量小于 offset 则跳过该文件,如果尾部的偏移量大于 offset,则进一步比较 offset 与文件的开始偏移量,如果 offset 大于文件的开始偏移量,说明当前文件包含了有效偏移量,设置 MappedFile 的 flushPosition 和 commitedPosition。 + +如果 offset 小于文件的开始偏移量,说明该文件是有效文件后面创建的,调用 MappedFile#destroy()方法释放资源 + +```java +if (fileTailOffset > offset) { + if (offset >= file.getFileFromOffset()) { + file.setWrotePosition((int) (offset % this.mappedFileSize)); + file.setCommittedPosition((int) (offset % this.mappedFileSize)); + file.setFlushedPosition((int) (offset % this.mappedFileSize)); + } else { + file.destroy(1000); + willRemoveFiles.add(file); + } +} +``` + +释放资源需要关闭 MappedFile 和文件通道 fileChannel + +```java +public boolean destroy(final long intervalForcibly) { + this.shutdown(intervalForcibly); + + if (this.isCleanupOver()) { + try { + this.fileChannel.close(); + log.info("close file channel " + this.fileName + " OK"); + + long beginTime = System.currentTimeMillis(); + boolean result = this.file.delete(); + log.info("delete file[REF:" + this.getRefCount() + "] " + this.fileName + + (result ? " OK, " : " Failed, ") + "W:" + this.getWrotePosition() + " M:" + + this.getFlushedPosition() + ", " + + UtilAll.computeElapsedTimeMilliseconds(beginTime)); + } catch (Exception e) { + log.warn("close file channel " + this.fileName + " Failed. ", e); + } + + return true; + } else { + log.warn("destroy mapped file[REF:" + this.getRefCount() + "] " + this.fileName + + " Failed. cleanupOver: " + this.cleanupOver); + } + + return false; +} +``` + +判断`maxPhyOffsetOfConsumeQueue`是否大于 processOffset,如果大于,需要删除 ConsumeQueue 中 processOffset 之后的数据 + +```java +if (maxPhyOffsetOfConsumeQueue >= processOffset) { + log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset); + this.defaultMessageStore.truncateDirtyLogicFiles(processOffset); +} +``` + +```java +public void truncateDirtyLogicFiles(long phyOffset) { + ConcurrentMap> tables = DefaultMessageStore.this.consumeQueueTable; + + for (ConcurrentMap maps : tables.values()) { + for (ConsumeQueue logic : maps.values()) { + logic.truncateDirtyLogicFiles(phyOffset); + } + } +} +``` diff --git a/docs/rocketmq/rocketmq-consume-message-process.md b/docs/rocketmq/rocketmq-consume-message-process.md new file mode 100644 index 00000000..50cf4cd1 --- /dev/null +++ b/docs/rocketmq/rocketmq-consume-message-process.md @@ -0,0 +1,220 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ 消息消费流程 + +拉取消息 成功之后 会调用 org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService#submitConsumeRequest 组装 消费消息 请求 + +获取 consumeMessageBatchMaxSize,表示一个 ConsumeRequest 包含的消息 数量,默认为 1 + +入参 msgs 为拉取消息的最大值,默认为 32 + +如果 msgs 小于等于 consumeMessageBatchMaxSize,直接创建`ConsumeRequest`任务并提交到 线程池,当出现`RejectedExecutionException`异常时会重新提交任务,但是查看线程池的队列 + +`this.consumeRequestQueue = new LinkedBlockingQueue();` + +为无界队列,最大值为`Integer.MAX_VALUE`,理论上不会出现该异常 + +```java +if (msgs.size() <= consumeBatchSize) { + ConsumeRequest consumeRequest = new ConsumeRequest(msgs, processQueue, messageQueue); + try { + this.consumeExecutor.submit(consumeRequest); + } catch (RejectedExecutionException e) { + this.submitConsumeRequestLater(consumeRequest); + } +} +``` + +如果 msgs 大于 consumeMessageBatchMaxSize,消息分批处理,即创建多个`ConsumeRequest`任务 + +```java +for (int total = 0; total < msgs.size(); ) { + List msgThis = new ArrayList(consumeBatchSize); + for (int i = 0; i < consumeBatchSize; i++, total++) { + if (total < msgs.size()) { + msgThis.add(msgs.get(total)); + } else { + break; + } + } + + ConsumeRequest consumeRequest = new ConsumeRequest(msgThis, processQueue, messageQueue); + try { + this.consumeExecutor.submit(consumeRequest); + } catch (RejectedExecutionException e) { + for (; total < msgs.size(); total++) { + msgThis.add(msgs.get(total)); + } + + this.submitConsumeRequestLater(consumeRequest); + } +} +``` + +`class ConsumeRequest implements Runnable` + +详细的消费逻辑查看 org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.ConsumeRequest#run + +第 1 步:首先会校验队列的 dropped 是否为 true,当队列重平衡的时候,该队列可能会被分配给其他消费者,如果该队列被分配给其他消费者,会设置 dropped 为 true + +```java +if (this.processQueue.isDropped()) { + log.info("the message queue not be able to consume, because it's dropped. group={} {}", ConsumeMessageConcurrentlyService.this.consumerGroup, this.messageQueue); + return; +} +``` + +第 2 步:如果是重试消息重新设置主题 + +```java +public void resetRetryAndNamespace(final List msgs, String consumerGroup) { + final String groupTopic = MixAll.getRetryTopic(consumerGroup); + for (MessageExt msg : msgs) { + String retryTopic = msg.getProperty(MessageConst.PROPERTY_RETRY_TOPIC); + if (retryTopic != null && groupTopic.equals(msg.getTopic())) { + msg.setTopic(retryTopic); + } + + if (StringUtils.isNotEmpty(this.defaultMQPushConsumer.getNamespace())) { + msg.setTopic(NamespaceUtil.withoutNamespace(msg.getTopic(), this.defaultMQPushConsumer.getNamespace())); + } + } +} +``` + +第 3 步:如果有钩子函数则执行 + +```java +if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { + consumeMessageContext = new ConsumeMessageContext(); + consumeMessageContext.setNamespace(defaultMQPushConsumer.getNamespace()); + consumeMessageContext.setConsumerGroup(defaultMQPushConsumer.getConsumerGroup()); + consumeMessageContext.setProps(new HashMap()); + consumeMessageContext.setMq(messageQueue); + consumeMessageContext.setMsgList(msgs); + consumeMessageContext.setSuccess(false); + ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookBefore(consumeMessageContext); +} +``` + +第 4 步:调用消息监听器的`consumeMessage执行具体的消费逻辑` ,返回值为`ConsumeConcurrentlyStatus` + +```java +try { + if (msgs != null && !msgs.isEmpty()) { + for (MessageExt msg : msgs) { + MessageAccessor.setConsumeStartTimeStamp(msg, String.valueOf(System.currentTimeMillis())); + } + } + status = listener.consumeMessage(Collections.unmodifiableList(msgs), context); +} catch (Throwable e) { + log.warn(String.format("consumeMessage exception: %s Group: %s Msgs: %s MQ: %s", + RemotingHelper.exceptionSimpleDesc(e), + ConsumeMessageConcurrentlyService.this.consumerGroup, + msgs, + messageQueue), e); + hasException = true; +} +``` + +```java +public enum ConsumeConcurrentlyStatus { + /** + * Success consumption + */ + CONSUME_SUCCESS, + /** + * Failure consumption,later try to consume + */ + RECONSUME_LATER; +} + +``` + +第 5 步:如果有 钩子 函数执行钩子 + +```java +if (ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.hasHook()) { + consumeMessageContext.setStatus(status.toString()); + consumeMessageContext.setSuccess(ConsumeConcurrentlyStatus.CONSUME_SUCCESS== status); + ConsumeMessageConcurrentlyService.this.defaultMQPushConsumerImpl.executeHookAfter(consumeMessageContext); +} +``` + +第 6 步:再次校验队列 的 dropped 状态 ,如果为 false 才会对结果进行处理 + +```java +if (!processQueue.isDropped()) { + ConsumeMessageConcurrentlyService.this.processConsumeResult(status, context, this); +} else { + log.warn("processQueue is dropped without process consume result. messageQueue={}, msgs={}", messageQueue, msgs); +} +``` + +org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService#processConsumeResult + +第 7 步:计算 ackIndex,如果为`CONSUME_SUCCESS`等于`consumeRequest.getMsgs().size() - 1;` + +如果为`RECONSUME_LATER`等于-1 + +```java +switch (status) { + caseCONSUME_SUCCESS: + if (ackIndex >= consumeRequest.getMsgs().size()) { + ackIndex = consumeRequest.getMsgs().size() - 1; + } + int ok = ackIndex + 1; + int failed = consumeRequest.getMsgs().size() - ok; + this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), ok); + this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), failed); + break; + caseRECONSUME_LATER: + ackIndex = -1; + this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), + consumeRequest.getMsgs().size()); + break; + default: + break; +} +``` + +第 8 步:如果是广播模式并且是消费失败,打印警告 信息,如果是集群模式并且消费失败会将消息发送到 broker,如果发送失败将消息封装到 consumerRequest 中延迟消费 + +```java +switch (this.defaultMQPushConsumer.getMessageModel()) { + caseBROADCASTING: + for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) { + MessageExt msg = consumeRequest.getMsgs().get(i); + log.warn("BROADCASTING, the message consume failed, drop it, {}", msg.toString()); + } + break; + caseCLUSTERING: + List msgBackFailed = new ArrayList(consumeRequest.getMsgs().size()); + for (int i = ackIndex + 1; i < consumeRequest.getMsgs().size(); i++) { + MessageExt msg = consumeRequest.getMsgs().get(i); + boolean result = this.sendMessageBack(msg, context); + if (!result) { + msg.setReconsumeTimes(msg.getReconsumeTimes() + 1); + msgBackFailed.add(msg); + } + } + + if (!msgBackFailed.isEmpty()) { + consumeRequest.getMsgs().removeAll(msgBackFailed); + + this.submitConsumeRequestLater(msgBackFailed, consumeRequest.getProcessQueue(), consumeRequest.getMessageQueue()); + } + break; + default: + break; +} +``` + +第 9 步:更新消息消费偏移量 + +```java +long offset = consumeRequest.getProcessQueue().removeMessage(consumeRequest.getMsgs()); +if (offset >= 0 && !consumeRequest.getProcessQueue().isDropped()) { + this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), offset, true); +} +``` diff --git a/docs/rocketmq/rocketmq-consumequeue.md b/docs/rocketmq/rocketmq-consumequeue.md new file mode 100644 index 00000000..40c6c7d8 --- /dev/null +++ b/docs/rocketmq/rocketmq-consumequeue.md @@ -0,0 +1,199 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ ConsumeQueue 详解 + +RocketMQ 基于主题订阅模式实现消息消费,消费者关注每一个主题下的所有消息,但是同一主题下的消息是不连续地存储在 CommitLog 文件中的,如果消费者直接从消息存储文件中遍历查找主题下的消息,效率会特别低。所以为了在查找消息的时候效率更高一些,设计了 ConsumeQueue 文件,可以看作 CommitLog 消费的目录文件. + +ConsumeQueue 的第一级目录为消息主题名称,第二级目录为主题的队列 id + +为了加速 ConsumeQueue 消息的查询速度并节省磁盘空间,不会存储消息的全量信息,只会 存储一些 关键信息,如 8 字节的 CommmitLog 偏移量、4 字节的文件大小、8 字节的 tag 哈希码 + +1、根据消息存储时间查找物理偏移量: + +org.apache.rocketmq.store.ConsumeQueue#getOffsetInQueueByTime + +第一步:根据时间戳定位物理文件 + +```java +public MappedFile getMappedFileByTime(final long timestamp) { + Object[] mfs = this.copyMappedFiles(0); + + if (null == mfs) + return null; + + for (int i = 0; i < mfs.length; i++) { + MappedFile mappedFile = (MappedFile) mfs[i]; + if (mappedFile.getLastModifiedTimestamp() >= timestamp) { + return mappedFile; + } + } + + return (MappedFile) mfs[mfs.length - 1]; +} +``` + +从第一个文件 开始,找到第一个更新时间大于该时间戳的文件 + +第二步:利用二分查找法来加速检索 + +计算最低查找偏移量,如果消息队列偏移量大于文件的偏移量,则最低偏移量等于消息队列偏移量减去文件的偏移量,反之为 0 + +`int low = minLogicOffset > mappedFile.getFileFromOffset() ? (int) (minLogicOffset - mappedFile.getFileFromOffset()) : 0;` + +计算中间偏移量,其中*`CQ_STORE_UNIT_SIZE` =* 8 字节的 CommmitLog 偏移量 + 4 字节的文件大小+8 字节的 tag 哈希码 + +`midOffset = (low + high) / (2 * *CQ_STORE_UNIT_SIZE*) * *CQ_STORE_UNIT_SIZE*;` + +如果得到的物理偏移量小于当前最小物理偏移量,则待查找消息的物理偏移量大于 midOffset,将 low 设置为 midOffset,继续查询 + +```java +byteBuffer.position(midOffset); +long phyOffset = byteBuffer.getLong(); +int size = byteBuffer.getInt(); +if (phyOffset < minPhysicOffset) { + low = midOffset +CQ_STORE_UNIT_SIZE; + leftOffset = midOffset; + continue; +} +``` + +如果得到的物理偏移量大于最小物理偏移量,说明该消息为有效信息,则根据消息物理偏移量和消息长度获取消息存储的时间戳 + +```java +long storeTime = this.defaultMessageStore.getCommitLog().pickupStoreTimestamp(phyOffset, size); +``` + +如果存储时间小于 0,则为无效消息,返回 0; + +如果存储时间戳等于待查找时间戳,说明查找到了目标消息,设置 targetOffset,跳出循环; + +如果存储时间戳大于待查找时间戳,说明待查找消息的物理偏移量小于 midOffset,设置 high 为 midOffset,设置 rightIndexValue 等于 storeTime,设置 rightOffset 为 midOffset; + +如果存储时间戳小于待查找时间戳,说明待查找消息的物理偏移量大于 midOffset,设置 low 为 midOffset,设置 leftIndexValue 等于 storeTime,设置 leftOffset 为 midOffset + +```java +if (storeTime < 0) { + return 0; +} else if (storeTime == timestamp) { + targetOffset = midOffset; + break; +} else if (storeTime > timestamp) { + high = midOffset -CQ_STORE_UNIT_SIZE; + rightOffset = midOffset; + rightIndexValue = storeTime; +} else { + low = midOffset +CQ_STORE_UNIT_SIZE; + leftOffset = midOffset; + leftIndexValue = storeTime; +} +``` + +如果 targetOffset 不等于-1,表示找到了存储时间戳等于待查找时间戳的消息; + +如果 leftIndexValue 等于-1,返回大于并且最接近待查找消息的时间戳的偏移量 + +如果 rightIndexValue 等于-1,返回小于并且最接近待查找消息的时间戳的偏移量 + +```java +if (targetOffset != -1) { + + offset = targetOffset; +} else { + if (leftIndexValue == -1) { + offset = rightOffset; + } else if (rightIndexValue == -1) { + offset = leftOffset; + } else { + offset = Math.abs(timestamp - leftIndexValue) > Math.abs(timestamp - rightIndexValue) ? rightOffset : leftOffset; + } +} +``` + +2、根据当前偏移量获取下一个文件的偏移量 + +org.apache.rocketmq.store.ConsumeQueue#rollNextFile + +```java +public long rollNextFile(final long index) { + int mappedFileSize = this.mappedFileSize; + int totalUnitsInFile = mappedFileSize /CQ_STORE_UNIT_SIZE; + return index + totalUnitsInFile - index % totalUnitsInFile; +} +``` + +3、ConsumeQueue 添加消息 + +org.apache.rocketmq.store.ConsumeQueue#putMessagePositionInfo + +将消息偏移量、消息长度、tag 哈希码写入 ByteBuffer,将内容追加到 ConsumeQueue 的内存映射文件中。 + +```java +private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode, + final long cqOffset) { + + if (offset + size <= this.maxPhysicOffset) { + log.warn("Maybe try to build consume queue repeatedly maxPhysicOffset={} phyOffset={}", maxPhysicOffset, offset); + return true; + } + + this.byteBufferIndex.flip(); + this.byteBufferIndex.limit(CQ_STORE_UNIT_SIZE); + this.byteBufferIndex.putLong(offset); + this.byteBufferIndex.putInt(size); + this.byteBufferIndex.putLong(tagsCode); + + final long expectLogicOffset = cqOffset *CQ_STORE_UNIT_SIZE; + + MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(expectLogicOffset); + if (mappedFile != null) { + + if (mappedFile.isFirstCreateInQueue() && cqOffset != 0 && mappedFile.getWrotePosition() == 0) { + this.minLogicOffset = expectLogicOffset; + this.mappedFileQueue.setFlushedWhere(expectLogicOffset); + this.mappedFileQueue.setCommittedWhere(expectLogicOffset); + this.fillPreBlank(mappedFile, expectLogicOffset); + log.info("fill pre blank space " + mappedFile.getFileName() + " " + expectLogicOffset + " " + + mappedFile.getWrotePosition()); + } + + if (cqOffset != 0) { + long currentLogicOffset = mappedFile.getWrotePosition() + mappedFile.getFileFromOffset(); + + if (expectLogicOffset < currentLogicOffset) { + log.warn("Build consume queue repeatedly, expectLogicOffset: {} currentLogicOffset: {} Topic: {} QID: {} Diff: {}", expectLogicOffset, currentLogicOffset, this.topic, this.queueId, expectLogicOffset - currentLogicOffset); + return true; + } + + if (expectLogicOffset != currentLogicOffset) { + LOG_ERROR.warn("[BUG]logic queue order maybe wrong, expectLogicOffset: {} currentLogicOffset: {} Topic: {} QID: {} Diff: {}", + expectLogicOffset, + currentLogicOffset, + this.topic, + this.queueId, + expectLogicOffset - currentLogicOffset + ); + } + } + this.maxPhysicOffset = offset + size; + return mappedFile.appendMessage(this.byteBufferIndex.array()); + } + return false; +} +``` + +4、ConsumeQueue 文件删除 + +org.apache.rocketmq.store.ConsumeQueue#destroy + +重置 ConsumeQueue 的 maxPhysicOffset 与 minLogicOffset,调用 MappedFileQueue 的 destroy()方法将 ConsumeQueue 目录下的文件全部删除 + +```java +public void destroy() { + this.maxPhysicOffset = -1; + this.minLogicOffset = 0; + this.mappedFileQueue.destroy(); + if (isExtReadEnable()) { + this.consumeQueueExt.destroy(); + } +} +``` diff --git a/docs/rocketmq/rocketmq-consumer-start.md b/docs/rocketmq/rocketmq-consumer-start.md new file mode 100644 index 00000000..10b86584 --- /dev/null +++ b/docs/rocketmq/rocketmq-consumer-start.md @@ -0,0 +1,299 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ 消费者启动流程 + +org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#start + +`1、检查配置信息` + +org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#checkConfig + +校验消费组的长度不能大于 255 + +`public static final int CHARACTER_MAX_LENGTH = 255;` + +```java +if (group.length() >CHARACTER_MAX_LENGTH) { + throw new MQClientException("the specified group is longer than group max length 255.", null); +} +``` + +消费组名称只能包含数字、字母、%、-、\_、| + +```java +// regex: ^[%|a-zA-Z0-9_-]+$ +// % +VALID_CHAR_BIT_MAP['%'] = true; +// - +VALID_CHAR_BIT_MAP['-'] = true; +// _ +VALID_CHAR_BIT_MAP['_'] = true; +// | +VALID_CHAR_BIT_MAP['|'] = true; +for (int i = 0; i = '0' && i <= '9') { + // 0-9 + VALID_CHAR_BIT_MAP[i] = true; + } else if (i >= 'A' && i <= 'Z') { + // A-Z + VALID_CHAR_BIT_MAP[i] = true; + } else if (i >= 'a' && i <= 'z') { + // a-z + VALID_CHAR_BIT_MAP[i] = true; + } +} +``` + +```java +public static boolean isTopicOrGroupIllegal(String str) { + int strLen = str.length(); + int len =VALID_CHAR_BIT_MAP.length; + boolean[] bitMap =VALID_CHAR_BIT_MAP; + for (int i = 0; i < strLen; i++) { + char ch = str.charAt(i); + if (ch >= len || !bitMap[ch]) { + return true; + } + } + return false; +} +``` + +消费组名称不能是`DEFAULT_CONSUMER` + +`public static final String DEFAULT_CONSUMER_GROUP = "DEFAULT_CONSUMER";` + +```java +if (this.defaultMQPushConsumer.getConsumerGroup().equals(MixAll.DEFAULT_CONSUMER_GROUP)) { + throw new MQClientException("consumerGroup can not equal " + MixAll.DEFAULT_CONSUMER_GROUP ++ ", please specify another one." + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); +} +``` + +消费者最小线程数需要在 1-1000 之间 + +```java +if (this.defaultMQPushConsumer.getConsumeThreadMin() < 1 + || this.defaultMQPushConsumer.getConsumeThreadMin() > 1000) { + throw new MQClientException("consumeThreadMin Out of range [1, 1000]" + + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); +} +``` + +消费者最大线程数需要在 1-1000 之间 + +```java +if (this.defaultMQPushConsumer.getConsumeThreadMax() < 1 || this.defaultMQPushConsumer.getConsumeThreadMax() > 1000) { + throw new MQClientException("consumeThreadMax Out of range [1, 1000]" + + FAQUrl.suggestTodo(FAQUrl.CLIENT_PARAMETER_CHECK_URL), null); +} +``` + +`2、设置订阅信息` + +构造主题订阅消息`SubscriptionData`并将其加入`RebalanceImpl`,如果是消费模式是集群,订阅默认的重试主题并且构造`SubscriptionData`加入`RebalanceImpl` + +```java +private void copySubscription() throws MQClientException { + try { + Map sub = this.defaultMQPushConsumer.getSubscription(); + if (sub != null) { + for (final Map.Entry entry : sub.entrySet()) { + final String topic = entry.getKey(); + final String subString = entry.getValue(); + SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(topic, subString); + this.rebalanceImpl.getSubscriptionInner().put(topic, subscriptionData); + } + } + + if (null == this.messageListenerInner) { + this.messageListenerInner = this.defaultMQPushConsumer.getMessageListener(); + } + + switch (this.defaultMQPushConsumer.getMessageModel()) { + caseBROADCASTING: + break; + caseCLUSTERING: + final String retryTopic = MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()); + SubscriptionData subscriptionData = FilterAPI.buildSubscriptionData(retryTopic, SubscriptionData.SUB_ALL); + this.rebalanceImpl.getSubscriptionInner().put(retryTopic, subscriptionData); + break; + default: + break; + } + } catch (Exception e) { + throw new MQClientException("subscription exception", e); + } +} +``` + +`3、初始化MqClientInstance、RebalanceImpl、PullApiWrapper` + +创建`MqClientInstance`, 无论在生产者端还是消费者端都是一个很重要的类, 封装了 Topic 信息、broker 信息,当然还有生产者和消费者的信息。 + +```java +public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) { + String clientId = clientConfig.buildMQClientId(); + MQClientInstance instance = this.factoryTable.get(clientId); + if (null == instance) { + instance = new MQClientInstance(clientConfig.cloneClientConfig(), + this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook); + MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance); + if (prev != null) { + instance = prev; + log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId); + } else { + log.info("Created new MQClientInstance for clientId:[{}]", clientId); + } + } + + return instance; +} +``` + +构造`RebalanceImpl` 用来负载消费者与队列的消费关系 + +```java +this.rebalanceImpl.setConsumerGroup(this.defaultMQPushConsumer.getConsumerGroup()); +this.rebalanceImpl.setMessageModel(this.defaultMQPushConsumer.getMessageModel()); +this.rebalanceImpl.setAllocateMessageQueueStrategy(this.defaultMQPushConsumer.getAllocateMessageQueueStrategy()); +this.rebalanceImpl.setmQClientFactory(this.mQClientFactory); +``` + +构造`PullApiWrapper` 消费者拉取消息类 + +```java +this.pullAPIWrapper = new PullAPIWrapper(mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup(), isUnitMode()); +this.pullAPIWrapper.registerFilterMessageHook(filterMessageHookList); +``` + +`4、设置消息偏移量` + +如果是广播模式消费,消息消费进度存储在消费端,如果是集群模式消费,消息消费进度存储在 broker 端 + +```java +if (this.defaultMQPushConsumer.getOffsetStore() != null) { + this.offsetStore = this.defaultMQPushConsumer.getOffsetStore(); +} else { + switch (this.defaultMQPushConsumer.getMessageModel()) { + caseBROADCASTING: + this.offsetStore = new LocalFileOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup()); + break; + caseCLUSTERING: + this.offsetStore = new RemoteBrokerOffsetStore(this.mQClientFactory, this.defaultMQPushConsumer.getConsumerGroup()); + break; + default: + break; + } + this.defaultMQPushConsumer.setOffsetStore(this.offsetStore); +} +this.offsetStore.load(); +``` + +`5、是否是顺序消费` + +根据是否是顺序消费构造不同的`ConsumeMessageService` + +```java +if (this.getMessageListenerInner() instanceof MessageListenerOrderly) { + this.consumeOrderly = true; + this.consumeMessageService = new ConsumeMessageOrderlyService(this, (MessageListenerOrderly) this.getMessageListenerInner()); +} else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) { + this.consumeOrderly = false; + this.consumeMessageService = new ConsumeMessageConcurrentlyService(this, (MessageListenerConcurrently) this.getMessageListenerInner()); +} +``` + +区别在于启动的线程任务不同: + +顺序消费线程: + +```java +if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) { + this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + ConsumeMessageOrderlyService.this.lockMQPeriodically(); + } catch (Throwable e) { + log.error("scheduleAtFixedRate lockMQPeriodically exception", e); + } + } + }, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS); +} +``` + +正常消费线程: + +```java +this.cleanExpireMsgExecutors.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + cleanExpireMsg(); + } catch (Throwable e) { + log.error("scheduleAtFixedRate cleanExpireMsg exception", e); + } + } + +}, this.defaultMQPushConsumer.getConsumeTimeout(), this.defaultMQPushConsumer.getConsumeTimeout(), TimeUnit.MINUTES); +``` + +6`、启动MQClientInstance` + +消费者与生产者共用 MQClientInstance + +大部分流程已经在生产者启动流程中讲解,这里主要讲解与生产者不同的部分 + +启动保证消费者偏移量最终一致性的任务 + +```java +this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + MQClientInstance.this.persistAllConsumerOffset(); + } catch (Exception e) { + log.error("ScheduledTask persistAllConsumerOffset exception", e); + } + } +}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS); +``` + +启动调整线程池大小任务: + +```java +this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + MQClientInstance.this.adjustThreadPool(); + } catch (Exception e) { + log.error("ScheduledTask adjustThreadPool exception", e); + } + } +}, 1, 1, TimeUnit.MINUTES); +``` + +启动重平衡服务: + +`this.rebalanceService.start();` + +7`、更新订阅主题信息` + +更新主题订阅信息: + +```java +private void updateTopicSubscribeInfoWhenSubscriptionChanged() { + Map subTable = this.getSubscriptionInner(); + if (subTable != null) { + for (final Map.Entry entry : subTable.entrySet()) { + final String topic = entry.getKey(); + this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); + } + } +} +``` diff --git a/docs/rocketmq/rocketmq-indexfile.md b/docs/rocketmq/rocketmq-indexfile.md new file mode 100644 index 00000000..9bd08596 --- /dev/null +++ b/docs/rocketmq/rocketmq-indexfile.md @@ -0,0 +1,168 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ IndexFile 详解 + +首先明确一下 IndexFile 的文件结构 + +Index header + 哈希槽,每个槽下面挂载 index 索引,类似哈希表的结构 + +一个 Index 文件默认包含 500 万个哈希槽,一个哈希槽最多存储 4 个 index,也就是一个 IndexFile 默认最多包含 2000 万个 index + +Index header: + +40byte Index header = 8byte 的 beginTimestamp(IndexFile 对应第一条消息的存储时间) + 8byte 的 endTimestamp (IndexFile 对应最后一条消息的存储时间) + 8byte 的 beginPhyoffset(IndexFile 对应第一条消息在 CommitLog 的物理偏移量) + 8byte 的 endPhyoffset(IndexFile 对应最后一条消息在 CommitLog 的物理偏移量)+ 4byte 的 hashSlotCount(已有 index 的槽个数)+ 4byte 的 indexCount(索引个数) + +哈希槽: + +每个哈希槽占用 4 字节,存储当前槽下面最新的 index 的序号 + +Index: + +20byte 的 index = 4byte 的 keyHash(key 的哈希码) + 8byte 的 phyOffset(消息在文件中的物理偏移量)+ 4byte 的 timeDiff(该索引对应消息的存储时间与当前索引文件第一条消息的存储时间的差值)+ 4byte 的 preIndexNo(该条目的前一个 Index 的索引值) + +1、将消息索引键与消息偏移量的映射关系写入 indexFile + +org.apache.rocketmq.store.index.IndexFile#putKey + +当前已使用的 Index 大于等于允许的最大个数时,返回 false,表示当前 Index 文件已满。 + +如果当前 Index 文件未满,则根据 key 计算出哈希码,然后对槽数量取余定位到某一个哈希槽位置, + +哈希槽的物理偏移量 = IndexHeader 的大小(默认 40Byte) + 哈希槽位置 \* 每个哈希槽的大小(4 字节) + +```java +int keyHash = indexKeyHashMethod(key); +int slotPos = keyHash % this.hashSlotNum; +int absSlotPos = IndexHeader.INDEX_HEADER_SIZE+ slotPos * hashSlotSize; +``` + +读取哈希槽中的数据,如果哈希槽中的数据小于 0 或者大于 index 的个数,则为无效索引,将 slotValue 置为 0 + +```java +int slotValue = this.mappedByteBuffer.getInt(absSlotPos); +if (slotValue <=invalidIndex|| slotValue > this.indexHeader.getIndexCount()) { + slotValue =invalidIndex; +} +``` + +计算本次存储消息的时间戳与 indexFile 第一条消息存储时间戳的差值并转换为秒 + +```java +long timeDiff = storeTimestamp - this.indexHeader.getBeginTimestamp(); + +timeDiff = timeDiff / 1000; + +if (this.indexHeader.getBeginTimestamp() <= 0) { + timeDiff = 0; +} else if (timeDiff > Integer.MAX_VALUE) { + timeDiff = Integer.MAX_VALUE; +} else if (timeDiff < 0) { + timeDiff = 0; +} +``` + +新添加的消息 index 的物理偏移量 = IndexHeader 大小(40Byte) + Index 文件哈希槽的数量 _ 哈希槽的大小(4Byte ) + Index 文件索引数量 _ 索引大小(20Byte) + +将消息哈希码、消息物理偏移量、消息存储时间戳与 Index 文件第一条消息的时间戳的差值、当前哈希槽的值、当前 Indexfile 的索引个数存入 mappedByteBuffer + +```java +int absIndexPos = IndexHeader.INDEX_HEADER_SIZE+ this.hashSlotNum *hashSlotSize ++ this.indexHeader.getIndexCount() *indexSize; + +this.mappedByteBuffer.putInt(absIndexPos, keyHash); +this.mappedByteBuffer.putLong(absIndexPos + 4, phyOffset); +this.mappedByteBuffer.putInt(absIndexPos + 4 + 8, (int) timeDiff); +this.mappedByteBuffer.putInt(absIndexPos + 4 + 8 + 4, slotValue); + +this.mappedByteBuffer.putInt(absSlotPos, this.indexHeader.getIndexCount()); +``` + +更新 IndexHeader 信息: + +如果该 IndexFile 哈希槽中消息的数量小于等于 1,更新 IndexHeader 的 beginPhyOffset 和 beginTimesttamp + +每次添加消息之后更新 IndexCount、endPhyOffset、endTimestamp + +```java +if (this.indexHeader.getIndexCount() <= 1) { + this.indexHeader.setBeginPhyOffset(phyOffset); + this.indexHeader.setBeginTimestamp(storeTimestamp); +} + +if (invalidIndex== slotValue) { + this.indexHeader.incHashSlotCount(); +} +this.indexHeader.incIndexCount(); +this.indexHeader.setEndPhyOffset(phyOffset); +this.indexHeader.setEndTimestamp(storeTimestamp); +``` + +2、根据 key 查找消息 + +org.apache.rocketmq.store.index.IndexFile#selectPhyOffset + +参数如下: + +`List phyOffsets`: 查询到的物理偏移量 + +`String key: 索引key` + +`int maxNum`:本次查找的最大消息条数 + +`long begin`:开始时间戳 + +long end: 结束时间戳 + +根据 key 计算哈希码,哈希码与哈希槽的数量取余得到哈希槽的索引 + +哈希槽的物理地址 = IndexHeader(40byte) + 哈希槽索引 \* 每个哈希槽的大小(4byte) + +```java +int keyHash = indexKeyHashMethod(key); +int slotPos = keyHash % this.hashSlotNum; +int absSlotPos = IndexHeader.INDEX_HEADER_SIZE+ slotPos * hashSlotSize; +``` + +`从mappedByteBuffer`获取哈希槽的值,如果值小于等于 0 或者值大于 IndexCount + +或者 IndexCount 的 值小于等于 1 则表示没有有效的结果数据 + +如果查询返回的结果数量大于等于要查询的最大消息条数,终止循环 + +```java +if (slotValue <=invalidIndex|| slotValue > this.indexHeader.getIndexCount() + || this.indexHeader.getIndexCount() <= 1) { +} else { + for (int nextIndexToRead = slotValue; ; ) { + if (phyOffsets.size() >= maxNum) { + break; + } +``` + +如果存储的时间戳小于 0,结束查找,如果哈希码匹配并且存储时间在要查找的开始时间戳和结束时间戳之间,将结果偏移量加入返回结果中 + +```java +if (timeDiff < 0) { + break; +} + +timeDiff *= 1000L; + +long timeRead = this.indexHeader.getBeginTimestamp() + timeDiff; +boolean timeMatched = (timeRead >= begin) && (timeRead <= end); + +if (keyHash == keyHashRead && timeMatched) { + phyOffsets.add(phyOffsetRead); +} +``` + +校验该 index 的上一个 index,如果上一个 index 的索引大于 0 并且小于等于 indexCount,时间戳大于等于要查找的开始时间戳,则继续查找 + +```java +if (prevIndexRead <=invalidIndex || prevIndexRead > this.indexHeader.getIndexCount() + || prevIndexRead == nextIndexToRead || timeRead < begin) { + break; +} + +nextIndexToRead = prevIndexRead; +``` diff --git a/docs/rocketmq/rocketmq-mappedfile-detail.md b/docs/rocketmq/rocketmq-mappedfile-detail.md new file mode 100644 index 00000000..0354e963 --- /dev/null +++ b/docs/rocketmq/rocketmq-mappedfile-detail.md @@ -0,0 +1,254 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ MappedFile 内存映射文件详解 + +1、MappedFile 初始化 + +```java +private void init(final String fileName, final int fileSize) throws IOException { + this.fileName = fileName; + this.fileSize = fileSize; + this.file = new File(fileName); + this.fileFromOffset = Long.parseLong(this.file.getName()); + boolean ok = false; + + ensureDirOK(this.file.getParent()); + + try { + this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel(); + this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize); + TOTAL_MAPPED_VIRTUAL_MEMORY.addAndGet(fileSize); + TOTAL_MAPPED_FILES.incrementAndGet(); + ok = true; + } catch (FileNotFoundException e) { + log.error("Failed to create file " + this.fileName, e); + throw e; + } catch (IOException e) { + log.error("Failed to map file " + this.fileName, e); + throw e; + } finally { + if (!ok && this.fileChannel != null) { + this.fileChannel.close(); + } + } +} +``` + +初始化`fileFromOffset`,因为 commitLog 文件夹下的文件都是以偏移量为命名的,所以转成了 long 类型 + +确认文件目录是否存在,不存在则创建 + +```java +public static void ensureDirOK(final String dirName) { + if (dirName != null) { + if (dirName.contains(MessageStoreConfig.MULTI_PATH_SPLITTER)) { + String[] dirs = dirName.trim().split(MessageStoreConfig.MULTI_PATH_SPLITTER); + for (String dir : dirs) { + createDirIfNotExist(dir); + } + } else { + createDirIfNotExist(dirName); + } + } +} +``` + +通过`RandomAccessFile`设置 fileChannel + +`this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();` + +使用 NIO 内存映射将文件映射到内存中 + +`this.mappedByteBuffer = this.fileChannel.map(MapMode.*READ_WRITE*, 0, fileSize);` + +2、MappedFile 提交 + +```java +public int commit(final int commitLeastPages) { + if (writeBuffer == null) { + //no need to commit data to file channel, so just regard wrotePosition as committedPosition. + return this.wrotePosition.get(); + } + if (this.isAbleToCommit(commitLeastPages)) { + if (this.hold()) { + commit0(); + this.release(); + } else { + log.warn("in commit, hold failed, commit offset = " + this.committedPosition.get()); + } + } + + // All dirty data has been committed to FileChannel. + if (writeBuffer != null && this.transientStorePool != null && this.fileSize == this.committedPosition.get()) { + this.transientStorePool.returnBuffer(writeBuffer); + this.writeBuffer = null; + } + + return this.committedPosition.get(); +} +``` + +如果 wroteBuffer 为空,直接返回 wrotePosition + +```java +if (writeBuffer == null) { + //no need to commit data to file channel, so just regard wrotePosition as committedPosition. + return this.wrotePosition.get(); +} +``` + +判断是否执行 commit 操作: + +如果文件已满,返回 true + +```java +if (this.isFull()) { + return true; +} +``` + +```java +public boolean isFull() { + return this.fileSize == this.wrotePosition.get(); +} +``` + +commitLeastPages 为本次提交的最小页数,如果 commitLeastPages 大于 0,计算当前写指针(`wrotePosition`)与上一次提交的指针`committedPosition`的差值 除以页*`OS_PAGE_SIZE`*的大小得到脏页数量,如果大于 commitLeastPages,就可以提交。如果 commitLeastPages 小于 0,则存在脏页就提交 + +```java +if (commitLeastPages > 0) { + return ((write /OS_PAGE_SIZE) - (flush /OS_PAGE_SIZE)) >= commitLeastPages; +} + +return write > flush; +``` + +MapperFile 具体的提交过程,首先创建 `writeBuffer`的共享缓存区,设置 position 为上一次提交的位置`committedPosition` ,设置 limit 为`wrotePosition`当前写指针,接着将 committedPosition 到 wrotePosition 的数据写入到 FileChannel 中,最后更新 committedPosition 指针为 wrotePosition + +```java +protected void commit0() { + int writePos = this.wrotePosition.get(); + int lastCommittedPosition = this.committedPosition.get(); + + if (writePos - lastCommittedPosition > 0) { + try { + ByteBuffer byteBuffer = writeBuffer.slice(); + byteBuffer.position(lastCommittedPosition); + byteBuffer.limit(writePos); + this.fileChannel.position(lastCommittedPosition); + this.fileChannel.write(byteBuffer); + this.committedPosition.set(writePos); + } catch (Throwable e) { + log.error("Error occurred when commit data to FileChannel.", e); + } + } +} +``` + +3、MappedFile 刷盘 + +判断是否要进行刷盘 + +文件是否已满 + +```java +if (this.isFull()) { + return true; +} +``` + +```java +public boolean isFull() { + return this.fileSize == this.wrotePosition.get(); +} +``` + +如果`flushLeastPages`大于 0,判断写数据指针位置-上次刷盘的指针位置, 然后除以*`OS_PAGE_SIZE 是否大于等于`*`flushLeastPages` + +如果 flushLeastPages 小于等于 0,判断是否有要刷盘的数据 + +```java +if (flushLeastPages > 0) { + return ((write /OS_PAGE_SIZE) - (flush /OS_PAGE_SIZE)) >= flushLeastPages; +} + +return write > flush; +``` + +获取最大读指针 + +```java +public int getReadPosition() { + return this.writeBuffer == null ? this.wrotePosition.get() : this.committedPosition.get(); +} +``` + +将数据刷出到磁盘 + +如果`writeBuffer`不为空或者通道的 position 不等于 0,通过 fileChannel 将数据刷新到磁盘 + +否则通过 MappedByteBuffer 将数据刷新到磁盘 + +4、MappedFile 销毁 + +```java +public boolean destroy(final long intervalForcibly) { + this.shutdown(intervalForcibly); + + if (this.isCleanupOver()) { + try { + this.fileChannel.close(); + log.info("close file channel " + this.fileName + " OK"); + + long beginTime = System.currentTimeMillis(); + boolean result = this.file.delete(); + log.info("delete file[REF:" + this.getRefCount() + "] " + this.fileName + + (result ? " OK, " : " Failed, ") + "W:" + this.getWrotePosition() + " M:" + + this.getFlushedPosition() + ", " + + UtilAll.computeElapsedTimeMilliseconds(beginTime)); + } catch (Exception e) { + log.warn("close file channel " + this.fileName + " Failed. ", e); + } + + return true; + } else { + log.warn("destroy mapped file[REF:" + this.getRefCount() + "] " + this.fileName + + " Failed. cleanupOver: " + this.cleanupOver); + } + + return false; +} +``` + +1> 关闭 MappedFile + +第一次调用时 this.`available为true`,设置 available 为 false,设置第一次关闭的时间戳为当前时间戳,调用 release()释放资源,只有在引用次数小于 1 的时候才会释放资源,如果引用次数大于 0,判断当前时间与 firstShutdownTimestamp 的差值是否大于最大拒绝存活期`intervalForcibly`,如果大于等于最大拒绝存活期,将引用数减少 1000,直到引用数小于 0 释放资源 + +```java +public void shutdown(final long intervalForcibly) { + if (this.available) { + this.available = false; + this.firstShutdownTimestamp = System.currentTimeMillis(); + this.release(); + } else if (this.getRefCount() > 0) { + if ((System.currentTimeMillis() - this.firstShutdownTimestamp) >= intervalForcibly) { + this.refCount.set(-1000 - this.getRefCount()); + this.release(); + } + } +} +``` + +2> 判断是否清理完成 + +是否清理完成的标准是引用次数小于等于 0 并且清理完成标记 cleanupOver 为 true + +```java +public boolean isCleanupOver() { + return this.refCount.get() <= 0 && this.cleanupOver; +} +``` + +3> 关闭文件通道 fileChannel + +`this.fileChannel.close();` diff --git a/docs/rocketmq/rocketmq-nameserver-broker.md b/docs/rocketmq/rocketmq-nameserver-broker.md new file mode 100644 index 00000000..45993f04 --- /dev/null +++ b/docs/rocketmq/rocketmq-nameserver-broker.md @@ -0,0 +1,220 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RockerMQ Nameserver 如何与 Broker 进行通信的? + +nameserver 每隔 10s 扫描一次 Broker,移除处于未激活状态的 Broker + +核心代码: + +`this.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5, 10, TimeUnit.*SECONDS*);` + +```java +public int scanNotActiveBroker() { + int removeCount = 0; + Iterator> it = this.brokerLiveTable.entrySet().iterator(); + while (it.hasNext()) { + Entry next = it.next(); + long last = next.getValue().getLastUpdateTimestamp(); + if ((last +BROKER_CHANNEL_EXPIRED_TIME) < System.currentTimeMillis()) { + RemotingUtil.closeChannel(next.getValue().getChannel()); + it.remove(); + log.warn("The broker channel expired, {} {}ms", next.getKey(),BROKER_CHANNEL_EXPIRED_TIME); + this.onChannelDestroy(next.getKey(), next.getValue().getChannel()); + + removeCount++; + } + } + + return removeCount; +} +``` + +broker 每隔 30 秒会向集群中所有的 NameServer 发送心跳包 + +核心代码: + +```java +this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister()); + } catch (Throwable e) { + log.error("registerBrokerAll Exception", e); + } + } +}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS); +``` + +```java +public synchronized void registerBrokerAll(final boolean checkOrderConfig, boolean oneway, boolean forceRegister) { + TopicConfigSerializeWrapper topicConfigWrapper = this.getTopicConfigManager().buildTopicConfigSerializeWrapper(); + + if (!PermName.isWriteable(this.getBrokerConfig().getBrokerPermission()) + || !PermName.isReadable(this.getBrokerConfig().getBrokerPermission())) { + ConcurrentHashMap topicConfigTable = new ConcurrentHashMap(); + for (TopicConfig topicConfig : topicConfigWrapper.getTopicConfigTable().values()) { + TopicConfig tmp = + new TopicConfig(topicConfig.getTopicName(), topicConfig.getReadQueueNums(), topicConfig.getWriteQueueNums(), + this.brokerConfig.getBrokerPermission()); + topicConfigTable.put(topicConfig.getTopicName(), tmp); + } + topicConfigWrapper.setTopicConfigTable(topicConfigTable); + } + + if (forceRegister || needRegister(this.brokerConfig.getBrokerClusterName(), + this.getBrokerAddr(), + this.brokerConfig.getBrokerName(), + this.brokerConfig.getBrokerId(), + this.brokerConfig.getRegisterBrokerTimeoutMills())) { + doRegisterBrokerAll(checkOrderConfig, oneway, topicConfigWrapper); + } +} +``` + +org.apache.rocketmq.namesrv.processor.DefaultRequestProcessor 是网络处理器解析请求类型,如果请求类型为`*RequestCode.REGISTER_BROKER`,则请求最终转发到 org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager#registerBroker\* + +代码太多,文字来描述一下: + +第一步:路由注册需要加写锁,防止并发修改 RouteInfoManager 中的路由表。首先判断 Broker 所属集群是否存在,如果不存在,则创建集群,然后将 broker 名加入集群。 + +第二步:维护 BrokerData 信息,首先从 brokerAddrTable 中根据 broker 名尝试获取 Broker 信息,如果不存在,则新建 BrokerData 放入 brokerAddrTable,registerFirst 设置为 true;如果存在,直接替换原先的 Broker 信息,registerFirst 设置为 false,表示非第一次注册 + +第三步:如果 Broker 为主节点,并且 Broker 的 topic 配置信息发生变化或者是初次注册,则需要创建或者更新 topic 的路由元数据,并填充 topicQueueTable + +根据 topicConfig 创建 QueueData 数据结构然后更新 topicQueueTable + +```java +private void createAndUpdateQueueData(final String brokerName, final TopicConfig topicConfig) { + QueueData queueData = new QueueData(); + queueData.setBrokerName(brokerName); + queueData.setWriteQueueNums(topicConfig.getWriteQueueNums()); + queueData.setReadQueueNums(topicConfig.getReadQueueNums()); + queueData.setPerm(topicConfig.getPerm()); + queueData.setTopicSysFlag(topicConfig.getTopicSysFlag()); + + Map queueDataMap = this.topicQueueTable.get(topicConfig.getTopicName()); + if (null == queueDataMap) { + queueDataMap = new HashMap<>(); + queueDataMap.put(queueData.getBrokerName(), queueData); + this.topicQueueTable.put(topicConfig.getTopicName(), queueDataMap); + log.info("new topic registered, {} {}", topicConfig.getTopicName(), queueData); + } else { + QueueData old = queueDataMap.put(queueData.getBrokerName(), queueData); + if (old != null && !old.equals(queueData)) { + log.info("topic changed, {} OLD: {} NEW: {}", topicConfig.getTopicName(), old, + queueData); + } + } +} +``` + +第四步:更新 BrokerLiveInfo,存储状态正常的 Broker 信息表,BrokerLiveInfo 是执行路由删除操作的重要依据。 + +```java +BrokerLiveInfo prevBrokerLiveInfo = this.brokerLiveTable.put(brokerAddr, + new BrokerLiveInfo( + System.currentTimeMillis(), + topicConfigWrapper.getDataVersion(), + channel, + haServerAddr)); +``` + +第五步:注册 Broker 的过滤器 Server 地址列表,一个 Broker 上会关联多个 FilterServer 消息过滤服务器。如果此 Broker 为从节点,则需要查找该 Broker 的主节点信息,并更新对应的 masterAddr 属性 + +```java +if (MixAll.MASTER_ID!= brokerId) { + String masterAddr = brokerData.getBrokerAddrs().get(MixAll.MASTER_ID); + if (masterAddr != null) { + BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.get(masterAddr); + if (brokerLiveInfo != null) { + result.setHaServerAddr(brokerLiveInfo.getHaServerAddr()); + result.setMasterAddr(masterAddr); + } + } +} +``` + +总结: + +NameServer 与 Broker 保持长连接,Broker 的状态信息存储在 BrokerLiveTable 中,NameServer 每收到一个心跳包,将更新 brokerLiveTable 中关于 broker 的状态信息以及路由表(topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable)。更新上述路由表使用了锁粒度较少的读写锁,允许多个消息发送者并发读操作,保证消息发送时的高并发,同一时刻 NameServer 只处理一个 Broker 心跳包,多个心跳包请求串行执行。 + +NameServer 如何剔除失效的 Broker? + +1、NameServer 每隔十秒注册一次 brokerLiveTable 状态表,如果 BrokerLive 的 lastUpdateTimestamp + +时间戳距当前时间超过 120 秒,则认为 Broker 失效,移除该 Broker,关闭与 broker 的连接,同时更新 topicQueueTable、brokerAddrTable、brokerLiveTable、filterServerTable。 + +2、如果 broker 在正常关闭的情况下,会发送 unRegisterBroker 指令。 + +不管是哪一种方式触发的路由删除,处理逻辑是一样的 + +第一步:申请写锁,移除 brokerLiveTable、filterServerTable 中 Broker 相关的信息 + +```java +this.lock.writeLock().lockInterruptibly(); +BrokerLiveInfo brokerLiveInfo = this.brokerLiveTable.remove(brokerAddr); +log.info("unregisterBroker, remove from brokerLiveTable {}, {}",brokerLiveInfo != null ? "OK" : "Failed",brokerAddr); +this.filterServerTable.remove(brokerAddr); +``` + +第二步:维护 brokerAddrTable,找到具体的 broker,将其从 brokerData 中移除,如果移除之后不再包含其他 broker,则在 brokerAddrtable 移除该 brokerName 对应的数据 + +``` +BrokerData brokerData = this.brokerAddrTable.get(brokerName); +if (null != brokerData) { + String addr = brokerData.getBrokerAddrs().remove(brokerId); + log.info("unregisterBroker, remove addr from brokerAddrTable {}, {}",addr != null ? "OK" : "Failed",brokerAddr); + + if (brokerData.getBrokerAddrs().isEmpty()) { + this.brokerAddrTable.remove(brokerName); + log.info("unregisterBroker, remove name from brokerAddrTable OK, {}",brokerName); + removeBrokerName = true; + } +} +``` + +第三步:根据 brokerName 从 clusterAddrTable 中找到 Broker 并将其中集群中移除,如果移除后集群中不包含任何 Broker,则将该集群从 clusterAddrTable 中移除 + +``` +if (removeBrokerName) { + Set nameSet = this.clusterAddrTable.get(clusterName); + if (nameSet != null) { + boolean removed = nameSet.remove(brokerName); + log.info("unregisterBroker, remove name from clusterAddrTable {}, {}",removed ? "OK" : "Failed",brokerName); + + if (nameSet.isEmpty()) { + this.clusterAddrTable.remove(clusterName); + log.info("unregisterBroker, remove cluster from clusterAddrTable {}",clusterName); + } + } + +} +``` + +第四步: 根据 brokerName,遍历所有主题的队列,如果队列中包含当前 broker 的队列,则移除,如果 topic 中包含待移除的 Broker 的队列,从路由表中删除该 topic + +``` +this.topicQueueTable.forEach((topic, queueDataMap) -> { + QueueData old = queueDataMap.remove(brokerName); + if (old != null) { + log.info("removeTopicByBrokerName, remove one broker's topic {} {}", topic, old); + } + + if (queueDataMap.size() == 0) { + noBrokerRegisterTopic.add(topic); + log.info("removeTopicByBrokerName, remove the topic all queue {}", topic); + } +}); +``` + +第五步: + +释放锁,完成路由删除 + +``` + finally { + this.lock.writeLock().unlock(); +} +``` diff --git a/docs/rocketmq/rocketmq-producer-start.md b/docs/rocketmq/rocketmq-producer-start.md new file mode 100644 index 00000000..804f9618 --- /dev/null +++ b/docs/rocketmq/rocketmq-producer-start.md @@ -0,0 +1,238 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ 生产者启动流程 + +入口: + +org.apache.rocketmq.client.producer.DefaultMQProducer#start + +```java +@Override + public void start() throws MQClientException { + this.setProducerGroup(withNamespace(this.producerGroup)); + this.defaultMQProducerImpl.start(); + if (null != traceDispatcher) { + try { + traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel()); + } catch (MQClientException e) { + log.warn("trace dispatcher start failed ", e); + } + } + } +``` + +第一步、检查 producerGroup + +```java +private void checkConfig() throws MQClientException { + Validators.checkGroup(this.defaultMQProducer.getProducerGroup()); + + if (null == this.defaultMQProducer.getProducerGroup()) { + throw new MQClientException("producerGroup is null", null); + } + + if (this.defaultMQProducer.getProducerGroup().equals(MixAll.DEFAULT_PRODUCER_GROUP)) { + throw new MQClientException("producerGroup can not equal " + MixAll.DEFAULT_PRODUCER_GROUP + ", please specify another one.",null); + } + } +``` + +第二步、设置 instanceName + +```java +public void changeInstanceNameToPID() { + if (this.instanceName.equals("DEFAULT")) { + this.instanceName = UtilAll.getPid() + "#" + System.nanoTime(); + } +} +``` + +第三步、创建 mqClientInstance,它是与 nameserver 和 broker 通信的中介 + +```java +public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) { + String clientId = clientConfig.buildMQClientId(); + MQClientInstance instance = this.factoryTable.get(clientId); + if (null == instance) { + instance = + new MQClientInstance(clientConfig.cloneClientConfig(), + this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook); + MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance); + if (prev != null) { + instance = prev; + log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId); + } else { + log.info("Created new MQClientInstance for clientId:[{}]", clientId); + } + } + + return instance; + } +``` + +第四步、将生产者加入 mqClientInstance 管理 + +```java +public synchronized boolean registerProducer(final String group, final DefaultMQProducerImpl producer) { + if (null == group || null == producer) { + return false; + } + + MQProducerInner prev = this.producerTable.putIfAbsent(group, producer); + if (prev != null) { + log.warn("the producer group[{}] exist already.", group); + return false; + } + + return true; +} +``` + +第五步、启动 MQClientInstance(有一些关于消费者的任务 会在消费者启动流程中讲解) + +1. 启动 netty 客户端 ,创建与 nameserver、broker 通信的 channel + +```java +public void start() { + this.defaultEventExecutorGroup = new DefaultEventExecutorGroup( + nettyClientConfig.getClientWorkerThreads(), + new ThreadFactory() { + + private AtomicInteger threadIndex = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet()); + } + }); + + Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_KEEPALIVE, false) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis()) + .handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + if (nettyClientConfig.isUseTLS()) { + if (null != sslContext) { + pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc())); + log.info("Prepend SSL handler"); + } else { + log.warn("Connections are insecure as SSLContext is null!"); + } + } + pipeline.addLast( + defaultEventExecutorGroup, + new NettyEncoder(), + new NettyDecoder(), + new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()), + new NettyConnectManageHandler(), + new NettyClientHandler()); + } + }); + if (nettyClientConfig.getClientSocketSndBufSize() > 0) { + log.info("client set SO_SNDBUF to {}", nettyClientConfig.getClientSocketSndBufSize()); + handler.option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize()); + } + if (nettyClientConfig.getClientSocketRcvBufSize() > 0) { + log.info("client set SO_RCVBUF to {}", nettyClientConfig.getClientSocketRcvBufSize()); + handler.option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize()); + } + if (nettyClientConfig.getWriteBufferLowWaterMark() > 0 && nettyClientConfig.getWriteBufferHighWaterMark() > 0) { + log.info("client set netty WRITE_BUFFER_WATER_MARK to {},{}",nettyClientConfig.getWriteBufferLowWaterMark(), nettyClientConfig.getWriteBufferHighWaterMark()); + handler.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( + nettyClientConfig.getWriteBufferLowWaterMark(), nettyClientConfig.getWriteBufferHighWaterMark())); + } + + this.timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + NettyRemotingClient.this.scanResponseTable(); + } catch (Throwable e) { + log.error("scanResponseTable exception", e); + } + } + }, 1000 * 3, 1000); + + if (this.channelEventListener != null) { + this.nettyEventExecutor.start(); + } +} +``` + +2. 启动一些周期性的任务: + +更新 nameserver 地址的任务: + +```java +if (null == this.clientConfig.getNamesrvAddr()) { + this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr(); + } catch (Exception e) { + log.error("ScheduledTask fetchNameServerAddr exception", e); + } + } + }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); +} +``` + +更新 topic 路由信息的任务: + +```java +this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + MQClientInstance.this.updateTopicRouteInfoFromNameServer(); + } catch (Exception e) { + log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e); + } + } +}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS); +``` + +更新 broker 的任务: + +```java +this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + + @Override + public void run() { + try { + MQClientInstance.this.cleanOfflineBroker(); + MQClientInstance.this.sendHeartbeatToAllBrokerWithLock(); + } catch (Exception e) { + log.error("ScheduledTask sendHeartbeatToAllBroker exception", e); + } + } +}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS); +``` + +启动拉取消息线程: + +`this.pullMessageService.start();` + +```java +public void run() { + log.info(this.getServiceName() + " service started"); + + while (!this.isStopped()) { + try { + PullRequest pullRequest = this.pullRequestQueue.take(); + this.pullMessage(pullRequest); + } catch (InterruptedException ignored) { + } catch (Exception e) { + log.error("Pull Message Service Run Method exception", e); + } + } + + log.info(this.getServiceName() + " service end"); +} +``` diff --git a/docs/rocketmq/rocketmq-pullmessage-processor.md b/docs/rocketmq/rocketmq-pullmessage-processor.md new file mode 100644 index 00000000..f6f2e006 --- /dev/null +++ b/docs/rocketmq/rocketmq-pullmessage-processor.md @@ -0,0 +1,116 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ broker 处理拉取消息请求流程 + +org.apache.rocketmq.broker.processor.PullMessageProcessor#processRequest(io.netty.channel.ChannelHandlerContext, org.apache.rocketmq.remoting.protocol.RemotingCommand) + +第 1 步、`校验broker是否可读` + +```java +if (!PermName.isReadable(this.brokerController.getBrokerConfig().getBrokerPermission())) { + response.setCode(ResponseCode.NO_PERMISSION); + response.setRemark(String.format("the broker[%s] pulling message is forbidden", this.brokerController.getBrokerConfig().getBrokerIP1())); + return response; +} +``` + +第 2 步、`根据消费组获取订阅信息` + +```java +SubscriptionGroupConfig subscriptionGroupConfig = + this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(requestHeader.getConsumerGroup()); +``` + +第 3 步、`校验是否允许消费` + +```java +if (!subscriptionGroupConfig.isConsumeEnable()) { + response.setCode(ResponseCode.NO_PERMISSION); + response.setRemark("subscription group no permission, " + requestHeader.getConsumerGroup()); + return response; +} +``` + +第 4 步、`根据主题获取对应的配置信息` + +```java +TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic()); +if (null == topicConfig) { + log.error("the topic {} not exist, consumer: {}", requestHeader.getTopic(), RemotingHelper.parseChannelRemoteAddr(channel)); + response.setCode(ResponseCode.TOPIC_NOT_EXIST); + response.setRemark(String.format("topic[%s] not exist, apply first please! %s", requestHeader.getTopic(), FAQUrl.suggestTodo(FAQUrl.APPLY_TOPIC_URL))); + return response; +} +``` + +第 5 步、`校验主题对应的队列` + +```java +if (requestHeader.getQueueId() < 0 || requestHeader.getQueueId() >= topicConfig.getReadQueueNums()) { + String errorInfo = String.format("queueId[%d] is illegal, topic:[%s] topicConfig.readQueueNums:[%d] consumer:[%s]", + requestHeader.getQueueId(), requestHeader.getTopic(), topicConfig.getReadQueueNums(), channel.remoteAddress()); + log.warn(errorInfo); + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark(errorInfo); + return response; +} +``` + +第 6 步、`如果配置了消息过滤表达式,根据表达式进行构建consumerFilterData,如果没有,则根据主题构建` + +```java +consumerFilterData = ConsumerFilterManager.build( + requestHeader.getTopic(), requestHeader.getConsumerGroup(), requestHeader.getSubscription(), + requestHeader.getExpressionType(), requestHeader.getSubVersion() + +consumerFilterData = this.brokerController.getConsumerFilterManager().get(requestHeader.getTopic(), + requestHeader.getConsumerGroup()); +``` + +第 7 步、`校验如果不是Tag过滤,是否开启了自定义属性过滤,如果没有开启,不允许操作 只有使用push推送模式的消费者才能用使用SQL92标准的sql语句,pull拉取模式的消费者是不支持这个功能的。` + +```java +if (!ExpressionType.isTagType(subscriptionData.getExpressionType()) + && !this.brokerController.getBrokerConfig().isEnablePropertyFilter()) { + response.setCode(ResponseCode.SYSTEM_ERROR); + response.setRemark("The broker does not support consumer to filter message by " + subscriptionData.getExpressionType()); + return response; +} +``` + +第 8 步、`根据是否支持重试过滤创建不同的MessageFilter` + +```java +if (this.brokerController.getBrokerConfig().isFilterSupportRetry()) { + messageFilter = new ExpressionForRetryMessageFilter(subscriptionData, consumerFilterData, + this.brokerController.getConsumerFilterManager()); +} else { + messageFilter = new ExpressionMessageFilter(subscriptionData, consumerFilterData, + this.brokerController.getConsumerFilterManager()); +} +``` + +第 9 步、`根据消费组、主题、队列、偏移量、最大拉取消息数量、消息过滤器查找信息` + +```java +final GetMessageResult getMessageResult = + this.brokerController.getMessageStore().getMessage(requestHeader.getConsumerGroup(), requestHeader.getTopic(), + requestHeader.getQueueId(), requestHeader.getQueueOffset(), requestHeader.getMaxMsgNums(), messageFilter); + +``` + +第 10 步、`消息为空 设置code为系统错误 返回response` + +```java +response.setCode(ResponseCode.SYSTEM_ERROR); +response.setRemark("store getMessage return null"); +``` + +第 11 步、`提交偏移量` + +```java +if (storeOffsetEnable) { + this.brokerController.getConsumerOffsetManager().commitOffset(RemotingHelper.parseChannelRemoteAddr(channel), + requestHeader.getConsumerGroup(), requestHeader.getTopic(), requestHeader.getQueueId(), requestHeader.getCommitOffset()); +} +``` diff --git a/docs/rocketmq/rocketmq-pullmessage.md b/docs/rocketmq/rocketmq-pullmessage.md new file mode 100644 index 00000000..55f7f5b8 --- /dev/null +++ b/docs/rocketmq/rocketmq-pullmessage.md @@ -0,0 +1,327 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ 消息拉取流程 + +之前在消费者启动流程中描述过 MQClientInstance 的启动流程,在启动过程中会启动 PullMessageService,它继承了`ServiceThread`,并且 ServiceThread 实现了 Runnable 接口,所以是单独启动了一个线程 + +`public class PullMessageService extends ServiceThread` + +`public abstract class ServiceThread implements Runnable` + +PullMessageService 的 run 方法如下: + +`protected volatile boolean stopped = false;` + +```java +public void run() { + log.info(this.getServiceName() + " service started"); + + while (!this.isStopped()) { + try { + PullRequest pullRequest = this.pullRequestQueue.take(); + this.pullMessage(pullRequest); + } catch (InterruptedException ignored) { + } catch (Exception e) { + log.error("Pull Message Service Run Method exception", e); + } + } + + log.info(this.getServiceName() + " service end"); +} +``` + +只要没有停止,线程一直会从 PullRequestQueue 中获取 PullRequest 消息拉取任务,如果队列为空,会一直阻塞,直到有 PullRequest 被放入队列中,如果拿到了 PullRequest 就会调用 pullMessage 方法拉取消息 + +添加 PullRequest 有两个方法,一个是延迟添加,另一个是立即添加 + +```java +public void executePullRequestLater(final PullRequest pullRequest, final long timeDelay) { + if (!isStopped()) { + this.scheduledExecutorService.schedule(new Runnable() { + @Override + public void run() { + PullMessageService.this.executePullRequestImmediately(pullRequest); + } + }, timeDelay, TimeUnit.MILLISECONDS); + } else { + log.warn("PullMessageServiceScheduledThread has shutdown"); + } +} + +public void executePullRequestImmediately(final PullRequest pullRequest) { + try { + this.pullRequestQueue.put(pullRequest); + } catch (InterruptedException e) { + log.error("executePullRequestImmediately pullRequestQueue.put", e); + } +} +``` + +org.apache.rocketmq.client.impl.consumer.PullMessageService#pullMessage + +拉取消息流程: + +根据消费组获取`MQConsumerInner`,根据推模式还是拉模式,强转为`DefaultMQPushConsumerImpl`还是`DefaultLitePullConsumerImpl` + +org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl#pullMessage + +`第1步:获取处理队列`,`如果队列被丢弃结束` + +```java +final ProcessQueue processQueue = pullRequest.getProcessQueue(); + +if (processQueue.isDropped()) { + log.info("the pull request[{}] is dropped.", pullRequest.toString()); + return; +} +``` + +第 2 步:`设置最后一次拉取时间戳` + +`pullRequest.getProcessQueue().setLastPullTimestamp(System.currentTimeMillis());` + +第 3 步:`确认消费者是启动的状态,如果不是启动的状态,将PullRequest延迟3s放入队列` + +```java +try { + this.makeSureStateOK(); +} catch (MQClientException e) { + log.warn("pullMessage exception, consumer state not ok", e); + this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + return; +} +``` + +第 4 步:`如果消费者停止了,将PullRequest延迟1s放入队列` + +```java +if (this.isPause()) { + log.warn("consumer was paused, execute pull request later. instanceName={}, group={}", this.defaultMQPushConsumer.getInstanceName(), this.defaultMQPushConsumer.getConsumerGroup()); + this.executePullRequestLater(pullRequest,PULL_TIME_DELAY_MILLS_WHEN_SUSPEND); + return; +} +``` + +第 5 步:`缓存的消息数量大于1000,将PullRequest延迟50ms放入队列,每触发1000次流控输出警告信息` + +```java +if (cachedMessageCount > this.defaultMQPushConsumer.getPullThresholdForQueue()) { + this.executePullRequestLater(pullRequest,PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); + if ((queueFlowControlTimes++ % 1000) == 0) { + log.warn("the cached message count exceeds the threshold {}, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}", + this.defaultMQPushConsumer.getPullThresholdForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes); + } + return; +} +``` + +第 6 步:`缓存的消息大小大于100M 将PullRequest延迟50ms放入队列,每触发1000次输出警告信息` + +```java +if (cachedMessageSizeInMiB > this.defaultMQPushConsumer.getPullThresholdSizeForQueue()) { + this.executePullRequestLater(pullRequest,PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); + if ((queueFlowControlTimes++ % 1000) == 0) { + log.warn("the cached message size exceeds the threshold {} MiB, so do flow control, minOffset={}, maxOffset={}, count={}, size={} MiB, pullRequest={}, flowControlTimes={}", + this.defaultMQPushConsumer.getPullThresholdSizeForQueue(), processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), cachedMessageCount, cachedMessageSizeInMiB, pullRequest, queueFlowControlTimes); + } + return; +} +``` + +第 7 步:`ProcessQueue中消息的最大偏移量与最小偏移量的差值不能大于2000,如果大于2000,触发流控,输出警告信息` + +```java +if (!this.consumeOrderly) { + if (processQueue.getMaxSpan() > this.defaultMQPushConsumer.getConsumeConcurrentlyMaxSpan()) { + this.executePullRequestLater(pullRequest,PULL_TIME_DELAY_MILLS_WHEN_FLOW_CONTROL); + if ((queueMaxSpanFlowControlTimes++ % 1000) == 0) { + log.warn("the queue's messages, span too long, so do flow control, minOffset={}, maxOffset={}, maxSpan={}, pullRequest={}, flowControlTimes={}", + processQueue.getMsgTreeMap().firstKey(), processQueue.getMsgTreeMap().lastKey(), processQueue.getMaxSpan(), + pullRequest, queueMaxSpanFlowControlTimes); + } + return; + } +} +``` + +第 8 步:`如果ProcessQueue被锁了,判断上一个PullRequest是否被锁,如果没有被锁通过RebalanceImpl计算拉取消息偏移量,如果计算异常,将请求延迟3s加入队列`,`如果下一次拉取消息 的偏移量大于计算出来的偏移量,说明要拉取的偏移量 大于消费偏移量,对 偏移量 进行修正,设置下一次拉取的偏移量为计算出来的偏移量` + +```java +if (processQueue.isLocked()) { + if (!pullRequest.isPreviouslyLocked()) { + long offset = -1L; + try { + offset = this.rebalanceImpl.computePullFromWhereWithException(pullRequest.getMessageQueue()); + } catch (Exception e) { + this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + log.error("Failed to compute pull offset, pullResult: {}", pullRequest, e); + return; + } + boolean brokerBusy = offset < pullRequest.getNextOffset(); + log.info("the first time to pull message, so fix offset from broker. pullRequest: {} NewOffset: {} brokerBusy: {}", + pullRequest, offset, brokerBusy); + if (brokerBusy) { + log.info("[NOTIFYME]the first time to pull message, but pull request offset larger than broker consume offset. pullRequest: {} NewOffset: {}", + pullRequest, offset); + } + + pullRequest.setPreviouslyLocked(true); + pullRequest.setNextOffset(offset); + } +} else { + this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + log.info("pull message later because not locked in broker, {}", pullRequest); + return; +} +``` + +第 9 步:`根据主题名称获取订阅信息,如果为空,将请求延迟3s放入队列` + +```java +final SubscriptionData subscriptionData = this.rebalanceImpl.getSubscriptionInner().get(pullRequest.getMessageQueue().getTopic()); +if (null == subscriptionData) { + this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + log.warn("find the consumer's subscription failed, {}", pullRequest); + return; +} +``` + +第 10 步:`创建PullCallback,为后面调用 拉取消息api做准备` + +```java +PullCallback pullCallback = new PullCallback() { + @Override + public void onSuccess(PullResult pullResult) { + if (pullResult != null) { + pullResult = DefaultMQPushConsumerImpl.this.pullAPIWrapper.processPullResult(pullRequest.getMessageQueue(), pullResult, + subscriptionData); + + switch (pullResult.getPullStatus()) { + caseFOUND: + long prevRequestOffset = pullRequest.getNextOffset(); + pullRequest.setNextOffset(pullResult.getNextBeginOffset()); + long pullRT = System.currentTimeMillis() - beginTimestamp; + DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullRT(pullRequest.getConsumerGroup(), + pullRequest.getMessageQueue().getTopic(), pullRT); + + long firstMsgOffset = Long.MAX_VALUE; + if (pullResult.getMsgFoundList() == null || pullResult.getMsgFoundList().isEmpty()) { + DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); + } else { + firstMsgOffset = pullResult.getMsgFoundList().get(0).getQueueOffset(); + + DefaultMQPushConsumerImpl.this.getConsumerStatsManager().incPullTPS(pullRequest.getConsumerGroup(), + pullRequest.getMessageQueue().getTopic(), pullResult.getMsgFoundList().size()); + + boolean dispatchToConsume = processQueue.putMessage(pullResult.getMsgFoundList()); + DefaultMQPushConsumerImpl.this.consumeMessageService.submitConsumeRequest( + pullResult.getMsgFoundList(), + processQueue, + pullRequest.getMessageQueue(), + dispatchToConsume); + + if (DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval() > 0) { + DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, + DefaultMQPushConsumerImpl.this.defaultMQPushConsumer.getPullInterval()); + } else { + DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); + } + } + + if (pullResult.getNextBeginOffset() < prevRequestOffset + || firstMsgOffset < prevRequestOffset) { + log.warn("[BUG] pull message result maybe data wrong, nextBeginOffset: {} firstMsgOffset: {} prevRequestOffset: {}", + pullResult.getNextBeginOffset(), + firstMsgOffset, + prevRequestOffset); + } + + break; + caseNO_NEW_MSG: + caseNO_MATCHED_MSG: + pullRequest.setNextOffset(pullResult.getNextBeginOffset()); + + DefaultMQPushConsumerImpl.this.correctTagsOffset(pullRequest); + + DefaultMQPushConsumerImpl.this.executePullRequestImmediately(pullRequest); + break; + caseOFFSET_ILLEGAL: + log.warn("the pull request offset illegal, {} {}", + pullRequest.toString(), pullResult.toString()); + pullRequest.setNextOffset(pullResult.getNextBeginOffset()); + + pullRequest.getProcessQueue().setDropped(true); + DefaultMQPushConsumerImpl.this.executeTaskLater(new Runnable() { + + @Override + public void run() { + try { + DefaultMQPushConsumerImpl.this.offsetStore.updateOffset(pullRequest.getMessageQueue(), + pullRequest.getNextOffset(), false); + + DefaultMQPushConsumerImpl.this.offsetStore.persist(pullRequest.getMessageQueue()); + + DefaultMQPushConsumerImpl.this.rebalanceImpl.removeProcessQueue(pullRequest.getMessageQueue()); + + log.warn("fix the pull request offset, {}", pullRequest); + } catch (Throwable e) { + log.error("executeTaskLater Exception", e); + } + } + }, 10000); + break; + default: + break; + } + } + } + + @Override + public void onException(Throwable e) { + if (!pullRequest.getMessageQueue().getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + log.warn("execute the pull request exception", e); + } + + DefaultMQPushConsumerImpl.this.executePullRequestLater(pullRequest, pullTimeDelayMillsWhenException); + } +}; +``` + +第 11 步:`设置系统标记` + +`FLAG_COMMIT_OFFSET: 消费进度 大于0` + +`FLAG_SUSPEND: 拉取消息时支持线程挂起` + +`FLAG_SUBSCRIPTION: 消息过滤机制表达式` + +`FLAG_CLASS_FILTER: 消息过滤机制是否为类过滤` + +```java +int sysFlag = PullSysFlag.buildSysFlag( + commitOffsetEnable, // commitOffset + true, // suspend + subExpression != null, // subscription + classFilter // class filter +); +``` + +第 12 步:`调用 broker 拉取消息` + +```java +// 每一个参数的含义如下 +this.pullAPIWrapper.pullKernelImpl( + pullRequest.getMessageQueue(), // 要拉取的消息队列 + subExpression, // 消息过滤表达式 + subscriptionData.getExpressionType(), // 过滤表达式类型 + subscriptionData.getSubVersion(), // 时间戳 + pullRequest.getNextOffset(), // 消息拉取的开始偏移量 + this.defaultMQPushConsumer.getPullBatchSize(), // 拉取消息的数量 默认32条 + sysFlag, // 系统标记 + commitOffsetValue, // 消费的偏移量 + BROKER_SUSPEND_MAX_TIME_MILLIS, // 允许broker挂起的时间 默认15s + CONSUMER_TIMEOUT_MILLIS_WHEN_SUSPEND, // 允许的超时时间 默认30s + CommunicationMode.ASYNC, // 默认为异步拉取 + pullCallback // 拉取消息之后的回调 +); +``` diff --git a/docs/rocketmq/rocketmq-send-message.md b/docs/rocketmq/rocketmq-send-message.md new file mode 100644 index 00000000..a97b890a --- /dev/null +++ b/docs/rocketmq/rocketmq-send-message.md @@ -0,0 +1,365 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ 消息发送流程 + +这里以同步发送为示例讲解: + +入口: + +org.apache.rocketmq.client.producer.DefaultMQProducer#send(org.apache.rocketmq.common.message.Message) + +消息发送 默认超时时间 3 秒 + +第一步:验证 + +主题的长度不能大于 127,消息的大小不能大于 4M + +```java +public static void checkMessage(Message msg, DefaultMQProducer defaultMQProducer) throws MQClientException { + if (null == msg) { + throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message is null"); + } + // topic + Validators.checkTopic(msg.getTopic()); + Validators.isNotAllowedSendTopic(msg.getTopic()); + + // body + if (null == msg.getBody()) { + throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body is null"); + } + + if (0 == msg.getBody().length) { + throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, "the message body length is zero"); + } + + if (msg.getBody().length > defaultMQProducer.getMaxMessageSize()) { + throw new MQClientException(ResponseCode.MESSAGE_ILLEGAL, + "the message body size over max value, MAX: " + defaultMQProducer.getMaxMessageSize()); + } +} +``` + +第二步:查找路由信息 + +如果缓存中存在路由信息,并且队列信息不为空直接返回路由信息,如果缓存不存在,根据当前主题从 NameServer 中获取 路由信息,如果路由信息没有找到,根据默认主题查询路由信息,如果没有找到抛出异常 + +```java +private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) { + TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic); + if (null == topicPublishInfo || !topicPublishInfo.ok()) { + this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo()); + this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic); + topicPublishInfo = this.topicPublishInfoTable.get(topic); + } + + if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) { + return topicPublishInfo; + } else { + this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer); + topicPublishInfo = this.topicPublishInfoTable.get(topic); + return topicPublishInfo; + } +} + +``` + +从 NameServer 查询路由信息方法: + +org.apache.rocketmq.client.impl.factory.MQClientInstance#updateTopicRouteInfoFromNameServer(java.lang.String, boolean, org.apache.rocketmq.client.producer.DefaultMQProducer) + +1、如果是默认的主题查询路由信息,返回成功,更新读队列和写队列的个数为默认的队列个数 + +```java +if (isDefault && defaultMQProducer != null) { + topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(), + clientConfig.getMqClientApiTimeout()); + if (topicRouteData != null) { + for (QueueData data : topicRouteData.getQueueDatas()) { + int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums()); + data.setReadQueueNums(queueNums); + data.setWriteQueueNums(queueNums); + } + } +} +``` + +2、返回路由信息之后,与本地缓存的路由信息比对,判断路由信息是否发生变化,如果发生变化更新 broker 地址缓存,更新`topicPublishInfoTable`,更新 topic 路由信息缓存`topicRouteTable` + +```java +if (changed) { + TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData(); + + for (BrokerData bd : topicRouteData.getBrokerDatas()) { + this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs()); + } + + // Update Pub info + if (!producerTable.isEmpty()) { + TopicPublishInfo publishInfo =topicRouteData2TopicPublishInfo(topic, topicRouteData); + publishInfo.setHaveTopicRouterInfo(true); + Iterator> it = this.producerTable.entrySet().iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + MQProducerInner impl = entry.getValue(); + if (impl != null) { + impl.updateTopicPublishInfo(topic, publishInfo); + } + } + } + log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData); + this.topicRouteTable.put(topic, cloneTopicRouteData); + return true; +} +``` + +第三步:选择消息 队列 + +设置消息发送失败重试次数 + +`int timesTotal = communicationMode == CommunicationMode.*SYNC* ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;` + +`MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);` + +首先判断是否启用故障延迟机制 ,默认不启用,第一次查询 lastBrokerName 为空,`sendWhichQueue`自增然后对队列个数取模获取队列,如果消息发送失败,下一次`sendWhichQueue`仍然自增然后对队列个数取模,可以规避掉上次失败的 broker + +```java +public MessageQueue selectOneMessageQueue(final String lastBrokerName) { + if (lastBrokerName == null) { + return selectOneMessageQueue(); + } else { + for (int i = 0; i < this.messageQueueList.size(); i++) { + int index = this.sendWhichQueue.incrementAndGet(); + int pos = Math.abs(index) % this.messageQueueList.size(); + if (pos < 0) + pos = 0; + MessageQueue mq = this.messageQueueList.get(pos); + if (!mq.getBrokerName().equals(lastBrokerName)) { + return mq; + } + } + return selectOneMessageQueue(); + } +} +``` + +如果启用故障延迟机制: + +轮询获取队列 ,如果可用直接返回 + +```java +for (int i = 0; i < tpInfo.getMessageQueueList().size(); i++) { + int pos = Math.abs(index++) % tpInfo.getMessageQueueList().size(); + if (pos < 0) + pos = 0; + MessageQueue mq = tpInfo.getMessageQueueList().get(pos); + if (latencyFaultTolerance.isAvailable(mq.getBrokerName())) + return mq; +} +``` + +判断是否可用逻辑:先从要规避的 broker 集合`faultItemTable`中获取该 broker 是否存在,如果存在判断是否可用,可用的标准是当前时间的时间戳大于上次该 broker 失败的时间 + 规避的时间,如果该 broker 在规避的 broker 集合中不存在,直接返回可用 + +```java +public boolean isAvailable(final String name) { + final FaultItem faultItem = this.faultItemTable.get(name); + if (faultItem != null) { + return faultItem.isAvailable(); + } + return true; +} +``` + +如果没有可用的 broker,尝试从 规避的 broker 集合中选择一个可用的 broker,如果选择的 broker 没有写队列,则从规避的 broker 列表中移除该 broker + +```java +final String notBestBroker = latencyFaultTolerance.pickOneAtLeast(); +int writeQueueNums = tpInfo.getQueueIdByBroker(notBestBroker); +if (writeQueueNums > 0) { + final MessageQueue mq = tpInfo.selectOneMessageQueue(); + if (notBestBroker != null) { + mq.setBrokerName(notBestBroker); + mq.setQueueId(tpInfo.getSendWhichQueue().incrementAndGet() % writeQueueNums); + } + return mq; +} else { + latencyFaultTolerance.remove(notBestBroker); +} +``` + +P.S. : + +要规避的 broker 集合在同步发送的时候不会 更新,在异步发送的时候会更新 + +```java +public void updateFaultItem(final String brokerName, final long currentLatency, boolean isolation) { + if (this.sendLatencyFaultEnable) { + long duration = computeNotAvailableDuration(isolation ? 30000 : currentLatency); + this.latencyFaultTolerance.updateFaultItem(brokerName, currentLatency, duration); + } +} +``` + +主要更新消息发送故障的延迟时间`currentLatency`和故障规避的 开始时间`startTimestamp` + +```java +public void updateFaultItem(final String name, final long currentLatency, final long notAvailableDuration) { + FaultItem old = this.faultItemTable.get(name); + if (null == old) { + final FaultItem faultItem = new FaultItem(name); + faultItem.setCurrentLatency(currentLatency); + faultItem.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration); + + old = this.faultItemTable.putIfAbsent(name, faultItem); + if (old != null) { + old.setCurrentLatency(currentLatency); + old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration); + } + } else { + old.setCurrentLatency(currentLatency); + old.setStartTimestamp(System.currentTimeMillis() + notAvailableDuration); + } +} +``` + +总结: + +不管开不开启故障延迟机制,都可以规避故障的 broker,只是开启故障延迟机制,会在一段时间内都不会访问到该 broker,而不开启只是下一次不会访问到该 broker + +第四步:消息发送 + +org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl#sendKernelImpl + +1、为消息分配全局唯一 id + +```java +if (!(msg instanceof MessageBatch)) { + MessageClientIDSetter.setUniqID(msg); +} +``` + +2、消息体大于 4k 启用压缩 + +```java +boolean msgBodyCompressed = false; +if (this.tryToCompressMessage(msg)) { + sysFlag |= MessageSysFlag.COMPRESSED_FLAG; + msgBodyCompressed = true; +} +``` + +3、如果是事务消息,设置消息类型为事务消息 + +```java +final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED); +if (Boolean.parseBoolean(tranMsg)) { + sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE; +} +``` + +4、校验是否超时 + +```java +long costTimeSync = System.currentTimeMillis() - beginStartTime; +if (timeout < costTimeSync) { + throw new RemotingTooMuchRequestException("sendKernelImpl call timeout"); +} +``` + +5、组装请求头 + +```java +SendMessageRequestHeader requestHeader = new SendMessageRequestHeader(); +requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup()); +requestHeader.setTopic(msg.getTopic()); +requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey()); +requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums()); +requestHeader.setQueueId(mq.getQueueId()); +requestHeader.setSysFlag(sysFlag); +requestHeader.setBornTimestamp(System.currentTimeMillis()); +requestHeader.setFlag(msg.getFlag()); +requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties())); +requestHeader.setReconsumeTimes(0); +requestHeader.setUnitMode(this.isUnitMode()); +requestHeader.setBatch(msg instanceof MessageBatch); +if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) { + String reconsumeTimes = MessageAccessor.getReconsumeTime(msg); + if (reconsumeTimes != null) { + requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes)); + MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME); + } + + String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg); + if (maxReconsumeTimes != null) { + requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes)); + MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES); + } +} +``` + +6、发送请求 + +```java +caseSYNC: + long costTimeSync = System.currentTimeMillis() - beginStartTime; + if (timeout < costTimeSync) { + throw new RemotingTooMuchRequestException("sendKernelImpl call timeout"); + } + sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage( + brokerAddr, + mq.getBrokerName(), + msg, + requestHeader, + timeout - costTimeSync, + communicationMode, + context, + this); + break; +``` + +第五步:处理响应结果 + +1、处理状态码 + +```java +switch (response.getCode()) { + case ResponseCode.FLUSH_DISK_TIMEOUT: { + sendStatus = SendStatus.FLUSH_DISK_TIMEOUT; + break; + } + case ResponseCode.FLUSH_SLAVE_TIMEOUT: { + sendStatus = SendStatus.FLUSH_SLAVE_TIMEOUT; + break; + } + case ResponseCode.SLAVE_NOT_AVAILABLE: { + sendStatus = SendStatus.SLAVE_NOT_AVAILABLE; + break; + } + case ResponseCode.SUCCESS: { + sendStatus = SendStatus.SEND_OK; + break; + } + default: { + throw new MQBrokerException(response.getCode(), response.getRemark(), addr); + } +} +``` + +2、构造 SendResult + +```java +SendResult sendResult = new SendResult(sendStatus, + uniqMsgId, + responseHeader.getMsgId(), messageQueue, responseHeader.getQueueOffset()); +sendResult.setTransactionId(responseHeader.getTransactionId()); +String regionId = response.getExtFields().get(MessageConst.PROPERTY_MSG_REGION); +String traceOn = response.getExtFields().get(MessageConst.PROPERTY_TRACE_SWITCH); +if (regionId == null || regionId.isEmpty()) { + regionId = MixAll.DEFAULT_TRACE_REGION_ID; +} +if (traceOn != null && traceOn.equals("false")) { + sendResult.setTraceOn(false); +} else { + sendResult.setTraceOn(true); +} +sendResult.setRegionId(regionId); +``` diff --git a/docs/rocketmq/rocketmq-send-store.md b/docs/rocketmq/rocketmq-send-store.md new file mode 100644 index 00000000..f8f870ee --- /dev/null +++ b/docs/rocketmq/rocketmq-send-store.md @@ -0,0 +1,193 @@ +该文所涉及的 RocketMQ 源码版本为 4.9.3。 + +# RocketMQ 消息发送存储流程 + +第一步:检查消息存储状态 + +org.apache.rocketmq.store.DefaultMessageStore#checkStoreStatus + +1、检查 broker 是否可用 + +```java +if (this.shutdown) { +log.warn("message store has shutdown, so putMessage is forbidden"); + return PutMessageStatus.SERVICE_NOT_AVAILABLE; +} +``` + +2、检查 broker 的角色 + +```java +if (BrokerRole.SLAVE== this.messageStoreConfig.getBrokerRole()) { + long value = this.printTimes.getAndIncrement(); + if ((value % 50000) == 0) { +log.warn("broke role is slave, so putMessage is forbidden"); + } + return PutMessageStatus.SERVICE_NOT_AVAILABLE; +} +``` + +3、检查 messageStore 是否可写 + +```java +if (!this.runningFlags.isWriteable()) { + long value = this.printTimes.getAndIncrement(); + if ((value % 50000) == 0) { +log.warn("the message store is not writable. It may be caused by one of the following reasons: " + + "the broker's disk is full, write to logic queue error, write to index file error, etc"); + } + return PutMessageStatus.SERVICE_NOT_AVAILABLE; +} else { + this.printTimes.set(0); +} +``` + +4、检查 pageCache + +```java +if (this.isOSPageCacheBusy()) { + return PutMessageStatus.OS_PAGECACHE_BUSY; +} +``` + +第二步:检查消息 + +org.apache.rocketmq.store.DefaultMessageStore#checkMessage + +1、校验主题的长度不能大于 127 + +```java +if (msg.getTopic().length() > Byte.MAX_VALUE) { +log.warn("putMessage message topic length too long " + msg.getTopic().length()); + return PutMessageStatus.MESSAGE_ILLEGAL; +} +``` + +2、校验属性的长度不能大于 32767 + +```java +if (msg.getPropertiesString() != null && msg.getPropertiesString().length() > Short.MAX_VALUE) { +log.warn("putMessage message properties length too long " + msg.getPropertiesString().length()); + return PutMessageStatus.MESSAGE_ILLEGAL; +} +``` + +第三步:获取当前可以写入的 CommitLog 文件 + +CommitLog 文件的存储目录为${ROCKET_HOME}/store/commitlog ,MappedFileQueue 对应此文件夹,MappedFile 对应文件夹下的文件 + +```java +msg.setStoreTimestamp(beginLockTimestamp); + +if (null == mappedFile || mappedFile.isFull()) { + mappedFile = this.mappedFileQueue.getLastMappedFile(0); // Mark: NewFile may be cause noise +} +if (null == mappedFile) { + log.error("create mapped file1 error, topic: " + msg.getTopic() + " clientAddr: " + msg.getBornHostString()); + return CompletableFuture.completedFuture(new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, null)); +} +``` + +如果是第一次写入或者最新偏移量所属文件已满,创建新的文件 + +```java +public MappedFile getLastMappedFile(final long startOffset, boolean needCreate) { + long createOffset = -1; + MappedFile mappedFileLast = getLastMappedFile(); + + if (mappedFileLast == null) { + createOffset = startOffset - (startOffset % this.mappedFileSize); + } + + if (mappedFileLast != null && mappedFileLast.isFull()) { + createOffset = mappedFileLast.getFileFromOffset() + this.mappedFileSize; + } + + if (createOffset != -1 && needCreate) { + return tryCreateMappedFile(createOffset); + } + + return mappedFileLast; +} +``` + +第四步:将消息写入到 MappedFile 中 + +```java +public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb, + PutMessageContext putMessageContext) { + assert messageExt != null; + assert cb != null; + + int currentPos = this.wrotePosition.get(); + + if (currentPos < this.fileSize) { + ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice(); + byteBuffer.position(currentPos); + AppendMessageResult result; + if (messageExt instanceof MessageExtBrokerInner) { + result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, + (MessageExtBrokerInner) messageExt, putMessageContext); + } else if (messageExt instanceof MessageExtBatch) { + result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, + (MessageExtBatch) messageExt, putMessageContext); + } else { + return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); + } + this.wrotePosition.addAndGet(result.getWroteBytes()); + this.storeTimestamp = result.getStoreTimestamp(); + return result; + } +log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize); + return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR); +} +``` + +org.apache.rocketmq.store.CommitLog.DefaultAppendMessageCallback#doAppend(long, java.nio.ByteBuffer, int, org.apache.rocketmq.store.MessageExtBrokerInner, org.apache.rocketmq.store.CommitLog.PutMessageContext) + +计算要写入的偏移量 + +`long wroteOffset = fileFromOffset + byteBuffer.position();` + +对事务消息做特殊处理: + +```java +final int tranType = MessageSysFlag.getTransactionValue(msgInner.getSysFlag()); +switch (tranType) { + // Prepared and Rollback message is not consumed, will not enter the + // consumer queue + case MessageSysFlag.TRANSACTION_PREPARED_TYPE: + case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: + queueOffset = 0L; + break; + case MessageSysFlag.TRANSACTION_NOT_TYPE: + case MessageSysFlag.TRANSACTION_COMMIT_TYPE: + default: + break; +} +``` + +构造 AppendMessageResult: + +```java +AppendMessageResult result = new AppendMessageResult(AppendMessageStatus.PUT_OK, wroteOffset, msgLen, msgIdSupplier, + msgInner.getStoreTimestamp(), queueOffset, CommitLog.this.defaultMessageStore.now() - beginTimeMills); +``` + +事务消息特殊处理: + +```java +switch (tranType) { + case MessageSysFlag.TRANSACTION_PREPARED_TYPE: + case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: + break; + case MessageSysFlag.TRANSACTION_NOT_TYPE: + case MessageSysFlag.TRANSACTION_COMMIT_TYPE: + // The next update ConsumeQueue information + CommitLog.this.topicQueueTable.put(key, ++queueOffset); + CommitLog.this.multiDispatch.updateMultiQueueOffset(msgInner); + break; + default: + break; +} +``` diff --git "a/images/ConcurrentProgramming/\347\272\277\347\250\213\346\261\240\346\265\201\347\250\213.png" "b/images/ConcurrentProgramming/\347\272\277\347\250\213\346\261\240\346\265\201\347\250\213.png" index b07b3b93..4df12cbc 100644 Binary files "a/images/ConcurrentProgramming/\347\272\277\347\250\213\346\261\240\346\265\201\347\250\213.png" and "b/images/ConcurrentProgramming/\347\272\277\347\250\213\346\261\240\346\265\201\347\250\213.png" differ diff --git "a/images/DesignPattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" "b/images/DesignPattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" index dc13b1ee..7290927d 100644 Binary files "a/images/DesignPattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" and "b/images/DesignPattern/\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" differ diff --git "a/images/DesignPattern/\347\255\226\347\225\245\346\250\241\345\274\217\347\261\273\345\233\276.png" "b/images/DesignPattern/\347\255\226\347\225\245\346\250\241\345\274\217\347\261\273\345\233\276.png" index 76d32266..4fe15b0b 100644 Binary files "a/images/DesignPattern/\347\255\226\347\225\245\346\250\241\345\274\217\347\261\273\345\233\276.png" and "b/images/DesignPattern/\347\255\226\347\225\245\346\250\241\345\274\217\347\261\273\345\233\276.png" differ diff --git "a/images/DesignPattern/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217\347\261\273\345\233\276.png" "b/images/DesignPattern/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217\347\261\273\345\233\276.png" index db9ee58b..82e4bdb8 100644 Binary files "a/images/DesignPattern/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217\347\261\273\345\233\276.png" and "b/images/DesignPattern/\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217\347\261\273\345\233\276.png" differ diff --git "a/images/DesignPattern/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" "b/images/DesignPattern/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" index 6166d7b4..3c9c53c7 100644 Binary files "a/images/DesignPattern/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" and "b/images/DesignPattern/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217\347\261\273\345\233\276.png" differ diff --git "a/images/DesignPattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.png" "b/images/DesignPattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.png" index 48404245..839d813f 100644 Binary files "a/images/DesignPattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.png" and "b/images/DesignPattern/\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.png" differ diff --git "a/images/Dubbo/Dubbo\345\267\245\344\275\234\345\216\237\347\220\206\345\233\276.png" "b/images/Dubbo/Dubbo\345\267\245\344\275\234\345\216\237\347\220\206\345\233\276.png" index 2ddea06e..051f5165 100644 Binary files "a/images/Dubbo/Dubbo\345\267\245\344\275\234\345\216\237\347\220\206\345\233\276.png" and "b/images/Dubbo/Dubbo\345\267\245\344\275\234\345\216\237\347\220\206\345\233\276.png" differ diff --git "a/images/Dubbo/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204\345\233\276.png" "b/images/Dubbo/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204\345\233\276.png" index 0fa017aa..9bcefc27 100644 Binary files "a/images/Dubbo/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204\345\233\276.png" and "b/images/Dubbo/Dubbo\346\225\264\344\275\223\346\236\266\346\236\204\345\233\276.png" differ diff --git "a/images/Dubbo/RegistryFactory\347\273\204\344\273\266\347\261\273\345\233\276.png" "b/images/Dubbo/RegistryFactory\347\273\204\344\273\266\347\261\273\345\233\276.png" index 1452a2e1..efae6c5a 100644 Binary files "a/images/Dubbo/RegistryFactory\347\273\204\344\273\266\347\261\273\345\233\276.png" and "b/images/Dubbo/RegistryFactory\347\273\204\344\273\266\347\261\273\345\233\276.png" differ diff --git "a/images/Dubbo/Registry\347\273\204\344\273\266\347\261\273\345\233\276.png" "b/images/Dubbo/Registry\347\273\204\344\273\266\347\261\273\345\233\276.png" index 3f18fca9..fc8f92c4 100644 Binary files "a/images/Dubbo/Registry\347\273\204\344\273\266\347\261\273\345\233\276.png" and "b/images/Dubbo/Registry\347\273\204\344\273\266\347\261\273\345\233\276.png" differ diff --git "a/images/Dubbo/SPI\347\273\204\344\273\266\347\233\256\345\275\225\347\273\223\346\236\204.png" "b/images/Dubbo/SPI\347\273\204\344\273\266\347\233\256\345\275\225\347\273\223\346\236\204.png" index 7a53c863..9681b8d3 100644 Binary files "a/images/Dubbo/SPI\347\273\204\344\273\266\347\233\256\345\275\225\347\273\223\346\236\204.png" and "b/images/Dubbo/SPI\347\273\204\344\273\266\347\233\256\345\275\225\347\273\223\346\236\204.png" differ diff --git "a/images/Dubbo/com.alibaba.dubbo.rpc.cluster\345\214\205\347\233\256\345\275\225.png" "b/images/Dubbo/com.alibaba.dubbo.rpc.cluster\345\214\205\347\233\256\345\275\225.png" index de733c1b..cd8a68a2 100644 Binary files "a/images/Dubbo/com.alibaba.dubbo.rpc.cluster\345\214\205\347\233\256\345\275\225.png" and "b/images/Dubbo/com.alibaba.dubbo.rpc.cluster\345\214\205\347\233\256\345\275\225.png" differ diff --git "a/images/Dubbo/dubbo-cluster\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204.png" "b/images/Dubbo/dubbo-cluster\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204.png" index 58294535..13c8fa7a 100644 Binary files "a/images/Dubbo/dubbo-cluster\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204.png" and "b/images/Dubbo/dubbo-cluster\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204.png" differ diff --git "a/images/Dubbo/dubbo-registry-zookeeper\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204\345\233\276.png" "b/images/Dubbo/dubbo-registry-zookeeper\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204\345\233\276.png" index 37aad04f..e1ff9499 100644 Binary files "a/images/Dubbo/dubbo-registry-zookeeper\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204\345\233\276.png" and "b/images/Dubbo/dubbo-registry-zookeeper\346\250\241\345\235\227\345\267\245\347\250\213\347\273\223\346\236\204\345\233\276.png" differ diff --git "a/images/Dubbo/dubbo-registry\346\250\241\345\235\227\347\273\223\346\236\204\345\233\276.png" "b/images/Dubbo/dubbo-registry\346\250\241\345\235\227\347\273\223\346\236\204\345\233\276.png" index bed15f02..0f709033 100644 Binary files "a/images/Dubbo/dubbo-registry\346\250\241\345\235\227\347\273\223\346\236\204\345\233\276.png" and "b/images/Dubbo/dubbo-registry\346\250\241\345\235\227\347\273\223\346\236\204\345\233\276.png" differ diff --git "a/images/Dubbo/dubbo-remoting-api\347\232\204\351\241\271\347\233\256\347\273\223\346\236\204.png" "b/images/Dubbo/dubbo-remoting-api\347\232\204\351\241\271\347\233\256\347\273\223\346\236\204.png" index 76044276..5e08fcc1 100644 Binary files "a/images/Dubbo/dubbo-remoting-api\347\232\204\351\241\271\347\233\256\347\273\223\346\236\204.png" and "b/images/Dubbo/dubbo-remoting-api\347\232\204\351\241\271\347\233\256\347\273\223\346\236\204.png" differ diff --git "a/images/Dubbo/dubbo-remoting\347\232\204\345\267\245\347\250\213\347\273\223\346\236\204.png" "b/images/Dubbo/dubbo-remoting\347\232\204\345\267\245\347\250\213\347\273\223\346\236\204.png" index b32f8892..6b972ed1 100644 Binary files "a/images/Dubbo/dubbo-remoting\347\232\204\345\267\245\347\250\213\347\273\223\346\236\204.png" and "b/images/Dubbo/dubbo-remoting\347\232\204\345\267\245\347\250\213\347\273\223\346\236\204.png" differ diff --git "a/images/Dubbo/dubbo\346\263\250\345\206\214\344\270\255\345\277\203\345\234\250zookeeper\344\270\255\347\232\204\347\273\223\346\236\204.png" "b/images/Dubbo/dubbo\346\263\250\345\206\214\344\270\255\345\277\203\345\234\250zookeeper\344\270\255\347\232\204\347\273\223\346\236\204.png" index 35f216c4..4c0eaa86 100644 Binary files "a/images/Dubbo/dubbo\346\263\250\345\206\214\344\270\255\345\277\203\345\234\250zookeeper\344\270\255\347\232\204\347\273\223\346\236\204.png" and "b/images/Dubbo/dubbo\346\263\250\345\206\214\344\270\255\345\277\203\345\234\250zookeeper\344\270\255\347\232\204\347\273\223\346\236\204.png" differ diff --git "a/images/Dubbo/dubbo\351\241\271\347\233\256\347\273\223\346\236\204.png" "b/images/Dubbo/dubbo\351\241\271\347\233\256\347\273\223\346\236\204.png" index b5d83d8f..5d774882 100644 Binary files "a/images/Dubbo/dubbo\351\241\271\347\233\256\347\273\223\346\236\204.png" and "b/images/Dubbo/dubbo\351\241\271\347\233\256\347\273\223\346\236\204.png" differ diff --git "a/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2251.png" "b/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2251.png" index 8f05b1f5..1c10e018 100644 Binary files "a/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2251.png" and "b/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2251.png" differ diff --git "a/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2252.png" "b/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2252.png" index d8eb6e85..e0946fab 100644 Binary files "a/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2252.png" and "b/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2252.png" differ diff --git "a/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2253.png" "b/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2253.png" index 9e4f2e38..01f6ce91 100644 Binary files "a/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2253.png" and "b/images/Dubbo/\344\270\200\350\207\264\346\200\247hash\347\256\227\346\263\2253.png" differ diff --git "a/images/JDK1.8/JUC\345\205\250\351\207\217UML\345\234\260\345\233\276.png" "b/images/JDK1.8/JUC\345\205\250\351\207\217UML\345\234\260\345\233\276.png" new file mode 100644 index 00000000..e15073d9 Binary files /dev/null and "b/images/JDK1.8/JUC\345\205\250\351\207\217UML\345\234\260\345\233\276.png" differ diff --git "a/images/JDK1.8/JUC\347\232\204locks\345\214\205.png" "b/images/JDK1.8/JUC\347\232\204locks\345\214\205.png" index 6b095c3b..0743a967 100644 Binary files "a/images/JDK1.8/JUC\347\232\204locks\345\214\205.png" and "b/images/JDK1.8/JUC\347\232\204locks\345\214\205.png" differ diff --git "a/images/JDK1.8/JUC\351\224\201\347\273\204\344\273\266\347\261\273\345\233\276.png" "b/images/JDK1.8/JUC\351\224\201\347\273\204\344\273\266\347\261\273\345\233\276.png" index 2a38b6f1..2e5a71cd 100644 Binary files "a/images/JDK1.8/JUC\351\224\201\347\273\204\344\273\266\347\261\273\345\233\276.png" and "b/images/JDK1.8/JUC\351\224\201\347\273\204\344\273\266\347\261\273\345\233\276.png" differ diff --git "a/images/JDK1.8/ThreadLocal\345\216\237\347\220\206.png" "b/images/JDK1.8/ThreadLocal\345\216\237\347\220\206.png" index b1fa2d17..f6f48a91 100644 Binary files "a/images/JDK1.8/ThreadLocal\345\216\237\347\220\206.png" and "b/images/JDK1.8/ThreadLocal\345\216\237\347\220\206.png" differ diff --git a/images/JDK1.8/ThreadStatusChange.png b/images/JDK1.8/ThreadStatusChange.png index cebfd681..9a476519 100644 Binary files a/images/JDK1.8/ThreadStatusChange.png and b/images/JDK1.8/ThreadStatusChange.png differ diff --git "a/images/JDK1.8/arrayList\345\210\240\351\231\244\345\205\203\347\264\240\347\232\204\350\277\207\347\250\213.png" "b/images/JDK1.8/arrayList\345\210\240\351\231\244\345\205\203\347\264\240\347\232\204\350\277\207\347\250\213.png" new file mode 100644 index 00000000..d7dc2563 Binary files /dev/null and "b/images/JDK1.8/arrayList\345\210\240\351\231\244\345\205\203\347\264\240\347\232\204\350\277\207\347\250\213.png" differ diff --git "a/images/JDK1.8/arraylist\347\232\204add\346\226\271\346\263\225.png" "b/images/JDK1.8/arraylist\347\232\204add\346\226\271\346\263\225.png" new file mode 100644 index 00000000..61472a7f Binary files /dev/null and "b/images/JDK1.8/arraylist\347\232\204add\346\226\271\346\263\225.png" differ diff --git "a/images/JDK1.8/\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266\347\261\273\345\233\276.png" "b/images/JDK1.8/\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266\347\261\273\345\233\276.png" index dc83ba5d..6fa03f8a 100644 Binary files "a/images/JDK1.8/\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266\347\261\273\345\233\276.png" and "b/images/JDK1.8/\347\272\277\347\250\213\346\261\240\347\273\204\344\273\266\347\261\273\345\233\276.png" differ diff --git "a/images/Netty/BIO\351\200\232\344\277\241\346\250\241\345\236\213.png" "b/images/Netty/BIO\351\200\232\344\277\241\346\250\241\345\236\213.png" index 1b5dd6c3..bd5d065c 100644 Binary files "a/images/Netty/BIO\351\200\232\344\277\241\346\250\241\345\236\213.png" and "b/images/Netty/BIO\351\200\232\344\277\241\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/Buffer\347\273\204\344\273\266\347\261\273\345\233\276.png" "b/images/Netty/Buffer\347\273\204\344\273\266\347\261\273\345\233\276.png" index ab12715e..3460767f 100644 Binary files "a/images/Netty/Buffer\347\273\204\344\273\266\347\261\273\345\233\276.png" and "b/images/Netty/Buffer\347\273\204\344\273\266\347\261\273\345\233\276.png" differ diff --git "a/images/Netty/ChannelHandler\347\273\204\344\273\266.png" "b/images/Netty/ChannelHandler\347\273\204\344\273\266.png" new file mode 100644 index 00000000..d2d303d3 Binary files /dev/null and "b/images/Netty/ChannelHandler\347\273\204\344\273\266.png" differ diff --git "a/images/Netty/ChannelPipeline\347\232\204\350\260\203\345\272\246\347\233\270\345\205\263\346\226\271\346\263\225.png" "b/images/Netty/ChannelPipeline\347\232\204\350\260\203\345\272\246\347\233\270\345\205\263\346\226\271\346\263\225.png" index 2b33429d..a69df723 100644 Binary files "a/images/Netty/ChannelPipeline\347\232\204\350\260\203\345\272\246\347\233\270\345\205\263\346\226\271\346\263\225.png" and "b/images/Netty/ChannelPipeline\347\232\204\350\260\203\345\272\246\347\233\270\345\205\263\346\226\271\346\263\225.png" differ diff --git "a/images/Netty/ChannelPipeline\350\264\243\344\273\273\351\223\276\344\272\213\344\273\266\345\244\204\347\220\206\350\277\207\347\250\213.png" "b/images/Netty/ChannelPipeline\350\264\243\344\273\273\351\223\276\344\272\213\344\273\266\345\244\204\347\220\206\350\277\207\347\250\213.png" index c6983763..8626e6c7 100644 Binary files "a/images/Netty/ChannelPipeline\350\264\243\344\273\273\351\223\276\344\272\213\344\273\266\345\244\204\347\220\206\350\277\207\347\250\213.png" and "b/images/Netty/ChannelPipeline\350\264\243\344\273\273\351\223\276\344\272\213\344\273\266\345\244\204\347\220\206\350\277\207\347\250\213.png" differ diff --git "a/images/Netty/Channel\347\273\204\344\273\266.png" "b/images/Netty/Channel\347\273\204\344\273\266.png" new file mode 100644 index 00000000..bb55cde0 Binary files /dev/null and "b/images/Netty/Channel\347\273\204\344\273\266.png" differ diff --git "a/images/Netty/Channel\347\273\204\344\273\266\347\261\273\345\233\276.png" "b/images/Netty/Channel\347\273\204\344\273\266\347\261\273\345\233\276.png" index 75ab37bb..502a31d9 100644 Binary files "a/images/Netty/Channel\347\273\204\344\273\266\347\261\273\345\233\276.png" and "b/images/Netty/Channel\347\273\204\344\273\266\347\261\273\345\233\276.png" differ diff --git "a/images/Netty/IO\345\244\215\347\224\250\346\250\241\345\236\213.png" "b/images/Netty/IO\345\244\215\347\224\250\346\250\241\345\236\213.png" index 3c70f03d..a1ae793c 100644 Binary files "a/images/Netty/IO\345\244\215\347\224\250\346\250\241\345\236\213.png" and "b/images/Netty/IO\345\244\215\347\224\250\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/NIO\345\256\242\346\210\267\347\253\257\345\272\217\345\210\227\345\233\276.png" "b/images/Netty/NIO\345\256\242\346\210\267\347\253\257\345\272\217\345\210\227\345\233\276.png" index 6b1572c5..115d2825 100644 Binary files "a/images/Netty/NIO\345\256\242\346\210\267\347\253\257\345\272\217\345\210\227\345\233\276.png" and "b/images/Netty/NIO\345\256\242\346\210\267\347\253\257\345\272\217\345\210\227\345\233\276.png" differ diff --git "a/images/Netty/NIO\346\234\215\345\212\241\347\253\257\345\272\217\345\210\227\345\233\276.png" "b/images/Netty/NIO\346\234\215\345\212\241\347\253\257\345\272\217\345\210\227\345\233\276.png" index cbe7c60d..31e7d47a 100644 Binary files "a/images/Netty/NIO\346\234\215\345\212\241\347\253\257\345\272\217\345\210\227\345\233\276.png" and "b/images/Netty/NIO\346\234\215\345\212\241\347\253\257\345\272\217\345\210\227\345\233\276.png" differ diff --git "a/images/Netty/Netty\344\270\262\350\241\214\345\214\226\350\256\276\350\256\241\345\267\245\344\275\234\345\216\237\347\220\206.png" "b/images/Netty/Netty\344\270\262\350\241\214\345\214\226\350\256\276\350\256\241\345\267\245\344\275\234\345\216\237\347\220\206.png" index 65fef473..2c085b2a 100644 Binary files "a/images/Netty/Netty\344\270\262\350\241\214\345\214\226\350\256\276\350\256\241\345\267\245\344\275\234\345\216\237\347\220\206.png" and "b/images/Netty/Netty\344\270\262\350\241\214\345\214\226\350\256\276\350\256\241\345\267\245\344\275\234\345\216\237\347\220\206.png" differ diff --git "a/images/Netty/Netty\346\234\215\345\212\241\347\253\257\345\210\233\345\273\272\346\227\266\345\272\217\345\233\276.png" "b/images/Netty/Netty\346\234\215\345\212\241\347\253\257\345\210\233\345\273\272\346\227\266\345\272\217\345\233\276.png" index 6f3b369c..a4e8df2c 100644 Binary files "a/images/Netty/Netty\346\234\215\345\212\241\347\253\257\345\210\233\345\273\272\346\227\266\345\272\217\345\233\276.png" and "b/images/Netty/Netty\346\234\215\345\212\241\347\253\257\345\210\233\345\273\272\346\227\266\345\272\217\345\233\276.png" differ diff --git "a/images/Netty/Netty\347\232\204Channel\347\273\204\344\273\266.png" "b/images/Netty/Netty\347\232\204Channel\347\273\204\344\273\266.png" new file mode 100644 index 00000000..b7477bda Binary files /dev/null and "b/images/Netty/Netty\347\232\204Channel\347\273\204\344\273\266.png" differ diff --git "a/images/Netty/Netty\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" "b/images/Netty/Netty\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" index 5cff61be..8a4ca0cb 100644 Binary files "a/images/Netty/Netty\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" and "b/images/Netty/Netty\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" differ diff --git "a/images/Netty/NioServerSocketChannel\347\232\204ChannelPipeline.png" "b/images/Netty/NioServerSocketChannel\347\232\204ChannelPipeline.png" index 8fd4cdc9..140ccbe7 100644 Binary files "a/images/Netty/NioServerSocketChannel\347\232\204ChannelPipeline.png" and "b/images/Netty/NioServerSocketChannel\347\232\204ChannelPipeline.png" differ diff --git "a/images/Netty/Selector\345\222\214SelectionKey\345\222\214Channel\345\205\263\347\263\273\345\233\276.png" "b/images/Netty/Selector\345\222\214SelectionKey\345\222\214Channel\345\205\263\347\263\273\345\233\276.png" new file mode 100644 index 00000000..a33f6771 Binary files /dev/null and "b/images/Netty/Selector\345\222\214SelectionKey\345\222\214Channel\345\205\263\347\263\273\345\233\276.png" differ diff --git "a/images/Netty/ServerBootstrap\347\232\204Handler\346\250\241\345\236\213.png" "b/images/Netty/ServerBootstrap\347\232\204Handler\346\250\241\345\236\213.png" index 16198cf9..cf9c9663 100644 Binary files "a/images/Netty/ServerBootstrap\347\232\204Handler\346\250\241\345\236\213.png" and "b/images/Netty/ServerBootstrap\347\232\204Handler\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/TCP\347\262\230\345\214\205\346\213\206\345\214\205\351\227\256\351\242\230.png" "b/images/Netty/TCP\347\262\230\345\214\205\346\213\206\345\214\205\351\227\256\351\242\230.png" index 805bbfde..3e5c3b38 100644 Binary files "a/images/Netty/TCP\347\262\230\345\214\205\346\213\206\345\214\205\351\227\256\351\242\230.png" and "b/images/Netty/TCP\347\262\230\345\214\205\346\213\206\345\214\205\351\227\256\351\242\230.png" differ diff --git a/images/Netty/image_1595751597062.png b/images/Netty/image_1595751597062.png new file mode 100644 index 00000000..009f0aae Binary files /dev/null and b/images/Netty/image_1595751597062.png differ diff --git a/images/Netty/image_1595752125587.png b/images/Netty/image_1595752125587.png new file mode 100644 index 00000000..c391c3f3 Binary files /dev/null and b/images/Netty/image_1595752125587.png differ diff --git a/images/Netty/image_1595756711656.png b/images/Netty/image_1595756711656.png new file mode 100644 index 00000000..1d18336e Binary files /dev/null and b/images/Netty/image_1595756711656.png differ diff --git a/images/Netty/image_1595756928493.png b/images/Netty/image_1595756928493.png new file mode 100644 index 00000000..ad327281 Binary files /dev/null and b/images/Netty/image_1595756928493.png differ diff --git a/images/Netty/image_1595757035360.png b/images/Netty/image_1595757035360.png new file mode 100644 index 00000000..1b20dc4d Binary files /dev/null and b/images/Netty/image_1595757035360.png differ diff --git a/images/Netty/image_1595757110003.png b/images/Netty/image_1595757110003.png new file mode 100644 index 00000000..08a77572 Binary files /dev/null and b/images/Netty/image_1595757110003.png differ diff --git a/images/Netty/image_1595757328715.png b/images/Netty/image_1595757328715.png new file mode 100644 index 00000000..2826d793 Binary files /dev/null and b/images/Netty/image_1595757328715.png differ diff --git a/images/Netty/image_1595758329809.png b/images/Netty/image_1595758329809.png new file mode 100644 index 00000000..afb03073 Binary files /dev/null and b/images/Netty/image_1595758329809.png differ diff --git "a/images/Netty/\344\274\252\345\274\202\346\255\245IO\351\200\232\344\277\241\346\250\241\345\236\213.png" "b/images/Netty/\344\274\252\345\274\202\346\255\245IO\351\200\232\344\277\241\346\250\241\345\236\213.png" index c510eff3..0e240f20 100644 Binary files "a/images/Netty/\344\274\252\345\274\202\346\255\245IO\351\200\232\344\277\241\346\250\241\345\236\213.png" and "b/images/Netty/\344\274\252\345\274\202\346\255\245IO\351\200\232\344\277\241\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/\344\277\241\345\217\267\351\251\261\345\212\250IO\346\250\241\345\236\213.png" "b/images/Netty/\344\277\241\345\217\267\351\251\261\345\212\250IO\346\250\241\345\236\213.png" index 58b61c19..fa15ac24 100644 Binary files "a/images/Netty/\344\277\241\345\217\267\351\251\261\345\212\250IO\346\250\241\345\236\213.png" and "b/images/Netty/\344\277\241\345\217\267\351\251\261\345\212\250IO\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/\345\237\272\344\272\216Netty\345\210\233\345\273\272\345\256\242\346\210\267\347\253\257\346\227\266\345\272\217\345\233\276.png" "b/images/Netty/\345\237\272\344\272\216Netty\345\210\233\345\273\272\345\256\242\346\210\267\347\253\257\346\227\266\345\272\217\345\233\276.png" index 6ab16a87..e26c3d4e 100644 Binary files "a/images/Netty/\345\237\272\344\272\216Netty\345\210\233\345\273\272\345\256\242\346\210\267\347\253\257\346\227\266\345\272\217\345\233\276.png" and "b/images/Netty/\345\237\272\344\272\216Netty\345\210\233\345\273\272\345\256\242\346\210\267\347\253\257\346\227\266\345\272\217\345\233\276.png" differ diff --git "a/images/Netty/\345\274\202\346\255\245IO\346\250\241\345\236\213.png" "b/images/Netty/\345\274\202\346\255\245IO\346\250\241\345\236\213.png" index 70739395..e84e722e 100644 Binary files "a/images/Netty/\345\274\202\346\255\245IO\346\250\241\345\236\213.png" and "b/images/Netty/\345\274\202\346\255\245IO\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/\346\225\260\346\215\256\345\234\250\345\256\242\346\210\267\347\253\257\345\217\212\346\234\215\345\212\241\345\231\250\344\271\213\351\227\264\347\232\204\346\225\264\344\275\223IO\346\265\201\347\250\213.png" "b/images/Netty/\346\225\260\346\215\256\345\234\250\345\256\242\346\210\267\347\253\257\345\217\212\346\234\215\345\212\241\345\231\250\344\271\213\351\227\264\347\232\204\346\225\264\344\275\223IO\346\265\201\347\250\213.png" index e3c89fe5..68ded2cb 100644 Binary files "a/images/Netty/\346\225\260\346\215\256\345\234\250\345\256\242\346\210\267\347\253\257\345\217\212\346\234\215\345\212\241\345\231\250\344\271\213\351\227\264\347\232\204\346\225\264\344\275\223IO\346\265\201\347\250\213.png" and "b/images/Netty/\346\225\260\346\215\256\345\234\250\345\256\242\346\210\267\347\253\257\345\217\212\346\234\215\345\212\241\345\231\250\344\271\213\351\227\264\347\232\204\346\225\264\344\275\223IO\346\265\201\347\250\213.png" differ diff --git "a/images/Netty/\351\230\273\345\241\236IO\346\250\241\345\236\213.png" "b/images/Netty/\351\230\273\345\241\236IO\346\250\241\345\236\213.png" index 6490bc03..d4eafcba 100644 Binary files "a/images/Netty/\351\230\273\345\241\236IO\346\250\241\345\236\213.png" and "b/images/Netty/\351\230\273\345\241\236IO\346\250\241\345\236\213.png" differ diff --git "a/images/Netty/\351\235\236\351\230\273\345\241\236IO\346\250\241\345\236\213.png" "b/images/Netty/\351\235\236\351\230\273\345\241\236IO\346\250\241\345\236\213.png" index bb6c8b06..5f4dbb04 100644 Binary files "a/images/Netty/\351\235\236\351\230\273\345\241\236IO\346\250\241\345\236\213.png" and "b/images/Netty/\351\235\236\351\230\273\345\241\236IO\346\250\241\345\236\213.png" differ diff --git a/images/SpringBoot/image-20200318080601725.png b/images/SpringBoot/image-20200318080601725.png index d33657ae..1894cc8c 100644 Binary files a/images/SpringBoot/image-20200318080601725.png and b/images/SpringBoot/image-20200318080601725.png differ diff --git a/images/SpringBoot/image-20200318080901881.png b/images/SpringBoot/image-20200318080901881.png index 2104fe14..7c91601c 100644 Binary files a/images/SpringBoot/image-20200318080901881.png and b/images/SpringBoot/image-20200318080901881.png differ diff --git a/images/SpringBoot/image-20200318081112670.png b/images/SpringBoot/image-20200318081112670.png index 393dd00b..4f72e8c0 100644 Binary files a/images/SpringBoot/image-20200318081112670.png and b/images/SpringBoot/image-20200318081112670.png differ diff --git a/images/SpringBoot/image-20200318081322781.png b/images/SpringBoot/image-20200318081322781.png index 63a899e5..d324a210 100644 Binary files a/images/SpringBoot/image-20200318081322781.png and b/images/SpringBoot/image-20200318081322781.png differ diff --git a/images/SpringBoot/image-20200318081352639.png b/images/SpringBoot/image-20200318081352639.png index 3eef6045..7b5b6cbe 100644 Binary files a/images/SpringBoot/image-20200318081352639.png and b/images/SpringBoot/image-20200318081352639.png differ diff --git a/images/SpringBoot/image-20200318081458019.png b/images/SpringBoot/image-20200318081458019.png index 0139f845..5a1033a0 100644 Binary files a/images/SpringBoot/image-20200318081458019.png and b/images/SpringBoot/image-20200318081458019.png differ diff --git a/images/SpringBoot/image-20200318085243888.png b/images/SpringBoot/image-20200318085243888.png index 79126411..b943077b 100644 Binary files a/images/SpringBoot/image-20200318085243888.png and b/images/SpringBoot/image-20200318085243888.png differ diff --git a/images/SpringBoot/image-20200318090128983.png b/images/SpringBoot/image-20200318090128983.png index a86605e2..32d76870 100644 Binary files a/images/SpringBoot/image-20200318090128983.png and b/images/SpringBoot/image-20200318090128983.png differ diff --git a/images/SpringBoot/image-20200318090312626.png b/images/SpringBoot/image-20200318090312626.png index aa444e78..cbd3a3e4 100644 Binary files a/images/SpringBoot/image-20200318090312626.png and b/images/SpringBoot/image-20200318090312626.png differ diff --git a/images/SpringBoot/image-20200318090935285.png b/images/SpringBoot/image-20200318090935285.png index aa0307cd..e9fd0d86 100644 Binary files a/images/SpringBoot/image-20200318090935285.png and b/images/SpringBoot/image-20200318090935285.png differ diff --git a/images/SpringBoot/image-20200318091558233.png b/images/SpringBoot/image-20200318091558233.png index 62329c66..2487a71f 100644 Binary files a/images/SpringBoot/image-20200318091558233.png and b/images/SpringBoot/image-20200318091558233.png differ diff --git a/images/SpringBoot/image-20200318092027020.png b/images/SpringBoot/image-20200318092027020.png index 12089bb6..b2f399f5 100644 Binary files a/images/SpringBoot/image-20200318092027020.png and b/images/SpringBoot/image-20200318092027020.png differ diff --git a/images/SpringBoot/image-20200319082131146.png b/images/SpringBoot/image-20200319082131146.png index 88c932c2..2140627f 100644 Binary files a/images/SpringBoot/image-20200319082131146.png and b/images/SpringBoot/image-20200319082131146.png differ diff --git a/images/SpringBoot/image-20200319082544653.png b/images/SpringBoot/image-20200319082544653.png index 9c4ca27d..46e0704b 100644 Binary files a/images/SpringBoot/image-20200319082544653.png and b/images/SpringBoot/image-20200319082544653.png differ diff --git a/images/SpringBoot/image-20200319083048849.png b/images/SpringBoot/image-20200319083048849.png index 5dba060f..071777bb 100644 Binary files a/images/SpringBoot/image-20200319083048849.png and b/images/SpringBoot/image-20200319083048849.png differ diff --git a/images/SpringBoot/image-20200319083140225.png b/images/SpringBoot/image-20200319083140225.png index ae87e415..a38667a2 100644 Binary files a/images/SpringBoot/image-20200319083140225.png and b/images/SpringBoot/image-20200319083140225.png differ diff --git a/images/SpringBoot/image-20200319083345067.png b/images/SpringBoot/image-20200319083345067.png index 9af94eba..696945b3 100644 Binary files a/images/SpringBoot/image-20200319083345067.png and b/images/SpringBoot/image-20200319083345067.png differ diff --git a/images/SpringBoot/image-20200319084141748.png b/images/SpringBoot/image-20200319084141748.png index cee97229..9be26350 100644 Binary files a/images/SpringBoot/image-20200319084141748.png and b/images/SpringBoot/image-20200319084141748.png differ diff --git a/images/SpringBoot/image-20200319084151997.png b/images/SpringBoot/image-20200319084151997.png index 1e2354be..0da7ef85 100644 Binary files a/images/SpringBoot/image-20200319084151997.png and b/images/SpringBoot/image-20200319084151997.png differ diff --git a/images/SpringBoot/image-20200319084357652.png b/images/SpringBoot/image-20200319084357652.png index e4595c4b..26e6c66d 100644 Binary files a/images/SpringBoot/image-20200319084357652.png and b/images/SpringBoot/image-20200319084357652.png differ diff --git a/images/SpringBoot/image-20200319084902957.png b/images/SpringBoot/image-20200319084902957.png index decba93e..01288c2c 100644 Binary files a/images/SpringBoot/image-20200319084902957.png and b/images/SpringBoot/image-20200319084902957.png differ diff --git a/images/SpringBoot/image-20200319085446640.png b/images/SpringBoot/image-20200319085446640.png index a9cd4b4f..99f3e1d6 100644 Binary files a/images/SpringBoot/image-20200319085446640.png and b/images/SpringBoot/image-20200319085446640.png differ diff --git a/images/SpringBoot/image-20200319090446231.png b/images/SpringBoot/image-20200319090446231.png index d2067523..003b6c79 100644 Binary files a/images/SpringBoot/image-20200319090446231.png and b/images/SpringBoot/image-20200319090446231.png differ diff --git a/images/SpringBoot/image-20200320150642022.png b/images/SpringBoot/image-20200320150642022.png new file mode 100644 index 00000000..8eb29912 Binary files /dev/null and b/images/SpringBoot/image-20200320150642022.png differ diff --git a/images/SpringBoot/image-20200320160423991.png b/images/SpringBoot/image-20200320160423991.png index 2979b07e..55790851 100644 Binary files a/images/SpringBoot/image-20200320160423991.png and b/images/SpringBoot/image-20200320160423991.png differ diff --git a/images/SpringBoot/image-20200320162835665.png b/images/SpringBoot/image-20200320162835665.png index 50fdbb05..531e1bb0 100644 Binary files a/images/SpringBoot/image-20200320162835665.png and b/images/SpringBoot/image-20200320162835665.png differ diff --git a/images/SpringBoot/image-20200320163001728.png b/images/SpringBoot/image-20200320163001728.png index ba1cbe76..36420daa 100644 Binary files a/images/SpringBoot/image-20200320163001728.png and b/images/SpringBoot/image-20200320163001728.png differ diff --git a/images/SpringBoot/image-20200320163806852.png b/images/SpringBoot/image-20200320163806852.png index 3e8e35bc..a79ce985 100644 Binary files a/images/SpringBoot/image-20200320163806852.png and b/images/SpringBoot/image-20200320163806852.png differ diff --git a/images/SpringBoot/image-20200320164145286.png b/images/SpringBoot/image-20200320164145286.png index fc98a742..3f4fab9a 100644 Binary files a/images/SpringBoot/image-20200320164145286.png and b/images/SpringBoot/image-20200320164145286.png differ diff --git a/images/SpringBoot/image-20200320171138431.png b/images/SpringBoot/image-20200320171138431.png index 94e2b407..386c7cba 100644 Binary files a/images/SpringBoot/image-20200320171138431.png and b/images/SpringBoot/image-20200320171138431.png differ diff --git a/images/SpringBoot/image-20200320171734270.png b/images/SpringBoot/image-20200320171734270.png index c213030f..921bd647 100644 Binary files a/images/SpringBoot/image-20200320171734270.png and b/images/SpringBoot/image-20200320171734270.png differ diff --git a/images/SpringBoot/image-20200323080611527.png b/images/SpringBoot/image-20200323080611527.png index 3d59fffe..892ec0c8 100644 Binary files a/images/SpringBoot/image-20200323080611527.png and b/images/SpringBoot/image-20200323080611527.png differ diff --git a/images/SpringBoot/image-20200323081009823.png b/images/SpringBoot/image-20200323081009823.png index 2192c03e..cc698537 100644 Binary files a/images/SpringBoot/image-20200323081009823.png and b/images/SpringBoot/image-20200323081009823.png differ diff --git a/images/SpringBoot/image-20200323081903145.png b/images/SpringBoot/image-20200323081903145.png index 55f0d0dc..e5824529 100644 Binary files a/images/SpringBoot/image-20200323081903145.png and b/images/SpringBoot/image-20200323081903145.png differ diff --git a/images/SpringBoot/image-20200323082553595.png b/images/SpringBoot/image-20200323082553595.png index 1ab60128..3dd2c884 100644 Binary files a/images/SpringBoot/image-20200323082553595.png and b/images/SpringBoot/image-20200323082553595.png differ diff --git a/images/SpringBoot/image-20200323083149737.png b/images/SpringBoot/image-20200323083149737.png index 3828ee7d..0de7208a 100644 Binary files a/images/SpringBoot/image-20200323083149737.png and b/images/SpringBoot/image-20200323083149737.png differ diff --git a/images/SpringBoot/image-20200323083247061.png b/images/SpringBoot/image-20200323083247061.png index 8534688f..3948f7c6 100644 Binary files a/images/SpringBoot/image-20200323083247061.png and b/images/SpringBoot/image-20200323083247061.png differ diff --git a/images/SpringBoot/image-20200323083656670.png b/images/SpringBoot/image-20200323083656670.png index 48d19fcd..09fcf305 100644 Binary files a/images/SpringBoot/image-20200323083656670.png and b/images/SpringBoot/image-20200323083656670.png differ diff --git a/images/SpringBoot/image-20200323084922159.png b/images/SpringBoot/image-20200323084922159.png index e417154a..f362eea6 100644 Binary files a/images/SpringBoot/image-20200323084922159.png and b/images/SpringBoot/image-20200323084922159.png differ diff --git a/images/SpringBoot/image-20200323094446756.png b/images/SpringBoot/image-20200323094446756.png index 8090af17..c8dad731 100644 Binary files a/images/SpringBoot/image-20200323094446756.png and b/images/SpringBoot/image-20200323094446756.png differ diff --git a/images/SpringBoot/image-20200323095626953.png b/images/SpringBoot/image-20200323095626953.png index fb5a6286..a4eec74a 100644 Binary files a/images/SpringBoot/image-20200323095626953.png and b/images/SpringBoot/image-20200323095626953.png differ diff --git a/images/SpringBoot/image-20200323104711545.png b/images/SpringBoot/image-20200323104711545.png index fad1fb00..76a9f9c6 100644 Binary files a/images/SpringBoot/image-20200323104711545.png and b/images/SpringBoot/image-20200323104711545.png differ diff --git a/images/SpringBoot/image-20200323104815305.png b/images/SpringBoot/image-20200323104815305.png index dca576bb..ec091255 100644 Binary files a/images/SpringBoot/image-20200323104815305.png and b/images/SpringBoot/image-20200323104815305.png differ diff --git a/images/SpringBoot/image-20200323105053757.png b/images/SpringBoot/image-20200323105053757.png index 15d5a58d..2bfaeac6 100644 Binary files a/images/SpringBoot/image-20200323105053757.png and b/images/SpringBoot/image-20200323105053757.png differ diff --git a/images/SpringBoot/image-20200323105155998.png b/images/SpringBoot/image-20200323105155998.png index a5bf0a95..e75e50ab 100644 Binary files a/images/SpringBoot/image-20200323105155998.png and b/images/SpringBoot/image-20200323105155998.png differ diff --git a/images/SpringBoot/image-20200323105830138.png b/images/SpringBoot/image-20200323105830138.png index c15768a8..8cd4ee4a 100644 Binary files a/images/SpringBoot/image-20200323105830138.png and b/images/SpringBoot/image-20200323105830138.png differ diff --git a/images/SpringBoot/image-20200323110603959.png b/images/SpringBoot/image-20200323110603959.png index 43fe5310..6675bc98 100644 Binary files a/images/SpringBoot/image-20200323110603959.png and b/images/SpringBoot/image-20200323110603959.png differ diff --git a/images/SpringBoot/image-20200323112945449.png b/images/SpringBoot/image-20200323112945449.png index 9ce8cf57..f28d599a 100644 Binary files a/images/SpringBoot/image-20200323112945449.png and b/images/SpringBoot/image-20200323112945449.png differ diff --git a/images/SpringBoot/image-20200323115401750.png b/images/SpringBoot/image-20200323115401750.png index 0acbe089..4d4464a5 100644 Binary files a/images/SpringBoot/image-20200323115401750.png and b/images/SpringBoot/image-20200323115401750.png differ diff --git a/images/SpringBoot/image-20200323115408877.png b/images/SpringBoot/image-20200323115408877.png index 0acbe089..4d4464a5 100644 Binary files a/images/SpringBoot/image-20200323115408877.png and b/images/SpringBoot/image-20200323115408877.png differ diff --git a/images/SpringBoot/image-20200323115701118.png b/images/SpringBoot/image-20200323115701118.png index b93ae15f..f9d67634 100644 Binary files a/images/SpringBoot/image-20200323115701118.png and b/images/SpringBoot/image-20200323115701118.png differ diff --git a/images/SpringBoot/image-20200323115711826.png b/images/SpringBoot/image-20200323115711826.png index 28e6a5d0..63750892 100644 Binary files a/images/SpringBoot/image-20200323115711826.png and b/images/SpringBoot/image-20200323115711826.png differ diff --git a/images/SpringBoot/image-20200323134135926.png b/images/SpringBoot/image-20200323134135926.png index ffbb30c2..a7096310 100644 Binary files a/images/SpringBoot/image-20200323134135926.png and b/images/SpringBoot/image-20200323134135926.png differ diff --git a/images/SpringBoot/image-20200323134325955.png b/images/SpringBoot/image-20200323134325955.png index b6f15764..8c9dd3e5 100644 Binary files a/images/SpringBoot/image-20200323134325955.png and b/images/SpringBoot/image-20200323134325955.png differ diff --git a/images/SpringBoot/image-20200323144523848.png b/images/SpringBoot/image-20200323144523848.png index 81b806fc..64313e22 100644 Binary files a/images/SpringBoot/image-20200323144523848.png and b/images/SpringBoot/image-20200323144523848.png differ diff --git a/images/SpringBoot/image-20200323151409473.png b/images/SpringBoot/image-20200323151409473.png index 4b114141..2a340fd9 100644 Binary files a/images/SpringBoot/image-20200323151409473.png and b/images/SpringBoot/image-20200323151409473.png differ diff --git a/images/SpringBoot/image-20200323154205484.png b/images/SpringBoot/image-20200323154205484.png index 1975ce99..1ee8804d 100644 Binary files a/images/SpringBoot/image-20200323154205484.png and b/images/SpringBoot/image-20200323154205484.png differ diff --git a/images/SpringBoot/image-20200323161442058.png b/images/SpringBoot/image-20200323161442058.png index f58262d3..8491bed5 100644 Binary files a/images/SpringBoot/image-20200323161442058.png and b/images/SpringBoot/image-20200323161442058.png differ diff --git a/images/SpringBoot/image-20200323161522570.png b/images/SpringBoot/image-20200323161522570.png index 6ecf3a5f..3eb98634 100644 Binary files a/images/SpringBoot/image-20200323161522570.png and b/images/SpringBoot/image-20200323161522570.png differ diff --git a/images/SpringBoot/image-20200324132053755.png b/images/SpringBoot/image-20200324132053755.png new file mode 100644 index 00000000..31088ec5 Binary files /dev/null and b/images/SpringBoot/image-20200324132053755.png differ diff --git a/images/SpringBoot/image-20200324133449749.png b/images/SpringBoot/image-20200324133449749.png new file mode 100644 index 00000000..2b458daf Binary files /dev/null and b/images/SpringBoot/image-20200324133449749.png differ diff --git a/images/SpringBoot/image-20200324133901498.png b/images/SpringBoot/image-20200324133901498.png new file mode 100644 index 00000000..80e9c262 Binary files /dev/null and b/images/SpringBoot/image-20200324133901498.png differ diff --git a/images/SpringBoot/image-20200324134813642.png b/images/SpringBoot/image-20200324134813642.png new file mode 100644 index 00000000..2e6afe10 Binary files /dev/null and b/images/SpringBoot/image-20200324134813642.png differ diff --git a/images/SpringBoot/image-20200324134837762.png b/images/SpringBoot/image-20200324134837762.png new file mode 100644 index 00000000..f483cfa7 Binary files /dev/null and b/images/SpringBoot/image-20200324134837762.png differ diff --git a/images/SpringBoot/image-20200325085209824.png b/images/SpringBoot/image-20200325085209824.png new file mode 100644 index 00000000..c45b35d6 Binary files /dev/null and b/images/SpringBoot/image-20200325085209824.png differ diff --git a/images/SpringBoot/image-20200325085708416.png b/images/SpringBoot/image-20200325085708416.png new file mode 100644 index 00000000..3333d5fa Binary files /dev/null and b/images/SpringBoot/image-20200325085708416.png differ diff --git a/images/SpringBoot/image-20200325090451465.png b/images/SpringBoot/image-20200325090451465.png new file mode 100644 index 00000000..52ea8471 Binary files /dev/null and b/images/SpringBoot/image-20200325090451465.png differ diff --git a/images/SpringBoot/image-20200325090738203.png b/images/SpringBoot/image-20200325090738203.png new file mode 100644 index 00000000..1521f697 Binary files /dev/null and b/images/SpringBoot/image-20200325090738203.png differ diff --git a/images/SpringBoot/image-20200325090946470.png b/images/SpringBoot/image-20200325090946470.png new file mode 100644 index 00000000..30084391 Binary files /dev/null and b/images/SpringBoot/image-20200325090946470.png differ diff --git a/images/SpringBoot/image-20200325091434110.png b/images/SpringBoot/image-20200325091434110.png new file mode 100644 index 00000000..fd89bd52 Binary files /dev/null and b/images/SpringBoot/image-20200325091434110.png differ diff --git a/images/SpringBoot/image-20200325093238025.png b/images/SpringBoot/image-20200325093238025.png new file mode 100644 index 00000000..cc3b9f33 Binary files /dev/null and b/images/SpringBoot/image-20200325093238025.png differ diff --git a/images/SpringBoot/image-20200325093319391.png b/images/SpringBoot/image-20200325093319391.png new file mode 100644 index 00000000..e4b4b0ff Binary files /dev/null and b/images/SpringBoot/image-20200325093319391.png differ diff --git a/images/SpringBoot/image-20200325094344562.png b/images/SpringBoot/image-20200325094344562.png new file mode 100644 index 00000000..18add806 Binary files /dev/null and b/images/SpringBoot/image-20200325094344562.png differ diff --git a/images/SpringBoot/image-20200325094421008.png b/images/SpringBoot/image-20200325094421008.png new file mode 100644 index 00000000..1c24d9b5 Binary files /dev/null and b/images/SpringBoot/image-20200325094421008.png differ diff --git a/images/SpringBoot/image-20200325095208523.png b/images/SpringBoot/image-20200325095208523.png new file mode 100644 index 00000000..9f1cf5f7 Binary files /dev/null and b/images/SpringBoot/image-20200325095208523.png differ diff --git a/images/SpringBoot/image-20200325100539794.png b/images/SpringBoot/image-20200325100539794.png new file mode 100644 index 00000000..523597a1 Binary files /dev/null and b/images/SpringBoot/image-20200325100539794.png differ diff --git a/images/SpringBoot/image-20200325101605773.png b/images/SpringBoot/image-20200325101605773.png new file mode 100644 index 00000000..6f2f2774 Binary files /dev/null and b/images/SpringBoot/image-20200325101605773.png differ diff --git a/images/SpringBoot/image-20200325102045939.png b/images/SpringBoot/image-20200325102045939.png new file mode 100644 index 00000000..9b891328 Binary files /dev/null and b/images/SpringBoot/image-20200325102045939.png differ diff --git a/images/SpringBoot/image-20200515150256581.png b/images/SpringBoot/image-20200515150256581.png new file mode 100644 index 00000000..d2d12171 Binary files /dev/null and b/images/SpringBoot/image-20200515150256581.png differ diff --git a/images/SpringBoot/image-20200515160103338.png b/images/SpringBoot/image-20200515160103338.png new file mode 100644 index 00000000..bd8d4a83 Binary files /dev/null and b/images/SpringBoot/image-20200515160103338.png differ diff --git a/images/SpringBoot/image-20200518111931477.png b/images/SpringBoot/image-20200518111931477.png new file mode 100644 index 00000000..07ce2e0b Binary files /dev/null and b/images/SpringBoot/image-20200518111931477.png differ diff --git a/images/SpringBoot/image-20200601170659521.png b/images/SpringBoot/image-20200601170659521.png new file mode 100644 index 00000000..0ca1d743 Binary files /dev/null and b/images/SpringBoot/image-20200601170659521.png differ diff --git a/images/SpringBoot/image-20200824085726621.png b/images/SpringBoot/image-20200824085726621.png new file mode 100644 index 00000000..a981e5fb Binary files /dev/null and b/images/SpringBoot/image-20200824085726621.png differ diff --git a/images/SpringBoot/image-20200825084844709.png b/images/SpringBoot/image-20200825084844709.png new file mode 100644 index 00000000..23cedbc5 Binary files /dev/null and b/images/SpringBoot/image-20200825084844709.png differ diff --git a/images/SpringBoot/image-20200825092343271.png b/images/SpringBoot/image-20200825092343271.png new file mode 100644 index 00000000..167586e1 Binary files /dev/null and b/images/SpringBoot/image-20200825092343271.png differ diff --git a/images/SpringBoot/image-20200825140750035.png b/images/SpringBoot/image-20200825140750035.png new file mode 100644 index 00000000..5ab1164e Binary files /dev/null and b/images/SpringBoot/image-20200825140750035.png differ diff --git a/images/SpringBoot/image-20200825141506531.png b/images/SpringBoot/image-20200825141506531.png new file mode 100644 index 00000000..bb046071 Binary files /dev/null and b/images/SpringBoot/image-20200825141506531.png differ diff --git a/images/SpringBoot/image-20200825142332485.png b/images/SpringBoot/image-20200825142332485.png new file mode 100644 index 00000000..04cb42e4 Binary files /dev/null and b/images/SpringBoot/image-20200825142332485.png differ diff --git a/images/SpringBoot/image-20200825142418115.png b/images/SpringBoot/image-20200825142418115.png new file mode 100644 index 00000000..3a67c3f3 Binary files /dev/null and b/images/SpringBoot/image-20200825142418115.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png new file mode 100644 index 00000000..c7d1a62c Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png new file mode 100644 index 00000000..3db35c12 Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png new file mode 100644 index 00000000..1c7055cc Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png new file mode 100644 index 00000000..0e8f16d8 Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png new file mode 100644 index 00000000..d1470d18 Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png new file mode 100644 index 00000000..9df10678 Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png differ diff --git a/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057.png b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057.png new file mode 100644 index 00000000..9df10678 Binary files /dev/null and b/images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057.png differ diff --git a/images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png b/images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png new file mode 100644 index 00000000..b9ac1439 Binary files /dev/null and b/images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png differ diff --git a/images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png b/images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png new file mode 100644 index 00000000..f9db08ed Binary files /dev/null and b/images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png differ diff --git a/images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png b/images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png new file mode 100644 index 00000000..3eab329e Binary files /dev/null and b/images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png differ diff --git a/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png b/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png new file mode 100644 index 00000000..4a983127 Binary files /dev/null and b/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png differ diff --git a/images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png b/images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png new file mode 100644 index 00000000..81305cb5 Binary files /dev/null and b/images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png differ diff --git a/images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png b/images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png new file mode 100644 index 00000000..0c8bbd5f Binary files /dev/null and b/images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png differ diff --git a/images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png b/images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png new file mode 100644 index 00000000..464fc7a0 Binary files /dev/null and b/images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png differ diff --git a/images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png b/images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png new file mode 100644 index 00000000..e6d0609f Binary files /dev/null and b/images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png differ diff --git a/images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png b/images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png new file mode 100644 index 00000000..92f5d54c Binary files /dev/null and b/images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png differ diff --git a/images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png b/images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png new file mode 100644 index 00000000..4bc142f7 Binary files /dev/null and b/images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png differ diff --git a/images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png b/images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png new file mode 100644 index 00000000..7347efa7 Binary files /dev/null and b/images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png differ diff --git a/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png b/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png new file mode 100644 index 00000000..29fa074e Binary files /dev/null and b/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png differ diff --git a/images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png b/images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png new file mode 100644 index 00000000..69c600a4 Binary files /dev/null and b/images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png differ diff --git a/images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png b/images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png new file mode 100644 index 00000000..9d3fa2a2 Binary files /dev/null and b/images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png differ diff --git a/images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png b/images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png new file mode 100644 index 00000000..f14e2dd7 Binary files /dev/null and b/images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png differ diff --git a/images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png b/images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png new file mode 100644 index 00000000..eff25747 Binary files /dev/null and b/images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png differ diff --git a/images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png b/images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png new file mode 100644 index 00000000..5b698b7e Binary files /dev/null and b/images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png differ diff --git a/images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png b/images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png new file mode 100644 index 00000000..552eda36 Binary files /dev/null and b/images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png differ diff --git a/images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png b/images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png new file mode 100644 index 00000000..7297595f Binary files /dev/null and b/images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png differ diff --git a/images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png b/images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png new file mode 100644 index 00000000..d59fe634 Binary files /dev/null and b/images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png differ diff --git a/images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png b/images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png new file mode 100644 index 00000000..523fc26b Binary files /dev/null and b/images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png differ diff --git a/images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png b/images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png new file mode 100644 index 00000000..43f7da8d Binary files /dev/null and b/images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png differ diff --git a/images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png b/images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png new file mode 100644 index 00000000..4406be57 Binary files /dev/null and b/images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png differ diff --git a/images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png b/images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png new file mode 100644 index 00000000..6d8106b4 Binary files /dev/null and b/images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png differ diff --git a/images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png b/images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png new file mode 100644 index 00000000..8cf41d88 Binary files /dev/null and b/images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png differ diff --git a/images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png b/images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png new file mode 100644 index 00000000..7347f81c Binary files /dev/null and b/images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png differ diff --git a/images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png b/images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png new file mode 100644 index 00000000..2646088d Binary files /dev/null and b/images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png differ diff --git a/images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png b/images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png new file mode 100644 index 00000000..21d184b4 Binary files /dev/null and b/images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png differ diff --git a/images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png b/images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png new file mode 100644 index 00000000..b1963854 Binary files /dev/null and b/images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png differ diff --git a/images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png b/images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png new file mode 100644 index 00000000..5be07350 Binary files /dev/null and b/images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png differ diff --git a/images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png b/images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png new file mode 100644 index 00000000..a682cafc Binary files /dev/null and b/images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png differ diff --git a/images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png b/images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png new file mode 100644 index 00000000..03b712f7 Binary files /dev/null and b/images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png differ diff --git a/images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png b/images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png new file mode 100644 index 00000000..ad089239 Binary files /dev/null and b/images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png differ diff --git a/images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png b/images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png new file mode 100644 index 00000000..6b86abbb Binary files /dev/null and b/images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png differ diff --git a/images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png b/images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png new file mode 100644 index 00000000..4deefe2f Binary files /dev/null and b/images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png differ diff --git a/images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png b/images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png new file mode 100644 index 00000000..eaa9e2bf Binary files /dev/null and b/images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png differ diff --git a/images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png b/images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png new file mode 100644 index 00000000..485f6331 Binary files /dev/null and b/images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png differ diff --git a/images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png b/images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png new file mode 100644 index 00000000..36fc45c1 Binary files /dev/null and b/images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png differ diff --git a/images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png b/images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png new file mode 100644 index 00000000..476122a6 Binary files /dev/null and b/images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png differ diff --git a/images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png b/images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png new file mode 100644 index 00000000..6b0903b2 Binary files /dev/null and b/images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png differ diff --git a/images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png b/images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png new file mode 100644 index 00000000..627e788f Binary files /dev/null and b/images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png differ diff --git a/images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png b/images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png new file mode 100644 index 00000000..8abf0adc Binary files /dev/null and b/images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png differ diff --git a/images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png b/images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png new file mode 100644 index 00000000..0364e925 Binary files /dev/null and b/images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png differ diff --git a/images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png b/images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png new file mode 100644 index 00000000..355fdaf3 Binary files /dev/null and b/images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png differ diff --git a/images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png b/images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png new file mode 100644 index 00000000..251ead03 Binary files /dev/null and b/images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png differ diff --git a/images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png b/images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png new file mode 100644 index 00000000..650c5991 Binary files /dev/null and b/images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png differ diff --git a/images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png b/images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png new file mode 100644 index 00000000..22a0b2ba Binary files /dev/null and b/images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png differ diff --git a/images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png b/images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png new file mode 100644 index 00000000..2f1b5c8e Binary files /dev/null and b/images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png differ diff --git a/images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png b/images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png new file mode 100644 index 00000000..ce3634e1 Binary files /dev/null and b/images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png differ diff --git a/images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png b/images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png new file mode 100644 index 00000000..d7fde052 Binary files /dev/null and b/images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png differ diff --git a/images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png b/images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png new file mode 100644 index 00000000..5a8c7679 Binary files /dev/null and b/images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png differ diff --git a/images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png b/images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png new file mode 100644 index 00000000..4d7a21e6 Binary files /dev/null and b/images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png differ diff --git a/images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png b/images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png new file mode 100644 index 00000000..52af7f14 Binary files /dev/null and b/images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png differ diff --git a/images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png b/images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png new file mode 100644 index 00000000..7f22ef85 Binary files /dev/null and b/images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png differ diff --git a/images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png b/images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png new file mode 100644 index 00000000..3f2018d8 Binary files /dev/null and b/images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png differ diff --git a/images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg b/images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg new file mode 100644 index 00000000..54666149 Binary files /dev/null and b/images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg differ diff --git a/images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png b/images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png new file mode 100644 index 00000000..aa0b6933 Binary files /dev/null and b/images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png differ diff --git a/images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png b/images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png new file mode 100644 index 00000000..c7bb4791 Binary files /dev/null and b/images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png differ diff --git a/images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png b/images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png new file mode 100644 index 00000000..3dc6f568 Binary files /dev/null and b/images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png differ diff --git a/images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png b/images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png new file mode 100644 index 00000000..3459fd83 Binary files /dev/null and b/images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png differ diff --git a/images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png b/images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png new file mode 100644 index 00000000..a1b0f0e2 Binary files /dev/null and b/images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png differ diff --git a/images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png b/images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png new file mode 100644 index 00000000..350a45cd Binary files /dev/null and b/images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png differ diff --git a/images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png b/images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png new file mode 100644 index 00000000..895b0147 Binary files /dev/null and b/images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png differ diff --git a/images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png b/images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png new file mode 100644 index 00000000..44127f36 Binary files /dev/null and b/images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png differ diff --git a/images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png b/images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png new file mode 100644 index 00000000..33c1efe7 Binary files /dev/null and b/images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png differ diff --git a/images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png b/images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png new file mode 100644 index 00000000..d8dda2b1 Binary files /dev/null and b/images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png differ diff --git a/images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png b/images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png new file mode 100644 index 00000000..1189996d Binary files /dev/null and b/images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png differ diff --git a/images/SpringSecurity/image-20210811091508157.png b/images/SpringSecurity/image-20210811091508157.png new file mode 100644 index 00000000..143f784b Binary files /dev/null and b/images/SpringSecurity/image-20210811091508157.png differ diff --git a/images/SpringSecurity/image-20210811091633434.png b/images/SpringSecurity/image-20210811091633434.png new file mode 100644 index 00000000..a0cf35fb Binary files /dev/null and b/images/SpringSecurity/image-20210811091633434.png differ diff --git a/images/SpringSecurity/image-20210811091659121.png b/images/SpringSecurity/image-20210811091659121.png new file mode 100644 index 00000000..fc8777e3 Binary files /dev/null and b/images/SpringSecurity/image-20210811091659121.png differ diff --git a/images/SpringSecurity/image-20210811091719470.png b/images/SpringSecurity/image-20210811091719470.png new file mode 100644 index 00000000..5196b801 Binary files /dev/null and b/images/SpringSecurity/image-20210811091719470.png differ diff --git a/images/SpringSecurity/image-20210811091755498.png b/images/SpringSecurity/image-20210811091755498.png new file mode 100644 index 00000000..fc1248cb Binary files /dev/null and b/images/SpringSecurity/image-20210811091755498.png differ diff --git a/images/SpringSecurity/image-20210811091815473.png b/images/SpringSecurity/image-20210811091815473.png new file mode 100644 index 00000000..2144f3ba Binary files /dev/null and b/images/SpringSecurity/image-20210811091815473.png differ diff --git a/images/SpringSecurity/image-20210811091833065.png b/images/SpringSecurity/image-20210811091833065.png new file mode 100644 index 00000000..f714be82 Binary files /dev/null and b/images/SpringSecurity/image-20210811091833065.png differ diff --git a/images/SpringSecurity/image-20210811092048784.png b/images/SpringSecurity/image-20210811092048784.png new file mode 100644 index 00000000..e97399dd Binary files /dev/null and b/images/SpringSecurity/image-20210811092048784.png differ diff --git a/images/SpringSecurity/img-2023-6-7-_2.png b/images/SpringSecurity/img-2023-6-7-_2.png new file mode 100644 index 00000000..76f24d31 Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7-_2.png differ diff --git a/images/SpringSecurity/img-2023-6-7-_3.png b/images/SpringSecurity/img-2023-6-7-_3.png new file mode 100644 index 00000000..5efaf193 Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7-_3.png differ diff --git a/images/SpringSecurity/img-2023-6-7_0.png b/images/SpringSecurity/img-2023-6-7_0.png new file mode 100644 index 00000000..9280c223 Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7_0.png differ diff --git a/images/SpringSecurity/img-2023-6-7_1.png b/images/SpringSecurity/img-2023-6-7_1.png new file mode 100644 index 00000000..1e335340 Binary files /dev/null and b/images/SpringSecurity/img-2023-6-7_1.png differ diff --git "a/images/Tomcat/Servlet\344\270\273\350\246\201\347\261\273\345\233\276.png" "b/images/Tomcat/Servlet\344\270\273\350\246\201\347\261\273\345\233\276.png" index ffd7c805..70eea66e 100644 Binary files "a/images/Tomcat/Servlet\344\270\273\350\246\201\347\261\273\345\233\276.png" and "b/images/Tomcat/Servlet\344\270\273\350\246\201\347\261\273\345\233\276.png" differ diff --git a/images/appreciateCode.JPG b/images/appreciateCode.JPG new file mode 100644 index 00000000..d335a1c2 Binary files /dev/null and b/images/appreciateCode.JPG differ diff --git a/images/favicon-16x16.png b/images/favicon-16x16.png new file mode 100644 index 00000000..77ea2817 Binary files /dev/null and b/images/favicon-16x16.png differ diff --git a/images/favicon-32x32.png b/images/favicon-32x32.png new file mode 100644 index 00000000..58f0f430 Binary files /dev/null and b/images/favicon-32x32.png differ diff --git a/images/github-doocs.png b/images/github-doocs.png index 23ab974e..b196da82 100644 Binary files a/images/github-doocs.png and b/images/github-doocs.png differ diff --git a/images/icon.png b/images/icon.png new file mode 100644 index 00000000..54f1537e Binary files /dev/null and b/images/icon.png differ diff --git a/images/mybatis/1575890354400.png b/images/mybatis/1575890354400.png index 370b7f58..85170ec2 100644 Binary files a/images/mybatis/1575890354400.png and b/images/mybatis/1575890354400.png differ diff --git a/images/mybatis/1575890475839.png b/images/mybatis/1575890475839.png index 376d957e..cd0b9878 100644 Binary files a/images/mybatis/1575890475839.png and b/images/mybatis/1575890475839.png differ diff --git a/images/mybatis/1575891988804.png b/images/mybatis/1575891988804.png new file mode 100644 index 00000000..650d965c Binary files /dev/null and b/images/mybatis/1575891988804.png differ diff --git a/images/mybatis/1575892046692.png b/images/mybatis/1575892046692.png new file mode 100644 index 00000000..a1d4bd9e Binary files /dev/null and b/images/mybatis/1575892046692.png differ diff --git a/images/mybatis/1575892167982.png b/images/mybatis/1575892167982.png new file mode 100644 index 00000000..ea4fb6e3 Binary files /dev/null and b/images/mybatis/1575892167982.png differ diff --git a/images/mybatis/1575892414120.png b/images/mybatis/1575892414120.png new file mode 100644 index 00000000..6241c756 Binary files /dev/null and b/images/mybatis/1575892414120.png differ diff --git a/images/mybatis/1575892511471.png b/images/mybatis/1575892511471.png new file mode 100644 index 00000000..4ebb1089 Binary files /dev/null and b/images/mybatis/1575892511471.png differ diff --git a/images/mybatis/1575892645405.png b/images/mybatis/1575892645405.png new file mode 100644 index 00000000..9036107e Binary files /dev/null and b/images/mybatis/1575892645405.png differ diff --git a/images/mybatis/1575892687076.png b/images/mybatis/1575892687076.png new file mode 100644 index 00000000..3756b24a Binary files /dev/null and b/images/mybatis/1575892687076.png differ diff --git a/images/mybatis/1575892763661.png b/images/mybatis/1575892763661.png new file mode 100644 index 00000000..7a2b5675 Binary files /dev/null and b/images/mybatis/1575892763661.png differ diff --git a/images/mybatis/1575894218362.png b/images/mybatis/1575894218362.png new file mode 100644 index 00000000..7336cf28 Binary files /dev/null and b/images/mybatis/1575894218362.png differ diff --git a/images/mybatis/1576027453035.png b/images/mybatis/1576027453035.png new file mode 100644 index 00000000..bfa72e76 Binary files /dev/null and b/images/mybatis/1576027453035.png differ diff --git a/images/mybatis/1576027589468.png b/images/mybatis/1576027589468.png new file mode 100644 index 00000000..b4a8a20b Binary files /dev/null and b/images/mybatis/1576027589468.png differ diff --git a/images/mybatis/1576027736912.png b/images/mybatis/1576027736912.png new file mode 100644 index 00000000..3c65d402 Binary files /dev/null and b/images/mybatis/1576027736912.png differ diff --git a/images/mybatis/1576028186530.png b/images/mybatis/1576028186530.png new file mode 100644 index 00000000..c9aeb0f6 Binary files /dev/null and b/images/mybatis/1576028186530.png differ diff --git a/images/mybatis/1576028554094.png b/images/mybatis/1576028554094.png new file mode 100644 index 00000000..f422dcbf Binary files /dev/null and b/images/mybatis/1576028554094.png differ diff --git a/images/mybatis/1576028709743.png b/images/mybatis/1576028709743.png new file mode 100644 index 00000000..d535661f Binary files /dev/null and b/images/mybatis/1576028709743.png differ diff --git a/images/mybatis/1576041628806.png b/images/mybatis/1576041628806.png new file mode 100644 index 00000000..9c92b76e Binary files /dev/null and b/images/mybatis/1576041628806.png differ diff --git a/images/mybatis/1576041889664.png b/images/mybatis/1576041889664.png new file mode 100644 index 00000000..026a6c88 Binary files /dev/null and b/images/mybatis/1576041889664.png differ diff --git a/images/mybatis/1576050247445.png b/images/mybatis/1576050247445.png new file mode 100644 index 00000000..92914d87 Binary files /dev/null and b/images/mybatis/1576050247445.png differ diff --git a/images/mybatis/1576050482190.png b/images/mybatis/1576050482190.png new file mode 100644 index 00000000..d184db68 Binary files /dev/null and b/images/mybatis/1576050482190.png differ diff --git a/images/mybatis/1576050580581.png b/images/mybatis/1576050580581.png new file mode 100644 index 00000000..76deab8f Binary files /dev/null and b/images/mybatis/1576050580581.png differ diff --git a/images/mybatis/1576050742205.png b/images/mybatis/1576050742205.png new file mode 100644 index 00000000..5702d23c Binary files /dev/null and b/images/mybatis/1576050742205.png differ diff --git a/images/mybatis/1576110788523.png b/images/mybatis/1576110788523.png new file mode 100644 index 00000000..bfaa7470 Binary files /dev/null and b/images/mybatis/1576110788523.png differ diff --git a/images/mybatis/1576111307305.png b/images/mybatis/1576111307305.png new file mode 100644 index 00000000..f0b2df5f Binary files /dev/null and b/images/mybatis/1576111307305.png differ diff --git a/images/mybatis/1576112853347.png b/images/mybatis/1576112853347.png new file mode 100644 index 00000000..8a1acc38 Binary files /dev/null and b/images/mybatis/1576112853347.png differ diff --git a/images/mybatis/1576112946984.png b/images/mybatis/1576112946984.png new file mode 100644 index 00000000..3ed1d80f Binary files /dev/null and b/images/mybatis/1576112946984.png differ diff --git a/images/mybatis/1576113272209.png b/images/mybatis/1576113272209.png new file mode 100644 index 00000000..bad80d7b Binary files /dev/null and b/images/mybatis/1576113272209.png differ diff --git a/images/mybatis/1576113287640.png b/images/mybatis/1576113287640.png new file mode 100644 index 00000000..24e18c88 Binary files /dev/null and b/images/mybatis/1576113287640.png differ diff --git a/images/mybatis/1576113345527.png b/images/mybatis/1576113345527.png new file mode 100644 index 00000000..7d41947d Binary files /dev/null and b/images/mybatis/1576113345527.png differ diff --git a/images/mybatis/1576113398394.png b/images/mybatis/1576113398394.png new file mode 100644 index 00000000..7fde3b8b Binary files /dev/null and b/images/mybatis/1576113398394.png differ diff --git a/images/mybatis/1576113864895.png b/images/mybatis/1576113864895.png new file mode 100644 index 00000000..6448c83b Binary files /dev/null and b/images/mybatis/1576113864895.png differ diff --git a/images/mybatis/1576114794663.png b/images/mybatis/1576114794663.png new file mode 100644 index 00000000..13a95bcf Binary files /dev/null and b/images/mybatis/1576114794663.png differ diff --git a/images/mybatis/1576114876295.png b/images/mybatis/1576114876295.png new file mode 100644 index 00000000..c0aa8253 Binary files /dev/null and b/images/mybatis/1576114876295.png differ diff --git a/images/mybatis/1576114996613.png b/images/mybatis/1576114996613.png new file mode 100644 index 00000000..987c34ef Binary files /dev/null and b/images/mybatis/1576114996613.png differ diff --git a/images/mybatis/1576117177349.png b/images/mybatis/1576117177349.png new file mode 100644 index 00000000..925aaeb8 Binary files /dev/null and b/images/mybatis/1576117177349.png differ diff --git a/images/mybatis/1576117195387.png b/images/mybatis/1576117195387.png new file mode 100644 index 00000000..0ea6e42e Binary files /dev/null and b/images/mybatis/1576117195387.png differ diff --git a/images/mybatis/1576117304942.png b/images/mybatis/1576117304942.png new file mode 100644 index 00000000..77a592fb Binary files /dev/null and b/images/mybatis/1576117304942.png differ diff --git a/images/mybatis/1576311527726.png b/images/mybatis/1576311527726.png new file mode 100644 index 00000000..0461177d Binary files /dev/null and b/images/mybatis/1576311527726.png differ diff --git a/images/mybatis/1576311999030.png b/images/mybatis/1576311999030.png new file mode 100644 index 00000000..65de96aa Binary files /dev/null and b/images/mybatis/1576311999030.png differ diff --git a/images/mybatis/1576312524112.png b/images/mybatis/1576312524112.png new file mode 100644 index 00000000..fd0cd471 Binary files /dev/null and b/images/mybatis/1576312524112.png differ diff --git a/images/mybatis/1576312612783.png b/images/mybatis/1576312612783.png new file mode 100644 index 00000000..145141b1 Binary files /dev/null and b/images/mybatis/1576312612783.png differ diff --git a/images/mybatis/1576312777050.png b/images/mybatis/1576312777050.png new file mode 100644 index 00000000..8aabd6a5 Binary files /dev/null and b/images/mybatis/1576312777050.png differ diff --git a/images/mybatis/1576313598939.png b/images/mybatis/1576313598939.png new file mode 100644 index 00000000..1c8593fe Binary files /dev/null and b/images/mybatis/1576313598939.png differ diff --git "a/images/mybatis/Cache\347\273\204\344\273\266.png" "b/images/mybatis/Cache\347\273\204\344\273\266.png" index aacd5534..a7e12bb5 100644 Binary files "a/images/mybatis/Cache\347\273\204\344\273\266.png" and "b/images/mybatis/Cache\347\273\204\344\273\266.png" differ diff --git "a/images/mybatis/DefaultSqlSession\346\226\271\346\263\225\350\260\203\347\224\250\346\240\210.png" "b/images/mybatis/DefaultSqlSession\346\226\271\346\263\225\350\260\203\347\224\250\346\240\210.png" index 26016cc0..d796c6fa 100644 Binary files "a/images/mybatis/DefaultSqlSession\346\226\271\346\263\225\350\260\203\347\224\250\346\240\210.png" and "b/images/mybatis/DefaultSqlSession\346\226\271\346\263\225\350\260\203\347\224\250\346\240\210.png" differ diff --git a/images/mybatis/image-20191217103309934.png b/images/mybatis/image-20191217103309934.png new file mode 100644 index 00000000..fbd95a96 Binary files /dev/null and b/images/mybatis/image-20191217103309934.png differ diff --git a/images/mybatis/image-20191217104008186.png b/images/mybatis/image-20191217104008186.png new file mode 100644 index 00000000..2950f3cc Binary files /dev/null and b/images/mybatis/image-20191217104008186.png differ diff --git a/images/mybatis/image-20191217104450495.png b/images/mybatis/image-20191217104450495.png new file mode 100644 index 00000000..c37668bc Binary files /dev/null and b/images/mybatis/image-20191217104450495.png differ diff --git a/images/mybatis/image-20191217143939247.png b/images/mybatis/image-20191217143939247.png new file mode 100644 index 00000000..b3ece9a5 Binary files /dev/null and b/images/mybatis/image-20191217143939247.png differ diff --git a/images/mybatis/image-20191217144453261.png b/images/mybatis/image-20191217144453261.png new file mode 100644 index 00000000..25d623c1 Binary files /dev/null and b/images/mybatis/image-20191217144453261.png differ diff --git a/images/mybatis/image-20191217144739434.png b/images/mybatis/image-20191217144739434.png new file mode 100644 index 00000000..65ac1c87 Binary files /dev/null and b/images/mybatis/image-20191217144739434.png differ diff --git a/images/mybatis/image-20191217145051629.png b/images/mybatis/image-20191217145051629.png new file mode 100644 index 00000000..c371042c Binary files /dev/null and b/images/mybatis/image-20191217145051629.png differ diff --git a/images/mybatis/image-20191217145607956.png b/images/mybatis/image-20191217145607956.png new file mode 100644 index 00000000..aefd99a4 Binary files /dev/null and b/images/mybatis/image-20191217145607956.png differ diff --git a/images/mybatis/image-20191217183853550.png b/images/mybatis/image-20191217183853550.png new file mode 100644 index 00000000..9b0b5b97 Binary files /dev/null and b/images/mybatis/image-20191217183853550.png differ diff --git a/images/mybatis/image-20191218082628696.png b/images/mybatis/image-20191218082628696.png new file mode 100644 index 00000000..751589b2 Binary files /dev/null and b/images/mybatis/image-20191218082628696.png differ diff --git a/images/mybatis/image-20191218191512184.png b/images/mybatis/image-20191218191512184.png index 7a61c08d..2bee090a 100644 Binary files a/images/mybatis/image-20191218191512184.png and b/images/mybatis/image-20191218191512184.png differ diff --git a/images/mybatis/image-20191218191550550.png b/images/mybatis/image-20191218191550550.png index 06d87b73..7f5d8af3 100644 Binary files a/images/mybatis/image-20191218191550550.png and b/images/mybatis/image-20191218191550550.png differ diff --git a/images/mybatis/image-20191219083223084.png b/images/mybatis/image-20191219083223084.png new file mode 100644 index 00000000..a4785bcc Binary files /dev/null and b/images/mybatis/image-20191219083223084.png differ diff --git a/images/mybatis/image-20191219083344439.png b/images/mybatis/image-20191219083344439.png index dec81ad2..2b90cd12 100644 Binary files a/images/mybatis/image-20191219083344439.png and b/images/mybatis/image-20191219083344439.png differ diff --git a/images/mybatis/image-20191219083354873.png b/images/mybatis/image-20191219083354873.png index b29f7478..e5f734c9 100644 Binary files a/images/mybatis/image-20191219083354873.png and b/images/mybatis/image-20191219083354873.png differ diff --git a/images/mybatis/image-20191219084455292.png b/images/mybatis/image-20191219084455292.png index dcb5bcea..b710b99d 100644 Binary files a/images/mybatis/image-20191219084455292.png and b/images/mybatis/image-20191219084455292.png differ diff --git a/images/mybatis/image-20191219084943102.png b/images/mybatis/image-20191219084943102.png index a0c56830..1b4750a2 100644 Binary files a/images/mybatis/image-20191219084943102.png and b/images/mybatis/image-20191219084943102.png differ diff --git a/images/mybatis/image-20191219085131167.png b/images/mybatis/image-20191219085131167.png index 9a6714c4..7ba61555 100644 Binary files a/images/mybatis/image-20191219085131167.png and b/images/mybatis/image-20191219085131167.png differ diff --git a/images/mybatis/image-20191219092442456.png b/images/mybatis/image-20191219092442456.png index 84593878..178c91c0 100644 Binary files a/images/mybatis/image-20191219092442456.png and b/images/mybatis/image-20191219092442456.png differ diff --git a/images/mybatis/image-20191219093043035.png b/images/mybatis/image-20191219093043035.png index dc340aa8..9fae110a 100644 Binary files a/images/mybatis/image-20191219093043035.png and b/images/mybatis/image-20191219093043035.png differ diff --git a/images/mybatis/image-20191219100446796.png b/images/mybatis/image-20191219100446796.png index 8e78f367..f7a9baf6 100644 Binary files a/images/mybatis/image-20191219100446796.png and b/images/mybatis/image-20191219100446796.png differ diff --git a/images/mybatis/image-20191219151245509.png b/images/mybatis/image-20191219151245509.png new file mode 100644 index 00000000..d0624f69 Binary files /dev/null and b/images/mybatis/image-20191219151245509.png differ diff --git a/images/mybatis/image-20191219151247240.png b/images/mybatis/image-20191219151247240.png index ebdcc674..d0624f69 100644 Binary files a/images/mybatis/image-20191219151247240.png and b/images/mybatis/image-20191219151247240.png differ diff --git a/images/mybatis/image-20191219151408597.png b/images/mybatis/image-20191219151408597.png index e2b44991..df0b42eb 100644 Binary files a/images/mybatis/image-20191219151408597.png and b/images/mybatis/image-20191219151408597.png differ diff --git a/images/mybatis/image-20191219152254274.png b/images/mybatis/image-20191219152254274.png index a97f92ea..f2868a0e 100644 Binary files a/images/mybatis/image-20191219152254274.png and b/images/mybatis/image-20191219152254274.png differ diff --git a/images/mybatis/image-20191219152502960.png b/images/mybatis/image-20191219152502960.png index 0377bed8..8ba3273d 100644 Binary files a/images/mybatis/image-20191219152502960.png and b/images/mybatis/image-20191219152502960.png differ diff --git a/images/mybatis/image-20191219152655746.png b/images/mybatis/image-20191219152655746.png index 99a197d0..8021de5f 100644 Binary files a/images/mybatis/image-20191219152655746.png and b/images/mybatis/image-20191219152655746.png differ diff --git a/images/mybatis/image-20191219153341466.png b/images/mybatis/image-20191219153341466.png index 56e94696..cccbc82e 100644 Binary files a/images/mybatis/image-20191219153341466.png and b/images/mybatis/image-20191219153341466.png differ diff --git a/images/mybatis/image-20191219153553127.png b/images/mybatis/image-20191219153553127.png index 459bedb4..59fa934e 100644 Binary files a/images/mybatis/image-20191219153553127.png and b/images/mybatis/image-20191219153553127.png differ diff --git a/images/mybatis/image-20191219155129772.png b/images/mybatis/image-20191219155129772.png index 664c16eb..7d7f8e47 100644 Binary files a/images/mybatis/image-20191219155129772.png and b/images/mybatis/image-20191219155129772.png differ diff --git a/images/mybatis/image-20191219160832704.png b/images/mybatis/image-20191219160832704.png index f8186f0d..770b79c0 100644 Binary files a/images/mybatis/image-20191219160832704.png and b/images/mybatis/image-20191219160832704.png differ diff --git a/images/mybatis/image-20191219160908212.png b/images/mybatis/image-20191219160908212.png index caf887d9..bf715b8e 100644 Binary files a/images/mybatis/image-20191219160908212.png and b/images/mybatis/image-20191219160908212.png differ diff --git a/images/mybatis/image-20191219161555793.png b/images/mybatis/image-20191219161555793.png index 5cbe5de2..b332b44a 100644 Binary files a/images/mybatis/image-20191219161555793.png and b/images/mybatis/image-20191219161555793.png differ diff --git a/images/mybatis/image-20191219162258040.png b/images/mybatis/image-20191219162258040.png index b586e454..078eac5b 100644 Binary files a/images/mybatis/image-20191219162258040.png and b/images/mybatis/image-20191219162258040.png differ diff --git a/images/mybatis/image-20191219162402291.png b/images/mybatis/image-20191219162402291.png new file mode 100644 index 00000000..f0735363 Binary files /dev/null and b/images/mybatis/image-20191219162402291.png differ diff --git a/images/mybatis/image-20191219162506920.png b/images/mybatis/image-20191219162506920.png index 2d28d2d0..aaca4ba5 100644 Binary files a/images/mybatis/image-20191219162506920.png and b/images/mybatis/image-20191219162506920.png differ diff --git a/images/mybatis/image-20191219163628214.png b/images/mybatis/image-20191219163628214.png index fa5053e1..8430bd40 100644 Binary files a/images/mybatis/image-20191219163628214.png and b/images/mybatis/image-20191219163628214.png differ diff --git a/images/mybatis/image-20191219163640968.png b/images/mybatis/image-20191219163640968.png index c8ae6594..cd42acae 100644 Binary files a/images/mybatis/image-20191219163640968.png and b/images/mybatis/image-20191219163640968.png differ diff --git a/images/mybatis/image-20191219163957488.png b/images/mybatis/image-20191219163957488.png index f9812308..e4fcd4ee 100644 Binary files a/images/mybatis/image-20191219163957488.png and b/images/mybatis/image-20191219163957488.png differ diff --git a/images/mybatis/image-20191223081023730.png b/images/mybatis/image-20191223081023730.png index 438447fd..1a9b1a51 100644 Binary files a/images/mybatis/image-20191223081023730.png and b/images/mybatis/image-20191223081023730.png differ diff --git a/images/mybatis/image-20191223083610214.png b/images/mybatis/image-20191223083610214.png index ddf1b176..62416e4f 100644 Binary files a/images/mybatis/image-20191223083610214.png and b/images/mybatis/image-20191223083610214.png differ diff --git a/images/mybatis/image-20191223083732972.png b/images/mybatis/image-20191223083732972.png index d9138cfd..6ca8b75c 100644 Binary files a/images/mybatis/image-20191223083732972.png and b/images/mybatis/image-20191223083732972.png differ diff --git a/images/mybatis/image-20191223100956713.png b/images/mybatis/image-20191223100956713.png index 1582cca6..8c44ce67 100644 Binary files a/images/mybatis/image-20191223100956713.png and b/images/mybatis/image-20191223100956713.png differ diff --git "a/images/mybatis/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240\346\265\201\347\250\213\345\233\276.png" "b/images/mybatis/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240\346\265\201\347\250\213\345\233\276.png" index 2ee2b713..2372efc6 100644 Binary files "a/images/mybatis/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240\346\265\201\347\250\213\345\233\276.png" and "b/images/mybatis/\346\225\260\346\215\256\345\272\223\350\277\236\346\216\245\346\261\240\346\265\201\347\250\213\345\233\276.png" differ diff --git "a/images/mybatis\350\277\236\346\216\245\346\261\240\350\216\267\345\217\226\350\277\236\346\216\245\351\200\273\350\276\221\345\233\276.png" "b/images/mybatis\350\277\236\346\216\245\346\261\240\350\216\267\345\217\226\350\277\236\346\216\245\351\200\273\350\276\221\345\233\276.png" index ad276d24..2aa8de5d 100644 Binary files "a/images/mybatis\350\277\236\346\216\245\346\261\240\350\216\267\345\217\226\350\277\236\346\216\245\351\200\273\350\276\221\345\233\276.png" and "b/images/mybatis\350\277\236\346\216\245\346\261\240\350\216\267\345\217\226\350\277\236\346\216\245\351\200\273\350\276\221\345\233\276.png" differ diff --git a/images/nacos/image-20200821111938485.png b/images/nacos/image-20200821111938485.png new file mode 100644 index 00000000..fc882310 Binary files /dev/null and b/images/nacos/image-20200821111938485.png differ diff --git a/images/nacos/image-20200821132413628.png b/images/nacos/image-20200821132413628.png new file mode 100644 index 00000000..03e329d4 Binary files /dev/null and b/images/nacos/image-20200821132413628.png differ diff --git a/images/nacos/image-20200821133350982.png b/images/nacos/image-20200821133350982.png new file mode 100644 index 00000000..956d4d55 Binary files /dev/null and b/images/nacos/image-20200821133350982.png differ diff --git a/images/nacos/image-20200821133445090.png b/images/nacos/image-20200821133445090.png new file mode 100644 index 00000000..dfba59a3 Binary files /dev/null and b/images/nacos/image-20200821133445090.png differ diff --git a/images/pdf.png b/images/pdf.png new file mode 100644 index 00000000..7d7e8f95 Binary files /dev/null and b/images/pdf.png differ diff --git a/images/qrcode-for-doocs.jpg b/images/qrcode-for-doocs.jpg new file mode 100644 index 00000000..79614411 Binary files /dev/null and b/images/qrcode-for-doocs.jpg differ diff --git a/images/qrcode-for-yanglbme.jpg b/images/qrcode-for-yanglbme.jpg new file mode 100644 index 00000000..709a15f8 Binary files /dev/null and b/images/qrcode-for-yanglbme.jpg differ diff --git a/images/repository-template-demo.png b/images/repository-template-demo.png index b46c36a7..2ca2b67b 100644 Binary files a/images/repository-template-demo.png and b/images/repository-template-demo.png differ diff --git a/images/spring/BeanFactory.png b/images/spring/BeanFactory.png new file mode 100644 index 00000000..ae9d6d53 Binary files /dev/null and b/images/spring/BeanFactory.png differ diff --git a/images/spring/BeanNameGenerator.png b/images/spring/BeanNameGenerator.png new file mode 100644 index 00000000..20cf2319 Binary files /dev/null and b/images/spring/BeanNameGenerator.png differ diff --git a/images/spring/DateTimeFormatAnnotationFormatterFactory.png b/images/spring/DateTimeFormatAnnotationFormatterFactory.png new file mode 100644 index 00000000..16ab6720 Binary files /dev/null and b/images/spring/DateTimeFormatAnnotationFormatterFactory.png differ diff --git a/images/spring/Mergeable.png b/images/spring/Mergeable.png new file mode 100644 index 00000000..473e2208 Binary files /dev/null and b/images/spring/Mergeable.png differ diff --git a/images/spring/MethodOverride.png b/images/spring/MethodOverride.png new file mode 100644 index 00000000..0529d2bc Binary files /dev/null and b/images/spring/MethodOverride.png differ diff --git a/images/spring/MultiValueMap.png b/images/spring/MultiValueMap.png new file mode 100644 index 00000000..f6c1773e Binary files /dev/null and b/images/spring/MultiValueMap.png differ diff --git "a/images/spring/MutablePropertyValues-\346\236\204\351\200\240.png" "b/images/spring/MutablePropertyValues-\346\236\204\351\200\240.png" new file mode 100644 index 00000000..bd3ad9cc Binary files /dev/null and "b/images/spring/MutablePropertyValues-\346\236\204\351\200\240.png" differ diff --git a/images/spring/Parser.png b/images/spring/Parser.png new file mode 100644 index 00000000..f70de182 Binary files /dev/null and b/images/spring/Parser.png differ diff --git a/images/spring/PropertyPlaceholderConfigurerResolver.png b/images/spring/PropertyPlaceholderConfigurerResolver.png new file mode 100644 index 00000000..4e34347e Binary files /dev/null and b/images/spring/PropertyPlaceholderConfigurerResolver.png differ diff --git a/images/spring/PropertySource.png b/images/spring/PropertySource.png new file mode 100644 index 00000000..a3f4769c Binary files /dev/null and b/images/spring/PropertySource.png differ diff --git a/images/spring/PropertyValue.png b/images/spring/PropertyValue.png new file mode 100644 index 00000000..3d4fb811 Binary files /dev/null and b/images/spring/PropertyValue.png differ diff --git a/images/spring/PropertyValues.png b/images/spring/PropertyValues.png new file mode 100644 index 00000000..8686ef3d Binary files /dev/null and b/images/spring/PropertyValues.png differ diff --git a/images/spring/RootBeanDefinition.png b/images/spring/RootBeanDefinition.png new file mode 100644 index 00000000..4463b34e Binary files /dev/null and b/images/spring/RootBeanDefinition.png differ diff --git a/images/spring/SystemPropertyUtils-resolvePlaceholders.png b/images/spring/SystemPropertyUtils-resolvePlaceholders.png new file mode 100644 index 00000000..3bb575ca Binary files /dev/null and b/images/spring/SystemPropertyUtils-resolvePlaceholders.png differ diff --git a/images/spring/TemplateAwareExpressionParser.png b/images/spring/TemplateAwareExpressionParser.png new file mode 100644 index 00000000..92008ec8 Binary files /dev/null and b/images/spring/TemplateAwareExpressionParser.png differ diff --git a/images/spring/image-20191231142829639.png b/images/spring/image-20191231142829639.png index 6888d6e4..caddebd0 100644 Binary files a/images/spring/image-20191231142829639.png and b/images/spring/image-20191231142829639.png differ diff --git a/images/spring/image-20191231162505748.png b/images/spring/image-20191231162505748.png index ab94ce79..1a161781 100644 Binary files a/images/spring/image-20191231162505748.png and b/images/spring/image-20191231162505748.png differ diff --git a/images/spring/image-20191231164622063.png b/images/spring/image-20191231164622063.png index 18f1973d..228ba3da 100644 Binary files a/images/spring/image-20191231164622063.png and b/images/spring/image-20191231164622063.png differ diff --git a/images/spring/image-20191231165638975.png b/images/spring/image-20191231165638975.png index 020ad9dd..b7baff1f 100644 Binary files a/images/spring/image-20191231165638975.png and b/images/spring/image-20191231165638975.png differ diff --git a/images/spring/image-20200101093742238.png b/images/spring/image-20200101093742238.png index 3986373d..5ecd37ec 100644 Binary files a/images/spring/image-20200101093742238.png and b/images/spring/image-20200101093742238.png differ diff --git a/images/spring/image-20200101100906778.png b/images/spring/image-20200101100906778.png index 4aa23bbd..c18ad2cc 100644 Binary files a/images/spring/image-20200101100906778.png and b/images/spring/image-20200101100906778.png differ diff --git a/images/spring/image-20200101111755022.png b/images/spring/image-20200101111755022.png index 7177f419..ce292aae 100644 Binary files a/images/spring/image-20200101111755022.png and b/images/spring/image-20200101111755022.png differ diff --git a/images/spring/image-20200101155451199.png b/images/spring/image-20200101155451199.png index dc73a7f3..ba588a25 100644 Binary files a/images/spring/image-20200101155451199.png and b/images/spring/image-20200101155451199.png differ diff --git a/images/spring/image-20200101155539501.png b/images/spring/image-20200101155539501.png index 190f1b36..f4a03f7a 100644 Binary files a/images/spring/image-20200101155539501.png and b/images/spring/image-20200101155539501.png differ diff --git a/images/spring/image-20200102083512005.png b/images/spring/image-20200102083512005.png index 0525f4bd..d17dbb56 100644 Binary files a/images/spring/image-20200102083512005.png and b/images/spring/image-20200102083512005.png differ diff --git a/images/spring/image-20200102085031641.png b/images/spring/image-20200102085031641.png index 8e382eac..f2d55630 100644 Binary files a/images/spring/image-20200102085031641.png and b/images/spring/image-20200102085031641.png differ diff --git a/images/spring/image-20200102091421516.png b/images/spring/image-20200102091421516.png index c549cc0e..1d8bac52 100644 Binary files a/images/spring/image-20200102091421516.png and b/images/spring/image-20200102091421516.png differ diff --git a/images/spring/image-20200108081404857.png b/images/spring/image-20200108081404857.png index 439c0707..084df04c 100644 Binary files a/images/spring/image-20200108081404857.png and b/images/spring/image-20200108081404857.png differ diff --git a/images/spring/image-20200108081623427.png b/images/spring/image-20200108081623427.png index 19a4ef7e..00e8f46c 100644 Binary files a/images/spring/image-20200108081623427.png and b/images/spring/image-20200108081623427.png differ diff --git a/images/spring/image-20200108082335031.png b/images/spring/image-20200108082335031.png index 2adee7ae..9c583f9c 100644 Binary files a/images/spring/image-20200108082335031.png and b/images/spring/image-20200108082335031.png differ diff --git a/images/spring/image-20200109084131415.png b/images/spring/image-20200109084131415.png index 6d99aa12..73ba8242 100644 Binary files a/images/spring/image-20200109084131415.png and b/images/spring/image-20200109084131415.png differ diff --git a/images/spring/image-20200109085606240.png b/images/spring/image-20200109085606240.png index 61883bf3..21cfe72e 100644 Binary files a/images/spring/image-20200109085606240.png and b/images/spring/image-20200109085606240.png differ diff --git a/images/spring/image-20200109090456547.png b/images/spring/image-20200109090456547.png index 19e745c5..8dd05445 100644 Binary files a/images/spring/image-20200109090456547.png and b/images/spring/image-20200109090456547.png differ diff --git a/images/spring/image-20200109090655157.png b/images/spring/image-20200109090655157.png index 97c431c8..7daa2c18 100644 Binary files a/images/spring/image-20200109090655157.png and b/images/spring/image-20200109090655157.png differ diff --git a/images/spring/image-20200109091216505.png b/images/spring/image-20200109091216505.png index cbc2d48d..9fa53f9d 100644 Binary files a/images/spring/image-20200109091216505.png and b/images/spring/image-20200109091216505.png differ diff --git a/images/spring/image-20200109092801572.png b/images/spring/image-20200109092801572.png index 043a9ca5..e1eea17a 100644 Binary files a/images/spring/image-20200109092801572.png and b/images/spring/image-20200109092801572.png differ diff --git a/images/spring/image-20200109093242494.png b/images/spring/image-20200109093242494.png index 8d8077f8..5f5a2bf4 100644 Binary files a/images/spring/image-20200109093242494.png and b/images/spring/image-20200109093242494.png differ diff --git a/images/spring/image-20200109094032421.png b/images/spring/image-20200109094032421.png index a8dcfb16..46473911 100644 Binary files a/images/spring/image-20200109094032421.png and b/images/spring/image-20200109094032421.png differ diff --git a/images/spring/image-20200109094649217.png b/images/spring/image-20200109094649217.png index 2c2b1f20..a7acefbf 100644 Binary files a/images/spring/image-20200109094649217.png and b/images/spring/image-20200109094649217.png differ diff --git a/images/spring/image-20200109094654409.png b/images/spring/image-20200109094654409.png index 2c2b1f20..a7acefbf 100644 Binary files a/images/spring/image-20200109094654409.png and b/images/spring/image-20200109094654409.png differ diff --git a/images/spring/image-20200109150841916.png b/images/spring/image-20200109150841916.png index f70bdde5..1ae975ce 100644 Binary files a/images/spring/image-20200109150841916.png and b/images/spring/image-20200109150841916.png differ diff --git a/images/spring/image-20200110093044672.png b/images/spring/image-20200110093044672.png index ddf04d9e..14c7219e 100644 Binary files a/images/spring/image-20200110093044672.png and b/images/spring/image-20200110093044672.png differ diff --git a/images/spring/image-20200115083744268.png b/images/spring/image-20200115083744268.png index a8cbb9dd..8d224334 100644 Binary files a/images/spring/image-20200115083744268.png and b/images/spring/image-20200115083744268.png differ diff --git a/images/spring/image-20200115084031725.png b/images/spring/image-20200115084031725.png index 3dba25a7..32f1fd1d 100644 Binary files a/images/spring/image-20200115084031725.png and b/images/spring/image-20200115084031725.png differ diff --git a/images/spring/image-20200115093602651.png b/images/spring/image-20200115093602651.png index f5238cba..be07e55d 100644 Binary files a/images/spring/image-20200115093602651.png and b/images/spring/image-20200115093602651.png differ diff --git a/images/spring/image-20200115105941265.png b/images/spring/image-20200115105941265.png index f89a6791..a572eb6a 100644 Binary files a/images/spring/image-20200115105941265.png and b/images/spring/image-20200115105941265.png differ diff --git a/images/spring/image-20200115141708702.png b/images/spring/image-20200115141708702.png index 1cbe7d14..730baaf7 100644 Binary files a/images/spring/image-20200115141708702.png and b/images/spring/image-20200115141708702.png differ diff --git a/images/spring/image-20200115143315633.png b/images/spring/image-20200115143315633.png index ce845f3b..616d0a7f 100644 Binary files a/images/spring/image-20200115143315633.png and b/images/spring/image-20200115143315633.png differ diff --git a/images/spring/image-20200115143456554.png b/images/spring/image-20200115143456554.png index 95eabdff..ceecedbe 100644 Binary files a/images/spring/image-20200115143456554.png and b/images/spring/image-20200115143456554.png differ diff --git a/images/spring/image-20200116085344737.png b/images/spring/image-20200116085344737.png index cb72c430..ac1b4288 100644 Binary files a/images/spring/image-20200116085344737.png and b/images/spring/image-20200116085344737.png differ diff --git a/images/spring/image-20200116085423073.png b/images/spring/image-20200116085423073.png index a7487265..2da66d95 100644 Binary files a/images/spring/image-20200116085423073.png and b/images/spring/image-20200116085423073.png differ diff --git a/images/spring/image-20200116085726577.png b/images/spring/image-20200116085726577.png index 6f4e6de1..a3878c96 100644 Binary files a/images/spring/image-20200116085726577.png and b/images/spring/image-20200116085726577.png differ diff --git a/images/spring/image-20200116085737632.png b/images/spring/image-20200116085737632.png index 7a514020..132b6a81 100644 Binary files a/images/spring/image-20200116085737632.png and b/images/spring/image-20200116085737632.png differ diff --git a/images/spring/image-20200116085927359.png b/images/spring/image-20200116085927359.png index 48d4779d..800b07a8 100644 Binary files a/images/spring/image-20200116085927359.png and b/images/spring/image-20200116085927359.png differ diff --git a/images/spring/image-20200116092259944.png b/images/spring/image-20200116092259944.png index e3ca21ee..d7ca9c40 100644 Binary files a/images/spring/image-20200116092259944.png and b/images/spring/image-20200116092259944.png differ diff --git a/images/spring/image-20200116141838601.png b/images/spring/image-20200116141838601.png index 4cb591a9..db208276 100644 Binary files a/images/spring/image-20200116141838601.png and b/images/spring/image-20200116141838601.png differ diff --git a/images/spring/image-20200116141932486.png b/images/spring/image-20200116141932486.png index fc6b9c6d..473a5114 100644 Binary files a/images/spring/image-20200116141932486.png and b/images/spring/image-20200116141932486.png differ diff --git a/images/spring/image-20200117104710142.png b/images/spring/image-20200117104710142.png index aca0d46a..cca3612c 100644 Binary files a/images/spring/image-20200117104710142.png and b/images/spring/image-20200117104710142.png differ diff --git a/images/spring/image-20200117110115741.png b/images/spring/image-20200117110115741.png index 2c42c2b9..a53b3eb5 100644 Binary files a/images/spring/image-20200117110115741.png and b/images/spring/image-20200117110115741.png differ diff --git a/images/spring/image-20200117110846256.png b/images/spring/image-20200117110846256.png index 0f5b7810..4585a34f 100644 Binary files a/images/spring/image-20200117110846256.png and b/images/spring/image-20200117110846256.png differ diff --git a/images/spring/image-20200117111131406.png b/images/spring/image-20200117111131406.png index 8e092a2d..5f3ffa8b 100644 Binary files a/images/spring/image-20200117111131406.png and b/images/spring/image-20200117111131406.png differ diff --git a/images/spring/image-20200117133325461.png b/images/spring/image-20200117133325461.png index 6e35c95b..6a931f43 100644 Binary files a/images/spring/image-20200117133325461.png and b/images/spring/image-20200117133325461.png differ diff --git a/images/spring/image-20200117141309038.png b/images/spring/image-20200117141309038.png index 781b163d..89eed798 100644 Binary files a/images/spring/image-20200117141309038.png and b/images/spring/image-20200117141309038.png differ diff --git a/images/spring/image-20200117141519123.png b/images/spring/image-20200117141519123.png index ddbb8b9b..e1f5aa66 100644 Binary files a/images/spring/image-20200117141519123.png and b/images/spring/image-20200117141519123.png differ diff --git a/images/spring/image-20200117142800671.png b/images/spring/image-20200117142800671.png index 97068821..1b78950b 100644 Binary files a/images/spring/image-20200117142800671.png and b/images/spring/image-20200117142800671.png differ diff --git a/images/spring/image-20200117143022827.png b/images/spring/image-20200117143022827.png index 92eabae2..a3e1a374 100644 Binary files a/images/spring/image-20200117143022827.png and b/images/spring/image-20200117143022827.png differ diff --git a/images/spring/image-20200119085346675.png b/images/spring/image-20200119085346675.png index 0861d25c..14832151 100644 Binary files a/images/spring/image-20200119085346675.png and b/images/spring/image-20200119085346675.png differ diff --git a/images/spring/image-20200119085655734.png b/images/spring/image-20200119085655734.png index 1915ae4c..2a3c6bd3 100644 Binary files a/images/spring/image-20200119085655734.png and b/images/spring/image-20200119085655734.png differ diff --git a/images/spring/image-20200119101017989.png b/images/spring/image-20200119101017989.png index 9a1549f5..d9060b2e 100644 Binary files a/images/spring/image-20200119101017989.png and b/images/spring/image-20200119101017989.png differ diff --git a/images/spring/image-20200119101026726.png b/images/spring/image-20200119101026726.png index 86722597..98043e86 100644 Binary files a/images/spring/image-20200119101026726.png and b/images/spring/image-20200119101026726.png differ diff --git a/images/spring/image-20200119101107820.png b/images/spring/image-20200119101107820.png index 4afdfe9c..261fd809 100644 Binary files a/images/spring/image-20200119101107820.png and b/images/spring/image-20200119101107820.png differ diff --git a/images/spring/image-20200119101516591.png b/images/spring/image-20200119101516591.png index 64a284b2..5fbebf02 100644 Binary files a/images/spring/image-20200119101516591.png and b/images/spring/image-20200119101516591.png differ diff --git a/images/spring/image-20200119141937915.png b/images/spring/image-20200119141937915.png index 1eeef7a6..fa8a44d1 100644 Binary files a/images/spring/image-20200119141937915.png and b/images/spring/image-20200119141937915.png differ diff --git a/images/spring/image-20200119143046066.png b/images/spring/image-20200119143046066.png index eaeb625e..dbe99c82 100644 Binary files a/images/spring/image-20200119143046066.png and b/images/spring/image-20200119143046066.png differ diff --git a/images/spring/image-20200119144019171.png b/images/spring/image-20200119144019171.png index 447e512c..0ff17222 100644 Binary files a/images/spring/image-20200119144019171.png and b/images/spring/image-20200119144019171.png differ diff --git a/images/spring/image-20200119145138205.png b/images/spring/image-20200119145138205.png index 0c27aeb9..ea2db4d7 100644 Binary files a/images/spring/image-20200119145138205.png and b/images/spring/image-20200119145138205.png differ diff --git a/images/spring/image-20200119163638222.png b/images/spring/image-20200119163638222.png index 080c72b9..ee619f9a 100644 Binary files a/images/spring/image-20200119163638222.png and b/images/spring/image-20200119163638222.png differ diff --git a/images/spring/image-20200119164149650.png b/images/spring/image-20200119164149650.png index 1b3e0dd8..04a3776f 100644 Binary files a/images/spring/image-20200119164149650.png and b/images/spring/image-20200119164149650.png differ diff --git a/images/spring/image-20200119164402137.png b/images/spring/image-20200119164402137.png index 40536af9..1b01d792 100644 Binary files a/images/spring/image-20200119164402137.png and b/images/spring/image-20200119164402137.png differ diff --git a/images/spring/image-20200119164410301.png b/images/spring/image-20200119164410301.png index 48b93fde..d39ab124 100644 Binary files a/images/spring/image-20200119164410301.png and b/images/spring/image-20200119164410301.png differ diff --git a/images/spring/image-20200226082614312.png b/images/spring/image-20200226082614312.png index 774ef4e9..60354c55 100644 Binary files a/images/spring/image-20200226082614312.png and b/images/spring/image-20200226082614312.png differ diff --git a/images/spring/image-20200226083247784.png b/images/spring/image-20200226083247784.png index 81e2e517..4b6d396e 100644 Binary files a/images/spring/image-20200226083247784.png and b/images/spring/image-20200226083247784.png differ diff --git a/images/spring/image-20200226084056993.png b/images/spring/image-20200226084056993.png index 8bbfe495..dc4e1a14 100644 Binary files a/images/spring/image-20200226084056993.png and b/images/spring/image-20200226084056993.png differ diff --git a/images/spring/image-20200226084200428.png b/images/spring/image-20200226084200428.png index effd52cd..d8267795 100644 Binary files a/images/spring/image-20200226084200428.png and b/images/spring/image-20200226084200428.png differ diff --git a/images/spring/image-20200226084400939.png b/images/spring/image-20200226084400939.png index 18077357..919df7e4 100644 Binary files a/images/spring/image-20200226084400939.png and b/images/spring/image-20200226084400939.png differ diff --git a/images/spring/image-20200226084514795.png b/images/spring/image-20200226084514795.png index 60cc5dd7..b2cf76d9 100644 Binary files a/images/spring/image-20200226084514795.png and b/images/spring/image-20200226084514795.png differ diff --git a/images/spring/image-20200226084640683.png b/images/spring/image-20200226084640683.png index e6c3b64e..12db42bf 100644 Binary files a/images/spring/image-20200226084640683.png and b/images/spring/image-20200226084640683.png differ diff --git a/images/spring/image-20200226084914000.png b/images/spring/image-20200226084914000.png index eee6439c..86327b0d 100644 Binary files a/images/spring/image-20200226084914000.png and b/images/spring/image-20200226084914000.png differ diff --git a/images/spring/image-20200226084923783.png b/images/spring/image-20200226084923783.png index 58e9e863..d7e3daba 100644 Binary files a/images/spring/image-20200226084923783.png and b/images/spring/image-20200226084923783.png differ diff --git a/images/spring/image-20200226085433130.png b/images/spring/image-20200226085433130.png index a50624b6..f0948cc0 100644 Binary files a/images/spring/image-20200226085433130.png and b/images/spring/image-20200226085433130.png differ diff --git a/images/spring/image-20200226085440865.png b/images/spring/image-20200226085440865.png index 1c41409b..fb754fae 100644 Binary files a/images/spring/image-20200226085440865.png and b/images/spring/image-20200226085440865.png differ diff --git a/images/spring/image-20200226085727426.png b/images/spring/image-20200226085727426.png index 619a1462..2f9b6cf1 100644 Binary files a/images/spring/image-20200226085727426.png and b/images/spring/image-20200226085727426.png differ diff --git a/images/spring/image-20200226085839496.png b/images/spring/image-20200226085839496.png index c2d898d8..5396f6fb 100644 Binary files a/images/spring/image-20200226085839496.png and b/images/spring/image-20200226085839496.png differ diff --git a/images/spring/image-20200226090042946.png b/images/spring/image-20200226090042946.png index cfa4d224..ace963fc 100644 Binary files a/images/spring/image-20200226090042946.png and b/images/spring/image-20200226090042946.png differ diff --git a/images/spring/image-20200226090315865.png b/images/spring/image-20200226090315865.png index c0280d10..707ea5e2 100644 Binary files a/images/spring/image-20200226090315865.png and b/images/spring/image-20200226090315865.png differ diff --git a/images/spring/image-20200226090432052.png b/images/spring/image-20200226090432052.png index e76d1f61..9c11bcbe 100644 Binary files a/images/spring/image-20200226090432052.png and b/images/spring/image-20200226090432052.png differ diff --git a/images/spring/image-20200226090650154.png b/images/spring/image-20200226090650154.png index 1470b891..5dc1f1a6 100644 Binary files a/images/spring/image-20200226090650154.png and b/images/spring/image-20200226090650154.png differ diff --git a/images/spring/image-20200226090719108.png b/images/spring/image-20200226090719108.png index 0f2a9dd3..82815f77 100644 Binary files a/images/spring/image-20200226090719108.png and b/images/spring/image-20200226090719108.png differ diff --git a/images/spring/image-20200226090827849.png b/images/spring/image-20200226090827849.png index 5b39be85..f56d65ff 100644 Binary files a/images/spring/image-20200226090827849.png and b/images/spring/image-20200226090827849.png differ diff --git a/images/spring/image-20200226090945418.png b/images/spring/image-20200226090945418.png index 7d52e13f..bbca557f 100644 Binary files a/images/spring/image-20200226090945418.png and b/images/spring/image-20200226090945418.png differ diff --git a/images/spring/image-20200728094658684.png b/images/spring/image-20200728094658684.png new file mode 100644 index 00000000..3183b1f1 Binary files /dev/null and b/images/spring/image-20200728094658684.png differ diff --git a/images/spring/image-20200728105926218.png b/images/spring/image-20200728105926218.png new file mode 100644 index 00000000..7e48c9b8 Binary files /dev/null and b/images/spring/image-20200728105926218.png differ diff --git a/images/spring/image-20200728133037075.png b/images/spring/image-20200728133037075.png new file mode 100644 index 00000000..a69d8155 Binary files /dev/null and b/images/spring/image-20200728133037075.png differ diff --git a/images/spring/image-20200729090322058.png b/images/spring/image-20200729090322058.png new file mode 100644 index 00000000..05254ea4 Binary files /dev/null and b/images/spring/image-20200729090322058.png differ diff --git a/images/spring/image-20200729144622440.png b/images/spring/image-20200729144622440.png new file mode 100644 index 00000000..5ce24595 Binary files /dev/null and b/images/spring/image-20200729144622440.png differ diff --git a/images/spring/image-20200729145518089.png b/images/spring/image-20200729145518089.png new file mode 100644 index 00000000..167ede2d Binary files /dev/null and b/images/spring/image-20200729145518089.png differ diff --git a/images/spring/image-20200729145637688.png b/images/spring/image-20200729145637688.png new file mode 100644 index 00000000..1e91de17 Binary files /dev/null and b/images/spring/image-20200729145637688.png differ diff --git a/images/spring/image-20200729145835608.png b/images/spring/image-20200729145835608.png new file mode 100644 index 00000000..f0345641 Binary files /dev/null and b/images/spring/image-20200729145835608.png differ diff --git a/images/spring/image-20200729160650401.png b/images/spring/image-20200729160650401.png new file mode 100644 index 00000000..4dab856c Binary files /dev/null and b/images/spring/image-20200729160650401.png differ diff --git a/images/spring/image-20200729161647214.png b/images/spring/image-20200729161647214.png new file mode 100644 index 00000000..d7c3bf14 Binary files /dev/null and b/images/spring/image-20200729161647214.png differ diff --git a/images/spring/image-20200729162023837.png b/images/spring/image-20200729162023837.png new file mode 100644 index 00000000..8799b61f Binary files /dev/null and b/images/spring/image-20200729162023837.png differ diff --git a/images/spring/image-20200729163303000.png b/images/spring/image-20200729163303000.png new file mode 100644 index 00000000..9e66cf8a Binary files /dev/null and b/images/spring/image-20200729163303000.png differ diff --git a/images/spring/image-20200824094154847.png b/images/spring/image-20200824094154847.png new file mode 100644 index 00000000..9df88884 Binary files /dev/null and b/images/spring/image-20200824094154847.png differ diff --git a/images/spring/image-20200824104529315.png b/images/spring/image-20200824104529315.png new file mode 100644 index 00000000..1b58a1bd Binary files /dev/null and b/images/spring/image-20200824104529315.png differ diff --git a/images/spring/image-20200902102912716.png b/images/spring/image-20200902102912716.png new file mode 100644 index 00000000..bf76be49 Binary files /dev/null and b/images/spring/image-20200902102912716.png differ diff --git a/images/spring/image-20200902103154580.png b/images/spring/image-20200902103154580.png new file mode 100644 index 00000000..283a0c0a Binary files /dev/null and b/images/spring/image-20200902103154580.png differ diff --git a/images/spring/image-20200902105454958.png b/images/spring/image-20200902105454958.png new file mode 100644 index 00000000..32e1cff4 Binary files /dev/null and b/images/spring/image-20200902105454958.png differ diff --git a/images/spring/image-20200903091759451.png b/images/spring/image-20200903091759451.png new file mode 100644 index 00000000..28b94bf1 Binary files /dev/null and b/images/spring/image-20200903091759451.png differ diff --git a/images/spring/image-20200903111128603.png b/images/spring/image-20200903111128603.png new file mode 100644 index 00000000..04757f8f Binary files /dev/null and b/images/spring/image-20200903111128603.png differ diff --git a/images/spring/image-20200903150738285.png b/images/spring/image-20200903150738285.png new file mode 100644 index 00000000..78316844 Binary files /dev/null and b/images/spring/image-20200903150738285.png differ diff --git a/images/spring/image-20200903150930186.png b/images/spring/image-20200903150930186.png new file mode 100644 index 00000000..309d0965 Binary files /dev/null and b/images/spring/image-20200903150930186.png differ diff --git a/images/spring/image-20200903153057321.png b/images/spring/image-20200903153057321.png new file mode 100644 index 00000000..57e7454f Binary files /dev/null and b/images/spring/image-20200903153057321.png differ diff --git a/images/spring/image-20200903153432559.png b/images/spring/image-20200903153432559.png new file mode 100644 index 00000000..9cfb6f32 Binary files /dev/null and b/images/spring/image-20200903153432559.png differ diff --git a/images/spring/image-20200903153533141.png b/images/spring/image-20200903153533141.png new file mode 100644 index 00000000..30f4e8fb Binary files /dev/null and b/images/spring/image-20200903153533141.png differ diff --git a/images/spring/image-20200903153617353.png b/images/spring/image-20200903153617353.png new file mode 100644 index 00000000..8bef5fea Binary files /dev/null and b/images/spring/image-20200903153617353.png differ diff --git a/images/spring/image-20210902072224002.png b/images/spring/image-20210902072224002.png new file mode 100644 index 00000000..07ff0d49 Binary files /dev/null and b/images/spring/image-20210902072224002.png differ diff --git a/images/spring/image-20210903080803199.png b/images/spring/image-20210903080803199.png new file mode 100644 index 00000000..50c82c74 Binary files /dev/null and b/images/spring/image-20210903080803199.png differ diff --git a/images/spring/image-20210904161436139.png b/images/spring/image-20210904161436139.png new file mode 100644 index 00000000..8f0aa663 Binary files /dev/null and b/images/spring/image-20210904161436139.png differ diff --git a/images/spring/image-20210904161808341.png b/images/spring/image-20210904161808341.png new file mode 100644 index 00000000..808aa91b Binary files /dev/null and b/images/spring/image-20210904161808341.png differ diff --git a/images/spring/image-20210904162844126.png b/images/spring/image-20210904162844126.png new file mode 100644 index 00000000..f411a0d7 Binary files /dev/null and b/images/spring/image-20210904162844126.png differ diff --git a/images/spring/image-20210904174616712.png b/images/spring/image-20210904174616712.png new file mode 100644 index 00000000..9f2160cf Binary files /dev/null and b/images/spring/image-20210904174616712.png differ diff --git a/images/spring/image-20211213224509864.png b/images/spring/image-20211213224509864.png new file mode 100644 index 00000000..ff027a9c Binary files /dev/null and b/images/spring/image-20211213224509864.png differ diff --git a/images/spring/image-20211213224920994.png b/images/spring/image-20211213224920994.png new file mode 100644 index 00000000..eedc62c7 Binary files /dev/null and b/images/spring/image-20211213224920994.png differ diff --git a/images/spring/image-20211213225044814.png b/images/spring/image-20211213225044814.png new file mode 100644 index 00000000..0cd160b3 Binary files /dev/null and b/images/spring/image-20211213225044814.png differ diff --git a/images/spring/image-20211213225124831.png b/images/spring/image-20211213225124831.png new file mode 100644 index 00000000..7538e9dc Binary files /dev/null and b/images/spring/image-20211213225124831.png differ diff --git a/images/spring/image-20211213225330193.png b/images/spring/image-20211213225330193.png new file mode 100644 index 00000000..cab1a870 Binary files /dev/null and b/images/spring/image-20211213225330193.png differ diff --git a/images/spring/image-20211213225748030.png b/images/spring/image-20211213225748030.png new file mode 100644 index 00000000..988dcc98 Binary files /dev/null and b/images/spring/image-20211213225748030.png differ diff --git a/images/spring/image-20211213225831583.png b/images/spring/image-20211213225831583.png new file mode 100644 index 00000000..62224e24 Binary files /dev/null and b/images/spring/image-20211213225831583.png differ diff --git a/images/spring/image-20211213225953964.png b/images/spring/image-20211213225953964.png new file mode 100644 index 00000000..c1f933b6 Binary files /dev/null and b/images/spring/image-20211213225953964.png differ diff --git a/images/spring/image-20211213230042502.png b/images/spring/image-20211213230042502.png new file mode 100644 index 00000000..09c8a1d6 Binary files /dev/null and b/images/spring/image-20211213230042502.png differ diff --git a/images/spring/image-20211213230212297.png b/images/spring/image-20211213230212297.png new file mode 100644 index 00000000..4e4c909c Binary files /dev/null and b/images/spring/image-20211213230212297.png differ diff --git "a/images/spring/\345\276\252\347\216\257\344\276\235\350\265\226.png" "b/images/spring/\345\276\252\347\216\257\344\276\235\350\265\226.png" new file mode 100644 index 00000000..c59d161a Binary files /dev/null and "b/images/spring/\345\276\252\347\216\257\344\276\235\350\265\226.png" differ diff --git "a/images/springMVC/DispatcherServlet\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.png" "b/images/springMVC/DispatcherServlet\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.png" index 40c227d8..bcd4b242 100644 Binary files "a/images/springMVC/DispatcherServlet\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.png" and "b/images/springMVC/DispatcherServlet\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.png" differ diff --git "a/images/springMVC/DispatcherServlet\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" "b/images/springMVC/DispatcherServlet\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" index 7b99c8b2..1d857d8e 100644 Binary files "a/images/springMVC/DispatcherServlet\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" and "b/images/springMVC/DispatcherServlet\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" differ diff --git a/images/springMVC/HandlerMapping.png b/images/springMVC/HandlerMapping.png new file mode 100644 index 00000000..fe3ef068 Binary files /dev/null and b/images/springMVC/HandlerMapping.png differ diff --git "a/images/springMVC/HandlerMapping\347\273\204\344\273\266.png" "b/images/springMVC/HandlerMapping\347\273\204\344\273\266.png" index 77034454..fe0f6282 100644 Binary files "a/images/springMVC/HandlerMapping\347\273\204\344\273\266.png" and "b/images/springMVC/HandlerMapping\347\273\204\344\273\266.png" differ diff --git "a/images/springMVC/SimpleUrlHandlerMapping\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" "b/images/springMVC/SimpleUrlHandlerMapping\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" index 2c4d64d2..ed5e74f2 100644 Binary files "a/images/springMVC/SimpleUrlHandlerMapping\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" and "b/images/springMVC/SimpleUrlHandlerMapping\347\232\204\347\273\247\346\211\277\345\205\263\347\263\273.png" differ diff --git "a/images/springMVC/WebApplicationContext\346\216\245\345\217\243\347\232\204\347\261\273\347\273\247\346\211\277\345\205\263\347\263\273.png" "b/images/springMVC/WebApplicationContext\346\216\245\345\217\243\347\232\204\347\261\273\347\273\247\346\211\277\345\205\263\347\263\273.png" index ad12de61..d1379a3f 100644 Binary files "a/images/springMVC/WebApplicationContext\346\216\245\345\217\243\347\232\204\347\261\273\347\273\247\346\211\277\345\205\263\347\263\273.png" and "b/images/springMVC/WebApplicationContext\346\216\245\345\217\243\347\232\204\347\261\273\347\273\247\346\211\277\345\205\263\347\263\273.png" differ diff --git "a/images/springMVC/Web\345\256\271\345\231\250\345\220\257\345\212\250spring\345\272\224\347\224\250\347\250\213\345\272\217\350\277\207\347\250\213\345\233\276.png" "b/images/springMVC/Web\345\256\271\345\231\250\345\220\257\345\212\250spring\345\272\224\347\224\250\347\250\213\345\272\217\350\277\207\347\250\213\345\233\276.png" index 4777ddfe..0f92b996 100644 Binary files "a/images/springMVC/Web\345\256\271\345\231\250\345\220\257\345\212\250spring\345\272\224\347\224\250\347\250\213\345\272\217\350\277\207\347\250\213\345\233\276.png" and "b/images/springMVC/Web\345\256\271\345\231\250\345\220\257\345\212\250spring\345\272\224\347\224\250\347\250\213\345\272\217\350\277\207\347\250\213\345\233\276.png" differ diff --git a/images/springMVC/clazz/image-20200123085741347.png b/images/springMVC/clazz/image-20200123085741347.png index 6d3b477d..a2b6ea25 100644 Binary files a/images/springMVC/clazz/image-20200123085741347.png and b/images/springMVC/clazz/image-20200123085741347.png differ diff --git a/images/springMVC/clazz/image-20200123085756168.png b/images/springMVC/clazz/image-20200123085756168.png index 58f5b3f3..5866a8a6 100644 Binary files a/images/springMVC/clazz/image-20200123085756168.png and b/images/springMVC/clazz/image-20200123085756168.png differ diff --git a/images/springMVC/clazz/image-20200123085946476.png b/images/springMVC/clazz/image-20200123085946476.png index d4fdde4a..193f6781 100644 Binary files a/images/springMVC/clazz/image-20200123085946476.png and b/images/springMVC/clazz/image-20200123085946476.png differ diff --git a/images/springMVC/clazz/image-20200123090442409.png b/images/springMVC/clazz/image-20200123090442409.png index 8668ac29..4777ed41 100644 Binary files a/images/springMVC/clazz/image-20200123090442409.png and b/images/springMVC/clazz/image-20200123090442409.png differ diff --git a/images/springMVC/clazz/image-20200123090851644.png b/images/springMVC/clazz/image-20200123090851644.png index 96dd4520..e35b8a21 100644 Binary files a/images/springMVC/clazz/image-20200123090851644.png and b/images/springMVC/clazz/image-20200123090851644.png differ diff --git a/images/springMVC/clazz/image-20200123091445694.png b/images/springMVC/clazz/image-20200123091445694.png index 9faa2bf4..0f987b02 100644 Binary files a/images/springMVC/clazz/image-20200123091445694.png and b/images/springMVC/clazz/image-20200123091445694.png differ diff --git a/images/springMVC/clazz/image-20200123093032179.png b/images/springMVC/clazz/image-20200123093032179.png index 27d88d9a..617f587a 100644 Binary files a/images/springMVC/clazz/image-20200123093032179.png and b/images/springMVC/clazz/image-20200123093032179.png differ diff --git a/images/springMVC/clazz/image-20200123093733129.png b/images/springMVC/clazz/image-20200123093733129.png index 22a276a3..be46b5d8 100644 Binary files a/images/springMVC/clazz/image-20200123093733129.png and b/images/springMVC/clazz/image-20200123093733129.png differ diff --git a/images/springMVC/clazz/image-20200123094439617.png b/images/springMVC/clazz/image-20200123094439617.png index 5e3b5ca4..93ffdcc4 100644 Binary files a/images/springMVC/clazz/image-20200123094439617.png and b/images/springMVC/clazz/image-20200123094439617.png differ diff --git a/images/springMVC/clazz/image-20200918130340555.png b/images/springMVC/clazz/image-20200918130340555.png new file mode 100644 index 00000000..a4415744 Binary files /dev/null and b/images/springMVC/clazz/image-20200918130340555.png differ diff --git a/images/springMVC/image-20200915135933146.png b/images/springMVC/image-20200915135933146.png new file mode 100644 index 00000000..bb1b44ad Binary files /dev/null and b/images/springMVC/image-20200915135933146.png differ diff --git "a/images/springTransaction/PlatformTransactionManager\347\273\204\344\273\266\347\232\204\350\256\276\350\256\241.png" "b/images/springTransaction/PlatformTransactionManager\347\273\204\344\273\266\347\232\204\350\256\276\350\256\241.png" index 16fa1511..0a4538b3 100644 Binary files "a/images/springTransaction/PlatformTransactionManager\347\273\204\344\273\266\347\232\204\350\256\276\350\256\241.png" and "b/images/springTransaction/PlatformTransactionManager\347\273\204\344\273\266\347\232\204\350\256\276\350\256\241.png" differ diff --git "a/images/springTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\346\250\241\345\235\227\347\261\273\345\261\202\346\254\241\347\273\223\346\236\204.png" "b/images/springTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\346\250\241\345\235\227\347\261\273\345\261\202\346\254\241\347\273\223\346\236\204.png" index 6eef9c71..01ef3333 100644 Binary files "a/images/springTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\346\250\241\345\235\227\347\261\273\345\261\202\346\254\241\347\273\223\346\236\204.png" and "b/images/springTransaction/Spring\344\272\213\345\212\241\345\244\204\347\220\206\346\250\241\345\235\227\347\261\273\345\261\202\346\254\241\347\273\223\346\236\204.png" differ diff --git "a/images/springTransaction/createMainInterceptor()\346\226\271\346\263\225\347\232\204\350\260\203\347\224\250\351\223\276.png" "b/images/springTransaction/createMainInterceptor()\346\226\271\346\263\225\347\232\204\350\260\203\347\224\250\351\223\276.png" index 99a5cb22..0db0ea04 100644 Binary files "a/images/springTransaction/createMainInterceptor()\346\226\271\346\263\225\347\232\204\350\260\203\347\224\250\351\223\276.png" and "b/images/springTransaction/createMainInterceptor()\346\226\271\346\263\225\347\232\204\350\260\203\347\224\250\351\223\276.png" differ diff --git "a/images/springTransaction/\345\256\236\347\216\260DataSourceTransactionManager\347\232\204\346\227\266\345\272\217\345\233\276.png" "b/images/springTransaction/\345\256\236\347\216\260DataSourceTransactionManager\347\232\204\346\227\266\345\272\217\345\233\276.png" index d87d0241..d393dd7e 100644 Binary files "a/images/springTransaction/\345\256\236\347\216\260DataSourceTransactionManager\347\232\204\346\227\266\345\272\217\345\233\276.png" and "b/images/springTransaction/\345\256\236\347\216\260DataSourceTransactionManager\347\232\204\346\227\266\345\272\217\345\233\276.png" differ diff --git "a/images/springTransaction/\350\260\203\347\224\250createTransactionIfNecessary()\346\226\271\346\263\225\347\232\204\346\227\266\345\272\217\345\233\276.png" "b/images/springTransaction/\350\260\203\347\224\250createTransactionIfNecessary()\346\226\271\346\263\225\347\232\204\346\227\266\345\272\217\345\233\276.png" index fa5b3477..3843f0e2 100644 Binary files "a/images/springTransaction/\350\260\203\347\224\250createTransactionIfNecessary()\346\226\271\346\263\225\347\232\204\346\227\266\345\272\217\345\233\276.png" and "b/images/springTransaction/\350\260\203\347\224\250createTransactionIfNecessary()\346\226\271\346\263\225\347\232\204\346\227\266\345\272\217\345\233\276.png" differ diff --git a/images/springmessage/image-20200304085303580.png b/images/springmessage/image-20200304085303580.png index 3ef4b9b5..914a6b4c 100644 Binary files a/images/springmessage/image-20200304085303580.png and b/images/springmessage/image-20200304085303580.png differ diff --git a/images/springmessage/image-20200304092154712.png b/images/springmessage/image-20200304092154712.png index 526d07e1..36d879b9 100644 Binary files a/images/springmessage/image-20200304092154712.png and b/images/springmessage/image-20200304092154712.png differ diff --git a/images/springmessage/image-20200305085013723.png b/images/springmessage/image-20200305085013723.png index acaf0b79..d16247f7 100644 Binary files a/images/springmessage/image-20200305085013723.png and b/images/springmessage/image-20200305085013723.png differ diff --git a/images/springmessage/image-20200305085845017.png b/images/springmessage/image-20200305085845017.png index a2795c04..f37ffd64 100644 Binary files a/images/springmessage/image-20200305085845017.png and b/images/springmessage/image-20200305085845017.png differ diff --git a/images/springmessage/image-20200305090846313.png b/images/springmessage/image-20200305090846313.png index cbea7c11..73259f7c 100644 Binary files a/images/springmessage/image-20200305090846313.png and b/images/springmessage/image-20200305090846313.png differ diff --git a/images/use-this-template-button.png b/images/use-this-template-button.png index de63d32e..2e5d9f70 100644 Binary files a/images/use-this-template-button.png and b/images/use-this-template-button.png differ diff --git "a/images/\345\212\250\346\200\201\344\273\243\347\220\206\345\216\237\347\220\206\345\233\2761.png" "b/images/\345\212\250\346\200\201\344\273\243\347\220\206\345\216\237\347\220\206\345\233\2761.png" index f7be48e8..6e8e64cd 100644 Binary files "a/images/\345\212\250\346\200\201\344\273\243\347\220\206\345\216\237\347\220\206\345\233\2761.png" and "b/images/\345\212\250\346\200\201\344\273\243\347\220\206\345\216\237\347\220\206\345\233\2761.png" differ diff --git a/index.html b/index.html deleted file mode 100644 index 069726ae..00000000 --- a/index.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - 读尽天下源码,心中自然无码 - - - - - - - - - - - -
本系列知识由 Doocs 开源社区总结发布
- - - - - - - - - - - - - - - \ No newline at end of file diff --git a/master.gh b/master.gh deleted file mode 100644 index 61be5183..00000000 --- a/master.gh +++ /dev/null @@ -1,3 +0,0 @@ -git add . -git commit -m "auto update" -git push -u -f origin master \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..0e827027 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "source-code-hunter", + "version": "1.0.0", + "private": true, + "packageManager": "pnpm@9.15.0", + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "dependencies": { + "vitepress-plugin-comment-with-giscus": "^1.1.15" + }, + "devDependencies": { + "vitepress": "^2.0.0-alpha.12" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..447bce1d --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1481 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + vitepress-plugin-comment-with-giscus: + specifier: ^1.1.15 + version: 1.1.15(vue@3.5.22) + devDependencies: + vitepress: + specifier: ^2.0.0-alpha.12 + version: 2.0.0-alpha.12(postcss@8.5.6) + +packages: + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@docsearch/css@4.2.0': + resolution: {integrity: sha512-65KU9Fw5fGsPPPlgIghonMcndyx1bszzrDQYLfierN+Ha29yotMHzVS94bPkZS6On9LS8dE4qmW4P/fGjtCf/g==} + + '@docsearch/js@4.2.0': + resolution: {integrity: sha512-KBHVPO29QiGUFJYeAqxW0oXtGf/aghNmRrIRPT4/28JAefqoCkNn/ZM/jeQ7fHjl0KNM6C+KlLVYjwyz6lNZnA==} + + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@giscus/vue@2.4.0': + resolution: {integrity: sha512-QOxKHgsMT91myyQagP2v20YYAei1ByZuc3qcaYxbHx4AwOeyVrybDIuRFwG9YDv6OraC86jYnU4Ixd37ddC/0A==} + peerDependencies: + vue: '>=3.2.0' + + '@iconify-json/simple-icons@1.2.55': + resolution: {integrity: sha512-9vc04pmup/zcef8hDypWU8nMwMaFVkWuUzWkxyL++DVp5AA8baoJHK6RyKN1v+cvfR2agxkUb053XVggzFFkTA==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@lit-labs/ssr-dom-shim@1.4.0': + resolution: {integrity: sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==} + + '@lit/reactive-element@2.1.1': + resolution: {integrity: sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==} + + '@rolldown/pluginutils@1.0.0-beta.29': + resolution: {integrity: sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==} + + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + cpu: [x64] + os: [win32] + + '@shikijs/core@3.13.0': + resolution: {integrity: sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==} + + '@shikijs/engine-javascript@3.13.0': + resolution: {integrity: sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==} + + '@shikijs/engine-oniguruma@3.13.0': + resolution: {integrity: sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==} + + '@shikijs/langs@3.13.0': + resolution: {integrity: sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==} + + '@shikijs/themes@3.13.0': + resolution: {integrity: sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==} + + '@shikijs/transformers@3.13.0': + resolution: {integrity: sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA==} + + '@shikijs/types@3.13.0': + resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-vue@6.0.1': + resolution: {integrity: sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.22': + resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} + + '@vue/compiler-dom@3.5.22': + resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} + + '@vue/compiler-sfc@3.5.22': + resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==} + + '@vue/compiler-ssr@3.5.22': + resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + + '@vue/devtools-api@8.0.3': + resolution: {integrity: sha512-YxZE7xNvvfq5XmjJh1ml+CzVNrRjuZYCuT5Xjj0u9RlXU7za/MRuZDUXcKfp0j7IvYkDut49vlKqbiQ1xhXP2w==} + + '@vue/devtools-kit@8.0.3': + resolution: {integrity: sha512-UF4YUOVGdfzXLCv5pMg2DxocB8dvXz278fpgEE+nJ/DRALQGAva7sj9ton0VWZ9hmXw+SV8yKMrxP2MpMhq9Wg==} + + '@vue/devtools-shared@8.0.3': + resolution: {integrity: sha512-s/QNll7TlpbADFZrPVsaUNPCOF8NvQgtgmmB7Tip6pLf/HcOvBTly0lfLQ0Eylu9FQ4OqBhFpLyBgwykiSf8zw==} + + '@vue/reactivity@3.5.22': + resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==} + + '@vue/runtime-core@3.5.22': + resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==} + + '@vue/runtime-dom@3.5.22': + resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==} + + '@vue/server-renderer@3.5.22': + resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==} + peerDependencies: + vue: 3.5.22 + + '@vue/shared@3.5.22': + resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} + + '@vueuse/core@13.9.0': + resolution: {integrity: sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/integrations@13.9.0': + resolution: {integrity: sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 || ^8 + vue: ^3.5.0 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@13.9.0': + resolution: {integrity: sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==} + + '@vueuse/shared@13.9.0': + resolution: {integrity: sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==} + peerDependencies: + vue: ^3.5.0 + + birpc@2.6.1: + resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + engines: {node: '>=18'} + hasBin: true + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + focus-trap@7.6.5: + resolution: {integrity: sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + giscus@1.6.0: + resolution: {integrity: sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + is-what@4.1.16: + resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} + engines: {node: '>=12.13'} + + lit-element@4.2.1: + resolution: {integrity: sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==} + + lit-html@3.3.1: + resolution: {integrity: sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==} + + lit@3.3.1: + resolution: {integrity: sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==} + + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + minisearch@7.2.0: + resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.3: + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} + + perfect-debounce@2.0.0: + resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + shiki@3.13.0: + resolution: {integrity: sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + superjson@2.2.2: + resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} + engines: {node: '>=16'} + + tabbable@6.2.0: + resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@7.1.11: + resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitepress-plugin-comment-with-giscus@1.1.15: + resolution: {integrity: sha512-1DJjgN+7SYvn5ZkjuSXPmz7nlqfcrh4qCGGviiZghA2ELXnaO2m9WY7m+RisPSaqCn90xqe0JbO2T4NMq8iUBg==} + + vitepress@2.0.0-alpha.12: + resolution: {integrity: sha512-yZwCwRRepcpN5QeAhwSnEJxS3I6zJcVixqL1dnm6km4cnriLpQyy2sXQDsE5Ti3pxGPbhU51nTMwI+XC1KNnJg==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + oxc-minify: ^0.82.1 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + oxc-minify: + optional: true + postcss: + optional: true + + vue@3.5.22: + resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@docsearch/css@4.2.0': {} + + '@docsearch/js@4.2.0': {} + + '@esbuild/aix-ppc64@0.25.11': + optional: true + + '@esbuild/android-arm64@0.25.11': + optional: true + + '@esbuild/android-arm@0.25.11': + optional: true + + '@esbuild/android-x64@0.25.11': + optional: true + + '@esbuild/darwin-arm64@0.25.11': + optional: true + + '@esbuild/darwin-x64@0.25.11': + optional: true + + '@esbuild/freebsd-arm64@0.25.11': + optional: true + + '@esbuild/freebsd-x64@0.25.11': + optional: true + + '@esbuild/linux-arm64@0.25.11': + optional: true + + '@esbuild/linux-arm@0.25.11': + optional: true + + '@esbuild/linux-ia32@0.25.11': + optional: true + + '@esbuild/linux-loong64@0.25.11': + optional: true + + '@esbuild/linux-mips64el@0.25.11': + optional: true + + '@esbuild/linux-ppc64@0.25.11': + optional: true + + '@esbuild/linux-riscv64@0.25.11': + optional: true + + '@esbuild/linux-s390x@0.25.11': + optional: true + + '@esbuild/linux-x64@0.25.11': + optional: true + + '@esbuild/netbsd-arm64@0.25.11': + optional: true + + '@esbuild/netbsd-x64@0.25.11': + optional: true + + '@esbuild/openbsd-arm64@0.25.11': + optional: true + + '@esbuild/openbsd-x64@0.25.11': + optional: true + + '@esbuild/openharmony-arm64@0.25.11': + optional: true + + '@esbuild/sunos-x64@0.25.11': + optional: true + + '@esbuild/win32-arm64@0.25.11': + optional: true + + '@esbuild/win32-ia32@0.25.11': + optional: true + + '@esbuild/win32-x64@0.25.11': + optional: true + + '@giscus/vue@2.4.0(vue@3.5.22)': + dependencies: + giscus: 1.6.0 + vue: 3.5.22 + + '@iconify-json/simple-icons@1.2.55': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@lit-labs/ssr-dom-shim@1.4.0': {} + + '@lit/reactive-element@2.1.1': + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + + '@rolldown/pluginutils@1.0.0-beta.29': {} + + '@rollup/rollup-android-arm-eabi@4.52.5': + optional: true + + '@rollup/rollup-android-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.5': + optional: true + + '@rollup/rollup-darwin-x64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.5': + optional: true + + '@shikijs/core@3.13.0': + dependencies: + '@shikijs/types': 3.13.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.13.0': + dependencies: + '@shikijs/types': 3.13.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.3 + + '@shikijs/engine-oniguruma@3.13.0': + dependencies: + '@shikijs/types': 3.13.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.13.0': + dependencies: + '@shikijs/types': 3.13.0 + + '@shikijs/themes@3.13.0': + dependencies: + '@shikijs/types': 3.13.0 + + '@shikijs/transformers@3.13.0': + dependencies: + '@shikijs/core': 3.13.0 + '@shikijs/types': 3.13.0 + + '@shikijs/types@3.13.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + + '@types/trusted-types@2.0.7': {} + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-vue@6.0.1(vite@7.1.11)(vue@3.5.22)': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.29 + vite: 7.1.11 + vue: 3.5.22 + + '@vue/compiler-core@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/shared': 3.5.22 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.22': + dependencies: + '@vue/compiler-core': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/compiler-sfc@3.5.22': + dependencies: + '@babel/parser': 7.28.4 + '@vue/compiler-core': 3.5.22 + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + estree-walker: 2.0.2 + magic-string: 0.30.19 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.22': + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/devtools-api@8.0.3': + dependencies: + '@vue/devtools-kit': 8.0.3 + + '@vue/devtools-kit@8.0.3': + dependencies: + '@vue/devtools-shared': 8.0.3 + birpc: 2.6.1 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 2.0.0 + speakingurl: 14.0.1 + superjson: 2.2.2 + + '@vue/devtools-shared@8.0.3': + dependencies: + rfdc: 1.4.1 + + '@vue/reactivity@3.5.22': + dependencies: + '@vue/shared': 3.5.22 + + '@vue/runtime-core@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/shared': 3.5.22 + + '@vue/runtime-dom@3.5.22': + dependencies: + '@vue/reactivity': 3.5.22 + '@vue/runtime-core': 3.5.22 + '@vue/shared': 3.5.22 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.22(vue@3.5.22)': + dependencies: + '@vue/compiler-ssr': 3.5.22 + '@vue/shared': 3.5.22 + vue: 3.5.22 + + '@vue/shared@3.5.22': {} + + '@vueuse/core@13.9.0(vue@3.5.22)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 13.9.0 + '@vueuse/shared': 13.9.0(vue@3.5.22) + vue: 3.5.22 + + '@vueuse/integrations@13.9.0(focus-trap@7.6.5)(vue@3.5.22)': + dependencies: + '@vueuse/core': 13.9.0(vue@3.5.22) + '@vueuse/shared': 13.9.0(vue@3.5.22) + vue: 3.5.22 + optionalDependencies: + focus-trap: 7.6.5 + + '@vueuse/metadata@13.9.0': {} + + '@vueuse/shared@13.9.0(vue@3.5.22)': + dependencies: + vue: 3.5.22 + + birpc@2.6.1: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + comma-separated-tokens@2.0.3: {} + + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + + csstype@3.1.3: {} + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + entities@4.5.0: {} + + esbuild@0.25.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + focus-trap@7.6.5: + dependencies: + tabbable: 6.2.0 + + fsevents@2.3.3: + optional: true + + giscus@1.6.0: + dependencies: + lit: 3.3.1 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hookable@5.5.3: {} + + html-void-elements@3.0.0: {} + + is-what@4.1.16: {} + + lit-element@4.2.1: + dependencies: + '@lit-labs/ssr-dom-shim': 1.4.0 + '@lit/reactive-element': 2.1.1 + lit-html: 3.3.1 + + lit-html@3.3.1: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.1: + dependencies: + '@lit/reactive-element': 2.1.1 + lit-element: 4.2.1 + lit-html: 3.3.1 + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mark.js@8.11.1: {} + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + minisearch@7.2.0: {} + + mitt@3.0.1: {} + + nanoid@3.3.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.0.1 + regex-recursion: 6.0.2 + + perfect-debounce@2.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + property-information@7.1.0: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + rfdc@1.4.1: {} + + rollup@4.52.5: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 + fsevents: 2.3.3 + + shiki@3.13.0: + dependencies: + '@shikijs/core': 3.13.0 + '@shikijs/engine-javascript': 3.13.0 + '@shikijs/engine-oniguruma': 3.13.0 + '@shikijs/langs': 3.13.0 + '@shikijs/themes': 3.13.0 + '@shikijs/types': 3.13.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + speakingurl@14.0.1: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + superjson@2.2.2: + dependencies: + copy-anything: 3.0.5 + + tabbable@6.2.0: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + trim-lines@3.0.1: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@7.1.11: + dependencies: + esbuild: 0.25.11 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.5 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + vitepress-plugin-comment-with-giscus@1.1.15(vue@3.5.22): + dependencies: + '@giscus/vue': 2.4.0(vue@3.5.22) + transitivePeerDependencies: + - vue + + vitepress@2.0.0-alpha.12(postcss@8.5.6): + dependencies: + '@docsearch/css': 4.2.0 + '@docsearch/js': 4.2.0 + '@iconify-json/simple-icons': 1.2.55 + '@shikijs/core': 3.13.0 + '@shikijs/transformers': 3.13.0 + '@shikijs/types': 3.13.0 + '@types/markdown-it': 14.1.2 + '@vitejs/plugin-vue': 6.0.1(vite@7.1.11)(vue@3.5.22) + '@vue/devtools-api': 8.0.3 + '@vue/shared': 3.5.22 + '@vueuse/core': 13.9.0(vue@3.5.22) + '@vueuse/integrations': 13.9.0(focus-trap@7.6.5)(vue@3.5.22) + focus-trap: 7.6.5 + mark.js: 8.11.1 + minisearch: 7.2.0 + shiki: 3.13.0 + vite: 7.1.11 + vue: 3.5.22 + optionalDependencies: + postcss: 8.5.6 + transitivePeerDependencies: + - '@types/node' + - async-validator + - axios + - change-case + - drauu + - fuse.js + - idb-keyval + - jiti + - jwt-decode + - less + - lightningcss + - nprogress + - qrcode + - sass + - sass-embedded + - sortablejs + - stylus + - sugarss + - terser + - tsx + - typescript + - universal-cookie + - yaml + + vue@3.5.22: + dependencies: + '@vue/compiler-dom': 3.5.22 + '@vue/compiler-sfc': 3.5.22 + '@vue/runtime-dom': 3.5.22 + '@vue/server-renderer': 3.5.22(vue@3.5.22) + '@vue/shared': 3.5.22 + + zwitch@2.0.4: {} diff --git a/vercel.json b/vercel.json new file mode 100644 index 00000000..7ae9a3de --- /dev/null +++ b/vercel.json @@ -0,0 +1,5 @@ +{ + "github": { + "silent": true + } +}