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 index 9ce85270..e4dfe145 100644 --- a/.github/workflows/compress.yml +++ b/.github/workflows/compress.yml @@ -1,13 +1,9 @@ name: Compress on: - push: - branches: [main] - paths: - - "**.jpg" - - "**.jpeg" - - "**.png" - - "**.webp" + schedule: + - cron: "0 0 * * 3" + workflow_dispatch: jobs: compress: @@ -15,21 +11,26 @@ jobs: if: github.repository == 'doocs/source-code-hunter' steps: - name: Checkout Branch - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Compress Images - uses: calibreapp/image-actions@master + 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 "action@github.com" - git config --local user.name "GitHub Action" - git commit -m "[Automated] Optimize images" -a + 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 }} + 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/prettier.yml b/.github/workflows/prettier.yml deleted file mode 100644 index f84c25d0..00000000 --- a/.github/workflows/prettier.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Prettier - -on: - pull_request: - push: - branches: - - main - -jobs: - prettier: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - name: Prettify code - uses: creyD/prettier_action@v3.3 - with: - prettier_options: --write **/*.{md} - commit_message: "style: prettify code or document" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml deleted file mode 100644 index 0daf6c22..00000000 --- a/.github/workflows/sync.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Sync - -on: - push: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - if: github.repository == 'doocs/source-code-hunter' - steps: - - name: Sync to Gitee - uses: wearerequired/git-mirror-action@master - env: - SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} - with: - source-repo: git@github.com:doocs/source-code-hunter.git - destination-repo: git@gitee.com:Doocs/source-code-hunter.git - - - name: Build Gitee Pages - uses: yanglbme/gitee-pages-action@main - with: - gitee-username: yanglbme - gitee-password: ${{ secrets.GITEE_PASSWORD }} - gitee-repo: doocs/source-code-hunter - branch: main 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 e210a068..5dae38c9 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ 本项目主要用于记录框架及中间件源码的阅读经验、个人理解及解析,希望能够使阅读源码变成一件简单有趣,且有价值的事情,抽空更新中... (如果本项目对您有帮助,请 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 系列 @@ -25,8 +25,10 @@ - [将 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 @@ -58,6 +60,10 @@ - [面筋哥 IoC 容器的一天(上)]() +### Spring 整体脉络 + +- [16 张图解锁 Spring 的整体脉络](/docs/Spring/Spring整体脉络/16张图解锁Spring的整体脉络.md) + ### Spring 类解析 - [Spring 自定义标签解析](/docs/Spring/clazz/Spring-Custom-label-resolution.md) @@ -112,6 +118,18 @@ - [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 ### 基础支持层 @@ -138,14 +156,14 @@ - [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-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) -- [Mybats-GenericTokenParser](/docs/Mybatis/核心处理层/Mybats-GenericTokenParser.md) +- [Mybatis-GenericTokenParser](/docs/Mybatis/核心处理层/Mybatis-GenericTokenParser.md) ## Netty @@ -263,6 +281,21 @@ - [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) ### 基础类库 @@ -287,8 +320,6 @@ - [Executor 线程池组件 源码赏析](docs/JDK/concurrentCoding/Executor线程池组件.md) - [Lock 锁组件 源码赏析](docs/JDK/concurrentCoding/Lock锁组件.md) - [详解 AbstractQueuedSynchronizer 抽象类](docs/JDK/concurrentCoding/详解AbstractQueuedSynchronizer.md) -- [CountdownLatch 类 源码赏析](docs/JDK/concurrentCoding/CountdownLatch.md) -- [CyclicBarrier 类 源码赏析](docs/JDK/concurrentCoding/CyclicBarrier.md) - [Semaphore 类 源码赏析](docs/JDK/concurrentCoding/Semaphore.md) ## 学习心得 @@ -297,10 +328,6 @@ - [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md) -### 编码规范 - -- [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md) - ### 设计模式 - [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)]() @@ -313,6 +340,10 @@ --- +## 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 的一些优秀项目,欢迎各位开发者朋友持续保持关注。 @@ -339,25 +370,19 @@ GitHub 技术社区 [Doocs](https://github.com/doocs),致力于打造一个内 ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取 [doocs/advanced-java](https://github.com/doocs/advanced-java) 项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**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/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 2d92455d..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" @@ -14,7 +14,7 @@ dubbo 自己实现了一套 SPI 机制,并对 JDK 的 SPI 进行了改进。 下面我们看一下 Dubbo 的 SPI 扩展机制实现的结构目录。 -![avatar](../../../images/Dubbo/SPI组件目录结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/SPI组件目录结构.png) ### SPI 注解 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 330ff880..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" @@ -2,7 +2,7 @@ 首先从 GitHub 上 clone 下来 Dubbo 项目,我们根据其中各子项目的项目名,也能大概猜出来各个模块的作用。 -![avatar](../../../images/Dubbo/dubbo项目结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo项目结构.png) ### dubbo-common @@ -54,7 +54,7 @@ 其运行原理如下图所示。 -![avatar](../../../images/Dubbo/Dubbo工作原理图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Dubbo工作原理图.png) ### 工作原理 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 b585447c..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" @@ -15,13 +15,13 @@ 下面我们来看一下 集群模块的项目结构图,结合上文的描述,可以对其有更加深刻的理解。 -![avatar](../../../images/Dubbo/dubbo-cluster模块工程结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-cluster模块工程结构.png) ### 集群模块核心 API 源码解析 从上图应该也能看出其核心 API 在哪个包里。 -![avatar](../../../images/Dubbo/com.alibaba.dubbo.rpc.cluster包目录.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/com.alibaba.dubbo.rpc.cluster包目录.png) 各核心接口的源码如下。 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 2c13a04e..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" @@ -309,15 +309,15 @@ public class LeastActiveLoadBalance extends AbstractLoadBalance { 大致效果如下图所示(引用一下官网的图)。每个缓存节点在圆环上占据一个位置,如果缓存项 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项,这里有两个概念不要弄混,缓存节点就好比 dubbo 中的服务提供者,会有很多的服务提供者,而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中,也就是调用 cache-4 这个服务提供者。 -![avatar](../../../images/Dubbo/一致性hash算法1.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/一致性hash算法1.png) 但 hash 一致性算法 并不能够保证 负载的平衡性,就拿上面的例子来看,cache-3 挂掉了,那该节点下的所有缓存项都要存储到 cache-4 节点中,这就导致 hash 值低的一直往高的存储,会面临一个不平衡的现象,见下图: -![avatar](../../../images/Dubbo/一致性hash算法2.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/一致性hash算法2.png) 可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入 “虚拟节点”,“虚拟节点” 是实际节点在 hash 空间的复制品,“虚拟节点” 在 hash 空间 中以 hash 值 排列,如下图。 -![avatar](../../../images/Dubbo/一致性hash算法3.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/一致性hash算法3.png) 可以看到各个节点都被均匀分布在圆环上,且一个服务提供者有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。 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 203e3758..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" @@ -2,23 +2,23 @@ 服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者 Provider 会往注册中心注册服务,而消费者 Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer 以及 Registry 之间的依赖关系 如下图所示。 -![avatar](../../../images/Dubbo/Dubbo工作原理图.png) +![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) +![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,其中用到了工厂方法模式,不同的工厂类用于获取不同类型的实例。其类图结构如下。 -![avatar](../../../images/Dubbo/RegistryFactory组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/RegistryFactory组件类图.png) ## 源码详解 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 15095635..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" @@ -2,7 +2,7 @@ Dubbo 的注册中心虽然提供了多种实现,但生产上的事实标准 由于 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 不同。 @@ -10,7 +10,7 @@ zookeeper 以斜杠来分割每一层的 znode 节点,比如第一层根节点 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 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 fcf160db..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" @@ -2,13 +2,13 @@ 服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分,前面我们分析了有关注册中心的源码,也就是服务管理,接下来要分析的就是跟服务通信有关的源码,也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能,而在对 NIO 框架选型上,dubbo 交由用户选择,它集成了 mina、netty、grizzly 等各类 NIO 框架来搭建 NIO 服务器和客户端,并且利用 dubbo 的 SPI 扩展机制可以让用户自定义选择。dubbo-remoting 的工程结构如下。 -![在这里插入图片描述](../../../images/Dubbo/dubbo-remoting的工程结构.png) +![在这里插入图片描述](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) +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo-remoting-api的项目结构.png) dubbo-remoting-api 定义了远程通信模块最核心的 API,对于 dubbo-remoting-api 的解读会分为如下五个部分,其中第五部分会在本文介绍。 @@ -20,7 +20,7 @@ dubbo-remoting-api 定义了远程通信模块最核心的 API,对于 dubbo-re 结合 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 的核心。 diff --git a/docs/JDK/basic/String.md b/docs/JDK/basic/String.md index 18c8ce3e..35c85636 100644 --- a/docs/JDK/basic/String.md +++ b/docs/JDK/basic/String.md @@ -4,7 +4,7 @@ String 的源码大家应该都能看懂,这里就不一一分析咯,重点 public final class String implements java.io.Serializable, Comparable, CharSequence { - /** 保存String的字节数组 */ + /** 保存String的字符数组 */ private final char value[]; /** 缓存这个String的hash值 */ diff --git a/docs/JDK/basic/Thread.md b/docs/JDK/basic/Thread.md index 4fe798f7..30184d61 100644 --- a/docs/JDK/basic/Thread.md +++ b/docs/JDK/basic/Thread.md @@ -319,4 +319,4 @@ public class Thread implements Runnable { 之前一直对线程状态 及 状态切换的概念模糊不清,现在通过源码中对线程状态的定义,我们可以画张图来重新回顾一下,以使我们对其有更加深刻的理解。 -![avatar](../../../images/JDK1.8/ThreadStatusChange.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/ThreadStatusChange.png) diff --git a/docs/JDK/basic/ThreadLocal.md b/docs/JDK/basic/ThreadLocal.md index a510d7d3..871a5ee1 100644 --- a/docs/JDK/basic/ThreadLocal.md +++ b/docs/JDK/basic/ThreadLocal.md @@ -260,7 +260,7 @@ 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 的使用注意事项: diff --git a/docs/JDK/collection/ArrayList.md b/docs/JDK/collection/ArrayList.md index b3787c53..7d866af9 100644 --- a/docs/JDK/collection/ArrayList.md +++ b/docs/JDK/collection/ArrayList.md @@ -1 +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/concurrentCoding/CountdownLatch.md b/docs/JDK/concurrentCoding/CountdownLatch.md deleted file mode 100644 index b3787c53..00000000 --- a/docs/JDK/concurrentCoding/CountdownLatch.md +++ /dev/null @@ -1 +0,0 @@ -努力编写中... diff --git a/docs/JDK/concurrentCoding/CyclicBarrier.md b/docs/JDK/concurrentCoding/CyclicBarrier.md deleted file mode 100644 index b3787c53..00000000 --- a/docs/JDK/concurrentCoding/CyclicBarrier.md +++ /dev/null @@ -1 +0,0 @@ -努力编写中... diff --git "a/docs/JDK/concurrentCoding/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" index 8091fcf7..7f8e251c 100644 --- "a/docs/JDK/concurrentCoding/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" @@ -2,7 +2,7 @@ 看源码之前,先了解一下该组件 最主要的几个 接口、抽象类和实现类的结构关系。 -![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 则对线程池进行了具体而复杂的实现。 @@ -218,7 +218,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService { ThreadPoolExecutor 中的 execute()方法 执行 Runnable 任务 的流程逻辑可以用下图表示。 -![avatar](../../../images/ConcurrentProgramming/线程池流程.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/ConcurrentProgramming/线程池流程.png) ### 工具类 Executors 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" index 53df1e31..cbf1e7eb 100644 --- "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" @@ -2,4 +2,4 @@ 根据功能,主要划分了六个部分,其中比较重要的是:线程池及其相关类、并发容器、AQS 与锁与同步工具类、原子类。图可能整理的不够细致,但看着这些类,回想一下其中的源码实现,感觉能侃一天。 -![avatar](../../../images/JDK1.8/JUC全量UML地图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/JUC全量UML地图.png) diff --git "a/docs/JDK/concurrentCoding/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" index 7ad642c4..91440629 100644 --- "a/docs/JDK/concurrentCoding/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" @@ -2,11 +2,11 @@ J.U.C 的锁组件中 类相对较少,从 JDK 相应的包中也能看出来,下图标记了其中最主要的几个接口和类,也是本文要分析的重点。 -![avatar](../../../images/JDK1.8/JUC的locks包.png) +![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 组件 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 ba0dbcdf..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" @@ -608,7 +608,7 @@ 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 种线程池 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 4cebde21..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" @@ -514,7 +514,7 @@ public class SmartTransformerFactoryImpl extends SAXTransformerFactory { 该模式主要用于将复杂对象的构建过程分解成一个个简单的步骤,或者分摊到多个类中进行构建,保证构建过程层次清晰,代码不会过分臃肿,屏蔽掉了复杂对象内部的具体构建细节,其类图结构如下所示。 -![avatar](../../../images/DesignPattern/建造者模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/建造者模式类图.png) 该模式的主要角色如下: 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 60a205b4..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" @@ -829,7 +829,7 @@ class PooledConnection implements InvocationHandler { 装饰器模式能够帮助我们解决上述问题,装饰器可以动态地为对象添加功能,它是基于组合的方式实现该功能的。在实践中,我们应该尽量使用组合的方式来扩展系统的功能,而非使用继承的方式。通过装饰器模式的介绍,可以帮助读者更好地理解设计模式中常见的一句话:组合优于继承。下面先来看一下装饰器模式的类图,及其核心角色。 -![avatar](../../../images/DesignPattern/装饰器模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/装饰器模式类图.png) - Component (组件):组件接口定义了全部 “组件实现类” 以及所有 “装饰器实现” 的行为。 - ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。 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 b0aa50d5..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" @@ -8,7 +8,7 @@ 去年看了蛮多源码,发现 框架的开发者在实际使用设计模式时,大都会根据实际情况 使用其变体,老老实实按照书上的类图及定义去设计代码的比较少。不过我们依然还是先看一下书上的定义,然后比较一下理论与实践的一些差别吧。策略模式的类图及定义如下。 -![avatar](../../../images/DesignPattern/策略模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/策略模式类图.png) 定义一系列算法,封装每个算法 并使它们可以互换。该模式的主要角色如下: @@ -967,7 +967,7 @@ public class ArrayList extends AbstractList 这个模式也是平时很少使用的,所以就简单介绍一下,然后结合 JDK 中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下: -![avatar](../../../images/DesignPattern/观察者模式类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/观察者模式类图.png) - Subject 主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的; - Observer 观察者:其注册需要 Subject 的 registerObserver()方法。 @@ -1102,12 +1102,12 @@ public class Observable { 在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 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) +![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 中。 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 2200df33..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,888 +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,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中用户参数进行相应修改。 - -- 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。 - 说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。 - -- 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。 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 a3f31434..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" @@ -18,7 +18,7 @@ 从 IoC 的各顶层接口到中间一层一层的抽象类,再到最后的实现类,这一整套体系的设计和实现,对自己在日常工作中设计某些功能的接口、抽象类和具体实现,都带来了很有价值的参考,设计模式和巧妙的编码技巧也渐渐变得触手可及。比如,设计一个 VO 字段校验功能时,会先定义一个顶层接口,抽象出公共方法,抽象类中有做必输项字段非空校验的,在其中利用模板方法模式对公共功能做具体实现,特性化功能写成抽象方法交由各子类具体实现即可。 -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 439a75b8..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" @@ -576,4 +576,4 @@ TypeHandlerRegistry 其实就是一个容器,前面注册了一堆东西,也 } ``` -除了 Mabatis 本身自带的 TypeHandler 实现,我们还可以添加自定义的 TypeHandler 实现类,在配置文件 mybatis-config.xml 中的 <typeHandler> 标签下配置好 自定义 TypeHandler,Mybatis 就会在初始化时解析该标签内容,完成 自定义 TypeHandler 的注册。 +除了 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 479d12e0..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" @@ -357,7 +357,7 @@ public class PoolState { PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。 PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection 对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图: -![avatar](../../../images/mybatis/数据库连接池流程图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/数据库连接池流程图.png) ```java public class PooledDataSource implements DataSource { diff --git "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/4\343\200\201\347\274\223\345\255\230\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/4\343\200\201\347\274\223\345\255\230\346\250\241\345\235\227.md" index b3488393..7d78de39 100644 --- "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/4\343\200\201\347\274\223\345\255\230\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/4\343\200\201\347\274\223\345\255\230\346\250\241\345\235\227.md" @@ -51,7 +51,7 @@ public interface Cache { 如下图所示,Cache 接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口 的基本实现。 -![avatar](../../../images/mybatis/Cache组件.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/Cache组件.png) ### 1.1 PerpetualCache diff --git "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/Mybatis-Reflector.md" "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/Mybatis-Reflector.md" index 834ae700..e52b8495 100644 --- "a/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/Mybatis-Reflector.md" +++ "b/docs/Mybatis/\345\237\272\347\241\200\346\224\257\346\214\201\345\261\202/Mybatis-Reflector.md" @@ -151,13 +151,13 @@ class HfReflectorTest { - 准备工作完成了开始进行 debug , 在`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`这个方法上打上断点 - ![1575890354400](../../../images/mybatis/1575890354400.png) + ![1575890354400](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/1575890354400.png) 观察`constructors`属性存在两个方法,这两个方法就是我在`People`类中的构造方法. 根据语法内容我们应该对`parameterTypes`属性进行查看 - ![1575890475839](../../../images/mybatis/1575890475839.png) + ![1575890475839](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/1575890475839.png) 可以发现空参构造的`parameterTypes`长度是 0.因此可以确认`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`方法获取了空参构造 @@ -213,7 +213,7 @@ class HfReflectorTest { Map uniqueMethods = new HashMap<>(); Class currentClass = clazz; while (currentClass != null && currentClass != Object.class) { - // getDeclaredMethods 获取 public ,private , protcted 方法 + // getDeclaredMethods 获取 public ,private , protected 方法 addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); // we also need to look for interface methods - @@ -283,17 +283,17 @@ class HfReflectorTest { - 照旧我们进行 debug 当前方法为`toString`方法 - ![1575891988804](../../../images/mybatis//1575891988804.png) + ![1575891988804](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575891988804.png) 从返回结果可以看到`sb.toString`返回的是: `返回值类型#方法名` - ![1575892046692](../../../images/mybatis//1575892046692.png) + ![1575892046692](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892046692.png) 上图返回结果为`void#setName:java.lang.String` 命名规则:`返回值类型#方法名称:参数列表` 回过头看看`uniqueMethods`里面是什么 - ![1575892167982](../../../images/mybatis//1575892167982.png) + ![1575892167982](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892167982.png) 方法签名:方法 @@ -321,11 +321,11 @@ class HfReflectorTest { 目标明确了就直接在 - ![1575892414120](../../../images/mybatis//1575892414120.png) + ![1575892414120](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892414120.png) 这里打断点了 - ![1575892511471](../../../images/mybatis//1575892511471.png) + ![1575892511471](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892511471.png) 在进入循环之前回率先加载本类的所有可见方法 @@ -338,15 +338,15 @@ class HfReflectorTest { 接下来断点继续往下走 - ![1575892645405](../../../images/mybatis//1575892645405.png) + ![1575892645405](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892645405.png) 走到这一步我们来看看`currentClass.getSuperclass()`是不是上一级的类 - ![1575892687076](../../../images/mybatis//1575892687076.png) + ![1575892687076](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892687076.png) 通过断点可见这个`currentClass`现在是`People`类,根据之前所说的最终`uniqueMethods`应该存在父类的方法 - ![1575892763661](../../../images/mybatis//1575892763661.png) + ![1575892763661](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575892763661.png) 可以看到父类的方法也都存在了 @@ -423,4 +423,4 @@ class HfReflectorTest { - 下图为一个类的解析结果 -![1575894218362](../../../images/mybatis/1575894218362.png) +![1575894218362](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/1575894218362.png) diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/5\343\200\201Executor\347\273\204\344\273\266.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/5\343\200\201Executor\347\273\204\344\273\266.md" index 684da3c2..e0f4f378 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/5\343\200\201Executor\347\273\204\344\273\266.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/5\343\200\201Executor\347\273\204\344\273\266.md" @@ -85,7 +85,7 @@ MyBatis 提供的缓存功能,分别为一级缓存和二级缓存。BaseExecu 为了避免上述问题,MyBatis 会在 Executor 对象中建立一个简单的一级缓存,将每次查询的结果集缓存起来。在执行查询操作时,会先查询一级缓存,如果存在完全一样的查询情况,则直接从一级缓存中取出相应的结果对象并返回给用户,减少数据库访问次数,从而减小了数据库的压力。 -一级缓存的生命周期与 SqlSession 相同,其实也就与 SqISession 中封装的 Executor 对象的生命周期相同。当调用 Executor 对象的 close()方法时(断开连接),该 Executor 对象对应的一级缓存就会被废弃掉。一级缓存中对象的存活时间受很多方面的影响,例如,在调用 Executor 的 update()方法时,也会先请空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。 +一级缓存的生命周期与 SqlSession 相同,其实也就与 SqISession 中封装的 Executor 对象的生命周期相同。当调用 Executor 对象的 close()方法时(断开连接),该 Executor 对象对应的一级缓存就会被废弃掉。一级缓存中对象的存活时间受很多方面的影响,例如,在调用 Executor 的 update()方法时,也会先清空一级缓存。一级缓存默认是开启的,一般情况下,不需要用户进行特殊配置。 ### 1.2 一级缓存的管理 @@ -375,7 +375,7 @@ public class SimpleExecutor extends BaseExecutor { ReuseExecutor 提供了 Statement 复用的功能,ReuseExecutor 中通过 statementMap 字段缓存使用过的 Statement 对象,key 是 SQL 语句,value 是 SQL 对应的 Statement 对象。 -ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与 SimpleExecutor 中对应方法的实现一样,区别在于其中调用的 prepareStatement()方法,SimpleExecutor 每次都会通过 JDBC 的 Connection 对象创建新的 Statement 对象,而 ReuseExecutor 则会先尝试重用 StaternentMap 中缓存的 Statement 对象。 +ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与 SimpleExecutor 中对应方法的实现一样,区别在于其中调用的 prepareStatement()方法,SimpleExecutor 每次都会通过 JDBC 的 Connection 对象创建新的 Statement 对象,而 ReuseExecutor 则会先尝试重用 StatementMap 中缓存的 Statement 对象。 ```java // 本map用于缓存使用过的Statement,以提升本框架的性能 @@ -438,7 +438,7 @@ ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的实现与 SimpleE (3)缓存预编译 -预编译语句被 DB 的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要重复编译,只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。 +预编译语句被 DB 的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要重复编译,只要将参数直接传入编译过的语句执行代码中(相当于一个函数)就会得到执行。并不是所有预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。 (4) 预编译的实现方法 diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/6\343\200\201SqlSession\347\273\204\344\273\266.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/6\343\200\201SqlSession\347\273\204\344\273\266.md" index 74a23d5c..fcbbd5ae 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/6\343\200\201SqlSession\347\273\204\344\273\266.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/6\343\200\201SqlSession\347\273\204\344\273\266.md" @@ -86,7 +86,7 @@ public interface SqlSession extends Closeable { DefaultSqlSession 是单独使用 MyBatis 进行开发时,最常用的 SqISession 接口实现。其实现了 SqISession 接口中定义的方法,及各方法的重载。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之间的调用关系如下图,殊途同归,它们最终都会调用 Executor 的 query()方法。 -![avatar](../../../images/mybatis/DefaultSqlSession方法调用栈.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/DefaultSqlSession方法调用栈.png) 上述重载方法最终都是通过调用 Executor 的 query(MappedStatement, Object, RowBounds,ResultHandler)方法实现数据库查询操作的,但各自对结果对象进行了相应的调整,例如:selectOne()方法是从结果对象集合中获取了第一个元素返回;selectMap()方法会将 List 类型的结果集 转换成 Map 类型集合返回;select()方法是将结果集交由用户指定的 ResultHandler 对象处理,且没有返回值;selectList()方法则是直接返回结果对象集合。 DefaultSqlSession 的 insert()方法、update()方法、delete()方法也有多个重载,它们最后都是通过调用 DefaultSqlSession 的 update(String, Object)方法实现的,该重载首先会将 dirty 字段置为 true,然后再通过 Executor 的 update()方法完成数据库修改操作。 diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DataSource.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DataSource.md" index 91ff60a0..7da616b1 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DataSource.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DataSource.md" @@ -31,7 +31,7 @@ public interface DataSourceFactory { 类图如下 -![image-20191223081023730](../../../images/mybatis/image-20191223081023730.png) +![image-20191223081023730](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191223081023730.png) - `setProperties`会将下列标签放入`datasource`中 @@ -133,7 +133,7 @@ public class JndiDataSourceFactory implements DataSourceFactory { ## PooledDataSource ```java - protected int poolMaximumActiveConnections = 10; + protected int poolMaximumActiveConnections = 10; protected int poolMaximumIdleConnections = 5; protected int poolMaximumCheckoutTime = 20000; protected int poolTimeToWait = 20000; @@ -155,7 +155,7 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory { } - // 初始化 + // 初始化 public PooledDataSource() { dataSource = new UnpooledDataSource(); } @@ -321,8 +321,8 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory { 从类图上或者代码中我们可以发现`PooledDataSourceFactory`是继承`UnpooledDataSourceFactory`那么方法应该也是`UnpooledDataSourceFactory`的。看看设置属性方法 -![image-20191223083610214](../../../images/mybatis/image-20191223083610214.png) +![image-20191223083610214](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191223083610214.png) 方法直接走完 -![image-20191223083732972](../../../images/mybatis/image-20191223083732972.png) +![image-20191223083732972](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191223083732972.png) diff --git "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DyanmicSqlSourcce.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DynamicSqlSource.md" similarity index 86% rename from "docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DyanmicSqlSourcce.md" rename to "docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DynamicSqlSource.md" index e0926044..141ce92e 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DyanmicSqlSourcce.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DynamicSqlSource.md" @@ -1,4 +1,4 @@ -# Mybatis DyanmicSqlSourcce +# Mybatis DynamicSqlSource - Author: [HuiFer](https://github.com/huifer) - 源码阅读工程: [SourceHot-Mybatis](https://github.com/SourceHot/mybatis-read.git) @@ -19,9 +19,9 @@ ``` -![image-20191219151247240](../../../images/mybatis/image-20191219151247240.png) +![image-20191219151247240](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219151247240.png) -![image-20191219151408597](../../../images/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 { @@ -48,7 +48,7 @@ public class MixedSqlNode implements SqlNode { `org.apache.ibatis.scripting.xmltags.IfSqlNode#apply` -![image-20191219152254274](../../../images/mybatis/image-20191219152254274.png) +![image-20191219152254274](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219152254274.png) ```java /** @@ -85,7 +85,7 @@ public class StaticTextSqlNode implements SqlNode { - 解析`trim`标签 -![image-20191219152502960](../../../images/mybatis/image-20191219152502960.png) +![image-20191219152502960](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219152502960.png) - 在解析`trim`的时候会往下解析下级标签 @@ -100,7 +100,7 @@ public class StaticTextSqlNode implements SqlNode { } ``` -![image-20191219152655746](../../../images/mybatis/image-20191219152655746.png) +![image-20191219152655746](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219152655746.png) ```java @Override @@ -154,17 +154,17 @@ public class StaticTextSqlNode implements SqlNode { ``` -![image-20191219153341466](../../../images/mybatis/image-20191219153341466.png) +![image-20191219153341466](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219153341466.png) 存在返回`true` 执行完成就得到了一个 sql -![image-20191219153553127](../../../images/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](../../../images/mybatis/image-20191219155129772.png) +![image-20191219155129772](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219155129772.png) - 发送 sql`org.apache.ibatis.executor.SimpleExecutor#doQuery` @@ -250,14 +250,14 @@ public class StaticTextSqlNode implements SqlNode { - `org.apache.ibatis.executor.BaseExecutor#doQuery` - `org.apache.ibatis.executor.SimpleExecutor#doQuery` -![image-20191219160832704](../../../images/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()); // 参数放入 @@ -267,7 +267,7 @@ public class StaticTextSqlNode implements SqlNode { ``` -![image-20191219160908212](../../../images/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` @@ -317,7 +317,7 @@ public class StaticTextSqlNode implements SqlNode { - 接下来需要考虑的问题是如何将`?`换成我们的参数`2` - ![image-20191219161555793](../../../images/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` @@ -326,11 +326,11 @@ public class StaticTextSqlNode implements SqlNode { - `org.apache.ibatis.executor.parameter.ParameterHandler` - `org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters` -![image-20191219162258040](../../../images/mybatis/image-20191219162258040.png) +![image-20191219162258040](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219162258040.png) 这样就拿到了`value`的值 -![image-20191219162506920](../../../images/mybatis/image-20191219162506920.png) +![image-20191219162506920](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219162506920.png) 准备工作就绪了发送就可以了 @@ -357,11 +357,11 @@ public class StaticTextSqlNode implements SqlNode { - `org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets` - `org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets` -![image-20191219163628214](../../../images/mybatis/image-20191219163628214.png) +![image-20191219163628214](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219163628214.png) -![image-20191219163640968](../../../images/mybatis/image-20191219163640968.png) +![image-20191219163640968](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219163640968.png) -![image-20191219163957488](../../../images/mybatis/image-20191219163957488.png) +![image-20191219163957488](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219163957488.png) 处理后结果如上 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 cefeb9eb..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" @@ -175,4 +175,4 @@ public class GenericTokenParser { ``` -![image-20191219100446796](../../../images/mybatis/image-20191219100446796.png) +![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 e5491f52..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" @@ -190,7 +190,7 @@ 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`进行测试 @@ -198,4 +198,4 @@ 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-MethodSignature.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-MethodSignature.md" index 480a130b..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" @@ -90,7 +90,7 @@ } /** - * 是否uresultHandler + * 是否有 resultHandler * * @return */ 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 67ce32f2..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" @@ -6,7 +6,7 @@ 类图: -![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 { 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 bdbf3c86..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" @@ -172,7 +172,7 @@ public class ParamNameResolver { ``` 如果不写`@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); @@ -180,9 +180,9 @@ public class ParamNameResolver { - 写`@Param`返回 -![image-20191219083344439](../../../images/mybatis/image-20191219083344439.png) +![image-20191219083344439](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219083344439.png) -![image-20191219083354873](../../../images/mybatis/image-20191219083354873.png) +![image-20191219083354873](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219083354873.png) - `org.apache.ibatis.reflection.ParamNameResolver#getNamedParams` @@ -190,7 +190,7 @@ 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); @@ -198,6 +198,6 @@ public class ParamNameResolver { ​ 写上`@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) +![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 408c48ae..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" @@ -94,6 +94,6 @@ ``` -![image-20191218191512184](../../../images/mybatis/image-20191218191512184.png) +![image-20191218191512184](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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 ffa3fdb8..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" @@ -5,7 +5,7 @@ Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。 -![avatar](../../../images/Netty/Netty逻辑架构图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty逻辑架构图.png) ### 通信调度层 Reactor 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 e9b7c60a..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" @@ -114,7 +114,7 @@ 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,期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争,从性能角度看是最优的。 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 d8bf4f39..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" @@ -6,7 +6,7 @@ Linux 的内核将所有外部设备都看做一个文件来操作,对一个 在内核将数据准备好之前,系统调用会一直等待所有的套接字(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() 就会解除阻塞,并得到网络数据的结果。 @@ -19,7 +19,7 @@ BIO 中的阻塞,就是阻塞在 2 个地方: #### 2、非阻塞 IO 模型 -![avatar](../../../images/Netty/非阻塞IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/非阻塞IO模型.png) 每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。 @@ -31,24 +31,24 @@ BIO 中的阻塞,就是阻塞在 2 个地方: Linux 提供 select/poll,进程通过将一个或多个 fd 传递给 select 或 poll 系统 调用,阻塞发生在 select/poll 操作上。select/poll 可以帮我们侦测多个 fd 是否处于就绪状态,它们顺序扫描 fd 是否就绪,但支持的 fd 数量有限,因此它的使用也受到了一些制约。Linux 还提供了一个 epoll 系统调用,epoll 使用 基于事件驱动方式 代替 顺序扫描,因此性能更高,当有 fd 就绪时,立即回调函数 rollback。 -![avatar](../../../images/Netty/IO复用模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/IO复用模型.png) #### 4、信号驱动 IO 模型 首先开启套接口信号驱动 IO 功能,并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。 -![avatar](../../../images/Netty/信号驱动IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/信号驱动IO模型.png) #### 5、异步 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](../../../images/Netty/数据在客户端及服务器之间的整体IO流程.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/数据在客户端及服务器之间的整体IO流程.png) ## IO 多路复用技术 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" index 2e072726..89d63c29 100644 --- "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" @@ -1,12 +1,12 @@ Selector、SelectionKey 和 Channel 这三个组件构成了 Java nio 包的核心,也是 Reactor 模型在代码层面的体现。Selector 能让单线程同时处理多个客户端 Channel,非常适用于高并发,传输数据量较小的场景。要使用 Selector,首先要将对应的 Channel 及 IO 事件(读、写、连接)注册到 Selector,注册后会产生一个 SelectionKey 对象,用于关联 Selector 和 Channel,及后续的 IO 事件处理。这三者的关系如下图所示。 -![在这里插入图片描述](../../../images/Netty/Selector和SelectionKey和Channel关系图.png) +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Selector和SelectionKey和Channel关系图.png) 对 nio 编程不熟的同学可以搜索一些简单的 demo 跑一下,下面 我们直接进入源码,窥探一些 nio 的奥秘。 ### Selector -其实,不管是 Selector 还是 SelectionKey 的源码,其具体实现类都是依赖于底层操作系统的,这里我们只看一下抽象类 Selector 的源码,日后有事件,再找一些具体的实现类深入分析一下。 +其实,不管是 Selector 还是 SelectionKey 的源码,其具体实现类都是依赖于底层操作系统的,这里我们只看一下抽象类 Selector 的源码,日后有时间,再找一些具体的实现类深入分析一下。 ```java public abstract class Selector implements Closeable { @@ -160,7 +160,7 @@ public abstract class SelectionKey { 平时编码用的比较多的就是 SocketChannel 和 ServerSocketChannel,而将 Channel 与 Selecor 关联到一起的核心 API 则定义在它们的公共父类 SelectableChannel 中,整个 Channel 组件的核心类图如下所示。 -![在这里插入图片描述](../../../images/Netty/Channel组件.png) +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Channel组件.png) #### SelectableChannel 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 fe97f5cd..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" @@ -9,7 +9,7 @@ 通过下面的通信模型图可以发现,采用 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 虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。 @@ -23,7 +23,7 @@ 采用线程池和任务队列可以实现一种叫做 伪异步的 IO 通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task 对象 (该类实现了 java.lang.Runnable 接口),投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 -![avatar](../../../images/Netty/伪异步IO通信模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/伪异步IO通信模型.png) 伪异步 IO 通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。 @@ -105,14 +105,14 @@ Buffer 对象 包含了一些要写入或者要读出的数据。在 NIO 类库 缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit)等信息。最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了 ByteBuffer,还有其他的一些缓冲区,事实上,每一种 Java 基本类型(除了 boolean)都对应有一种与之对应的缓冲区,如:CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer 组件中主要类的类图如下所示。 -![avatar](../../../images/Netty/Buffer组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Buffer组件类图.png) 除了 ByteBuffer,每一个 Buffer 类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准 IO 操作 都使用 ByteBuffer,所以它在具有一般缓冲区的操作之外还提供了一些特有的操作,以方便网络读写。 **2、通道 Channel** Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream,要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX 网络编程模型 中,底层操作系统的通道都是全双工的,同时支持读写操作。Channel 组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。 -![avatar](../../../images/Netty/Channel组件类图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Channel组件类图.png) **3、多路复用器 Selector** 多路复用器 Selector 是 Java NIO 编程 的基础,熟练地掌握 Selector 对于 NIO 编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector 会不断地轮询 “注册在其上的 Channel”,如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO 操作。 @@ -121,7 +121,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann ### NIO 服务端序列图 -![avatar](../../../images/Netty/NIO服务端序列图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NIO服务端序列图.png) 下面,我们看一下 NIO 服务端 的主要创建过程。 @@ -224,7 +224,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann ### NIO 客户端序列图 -![avatar](../../../images/Netty/NIO客户端序列图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NIO客户端序列图.png) 1、打开 SocketChannel,绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。 @@ -356,7 +356,7 @@ NIO2.0 的异步套接字通道是真正的 异步非阻塞 IO,对应于 UNIX 对比之前,这里再澄清一下 “伪异步 IO” 的概念。伪异步 IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO 编程 没有流行之前,为了解决 Tomcat 通信线程同步 IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO 线程 和业务线程间的直接访问,这样业务线程就不会被 IO 线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO 线程 或者进行 IO 读写,这样也就不会被同步阻塞。 -![avatar](../../../images/Netty/四种IO模型的功能特性对比图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/四种IO模型的功能特性对比图.png) ## 选择 Netty 开发项目的理由 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 1ff0a007..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" @@ -20,7 +20,7 @@ Java 中将输入输出抽象称为流,就好像水管,将两个容器连接 在内核将数据准备好之前,系统调用会一直等待所有的套接字(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 方法。 @@ -35,7 +35,7 @@ BIO 中的阻塞,就是阻塞在 2 个地方: ##### 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 来说是较大的浪费,一般只有在特定的场景下才使用。 @@ -58,7 +58,7 @@ serverSocketChannel.configureBlocking(false); ##### 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,服务端的 server socket,服务端中用来和客户端通信的 socket),而在 IO 多路复用中,客户端与服务端通信需要的不是 socket,而是 3 个 channel,通过 channel 可以完成与 socket 同样的操作,channel 的底层还是使用的 socket 进行通信,但是多个 channel 只对应一个 socket(可能不只是一个,但是 socket 的数量一定少于 channel 数量),这样仅仅通过少量的 socket 就可以完成更多的连接,提高了 client 容量。 @@ -73,13 +73,13 @@ serverSocketChannel.configureBlocking(false); ##### 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](../../../images/Netty/异步IO模型.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/异步IO模型.png) Asynchronous IO 调用中是真正的无阻塞,其他 IO model 中多少会有点阻塞。程序发起 read 操作之后,立刻就可以开始去做其它的事。而在内核角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。 @@ -263,7 +263,7 @@ Selector(选择器)用于监听多个通道的事件(比如:连接打开 ![在这里插入图片描述](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) 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 130bfc81..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" @@ -257,4 +257,4 @@ ChannelHandler 负责对 I/O 事件 进行拦截处理,它可以选择性地 ChannelHandler 组件 的核心类及常用类的类图如下。 -![在这里插入图片描述](../../../images/Netty/ChannelHandler组件.png) +![在这里插入图片描述](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 83c45628..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" @@ -5,7 +5,7 @@ Netty 的 **Channel 组件 是 Netty 对网络操作的封装**,**如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty 框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外,Netty 并没有直接使用 java.nio 包 的 SocketChannel 和 ServerSocketChannel,而是**使用 NioSocketChannel 和 NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel 接口 的 API 开始分析,然后看一下其重要子类的源码实现。 为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。 -![在这里插入图片描述](../../../images/Netty/Netty的Channel组件.png) +![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty的Channel组件.png) #### Channel 接口 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" index 596f0b2b..d22cbc7e 100644 --- "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" @@ -2,8 +2,9 @@ ## Netty 中的 ByteBuf 为什么会发生内存泄漏 -在 Netty 中,ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 byte[]数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。 -在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 retain()方法被调用时,将会增加 refCnt 的计数,而其 release()方法被调用时将会减少其被引用数计数。 +在 Netty 中,ByetBuf 并不是只采用可达性分析来对 ByteBuf 底层的 `byte[]` 数组来进行垃圾回收,而同时采用引用计数法来进行回收,来保证堆外内存的准确时机的释放。 + +在每个 ByteBuf 中都维护着一个 refCnt 用来对 ByteBuf 的被引用数进行记录,当 ByteBuf 的 `retain()` 方法被调用时,将会增加 refCnt 的计数,而其 `release()` 方法被调用时将会减少其被引用数计数。 ```java private boolean release0(int decrement) { @@ -23,9 +24,11 @@ private boolean release0(int decrement) { } ``` -当调用了 ByteBuf 的 release()方法的时候,最后在上方的 release0()方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 deallocate()方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 deallocate()方法里会把该 ByteBuf 的 byte[]回收到底层内存池中,以确保 byte[]可以重复利用)。 -由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。 -当采用了池化的 ByteBuffer 的时候,比如 PooledHeapByteBuf 和 PooledDirectByteBuf,其 deallocate()方法一共主要分为两个步骤。 +当调用了 ByteBuf 的 `release()` 方法的时候,最后在上方的 `release0()` 方法中将会为 ByteBuf 的引用计数减一,当引用计数归于 0 的时候,将会调用 `deallocate()` 方法对其对应的底层存储数组进行释放(在池化的 ByteBuf 中,在 `deallocate()` 方法里会把该 ByteBuf 的 `byte[]` 回收到底层内存池中,以确保 `byte[]` 可以重复利用)。 + +由于 Netty 中的 ByteBuf 并不是随着申请之后会马上使其引用计数归 0 而进行释放,往往在这两个操作之间还有许多操作,如果在这其中如果发生异常抛出导致引用没有及时释放,在使用池化 ByetBuffer 的情况下内存泄漏的问题就会产生。 + +当采用了池化的 ByteBuffer 的时候,比如 PooledHeapByteBuf 和 PooledDirectByteBuf,其 `deallocate()` 方法一共主要分为两个步骤。 ```java @Override @@ -40,10 +43,10 @@ protected final void deallocate() { } ``` -- 将其底层的 byte[]通过 free()方法回收到内存池中等待下一次使用。 -- 通过 recycle()方法将其本身回收到对象池中等待下一次使用。 - 关键在第一步的内存回收到池中,如果其引用计数未能在 ByteBuf 对象被回收之前归 0,将会导致其底层占用 byte[]无法回收到内存池 PoolArena 中,导致该部分无法被重复利用,下一次将会申请新的内存进行操作,从而产生内存泄漏。 - 而非池化的 ByteBuffer 即使引用计数没有在对象被回收的时候被归 0,因为其使用的是单独一块 byte[]内存,因此也会随着 java 对象被回收使得底层 byte[]被释放(由 JDK 的 Cleaner 来保证)。 +- 将其底层的 `byte[]` 通过 `free()` 方法回收到内存池中等待下一次使用。 +- 通过 `recycle()` 方法将其本身回收到对象池中等待下一次使用。 + 关键在第一步的内存回收到池中,如果其引用计数未能在 ByteBuf 对象被回收之前归 0,将会导致其底层占用 `byte[]` 无法回收到内存池 PoolArena 中,导致该部分无法被重复利用,下一次将会申请新的内存进行操作,从而产生内存泄漏。 + 而非池化的 ByteBuffer 即使引用计数没有在对象被回收的时候被归 0,因为其使用的是单独一块 `byte[]` 内存,因此也会随着 java 对象被回收使得底层 `byte[]` 被释放(由 JDK 的 Cleaner 来保证)。 ## Netty 进行内存泄漏检测的原理 @@ -73,7 +76,7 @@ public boolean release(int decrement) { } ``` -在包装类中,如果该 ByteBuf 成功 deallocated 释放掉了其持有的 byte[]数组将会调用 DefaultResourceLeak 的 close()方法来已通知当前 ByteBuf 已经释放了其持有的内存。 +在包装类中,如果该 ByteBuf 成功 deallocated 释放掉了其持有的 byte[]数组将会调用 DefaultResourceLeak 的 `close()` 方法来已通知当前 ByteBuf 已经释放了其持有的内存。 正是这个虚引用使得该 DefaultResourceLeak 对象被回收的时候将会被放入到与这个虚引用所对应的 ReferenceQueue 中。 ```java @@ -104,6 +107,6 @@ if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) { } ``` -Netty 会在下一次 ByteBuf 的采样中通过 reportLeak()方法将 ReferenceQueue 中的 DefaultResourceLeak 取出并判断其对应的 ByteBuf 是否已经在其回收前调用过其 close()方法,如果没有,显然在池化 ByteBuf 的场景下内存泄漏已经产生,将会以 ERROR 日志的方式进行日志打印。 +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/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" index b88ce558..5e126c5b 100644 --- "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" @@ -21,7 +21,7 @@ # HashedWheelTimer 实现图示 -![HashedWheelTimer实现图示.png](../../../images/Netty/image_1595752125587.png) +![HashedWheelTimer实现图示.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595752125587.png) 大致有个理解就行,关于蓝色格子中的数字,其实就是剩余时钟轮数,这里听不懂也没关系,等后面看到源码解释就懂了~~(大概)~~。 @@ -50,7 +50,7 @@ public void handlerAdded(final ChannelHandlerContext ctx) { ### 继承关系、方法 -![继承关系&方法.png](../../../images/Netty/image_1595751597062.png) +![继承关系&方法.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595751597062.png) ### 构造函数、属性 @@ -425,25 +425,25 @@ PriorityQueue> scheduledTaskQueue() { 这里我就直接贴下网上大佬给出的解释: 如果使用最小堆实现的优先级队列: -![最小堆.png](../../../images/Netty/image_1595756711656.png) +![最小堆.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595756711656.png) - 大致意思就是你的任务如果插入到堆顶,时间复杂度为 O(log(n))。 如果使用链表(既然有说道,那就扩展下): -![链表.png](../../../images/Netty/image_1595756928493.png) +![链表.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595756928493.png) - 中间插入后的事件复杂度为 O(n) 单个时间轮: -![单个时间轮.png](../../../images/Netty/image_1595757035360.png) +![单个时间轮.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595757035360.png) - 复杂度可以降至 O(1)。 记录轮数的时间轮(其实就是文章开头的那个): -![记录轮数的时间轮.png](../../../images/Netty/image_1595757110003.png) +![记录轮数的时间轮.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595757110003.png) 层级时间轮: -![层级时间轮.png](../../../images/Netty/image_1595757328715.png) +![层级时间轮.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595757328715.png) - 时间复杂度是 O(n),n 是轮子的数量,除此之外还要计算一个轮子上的 bucket。 @@ -451,7 +451,7 @@ PriorityQueue> scheduledTaskQueue() { 根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 bucket 数就会变得非常多,从而导致内存占用持续升高(CPU 空转时间还是不变的,仅仅是内存需求变高了),如下图: -![image.png](../../../images/Netty/image_1595758329809.png) +![image.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/image_1595758329809.png) Netty 对于单个时间轮的优化方式就是记录下 remainingRounds,从而减少 bucket 过多的内存占用。 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" index 32ee939b..06d86a2b 100644 --- "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" @@ -11,12 +11,12 @@ - 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、 +- `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 大小的时候将会直接申请一段非池化的内存返回,并不会占用内存池中的内存空间。 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 b4e773d1..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,21 +1,21 @@ ## TCP 粘包/拆包 -熟悉 TCP 编程 的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP 底层 的 粘包/拆包机制。TCP 粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP 粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。 +熟悉 TCP 编程的都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑 TCP 底层 的 粘包/拆包机制。TCP 粘包/拆包问题,在功能测试时往往不会怎么出现,而一旦并发压力上来,或者发送大报文之后,就很容易出现 粘包 / 拆包问题。如果代码没有考虑,往往就会出现解码错位或者错误,导致程序不能正常工作。本篇博文,我们先简单了解 TCP 粘包/拆包 的基础知识,然后来看看 Netty 是如何解决这个问题的。 ### TCP 粘包/拆包问题说明 TCP 是个 “流” 协议,所谓流,就是没有界限的一串数据。TCP 底层 并不了解上层(如 HTTP 协议)业务数据的具体含义,它会根据 TCP 缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。我们可以通过下面的示例图,对 TCP 粘包和拆包问题 进行说明。 -![avatar](../../../images/Netty/TCP粘包拆包问题.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/TCP粘包拆包问题.png) -假设客户端依次发送了两个数据包 DI 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。 +假设客户端依次发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。 1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包; -2. 服务端一次接收到了两个数据包,DI 和 D2 粘合在一起,被称为 TCP 粘包; -3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 DI 包 和 D2 包的部分内容,第二次读取到了 D2 包 的剩余内容,这被称为 TCP 拆包; +2. 服务端一次接收到了两个数据包,D1 和 D2 粘合在一起,被称为 TCP 粘包; +3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 D1 包 和 D2 包的部分内容,第二次读取到了 D2 包 的剩余内容,这被称为 TCP 拆包; 4. 服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部分内容,第二次读取到了 D1 包的剩余内容 和 D2 包的整包。 -如果此时服务端 TCP 接收滑窗非常小,而 数据包 DI 和 D2 比较大,很有可能会发生第 5 种可能,即服务端分多次才能将 D1 和 D2 包 接收完全,期间发生多次拆包。 +如果此时服务端 TCP 接收滑窗非常小,而 数据包 D1 和 D2 比较大,很有可能会发生第 5 种可能,即服务端分多次才能将 D1 和 D2 包 接收完全,期间发生多次拆包。 ### TCP 粘包/拆包发生的原因 @@ -34,6 +34,11 @@ TCP 是个 “流” 协议,所谓流,就是没有界限的一串数据。TC 3. 将消息分为消息头和消息体,在消息头中定义一个 长度字段 Len 来标识消息的总长度; 4. 更复杂的应用层协议。 +**注意**:从 TCP 流式设计上来看,TCP 粘包其实是一个伪命题。应用层协议需要自己划分消息的边界。**TCP 粘包问题是因为应用层协议开发者的错误设计导致的,他们忽略了 TCP 协议数据传输的核心机制 — 基于字节流,其本身并不存在数据包的概念。** 所有在 TCP 中传输的数据都是以流的形式进行传输,这就需要应用层协议开发者自行设计消息的边界划分规则。所以粘包总的来说还是以下两点: + +- TCP 协议是面向字节流的协议,它可能会重新分割组合应用层协议的消息到多个数据段中; +- 应用层协议没有定义消息的边界,导致数据的接收方无法按边界拆分粘连的消息。 + 介绍完了 TCP 粘包/拆包 的基础,下面我们来看看 Netty 是如何使用一系列 “半包解码器” 来解决 TCP 粘包/拆包问题的。 ## 利用 Netty 的解码器 解决 TCP 粘拆包问题 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 f8e1c0fc..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" @@ -6,7 +6,7 @@ Netty 为了向使用者屏蔽 NIO 通信 的底层细节,在和用户交互 ### 基于 Netty 创建客户端 时序图 -![avatar](../../../images/Netty/基于Netty创建客户端时序图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/基于Netty创建客户端时序图.png) ### Netty 创建客户端 流程分析 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 25f33a60..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" @@ -4,7 +4,7 @@ ### Netty 服务端创建时序图 -![avatar](../../../images/Netty/Netty服务端创建时序图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty服务端创建时序图.png) 下面我们对 Netty 服务端创建 的关键步骤和原理进行详细解析。 @@ -84,7 +84,7 @@ public final class NioEventLoop extends SingleThreadEventLoop { 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,相关代码如下。 @@ -190,7 +190,7 @@ backlog 指定了内核为此套接口排队的最大连接个数,对于给定 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。 @@ -326,7 +326,7 @@ NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作 ``` 到此,Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor 线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。 -![avatar](../../../images/Netty/NioServerSocketChannel的ChannelPipeline.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NioServerSocketChannel的ChannelPipeline.png) 最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor 线程 的多路复用器上监听新客户端的接入,代码如下。 ```java diff --git a/docs/Redis/redis-sds.md b/docs/Redis/redis-sds.md index 863825f6..460cf7f8 100644 --- a/docs/Redis/redis-sds.md +++ b/docs/Redis/redis-sds.md @@ -36,7 +36,7 @@ struct sdshdr { 那么接下来,我们就来看看最新的 Redis 是**如何根据字符串的长度,使用不同的数据结构进行存储的**。 -## Redis SDS 最新实现 +## Redis SDS [v6.0] 在 Redis 3.2 版本之后(v3.2 - v6.0),Redis 将 SDS 划分为 5 种类型: @@ -111,7 +111,11 @@ sds sdsnewlen(const void *init, size_t initlen) { // 指向flags的指针 unsigned char *fp; + // 检查长度是否溢出 + assert(initlen + hdrlen + 1 > initlen); + // 创建字符串,+1是因为 `\0` 结束符 + // sh指向header首字节 sh = s_malloc(hdrlen+initlen+1); if (sh == NULL) return NULL; if (init==SDS_NOINIT) @@ -125,8 +129,14 @@ sds sdsnewlen(const void *init, size_t initlen) { // s减1得到flags fp = ((unsigned char*)s)-1; + // 赋值len, alloc, flags ... + + // 赋值buf[] + if (initlen && init) + memcpy(s, init, initlen); + // 在s末尾添加\0结束符 s[initlen] = '\0'; @@ -135,13 +145,14 @@ sds sdsnewlen(const void *init, size_t initlen) { } ``` -创建 SDS 的大致流程是这样的:首先根据字符串长度计算得到 type,根据 type 计算头部所需长度,然后动态分配内存空间。 +创建 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. 清空字符串 @@ -169,7 +180,19 @@ void sdsclear(sds s) { } ``` -### 3. 拼接字符串 +### 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 拼接字符串的实现如下: @@ -232,6 +255,9 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { // 计算新长度 newlen = (len+addlen); + // 检查长度是否溢出 + assert(newlen > len); + // 新长度<1MB,按新长度的2倍扩容 if (newlen < SDS_MAX_PREALLOC) newlen *= 2; @@ -246,6 +272,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { 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); @@ -278,14 +308,6 @@ sds sdsMakeRoomFor(sds s, size_t addlen) { ## 总结 -1. SDS 返回的是指向 buf 的指针,兼容了 C 语言操作字符串的函数,读取内容时,通过 len 属性来限制读取的长度,不受 `\0` 影响,从而保证二进制安全; +1. SDS 返回的是指向 buf 的指针,同时以`\0`结尾,所以兼容了 C 语言操作字符串的函数,读取内容时,通过 len 属性来限制读取的长度,不受 `\0` 影响,从而保证二进制安全; 2. Redis 根据字符串长度的不同,定义了多种数据结构,包括:sdshdr5/sdshdr8/sdshdr16/sdshdr32/sdshdr64。 3. SDS 在设计字符串修改出会调用 `sdsMakeRoomFor` 函数进行检查,根据不同情况进行扩容。 - -全文完! - -**希望本文对大家有所帮助。如果感觉本文有帮助,有劳转发或点一下“在看”!让更多人收获知识!** - ---- - -长按识别下图二维码,关注公众号「**Doocs 开源社区**」,第一时间跟你们分享好玩、实用的技术文章与业内最新资讯。 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" index e6b268b4..677130f5 100644 --- "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" @@ -1,6 +1,7 @@ ## LongAdder 的原理 -在 LongAdder 中,底层通过多个数值进行累加来得到最后的结果。当多个线程对同一个 LongAdder 进行更新的时候,将会对这一些列的集合进行动态更新,以避免多线程之间的资源竞争。当需要得到 LongAdder 的具体的值的时候,将会将一系列的值进行求和作为最后的结果。 +在 LongAdder 中,底层通过多个数值进行累加来得到最后的结果。当多个线程对同一个 LongAdder 进行更新的时候,将会对这一些列的集合进行动态更新,以避免多线程之间的资源竞争。当需要得到 LongAdder 的具体的值的时候,将会将一系列的值进行求和作为最后的结果。 + 在高并发的竞争下进行类似指标数据的收集的时候,LongAdder 通常会和 AtomicLong 进行比较,在低竞争的场景下,两者有着相似的性能表现。而当在高并发竞争的场景下,LongAdder 将会表现更高的性能,但是也会伴随更高的内存消耗。 ## LongAdder 的代码实现 @@ -10,7 +11,7 @@ transient volatile Cell[] cells; transient volatile long base; ``` -cells 是一个简单的 Cell 数组,当比如通过 LongAdder 的 add()方法进行 LongAdder 内部的数据的更新的时候,将会根据每个线程的一个 hash 值与 cells 数组的长度进行取模而定位,并在定位上的位置进行数据更新。而 base 则是当针对 LongAdder 的数据的更新时,并没有线程竞争的时候,将会直接更新在 base 上,而不需要前面提到的 hash 再定位过程,当 LongAdder 的 sum()方法被调用的时候,将会对 cells 的所有数据进行累加在加上 sum 的值进行返回。 +cells 是一个简单的 Cell 数组,当比如通过 LongAdder 的 `add()` 方法进行 LongAdder 内部的数据的更新的时候,将会根据每个线程的一个 hash 值与 cells 数组的长度进行取模而定位,并在定位上的位置进行数据更新。而 base 则是当针对 LongAdder 的数据的更新时,并没有线程竞争的时候,将会直接更新在 base 上,而不需要前面提到的 hash 再定位过程,当 LongAdder 的 `sum()` 方法被调用的时候,将会对 cells 的所有数据进行累加在加上 sum 的值进行返回。 ```java public long sum() { @@ -27,7 +28,7 @@ public long sum() { } ``` -相比 sum()方法,LongAdder 的 add()方法要复杂得多。 +相比 `sum()` 方法,LongAdder 的 `add()` 方法要复杂得多。 ```java public void add(long x) { @@ -46,10 +47,15 @@ public void add(long x) { } ``` -在 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 数组中进行定位更新。 +在 `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 本身的内存填充 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" index a2cf0321..1c58f2cf 100644 --- "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" @@ -3,32 +3,36 @@ 在 Sentinel 中,主要是通过 LeapArray 类来实现滑动时间窗口的实现和选择。在 sentinel 的这个获取时间窗口并为时间窗口添加指标的过程中,主要的流程为: - 根据当前时间选择当前时间应该定位当前时间应该属于的时间窗口 id。 -- 根据时间窗口 id 获取时间窗口。这里可能会存在三种情况: +- 根据时间窗口 id 获取时间窗口。这里可能会存在四种情况: 1. 时间窗口还未建立,那么将会为此次流量的进入建立一个新的时间窗口返回,并且接下来这个时间窗口内的获取请求都将返回该窗口。 2. 时间窗口已经建立的情况下,将会直接获取已经存在的符合条件的时间窗口。 3. 时间窗口可能已经存在,但是当前获取的时间窗口已经过期,需要加锁,并重置当前时间窗口。 -4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。 - 其中的第四个情况表明,sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。 +4. 当前进入的时间已经远远落后当前的时间,目标时间窗口已经被 reset 更新成更新的时间窗口,那么将不会返回目标时间窗口,而是返回一个新的空的时间窗口进行统计,这个时间窗口不会再被重复利用。 + +其中的第四个情况表明,sentinel 的滑动时间窗口是有时间范围的,这也是为了尽量减少 sentinel 的所占用的内存,默认情况下 sentinel 的采取的时间长度为 1 分钟和 1 秒钟。这里的实现与 LeapArray 类的结构非常有关系。 ```java protected final AtomicReferenceArray> array; ``` -在 LeapArray 中,时间窗口的存放通过一个由 AtomicReferenceArray 实现的 array 来实现。AtomicReferenceArray 支持原子读取和写入,并支持通过 cas 来为指定位置的成员进行更新。在时间窗口的创建并放回 array 的过程中,也就是上文的第一步,就是通过 AtomicReferenceArray 的 compareAndSet()方法来实现,保证并发下的线程安全。并发情况下,通过 cas 更新失败的线程将会回到就绪态,在下一次婚欢得到已经初始化完成的时间窗口。 +在 LeapArray 中,时间窗口的存放通过一个由 AtomicReferenceArray 实现的 array 来实现。AtomicReferenceArray 支持原子读取和写入,并支持通过 cas 来为指定位置的成员进行更新。在时间窗口的创建并放回 array 的过程中,也就是上文的第一步,就是通过 AtomicReferenceArray 的 `compareAndSet()` 方法来实现,保证并发下的线程安全。并发情况下,通过 cas 更新失败的线程将会回到就绪态,在下一次循环得到已经初始化完成的时间窗口。 ```java private final ReentrantLock updateLock = new ReentrantLock(); ``` -此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 yield()方法回到就绪态在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。 +此处的 updateLock 是专门在上述的第三个情况来进行加锁的,只有成功得到锁的线程才会对过期的时间窗口进行 reset 操作,其他没有成功获取的线程将不会挂起等待,而是通过 `yield()` 方法回到就绪态,在下一次的循环尝试重新获取该位置的时间窗口。在下一次获取该锁的线程可能已经完成了,那么将会执行上述第二步,否则继续回到就绪态等待下一次循环中再次获取该时间窗口。 + 以上两个数据结构是 LeapArray 类实现时间窗口在高并发下准确获取时间窗口并更新的关键。 ## 以秒级别的时间窗口举个例子 -在 sentinel 默认的秒级别时间窗口中,array 的大小为 2,也就是每 500ms 为一个时间窗口的大小。 -因此当一个线程试图获取一个时间窗口来记录指标数据的时候,将会根据单个时间窗口的时间跨度进行取模,来得到 array 上对应的时间窗口的下标,在这个情况下,将为 0 或者 1,之后计算当前线程时间指标所属的时间窗口的起始时间,以此为依据来判断如果在后面如果获取到的时间窗口是过期还是正好所需要的。 -最后,将会不断循环从 array 尝试获取之前计算得到下标位置处的时间窗口,可能发生的 4 种情况如上所示。在这个情况,如果 cas 失败或事没有尝试获取到更新锁,都不会阻塞或是挂起,而是通过 yield 重新回到就绪态等待下一次循环获取。 +在 sentinel 默认的秒级别时间窗口中,array 的大小为 2,也就是每 500ms 为一个时间窗口的大小。 + +因此当一个线程试图获取一个时间窗口来记录指标数据的时候,将会根据单个时间窗口的时间跨度进行取模,来得到 array 上对应的时间窗口的下标,在这个情况下,将为 0 或者 1,之后计算当前线程时间指标所属的时间窗口的起始时间,以此为依据来判断获取到的时间窗口是过期还是正好所需要的。 + +最后,将会不断循环从 array 尝试获取之前计算得到下标位置处的时间窗口,可能发生的 4 种情况如上所示。在这个情况,如果 cas 失败或是没有尝试获取到更新锁,都不会阻塞或是挂起,而是通过 yield 重新回到就绪态等待下一次循环获取。 ## 时间窗口本身的线程安全指标更新 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" index eec8d34c..90b2413e 100644 --- "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" @@ -68,7 +68,8 @@ maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFacto 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 是当前 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 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 76500e50..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" @@ -298,7 +298,7 @@ public class ProxyCreatorSupport extends AdvisedSupport { } ``` -可以看到其根据目标对象是否实现了接口,而决定是使用 JDK 动态代理 还是 CGLIB 去生成代理对象,而 AopProxy 接口的实现类也只有 JdkDynamicAopProxy 和 CglibAopProxy 这两个。 +可以看到其根据目标对象是否为接口,而决定是使用 JDK 动态代理 还是 CGLIB 去生成代理对象,而 AopProxy 接口的实现类也只有 JdkDynamicAopProxy 和 CglibAopProxy 这两个。 ### 2.5 JDK 动态代理 生成 AopProxy 代理对象 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 cae8507c..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" @@ -13,7 +13,7 @@ - 源码阅读目标找到了,那么怎么去找入口或者对这句话的标签解析方法呢?项目中使用搜索 - ![image-20200115083744268](../../../images/spring/image-20200115083744268.png) + ![image-20200115083744268](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115083744268.png) 这样就找到了具体解析方法了 @@ -21,7 +21,7 @@ - 类图 -![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 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 8350c164..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" @@ -51,7 +51,7 @@ public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh // 动态地确定用哪个加载器去加载我们的配置文件 super(parent); - // 告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableApplicationContext + // 告诉读取器 配置文件放在哪里,该方法继承于爷类 AbstractRefreshableConfigApplicationContext setConfigLocations(configLocations); if (refresh) { // 容器初始化 @@ -211,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)); 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 index 5f3baa97..f3581a78 100644 --- a/docs/Spring/IoC/BeanPostProcessor.md +++ b/docs/Spring/IoC/BeanPostProcessor.md @@ -1,4 +1,6 @@ -BeanPostProcessor 接口 也叫 Bean 后置处理器,作用是在 Bean 对象实例化和依赖注入完成后,在显示调用 bean 的 init-method(初始化方法)的前后添加我们自己的处理逻辑。注意是 Bean 实例化完毕后及依赖注入完成后触发的,接口的源码如下。 +# BeanPostProcessor 源码分析 + +BeanPostProcessor 接口也叫 Bean 后置处理器,作用是在 Bean 对象实例化和依赖注入完成后,在配置文件 bean 的 init-method(初始化方法)或者 InitializingBean 的 afterPropertiesSet 的前后添加我们自己的处理逻辑。注意是 Bean 实例化完毕后及依赖注入完成后触发的,接口的源码如下。 ```java public interface BeanPostProcessor { @@ -15,4 +17,129 @@ public interface BeanPostProcessor { } ``` -使用方法也很简单,实现 BeanPostProcessor 接口,然后将实现类注入 IoC 容器即可。 +共有两种方式实现: + +- 实现 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 083960f1..e69de29b 100644 --- a/docs/Spring/JDBC/Spring-jdbc.md +++ b/docs/Spring/JDBC/Spring-jdbc.md @@ -1,485 +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 f3f5f50f..a9597980 100644 --- a/docs/Spring/RMI/Spring-RMI.md +++ b/docs/Spring/RMI/Spring-RMI.md @@ -8,7 +8,7 @@ ### 服务提供方 -- 服务提供方需要准备**接口**、**接口实现泪** +- 服务提供方需要准备**接口**、**接口实现类** - 接口 ```java @@ -458,7 +458,7 @@ 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`方法 @@ -687,7 +687,7 @@ 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`方法 @@ -759,7 +759,7 @@ protected Remote lookupStub() throws RemoteLookupFailureException { 类图 -![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 public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor { @@ -793,77 +793,77 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor - `org.springframework.remoting.rmi.RmiServiceExporter#afterPropertiesSet`打上断点 -![image-20200226084056993](../../../images/spring/image-20200226084056993.png) +![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 的就不进去看了 执行完成就创建出了 `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 - ![image-20200226084923783](../../../images/spring/image-20200226084923783.png) + ![image-20200226084923783](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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-20200226085433130](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226085433130.png) -![image-20200226085440865](../../../images/spring/image-20200226085440865.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 相关技术不在此处展开 -![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 调用结束 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 175ea915..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" @@ -106,7 +106,7 @@ example.scannable.sub.BarComponent=org.springframework.stereotype.Component } ``` -![image-20200115105941265](../../../images/spring/image-20200115105941265.png) +![image-20200115105941265](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200115105941265.png) - 该类给`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents`提供了帮助 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 ef2e03bb..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" @@ -37,7 +37,7 @@ DispatchServlet 和 ContextLoaderListener 提供了在 Web 容器 中对 Spring IoC 容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC 容器 在 Web 应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC 对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在 Web 容器 中启动 Spring 应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML 时序图 如下图所示。 -![avatar](../../../images/springMVC/Web容器启动spring应用程序过程图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/Web容器启动spring应用程序过程图.png) 在 web.xml 中,已经配置了 ContextLoaderListener,它是 Spring 提供的类,是为在 Web 容器 中建立 IoC 容器 服务的,它实现了 ServletContextListener 接口,这个接口是在 Servlet API 中定义的,提供了与 Servlet 生命周期 结合的回调,比如上下文初始化 contextInitialized()方法 和 上下文销毁 contextDestroyed()方法。而在 Web 容器 中,建立 WebApplicationContext 的过程,是在 contextInitialized()方法 中完成的。另外,ContextLoaderListener 还继承了 ContextLoader,具体的载入 IoC 容器 的过程是由 ContextLoader 来完成的。 @@ -48,7 +48,7 @@ IoC 容器 的启动过程就是建立上下文的过程,该上下文是与 Se 先从 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 中完成的。 diff --git a/docs/Spring/SpringMVC/SpringMVC-CROS.md b/docs/Spring/SpringMVC/SpringMVC-CROS.md index 49c598b6..a9c36673 100644 --- a/docs/Spring/SpringMVC/SpringMVC-CROS.md +++ b/docs/Spring/SpringMVC/SpringMVC-CROS.md @@ -119,9 +119,9 @@ public class JSONController { 信息截图: -![image-20200123085741347](../../../images/springMVC/clazz/image-20200123085741347.png) +![image-20200123085741347](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123085741347.png) -![image-20200123085756168](../../../images/springMVC/clazz/image-20200123085756168.png) +![image-20200123085756168](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123085756168.png) ### updateCorsConfig @@ -166,7 +166,7 @@ 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`** @@ -237,7 +237,7 @@ public class JSONController { #### 类图 -![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) #### 解析 @@ -303,7 +303,7 @@ 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) - 可以看出这个是我们的第一个跨域配置的信息 @@ -335,7 +335,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { ``` -- ![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 @@ -509,7 +509,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser { - 经过跨域拦截器 **`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 @Override @@ -560,4 +560,4 @@ Origin: localhost 变量截图 -![image-20200123093032179](../../../images/springMVC/clazz/image-20200123093032179.png) +![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 599290cd..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" @@ -10,13 +10,13 @@ 为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet 类 的继承关系。 -![avatar](../../../images/springMVC/DispatcherServlet的继承关系.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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](../../../images/springMVC/DispatcherServlet的处理过程.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/DispatcherServlet的处理过程.png) ## 3 DispatcherServlet 的启动和初始化 @@ -391,7 +391,7 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W 在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP 请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL 请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。 -![avatar](../../../images/springMVC/HandlerMapping组件.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/HandlerMapping组件.png) 以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL 请求 和控制器的对应关系,使 SpringMVC 应用 可以根据 HTTP 请求 确定一个对应的 Controller。具体来说,这些映射关系是通过 HandlerMapping 接口 来封装的,在 HandlerMapping 接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP 请求 对应的 HandlerExecutionChain,在这个 HandlerExecutionChain 中,封装了具体的 Controller 对象。 @@ -515,7 +515,7 @@ public class HandlerExecutionChain { HandlerExecutionChain 中定义的 Handler 和 HandlerInterceptor[]属性 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping,要做的就是根据 URL 映射 的方式,注册 Handler 和 HandlerInterceptor[],从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP 请求 时,需要查询这个 handlerMap 中的信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor() 来完成的。以 SimpleHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleHandlerMapping 是 ApplicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间映射关系的 handlerMap 的建立。 -![avatar](../../../images/springMVC/SimpleUrlHandlerMapping的继承关系.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/SimpleUrlHandlerMapping的继承关系.png) ```java public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { 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 ef6d1063..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" @@ -8,7 +8,7 @@ JavaEE 应用中的事务处理是一个重要并且涉及范围很广的领域 Spring 事务处理模块的类层次结构如下图所示。 -![avatar](../../../images/springTransaction/Spring事务处理模块类层次结构.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/Spring事务处理模块类层次结构.png) 从上图可以看到,Spring 事务处理模块 是通过 AOP 功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring 事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP 功能,通过这个 TransactionProxyFactoryBean 可以生成 Proxy 代理对象,在这个代理对象中,通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。 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 4d1f9ed5..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" @@ -24,7 +24,7 @@ 这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction 事务对象 的过程,在 AbstractTransactionManager 实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus,并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定,从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。 -![avatar]() +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png) ```java public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { 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 5873669b..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" @@ -4,7 +4,7 @@ 可以看到,在 PlatformTransactionManager 组件 的设计中 ,通过 PlatformTransactionManager 接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager 抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。 -![avatar](../../../images/springTransaction/PlatformTransactionManager组件的设计.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/PlatformTransactionManager组件的设计.png) ## 2 DataSourceTransactionManager 的实现 @@ -14,7 +14,7 @@ 上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit 属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 -![avatar](../../../images/springTransaction/实现DataSourceTransactionManager的时序图.png) +![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/实现DataSourceTransactionManager的时序图.png) ```java public class DataSourceTransactionManager extends AbstractPlatformTransactionManager 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 f136c712..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" @@ -2,7 +2,7 @@ 在使用 Spring 声明式事务处理 的时候,一种常用的方法是结合 IoC 容器 和 Spring 已有的 TransactionProxyFactoryBean 对事务管理进行配置,比如,可以在这个 TransactionProxyFactoryBean 中为事务方法配置传播行为、并发事务隔离级别等事务处理属性,从而对声明式事务的处理提供指导。具体来说,在对声明式事务处理的原理分析中,声明式事务处理的实现大致可以分为以下几个部分: -- 读取和处理在 IoC 容器 中配置的事务处理属性,并转化为 Spring 事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor,从名字可以看出,它是一个 AOP 通知器,Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC 容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事物处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。 +- 读取和处理在 IoC 容器 中配置的事务处理属性,并转化为 Spring 事务处理 需要的内部数据结构,这里涉及的类是 TransactionAttributeSourceAdvisor,从名字可以看出,它是一个 AOP 通知器,Spring 使用这个通知器来完成对事务处理属性值的处理。处理的结果是,在 IoC 容器 中配置的事务处理属性信息,会被读入并转化成 TransactionAttribute 表示的数据对象,这个数据对象是 Spring 对事务处理属性值的数据抽象,对这些属性的处理是和 TransactionProxyFactoryBean 拦截下来的事务方法的处理结合起来的。 - Spring 事务处理模块 实现统一的事务处理过程。这个通用的事务处理过程包含处理事务配置属性,以及与线程绑定完成事务处理的过程,Spring 通过 TransactionInfo 和 TransactionStatus 这两个数据对象,在事务处理过程中记录和传递相关执行场景。 - 底层的事务处理实现。对于底层的事务操作,Spring 委托给具体的事务处理器来完成,这些具体的事务处理器,就是在 IoC 容器 中配置声明式事务处理时,配置的 PlatformTransactionManager 的具体实现,比如 DataSourceTransactionManager 和 HibernateTransactionManager 等。 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 2720126f..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,32 +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 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。 -就这样,每一种取出来的面筋都会经过这些处理。等到所有的面筋处理完了,也差不多到了傍晚,每到这时我就会拿起梳子和发油,对着镶满钻石的镜子,梳理整齐与徐峥同款的明星发型,唱着魔性的“我的烤面筋 ~”,骑着小车车,出摊咯 ~ +妹妹在用神器 `BeanDefinitionParserDelegate` 经过一顿疯狂操作之后,将包装好的半成品 `BeanDefinitionHolder` 扔进传输机 `BeanDefinitionReaderUtils`,并且输入哥哥给她的神秘人地址,就继续处理下一个面筋 bean 咯。 + +之后,传输机将 `BeanDefinitionHolder` 的包装打开,分别取出 `beanName`(面筋的唯一标识)和 `BeanDefinition`(面筋本筋),传输的目的地是 `BeanDefinitionRegistry` 的工作室(这就是我前面给哥哥 `beanDefinitionReader` 的地址)。 + +这家工作室的 `BeanDefinitionRegistry` 其实就是我的影分身之一,因为我的祖先实现了这个接口。 + +影分身 `Registry` 检查一下传输过来的 `beanName`(面筋的唯一标识)和 `BeanDefinition`(面筋本筋),如果没什么问题,就把它们用根绳子系在一起扔进我的“王之面筋宝库”,一个 `ConcurrentHashMap(64)`,也有人把我的“面筋宝库”称作 “IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。 + +就这样,每一种取出来的面筋都会经过这些处理。 + +等到所有的面筋处理完了,也差不多到了傍晚,每到这时我就会拿起梳子和发油,对着镶满钻石的镜子,梳理整齐与徐峥同款的明星发型,唱着魔性的: + +> 我的烤面筋 ~ + +骑着小车车,出摊咯 ~ + +--- -面筋等原材料基本上都已经处理完毕,但把这些原材料变成程序员手中的“烤面筋”也是一门复杂而精细的手艺,老铁们记得 watch、star、fork,素质三连一波,下一期我将带领你们走进 spring 商业街的夜市,一起烤出香喷喷的面筋,成为这条 gai 上最亮的仔! +面筋等原材料基本上都已经处理完毕,但把这些原材料变成程序员手中的“烤面筋”也是一门复杂而精细的手艺,老铁们记得 `watch`、`star`、`fork`,素质三连一波,下一期我将带领你们走进 spring 商业街的夜市,一起烤出香喷喷的面筋,成为这条 `gai` 上最亮的仔! diff --git a/docs/Spring/TX/Spring-transaction.md b/docs/Spring/TX/Spring-transaction.md index c5c4b019..e7cee6d7 100644 --- a/docs/Spring/TX/Spring-transaction.md +++ b/docs/Spring/TX/Spring-transaction.md @@ -257,7 +257,7 @@ public class ProxyTransactionManagementConfiguration extends AbstractTransaction ### TransactionInterceptor -![image-20200729144622440](/images/spring/image-20200729144622440.png) +![image-20200729144622440](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729144622440.png) - 实现了`org.aopalliance.intercept.MethodInterceptor`接口的方法 @@ -310,19 +310,19 @@ public class DeclarativeTransactionTest { } ``` -![image-20200729145518089](/images/spring/image-20200729145518089.png) +![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](/images/spring/image-20200729145637688.png) +![image-20200729145637688](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729145637688.png) 走到`invoke`方法了 入参对象查看 -![image-20200729145835608](/images/spring/image-20200729145835608.png) +![image-20200729145835608](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729145835608.png) - 获取事务属性 @@ -377,7 +377,7 @@ public class DeclarativeTransactionTest { ``` -![image-20200729162023837](/images/spring/image-20200729162023837.png) +![image-20200729162023837](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729162023837.png) - 此处方法已经获取到了这个方法就是后面的一个切面 @@ -423,7 +423,7 @@ public class DeclarativeTransactionTest { } ``` -![image-20200729160650401](/images/spring/image-20200729160650401.png) +![image-20200729160650401](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729160650401.png) - 类型转换 @@ -462,7 +462,7 @@ public class DeclarativeTransactionTest { } ``` -![image-20200729161647214](/images/spring/image-20200729161647214.png) +![image-20200729161647214](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729161647214.png) - 创建一个新的事务根据事务传播性 @@ -501,7 +501,7 @@ public class DeclarativeTransactionTest { ``` -![image-20200729163303000](/images/spring/image-20200729163303000.png) +![image-20200729163303000](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729163303000.png) - `tm.getTransaction` @@ -878,7 +878,7 @@ void rollback(TransactionStatus status) throws TransactionException; - 贴出一部分 -![image-20200728105926218](/images/spring/image-20200728105926218.png) +![image-20200728105926218](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200728105926218.png) - AbstractPlatformTransactionManager 定义了一些基础属性 以及一些需要子类实现的方法 @@ -939,7 +939,7 @@ doCleanupAfterCompletion - bean 的属性注入就不具体描述了 -![image-20200728133037075](/images/spring/image-20200728133037075.png) +![image-20200728133037075](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200728133037075.png) - `InitializingBean` @@ -1603,7 +1603,7 @@ public static void bindResource(Object key, Object value) throws IllegalStateExc - debug 使用的是 druid 的数据源 -![image-20200729090322058](/images/spring/image-20200729090322058.png) +![image-20200729090322058](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200729090322058.png) - `unwrapResourceIfNecessary` 方法 @@ -1741,7 +1741,7 @@ map 对象的 remove 操作 - 事务操作模板类图 - ![image-20200728094658684](/images/spring/image-20200728094658684.png) + ![image-20200728094658684](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200728094658684.png) - `org.springframework.beans.factory.InitializingBean`接口的实现 diff --git a/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md b/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md index a994025c..444300f2 100644 --- a/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md +++ b/docs/Spring/clazz/PlaceholderResolver/Spring-PlaceholderResolver.md @@ -24,4 +24,4 @@ - 类图如下 -![PropertyPlaceholderConfigurerResolver](/images/spring/PropertyPlaceholderConfigurerResolver.png) +![PropertyPlaceholderConfigurerResolver](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyPlaceholderConfigurerResolver.png) diff --git a/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png b/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png index fb6a9261..4e34347e 100644 Binary files a/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png and b/docs/Spring/clazz/PlaceholderResolver/images/PropertyPlaceholderConfigurerResolver.png differ diff --git a/docs/Spring/clazz/Spring-AnnotationUtils.md b/docs/Spring/clazz/Spring-AnnotationUtils.md index 6489e4d9..e26feb98 100644 --- a/docs/Spring/clazz/Spring-AnnotationUtils.md +++ b/docs/Spring/clazz/Spring-AnnotationUtils.md @@ -55,11 +55,11 @@ public static A getAnnotation(Method method, Class ann - method -![image-20200116085344737](../../../images/spring/image-20200116085344737.png) +![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 @@ -239,9 +239,9 @@ public static void makeAccessible(Method method) { 处理结果 -![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 定义相同 @@ -265,7 +265,7 @@ 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 @@ -402,7 +402,7 @@ private static A findAnnotation( ``` -![image-20200116092259944](../../../images/spring/image-20200116092259944.png) +![image-20200116092259944](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200116092259944.png) - `synthesizeAnnotation`方法就不再重复一遍了可以看上文 diff --git a/docs/Spring/clazz/Spring-ApplicationListener.md b/docs/Spring/clazz/Spring-ApplicationListener.md index 159175db..9f3b5a1a 100644 --- a/docs/Spring/clazz/Spring-ApplicationListener.md +++ b/docs/Spring/clazz/Spring-ApplicationListener.md @@ -102,7 +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 发布 @@ -172,7 +172,7 @@ 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) { @@ -217,6 +217,6 @@ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { ``` -![image-20200119164402137](../../../images/spring/image-20200119164402137.png) +![image-20200119164402137](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119164402137.png) -![image-20200119164410301](../../../images/spring/image-20200119164410301.png) +![image-20200119164410301](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119164410301.png) diff --git a/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md b/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md index 2ef5a2e9..36a9392c 100644 --- a/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md +++ b/docs/Spring/clazz/Spring-BeanFactoryPostProcessor.md @@ -248,9 +248,9 @@ public class BeanFactoryPostProcessorSourceCode { } ``` -![image-20200119085346675](../../../images/spring/image-20200119085346675.png) +![image-20200119085346675](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119085346675.png) -![image-20200119085655734](../../../images/spring/image-20200119085655734.png) +![image-20200119085655734](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119085655734.png) ## InstantiationAwareBeanPostProcessor @@ -361,13 +361,13 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa - 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序 Bean -![image-20200119101026726](../../../images/spring/image-20200119101026726.png) +![image-20200119101026726](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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) ### 使用阶段(调用阶段) @@ -425,4 +425,4 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa 这个地方已经可以看到`InstantiationAwareBeanPostProcessor`出现了,并且调用了方法`postProcessBeforeInstantiation`,此处就可以调用我们的自定义方法了 -![image-20200119101516591](../../../images/spring/image-20200119101516591.png) +![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 index 97473ad9..90748cef 100644 --- a/docs/Spring/clazz/Spring-BeanNameGenerator.md +++ b/docs/Spring/clazz/Spring-BeanNameGenerator.md @@ -22,7 +22,7 @@ public interface BeanNameGenerator { } ``` -![](/images/spring/BeanNameGenerator.png) +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/BeanNameGenerator.png) ## DefaultBeanNameGenerator diff --git a/docs/Spring/clazz/Spring-Custom-attribute-resolver.md b/docs/Spring/clazz/Spring-Custom-attribute-resolver.md index aeada5e4..7281c6cd 100644 --- a/docs/Spring/clazz/Spring-Custom-attribute-resolver.md +++ b/docs/Spring/clazz/Spring-Custom-attribute-resolver.md @@ -74,7 +74,7 @@ public class DatePropertyEditor extends PropertyEditorSupport { - 直接在`DatePropertyRegister`打上断点进行查看注册流程 - ![image-20200117104710142](../../../images/spring/image-20200117104710142.png) + ![image-20200117104710142](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117104710142.png) 直接看调用堆栈获取调用层次 @@ -112,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`传递的 @@ -167,7 +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` @@ -191,7 +191,7 @@ 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 @@ -305,15 +305,15 @@ public class DatePropertyEditor extends PropertyEditorSupport { ``` - ![image-20200117133325461](../../../images/spring/image-20200117133325461.png) + ![image-20200117133325461](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117133325461.png) -![image-20200117141309038](../../../images/spring/image-20200117141309038.png) +![image-20200117141309038](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200117141309038.png) -![image-20200117141519123](../../../images/spring/image-20200117141519123.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 @Nullable @@ -368,6 +368,6 @@ public class DatePropertyEditor extends PropertyEditorSupport { ``` -![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 84ef413e..233323a9 100644 --- a/docs/Spring/clazz/Spring-Custom-label-resolution.md +++ b/docs/Spring/clazz/Spring-Custom-label-resolution.md @@ -204,7 +204,7 @@ 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`中有定义, @@ -282,7 +282,7 @@ 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) - 这里直接存在数据了,他是从什么时候加载的? @@ -356,7 +356,7 @@ public class XSDDemo { 断点 - ![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 public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) { @@ -366,13 +366,13 @@ 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 @@ -422,7 +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 @@ -530,7 +530,7 @@ public class UserNamespaceHandler extends NamespaceHandlerSupport { ``` -![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 @@ -566,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 @@ -624,7 +624,7 @@ 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` diff --git a/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md b/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md index 74e58e49..83e5e0bb 100644 --- a/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md +++ b/docs/Spring/clazz/Spring-DefaultSingletonBeanRegistry.md @@ -6,7 +6,7 @@ - 官方提供的测试类: `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) ## 注册方法解析 diff --git a/docs/Spring/clazz/Spring-EntityResolver.md b/docs/Spring/clazz/Spring-EntityResolver.md index bb04ef97..b6b0e10a 100644 --- a/docs/Spring/clazz/Spring-EntityResolver.md +++ b/docs/Spring/clazz/Spring-EntityResolver.md @@ -87,9 +87,9 @@ ``` -![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) 得到本地路径,后续直接返回读取资源 @@ -151,7 +151,7 @@ - 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) ## 总结 diff --git a/docs/Spring/clazz/Spring-MessageSource.md b/docs/Spring/clazz/Spring-MessageSource.md index 2038b15f..35af8eb1 100644 --- a/docs/Spring/clazz/Spring-MessageSource.md +++ b/docs/Spring/clazz/Spring-MessageSource.md @@ -45,7 +45,7 @@ 读取 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 @@ -173,14 +173,14 @@ ``` -![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) +![image-20200119144019171](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119144019171.png) - 没有配置文件的情况 - ![image-20200119145138205](../../../images/spring/image-20200119145138205.png) + ![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 index 13e633cb..adc1422f 100644 --- a/docs/Spring/clazz/Spring-Metadata.md +++ b/docs/Spring/clazz/Spring-Metadata.md @@ -85,7 +85,7 @@ public interface ClassMetadata { } ``` -![image-20200824094154847](/images/spring/image-20200824094154847.png) +![image-20200824094154847](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200824094154847.png) ## AnnotatedTypeMetadata @@ -775,7 +775,7 @@ qulifiter:transactionManager readOnlay:false ``` -![image-20200824104529315](/images/spring/image-20200824104529315.png) +![image-20200824104529315](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200824104529315.png) ## SimpleMetadataReader diff --git a/docs/Spring/clazz/Spring-MethodOverride.md b/docs/Spring/clazz/Spring-MethodOverride.md index 1a4673be..c8f3a854 100644 --- a/docs/Spring/clazz/Spring-MethodOverride.md +++ b/docs/Spring/clazz/Spring-MethodOverride.md @@ -47,7 +47,7 @@ public abstract boolean matches(Method method); 类图 -![MethodOverride](/images/spring/MethodOverride.png) +![MethodOverride](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/MethodOverride.png) - 在 Spring 中有两种可以重写的机制(XML) diff --git a/docs/Spring/clazz/Spring-MultiValueMap.md b/docs/Spring/clazz/Spring-MultiValueMap.md index 92a813f8..b7d76435 100644 --- a/docs/Spring/clazz/Spring-MultiValueMap.md +++ b/docs/Spring/clazz/Spring-MultiValueMap.md @@ -58,7 +58,7 @@ public interface MultiValueMap extends Map> { 类图 -![](/images/spring/MultiValueMap.png) +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/MultiValueMap.png) ## LinkedMultiValueMap diff --git a/docs/Spring/clazz/Spring-OrderComparator.md b/docs/Spring/clazz/Spring-OrderComparator.md index ecea6f52..81855606 100644 --- a/docs/Spring/clazz/Spring-OrderComparator.md +++ b/docs/Spring/clazz/Spring-OrderComparator.md @@ -61,7 +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 @@ -90,6 +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 ! diff --git a/docs/Spring/clazz/Spring-Property.md b/docs/Spring/clazz/Spring-Property.md index a973e184..67896f23 100644 --- a/docs/Spring/clazz/Spring-Property.md +++ b/docs/Spring/clazz/Spring-Property.md @@ -11,7 +11,7 @@ - 类图如下 - ![images](/images/spring/PropertyValues.png) + ![images](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyValues.png) - 在 Spring IoC 中,**非 Web 工程**,使用 xml 或者注解进行配置主要使用到的是 `PropertyValues` ,`PropertyValue` ,`MutablePropertyValues` 三个 @@ -27,7 +27,7 @@ - 类图 - ![](/images/spring/PropertyValue.png) + ![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyValue.png) - 这个类暂时只关注两个属性 @@ -285,7 +285,7 @@ public interface Mergeable { } ``` -![](/images/spring/Mergeable.png) +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/Mergeable.png) - 看一下 List 怎么实现`merge` diff --git a/docs/Spring/clazz/Spring-PropertySources.md b/docs/Spring/clazz/Spring-PropertySources.md index f84cd6ab..9511c20c 100644 --- a/docs/Spring/clazz/Spring-PropertySources.md +++ b/docs/Spring/clazz/Spring-PropertySources.md @@ -484,4 +484,4 @@ public abstract class PropertySource { 类图 -![PropertySource.png](/images/spring/PropertySource.png) +![PropertySource.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertySource.png) diff --git a/docs/Spring/clazz/Spring-SystemPropertyUtils.md b/docs/Spring/clazz/Spring-SystemPropertyUtils.md index 5db5f075..7291fec6 100644 --- a/docs/Spring/clazz/Spring-SystemPropertyUtils.md +++ b/docs/Spring/clazz/Spring-SystemPropertyUtils.md @@ -42,7 +42,7 @@ private static final PropertyPlaceholderHelper nonStrictHelper = - 解析属性 -![SystemPropertyUtils-resolvePlaceholders.png](/images/spring/SystemPropertyUtils-resolvePlaceholders.png) +![SystemPropertyUtils-resolvePlaceholders.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/SystemPropertyUtils-resolvePlaceholders.png) 时序图因为有递归所以看着有点长, 其核心方法最后会指向 PlaceholderResolver diff --git a/docs/Spring/clazz/Spring-beanFactory.md b/docs/Spring/clazz/Spring-beanFactory.md index 4af43cb1..de903d19 100644 --- a/docs/Spring/clazz/Spring-beanFactory.md +++ b/docs/Spring/clazz/Spring-beanFactory.md @@ -9,7 +9,7 @@ ### 类图 -![beanFactory](/images/spring/BeanFactory.png) +![beanFactory](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/BeanFactory.png) ### 方法列表 @@ -226,11 +226,11 @@ protected void assertBeanFactoryActive() { - 获取到的对象是`org.springframework.beans.factory.support.DefaultListableBeanFactory` -![image-20200902102912716](images/image-20200902102912716.png) +![image-20200902102912716](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902102912716.png) - 整体类图 -![image-20200902103154580](images/image-20200902103154580.png) +![image-20200902103154580](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902103154580.png) ### doGetBean @@ -299,7 +299,7 @@ private final Map aliasMap = new ConcurrentHashMap<>(16); aliasMap 和 别名标签的对应关系 -![image-20200902105454958](images/image-20200902105454958.png) +![image-20200902105454958](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902105454958.png) alias 标签的 alias 值作为别名的 key , alias 标签的 name 值作为 value @@ -704,7 +704,7 @@ protected void clearMergedBeanDefinition(String beanName) { - 这个方法获取一个`RootBeanDefinition`对象 , 这个对象也是 bean 的一种定义。 - 从目前的几个方法名称来看,暂且认为这是一个合并了多个 `BeanDefinition`的对象吧 -![rootBeanDefinition](/images/spring/RootBeanDefinition.png) +![rootBeanDefinition](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/RootBeanDefinition.png) ```java protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { @@ -1009,7 +1009,7 @@ private boolean isDependent(String beanName, String dependentBeanName, @Nullable ``` -![image-20200903091759451](images/image-20200903091759451.png) +![image-20200903091759451](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903091759451.png) #### registerDependentBean @@ -1476,7 +1476,7 @@ protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable - 类图 -![](/images/spring/TemplateAwareExpressionParser.png) +![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/TemplateAwareExpressionParser.png) ###### BeanExpressionContext @@ -1562,7 +1562,7 @@ private Expression parseTemplate(String expressionString, ParserContext context) } ``` -![image-20200903111128603](images/image-20200903111128603.png) +![image-20200903111128603](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903111128603.png) - `parseExpressions` @@ -2175,7 +2175,7 @@ try { pvs 属性如下 -![image-20200903150738285](images/image-20200903150738285.png) +![image-20200903150738285](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903150738285.png) ###### applyPropertyValues @@ -2304,7 +2304,7 @@ try { } ``` -![image-20200903150930186](images/image-20200903150930186.png) +![image-20200903150930186](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903150930186.png) ###### initializeBean @@ -2461,7 +2461,7 @@ protected void invokeInitMethods(String beanName, final Object bean, @Nullable R } ``` -![image-20200903153057321](images/image-20200903153057321.png) +![image-20200903153057321](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153057321.png) 我们现在的 bean 不是`InitializingBean` 会走自定义的`init-mthod`方法 @@ -2483,15 +2483,15 @@ protected void invokeInitMethods(String beanName, final Object bean, @Nullable R - 观察 `initMethodName` 会变成 标签属性`init-method` 的内容. 接下来就是通过反射执行方法 -![image-20200903153432559](images/image-20200903153432559.png) +![image-20200903153432559](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153432559.png) - 在执行方法前将 bean 的信息先做一次截图 - ![image-20200903153533141](images/image-20200903153533141.png) + ![image-20200903153533141](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153533141.png) - 如果按照我们代码中的编写方式 bean 的属性会被覆盖 - ![image-20200903153617353](images/image-20200903153617353.png) + ![image-20200903153617353](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153617353.png) ###### invokeCustomInitMethod diff --git a/docs/Spring/clazz/Spring-scan.md b/docs/Spring/clazz/Spring-scan.md index 15366417..b89fbb04 100644 --- a/docs/Spring/clazz/Spring-scan.md +++ b/docs/Spring/clazz/Spring-scan.md @@ -53,7 +53,7 @@ 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`方法 @@ -302,7 +302,7 @@ 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 @@ -363,7 +363,7 @@ 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)` @@ -393,7 +393,7 @@ 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 diff --git a/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md b/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md index 22d121db..52bc9d32 100644 --- a/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md +++ b/docs/Spring/clazz/format/AnnotationFormatterFactory/Spring-DateTimeFormatAnnotationFormatterFactory.md @@ -3,7 +3,7 @@ - 类全路径: `org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory` - 类图 - ![EmbeddedValueResolutionSupport](/images/spring/DateTimeFormatAnnotationFormatterFactory.png) + ![EmbeddedValueResolutionSupport](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/DateTimeFormatAnnotationFormatterFactory.png) ```java public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport diff --git a/docs/Spring/clazz/format/Spring-Parser.md b/docs/Spring/clazz/format/Spring-Parser.md index 752a5079..9625774e 100644 --- a/docs/Spring/clazz/format/Spring-Parser.md +++ b/docs/Spring/clazz/format/Spring-Parser.md @@ -24,4 +24,4 @@ public interface Parser { - 类图 -![Parser](/images/spring/Parser.png) +![Parser](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/Parser.png) diff --git a/docs/Spring/message/Spring-EnableJms.md b/docs/Spring/message/Spring-EnableJms.md index 877686d7..c6c49d82 100644 --- a/docs/Spring/message/Spring-EnableJms.md +++ b/docs/Spring/message/Spring-EnableJms.md @@ -49,7 +49,7 @@ 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) - 主要关注 @@ -316,7 +316,7 @@ public class JmsBootstrapConfiguration { } ``` - ![image-20200304092154712](../../../images/springmessage/image-20200304092154712.png) + ![image-20200304092154712](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springmessage/image-20200304092154712.png) - 注册完成后是否立即启动 diff --git a/docs/Spring/message/Spring-MessageConverter.md b/docs/Spring/message/Spring-MessageConverter.md index ea90b9e9..68959a5f 100644 --- a/docs/Spring/message/Spring-MessageConverter.md +++ b/docs/Spring/message/Spring-MessageConverter.md @@ -8,7 +8,7 @@ - 消息转换接口 - 类图如下 - ![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 @@ -34,7 +34,7 @@ 类图: -![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 @@ -181,6 +181,6 @@ - 两种创建方式基本相同,如果出现异常组装异常消息对象`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`的子类 diff --git a/docs/Spring/mvc/Spring-MVC-HandlerMapping.md b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md index 877c1cf7..b70dac49 100644 --- a/docs/Spring/mvc/Spring-MVC-HandlerMapping.md +++ b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md @@ -14,7 +14,7 @@ public interface HandlerMapping { } ``` -![image](/images/springMVC/HandlerMapping.png) +![image](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/HandlerMapping.png) ```java @Override @@ -65,7 +65,7 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws 存在的实现方法 - ![image-20200915135933146](images/image-20200915135933146.png) + ![image-20200915135933146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/image-20200915135933146.png) - 先看`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`方法是怎么一回事. diff --git a/docs/Spring/mvc/Spring-mvc-MappingRegistry.md b/docs/Spring/mvc/Spring-mvc-MappingRegistry.md index 178a888f..e57e6ca7 100644 --- a/docs/Spring/mvc/Spring-mvc-MappingRegistry.md +++ b/docs/Spring/mvc/Spring-mvc-MappingRegistry.md @@ -86,7 +86,7 @@ public class DemoController { 先将对象截图出来方便后续理解 -![image-20200918130340555](/images/springMVC/clazz/image-20200918130340555.png) +![image-20200918130340555](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200918130340555.png) ## createHandlerMethod diff --git a/docs/SpringBoot/Spring-Boot-Run.md b/docs/SpringBoot/Spring-Boot-Run.md index 1f08c26f..a989fa17 100644 --- a/docs/SpringBoot/Spring-Boot-Run.md +++ b/docs/SpringBoot/Spring-Boot-Run.md @@ -141,11 +141,11 @@ private List createSpringFactoriesInstances(Class type, Class[] par - `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)`排序 @@ -153,21 +153,21 @@ private List createSpringFactoriesInstances(Class type, Class[] par `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) + ![image-20200318081322781](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081322781.png) - 下图中的所有类都有 Order 数值返回 排序前: -![image-20200318081352639](../../images/SpringBoot/image-20200318081352639.png) +![image-20200318081352639](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081352639.png) 排序后: -![image-20200318081458019](../../images/SpringBoot/image-20200318081458019.png) +![image-20200318081458019](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318081458019.png) ### listeners.starting() @@ -360,7 +360,7 @@ private List createSpringFactoriesInstances(Class type, Class[] par ### 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 @@ -439,9 +439,9 @@ private List createSpringFactoriesInstances(Class type, Class[] par context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); ``` -![image-20200318090128983](../../images/SpringBoot/image-20200318090128983.png) +![image-20200318090128983](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090128983.png) -![image-20200318090312626](../../images/SpringBoot/image-20200318090312626.png) +![image-20200318090312626](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090312626.png) ### applyInitializers @@ -466,7 +466,7 @@ private List createSpringFactoriesInstances(Class type, Class[] par - 数据结果 -![image-20200318090935285](../../images/SpringBoot/image-20200318090935285.png) +![image-20200318090935285](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318090935285.png) - 子类的具体实现不展开了 @@ -488,7 +488,7 @@ private List createSpringFactoriesInstances(Class type, Class[] par - `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 @@ -552,7 +552,7 @@ private int load(Object source) { - 通过前文我们已经知道 `source`就是一个 class - ![image-20200318092027020](../../images/SpringBoot/image-20200318092027020.png) + ![image-20200318092027020](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318092027020.png) ```java private int load(Class source) { diff --git a/docs/SpringBoot/SpringBoot-ConditionalOnBean.md b/docs/SpringBoot/SpringBoot-ConditionalOnBean.md index 2216b31b..f816a150 100644 --- a/docs/SpringBoot/SpringBoot-ConditionalOnBean.md +++ b/docs/SpringBoot/SpringBoot-ConditionalOnBean.md @@ -97,7 +97,7 @@ public enum SearchStrategy { - 类图 - ![image-20200824085726621](../../images/SpringBoot//SpringBoot/image-20200824085726621.png) + ![image-20200824085726621](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200824085726621.png) 在看这部分源码之前需要先了解 `Conditional`和`Condition`的源码 @@ -421,7 +421,7 @@ for (String type : spec.getTypes()) { - 在忽略 bean 找到之后做一个类型移除的操作. -![image-20200825140750035](../../images/SpringBoot//image-20200825140750035.png) +![image-20200825140750035](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825140750035.png) ### 返回值 @@ -469,7 +469,7 @@ public static ConditionOutcome noMatch(ConditionMessage message) { return ConditionOutcome.match(matchMessage); ``` -![image-20200825141506531](../../images/SpringBoot//image-20200825141506531.png) +![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 初始化咯 @@ -492,7 +492,7 @@ public static ConditionOutcome noMatch(ConditionMessage message) { - 根据类的注解信息我们可以找到有`ResourceBundleCondition` - ![image-20200825092343271](../../images/SpringBoot//image-20200825092343271.png) + ![image-20200825092343271](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825092343271.png) - 获取类名或者方法名的结果是`MessageSourceAutoConfiguration`全路径 @@ -592,8 +592,8 @@ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition - 此时我们可以和前文的源码分析连接起来有一个完整的认识了 - ![image-20200825142332485](../../images/SpringBoot//image-20200825142332485.png) + ![image-20200825142332485](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200825142332485.png) - 最后来看整体类图 - ![image-20200825142418115](../../images/SpringBoot//image-20200825142418115.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 124043f6..8d3ba5ed 100644 --- a/docs/SpringBoot/SpringBoot-ConfigurationProperties.md +++ b/docs/SpringBoot/SpringBoot-ConfigurationProperties.md @@ -33,7 +33,7 @@ public @interface ConfigurationPropertiesScan {} ## ConfigurationPropertiesScanRegistrar -![image-20200323094446756](../../images/SpringBoot/image-20200323094446756.png) +![image-20200323094446756](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323094446756.png) - debug 没有抓到后续补充 @@ -137,11 +137,11 @@ public @interface EnableConfigurationProperties { - 先看输入参数 **metadata** -![image-20200323134135926](../../images/SpringBoot/image-20200323134135926.png) +![image-20200323134135926](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323134135926.png) - getTypes 结果 -![image-20200323134325955](../../images/SpringBoot/image-20200323134325955.png) +![image-20200323134325955](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323134325955.png) - 源码开始,先找出刚才的对象`org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration` @@ -192,7 +192,7 @@ 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 @@ -301,15 +301,15 @@ public @interface EnableConfigurationProperties { - `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) - 此时数据还没有进去 @@ -319,7 +319,7 @@ public @interface EnableConfigurationProperties { 直接看结果 -![image-20200323105155998](../../images/SpringBoot/image-20200323105155998.png) +![image-20200323105155998](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323105155998.png) - 上述配置和我在配置文件中写的配置一致 @@ -361,7 +361,7 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { } ``` -![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 @@ -427,11 +427,11 @@ BindResult bind(ConfigurationPropertiesBean propertiesBean) { ``` -![image-20200323115408877](../../images/SpringBoot/image-20200323115408877.png) +![image-20200323115408877](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115408877.png) -![image-20200323115701118](../../images/SpringBoot/image-20200323115701118.png) +![image-20200323115701118](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115701118.png) -![image-20200323115711826](../../images/SpringBoot/image-20200323115711826.png) +![image-20200323115711826](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323115711826.png) ##### getBindHandler @@ -464,7 +464,7 @@ 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 @@ -498,7 +498,7 @@ 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 8430b12d..9da37e9e 100644 --- a/docs/SpringBoot/SpringBoot-LogSystem.md +++ b/docs/SpringBoot/SpringBoot-LogSystem.md @@ -19,7 +19,7 @@ - `org.springframework.boot.logging.java.JavaLoggingSystem` - ![image-20200323144523848](../../images/SpringBoot/image-20200323144523848.png) + ![image-20200323144523848](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323144523848.png) ```java static { @@ -125,7 +125,7 @@ private static LoggingSystem get(ClassLoader classLoader, String loggingSystemCl ``` -![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` @@ -133,7 +133,7 @@ private static LoggingSystem get(ClassLoader classLoader, String loggingSystemCl - 初始化之前 - ![image-20200323154205484](../../images/SpringBoot/image-20200323154205484.png) + ![image-20200323154205484](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323154205484.png) - 链路 @@ -344,9 +344,9 @@ private static LoggingSystem get(ClassLoader classLoader, String loggingSystemCl - 添加配置文件 - ![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](../../images/SpringBoot/image-20200323161522570.png) + ![image-20200323161522570](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323161522570.png) - 此时配置文件地址出现了 diff --git a/docs/SpringBoot/SpringBoot-application-load.md b/docs/SpringBoot/SpringBoot-application-load.md index e8641adb..06c60b45 100644 --- a/docs/SpringBoot/SpringBoot-application-load.md +++ b/docs/SpringBoot/SpringBoot-application-load.md @@ -9,17 +9,17 @@ 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) - 一步一步回上去看如何调用具体方法的 @@ -29,9 +29,9 @@ ### 调用过程 -![image-20200319082131146](../../images/SpringBoot/image-20200319082131146.png) +![image-20200319082131146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319082131146.png) -![image-20200319082544653](../../images/SpringBoot/image-20200319082544653.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` @@ -68,13 +68,13 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL - 搜索目标: `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](../../images/SpringBoot/image-20200319084151997.png) +![image-20200319084151997](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319084151997.png) 观察发现里面有一个`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) 初始化完成,后续进行解析了 @@ -110,7 +110,7 @@ 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 @@ -135,7 +135,7 @@ 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) 该方法采用循环每个路径下面都去尝试一遍 @@ -190,7 +190,7 @@ 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`解析同理不在次展开描述了 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 049ab306..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" @@ -53,7 +53,7 @@ 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() @@ -107,7 +107,7 @@ public @interface EnableAutoConfiguration { ``` - ![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";` @@ -131,11 +131,11 @@ public @interface EnableAutoConfiguration { org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ ``` -![image-20200320162835665](../../images/SpringBoot/image-20200320162835665.png) +![image-20200320162835665](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320162835665.png) 同样找一下 redis -![image-20200320163001728](../../images/SpringBoot/image-20200320163001728.png) +![image-20200320163001728](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320163001728.png) - 仔细看`org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration`类 @@ -213,13 +213,13 @@ public class RedisProperties { - `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` 存储了一些什么 -![image-20200320164145286](../../images/SpringBoot/image-20200320164145286.png) +![image-20200320164145286](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320164145286.png) 这里简单理解 @@ -271,7 +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 @@ -289,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) - 第一个是我自己写的一个测试用 @@ -341,7 +341,7 @@ 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) - 修改启动类 @@ -350,7 +350,7 @@ public class RedisProperties { ``` - ![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 @@ -418,7 +418,7 @@ 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`系列接口 @@ -426,7 +426,7 @@ public class RedisProperties { - `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 @@ -448,11 +448,11 @@ 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`** @@ -470,7 +470,7 @@ public class RedisProperties { ``` -![image-20200323083656670](../../images/SpringBoot/image-20200323083656670.png) +![image-20200323083656670](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323083656670.png) - 初始化完 @@ -478,7 +478,7 @@ public class RedisProperties { - `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) - 后续的一些行为相对简单,直接放个源码了. 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 7a13bcd3..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" @@ -215,7 +215,7 @@ public interface ServletResponse { 其主要部分的类图 如下。 -![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。其源码如下。 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 b3787c53..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 @@ -努力编写中... +# 一个简单的 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 index feb38883..e72a14d1 100644 --- a/docs/nacos/nacos-discovery.md +++ b/docs/nacos/nacos-discovery.md @@ -10,7 +10,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.alibaba.boot.nacos.discovery.autoconfigure.NacosDiscoveryAutoConfiguration ``` -找到注解`NacosDiscoveryAutoConfiguration` +找到类 `NacosDiscoveryAutoConfiguration` ```java @ConditionalOnProperty(name = NacosDiscoveryConstants.ENABLED, matchIfMissing = true) @@ -128,7 +128,7 @@ public static void registerGlobalNacosProperties(AnnotationAttributes attributes ``` -![image-20200821111938485](/images/nacos/image-20200821111938485.png) +![image-20200821111938485](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821111938485.png) ## registerNacosCommonBeans @@ -168,7 +168,7 @@ public static void registerInfrastructureBean(BeanDefinitionRegistry registry, 属性读取,从 application 配置文件中读取数据转换成 java 对象。 -![image-20200821132413628](/images/nacos/image-20200821132413628.png) +![image-20200821132413628](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821132413628.png) ## NacosDiscoveryAutoRegister @@ -228,11 +228,11 @@ public void onApplicationEvent(WebServerInitializedEvent event) { - 注册的参数 - ![image-20200821133350982](/images/nacos/image-20200821133350982.png) + ![image-20200821133350982](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821133350982.png) ## 服务注册 -![image-20200821133445090](/images/nacos/image-20200821133445090.png) +![image-20200821133445090](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/nacos/image-20200821133445090.png) - 注册一个实例 1. 将 instance 对象转换成 BeatInfo 对象 @@ -301,18 +301,18 @@ class BeatTask implements Runnable { try { // 与nacos进行一次rest请求交互 JSONObject result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled); - long interval = result.getIntValue("clientBeatInterval"); + long interval = result.get(CLIENT_BEAT_INTERVAL_FIELD).asLong(); boolean lightBeatEnabled = false; - if (result.containsKey(CommonParams.LIGHT_BEAT_ENABLED)) { - lightBeatEnabled = result.getBooleanValue(CommonParams.LIGHT_BEAT_ENABLED); + 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.containsKey(CommonParams.CODE)) { - code = result.getIntValue(CommonParams.CODE); + if (result.has(CommonParams.CODE)) { + code = result.get(CommonParams.CODE).asInt(); } // 如果nacos找不到当前实例, if (code == NamingResponseCode.RESOURCE_NOT_FOUND) { @@ -336,8 +336,12 @@ class BeatTask implements Runnable { 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); } - executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS); } } ``` @@ -359,47 +363,41 @@ class BeatTask implements Runnable { NacosException exception = new NacosException(); - if (servers != null && !servers.isEmpty()) { - - 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(); - } - } - - if (StringUtils.isNotBlank(nacosDomain)) { - for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; 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); - } - } - } - } + 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()); - NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", - api, servers, exception.getErrCode(), exception.getErrMsg()); + 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(); + } + } - throw new NacosException(exception.getErrCode(), "failed to req API:/api/" + api + " after all servers(" + servers + ") tried: " - + exception.getMessage()); + 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()); ``` **学习点** @@ -437,19 +435,24 @@ public void registerService(String serviceName, String groupName, Instance insta 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(9); + final Map params = new HashMap(32); params.put(CommonParams.NAMESPACE_ID, namespaceId); - params.put(CommonParams.SERVICE_NAME, serviceName); + params.put(CommonParams.SERVICE_NAME, groupedServiceName); params.put(CommonParams.GROUP_NAME, groupName); params.put(CommonParams.CLUSTER_NAME, instance.getClusterName()); - params.put("ip", instance.getIp()); - params.put("port", String.valueOf(instance.getPort())); - params.put("weight", String.valueOf(instance.getWeight())); - params.put("enable", String.valueOf(instance.isEnabled())); - params.put("healthy", String.valueOf(instance.isHealthy())); - params.put("ephemeral", String.valueOf(instance.isEphemeral())); - params.put("metadata", JSON.toJSONString(instance.getMetadata())); + 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); @@ -645,76 +648,41 @@ public Instance getInstance(String namespaceId, String serviceName, String clust ```java @CanDistro @PutMapping("/beat") -@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE) -public JSONObject beat(HttpServletRequest request) throws Exception { - - JSONObject result = new JSONObject(); - - result.put("clientBeatInterval", switchDomain.getClientBeatInterval()); - String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); - String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, - Constants.DEFAULT_NAMESPACE_ID); - String clusterName = WebUtils.optional(request, CommonParams.CLUSTER_NAME, - UtilsAndCommons.DEFAULT_CLUSTER_NAME); - String ip = WebUtils.optional(request, "ip", StringUtils.EMPTY); - int port = Integer.parseInt(WebUtils.optional(request, "port", "0")); - String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY); - - RsInfo clientBeat = null; - if (StringUtils.isNotBlank(beat)) { - clientBeat = JSON.parseObject(beat, RsInfo.class); - } - - if (clientBeat != null) { - if (StringUtils.isNotBlank(clientBeat.getCluster())) { - clusterName = clientBeat.getCluster(); - } +@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(); - } - - if (Loggers.SRV_LOG.isDebugEnabled()) { - Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}", clientBeat, serviceName); - } - // 获取实例 - Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port); - - if (instance == null) { - if (clientBeat == null) { - result.put(CommonParams.CODE, NamingResponseCode.RESOURCE_NOT_FOUND); - return result; } - instance = new Instance(); - instance.setPort(clientBeat.getPort()); - instance.setIp(clientBeat.getIp()); - instance.setWeight(clientBeat.getWeight()); - instance.setMetadata(clientBeat.getMetadata()); - instance.setClusterName(clusterName); - instance.setServiceName(serviceName); - instance.setInstanceId(instance.getInstanceId()); - instance.setEphemeral(clientBeat.isEphemeral()); - - serviceManager.registerInstance(namespaceId, serviceName, instance); - } - - Service service = serviceManager.getService(namespaceId, serviceName); - - if (service == null) { - throw new NacosException(NacosException.SERVER_ERROR, - "service not found: " + serviceName + "@" + namespaceId); - } - if (clientBeat == null) { - clientBeat = new RsInfo(); - clientBeat.setIp(ip); - clientBeat.setPort(port); - clientBeat.setCluster(clusterName); - } - // 处理心跳方法 - service.processClientBeat(clientBeat); - result.put(CommonParams.CODE, NamingResponseCode.OK); - result.put("clientBeatInterval", instance.getInstanceHeartBeatInterval()); - result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled()); - return result; + 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 fce59f93..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 376f575e..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 4cc9f8d5..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" index 297f8fc7..e15073d9 100644 Binary files "a/images/JDK1.8/JUC\345\205\250\351\207\217UML\345\234\260\345\233\276.png" 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" index 61f8bdf4..d2d303d3 100644 Binary files "a/images/Netty/ChannelHandler\347\273\204\344\273\266.png" 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" index a4e3e46a..bb55cde0 100644 Binary files "a/images/Netty/Channel\347\273\204\344\273\266.png" 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" index 9003f25b..b7477bda 100644 Binary files "a/images/Netty/Netty\347\232\204Channel\347\273\204\344\273\266.png" 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" index 8f9a67ea..a33f6771 100644 Binary files "a/images/Netty/Selector\345\222\214SelectionKey\345\222\214Channel\345\205\263\347\263\273\345\233\276.png" 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 index df8f77a3..009f0aae 100644 Binary files a/images/Netty/image_1595751597062.png and b/images/Netty/image_1595751597062.png differ diff --git a/images/Netty/image_1595752125587.png b/images/Netty/image_1595752125587.png index 957075eb..c391c3f3 100644 Binary files a/images/Netty/image_1595752125587.png and b/images/Netty/image_1595752125587.png differ diff --git a/images/Netty/image_1595756711656.png b/images/Netty/image_1595756711656.png index 6f6c9b8a..1d18336e 100644 Binary files a/images/Netty/image_1595756711656.png and b/images/Netty/image_1595756711656.png differ diff --git a/images/Netty/image_1595756928493.png b/images/Netty/image_1595756928493.png index 887d1a22..ad327281 100644 Binary files a/images/Netty/image_1595756928493.png and b/images/Netty/image_1595756928493.png differ diff --git a/images/Netty/image_1595757035360.png b/images/Netty/image_1595757035360.png index 57a4ccbd..1b20dc4d 100644 Binary files a/images/Netty/image_1595757035360.png and b/images/Netty/image_1595757035360.png differ diff --git a/images/Netty/image_1595757110003.png b/images/Netty/image_1595757110003.png index b50a840a..08a77572 100644 Binary files a/images/Netty/image_1595757110003.png and b/images/Netty/image_1595757110003.png differ diff --git a/images/Netty/image_1595757328715.png b/images/Netty/image_1595757328715.png index 6c8dd089..2826d793 100644 Binary files a/images/Netty/image_1595757328715.png and b/images/Netty/image_1595757328715.png differ diff --git a/images/Netty/image_1595758329809.png b/images/Netty/image_1595758329809.png index bc82915d..afb03073 100644 Binary files a/images/Netty/image_1595758329809.png 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 index d09af36c..8eb29912 100644 Binary files a/images/SpringBoot/image-20200320150642022.png 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 index b69f2119..31088ec5 100644 Binary files a/images/SpringBoot/image-20200324132053755.png and b/images/SpringBoot/image-20200324132053755.png differ diff --git a/images/SpringBoot/image-20200324133449749.png b/images/SpringBoot/image-20200324133449749.png index 1e9940d6..2b458daf 100644 Binary files a/images/SpringBoot/image-20200324133449749.png and b/images/SpringBoot/image-20200324133449749.png differ diff --git a/images/SpringBoot/image-20200324133901498.png b/images/SpringBoot/image-20200324133901498.png index 2ad835a8..80e9c262 100644 Binary files a/images/SpringBoot/image-20200324133901498.png and b/images/SpringBoot/image-20200324133901498.png differ diff --git a/images/SpringBoot/image-20200324134813642.png b/images/SpringBoot/image-20200324134813642.png index 978b8684..2e6afe10 100644 Binary files a/images/SpringBoot/image-20200324134813642.png and b/images/SpringBoot/image-20200324134813642.png differ diff --git a/images/SpringBoot/image-20200324134837762.png b/images/SpringBoot/image-20200324134837762.png index faba4b0f..f483cfa7 100644 Binary files a/images/SpringBoot/image-20200324134837762.png and b/images/SpringBoot/image-20200324134837762.png differ diff --git a/images/SpringBoot/image-20200325085209824.png b/images/SpringBoot/image-20200325085209824.png index 53765ea9..c45b35d6 100644 Binary files a/images/SpringBoot/image-20200325085209824.png and b/images/SpringBoot/image-20200325085209824.png differ diff --git a/images/SpringBoot/image-20200325085708416.png b/images/SpringBoot/image-20200325085708416.png index 1df2be96..3333d5fa 100644 Binary files a/images/SpringBoot/image-20200325085708416.png and b/images/SpringBoot/image-20200325085708416.png differ diff --git a/images/SpringBoot/image-20200325090451465.png b/images/SpringBoot/image-20200325090451465.png index b4314cbc..52ea8471 100644 Binary files a/images/SpringBoot/image-20200325090451465.png and b/images/SpringBoot/image-20200325090451465.png differ diff --git a/images/SpringBoot/image-20200325090738203.png b/images/SpringBoot/image-20200325090738203.png index 20550ed5..1521f697 100644 Binary files a/images/SpringBoot/image-20200325090738203.png and b/images/SpringBoot/image-20200325090738203.png differ diff --git a/images/SpringBoot/image-20200325090946470.png b/images/SpringBoot/image-20200325090946470.png index 51ed489b..30084391 100644 Binary files a/images/SpringBoot/image-20200325090946470.png and b/images/SpringBoot/image-20200325090946470.png differ diff --git a/images/SpringBoot/image-20200325091434110.png b/images/SpringBoot/image-20200325091434110.png index 4c1a6323..fd89bd52 100644 Binary files a/images/SpringBoot/image-20200325091434110.png and b/images/SpringBoot/image-20200325091434110.png differ diff --git a/images/SpringBoot/image-20200325093238025.png b/images/SpringBoot/image-20200325093238025.png index 4c08dbc5..cc3b9f33 100644 Binary files a/images/SpringBoot/image-20200325093238025.png and b/images/SpringBoot/image-20200325093238025.png differ diff --git a/images/SpringBoot/image-20200325093319391.png b/images/SpringBoot/image-20200325093319391.png index 0f6ec1f3..e4b4b0ff 100644 Binary files a/images/SpringBoot/image-20200325093319391.png and b/images/SpringBoot/image-20200325093319391.png differ diff --git a/images/SpringBoot/image-20200325094344562.png b/images/SpringBoot/image-20200325094344562.png index b6016e17..18add806 100644 Binary files a/images/SpringBoot/image-20200325094344562.png and b/images/SpringBoot/image-20200325094344562.png differ diff --git a/images/SpringBoot/image-20200325094421008.png b/images/SpringBoot/image-20200325094421008.png index a6777f83..1c24d9b5 100644 Binary files a/images/SpringBoot/image-20200325094421008.png and b/images/SpringBoot/image-20200325094421008.png differ diff --git a/images/SpringBoot/image-20200325095208523.png b/images/SpringBoot/image-20200325095208523.png index d5fbff3b..9f1cf5f7 100644 Binary files a/images/SpringBoot/image-20200325095208523.png and b/images/SpringBoot/image-20200325095208523.png differ diff --git a/images/SpringBoot/image-20200325100539794.png b/images/SpringBoot/image-20200325100539794.png index 182dff55..523597a1 100644 Binary files a/images/SpringBoot/image-20200325100539794.png and b/images/SpringBoot/image-20200325100539794.png differ diff --git a/images/SpringBoot/image-20200325101605773.png b/images/SpringBoot/image-20200325101605773.png index 7d7ac36d..6f2f2774 100644 Binary files a/images/SpringBoot/image-20200325101605773.png and b/images/SpringBoot/image-20200325101605773.png differ diff --git a/images/SpringBoot/image-20200325102045939.png b/images/SpringBoot/image-20200325102045939.png index 56cc6e31..9b891328 100644 Binary files a/images/SpringBoot/image-20200325102045939.png and b/images/SpringBoot/image-20200325102045939.png differ diff --git a/images/SpringBoot/image-20200515150256581.png b/images/SpringBoot/image-20200515150256581.png index a53c9633..d2d12171 100644 Binary files a/images/SpringBoot/image-20200515150256581.png and b/images/SpringBoot/image-20200515150256581.png differ diff --git a/images/SpringBoot/image-20200515160103338.png b/images/SpringBoot/image-20200515160103338.png index 6a880404..bd8d4a83 100644 Binary files a/images/SpringBoot/image-20200515160103338.png and b/images/SpringBoot/image-20200515160103338.png differ diff --git a/images/SpringBoot/image-20200518111931477.png b/images/SpringBoot/image-20200518111931477.png index 84f5ea77..07ce2e0b 100644 Binary files a/images/SpringBoot/image-20200518111931477.png and b/images/SpringBoot/image-20200518111931477.png differ diff --git a/images/SpringBoot/image-20200601170659521.png b/images/SpringBoot/image-20200601170659521.png index 15b34624..0ca1d743 100644 Binary files a/images/SpringBoot/image-20200601170659521.png and b/images/SpringBoot/image-20200601170659521.png differ diff --git a/images/SpringBoot/image-20200824085726621.png b/images/SpringBoot/image-20200824085726621.png index 26d75ea1..a981e5fb 100644 Binary files a/images/SpringBoot/image-20200824085726621.png and b/images/SpringBoot/image-20200824085726621.png differ diff --git a/images/SpringBoot/image-20200825084844709.png b/images/SpringBoot/image-20200825084844709.png index 700597aa..23cedbc5 100644 Binary files a/images/SpringBoot/image-20200825084844709.png and b/images/SpringBoot/image-20200825084844709.png differ diff --git a/images/SpringBoot/image-20200825092343271.png b/images/SpringBoot/image-20200825092343271.png index 9d5289e6..167586e1 100644 Binary files a/images/SpringBoot/image-20200825092343271.png and b/images/SpringBoot/image-20200825092343271.png differ diff --git a/images/SpringBoot/image-20200825140750035.png b/images/SpringBoot/image-20200825140750035.png index f24f1cea..5ab1164e 100644 Binary files a/images/SpringBoot/image-20200825140750035.png and b/images/SpringBoot/image-20200825140750035.png differ diff --git a/images/SpringBoot/image-20200825141506531.png b/images/SpringBoot/image-20200825141506531.png index 061160a6..bb046071 100644 Binary files a/images/SpringBoot/image-20200825141506531.png and b/images/SpringBoot/image-20200825141506531.png differ diff --git a/images/SpringBoot/image-20200825142332485.png b/images/SpringBoot/image-20200825142332485.png index 6d1e4bd0..04cb42e4 100644 Binary files a/images/SpringBoot/image-20200825142332485.png and b/images/SpringBoot/image-20200825142332485.png differ diff --git a/images/SpringBoot/image-20200825142418115.png b/images/SpringBoot/image-20200825142418115.png index 64261d40..3a67c3f3 100644 Binary files a/images/SpringBoot/image-20200825142418115.png 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/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 index 2333dc4f..650d965c 100644 Binary files a/images/mybatis/1575891988804.png and b/images/mybatis/1575891988804.png differ diff --git a/images/mybatis/1575892046692.png b/images/mybatis/1575892046692.png index 03c5c059..a1d4bd9e 100644 Binary files a/images/mybatis/1575892046692.png and b/images/mybatis/1575892046692.png differ diff --git a/images/mybatis/1575892167982.png b/images/mybatis/1575892167982.png index 10b0ef14..ea4fb6e3 100644 Binary files a/images/mybatis/1575892167982.png and b/images/mybatis/1575892167982.png differ diff --git a/images/mybatis/1575892414120.png b/images/mybatis/1575892414120.png index db2c6f93..6241c756 100644 Binary files a/images/mybatis/1575892414120.png and b/images/mybatis/1575892414120.png differ diff --git a/images/mybatis/1575892511471.png b/images/mybatis/1575892511471.png index 5b2e76b5..4ebb1089 100644 Binary files a/images/mybatis/1575892511471.png and b/images/mybatis/1575892511471.png differ diff --git a/images/mybatis/1575892645405.png b/images/mybatis/1575892645405.png index e0c1e753..9036107e 100644 Binary files a/images/mybatis/1575892645405.png and b/images/mybatis/1575892645405.png differ diff --git a/images/mybatis/1575892687076.png b/images/mybatis/1575892687076.png index 545d383e..3756b24a 100644 Binary files a/images/mybatis/1575892687076.png and b/images/mybatis/1575892687076.png differ diff --git a/images/mybatis/1575892763661.png b/images/mybatis/1575892763661.png index 71d67be3..7a2b5675 100644 Binary files a/images/mybatis/1575892763661.png and b/images/mybatis/1575892763661.png differ diff --git a/images/mybatis/1575894218362.png b/images/mybatis/1575894218362.png index 4648aa4b..7336cf28 100644 Binary files a/images/mybatis/1575894218362.png and b/images/mybatis/1575894218362.png differ diff --git a/images/mybatis/1576027453035.png b/images/mybatis/1576027453035.png index a78c57d0..bfa72e76 100644 Binary files a/images/mybatis/1576027453035.png and b/images/mybatis/1576027453035.png differ diff --git a/images/mybatis/1576027589468.png b/images/mybatis/1576027589468.png index 681ec275..b4a8a20b 100644 Binary files a/images/mybatis/1576027589468.png and b/images/mybatis/1576027589468.png differ diff --git a/images/mybatis/1576027736912.png b/images/mybatis/1576027736912.png index 8191dc69..3c65d402 100644 Binary files a/images/mybatis/1576027736912.png and b/images/mybatis/1576027736912.png differ diff --git a/images/mybatis/1576028186530.png b/images/mybatis/1576028186530.png index 0a5b224f..c9aeb0f6 100644 Binary files a/images/mybatis/1576028186530.png and b/images/mybatis/1576028186530.png differ diff --git a/images/mybatis/1576028554094.png b/images/mybatis/1576028554094.png index 040c9155..f422dcbf 100644 Binary files a/images/mybatis/1576028554094.png and b/images/mybatis/1576028554094.png differ diff --git a/images/mybatis/1576028709743.png b/images/mybatis/1576028709743.png index ad567641..d535661f 100644 Binary files a/images/mybatis/1576028709743.png and b/images/mybatis/1576028709743.png differ diff --git a/images/mybatis/1576041628806.png b/images/mybatis/1576041628806.png index 8b4d9d4a..9c92b76e 100644 Binary files a/images/mybatis/1576041628806.png and b/images/mybatis/1576041628806.png differ diff --git a/images/mybatis/1576041889664.png b/images/mybatis/1576041889664.png index 6b6a8449..026a6c88 100644 Binary files a/images/mybatis/1576041889664.png and b/images/mybatis/1576041889664.png differ diff --git a/images/mybatis/1576050247445.png b/images/mybatis/1576050247445.png index 2f6b0c5e..92914d87 100644 Binary files a/images/mybatis/1576050247445.png and b/images/mybatis/1576050247445.png differ diff --git a/images/mybatis/1576050482190.png b/images/mybatis/1576050482190.png index a4ea8bd9..d184db68 100644 Binary files a/images/mybatis/1576050482190.png and b/images/mybatis/1576050482190.png differ diff --git a/images/mybatis/1576050580581.png b/images/mybatis/1576050580581.png index a0ad4a80..76deab8f 100644 Binary files a/images/mybatis/1576050580581.png and b/images/mybatis/1576050580581.png differ diff --git a/images/mybatis/1576050742205.png b/images/mybatis/1576050742205.png index c9fc3fce..5702d23c 100644 Binary files a/images/mybatis/1576050742205.png and b/images/mybatis/1576050742205.png differ diff --git a/images/mybatis/1576110788523.png b/images/mybatis/1576110788523.png index 737a2c78..bfaa7470 100644 Binary files a/images/mybatis/1576110788523.png and b/images/mybatis/1576110788523.png differ diff --git a/images/mybatis/1576111307305.png b/images/mybatis/1576111307305.png index 31429b73..f0b2df5f 100644 Binary files a/images/mybatis/1576111307305.png and b/images/mybatis/1576111307305.png differ diff --git a/images/mybatis/1576112853347.png b/images/mybatis/1576112853347.png index 209c35bc..8a1acc38 100644 Binary files a/images/mybatis/1576112853347.png and b/images/mybatis/1576112853347.png differ diff --git a/images/mybatis/1576112946984.png b/images/mybatis/1576112946984.png index d21ed19c..3ed1d80f 100644 Binary files a/images/mybatis/1576112946984.png and b/images/mybatis/1576112946984.png differ diff --git a/images/mybatis/1576113272209.png b/images/mybatis/1576113272209.png index c61b1b29..bad80d7b 100644 Binary files a/images/mybatis/1576113272209.png and b/images/mybatis/1576113272209.png differ diff --git a/images/mybatis/1576113287640.png b/images/mybatis/1576113287640.png index 938f6478..24e18c88 100644 Binary files a/images/mybatis/1576113287640.png and b/images/mybatis/1576113287640.png differ diff --git a/images/mybatis/1576113345527.png b/images/mybatis/1576113345527.png index 9ad994b9..7d41947d 100644 Binary files a/images/mybatis/1576113345527.png and b/images/mybatis/1576113345527.png differ diff --git a/images/mybatis/1576113398394.png b/images/mybatis/1576113398394.png index 9f5428e2..7fde3b8b 100644 Binary files a/images/mybatis/1576113398394.png and b/images/mybatis/1576113398394.png differ diff --git a/images/mybatis/1576113864895.png b/images/mybatis/1576113864895.png index d3b970cb..6448c83b 100644 Binary files a/images/mybatis/1576113864895.png and b/images/mybatis/1576113864895.png differ diff --git a/images/mybatis/1576114794663.png b/images/mybatis/1576114794663.png index 657d5ac9..13a95bcf 100644 Binary files a/images/mybatis/1576114794663.png and b/images/mybatis/1576114794663.png differ diff --git a/images/mybatis/1576114876295.png b/images/mybatis/1576114876295.png index 368ee99d..c0aa8253 100644 Binary files a/images/mybatis/1576114876295.png and b/images/mybatis/1576114876295.png differ diff --git a/images/mybatis/1576114996613.png b/images/mybatis/1576114996613.png index 3bbe2a29..987c34ef 100644 Binary files a/images/mybatis/1576114996613.png and b/images/mybatis/1576114996613.png differ diff --git a/images/mybatis/1576117177349.png b/images/mybatis/1576117177349.png index ea3b6382..925aaeb8 100644 Binary files a/images/mybatis/1576117177349.png and b/images/mybatis/1576117177349.png differ diff --git a/images/mybatis/1576117195387.png b/images/mybatis/1576117195387.png index 617257af..0ea6e42e 100644 Binary files a/images/mybatis/1576117195387.png and b/images/mybatis/1576117195387.png differ diff --git a/images/mybatis/1576117304942.png b/images/mybatis/1576117304942.png index 2e2f3345..77a592fb 100644 Binary files a/images/mybatis/1576117304942.png and b/images/mybatis/1576117304942.png differ diff --git a/images/mybatis/1576311527726.png b/images/mybatis/1576311527726.png index 98d4deeb..0461177d 100644 Binary files a/images/mybatis/1576311527726.png and b/images/mybatis/1576311527726.png differ diff --git a/images/mybatis/1576311999030.png b/images/mybatis/1576311999030.png index d59cc034..65de96aa 100644 Binary files a/images/mybatis/1576311999030.png and b/images/mybatis/1576311999030.png differ diff --git a/images/mybatis/1576312524112.png b/images/mybatis/1576312524112.png index f5d53149..fd0cd471 100644 Binary files a/images/mybatis/1576312524112.png and b/images/mybatis/1576312524112.png differ diff --git a/images/mybatis/1576312612783.png b/images/mybatis/1576312612783.png index 4daa74eb..145141b1 100644 Binary files a/images/mybatis/1576312612783.png and b/images/mybatis/1576312612783.png differ diff --git a/images/mybatis/1576312777050.png b/images/mybatis/1576312777050.png index 43000092..8aabd6a5 100644 Binary files a/images/mybatis/1576312777050.png and b/images/mybatis/1576312777050.png differ diff --git a/images/mybatis/1576313598939.png b/images/mybatis/1576313598939.png index c80758bd..1c8593fe 100644 Binary files a/images/mybatis/1576313598939.png 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 index f944799f..fbd95a96 100644 Binary files a/images/mybatis/image-20191217103309934.png and b/images/mybatis/image-20191217103309934.png differ diff --git a/images/mybatis/image-20191217104008186.png b/images/mybatis/image-20191217104008186.png index 76f4cc09..2950f3cc 100644 Binary files a/images/mybatis/image-20191217104008186.png and b/images/mybatis/image-20191217104008186.png differ diff --git a/images/mybatis/image-20191217104450495.png b/images/mybatis/image-20191217104450495.png index 54eb2066..c37668bc 100644 Binary files a/images/mybatis/image-20191217104450495.png and b/images/mybatis/image-20191217104450495.png differ diff --git a/images/mybatis/image-20191217143939247.png b/images/mybatis/image-20191217143939247.png index 26925531..b3ece9a5 100644 Binary files a/images/mybatis/image-20191217143939247.png and b/images/mybatis/image-20191217143939247.png differ diff --git a/images/mybatis/image-20191217144453261.png b/images/mybatis/image-20191217144453261.png index d6eade58..25d623c1 100644 Binary files a/images/mybatis/image-20191217144453261.png and b/images/mybatis/image-20191217144453261.png differ diff --git a/images/mybatis/image-20191217144739434.png b/images/mybatis/image-20191217144739434.png index e7f66186..65ac1c87 100644 Binary files a/images/mybatis/image-20191217144739434.png and b/images/mybatis/image-20191217144739434.png differ diff --git a/images/mybatis/image-20191217145051629.png b/images/mybatis/image-20191217145051629.png index ab9bb774..c371042c 100644 Binary files a/images/mybatis/image-20191217145051629.png and b/images/mybatis/image-20191217145051629.png differ diff --git a/images/mybatis/image-20191217145607956.png b/images/mybatis/image-20191217145607956.png index 75f421d0..aefd99a4 100644 Binary files a/images/mybatis/image-20191217145607956.png and b/images/mybatis/image-20191217145607956.png differ diff --git a/images/mybatis/image-20191217183853550.png b/images/mybatis/image-20191217183853550.png index 806d2770..9b0b5b97 100644 Binary files a/images/mybatis/image-20191217183853550.png and b/images/mybatis/image-20191217183853550.png differ diff --git a/images/mybatis/image-20191218082628696.png b/images/mybatis/image-20191218082628696.png index e335480d..751589b2 100644 Binary files a/images/mybatis/image-20191218082628696.png 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 index 12080c8f..a4785bcc 100644 Binary files a/images/mybatis/image-20191219083223084.png 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 index ebdcc674..d0624f69 100644 Binary files a/images/mybatis/image-20191219151245509.png 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 index 421a246f..f0735363 100644 Binary files a/images/mybatis/image-20191219162402291.png 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 index ff27ea0d..fc882310 100644 Binary files a/images/nacos/image-20200821111938485.png and b/images/nacos/image-20200821111938485.png differ diff --git a/images/nacos/image-20200821132413628.png b/images/nacos/image-20200821132413628.png index 22725bd3..03e329d4 100644 Binary files a/images/nacos/image-20200821132413628.png and b/images/nacos/image-20200821132413628.png differ diff --git a/images/nacos/image-20200821133350982.png b/images/nacos/image-20200821133350982.png index 23a74f76..956d4d55 100644 Binary files a/images/nacos/image-20200821133350982.png and b/images/nacos/image-20200821133350982.png differ diff --git a/images/nacos/image-20200821133445090.png b/images/nacos/image-20200821133445090.png index 9d59fdd5..dfba59a3 100644 Binary files a/images/nacos/image-20200821133445090.png and b/images/nacos/image-20200821133445090.png differ diff --git a/images/pdf.png b/images/pdf.png index 3ed6d14a..7d7e8f95 100644 Binary files a/images/pdf.png and b/images/pdf.png differ diff --git a/images/qrcode-for-doocs.jpg b/images/qrcode-for-doocs.jpg index bd1db5d1..79614411 100644 Binary files a/images/qrcode-for-doocs.jpg and b/images/qrcode-for-doocs.jpg differ diff --git a/images/qrcode-for-yanglbme.jpg b/images/qrcode-for-yanglbme.jpg index 5bd385bc..709a15f8 100644 Binary files a/images/qrcode-for-yanglbme.jpg 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 index b3987ed2..ae9d6d53 100644 Binary files a/images/spring/BeanFactory.png and b/images/spring/BeanFactory.png differ diff --git a/images/spring/BeanNameGenerator.png b/images/spring/BeanNameGenerator.png index 78523d4c..20cf2319 100644 Binary files a/images/spring/BeanNameGenerator.png and b/images/spring/BeanNameGenerator.png differ diff --git a/images/spring/DateTimeFormatAnnotationFormatterFactory.png b/images/spring/DateTimeFormatAnnotationFormatterFactory.png index e34488e8..16ab6720 100644 Binary files a/images/spring/DateTimeFormatAnnotationFormatterFactory.png and b/images/spring/DateTimeFormatAnnotationFormatterFactory.png differ diff --git a/images/spring/Mergeable.png b/images/spring/Mergeable.png index 21f1b5cc..473e2208 100644 Binary files a/images/spring/Mergeable.png and b/images/spring/Mergeable.png differ diff --git a/images/spring/MethodOverride.png b/images/spring/MethodOverride.png index f475dff9..0529d2bc 100644 Binary files a/images/spring/MethodOverride.png and b/images/spring/MethodOverride.png differ diff --git a/images/spring/MultiValueMap.png b/images/spring/MultiValueMap.png index 0b637429..f6c1773e 100644 Binary files a/images/spring/MultiValueMap.png 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" index ca419fe1..bd3ad9cc 100644 Binary files "a/images/spring/MutablePropertyValues-\346\236\204\351\200\240.png" 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 index ac3e1dd3..f70de182 100644 Binary files a/images/spring/Parser.png and b/images/spring/Parser.png differ diff --git a/images/spring/PropertyPlaceholderConfigurerResolver.png b/images/spring/PropertyPlaceholderConfigurerResolver.png index fb6a9261..4e34347e 100644 Binary files a/images/spring/PropertyPlaceholderConfigurerResolver.png and b/images/spring/PropertyPlaceholderConfigurerResolver.png differ diff --git a/images/spring/PropertySource.png b/images/spring/PropertySource.png index 46b22a7d..a3f4769c 100644 Binary files a/images/spring/PropertySource.png and b/images/spring/PropertySource.png differ diff --git a/images/spring/PropertyValue.png b/images/spring/PropertyValue.png index 5580ae3d..3d4fb811 100644 Binary files a/images/spring/PropertyValue.png and b/images/spring/PropertyValue.png differ diff --git a/images/spring/PropertyValues.png b/images/spring/PropertyValues.png index f40e83c2..8686ef3d 100644 Binary files a/images/spring/PropertyValues.png and b/images/spring/PropertyValues.png differ diff --git a/images/spring/RootBeanDefinition.png b/images/spring/RootBeanDefinition.png index a028b4db..4463b34e 100644 Binary files a/images/spring/RootBeanDefinition.png and b/images/spring/RootBeanDefinition.png differ diff --git a/images/spring/SystemPropertyUtils-resolvePlaceholders.png b/images/spring/SystemPropertyUtils-resolvePlaceholders.png index 15e8a834..3bb575ca 100644 Binary files a/images/spring/SystemPropertyUtils-resolvePlaceholders.png and b/images/spring/SystemPropertyUtils-resolvePlaceholders.png differ diff --git a/images/spring/TemplateAwareExpressionParser.png b/images/spring/TemplateAwareExpressionParser.png index 05224a8e..92008ec8 100644 Binary files a/images/spring/TemplateAwareExpressionParser.png 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 index f29921d8..3183b1f1 100644 Binary files a/images/spring/image-20200728094658684.png and b/images/spring/image-20200728094658684.png differ diff --git a/images/spring/image-20200728105926218.png b/images/spring/image-20200728105926218.png index f3a177eb..7e48c9b8 100644 Binary files a/images/spring/image-20200728105926218.png and b/images/spring/image-20200728105926218.png differ diff --git a/images/spring/image-20200728133037075.png b/images/spring/image-20200728133037075.png index 8c7dc713..a69d8155 100644 Binary files a/images/spring/image-20200728133037075.png and b/images/spring/image-20200728133037075.png differ diff --git a/images/spring/image-20200729090322058.png b/images/spring/image-20200729090322058.png index 94980149..05254ea4 100644 Binary files a/images/spring/image-20200729090322058.png and b/images/spring/image-20200729090322058.png differ diff --git a/images/spring/image-20200729144622440.png b/images/spring/image-20200729144622440.png index fa1ec1ba..5ce24595 100644 Binary files a/images/spring/image-20200729144622440.png and b/images/spring/image-20200729144622440.png differ diff --git a/images/spring/image-20200729145518089.png b/images/spring/image-20200729145518089.png index 39fca74e..167ede2d 100644 Binary files a/images/spring/image-20200729145518089.png and b/images/spring/image-20200729145518089.png differ diff --git a/images/spring/image-20200729145637688.png b/images/spring/image-20200729145637688.png index 6366a113..1e91de17 100644 Binary files a/images/spring/image-20200729145637688.png and b/images/spring/image-20200729145637688.png differ diff --git a/images/spring/image-20200729145835608.png b/images/spring/image-20200729145835608.png index 2c096590..f0345641 100644 Binary files a/images/spring/image-20200729145835608.png and b/images/spring/image-20200729145835608.png differ diff --git a/images/spring/image-20200729160650401.png b/images/spring/image-20200729160650401.png index 850dfcaf..4dab856c 100644 Binary files a/images/spring/image-20200729160650401.png and b/images/spring/image-20200729160650401.png differ diff --git a/images/spring/image-20200729161647214.png b/images/spring/image-20200729161647214.png index 02c0653b..d7c3bf14 100644 Binary files a/images/spring/image-20200729161647214.png and b/images/spring/image-20200729161647214.png differ diff --git a/images/spring/image-20200729162023837.png b/images/spring/image-20200729162023837.png index c904c271..8799b61f 100644 Binary files a/images/spring/image-20200729162023837.png and b/images/spring/image-20200729162023837.png differ diff --git a/images/spring/image-20200729163303000.png b/images/spring/image-20200729163303000.png index f3d58935..9e66cf8a 100644 Binary files a/images/spring/image-20200729163303000.png and b/images/spring/image-20200729163303000.png differ diff --git a/images/spring/image-20200824094154847.png b/images/spring/image-20200824094154847.png index c6c2d6ad..9df88884 100644 Binary files a/images/spring/image-20200824094154847.png and b/images/spring/image-20200824094154847.png differ diff --git a/images/spring/image-20200824104529315.png b/images/spring/image-20200824104529315.png index 67ea81b3..1b58a1bd 100644 Binary files a/images/spring/image-20200824104529315.png and b/images/spring/image-20200824104529315.png differ diff --git a/images/spring/image-20200902102912716.png b/images/spring/image-20200902102912716.png index e0620b78..bf76be49 100644 Binary files a/images/spring/image-20200902102912716.png and b/images/spring/image-20200902102912716.png differ diff --git a/images/spring/image-20200902103154580.png b/images/spring/image-20200902103154580.png index 984333cd..283a0c0a 100644 Binary files a/images/spring/image-20200902103154580.png and b/images/spring/image-20200902103154580.png differ diff --git a/images/spring/image-20200902105454958.png b/images/spring/image-20200902105454958.png index ef2c3126..32e1cff4 100644 Binary files a/images/spring/image-20200902105454958.png and b/images/spring/image-20200902105454958.png differ diff --git a/images/spring/image-20200903091759451.png b/images/spring/image-20200903091759451.png index 4189051c..28b94bf1 100644 Binary files a/images/spring/image-20200903091759451.png and b/images/spring/image-20200903091759451.png differ diff --git a/images/spring/image-20200903111128603.png b/images/spring/image-20200903111128603.png index 9dba6908..04757f8f 100644 Binary files a/images/spring/image-20200903111128603.png and b/images/spring/image-20200903111128603.png differ diff --git a/images/spring/image-20200903150738285.png b/images/spring/image-20200903150738285.png index 4bff15ca..78316844 100644 Binary files a/images/spring/image-20200903150738285.png and b/images/spring/image-20200903150738285.png differ diff --git a/images/spring/image-20200903150930186.png b/images/spring/image-20200903150930186.png index fe8f4bb2..309d0965 100644 Binary files a/images/spring/image-20200903150930186.png and b/images/spring/image-20200903150930186.png differ diff --git a/images/spring/image-20200903153057321.png b/images/spring/image-20200903153057321.png index fce39bc3..57e7454f 100644 Binary files a/images/spring/image-20200903153057321.png and b/images/spring/image-20200903153057321.png differ diff --git a/images/spring/image-20200903153432559.png b/images/spring/image-20200903153432559.png index 5dfdf3e4..9cfb6f32 100644 Binary files a/images/spring/image-20200903153432559.png and b/images/spring/image-20200903153432559.png differ diff --git a/images/spring/image-20200903153533141.png b/images/spring/image-20200903153533141.png index b3088088..30f4e8fb 100644 Binary files a/images/spring/image-20200903153533141.png and b/images/spring/image-20200903153533141.png differ diff --git a/images/spring/image-20200903153617353.png b/images/spring/image-20200903153617353.png index 75e2f91c..8bef5fea 100644 Binary files a/images/spring/image-20200903153617353.png 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 index 4a649931..fe3ef068 100644 Binary files a/images/springMVC/HandlerMapping.png 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 index 9d8bcd31..a4415744 100644 Binary files a/images/springMVC/clazz/image-20200918130340555.png and b/images/springMVC/clazz/image-20200918130340555.png differ diff --git a/images/springMVC/image-20200915135933146.png b/images/springMVC/image-20200915135933146.png index 18f63ae3..bb1b44ad 100644 Binary files a/images/springMVC/image-20200915135933146.png 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 fefb1cda..00000000 --- a/index.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - 读尽天下源码,心中自然无码 - - - - - - - - - - - - - -
本系列知识由 Doocs 开源社区总结发布
- - - - - - - - - - - - - - - - \ 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 + } +}