From ea18242b79fc510d5d20eb7d6386b94c235c16c9 Mon Sep 17 00:00:00 2001 From: yanglbme Date: Mon, 14 Apr 2025 21:44:42 +0800 Subject: [PATCH 1/5] chore: build site with vitepress --- .github/workflows/sync.yml | 46 +- .gitignore | 5 +- CNAME | 1 - README.md | 4 - docs/.vitepress/config.mts | 525 +++ ...347\232\204SPI\346\234\272\345\210\266.md" | 2 +- ...64\344\275\223\346\236\266\346\236\204.md" | 4 +- ...41\345\235\227\347\256\200\346\236\220.md" | 4 +- ...37\350\275\275\345\235\207\350\241\241.md" | 6 +- ...41\345\235\227\347\256\200\346\236\220.md" | 8 +- ...2\204Zookeeper\345\256\236\347\216\260.md" | 4 +- ...41\345\235\227\347\256\200\346\236\220.md" | 6 +- docs/JDK/basic/Thread.md | 2 +- docs/JDK/basic/ThreadLocal.md | 2 +- docs/JDK/collection/ArrayList.md | 4 +- ...13\346\261\240\347\273\204\344\273\266.md" | 4 +- ...50\351\207\217\347\261\273\345\233\276.md" | 2 +- ...ck\351\224\201\347\273\204\344\273\266.md" | 4 +- ...55\347\232\204\345\272\224\347\224\250.md" | 2 +- ...(\345\210\233\345\273\272\345\236\213).md" | 2 +- ...(\347\273\223\346\236\204\345\236\213).md" | 2 +- ...(\350\241\214\344\270\272\345\236\213).md" | 8 +- ...52\346\210\221\344\277\256\345\205\273.md" | 888 ----- ...212Transaction\346\250\241\345\235\227.md" | 2 +- ...23\345\255\230\346\250\241\345\235\227.md" | 2 +- .../Mybatis-Reflector.md" | 22 +- ...\201SqlSession\347\273\204\344\273\266.md" | 2 +- .../Mybatis-DataSource.md" | 6 +- .../Mybatis-DynamicSqlSource.md" | 32 +- .../Mybatis-GenericTokenParser.md" | 2 +- .../Mybatis-MapperMethod.md" | 4 +- .../Mybatis-ObjectWrapper.md" | 2 +- .../Mybatis-ParamNameResolver.md" | 12 +- .../Mybatis-SqlCommand.md" | 4 +- ...66\346\236\204\350\256\276\350\256\241.md" | 2 +- ...47\350\203\275\344\271\213\351\201\223.md" | 2 +- .../IO\346\250\241\345\236\213.md" | 12 +- ...217\212Channel\347\273\204\344\273\266.md" | 4 +- ...13\345\217\212\345\257\271\346\257\224.md" | 14 +- ...76\346\211\257\344\270\200\351\201\215.md" | 10 +- ...ChannelHandler\347\273\204\344\273\266.md" | 2 +- ...\222\214Unsafe\347\273\204\344\273\266.md" | 2 +- .../HashedWheelTimer&schedule.md" | 16 +- ...63\350\257\267\345\206\205\345\255\230.md" | 12 +- ...43\345\206\263\346\226\271\346\241\210.md" | 2 +- ...67\347\253\257\345\274\200\345\217\221.md" | 2 +- ...41\347\253\257\345\274\200\345\217\221.md" | 8 +- ...02\344\275\225\347\224\237\346\225\210.md" | 4 +- ...52\347\216\257\344\276\235\350\265\226.md" | 2 +- docs/Spring/JDBC/Spring-jdbc.md | 60 +- docs/Spring/RMI/Spring-RMI.md | 42 +- .../Spring-spring-components.md" | 2 +- ...55\347\232\204\345\220\257\345\212\250.md" | 4 +- docs/Spring/SpringMVC/SpringMVC-CROS.md | 16 +- ...41\344\270\216\345\256\236\347\216\260.md" | 8 +- ...13\345\212\241\345\244\204\347\220\206.md" | 2 +- ...41\344\270\216\345\256\236\347\216\260.md" | 4 +- ...64\344\275\223\350\204\211\347\273\234.md" | 32 +- ...\344\270\200\345\244\251(\344\270\212).md" | 68 +- docs/Spring/TX/Spring-transaction.md | 24 +- docs/Spring/clazz/Spring-AnnotationUtils.md | 12 +- .../clazz/Spring-ApplicationListener.md | 8 +- .../clazz/Spring-BeanFactoryPostProcessor.md | 12 +- .../clazz/Spring-Custom-attribute-resolver.md | 18 +- .../clazz/Spring-Custom-label-resolution.md | 18 +- .../Spring-DefaultSingletonBeanRegistry.md | 2 +- docs/Spring/clazz/Spring-EntityResolver.md | 6 +- docs/Spring/clazz/Spring-MessageSource.md | 8 +- docs/Spring/clazz/Spring-Metadata.md | 4 +- docs/Spring/clazz/Spring-MethodOverride.md | 2 +- docs/Spring/clazz/Spring-MultiValueMap.md | 2 +- docs/Spring/clazz/Spring-OrderComparator.md | 4 +- docs/Spring/clazz/Spring-PropertySources.md | 2 +- docs/Spring/clazz/Spring-beanFactory.md | 28 +- docs/Spring/clazz/Spring-scan.md | 8 +- docs/Spring/clazz/format/Spring-Parser.md | 2 +- docs/Spring/message/Spring-EnableJms.md | 4 +- .../Spring/message/Spring-MessageConverter.md | 6 +- docs/Spring/mvc/Spring-MVC-HandlerMapping.md | 2 +- docs/Spring/mvc/Spring-mvc-MappingRegistry.md | 2 +- ...pringBootBatch\346\272\220\347\240\201.md" | 30 +- ...01\347\250\213\350\241\245\345\205\205.md" | 22 +- ...50\346\210\267\350\256\244\350\257\201.md" | 240 +- docs/index.md | 45 + package-lock.json | 3444 +++++++++-------- package.json | 20 +- 86 files changed, 2935 insertions(+), 2997 deletions(-) delete mode 100644 CNAME create mode 100644 docs/.vitepress/config.mts delete mode 100644 "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" create mode 100644 docs/index.md diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index f459835f..bd464ec5 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -1,13 +1,47 @@ -name: Sync +name: Build and deploy on: push: branches: [main] + workflow_dispatch: jobs: + docs: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Install dependencies + run: npm ci + + - name: Build with VitePress + run: npm run docs:build + + - name: Deploy to GitHub Pages + uses: crazy-max/ghaction-github-pages@v4 + with: + target_branch: gh-pages + build_dir: docs/.vitepress/dist + fqdn: schunter.doocs.org + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + build: runs-on: ubuntu-latest if: github.repository == 'doocs/source-code-hunter' + needs: docs steps: - name: Sync to Gitee uses: wearerequired/git-mirror-action@master @@ -15,12 +49,4 @@ jobs: 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 + destination-repo: git@gitee.com:Doocs/source-code-hunter.git \ No newline at end of file diff --git a/.gitignore b/.gitignore index dc3d55a3..8f014948 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,7 @@ local.properties gradle.properties *.gh .vscode -/node_modules \ No newline at end of file +/node_modules + +docs/.vitepress/dist +docs/.vitepress/cache \ No newline at end of file diff --git a/CNAME b/CNAME deleted file mode 100644 index 2531526a..00000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -schunter.doocs.org diff --git a/README.md b/README.md index f207a31a..dc6aadeb 100644 --- a/README.md +++ b/README.md @@ -332,10 +332,6 @@ - [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md) -### 编码规范 - -- [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md) - ### 设计模式 - [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)]() diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 00000000..76409e5a --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,525 @@ +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' }] + ], + 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: 'SpringBootBatch', + // collapsed: true, + // items: [ + // { text: 'SpringBootBatch 源码', link: '/SpringBootBatch/SpringBootBatch源码' }, + // ], + // }, + { + 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/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/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 21663ed7..7d866af9 100644 --- a/docs/JDK/collection/ArrayList.md +++ b/docs/JDK/collection/ArrayList.md @@ -121,7 +121,7 @@ private void rangeCheck(int index) { } ``` -![arraylist添加集合的方法](../../../images/JDK1.8/arraylist的add方法.png) +![arraylist添加集合的方法](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/arraylist的add方法.png) 先判断当前数组元素是否满了,如果塞满了就会进行数组扩容,随后进行数组拷贝。 @@ -207,7 +207,7 @@ public E remove(int index) { } ``` -![arrayList删除元素的过程.png](../../../images/JDK1.8/arrayList删除元素的过程.png) +![arrayList删除元素的过程.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/arrayList删除元素的过程.png) 1、先进行下标是否越界的判断,获取 index 处的元素值(这是要删除的值) 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 9c4c12e1..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/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 c64a23e9..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`方法获取了空参构造 @@ -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/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 ccd98e91..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`中 @@ -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-DynamicSqlSource.md" "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DynamicSqlSource.md" index fbaf1833..141ce92e 100644 --- "a/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DynamicSqlSource.md" +++ "b/docs/Mybatis/\346\240\270\345\277\203\345\244\204\347\220\206\345\261\202/Mybatis-DynamicSqlSource.md" @@ -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,7 +250,7 @@ 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 { @@ -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/Mybatis-GenericTokenParser.md" "b/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/Mybatis-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-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 796ecab8..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,6 +1,6 @@ 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 的奥秘。 @@ -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 c18b8321..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 操作完成了。 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/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 6047cd56..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" @@ -6,7 +6,7 @@ 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) 假设客户端依次发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。 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/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/\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" index d86972a1..b1057f2c 100644 --- "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" @@ -265,4 +265,4 @@ protected Object getSingleton(String beanName, boolean allowEarlyReference) { > 该历程仅代表当前这个项目工程 -![image](/images/spring/循环依赖.png) +![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..bdc8189d 100644 --- a/docs/Spring/JDBC/Spring-jdbc.md +++ b/docs/Spring/JDBC/Spring-jdbc.md @@ -268,43 +268,41 @@ ```java public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException { - if (con == null) { + 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; } - 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); } + // 处理其他情况 + doCloseConnection(con, dataSource); +} ``` -### org.springframework.transaction.support.ResourceHolderSupport - -链接数 +- `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--; - } +/** + * 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--; +} ``` ## 查询解析 @@ -427,7 +425,7 @@ public void setDataSource(@Nullable DataSource dataSource) { ``` -![image-20200109150841916](../../../images/spring/image-20200109150841916.png) +![image-20200109150841916](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200109150841916.png) 这样就可以获取到了 diff --git a/docs/Spring/RMI/Spring-RMI.md b/docs/Spring/RMI/Spring-RMI.md index 0e63c4ae..a9597980 100644 --- a/docs/Spring/RMI/Spring-RMI.md +++ b/docs/Spring/RMI/Spring-RMI.md @@ -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\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/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" index e8dd6ba4..bcb302b8 100644 --- "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" @@ -8,7 +8,7 @@ 本文会先大概介绍下这些知识点 👇 -![image-20211213224509864](../../../images/spring/image-20211213224509864.png) +![image-20211213224509864](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213224509864.png) ### 印象中的 Spring @@ -26,7 +26,7 @@ 把 Spring 浓缩一下,就有了这么一点小东西 🐖 -![image-20211213224920994](../../../images/spring/image-20211213224920994.png) +![image-20211213224920994](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213224920994.png) 想了下,我们用 Spring ,其中最主要的一点,就是用它来帮我们管理,创建这个 Bean 。 @@ -34,7 +34,7 @@ ### Bean 解析流程 -![image-20211213225044814](../../../images/spring/image-20211213225044814.png) +![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 。 @@ -62,7 +62,7 @@ 那么,结合我们从原料中获取的重要属性之一的 beanClass ,我们可以画出这么一张图 👇 -![image-20211213225124831](../../../images/spring/image-20211213225124831.png) +![image-20211213225124831](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225124831.png) 那么我们再来看看这个 BeanFactory 叭 😄 @@ -70,7 +70,7 @@ 先来看看 作为 IOC 容器的**根接口** 的 BeanFactory 提供了什么方法吧 👇 -![image-20210904162844126](../../../images/spring/image-20210904162844126.png) +![image-20210904162844126](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904162844126.png) 主要是这个 getBean 方法,以及 **别名获取**,**类型获取** 方法和其他一些判断方法如 :**单例**,**多例**,**类型匹配**,**包含 bean** @@ -80,7 +80,7 @@ 看源码的时候,一般就直接看这个**默认**接口 如这里的 DefaultListableBeanFactory -![image-20210904161436139](../../../images/spring/image-20210904161436139.png) +![image-20210904161436139](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904161436139.png) 基本上看个类名就知道大概作用了,那么先对号入座下 👇 @@ -122,7 +122,7 @@ FactoryBean ,它本身就是个 Bean,算是小工厂 ,归 BeanFactory 这个大工厂管理的。 -![image-20210904174616712](../../../images/spring/image-20210904174616712.png) +![image-20210904174616712](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904174616712.png) 可以看到它就只有三个方法 @@ -142,13 +142,13 @@ beanName 就是正常对象 大致如下 👇 -![image-20211213225330193](../../../images/spring/image-20211213225330193.png) +![image-20211213225330193](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225330193.png) ### ApplicationContext 我们再来看看这个 ApplicationContext -![image-20210904161808341](../../../images/spring/image-20210904161808341.png) +![image-20210904161808341](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210904161808341.png) 可以看到它扩展了很多功能,除了 BeanFactory ,它还可以**创建 , 获取 Bean**,以及处理**国际化**,**事件**,**获取资源**等 @@ -183,7 +183,7 @@ beanName 就是正常对象 我们可以在各个过程中合理应用这些 PostProcessor 来扩展,或者修改 Bean 定义信息等等 -![image-20211213225748030](../../../images/spring/image-20211213225748030.png) +![image-20211213225748030](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225748030.png) 可以看到在这个容器中,完成了 Bean 的初始化,而这个过程还有很多细节 ,请往下看看 👇 @@ -211,11 +211,11 @@ Bean 的创建和管理有**标准化的流程**! 这里在我们的工厂 BeanFactory 中写得很清楚 👇 -![image-20210902072224002](../../../images/spring/image-20210902072224002.png) +![image-20210902072224002](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210902072224002.png) 总共 **14** 个步骤,是不是一下子就清晰多了 😄 -![image-20211213225831583](../../../images/spring/image-20211213225831583.png) +![image-20211213225831583](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225831583.png) 在看这部分的源码时,要多注意两个英文单词 😝 @@ -232,7 +232,7 @@ ps: 别看快搞错了 哈哈 😝 在实例化 和 初始化流程中,把这个 Bean 的后置处理器 BeanPostProcessor 安排上,就得到下图啦 👇 -![image-20211213225953964](../../../images/spring/image-20211213225953964.png) +![image-20211213225953964](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213225953964.png) 这里留意下 **实例化** 有扩展点 InstantiationAwareBeanPostProcessor , **初始化** 扩展点 BeanPostProcessor 就非常多啦,我们主要来关注下这个 AOP @@ -240,11 +240,11 @@ ps: 别看快搞错了 哈哈 😝 那么 AOP 是在哪个步骤代理对象的呢?👇 -![image-20211213230042502](../../../images/spring/image-20211213230042502.png) +![image-20211213230042502](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20211213230042502.png) 可以在 AbstractAutoProxyCreator 类中看到 👇 -![image-20210903080803199](../../../images/spring/image-20210903080803199.png) +![image-20210903080803199](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20210903080803199.png) ### 总结 @@ -258,4 +258,4 @@ ps: 别看快搞错了 哈哈 😝 还有这个核心机制: **工厂+XML+反射**,以及 AOP **发生的地方**。😋 -![image-20211213230212297](../../../images/spring/image-20211213230212297.png) +![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/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-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-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-beanFactory.md b/docs/Spring/clazz/Spring-beanFactory.md index 1d37454d..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/spring/image-20200902102912716.png) +![image-20200902102912716](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200902102912716.png) - 整体类图 -![image-20200902103154580](../../../images/spring/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/spring/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/spring/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/spring/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/spring/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/spring/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/spring/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/spring/image-20200903153432559.png) +![image-20200903153432559](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153432559.png) - 在执行方法前将 bean 的信息先做一次截图 - ![image-20200903153533141](../../../images/spring/image-20200903153533141.png) + ![image-20200903153533141](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200903153533141.png) - 如果按照我们代码中的编写方式 bean 的属性会被覆盖 - ![image-20200903153617353](../../../images/spring/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/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..df07c90a 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 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/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" "b/docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" index 28effece..d02a1372 100644 --- "a/docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" +++ "b/docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" @@ -43,7 +43,7 @@ org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration.Condit ##### SimpleBatchConfiguration 这个类继承了`AbstractBatchConfiguration`。因为继承了`InitializingBean`执行`afterPropertiesSet` -,创建出俩个类`jobBuilderFactory`和 `stepBuilderFactory`. +, 创建出俩个类`jobBuilderFactory`和 `stepBuilderFactory`. ###### JobBuilderFactory @@ -201,12 +201,12 @@ public Step step() { .listener(new StepExecutionListener() { @Override public void beforeStep(StepExecution stepExecution) { - System.out.println("step-我是beforeJob--" + stepExecution); + System.out.println("step-我是 beforeJob--" + stepExecution); } @Override public ExitStatus afterStep(StepExecution stepExecution) { - System.out.println("step-我是beforeJob--" + stepExecution); + System.out.println("step-我是 beforeJob--" + stepExecution); return stepExecution.getExitStatus(); } }) @@ -260,7 +260,7 @@ public SimpleStepBuilder chunk(int chunkSize){ ``` 创建一个`SimpleStepBuilder`,并确定泛型,设置一个`chunkSize` -大小。翻译一下注释`构建一个步骤,以提供的大小分块处理项目。要将步骤扩展为容错,请在构建器上调用SimpleStepBuilder.faultTolerant()方法。在大多数情况下,您需要参数化对此方法的调用,以保护读者和作者的类型安全` +大小。翻译一下注释`构建一个步骤,以提供的大小分块处理项目。要将步骤扩展为容错,请在构建器上调用 SimpleStepBuilder.faultTolerant()方法。在大多数情况下,您需要参数化对此方法的调用,以保护读者和作者的类型安全` ,chunkSize – 块大小(提交间隔)。 接下来都是操作`SimpleStepBuilder`类。这个大小也就是是每次读取多少次。 `SimpleStepBuilder`继承`AbstractTaskletStepBuilder>` @@ -410,7 +410,7 @@ public TaskletStep build() { 注意:模板在迭代过程中累积抛出的异常,当主循环结束时(即完成对项目的处理),它们都会一起处理。不希望在引发异常时停止执行的客户端可以使用在收到异常时未完成的特定 CompletionPolicy 。这不是默认行为。 想要在 引发 RepeatCallback 异常时执行某些业务操作的客户端可以考虑使用自定义,而不是尝试自定义 RepeatListener -CompletionPolicy.这通常是一个更友好的接口来实现,该方法在回调的结果中传递,如果业务处理抛出异常, RepeatListener.after( +CompletionPolicy. 这通常是一个更友好的接口来实现,该方法在回调的结果中传递,如果业务处理抛出异常, RepeatListener.after( RepeatContext, RepeatStatus) 这将是一个实例 Throwable 。 如果不将异常传播到调用方,则还需要提供非默认值 CompletionPolicy ,但这可能是现成的,业务操作仅在拦截器中实现。 @@ -479,7 +479,7 @@ protected CompletionPolicy getChunkCompletionPolicy() { } ``` -每次处理大小:`在固定数量的操作后终止批处理的策略。维护内部状态并增加计数器,因此成功使用此策略需要每个批处理项仅调用一次isComplete()。使用标准的RepeatTemplate应确保保留此合同,但需要仔细监控。` +每次处理大小:`在固定数量的操作后终止批处理的策略。维护内部状态并增加计数器,因此成功使用此策略需要每个批处理项仅调用一次 isComplete()。使用标准的 RepeatTemplate 应确保保留此合同,但需要仔细监控。` 留心观察一下就会发现,这个值是`.chunk(2)`这里的 2,里面还可以传一个` chunk(CompletionPolicy completionPolicy)`。 @@ -951,8 +951,8 @@ public C register(@Nullable E execution) { ```java /** - * 当前执行的存储; 必须是ThreadLocal的,因为它需要在不属于步骤/作业的组件中定位上下文 (如重新补水作用域代理时)。不使用InheritableThreadLocal,因为如果一个步骤试图运行多个子步骤 (例如分区),会有副作用。 - * 堆栈用于覆盖单线程的情况,从而使API与多线程相同。 + * 当前执行的存储; 必须是 ThreadLocal 的,因为它需要在不属于步骤 / 作业的组件中定位上下文 (如重新补水作用域代理时)。不使用 InheritableThreadLocal,因为如果一个步骤试图运行多个子步骤 (例如分区),会有副作用。 + * 堆栈用于覆盖单线程的情况,从而使 API 与多线程相同。 */ private final ThreadLocal>executionHolder=new ThreadLocal<>(); @@ -1041,7 +1041,7 @@ private void updateStatus(JobExecution jobExecution, BatchStatus status) { > jobExecution.setStatus(status); -设置当前状态. +设置当前状态 . > jobRepository.update(jobExecution); @@ -1087,7 +1087,7 @@ public void synchronizeStatus(JobExecution jobExecution) { } ``` -版本不一样就更新,然后版本++,这里版本没有改变。回到`SimpleJobRepository#update(JobExecution jobExecution)` +版本不一样就更新,然后版本 ++,这里版本没有改变。回到`SimpleJobRepository#update(JobExecution jobExecution)` 。如果状态`STOPPING`并且结束时间不为空,就使用`jobExecution`更新。这里也没有结束,不需要更新。 > jobExecutionDao.updateJobExecution(jobExecution); @@ -2190,7 +2190,7 @@ public RepeatStatus doInTransaction(TransactionStatus status) { > oldVersion = new StepExecution(stepExecution.getStepName(), stepExecution.getJobExecution()); copy(stepExecution, > oldVersion); -如果提交失败后我们需要将其推回到旧值......,赋值一份用来进行回滚。 +如果提交失败后我们需要将其推回到旧值 ......,赋值一份用来进行回滚。 > result = tasklet.execute(contribution, chunkContext); @@ -2482,7 +2482,7 @@ protected final O doProcess(I item) throws Exception { > write(contribution, inputs, getAdjustedOutputs(inputs, outputs)); -如果需要的话,调整输出以进行内务处理,然后将它们写出来...... +如果需要的话,调整输出以进行内务处理,然后将它们写出来 ...... ```java /** @@ -2543,7 +2543,7 @@ protected final void doWrite(List items) throws Exception { `writeItems(items);`:自己写的`itemWriter`。执行里面的方法。 `doAfterWrite(items)`:执行`ItemWriteListener#afterWrite`方法。 -这里我们要注意,这只是一次,也就是我们写的 `stepBuilders.get("test-step").chunk(2)`,读取俩次值放入 Chunk +这里我们要注意,这只是一次,也就是我们写的 `stepBuilders.get("test-step").chunk(2)`, 读取俩次值放入 Chunk 中,再`RepeatTemplate#iterate(RepeatCallback callback)` 调用 `RepeatTemplate#executeInternal(final RepeatCallback callback)`里面。有个 while 循环,出去条件就是`isComplete(context, result) || isMarkedComplete(context) || !deferred.isEmpty()` @@ -2568,7 +2568,7 @@ protected final void doWrite(List items) throws Exception { 然后接着执行 finally 中的代码。 semaphore.acquire(); 信号量,锁上。 -locked = true; 锁=true。 +locked = true; 锁 =true。 如果步骤操作是异步的,那么我们需要将更改同步到步骤执行(至少)。在更改步骤执行之前锁定。 @@ -2670,7 +2670,7 @@ private boolean isMarkedComplete(RepeatContext context) { `doExecutionRelease();` 回到`SimpleStepHandler#handleStep(Step step, JobExecution execution)` -。刚刚我们所有方法都是在`step.execute(currentStepExecution);`中执行的。继续也是一些异常处理,关键就下面几句。 +。刚刚我们所有方法都是在`step.execute(currentStepExecution);`中执行的。继续也是一些异常处理 , 关键就下面几句。 `currentStepExecution.getExecutionContext().put("batch.executed", true);` `jobRepository.updateExecutionContext(execution);` 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" index d43bafa2..29f8df2b 100644 --- "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" @@ -211,7 +211,8 @@ Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQ 注意: 这里需要了解一下`DelegatingFilterProxyRegistrationBean`以及 spring 如何整合 filter 和 mvc 的。 springSecurity 核心就是 filter -![img.png](../../images/SpringSecurity/img-2023-6-7_0.png)image.png +![img.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7_0.png) + `DelegatingFilterProxyRegistrationBean`和`FilterRegistrationBean`都是继承的`RegistrationBean`,而`RegistrationBean` 又是`ServletContextInitializer`的实现类。其中`void onStartup(ServletContext servletContext)`方法是关键。 在`javax.servlet` 中,存在这样一个类 @@ -312,9 +313,9 @@ public @interface EnableWebSecurity { 这里有几个类需要了解`SecurityConfigurer`和`SecurityBuilder` 先了解一下结构 -![img_1.png](../../images/SpringSecurity/img-2023-6-7_1.png) +![img_1.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7_1.png) -![img_2.png](../../images/SpringSecurity/img-2023-6-7-_2.png) +![img_2.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7-_2.png) 使用`WebSecurity` 聚合了`private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();` @@ -610,7 +611,7 @@ throw new IllegalArgumentException("The Filter class "+filter.getClass().getName 后面调用的`.anyRequest()`,也就是`AbstractRequestMatcherRegistry#anyRequest()`。先了解一下结构图 -![img_3.png](../../images/SpringSecurity/img-2023-6-7-_3.png) +![img_3.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7-_3.png) 完整调用链就是`AbstractRequestMatcherRegistry#anyRequest()` -> `AbstractRequestMatcherRegistry#requestMatchers(RequestMatcher... requestMatchers)` -> `AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers(List requestMatchers)` -> `ExpressionUrlAuthorizationConfigurer#chainRequestMatchersInternal(List requestMatchers)` -> `return new AuthorizedUrl(requestMatchers);` @@ -699,10 +700,11 @@ HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是 这个就是在`SecurityAutoConfiguration`中创建的 springSecurity 的发布订阅,用来订阅事件 -> DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder( -> objectPostProcessor, defaultPasswordEncoder); +```java +DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); +``` -就是`AuthenticationManagerBuilder`的真正实现了。接下来回到`getAuthenticationManager()`方法 +就是 `AuthenticationManagerBuilder`的真正实现了。接下来回到`getAuthenticationManager()`方法 ```java public AuthenticationManager getAuthenticationManager() throws Exception { @@ -725,9 +727,11 @@ public AuthenticationManager getAuthenticationManager() throws Exception { } ``` -> AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); +```java +AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); +``` -获取到`DefaultPasswordEncoderAuthenticationManagerBuilder` +获取到 `DefaultPasswordEncoderAuthenticationManagerBuilder` ```java for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { 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" index 596a88f6..52da85f3 100644 --- "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" @@ -4,7 +4,7 @@ ## 配置自定义登录页 -为了方便起见,我们直接在src/main/resources/resources目录下创建一个login.html(不需要 Controller 跳转): +为了方便起见,我们直接在`src/main/resources/resources`目录下创建一个`login.html`(不需要 Controller 跳转): ``` @@ -27,11 +27,12 @@ ``` -要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在BrowserSecurityConfigconfigure中添加一些配置: +要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在`BrowserSecurityConfig`的`configure`中添加一些配置: -``` +```java @Configuration public class BrowserConfig extends WebSecurityConfigurerAdapter { + @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 表单登录 @@ -39,18 +40,19 @@ public class BrowserConfig extends WebSecurityConfigurerAdapter { .loginProcessingUrl("/login") // 登录认证路径 .and() .authorizeRequests() // 授权配置 - .antMatchers("/login.html", "/css/", "/error").permitAll() // 无需认证 - .anyRequest().authenticated() // 除antMatchers中配置路径外其他所有请求都需要认证 - .and().csrf().disable(); + .antMatchers("/login.html", "/css/**", "/error").permitAll() // 无需认证 + .anyRequest().authenticated() // 其他所有请求都需要认证 + .and() + .csrf().disable(); // 禁用 CSRF } } ``` -上面代码中.loginPage("/login.html")指定了跳转到登录页面的请求 URL,.loginProcessingUrl("/login")对应登录页面 form 表单的action="/login".antMatchers("/login.html", "/css/", "/error").permitAll()表示跳转到登录页面的请求不被拦截。 +上面代码中`.loginPage("/login.html")`指定了跳转到登录页面的请求 URL,`.loginProcessingUrl("/login")`对应登录页面 form 表单的`action="/login"`,`.antMatchers("/login.html", "/css/", "/error").permitAll()`表示跳转到登录页面的请求不被拦截。 -这时候启动系统,访问http://localhost:8080/hello,会看到页面已经被重定向到了http://localhost:8080/login.html: +这时候启动系统,访问`http://localhost:8080/hello`,会看到页面已经被重定向到了`http://localhost:8080/login.html`: -![img](../../images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg) ## 配置用户信息的获取逻辑 @@ -68,179 +70,179 @@ public class UserDetailService implements UserDetailsService { } ``` -通过以上配置,我们定义了一个用户名随机,密码统一为 123456 的用户信息的获取逻辑。这样,当我们启动项目,访问http://localhost:8080/login,只需要输入任意用户名以及 123456 作为密码即可登录系统。 +通过以上配置,我们定义了一个用户名随机,密码统一为 123456 的用户信息的获取逻辑。这样,当我们启动项目,访问`http://localhost:8080/login`,只需要输入任意用户名以及 123456 作为密码即可登录系统。 ## 源码解析 ### BrowserConfig 配置解析 -我们首先来梳理下BrowserConfig中的配置是如何被 Spring Security 所加载的。 +我们首先来梳理下`BrowserConfig`中的配置是如何被 Spring Security 所加载的。 -首先找到调用BrowserConfigconfigure()的地方,在其父类WebSecurityConfigurerAdaptergetHttp()中: +首先找到调用`BrowserConfig`的`configure()`的地方,在其父类`WebSecurityConfigurerAdapter`的`getHttp()`中: -![img](../../images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png) -往上一步找到调用getHttp()的地方,在同个类的init()中: +往上一步找到调用`getHttp()`的地方,在同个类的`init()`中: -![img](../../images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png) -往上一步找到调用init()的地方,在AbstractConfiguredSecurityBuilderinit()中: +往上一步找到调用`init()`的地方,在 `AbstractConfiguredSecurityBuilder` 的`init()`中: -![img](../../images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png) -在init()被调用时,它首先会遍历getConfigurers()返回的集合中的元素,调用其init(),点击getConfigurers()查看,发现其读取的是configurers属性的值,那么configurers是什么时候被赋值的呢?我们在同个类的add()中找到configurers被赋值的代码: +在`init()`被调用时,它首先会遍历`getConfigurers()`返回的集合中的元素,调用其`init()`,点击`getConfigurers()`查看,发现其读取的是`configurers`属性的值,那么`configurers`是什么时候被赋值的呢?我们在同个类的`add()`中找到`configurers`被赋值的代码: -![img](../../images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png) -往上一步找到调用add()的地方,在同个类的apply()中: +往上一步找到调用`add()`的地方,在同个类的`apply()`中: -![img](../../images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png) -往上一步找到调用apply()的地方,在WebSecurityConfigurationsetFilterChainProxySecurityConfigurer()中: +往上一步找到调用`apply()`的地方,在`WebSecurityConfiguration`的`setFilterChainProxySecurityConfigurer()`中: -![img](../../images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png) -我们可以看到,在setFilterChainProxySecurityConfigurer()中,首先会实例化一个WebSecurityAbstractConfiguredSecurityBuilder的实现类)的实例,遍历参数webSecurityConfigurers,将存储在其中的元素作为参数传递给WebSecurityapply(),那么webSecurityConfigurers是什么时候被赋值的呢?我们根据@Value中的信息找到webSecurityConfigurers被赋值的地方,在AutowiredWebSecurityConfigurersIgnoreParentsgetWebSecurityConfigurers()中: +我们可以看到,在`setFilterChainProxySecurityConfigurer()`中,首先会实例化一个`WebSecurity`(`AbstractConfiguredSecurityBuilder`的实现类)的实例,遍历参数`webSecurityConfigurers`,将存储在其中的元素作为参数传递给`WebSecurity`的`apply()`,那么`webSecurityConfigurers`是什么时候被赋值的呢?我们根据`@Value`中的信息找到`webSecurityConfigurers`被赋值的地方,在`AutowiredWebSecurityConfigurersIgnoreParents`的`getWebSecurityConfigurers()`中: -![img](../../images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png) -我们重点看第二句代码,可以看到这里会提取存储在 bean 工厂中类型为WebSecurityConfigurer.class的 bean,而BrowserConfig正是WebSecurityConfigurerAdapter的实现类。 +我们重点看第二句代码,可以看到这里会提取存储在 bean 工厂中类型为`WebSecurityConfigurer.class`的 bean,而`BrowserConfig`正是`WebSecurityConfigurerAdapter`的实现类。 -解决完configurers的赋值问题,我们回到AbstractConfiguredSecurityBuilderinit()处,找到调用该方法的地方,在同个类的doBuild()中: +解决完`configurers`的赋值问题,我们回到`AbstractConfiguredSecurityBuilder`的`init()`处,找到调用该方法的地方,在同个类的`doBuild()`中: -![img](../../images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png) -往上一步找到调用doBuild()的地方,在AbstractSecurityBuilderbuild()中: +往上一步找到调用`doBuild()`的地方,在`AbstractSecurityBuilder`的`build()`中: -![img](../../images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png) -往上一步找到调用doBuild()的地方,在WebSecurityConfigurationspringSecurityFilterChain()中: +往上一步找到调用`doBuild()`的地方,在`WebSecurityConfiguration`的`springSecurityFilterChain()`中: -![img](../../images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png) -至此,我们分析完了BrowserConfig被 Spring Security 加载的过程。现在我们再来看看当我们自定义的配置被加载完后,http各属性的变化,在BrowserConfigconfigure()末尾打上断点,当程序走到断点处时,查看http属性: +至此,我们分析完了`BrowserConfig`被 Spring Security 加载的过程。现在我们再来看看当我们自定义的配置被加载完后,`http`各属性的变化,在`BrowserConfig`的`configure()`末尾打上断点,当程序走到断点处时,查看`http`属性: -![img](../../images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png) -我们配置的.loginPage("/login.html").loginProcessingUrl("/login")FormLoginConfigurer中: +我们配置的`.loginPage("/login.html")`和`.loginProcessingUrl("/login")`在`FormLoginConfigurer`中: -![img](../../images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png) -配置的.antMatchers("/login.html", "/css/", "/error").permitAll()ExpressionUrlAuthorizationConfigurer中: +配置的`.antMatchers("/login.html", "/css/", "/error").permitAll()`在`ExpressionUrlAuthorizationConfigurer`中: -![img](../../images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png) -这样,当我们访问除"/login.html", "/css/", "/error"以外的路径时,在AbstractSecurityInterceptorFilterSecurityInterceptor的父类)的attemptAuthorization()中会抛出AccessDeniedException异常(最终由AuthenticationTrustResolverImplisAnonymous()进行判断) +这样,当我们访问除`"/login.html", "/css/", "/error"`以外的路径时,在`AbstractSecurityInterceptor`(`FilterSecurityInterceptor`的父类)的`attemptAuthorization()`中会抛出`AccessDeniedException`异常(最终由`AuthenticationTrustResolverImpl`的isAnonymous()`进行判断) -![img](../../images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png) -当我们访问"/login.html", "/css/", "/error"这几个路径时,在AbstractSecurityInterceptorFilterSecurityInterceptor的父类)的attemptAuthorization()中正常执行(最终由SecurityExpressionRootpermitAll()进行判断) +当我们访问`"/login.html", "/css/", "/error"`这几个路径时,在`AbstractSecurityInterceptor`(`FilterSecurityInterceptor`的父类)的`attemptAuthorization()`中正常执行(最终由`SecurityExpressionRoot`的`permitAll()`进行判断) -![img](../../images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png) ### login.html 路径解析 当我们请求的资源需要经过认证时,Spring Security 会将请求重定向到我们自定义的登录页,那么 Spring 又是如何找到我们自定义的登录页的呢?下面就让我们来解析一下: -我们首先来到DispatcherServlet中,DispatcherServlet是 Spring Web 处理请求的入口。当 Spring Web 项目启动后,第一次接收到请求时,会调用其initStrategies()进行初始化: +我们首先来到`DispatcherServlet`中,`DispatcherServlet`是 Spring Web 处理请求的入口。当 Spring Web 项目启动后,第一次接收到请求时,会调用其`initStrategies()`进行初始化: -![img](../../images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png) -我们重点关注initHandlerMappings(context);这句,initHandlerMappings()用于初始化处理器映射器(处理器映射器可以根据请求找到对应的资源),我们来到initHandlerMappings()中: +我们重点关注`initHandlerMappings(context);`这句,`initHandlerMappings()`用于初始化处理器映射器(处理器映射器可以根据请求找到对应的资源),我们来到`initHandlerMappings()`中: -![img](../../images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png) -可以看到,当程序走到initHandlerMappings()中时,会从 bean 工厂中找出HandlerMapping.class类型的 bean,将其存储到handlerMappings属性中。这里看到一共找到 5 个,分别是:requestMappingHandlerMapping(将请求与标注了@RequestMapping的方法进行关联)、weclomePageHandlerMapping(将请求与主页进行关联)、beanNameHandlerMapping(将请求与同名的 bean 进行关联)、routerFunctionMapping(将请求与RouterFunction进行关联)、resourceHandlerMapping(将请求与静态资源进行关联),这 5 个 bean 是在WebMvcAutoConfiguration$EnableWebMvcConfiguration中配置的: +可以看到,当程序走到`initHandlerMappings()`中时,会从 bean 工厂中找出`HandlerMapping.class`类型的 bean,将其存储到`handlerMappings`属性中。这里看到一共找到 5 个,分别是:`requestMappingHandlerMapping`(将请求与标注了`@RequestMapping`的方法进行关联)、`weclomePageHandlerMapping`(将请求与主页进行关联)、`beanNameHandlerMapping`(将请求与同名的 bean 进行关联)、`routerFunctionMapping`(将请求与`RouterFunction`进行关联)、`resourceHandlerMapping`(将请求与静态资源进行关联),这 5 个 bean 是在`WebMvcAutoConfiguration$EnableWebMvcConfiguration`中配置的: -requestMappingHandlerMapping: +`requestMappingHandlerMapping:` -![img](../../images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png) -weclomePageHandlerMapping: +`weclomePageHandlerMapping:` -![img](../../images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png) -beanNameHandlerMappingrouterFunctionMappingresourceHandlerMappingEnableWebMvcConfiguration的父类(WebMvcConfigurationSupport)中配置: +`beanNameHandlerMapping`、`routerFunctionMapping`、`resourceHandlerMapping`在`EnableWebMvcConfiguration`的父类(`WebMvcConfigurationSupport`)中配置: -beanNameHandlerMapping: +`beanNameHandlerMapping:` -![img](../../images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png) -routerFunctionMapping: +`routerFunctionMapping:` -![img](../../images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png) -resourceHandlerMapping: +`resourceHandlerMapping:` -![img](../../images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png) -我们将目光锁定在resourceHandlerMapping上,当resourceHandlerMapping被初始化时,会调用addResourceHandlers()registry添加资源处理器,我们找到实际被调用的addResourceHandlers(),在DelegatingWebMvcConfiguration中: +我们将目光锁定在`resourceHandlerMapping`上,当`resourceHandlerMapping`被初始化时,会调用`addResourceHandlers()`为`registry`添加资源处理器,我们找到实际被调用的`addResourceHandlers()`,在`DelegatingWebMvcConfiguration`中: -![img](../../images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png) -可以看到这里实际调用的是configurers属性的addResourceHandlers(),而configurers是一个 final 类型的成员变量,其值是WebMvcConfigurerComposite的实例,我们来到WebMvcConfigurerComposite中: +可以看到这里实际调用的是`configurers`属性的`addResourceHandlers()`,而`configurers`是一个 final 类型的成员变量,其值是`WebMvcConfigurerComposite`的实例,我们来到`WebMvcConfigurerComposite`中: -![img](../../images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png) -可以看到这里实际调用的是delegates属性的addResourceHandlers()delegates是一个 final 类型的集合,集合的元素由addWebMvcConfigurers()负责添加: +可以看到这里实际调用的是`delegates`属性的`addResourceHandlers()`,`delegates`是一个 final 类型的集合,集合的元素由`addWebMvcConfigurers()`负责添加: -![img](../../images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png) -我们找到调用addWebMvcConfigurers()的地方,在DelegatingWebMvcConfigurationsetConfigurers()中: +我们找到调用`addWebMvcConfigurers()`的地方,在`DelegatingWebMvcConfiguration`的`setConfigurers()`中: -![img](../../images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png) -可以看到当setConfigurers()被初始化时,Spring 会往参数configurers中传入两个值,我们关注第一个值,是一个WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter的实例,注意它的属性resourceProperties,是一个WebProperties$Resources的实例,默认情况下,在实例化WebMvcAutoConfigurationAdapter时,由传入参数webProperties进行赋值:webProperties.getResources(): +可以看到当`setConfigurers()`被初始化时,Spring 会往参数`configurers`中传入两个值,我们关注第一个值,是一个`WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter`的实例,注意它的属性`resourceProperties`,是一个`WebProperties$Resources`的实例,默认情况下,在实例化`WebMvcAutoConfigurationAdapter`时,由传入参数`webProperties`进行赋值:`webProperties.getResources()`: -![img](../../images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png) -我们进入参数webProperties的类中,可以看到getResources()是直接实例化了一个Resources,其属性staticLocations是一个含有 4 个值的 final 类型的字符串数组,这 4 个值正是 Spring 寻找静态文件的地方: +我们进入参数`webProperties`的类中,可以看到`getResources()`是直接实例化了一个`Resources`,其属性`staticLocations`是一个含有 4 个值的 final 类型的字符串数组,这 4 个值正是 Spring 寻找静态文件的地方: -![img](../../images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png) -![img](../../images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png) -我们回到WebMvcAutoConfigurationaddResourceHandlers()中:![img](../../images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png) +我们回到`WebMvcAutoConfiguration`的`addResourceHandlers()`中:![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png) -![img](../../images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png) -在addResourceHandlers()中,会为registry添加两个资源处理器,当请求路径是“/webjars/”时,会在”classpath:/META-INF/resources/webjars/“路径下寻找对应的资源,当请求路径是“/\*\*”时,会在”classpath:/META-INF/resources/“、”classpath:/resources/“、”classpath:/static/“、”classpath:/public/“路径下寻找对应的资源。 +在`addResourceHandlers()`中,会为`registry`添加两个资源处理器,当请求路径是“/webjars/”时,会在”classpath:/META-INF/resources/webjars/“路径下寻找对应的资源,当请求路径是“/\*\*”时,会在”classpath:/META-INF/resources/“、”classpath:/resources/“、”classpath:/static/“、”classpath:/public/“路径下寻找对应的资源。 -现在我们通过访问http://localhost:8080/login.html来验证这个过程。 +现在我们通过访问`http://localhost:8080/login.html`来验证这个过程。 -请求首先来到DispatcherServletdoDispatch()中,由于是对静态资源的请求,当程序走到mappedHandler = getHandler(processedRequest);时,通过getHandler()返回SimpleUrlHandlerMapping(即resourceHandlerMapping的类型)的HandlerExecutionChain: +请求首先来到`DispatcherServlet`的`doDispatch()`中,由于是对静态资源的请求,当程序走到`mappedHandler = getHandler(processedRequest);`时,通过`getHandler()`返回`SimpleUrlHandlerMapping`(即`resourceHandlerMapping`的类型)的`HandlerExecutionChain`: -![img](../../images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png) 然后由实际的处理器进行处理: -![img](../../images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png) -程序一路调试,来到ResourceHttpRequestHandlerhandleRequest()中,通过调用Resource resource = getResource(request);找到请求对应的资源: +程序一路调试,来到`ResourceHttpRequestHandler`的`handleRequest()`中,通过调用`Resource resource = getResource(request);`找到请求对应的资源: -![img](../../images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png) -而在getResource()中,实际是将请求路径(即login.html)与前面配置的路径进行拼接(组合成/resources/login.html这样的路径),再通过类加载器来寻找资源。 +而在`getResource()`中,实际是将请求路径(即`login.html`)与前面配置的路径进行拼接(组合成`/resources/login.html`这样的路径),再通过类加载器来寻找资源。 后面源码深入过深,就不一一展开了,只截取其中比较重要的几段代码: -PathResourceResolver中: +`PathResourceResolver`中: -![img](../../images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png) -![img](../../images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png) -ClassPathResource中: +`ClassPathResource`中: -![img](../../images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png) -ClassLoader中: +`ClassLoader`中: -![img](../../images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png) -URLClassLoader中: +`URLClassLoader`中: -![img](../../images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png) 最终,类加载器会在如上两个路径下找到登录页并返回。 @@ -250,67 +252,67 @@ public class UserDetailService implements UserDetailsService { 还记得前面我们讲**_BrowserConfig 配置_**被加载的过程吗?**_UserDetailService_**也是在这个过程中被一起加载完成的,回到**BrowserConfig 配置解析**的第一幅图中,如下: -![img](../../images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png) -在断点处位置,authenticationManager()会返回一个**_AuthenticationManager_**实例,我们进入authenticationManager()中: +在断点处位置,`authenticationManager()`会返回一个**_AuthenticationManager_**实例,我们进入`authenticationManager()`中: -![img](../../images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png) -在authenticationManager()中,**_AuthenticationManager_**转由**_AuthenticationConfiguration_**中获取,我们进入getAuthenticationManager()中: +在`authenticationManager()`中,**_AuthenticationManager_**转由**_AuthenticationConfiguration_**中获取,我们进入`getAuthenticationManager()`中: -![img](../../images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png) -程序来到**_AuthenticationConfiguration_**的getAuthenticationManager()中,**_AuthenticationManager_**转由**_AuthenticationManagerBuilder_**中获取,我们进入build()中: +程序来到**_AuthenticationConfiguration_**的`getAuthenticationManager()`中,**_AuthenticationManager_**转由**_AuthenticationManagerBuilder_**中获取,我们进入`build()`中: -![img](../../images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png) -程序来到**_AbstractConfiguredSecurityBuilder_**的doBuild()中,这里在构建**_AuthenticationManager_**实例时,需要初始化 3 个配置类,我们重点关注第 3 个配置类:**_org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer_**,这个配置类是在**_AuthenticationConfiguration_**中引入的: +程序来到**_AbstractConfiguredSecurityBuilder_**的`doBuild()`中,这里在构建**_AuthenticationManager_**实例时,需要初始化 3 个配置类,我们重点关注第 3 个配置类:**_org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer_**,这个配置类是在**_AuthenticationConfiguration_**中引入的: -![img](../../images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png) -我们来到**_InitializeUserDetailsBeanManagerConfigurer_**的init()中: +我们来到**_InitializeUserDetailsBeanManagerConfigurer_**的`init()`中: -![img](../../images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png) -这里会新建一个**_InitializeUserDetailsManagerConfigurer_**实例添加到**_AuthenticationManagerBuilder_**中。我们回到doBuild()中: +这里会新建一个**_InitializeUserDetailsManagerConfigurer_**实例添加到**_AuthenticationManagerBuilder_**中。我们回到`doBuild()`中: -![img](../../images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png) -可以看到配置类变成了 5 个,其中就有刚刚新建的**_InitializeUserDetailsManagerConfigurer_**,程序接下来会调用各个配置类的configure()进行配置,我们来到**_InitializeUserDetailsManagerConfigurer_**的configure()中: +可以看到配置类变成了 5 个,其中就有刚刚新建的**_InitializeUserDetailsManagerConfigurer_**,程序接下来会调用各个配置类的`configure()`进行配置,我们来到**_InitializeUserDetailsManagerConfigurer_**的`configure()`中: -![img](../../images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png) -可以看到在configure()中,就会去 bean 工厂中寻找**_UserDetailsService_**类型的 bean,若是我们没有自定义**_UserDetailsService_**的实现类的话,Spring Security 默认会生成一个**_InMemoryUserDetailsManager_**的实例: +可以看到在`configure()`中,就会去 bean 工厂中寻找**_UserDetailsService_**类型的 bean,若是我们没有自定义**_UserDetailsService_**的实现类的话,Spring Security 默认会生成一个**_InMemoryUserDetailsManager_**的实例: -![img](../../images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png) **_InMemoryUserDetailsManager_**是在**_UserDetailsServiceAutoConfiguration_**类中配置的: -![img](../../images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png) 解决完**_UserDetailsService_**的加载问题,现在我们来看看 Spring Security 是如何通过**_UserDetailsService_**获取用户信息的。 -通过**Spring Boot 中开启 Spring Security**一节的学习我们知道,登录判断的逻辑是在**_UsernamePasswordAuthenticationFilter_**中进行的,因此我们在**_UsernamePasswordAuthenticationFilter_**的attemptAuthenticatio()中打上断点,然后启动项目,访问登录页,输入用户名和密码点击登录后,程序来到**_UsernamePasswordAuthenticationFilter_**中: +通过**Spring Boot 中开启 Spring Security**一节的学习我们知道,登录判断的逻辑是在**_UsernamePasswordAuthenticationFilter_**中进行的,因此我们在**_UsernamePasswordAuthenticationFilter_**的`attemptAuthenticatio()`中打上断点,然后启动项目,访问登录页,输入用户名和密码点击登录后,程序来到**_UsernamePasswordAuthenticationFilter_**中: -![img](../../images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png) -这里将验证的逻辑交由**_AuthenticationManager_**进行,我们进入authenticate()中: +这里将验证的逻辑交由**_AuthenticationManager_**进行,我们进入`authenticate()`中: -![img](../../images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png) -程序来到**_ProviderManager_**的authenticate()中,这里将验证的逻辑委托给其父类进行,再次点击进入authenticate()中: +程序来到**_ProviderManager_**的`authenticate()`中,这里将验证的逻辑委托给其父类进行,再次点击进入`authenticate()`中: -![img](../../images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png) -这里将验证的逻辑交由**_AuthenticationProvider_**进行,我们进入authenticate()中: +这里将验证的逻辑交由**_AuthenticationProvider_**进行,我们进入`authenticate()`中: -![img](../../images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png) -程序来到**_AbstractUserDetailsAuthenticationProvider_**的authenticate()中,这里会根据用户名去寻找对应的用户实例,我们进入retrieveUser()中: +程序来到**_AbstractUserDetailsAuthenticationProvider_**的`authenticate()`中,这里会根据用户名去寻找对应的用户实例,我们进入`retrieveUser()`中: -![img](../../images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png) -程序来到**_DaoAuthenticationProvider_**的retrieveUser()中,可以看到正是在这里,会从**_UserDetailsService_**的loadUserByUsername()中寻找对应的用户信息。 +程序来到**_DaoAuthenticationProvider_**的`retrieveUser()`中,可以看到正是在这里,会从**_UserDetailsService_**的`loadUserByUsername()`中寻找对应的用户信息。 ## 参考 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/package-lock.json b/package-lock.json index 24d178cd..1c99dda0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2046 +1,2250 @@ { "name": "source-code-hunter", - "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "source-code-hunter", - "version": "1.0.0", - "license": "SEE LICENSE IN LICENSE", - "dependencies": { - "docsify-cli": "^4.4.4" - }, "devDependencies": { - "prettier": "2.8.8" + "vitepress": "^1.6.3" } }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "engines": { - "node": ">=6" + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" } }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, "dependencies": { - "defer-to-connect": "^1.0.1" + "@algolia/autocomplete-shared": "1.17.7" }, - "engines": { - "node": ">=6" + "peerDependencies": { + "search-insights": ">= 1 < 3" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, "dependencies": { - "string-width": "^4.1.0" + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "engines": { - "node": ">=6" + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@algolia/client-abtesting": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-abtesting/-/client-abtesting-5.23.3.tgz", + "integrity": "sha512-yHI0hBwYcNPc+nJoHPTmmlP8pG6nstCEhpHaZQCDwLZhdMtNhd1hliZMCtLgNnvd1yKEgTt/ZDnTSdZLehfKdA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, "engines": { - "node": ">=8" + "node": ">= 14.0.0" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@algolia/client-analytics": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-analytics/-/client-analytics-5.23.3.tgz", + "integrity": "sha512-/70Ey+nZm4bRr2DcNrGU251YIn9lDu0g8xeP4jTCyunGRNFZ/d8hQAw9El34pcTpO1QDojJWAi6ywKIrUaks9w==", + "dev": true, "dependencies": { - "color-convert": "^1.9.0" + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=4" + "node": ">= 14.0.0" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "node_modules/@algolia/client-common": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-common/-/client-common-5.23.3.tgz", + "integrity": "sha512-fkpbPclIvaiyw3ADKRBCxMZhrNx/8//6DClfWGxeEiTJ0HEEYtHlqE6GjAkEJubz4v1ioCQkhZwMoFfFct2/vQ==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-insights/-/client-insights-5.23.3.tgz", + "integrity": "sha512-TXc5Ve6QOCihWCTWY9N56CZxF1iovzpBWBUhQhy6JSiUfX3MXceV3saV+sXHQ1NVt2NKkyUfEspYHBsTrYzIDg==", + "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">= 8" + "node": ">= 14.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", - "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^5.3.1", - "chalk": "^3.0.0", - "cli-boxes": "^2.2.0", - "string-width": "^4.1.0", - "term-size": "^2.1.0", - "type-fest": "^0.8.1", - "widest-line": "^3.1.0" + "node_modules/@algolia/client-personalization": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-personalization/-/client-personalization-5.23.3.tgz", + "integrity": "sha512-JlReruxxiw9LB53jF/BmvVV+c0thiWQUHRdgtbVIEusvRaiX1IdpWJSPQExEtBQ7VFg89nP8niCzWtA34ktKSA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14.0.0" } }, - "node_modules/boxen/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@algolia/client-query-suggestions": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.23.3.tgz", + "integrity": "sha512-GDEExFMXwx0ScE0AZUA4F6ssztdJvGcXUkdWmWyt2hbYz43ukqmlVJqPaYgGmWdjJjvTx+dNF/hcinwWuXbCug==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14.0.0" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/@algolia/client-search": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-search/-/client-search-5.23.3.tgz", + "integrity": "sha512-mwofV6tGo0oHt4BPi+S5eLC3wnhOa4A1OVgPxetTxZuetod+2W4cxKavUW2v/Ma5CABXPLooXX+g9E67umELZw==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=8" + "node": ">= 14.0.0" } }, - "node_modules/boxen/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@algolia/ingestion": { + "version": "1.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/ingestion/-/ingestion-1.23.3.tgz", + "integrity": "sha512-Zxgmi7Hk4lI52YFphzzJekUqWxYxVjY2GrCpOxV+QiojvUi8Ru+knq6REcwLHFSwpwaDh2Th5pOefMpn4EkQCw==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=7.0.0" + "node": ">= 14.0.0" } }, - "node_modules/boxen/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/boxen/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@algolia/monitoring": { + "version": "1.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/monitoring/-/monitoring-1.23.3.tgz", + "integrity": "sha512-zi/IqvsmFW4E5gMaovAE4KRbXQ+LDYpPGG1nHtfuD5u3SSuQ31fT1vX2zqb6PbPTlgJMEmMk91Mbb7fIKmbQUw==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, "engines": { - "node": ">=8" + "node": ">= 14.0.0" } }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/@algolia/recommend": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/recommend/-/recommend-5.23.3.tgz", + "integrity": "sha512-C9TwbT1zGwULLXGSUSB+G7o/30djacPmQcsTHepvT47PVfPr2ISK/5QVtUnjMU84LEP8uNjuPUeM4ZeWVJ2iuQ==", + "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=8" + "node": ">= 14.0.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.23.3.tgz", + "integrity": "sha512-/7oYeUhYzY0lls7WtkAURM6wy21/Wwmq9GdujW1MpoYVC0ATXXxwCiAfOpYL9xdWxLV0R3wjyD+yZEni+nboKg==", + "dev": true, "dependencies": { - "fill-range": "^7.1.1" + "@algolia/client-common": "5.23.3" }, "engines": { - "node": ">=8" + "node": ">= 14.0.0" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "node_modules/@algolia/requester-fetch": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/requester-fetch/-/requester-fetch-5.23.3.tgz", + "integrity": "sha512-r/4fKz4t+bSU1KdjRq+swdNvuGfJ0spV8aFTHPtcsF+1ZaN/VqmdXrTe5NkaZLSztFeMqKwZlJIVvE7VuGlFtw==", + "dev": true, "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" + "@algolia/client-common": "5.23.3" }, "engines": { - "node": ">=8" + "node": ">= 14.0.0" } }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/@algolia/requester-node-http": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/requester-node-http/-/requester-node-http-5.23.3.tgz", + "integrity": "sha512-UZiTNmUBQFPl3tUKuXaDd8BxEC0t0ny86wwW6XgwfM9IQf4PrzuMpvuOGIJMcCGlrNolZDEI0mcbz/tqRdKW7A==", + "dev": true, "dependencies": { - "pump": "^3.0.0" + "@algolia/client-common": "5.23.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14.0.0" } }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=6.9.0" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, "engines": { - "node": ">=6" + "node": ">=6.9.0" } }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=4" + "node": ">=6.0.0" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=6.9.0" } }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmmirror.com/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmmirror.com/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmmirror.com/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, "dependencies": { - "mimic-response": "^1.0.0" + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } } }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.10.0" + "node": ">=12" } }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=0.8" + "node": ">=12" } }, - "node_modules/connect-livereload": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", - "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "*" + "node": ">=12" } }, - "node_modules/cp-file": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", - "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", - "dependencies": { - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "nested-error-stacks": "^2.0.0", - "p-event": "^4.1.0" - }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", - "dependencies": { - "mimic-response": "^1.0.0" - }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4.0.0" + "node": ">=12" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=12" } }, - "node_modules/docsify": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/docsify/-/docsify-4.13.0.tgz", - "integrity": "sha512-dM2D0LZKrhK9e5cPwzOTO8FJ2l9IxgiSmTgLBIMjkBlTs1rAUT5camzekbk2AhH0Tw+5lzkNrSb7dmKFuTiLCA==", - "hasInstallScript": true, - "dependencies": { - "marked": "^1.2.9", - "medium-zoom": "^1.0.6", - "opencollective-postinstall": "^2.0.2", - "prismjs": "^1.27.0", - "strip-indent": "^3.0.0", - "tinydate": "^1.3.0", - "tweezer.js": "^1.4.0" - } - }, - "node_modules/docsify-cli": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/docsify-cli/-/docsify-cli-4.4.4.tgz", - "integrity": "sha512-NAZgg6b0BsDuq/Pe+P19Qb2J1d+ZVbS0eGkeCNxyu4F9/CQSsRqZqAvPJ9/0I+BCHn4sgftA2jluqhQVzKzrSA==", - "dependencies": { - "chalk": "^2.4.2", - "connect": "^3.6.0", - "connect-history-api-fallback": "^1.6.0", - "connect-livereload": "^0.6.0", - "cp-file": "^7.0.0", - "docsify": "^4.12.2", - "docsify-server-renderer": ">=4.10.0", - "enquirer": "^2.3.6", - "fs-extra": "^8.1.0", - "get-port": "^5.0.0", - "livereload": "^0.9.2", - "lru-cache": "^5.1.1", - "open": "^6.4.0", - "serve-static": "^1.12.1", - "update-notifier": "^4.1.0", - "yargonaut": "^1.1.2", - "yargs": "^15.3.0" - }, - "bin": { - "docsify": "bin/docsify" - }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 10", - "npm": ">= 6" + "node": ">=12" } }, - "node_modules/docsify-server-renderer": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/docsify-server-renderer/-/docsify-server-renderer-4.13.0.tgz", - "integrity": "sha512-dgJ+mYfkTLuePfTKMmdcRJMFw9YBhmqFS9SgdOc2Fw8emiCTcnOup23wnZ0VdmrEmlc+97K85kASiw/75w6PPQ==", - "dependencies": { - "debug": "^4.3.3", - "docsify": "^4.12.4", - "node-fetch": "^2.6.6", - "resolve-pathname": "^3.0.0" + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "node_modules/docsify-server-renderer/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=12" } }, - "node_modules/docsify-server-renderer/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dependencies": { - "is-obj": "^2.0.0" - }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dependencies": { - "ansi-colors": "^4.1.1" - }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8.6" + "node": ">=12" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=0.8.0" + "node": ">=12" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.6" + "node": ">=12" } }, - "node_modules/figlet": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.6.0.tgz", - "integrity": "sha512-31EQGhCEITv6+hi2ORRPyn3bulaV9Fl4xOdR169cBzH/n1UqcxsiSB/noo6SJdD7Kfb1Ljit+IgR1USvF/XbdA==", - "bin": { - "figlet": "bin/index.js" - }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4.0" + "node": ">=12" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.32", + "resolved": "https://registry.npmmirror.com/@iconify-json/simple-icons/-/simple-icons-1.2.32.tgz", + "integrity": "sha512-gxgLq0raip7SJaeJ0302vwhsqupQttS21B93Ci1kA/++B+hIgGw71HzTOWQoUhwjlrdWcoVUxSvpPJoMs7oURg==", + "dev": true, "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "@iconify/types": "*" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, "optional": true, "os": [ "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" } }, - "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, "dependencies": { - "ini": "1.3.7" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "@shikijs/types": "2.5.0" } }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", - "engines": { - "node": ">=4" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.5.0" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" + "@types/unist": "*" } }, - "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, - "node_modules/is-npm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", - "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "dependencies": { + "@types/unist": "*" } }, - "node_modules/is-obj": { + "node_modules/@types/mdurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } + "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true }, - "node_modules/is-path-inside": { + "node_modules/@types/unist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", + "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", + "dev": true, "engines": { - "node": ">=4" + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==" - }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" } }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, "dependencies": { - "json-buffer": "3.0.0" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dev": true, "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, - "node_modules/livereload": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz", - "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==", + "node_modules/@vue/devtools-api": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.2.tgz", + "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==", + "dev": true, "dependencies": { - "chokidar": "^3.5.0", - "livereload-js": "^3.3.1", - "opts": ">= 1.2.0", - "ws": "^7.4.3" - }, - "bin": { - "livereload": "bin/livereload.js" - }, - "engines": { - "node": ">=8.0.0" + "@vue/devtools-kit": "^7.7.2" } }, - "node_modules/livereload-js": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.4.1.tgz", - "integrity": "sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/@vue/devtools-kit": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", + "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", + "dev": true, "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" + "@vue/devtools-shared": "^7.7.2", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" } }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "engines": { - "node": ">=0.10.0" + "node_modules/@vue/devtools-shared": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", + "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", + "dev": true, + "dependencies": { + "rfdc": "^1.4.1" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dev": true, "dependencies": { - "yallist": "^3.0.2" + "@vue/shared": "3.5.13" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "dev": true, "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, - "node_modules/marked": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.9.tgz", - "integrity": "sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw==", - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">= 8.16.2" + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" } }, - "node_modules/medium-zoom": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/medium-zoom/-/medium-zoom-1.0.8.tgz", - "integrity": "sha512-CjFVuFq/IfrdqesAXfg+hzlDKu6A2n80ZIq0Kl9kWjoHh9j1N9Uvk5X0/MmN0hOfm5F9YBswlClhcwnmtwz7gA==" - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "dev": true, + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "engines": { - "node": ">=4" + "peerDependencies": { + "vue": "3.5.13" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "engines": { - "node": ">=4" - } + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/nested-error-stacks": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", - "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==" - }, - "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" }, - "engines": { - "node": "4.x || >=6.0.0" + "funding": { + "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "encoding": "^0.1.0" + "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" }, "peerDependenciesMeta": { - "encoding": { + "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 } } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "engines": { - "node": ">=8" + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, "dependencies": { - "ee-first": "1.1.1" + "vue": "^3.5.13" }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "node_modules/algoliasearch": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/algoliasearch/-/algoliasearch-5.23.3.tgz", + "integrity": "sha512-0JlUaY/hl3LrKvbidI5FysEi2ggAlcTHM8AHV2UsrJUXnNo8/lWBfhzc1b7o8bK3YZNiU26JtLyT9exoj5VBgA==", + "dev": true, "dependencies": { - "is-wsl": "^1.1.0" + "@algolia/client-abtesting": "5.23.3", + "@algolia/client-analytics": "5.23.3", + "@algolia/client-common": "5.23.3", + "@algolia/client-insights": "5.23.3", + "@algolia/client-personalization": "5.23.3", + "@algolia/client-query-suggestions": "5.23.3", + "@algolia/client-search": "5.23.3", + "@algolia/ingestion": "1.23.3", + "@algolia/monitoring": "1.23.3", + "@algolia/recommend": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" }, "engines": { - "node": ">=8" - } - }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "bin": { - "opencollective-postinstall": "index.js" + "node": ">= 14.0.0" } }, - "node_modules/opts": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz", - "integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==" - }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "engines": { - "node": ">=6" + "node_modules/birpc": { + "version": "0.2.19", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" } }, - "node_modules/p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", - "dependencies": { - "p-timeout": "^3.1.0" - }, - "engines": { - "node": ">=8" - }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "engines": { - "node": ">=4" + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, "dependencies": { - "p-finally": "^1.0.0" + "is-what": "^4.1.8" }, "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parent-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", - "integrity": "sha512-2MXDNZC4aXdkkap+rBBMv0lUsfJqvX5/2FiYYnfCnorZt3Pk06/IOR5KeaoghgS2w07MLWgjbsnyaq6PdHn2LQ==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" + "node": ">=12.13" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "engines": { - "node": ">=4" - } + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", "engines": { "node": ">=6" } }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dependencies": { - "escape-goat": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, "dependencies": { - "picomatch": "^2.2.1" + "dequal": "^2.0.0" }, - "engines": { - "node": ">=8.10.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/registry-auth-token": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", - "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", - "dependencies": { - "rc": "1.2.8" - }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dependencies": { - "rc": "^1.2.8" + "node": ">=0.12" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", - "dependencies": { - "lowercase-keys": "^1.0.0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dependencies": { - "semver": "^6.3.0" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=8" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" + "node": ">=12" }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/focus-trap": { + "version": "7.6.4", + "resolved": "https://registry.npmmirror.com/focus-trap/-/focus-trap-7.6.4.tgz", + "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", + "dev": true, "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" + "tabbable": "^6.2.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/strip-indent": { + "node_modules/hast-util-whitespace": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, "dependencies": { - "min-indent": "^1.0.0" + "@types/hast": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/term-size": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", - "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=12.13" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/tinydate": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz", - "integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==", - "engines": { - "node": ">=4" + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "engines": { - "node": ">=6" - } + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmmirror.com/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, "dependencies": { - "is-number": "^7.0.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, - "engines": { - "node": ">=8.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/tweezer.js": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/tweezer.js/-/tweezer.js-1.5.0.tgz", - "integrity": "sha512-aSiJz7rGWNAQq7hjMK9ZYDuEawXupcCWgl3woQQSoDP2Oh8O4srWb/uO1PzzHIsrPEOqrjJ2sUb9FERfzuBabQ==" + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/minisearch": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minisearch/-/minisearch-7.1.2.tgz", + "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==", + "dev": true }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, "dependencies": { - "is-typedarray": "^1.0.0" + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" } }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "crypto-random-string": "^2.0.0" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "engines": { - "node": ">= 4.0.0" + "node_modules/preact": { + "version": "10.26.5", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.26.5.tgz", + "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-notifier": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", - "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", - "dependencies": { - "boxen": "^4.2.0", - "chalk": "^3.0.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.3.1", - "is-npm": "^4.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.0.0", - "pupa": "^2.0.1", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, + "node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "dev": true, "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/update-notifier/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "regex-utilities": "^2.3.0" } }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=8" + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" } }, - "node_modules/update-notifier/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmmirror.com/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" - }, + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "engines": { - "node": ">=7.0.0" + "node": ">=0.10.0" } }, - "node_modules/update-notifier/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/update-notifier/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/update-notifier/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "dev": true, "dependencies": { - "prepend-http": "^2.0.0" + "copy-anything": "^3.0.2" }, "engines": { - "node": ">=4" + "node": ">=16" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true }, - "node_modules/webidl-conversions": { + "node_modules/trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, "dependencies": { - "string-width": "^4.0.0" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "@types/unist": "^3.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" }, - "engines": { - "node": ">=7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "node_modules/vite": { + "version": "5.4.18", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.18.tgz", + "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=8.3.0" + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { - "bufferutil": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { "optional": true }, - "utf-8-validate": { + "terser": { "optional": true } } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/yargonaut": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", - "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", - "dependencies": { - "chalk": "^1.1.1", - "figlet": "^1.1.1", - "parent-require": "^1.0.0" - } - }, - "node_modules/yargonaut/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "node_modules/vitepress": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/vitepress/-/vitepress-1.6.3.tgz", + "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", + "dev": true, "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } } }, - "node_modules/yargonaut/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "dev": true, "dependencies": { - "ansi-regex": "^2.0.0" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "peerDependencies": { + "typescript": "*" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } } } diff --git a/package.json b/package.json index 9ab4ca82..6c16a104 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,10 @@ { - "name": "source-code-hunter", - "version": "1.0.0", - "description": "从源码层面,剖析挖掘互联网行业主流技术的底层实现原理,为广大开发者 “提升技术深度” 提供便利。", - "scripts": { - "dev": "docsify serve --open", - "convert": "docsify-pdf-converter" - }, - "dependencies": { - "docsify-cli": "^4.4.4" - }, "devDependencies": { - "prettier": "2.8.8" + "vitepress": "^1.6.3" }, - "license": "SEE LICENSE IN LICENSE" -} + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + } +} \ No newline at end of file From fc23bc36b8b0cd5f231e3c5e2b997e94dd97ddbd Mon Sep 17 00:00:00 2001 From: yanglbme Date: Tue, 15 Apr 2025 06:31:22 +0800 Subject: [PATCH 2/5] fix: update docs --- docs/Spring/JDBC/Spring-jdbc.md | 483 ------------------ ...41\344\270\216\345\256\236\347\216\260.md" | 2 +- .../Spring-PlaceholderResolver.md | 2 +- docs/Spring/clazz/Spring-BeanNameGenerator.md | 2 +- docs/Spring/clazz/Spring-Property.md | 6 +- 5 files changed, 6 insertions(+), 489 deletions(-) diff --git a/docs/Spring/JDBC/Spring-jdbc.md b/docs/Spring/JDBC/Spring-jdbc.md index bdc8189d..e69de29b 100644 --- a/docs/Spring/JDBC/Spring-jdbc.md +++ b/docs/Spring/JDBC/Spring-jdbc.md @@ -1,483 +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](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/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/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/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/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-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` From bee198f4ccc7960fd52441077342f461d5bba28a Mon Sep 17 00:00:00 2001 From: yanglbme Date: Tue, 15 Apr 2025 06:33:00 +0800 Subject: [PATCH 3/5] fix: --- .../IoC/\345\276\252\347\216\257\344\276\235\350\265\226.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" index b1057f2c..a7506b3e 100644 --- "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" @@ -10,7 +10,7 @@ tip: 解决方法:当一个对象已经实例化完毕了,还未初始化的时候,将它注入到它所依赖的已经实例好的对象(提前暴露对象),使得它所依赖的对象是个完整对象(实例化+初始化),然后再将这个完整对象注入给它。 -## 简单工程(Spring-version-5.3.18) +## 简单工程(Spring-version-5.3.18) 我们就用下面两个类进行实践,多个类间依赖也是如此。 From 464d7b5c0f2bd279198289832488348c4fb252b8 Mon Sep 17 00:00:00 2001 From: Libin YANG Date: Tue, 15 Apr 2025 04:46:18 +0000 Subject: [PATCH 4/5] docs: update --- README.md | 4 - docs/.vitepress/config.mts | 7 - ...pringBootBatch\346\272\220\347\240\201.md" | 2740 ----------------- ...01\347\250\213\350\241\245\345\205\205.md" | 41 +- ...50\346\210\267\350\256\244\350\257\201.md" | 12 +- 5 files changed, 28 insertions(+), 2776 deletions(-) delete mode 100644 "docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" diff --git a/README.md b/README.md index dc6aadeb..5dae38c9 100644 --- a/README.md +++ b/README.md @@ -118,10 +118,6 @@ - [SpringBoot 日志系统](/docs/SpringBoot/SpringBoot-LogSystem.md) - [SpringBoot ConditionalOnBean](/docs/SpringBoot/SpringBoot-ConditionalOnBean.md) -### SpringBootBatch - -- [SpringBootBatch 源码](/docs/SpringBootBatch/SpringBootBatch源码.md) - ### Spring Cloud - [Spring Cloud Commons 源码](docs/SpringCloud/spring-cloud-commons-source-note.md) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 76409e5a..03139f16 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -171,13 +171,6 @@ export default defineConfig({ { text: 'SpringBoot ConditionalOnBean', link: '/SpringBoot/SpringBoot-ConditionalOnBean' }, ], }, - // { - // text: 'SpringBootBatch', - // collapsed: true, - // items: [ - // { text: 'SpringBootBatch 源码', link: '/SpringBootBatch/SpringBootBatch源码' }, - // ], - // }, { text: 'Spring Cloud', collapsed: true, diff --git "a/docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" "b/docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" deleted file mode 100644 index d02a1372..00000000 --- "a/docs/SpringBootBatch/SpringBootBatch\346\272\220\347\240\201.md" +++ /dev/null @@ -1,2740 +0,0 @@ -# SpringBootBatch 源码 - -## 加载 - -版本使用 2.7.13 - -```xml - - org.springframework.boot - spring-boot-dependencies - 2.7.13 - import - pom - -``` - -> spring-autoconfigure-metadata.properties - -```properties -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration= -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration= -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration.ConditionalOnBean=javax.sql.DataSource -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration$DataSourceInitializerConfiguration.ConditionalOnClass=org.springframework.jdbc.datasource.init.DatabasePopulator -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnBean=org.springframework.batch.core.launch.JobLauncher -org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.batch.core.launch.JobLauncher -org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration= -org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration$JpaBatchConfiguration= -org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration$JpaBatchConfiguration.ConditionalOnBean= -org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration$JpaBatchConfiguration.ConditionalOnClass=javax.persistence.EntityManagerFactory -org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration.ConditionalOnBean=javax.sql.DataSource -org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration.ConditionalOnClass=org.springframework.transaction.PlatformTransactionManager -``` - -### @EnableBatchProcessing - -启动首先添加`@EnableBatchProcessing`,这个类引入了`BatchConfigurationSelector`. - -#### BatchConfigurationSelector - -这里面主要是判断`modular`决定加载`ModularBatchConfiguration`还是`SimpleBatchConfiguration` - -##### SimpleBatchConfiguration - -这个类继承了`AbstractBatchConfiguration`。因为继承了`InitializingBean`执行`afterPropertiesSet` -, 创建出俩个类`jobBuilderFactory`和 `stepBuilderFactory`. - -###### JobBuilderFactory - -JobBuilderFactory 需要`jobRepository`。`SimpleBatchConfiguration#jobRepository()` -方法,进入`SimpleBatchConfiguration#createLazyProxy(AtomicReference reference, Class type)`。 -是使用`ProxyFactory`构建出代理对象,`Advice`使用`PassthruAdvice`类。 -关键是`TargetSource`属性,使用`ReferenceTargetSource`类继承了`AbstractLazyCreationTargetSource`,这是 aop 中`TargetSource` -,一种懒加载方式。在`ReferenceTargetSource#createObject()`中调用了`initialize`方法。 -接着进入`initialize`方法 - -```java -/** - * Sets up the basic components by extracting them from the {@link BatchConfigurer configurer}, defaulting to some - * sensible values as long as a unique DataSource is available. - * - * @throws Exception if there is a problem in the configurer - */ -protected void initialize() throws Exception { - if (initialized) { - return; - } - BatchConfigurer configurer = getConfigurer(context.getBeansOfType(BatchConfigurer.class).values()); - jobRepository.set(configurer.getJobRepository()); - jobLauncher.set(configurer.getJobLauncher()); - transactionManager.set(configurer.getTransactionManager()); - jobRegistry.set(new MapJobRegistry()); - jobExplorer.set(configurer.getJobExplorer()); - initialized = true; -} -``` - -> BatchConfigurer configurer=getConfigurer(context.getBeansOfType(BatchConfigurer.class).values()); - -```java -protected BatchConfigurer getConfigurer(Collection configurers) throws Exception { - if (this.configurer != null) { - return this.configurer; - } - if (configurers == null || configurers.isEmpty()) { - if (dataSource == null) { - DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(); - configurer.initialize(); - this.configurer = configurer; - return configurer; - } else { - DefaultBatchConfigurer configurer = new DefaultBatchConfigurer(dataSource); - configurer.initialize(); - this.configurer = configurer; - return configurer; - } - } - if (configurers.size() > 1) { - throw new IllegalStateException( - "To use a custom BatchConfigurer the context must contain precisely one, found " - + configurers.size()); - } - this.configurer = configurers.iterator().next(); - return this.configurer; -} -``` - -从 spring 中获取 `BatchConfigurer`,没有使用 `DefaultBatchConfigurer`,并调用 `configurer.initialize()` - -1. 获取所有的`BatchConfigurer`类,进入`AbstractBatchConfiguration#getConfigurer` - 方法。如果为空就创建一个`DefaultBatchConfigurer`,并执行`configurer.initialize();`方法,`DefaultBatchConfigurer`后面单独讲。 -2. 这里的`JobRepository`,`JobLauncher`,`JobRegistry`,`PlatformTransactionManager`,`JobExplorer`都是使用`AtomicReference` - 原子性封装的。把`initialized`设置为 true,只允许初始化一次 - -现在`JobRepository`,`JobLauncher`,`JobRegistry`,`PlatformTransactionManager`,`JobExplorer`5 个类就全部初始化好了, - -###### stepBuilderFactory - -与上面一样,只不过初始化过了,就不会再初始化,直接获取值(注意:这里的都是使用`AbstractLazyCreationTargetSource` -创建代理对象,都是懒加载的)。 - -## 使用 - -首先弄清楚类直接关系,不使用数据库。所以所有默认,使用刚刚我们看过的源码默认创建的类就好了。 -我们使用`SimpleBatchConfiguration`进行讲解 - -这里需要设置`spring.batch.job.enabled`为 false,不然会加载`BatchAutoConfiguration#jobLauncherApplicationRunner`类,启动的时候会执行 -job。`BatchProperties`类放在`BatchAutoConfiguration`类中讲解 - -> BatchConfig - -```java - -@Configuration -@EnableBatchProcessing -public class BatchConfig { - - @Autowired - JobBuilderFactory jobBuilders; - - @Autowired - StepBuilderFactory stepBuilders; - - @Bean - public Job footballjob() { - return jobBuilders.get("test-job") - .start(step()) - .build(); - } - - @Bean - public Step step() { - return stepBuilders.get("test-step") - .chunk(2) - .reader(itemReader()) - .writer(itemWriter()) - .build(); - } - - - @Bean - public ItemReader itemReader() { - return new ItemReader() { - final AtomicInteger atomicInteger = new AtomicInteger(); - - @Override - public Integer read() throws UnexpectedInputException, ParseException, NonTransientResourceException { - if (atomicInteger.compareAndSet(10, 0)) { - return null; - } - atomicInteger.getAndIncrement(); - return atomicInteger.intValue(); - } - }; - } - - @Bean - public ItemWriter itemWriter() { - return new ItemWriter() { - @Override - public void write(List items) { - System.out.println("一次读取:" + Arrays.toString(items.toArray())); - } - }; - - } -} -``` - -### Step - -使用`JobBuilderFactory`创建出`Step`。`JobBuilderFactory`在上面`AbstractBatchConfiguration`中创建了。 - -```java -@Bean -public Step step() { - TaskletStep taskletStep = stepBuilders.get("test-step") - .chunk(2) - .reader(itemReader()) - .writer(itemWriter()) - .listener(new StepExecutionListener() { - @Override - public void beforeStep(StepExecution stepExecution) { - System.out.println("step-我是 beforeJob--" + stepExecution); - } - - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - System.out.println("step-我是 beforeJob--" + stepExecution); - return stepExecution.getExitStatus(); - } - }) -// .startLimit(2) - .build(); - - taskletStep.setAllowStartIfComplete(true); - return taskletStep; -} -``` - -#### stepBuilders.get("test-stop") - -`StepBuilderFactory`只有一个方法 get - -```java -public StepBuilder get(String name) { - StepBuilder builder = new StepBuilder(name).repository(jobRepository).transactionManager( - transactionManager); - return builder; -} -``` - -`stepBuilders.get("test-stop")`创建一个`StepBuilder`。 - -`StepBuilder` 继承`StepBuilderHelper`,`StepBuilderHelper` -中有个`protected final CommonStepProperties properties;`属性。 -对`CommonStepProperties`中`name`,`JobRepository`和`PlatformTransactionManager`进行赋值。 - -#### chunk(2) - -```java -/** - * Build a step that processes items in chunks with the size provided. To extend the step to being fault tolerant, - * call the {@link SimpleStepBuilder#faultTolerant()} method on the builder. In most cases you will want to - * parameterize your call to this method, to preserve the type safety of your readers and writers, e.g. - * - *
- * new StepBuilder("step1").<Order, Ledger> chunk(100).reader(new OrderReader()).writer(new LedgerWriter())
- * // ... etc.
- * 
- * - * @param chunkSize the chunk size (commit interval) - * @return a {@link SimpleStepBuilder} - * @param the type of item to be processed as input - * @param the type of item to be output - */ -public SimpleStepBuilder chunk(int chunkSize){ - return new SimpleStepBuilder(this).chunk(chunkSize); -} -``` - -创建一个`SimpleStepBuilder`,并确定泛型,设置一个`chunkSize` -大小。翻译一下注释`构建一个步骤,以提供的大小分块处理项目。要将步骤扩展为容错,请在构建器上调用 SimpleStepBuilder.faultTolerant()方法。在大多数情况下,您需要参数化对此方法的调用,以保护读者和作者的类型安全` -,chunkSize – 块大小(提交间隔)。 -接下来都是操作`SimpleStepBuilder`类。这个大小也就是是每次读取多少次。 -`SimpleStepBuilder`继承`AbstractTaskletStepBuilder>` -类。`AbstractTaskletStepBuilder>`也是继承`StepBuilderHelper>`。 - -#### .reader(itemReader()) - -这里就是读取,需要一个`ItemReader reader`对象。 -创建一个 - -```java -@Bean -public ItemReader itemReader() { - return new ItemReader() { - final AtomicInteger atomicInteger = new AtomicInteger(); - - @Override - public Integer read() throws UnexpectedInputException, ParseException, NonTransientResourceException { - if (atomicInteger.compareAndSet(10, 0)) { - return null; - } - atomicInteger.getAndIncrement(); - return atomicInteger.intValue(); - } - }; -} -``` - -每次读取 1 个值,总共读取 10 个值。返回 null 表示没有数据了。 - -#### .writer(itemWriter()) - -与上面一样。这里的泛型要注意,是根据`.chunk(2)`进行确认的,前面 Integer 表示`reader` -返回值,后面表示`writer`入参。 - -```java -@Bean -public ItemWriter itemWriter() { - return new ItemWriter() { - @Override - public void write(List items) { - System.out.println("一次读取:" + Arrays.toString(items.toArray())); - } - }; -} -``` - -#### .build(); - -```java -/** - * Build a step with the reader, writer, processor as provided. - * - * @see org.springframework.batch.core.step.builder.AbstractTaskletStepBuilder#build() - */ -@Override -public TaskletStep build() { - registerStepListenerAsItemListener(); - registerAsStreamsAndListeners(reader, processor, writer); - return super.build(); -} -``` - -`registerStepListenerAsItemListener()`:注册监听器,`ItemReadListener`,`ItemProcessListener`,`ItemWriteListener`. - -`registerAsStreamsAndListeners`:注册`ItemStream`,`StepListenerMetaData` -中的所有接口,`StepExecutionListener`,`ChunkListener`,`ItemReadListener`,`ItemProcessListener`和`ItemWriteListener`. - -`AbstractListenerFactoryBean.isListener(delegate, StepListener.class, StepListenerMetaData.values());`进行判断 - -```java -BEFORE_STEP("beforeStep","before-step-method",BeforeStep.class,StepExecutionListener.class,StepExecution.class), -AFTER_STEP("afterStep","after-step-method",AfterStep.class,StepExecutionListener.class,StepExecution.class), -BEFORE_CHUNK("beforeChunk","before-chunk-method",BeforeChunk.class,ChunkListener.class,ChunkContext.class), -AFTER_CHUNK("afterChunk","after-chunk-method",AfterChunk.class,ChunkListener.class,ChunkContext.class), -AFTER_CHUNK_ERROR("afterChunkError","after-chunk-error-method",AfterChunkError.class,ChunkListener.class,ChunkContext.class), -BEFORE_READ("beforeRead","before-read-method",BeforeRead.class,ItemReadListener.class), -AFTER_READ("afterRead","after-read-method",AfterRead.class,ItemReadListener.class,Object.class), -ON_READ_ERROR("onReadError","on-read-error-method",OnReadError.class,ItemReadListener.class,Exception.class), -BEFORE_PROCESS("beforeProcess","before-process-method",BeforeProcess.class,ItemProcessListener.class,Object.class), -AFTER_PROCESS("afterProcess","after-process-method",AfterProcess.class,ItemProcessListener.class,Object.class,Object.class), -ON_PROCESS_ERROR("onProcessError","on-process-error-method",OnProcessError.class,ItemProcessListener.class,Object.class,Exception.class), -BEFORE_WRITE("beforeWrite","before-write-method",BeforeWrite.class,ItemWriteListener.class,List.class), -AFTER_WRITE("afterWrite","after-write-method",AfterWrite.class,ItemWriteListener.class,List.class), -ON_WRITE_ERROR("onWriteError","on-write-error-method",OnWriteError.class,ItemWriteListener.class,Exception.class,List.class), -ON_SKIP_IN_READ("onSkipInRead","on-skip-in-read-method",OnSkipInRead.class,SkipListener.class,Throwable.class), -ON_SKIP_IN_PROCESS("onSkipInProcess","on-skip-in-process-method",OnSkipInProcess.class,SkipListener.class,Object.class,Throwable.class), -ON_SKIP_IN_WRITE("onSkipInWrite","on-skip-in-write-method",OnSkipInWrite.class,SkipListener.class,Object.class,Throwable.class); -``` - -这些监听器之类的后面单独讲,所有这些之类的顶级接口`StepListener`。 - -> super.build(); - -```java -public TaskletStep build() { - - registerStepListenerAsChunkListener(); - - TaskletStep step = new TaskletStep(getName()); - - super.enhance(step); - - step.setChunkListeners(chunkListeners.toArray(new ChunkListener[0])); - - if (transactionAttribute != null) { - step.setTransactionAttribute(transactionAttribute); - } - - if (stepOperations == null) { - - stepOperations = new RepeatTemplate(); - - if (taskExecutor != null) { - TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate(); - repeatTemplate.setTaskExecutor(taskExecutor); - repeatTemplate.setThrottleLimit(throttleLimit); - stepOperations = repeatTemplate; - } - - ((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler); - - } - step.setStepOperations(stepOperations); - step.setTasklet(createTasklet()); - - step.setStreams(streams.toArray(new ItemStream[0])); - - try { - step.afterPropertiesSet(); - } catch (Exception e) { - throw new StepBuilderException(e); - } - - return step; - -} -``` - -###### super.enhance(step); - -把`CommonStepProperties`中的`jobRepository`,`allowStartIfComplete`,`startLimit`和`stepExecutionListeners`赋值给`step`. - -> RepeatOperations - -注释翻译后:批处理模板实现的简单实现 RepeatOperations 和基类。提供包含拦截器和策略的框架。子类只需要提供一个获取下一个结果的方法,以及一个等待所有结果从并发进程或线程返回的方法。 -注意:模板在迭代过程中累积抛出的异常,当主循环结束时(即完成对项目的处理),它们都会一起处理。不希望在引发异常时停止执行的客户端可以使用在收到异常时未完成的特定 -CompletionPolicy 。这不是默认行为。 -想要在 引发 RepeatCallback 异常时执行某些业务操作的客户端可以考虑使用自定义,而不是尝试自定义 RepeatListener -CompletionPolicy. 这通常是一个更友好的接口来实现,该方法在回调的结果中传递,如果业务处理抛出异常, RepeatListener.after( -RepeatContext, RepeatStatus) 这将是一个实例 Throwable 。 -如果不将异常传播到调用方,则还需要提供非默认值 CompletionPolicy ,但这可能是现成的,业务操作仅在拦截器中实现。 - -taskExecutor:一般用来设置线程池线程池。这里我们也没有设置这个值。 - -((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler);:异常处理,默认抛出异常,`DefaultExceptionHandler` -类。 - -> step.setStepOperations(stepOperations); - -赋值给 step - -> step.setTasklet(createTasklet()); - -```java -@Override -protected Tasklet createTasklet() { - Assert.state(reader != null, "ItemReader must be provided"); - Assert.state(writer != null, "ItemWriter must be provided"); - RepeatOperations repeatOperations = createChunkOperations(); - SimpleChunkProvider chunkProvider = new SimpleChunkProvider<>(getReader(), repeatOperations); - SimpleChunkProcessor chunkProcessor = new SimpleChunkProcessor<>(getProcessor(), getWriter()); - chunkProvider.setListeners(new ArrayList<>(itemListeners)); - chunkProcessor.setListeners(new ArrayList<>(itemListeners)); - ChunkOrientedTasklet tasklet = new ChunkOrientedTasklet<>(chunkProvider, chunkProcessor); - tasklet.setBuffering(!readerTransactionalQueue); - return tasklet; -} -``` - -`createChunkOperations();`:重复处理类,这里也是`RepeatTemplate`。 - -```java -protected RepeatOperations createChunkOperations() { - RepeatOperations repeatOperations = chunkOperations; - if (repeatOperations == null) { - RepeatTemplate repeatTemplate = new RepeatTemplate(); - repeatTemplate.setCompletionPolicy(getChunkCompletionPolicy()); - repeatOperations = repeatTemplate; - } - return repeatOperations; -} -``` - -里面的`repeatTemplate.setCompletionPolicy(getChunkCompletionPolicy());`。 - -```java -/** - * @return a {@link CompletionPolicy} consistent with the chunk size and injected policy (if present). - */ -protected CompletionPolicy getChunkCompletionPolicy() { - Assert.state(!(completionPolicy != null && chunkSize > 0), - "You must specify either a chunkCompletionPolicy or a commitInterval but not both."); - Assert.state(chunkSize >= 0, "The commitInterval must be positive or zero (for default value)."); - - if (completionPolicy != null) { - return completionPolicy; - } - if (chunkSize == 0) { - if (logger.isInfoEnabled()) { - logger.info("Setting commit interval to default value (" + DEFAULT_COMMIT_INTERVAL + ")"); - } - chunkSize = DEFAULT_COMMIT_INTERVAL; - } - return new SimpleCompletionPolicy(chunkSize); -} -``` - -每次处理大小:`在固定数量的操作后终止批处理的策略。维护内部状态并增加计数器,因此成功使用此策略需要每个批处理项仅调用一次 isComplete()。使用标准的 RepeatTemplate 应确保保留此合同,但需要仔细监控。` -留心观察一下就会发现,这个值是`.chunk(2)`这里的 -2,里面还可以传一个` chunk(CompletionPolicy completionPolicy)`。 - -`SimpleStepBuilder#createTasklet()`创建一个`ChunkOrientedTasklet`。里面有俩个属性`chunkProvider`和`chunkProcessor`。 - -> SimpleChunkProvider chunkProvider = new SimpleChunkProvider<>(getReader(), repeatOperations); - -然后创建`SimpleChunkProvider`对象,`getReader()`就是`ItemReader`,`repeatOperations`就是`SimpleCompletionPolicy`。 - -> impleChunkProcessor chunkProcessor = new SimpleChunkProcessor<>(getProcessor(), getWriter()); - -`getProcessor()`:就是`processor(Function function)`,`stepBuilders.get().processor()`里面的值。 - -使用`chunkProvider, chunkProcessor`构建出`ChunkOrientedTasklet`返回给`step`的`tasklet`。 - -```java -/** - * Public setter for the {@link Tasklet}. - * - * @param tasklet the {@link Tasklet} to set - */ -public void setTasklet(Tasklet tasklet) { - this.tasklet = tasklet; - if (tasklet instanceof StepExecutionListener) { - registerStepExecutionListener((StepExecutionListener) tasklet); - } -} -``` - -这里默认构建出来的不是`StepExecutionListener`。是`ChunkOrientedTasklet`。这里不是主体流程,下面补充。 - -> step.afterPropertiesSet(); - -里面就是一些校验了。 - -### Job - -使用`JobBuilderFactory`创建出`Step`。`JobBuilderFactory`在上面`AbstractBatchConfiguration`创建了。 - -```java -@Bean -public Job footballjob() { - return jobBuilders.get("test") - .start(step()) - .build(); -} -``` - -#### jobBuilders.get("test") - -与 step 基本一样,先创建`JobBuilder`。 `Flow`下面单独讲。 - -#### .start(step()) - -```java -/** - * Create a new job builder that will execute a step or sequence of steps. - * - * @param step a step to execute - * @return a {@link SimpleJobBuilder} - */ -public SimpleJobBuilder start(Step step) { - return new SimpleJobBuilder(this).start(step); -} -``` - -创建一个`SimpleJobBuilder`,set 属性` private List steps = new ArrayList<>();`。 - -#### .build(); - -```java -public Job build() { - if (builder != null) { - return builder.end().build(); - } - SimpleJob job = new SimpleJob(getName()); - super.enhance(job); - job.setSteps(steps); - try { - job.afterPropertiesSet(); - } catch (Exception e) { - throw new JobBuilderException(e); - } - return job; -} -``` - -真正构建出来的是`SimpleJob`对象。 - -> super.enhance(job); - -```java -protected void enhance(Job target) { - if (target instanceof AbstractJob) { - AbstractJob job = (AbstractJob) target; - job.setJobRepository(properties.getJobRepository()); - JobParametersIncrementer jobParametersIncrementer = properties.getJobParametersIncrementer(); - if (jobParametersIncrementer != null) { - job.setJobParametersIncrementer(jobParametersIncrementer); - } - JobParametersValidator jobParametersValidator = properties.getJobParametersValidator(); - if (jobParametersValidator != null) { - job.setJobParametersValidator(jobParametersValidator); - } - Boolean restartable = properties.getRestartable(); - if (restartable != null) { - job.setRestartable(restartable); - } - List listeners = properties.getJobExecutionListeners(); - if (!listeners.isEmpty()) { - job.setJobExecutionListeners(listeners.toArray(new JobExecutionListener[0])); - } - } -} -``` - -把`CommonJobProperties properties`的属性赋值,比如`JobRepository`,`JobParametersIncrementer`,`JobParametersValidator` -和`Restartable`. - -> job.setJobExecutionListeners(listeners.toArray(new JobExecutionListener[0])); - -```java -List listeners = properties.getJobExecutionListeners(); -if (!listeners.isEmpty()) { - job.setJobExecutionListeners(listeners.toArray(new JobExecutionListener[0])); -} -``` - -获取里面的`JobExecutionListener`set。 - -> job.setSteps(steps); - -里面所有的`steps`。 - -> job.afterPropertiesSet(); - -一些校验 - -### JobLauncher - -真正的使用类,在 spring-boot 中`ApplicationRunner` -也是使用这个`JobLauncherApplicationRunner#executeRegisteredJobs(JobParameters jobParameters)`。 - -```java -@RestController -public class BatchController { - - @Autowired - JobLauncher jobLauncher; - - @Autowired - Job footballjob; - - - @GetMapping("/batch") - public String getBatch() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { - JobParameters jobParameters = new JobParametersBuilder().toJobParameters(); - jobLauncher.run(footballjob, jobParameters); - return "batch"; - } -} -``` - -`JobParameters`下面单独讲解。 - -`JobLauncher`对象在`SimpleBatchConfiguration`中使用懒加载的代理对象创建出来的。 - -`Job`是上面我们自己创建的。 - -进入到`SimpleJobLauncher#run(final Job job, final JobParameters jobParameters)`中。 - -#### `JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters);` - -```java -@Override -@Nullable -public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { - JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters); - if (jobInstance == null) { - return null; - } - JobExecution jobExecution = jobExecutionDao.getLastJobExecution(jobInstance); - - if (jobExecution != null) { - jobExecution.setExecutionContext(ecDao.getExecutionContext(jobExecution)); - stepExecutionDao.addStepExecutions(jobExecution); - } - return jobExecution; - -} -``` - -`MapJobInstanceDao`已弃用 从 v4.3 开始,赞成使用 与 JdbcJobInstanceDao 内存数据库一起使用。计划在 v5.0 中删除。 - -`MapJobInstanceDao#getJobInstance`,第一次进来是获取不到的,直接返回 null。 - -#### job.getJobParametersValidator().validate(jobParameters); - -校验参数。我们没填。 - -#### jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - -创建一个`JobExecution` - -`SimpleJobRepository#createJobExecution(String jobName, JobParameters jobParameters)` - -```java -@Override -public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { - JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters); - ExecutionContext executionContext; - if (jobInstance != null) { - List executions = jobExecutionDao.findJobExecutions(jobInstance); - if (executions.isEmpty()) { - throw new IllegalStateException("Cannot find any job execution for job instance: " + jobInstance); - } - for (JobExecution execution : executions) { - if (execution.isRunning() || execution.isStopping()) { - throw new JobExecutionAlreadyRunningException("A job execution for this job is already running: " - + jobInstance); - } - BatchStatus status = execution.getStatus(); - if (status == BatchStatus.UNKNOWN) { - throw new JobRestartException("Cannot restart job from UNKNOWN status. " - + "The last execution ended with a failure that could not be rolled back, " - + "so it may be dangerous to proceed. Manual intervention is probably necessary."); - } - Collection allJobParameters = execution.getJobParameters().getParameters().values(); - long identifyingJobParametersCount = allJobParameters.stream().filter(JobParameter::isIdentifying).count(); - if (identifyingJobParametersCount > 0 && (status == BatchStatus.COMPLETED || status == BatchStatus.ABANDONED)) { - throw new JobInstanceAlreadyCompleteException( - "A job instance already exists and is complete for parameters=" + jobParameters - + ". If you want to run this job again, change the parameters."); - } - } - executionContext = ecDao.getExecutionContext(jobExecutionDao.getLastJobExecution(jobInstance)); - } else { - // no job found, create one - jobInstance = jobInstanceDao.createJobInstance(jobName, jobParameters); - executionContext = new ExecutionContext(); - } - JobExecution jobExecution = new JobExecution(jobInstance, jobParameters, null); - jobExecution.setExecutionContext(executionContext); - jobExecution.setLastUpdated(new Date(System.currentTimeMillis())); - // Save the JobExecution so that it picks up an ID (useful for clients - // monitoring asynchronous executions): - jobExecutionDao.saveJobExecution(jobExecution); - ecDao.saveExecutionContext(jobExecution); - return jobExecution; -} -``` - -首先`JobInstance jobInstance = jobInstanceDao.getJobInstance(jobName, jobParameters);`。这里的`jobInstanceDao`是 -Mapxxx,注意这几个类都是过时的,我们先把流程弄懂在看这些细节。 -为空,去到`else`。 - -##### jobInstanceDao#createJobInstance(String jobName, JobParameters jobParameters) - -```java -@Override -public JobInstance createJobInstance(String jobName, JobParameters jobParameters) { - Assert.state(getJobInstance(jobName, jobParameters) == null, "JobInstance must not already exist"); - JobInstance jobInstance = new JobInstance(currentId.getAndIncrement(), jobName); - jobInstance.incrementVersion(); - jobInstances.put(jobName + "|" + jobKeyGenerator.generateKey(jobParameters), jobInstance); - return jobInstance; -} -``` - -就是`Map jobInstances = new ConcurrentHashMap<>();`put -了一个值。后面`jobKeyGenerator.generateKey(jobParameters)`默认为一个 md5 加密算法。 -现在`MapJobInstanceDao#jobInstances`就有值了。接着回到`SimpleJobRepository`类。 - -这里有个地方需要注意,这些类都是一些代理对象,使用代理类懒加载的。断点打在实现类上就好了! - -##### executionContext = new ExecutionContext(); - -里面维护了一个`dirty`和`Map map`。 - -##### JobExecution - -接着创建一个`JobExecution`对象。继承了`Entity`对象。 -把`ExecutionContext`赋值。 -最后修改时间赋值为当前时间。 -这个类里面关注`jobParameters`和`jobInstance`对象。`jobInstanceDao`创建的`jobInstance`对象。 - -##### jobExecutionDao.saveJobExecution(jobExecution); - -```java -Assert.isTrue(jobExecution.getId() == null, "jobExecution id is not null"); -Long newId = currentId.getAndIncrement(); -jobExecution.setId(newId); -jobExecution.incrementVersion(); -executionsById.put(newId, copy(jobExecution)); -``` - -来到`MapJobExecutionDao`类,这 4 个`Dao`后面单独讲解。现在都是使用 MapxxxDao。 - -里面有一个`ConcurrentMap executionsById = new ConcurrentHashMap<>();` -和`AtomicLong currentId = new AtomicLong(0L);` - -版本号和值加一,并且存入 map 中,如果是数据库,就会存入数据库。逻辑基本一致。 - -##### ecDao.saveExecutionContext(jobExecution); - -```java -@Override -public void saveExecutionContext(JobExecution jobExecution) { - updateExecutionContext(jobExecution); -} - -@Override -public void updateExecutionContext(JobExecution jobExecution) { - ExecutionContext executionContext = jobExecution.getExecutionContext(); - if (executionContext != null) { - contexts.put(ContextKey.job(jobExecution.getId()), copy(executionContext)); - } -} -``` - -值放入`ConcurrentMap contexts = TransactionAwareProxyFactory.createAppendOnlyTransactionalMap()` -中 - -#### taskExecutor.execute(new Runnable() ) - -```java -try { - taskExecutor.execute(new Runnable() { - @Override - public void run() { - try { - job.execute(jobExecution); - } catch (Throwable t) { - rethrow(t); - } - } - - private void rethrow(Throwable t) { - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } else if (t instanceof Error) { - throw (Error) t; - } - throw new IllegalStateException(t); - } - }); -} catch (TaskRejectedException e) { - jobExecution.upgradeStatus(BatchStatus.FAILED); - if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) { - jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e)); - } - jobRepository.update(jobExecution); -} -``` - -`taskExecutor`是在`afterPropertiesSet()`方法创建的,为`SyncTaskExecutor` -。这个是在`DefaultBatchConfigurer#createJobLauncher()`中调用。 - -这里主要就是俩行代码 - -执行`job.execute(jobExecution);`,出现异常执行`rethrow(t);`。 - -##### job.execute(jobExecution execution) - -job 是`SimpleJob`类。进入`AbstractJob#execute(JobExecution execution)`方法。 - -```java -@Override -public final void execute(JobExecution execution) { - Assert.notNull(execution, "jobExecution must not be null"); - if (logger.isDebugEnabled()) { - logger.debug("Job execution starting: " + execution); - } - JobSynchronizationManager.register(execution); - LongTaskTimer longTaskTimer = BatchMetrics.createLongTaskTimer("job.active", "Active jobs", - Tag.of("name", execution.getJobInstance().getJobName())); - LongTaskTimer.Sample longTaskTimerSample = longTaskTimer.start(); - Timer.Sample timerSample = BatchMetrics.createTimerSample(); - try { - jobParametersValidator.validate(execution.getJobParameters()); - if (execution.getStatus() != BatchStatus.STOPPING) { - execution.setStartTime(new Date()); - updateStatus(execution, BatchStatus.STARTED); - listener.beforeJob(execution); - try { - doExecute(execution); - if (logger.isDebugEnabled()) { - logger.debug("Job execution complete: " + execution); - } - } catch (RepeatException e) { - throw e.getCause(); - } - } else { - // The job was already stopped before we even got this far. Deal - // with it in the same way as any other interruption. - execution.setStatus(BatchStatus.STOPPED); - execution.setExitStatus(ExitStatus.COMPLETED); - if (logger.isDebugEnabled()) { - logger.debug("Job execution was stopped: " + execution); - } - } - } catch (JobInterruptedException e) { - if (logger.isInfoEnabled()) { - logger.info("Encountered interruption executing job: " - + e.getMessage()); - } - if (logger.isDebugEnabled()) { - logger.debug("Full exception", e); - } - execution.setExitStatus(getDefaultExitStatusForFailure(e, execution)); - execution.setStatus(BatchStatus.max(BatchStatus.STOPPED, e.getStatus())); - execution.addFailureException(e); - } catch (Throwable t) { - logger.error("Encountered fatal error executing job", t); - execution.setExitStatus(getDefaultExitStatusForFailure(t, execution)); - execution.setStatus(BatchStatus.FAILED); - execution.addFailureException(t); - } finally { - try { - if (execution.getStatus().isLessThanOrEqualTo(BatchStatus.STOPPED) - && execution.getStepExecutions().isEmpty()) { - ExitStatus exitStatus = execution.getExitStatus(); - ExitStatus newExitStatus = - ExitStatus.NOOP.addExitDescription("All steps already completed or no steps configured for this job."); - execution.setExitStatus(exitStatus.and(newExitStatus)); - } - timerSample.stop(BatchMetrics.createTimer("job", "Job duration", - Tag.of("name", execution.getJobInstance().getJobName()), - Tag.of("status", execution.getExitStatus().getExitCode()) - )); - longTaskTimerSample.stop(); - execution.setEndTime(new Date()); - try { - listener.afterJob(execution); - } catch (Exception e) { - logger.error("Exception encountered in afterJob callback", e); - } - jobRepository.update(execution); - } finally { - JobSynchronizationManager.release(); - } - } -} -``` - -##### JobSynchronizationManager.register(execution); - -```java -@Nullable -public C register(@Nullable E execution) { - if (execution == null) { - return null; - } - getCurrent().push(execution); - C context; - synchronized (contexts) { - context = contexts.get(execution); - if (context == null) { - context = createNewContext(execution, null); - contexts.put(execution, context); - } - } - increment(); - return context; -} -``` - -`SynchronizationManagerSupport`的泛型为`JobExecution, JobContext` - -```java - /** - * 当前执行的存储; 必须是 ThreadLocal 的,因为它需要在不属于步骤 / 作业的组件中定位上下文 (如重新补水作用域代理时)。不使用 InheritableThreadLocal,因为如果一个步骤试图运行多个子步骤 (例如分区),会有副作用。 - * 堆栈用于覆盖单线程的情况,从而使 API 与多线程相同。 - */ -private final ThreadLocal>executionHolder=new ThreadLocal<>(); - -/** - * 每个执行的引用计数器: 有多少个线程正在使用同一个? - */ -private final Map counts=new ConcurrentHashMap<>(); - -/** - * 从正在运行的执行到关联上下文的简单映射。 - */ -private final Map contexts=new ConcurrentHashMap<>(); -``` - -> `increment();` - -```java -public void increment() { - E current = getCurrent().peek(); - if (current != null) { - AtomicInteger count; - synchronized (counts) { - count = counts.get(current); - if (count == null) { - count = new AtomicInteger(); - counts.put(current, count); - } - } - count.incrementAndGet(); - } -} -``` - -简单来说就是里面三个属性不存在就创建。存在把`executionHolder`更新。`count`++。 - -`SynchronizationManagerSupport`类维护了三个对象 - -```java - /** - * Storage for the current execution; has to be ThreadLocal because it - * is needed to locate a context in components that are not part of a - * step/job (like when re-hydrating a scoped proxy). Doesn't use - * InheritableThreadLocal because there are side effects if a step is trying - * to run multiple child steps (e.g. with partitioning). The Stack is used - * to cover the single threaded case, so that the API is the same as - * multi-threaded. - */ -private final ThreadLocal>executionHolder=new ThreadLocal<>(); - -/** - * Reference counter for each execution: how many threads are using the - * same one? - */ -private final Map counts=new ConcurrentHashMap<>(); - -/** - * Simple map from a running execution to the associated context. - */ -private final Map contexts=new ConcurrentHashMap<>(); -``` - -接着回到`AbstractJob`类 - -`LongTaskTimer`:计时器。 - -> jobParametersValidator.validate(execution.getJobParameters()); - -参数没有,跳过 - -> if (execution.getStatus() != BatchStatus.STOPPING) { - -默认状态为`BatchStatus.STARTING`。 - -> execution.setStartTime(new Date()); - -设置当前开始时间 - -##### updateStatus(execution, BatchStatus.STARTED); - -```java -private void updateStatus(JobExecution jobExecution, BatchStatus status) { - jobExecution.setStatus(status); - jobRepository.update(jobExecution); -} -``` - -> jobExecution.setStatus(status); - -设置当前状态 . - -> jobRepository.update(jobExecution); - -更新当前`jobExecution`。 - -`SimpleJobRepository#update(JobExecution jobExecution)` - -```java -@Override -public void update(JobExecution jobExecution) { - - Assert.notNull(jobExecution, "JobExecution cannot be null."); - Assert.notNull(jobExecution.getJobId(), "JobExecution must have a Job ID set."); - Assert.notNull(jobExecution.getId(), "JobExecution must be already saved (have an id assigned)."); - - jobExecution.setLastUpdated(new Date(System.currentTimeMillis())); - - jobExecutionDao.synchronizeStatus(jobExecution); - if (jobExecution.getStatus() == BatchStatus.STOPPING && jobExecution.getEndTime() != null) { - if (logger.isInfoEnabled()) { - logger.info("Upgrading job execution status from STOPPING to STOPPED since it has already ended."); - } - jobExecution.upgradeStatus(BatchStatus.STOPPED); - } - jobExecutionDao.updateJobExecution(jobExecution); -} -``` - -先判断不能为空,设置最后修改时间。 - -> jobExecutionDao.synchronizeStatus(jobExecution); - -`MapJobExecutionDao#synchronizeStatus(JobExecution jobExecution)` - -```java -@Override -public void synchronizeStatus(JobExecution jobExecution) { - JobExecution saved = getJobExecution(jobExecution.getId()); - if (saved.getVersion().intValue() != jobExecution.getVersion().intValue()) { - jobExecution.upgradeStatus(saved.getStatus()); - jobExecution.setVersion(saved.getVersion()); - } -} -``` - -版本不一样就更新,然后版本 ++,这里版本没有改变。回到`SimpleJobRepository#update(JobExecution jobExecution)` -。如果状态`STOPPING`并且结束时间不为空,就使用`jobExecution`更新。这里也没有结束,不需要更新。 - -> jobExecutionDao.updateJobExecution(jobExecution); - -`MapJobExecutionDao#updateJobExecution(JobExecution jobExecution)` - -```java -@Override -public void updateJobExecution(JobExecution jobExecution) { - Long id = jobExecution.getId(); - Assert.notNull(id, "JobExecution is expected to have an id (should be saved already)"); - JobExecution persistedExecution = executionsById.get(id); - Assert.notNull(persistedExecution, "JobExecution must already be saved"); - - synchronized (jobExecution) { - if (!persistedExecution.getVersion().equals(jobExecution.getVersion())) { - throw new OptimisticLockingFailureException("Attempt to update job execution id=" + id - + " with wrong version (" + jobExecution.getVersion() + "), where current version is " - + persistedExecution.getVersion()); - } - jobExecution.incrementVersion(); - executionsById.put(id, copy(jobExecution)); - } -} -``` - -首先校验不能为空,版本不一致就抛出异常,这里基本都是使用乐观锁。官网有介绍。接着更新版本,保存数据。 -到此这一步结束了,主要是跟新版本,把版本从`STARTING`跟新为`BatchStatus.STARTED`。 -回到`AbstractJob#execute(JobExecution execution)`,接着执行。 - -##### listener.beforeJob(execution); - -这里就是监听器的前处理器,我们回头找一下会有哪一些监听器会注入。这个监听器主要有俩个方法`beforeJob(JobExecution jobExecution)` -和`afterJob(JobExecution jobExecution);`。 - -主要是`SimpleJobBuilder#build()`->`super.enhance(job);`->`job.setJobExecutionListeners(listeners.toArray(new JobExecutionListener[0]));` -这一段加入的。也就是`JobBuilderHelper#properties#jobExecutionListeners`这个属性里面的值。 -也就是我们如果有`jobBuilders.get("test").listener(new MyJobExecutionListener)`。就会创建一个监听器。 - -还有一种方法: - -`JobBuilderHelper#listener(Object listener)` - -```java -/** - * Registers objects using the annotation based listener configuration. - * - * @param listener the object that has a method configured with listener annotation - * @return this for fluent chaining - */ -public B listener(Object listener) { - Set jobExecutionListenerMethods = new HashSet<>(); - jobExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), BeforeJob.class)); - jobExecutionListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterJob.class)); - - if (jobExecutionListenerMethods.size() > 0) { - JobListenerFactoryBean factory = new JobListenerFactoryBean(); - factory.setDelegate(listener); - properties.addJobExecutionListener((JobExecutionListener) factory.getObject()); - } - - @SuppressWarnings("unchecked") - B result = (B) this; - return result; -} -``` - -参数是一个 object,里面只要有`BeforeJob.class`或者`AfterJob.class`注解也是`JobExecutionListener` -监听器。现在看这个名字就能理解为什么是`JobExecution`了。 - -接着回到`AbstractJob#execute(JobExecution execution)` - -##### doExecute(execution); - -```java -@Override -protected void doExecute(JobExecution execution) throws JobInterruptedException, JobRestartException, - StartLimitExceededException { - StepExecution stepExecution = null; - for (Step step : steps) { - stepExecution = handleStep(step, execution); - if (stepExecution.getStatus() != BatchStatus.COMPLETED) { - break; - } - } - - // - // Update the job status to be the same as the last step - // - if (stepExecution != null) { - if (logger.isDebugEnabled()) { - logger.debug("Upgrading JobExecution status: " + stepExecution); - } - execution.upgradeStatus(stepExecution.getStatus()); - execution.setExitStatus(stepExecution.getExitStatus()); - } -} -``` - -循环所有的`steps`。 - -> stepExecution = handleStep(step, execution); - -```java -protected final StepExecution handleStep(Step step, JobExecution execution) - throws JobInterruptedException, JobRestartException, - StartLimitExceededException { - return stepHandler.handleStep(step, execution); - -} -``` - -这里的`stepHandler`对象,是`AbstractJob#setJobRepository(JobRepository jobRepository)`的时候创建的。代码如下 - -```java -public void setJobRepository(JobRepository jobRepository) { - this.jobRepository = jobRepository; - stepHandler = new SimpleStepHandler(jobRepository); -} -``` - -###### SimpleStepHandler#handleStep(Step step, JobExecution execution) - -接着到了`SimpleStepHandler#handleStep(Step step, JobExecution execution)` - -```java -@Override -public StepExecution handleStep(Step step, JobExecution execution) throws JobInterruptedException, - JobRestartException, StartLimitExceededException { - if (execution.isStopping()) { - throw new JobInterruptedException("JobExecution interrupted."); - } - JobInstance jobInstance = execution.getJobInstance(); - StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); - if (stepExecutionPartOfExistingJobExecution(execution, lastStepExecution)) { - lastStepExecution = null; - } - StepExecution currentStepExecution = lastStepExecution; - if (shouldStart(lastStepExecution, execution, step)) { - currentStepExecution = execution.createStepExecution(step.getName()); - boolean isRestart = (lastStepExecution != null && !lastStepExecution.getStatus().equals( - BatchStatus.COMPLETED)); - if (isRestart) { - currentStepExecution.setExecutionContext(lastStepExecution.getExecutionContext()); - if (lastStepExecution.getExecutionContext().containsKey("batch.executed")) { - currentStepExecution.getExecutionContext().remove("batch.executed"); - } - } else { - currentStepExecution.setExecutionContext(new ExecutionContext(executionContext)); - } - jobRepository.add(currentStepExecution); - try { - step.execute(currentStepExecution); - currentStepExecution.getExecutionContext().put("batch.executed", true); - } catch (JobInterruptedException e) { - execution.setStatus(BatchStatus.STOPPING); - throw e; - } - jobRepository.updateExecutionContext(execution); - if (currentStepExecution.getStatus() == BatchStatus.STOPPING - || currentStepExecution.getStatus() == BatchStatus.STOPPED) { - // Ensure that the job gets the message that it is stopping - execution.setStatus(BatchStatus.STOPPING); - throw new JobInterruptedException("Job interrupted by step execution"); - } - } - return currentStepExecution; -} -``` - -首先获取`JobInstance`对象,是`MapJobInstanceDao`对象创建的。 - -> StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName()); - -`SimpleJobRepository#getLastStepExecution(JobInstance jobInstance, String stepName)` - -```java -@Override -@Nullable -public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { - StepExecution latest = stepExecutionDao.getLastStepExecution(jobInstance, stepName); - - if (latest != null) { - ExecutionContext stepExecutionContext = ecDao.getExecutionContext(latest); - latest.setExecutionContext(stepExecutionContext); - ExecutionContext jobExecutionContext = ecDao.getExecutionContext(latest.getJobExecution()); - latest.getJobExecution().setExecutionContext(jobExecutionContext); - } - - return latest; -} -``` - -进入`MapStepExecutionDao#getLastStepExecution(JobInstance jobInstance, String stepName)` - -```java -@Override -public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { - StepExecution latest = null; - for (StepExecution stepExecution : executionsByStepExecutionId.values()) { - if (!stepExecution.getStepName().equals(stepName) - || stepExecution.getJobExecution().getJobInstance().getInstanceId() != jobInstance.getInstanceId()) { - continue; - } - if (latest == null) { - latest = stepExecution; - } - if (latest.getStartTime().getTime() < stepExecution.getStartTime().getTime()) { - latest = stepExecution; - } - if (latest.getStartTime().getTime() == stepExecution.getStartTime().getTime() && - latest.getId() < stepExecution.getId()) { - latest = stepExecution; - } - } - return latest; -} -``` - -第一次进来这里是没有值的,直接返回 null。 -回到`SimpleStepHandler#handleStep(Step step, JobExecution execution)` - -`stepExecutionPartOfExistingJobExecution(execution, lastStepExecution)`为 false - -接着`shouldStart(lastStepExecution, execution, step)` - -> `SimpleStepHandler#shouldStart(StepExecution lastStepExecution, JobExecution jobExecution, Step step)` - -```java -// 给定一个步骤和配置,如果该步骤应该开始,则返回 true;如果不应该启动,则返回 false;如果作业应该完成,则抛出异常。 -protected boolean shouldStart(StepExecution lastStepExecution, JobExecution jobExecution, Step step) - throws JobRestartException, StartLimitExceededException { - - BatchStatus stepStatus; - if (lastStepExecution == null) { - stepStatus = BatchStatus.STARTING; - } else { - stepStatus = lastStepExecution.getStatus(); - } - - if (stepStatus == BatchStatus.UNKNOWN) { - throw new JobRestartException("Cannot restart step from UNKNOWN status. " - + "The last execution ended with a failure that could not be rolled back, " - + "so it may be dangerous to proceed. Manual intervention is probably necessary."); - } - - if ((stepStatus == BatchStatus.COMPLETED && !step.isAllowStartIfComplete()) - || stepStatus == BatchStatus.ABANDONED) { - // step is complete, false should be returned, indicating that the - // step should not be started - if (logger.isInfoEnabled()) { - logger.info("Step already complete or not restartable, so no action to execute: " + lastStepExecution); - } - return false; - } - - if (jobRepository.getStepExecutionCount(jobExecution.getJobInstance(), step.getName()) < step.getStartLimit()) { - // step start count is less than start max, return true - return true; - } else { - // start max has been exceeded, throw an exception. - throw new StartLimitExceededException("Maximum start limit exceeded for step: " + step.getName() - + "StartMax: " + step.getStartLimit()); - } -} -``` - -`lastStepExecution`为空,`stepStatus`设置为`BatchStatus.STARTING`。 - -`step.isAllowStartIfComplete()`是否能够重复执行 - -```java -@Bean -public Step step() { - TaskletStep taskletStep = stepBuilders.get("test-step") - .chunk(2) - .reader(itemReader()) - .writer(itemWriter()) - .startLimit(2) - .build(); - taskletStep.setAllowStartIfComplete(true); - return taskletStep; -} -``` - -改一下,设置成重复执行。`taskletStep.setAllowStartIfComplete(true);` -,如果不能重复执行打印日志`Step already complete or not restartable, so no action to execute`。 - -`jobRepository.getStepExecutionCount(jobExecution.getJobInstance(), step.getName()) < step.getStartLimit()`。 -`startLimit(2)`。最多允许执行几次,每执行一次,`StepExecutionDao#executionsByStepExecutionId` -中的数据就会多一条。根据`startLimit`判断是否还能重复执行。 -继续执行会报错`org.springframework.batch.core.StartLimitExceededException: Maximum start limit exceeded for step: test-stepStartMax: 2` - -> currentStepExecution = execution.createStepExecution(step.getName()); - -```java -public StepExecution createStepExecution(String stepName) { - StepExecution stepExecution = new StepExecution(stepName, this); - this.stepExecutions.add(stepExecution); - return stepExecution; -} -``` - -创建一个`StepExecution`并返回。`stepExecutions.add` - -> boolean isRestart = (lastStepExecution != null && !lastStepExecution.getStatus().equals(BatchStatus.COMPLETED)); - -`lastStepExecution`为空。进入 else。 - -> `currentStepExecution.setExecutionContext(new ExecutionContext(executionContext));` - -普通 set,`executionContext`也是一个普通的`new ExecutionContext()`。构造器方法中 new 的。 - -> jobRepository.add(currentStepExecution); - -`SimpleJobRepository#add(StepExecution stepExecution)` - -```java -@Override -public void add(StepExecution stepExecution) { - validateStepExecution(stepExecution); - - stepExecution.setLastUpdated(new Date(System.currentTimeMillis())); - stepExecutionDao.saveStepExecution(stepExecution); - ecDao.saveExecutionContext(stepExecution); -} -``` - -`validateStepExecution(stepExecution)`:校验 -`stepExecution.setLastUpdated(new Date(System.currentTimeMillis()));`:设置最后修改时间 - -> stepExecutionDao.saveStepExecution(stepExecution); - -```java -@Override -public void saveStepExecution(StepExecution stepExecution) { - Assert.isTrue(stepExecution.getId() == null, "stepExecution id was not null"); - Assert.isTrue(stepExecution.getVersion() == null, "stepExecution version was not null"); - Assert.notNull(stepExecution.getJobExecutionId(), "JobExecution must be saved already."); - Map executions = executionsByJobExecutionId.get(stepExecution.getJobExecutionId()); - if (executions == null) { - executions = new ConcurrentHashMap<>(); - executionsByJobExecutionId.put(stepExecution.getJobExecutionId(), executions); - } - stepExecution.setId(currentId.incrementAndGet()); - stepExecution.incrementVersion(); - StepExecution copy = copy(stepExecution); - executions.put(stepExecution.getId(), copy); - executionsByStepExecutionId.put(stepExecution.getId(), copy); - -} -``` - -先校验,再新增一条数据。 `StepExecution`里面包含了`JobExecution`。 `JobExecutionDao`维护`jobExecution`的 Id。`StepExecution` -维护`StepExecution`的 Id。 - -> ecDao.saveExecutionContext(stepExecution); - -```java -@Override -public void saveExecutionContext(StepExecution stepExecution) { - updateExecutionContext(stepExecution); -} - -@Override -public void updateExecutionContext(StepExecution stepExecution) { - ExecutionContext executionContext = stepExecution.getExecutionContext(); - if (executionContext != null) { - contexts.put(ContextKey.step(stepExecution.getId()), copy(executionContext)); - } -} -``` - -新增一条数据。前面已经新增了一条`job`。现在新增的是`step`。 - -> `step.execute(currentStepExecution);` - -继续回到`SimpleStepHandler#handleStep(Step step, JobExecution execution)`。 -`AbstractStep#execute(StepExecution stepExecution)` - -```java -/** - * Template method for step execution logic - calls abstract methods for resource initialization ( - * {@link #open(ExecutionContext)}), execution logic ({@link #doExecute(StepExecution)}) and resource closing ( - * {@link #close(ExecutionContext)}). - */ -@Override -public final void execute(StepExecution stepExecution) throws JobInterruptedException, - UnexpectedJobExecutionException { - - Assert.notNull(stepExecution, "stepExecution must not be null"); - - if (logger.isDebugEnabled()) { - logger.debug("Executing: id=" + stepExecution.getId()); - } - stepExecution.setStartTime(new Date()); - stepExecution.setStatus(BatchStatus.STARTED); - Timer.Sample sample = BatchMetrics.createTimerSample(); - getJobRepository().update(stepExecution); - - // Start with a default value that will be trumped by anything - ExitStatus exitStatus = ExitStatus.EXECUTING; - - doExecutionRegistration(stepExecution); - - try { - getCompositeListener().beforeStep(stepExecution); - open(stepExecution.getExecutionContext()); - - try { - doExecute(stepExecution); - } catch (RepeatException e) { - throw e.getCause(); - } - exitStatus = ExitStatus.COMPLETED.and(stepExecution.getExitStatus()); - - // Check if someone is trying to stop us - if (stepExecution.isTerminateOnly()) { - throw new JobInterruptedException("JobExecution interrupted."); - } - - // Need to upgrade here not set, in case the execution was stopped - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - if (logger.isDebugEnabled()) { - logger.debug("Step execution success: id=" + stepExecution.getId()); - } - } catch (Throwable e) { - stepExecution.upgradeStatus(determineBatchStatus(e)); - exitStatus = exitStatus.and(getDefaultExitStatusForFailure(e)); - stepExecution.addFailureException(e); - if (stepExecution.getStatus() == BatchStatus.STOPPED) { - logger.info(String.format("Encountered interruption executing step %s in job %s : %s", name, stepExecution.getJobExecution().getJobInstance().getJobName(), e.getMessage())); - if (logger.isDebugEnabled()) { - logger.debug("Full exception", e); - } - } else { - logger.error(String.format("Encountered an error executing step %s in job %s", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); - } - } finally { - - try { - // Update the step execution to the latest known value so the - // listeners can act on it - exitStatus = exitStatus.and(stepExecution.getExitStatus()); - stepExecution.setExitStatus(exitStatus); - exitStatus = exitStatus.and(getCompositeListener().afterStep(stepExecution)); - } catch (Exception e) { - logger.error(String.format("Exception in afterStep callback in step %s in job %s", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); - } - - try { - getJobRepository().updateExecutionContext(stepExecution); - } catch (Exception e) { - stepExecution.setStatus(BatchStatus.UNKNOWN); - exitStatus = exitStatus.and(ExitStatus.UNKNOWN); - stepExecution.addFailureException(e); - logger.error(String.format("Encountered an error saving batch meta data for step %s in job %s. " - + "This job is now in an unknown state and should not be restarted.", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); - } - - sample.stop(BatchMetrics.createTimer("step", "Step duration", - Tag.of("job.name", stepExecution.getJobExecution().getJobInstance().getJobName()), - Tag.of("name", stepExecution.getStepName()), - Tag.of("status", stepExecution.getExitStatus().getExitCode()) - )); - stepExecution.setEndTime(new Date()); - stepExecution.setExitStatus(exitStatus); - Duration stepExecutionDuration = BatchMetrics.calculateDuration(stepExecution.getStartTime(), stepExecution.getEndTime()); - if (logger.isInfoEnabled()) { - logger.info("Step: [" + stepExecution.getStepName() + "] executed in " + BatchMetrics.formatDuration(stepExecutionDuration)); - } - try { - getJobRepository().update(stepExecution); - } catch (Exception e) { - stepExecution.setStatus(BatchStatus.UNKNOWN); - stepExecution.setExitStatus(exitStatus.and(ExitStatus.UNKNOWN)); - stepExecution.addFailureException(e); - logger.error(String.format("Encountered an error saving batch meta data for step %s in job %s. " - + "This job is now in an unknown state and should not be restarted.", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); - } - - try { - close(stepExecution.getExecutionContext()); - } catch (Exception e) { - logger.error(String.format("Exception while closing step execution resources in step %s in job %s", name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); - stepExecution.addFailureException(e); - } - - doExecutionRelease(); - - if (logger.isDebugEnabled()) { - logger.debug("Step execution complete: " + stepExecution.getSummary()); - } - } -} -``` - -前面几行校验,打印日志,计时器。 - -> getJobRepository().update(stepExecution); - -```java -@Override -public void update(StepExecution stepExecution) { - validateStepExecution(stepExecution); - Assert.notNull(stepExecution.getId(), "StepExecution must already be saved (have an id assigned)"); - - stepExecution.setLastUpdated(new Date(System.currentTimeMillis())); - stepExecutionDao.updateStepExecution(stepExecution); - checkForInterruption(stepExecution); -} -``` - -校验参数,设置最后修改时间。 - -> `stepExecutionDao.updateStepExecution(stepExecution);` - -跟前面一样,版本号升级,新增一条数据。这里有俩个 Map,`executionsByStepExecutionId`和`executionsByJobExecutionId` - -`executionsByJobExecutionId`: -`private Map> executionsByJobExecutionId = new ConcurrentHashMap<>();` -key 为`Job`的 id。然后`job`有多个`Step`。泛型里面的 map 就是`Step`的 id 和对象。 - -`executionsByStepExecutionId`: -`private Map executionsByStepExecutionId = new ConcurrentHashMap<>();` -key 为 Step 的 id - -> checkForInterruption(stepExecution); - -回到`SimpleJobRepository#update(StepExecution stepExecution)`。 - -```java -/** - * Check to determine whether or not the JobExecution that is the parent of - * the provided StepExecution has been interrupted. If, after synchronizing - * the status with the database, the status has been updated to STOPPING, - * then the job has been interrupted. - * - * @param stepExecution - */ -private void checkForInterruption(StepExecution stepExecution) { - JobExecution jobExecution = stepExecution.getJobExecution(); - jobExecutionDao.synchronizeStatus(jobExecution); - if (jobExecution.isStopping()) { - logger.info("Parent JobExecution is stopped, so passing message on to StepExecution"); - stepExecution.setTerminateOnly(); - } -} -``` - -检查以确定作为所提供的 StepExecution 的父级 JobExecution 是否已被中断。如果与数据库同步状态后,状态已更新为 -STOPPING,则作业已中断。 -`jobExecutionDao.synchronizeStatus(jobExecution);`:版本号是否改变了。 - -> ExitStatus exitStatus = ExitStatus.EXECUTING; - -回到`AbstractStep#execute(StepExecution stepExecution)`方法,创建`exitStatus` - -> doExecutionRegistration(stepExecution); - -前面是`JobSynchronizationManager.register(execution);`。注入的是 job,现在这个是 step。逻辑一样的。 - -> getCompositeListener().beforeStep(stepExecution); - -跟 job 也基本一致,在` taskletStep = stepBuilders.get("test-step").listener(new StepExecutionListener() {` -中或者`listener(Object listener)`里面存在`BeforeStep.class`和`AfterStep.class`注解的方法。 可以得出`xxExecutionListener` -是比较前面执行的。 - -> open(stepExecution.getExecutionContext()); - -```java -@Override -protected void open(ExecutionContext ctx) throws Exception { - stream.open(ctx); -} -``` - -里面的`stream`存在一个`private final List streams = new ArrayList<>();`属性,也就是`ItemStream`对象。里面的 -open 方法会在这里执行。 - -> doExecute(stepExecution); - -```java -@Override -protected void doExecute(StepExecution stepExecution) throws Exception { - stepExecution.getExecutionContext().put(TASKLET_TYPE_KEY, tasklet.getClass().getName()); - stepExecution.getExecutionContext().put(STEP_TYPE_KEY, this.getClass().getName()); - stream.update(stepExecution.getExecutionContext()); - getJobRepository().updateExecutionContext(stepExecution); - final Semaphore semaphore = createSemaphore(); - stepOperations.iterate(new StepContextRepeatCallback(stepExecution) { - @Override - public RepeatStatus doInChunkContext(RepeatContext repeatContext, ChunkContext chunkContext) - throws Exception { - StepExecution stepExecution = chunkContext.getStepContext().getStepExecution(); - interruptionPolicy.checkInterrupted(stepExecution); - - RepeatStatus result; - try { - result = new TransactionTemplate(transactionManager, transactionAttribute) - .execute(new ChunkTransactionCallback(chunkContext, semaphore)); - } catch (UncheckedTransactionException e) { - throw (Exception) e.getCause(); - } - chunkListener.afterChunk(chunkContext); - interruptionPolicy.checkInterrupted(stepExecution); - return result == null ? RepeatStatus.FINISHED : result; - } - }); -} -``` - -首先在`stepExecution#ExecutionContext`set 了`batch.taskletType`和`batch.stepType` -。接着执行了`stream.update(stepExecution.getExecutionContext());`。与上面一样,`ItemStream#update`方法都在这里执行的。 - -又是更新`getJobRepository().updateExecutionContext(stepExecution);` -。与上面一样。里面主要就是环境更新`ecDao.updateExecutionContext(stepExecution);`。 - -> final Semaphore semaphore = createSemaphore(); - -每个步骤执行共享信号量,因此其他步骤执行可以并行运行而不需要锁,这里只是 new 一个对象,不是使用。 - -> stepOperations.iterate(new StepContextRepeatCallback(stepExecution) - -`stepOperations`是一个`RepeatTemplate`对象,实现了`RepeatOperations` -。里面只有一个方法。`RepeatStatus#iterate(RepeatCallback callback)`。 - -`RepeatTemplate#iterate(RepeatCallback callback)` - -```java -/** - * Execute the batch callback until the completion policy decides that we - * are finished. Wait for the whole batch to finish before returning even if - * the task executor is asynchronous. - * - * @see org.springframework.batch.repeat.RepeatOperations#iterate(org.springframework.batch.repeat.RepeatCallback) - */ -@Override -public RepeatStatus iterate(RepeatCallback callback) { - - RepeatContext outer = RepeatSynchronizationManager.getContext(); - - RepeatStatus result = RepeatStatus.CONTINUABLE; - try { - // This works with an asynchronous TaskExecutor: the - // interceptors have to wait for the child processes. - result = executeInternal(callback); - } finally { - RepeatSynchronizationManager.clear(); - if (outer != null) { - RepeatSynchronizationManager.register(outer); - } - } - - return result; -} -``` - -第一次进来`outer`为空,进入`result = executeInternal(callback);`方法。 - -```java -private RepeatStatus executeInternal(final RepeatCallback callback) { - - // Reset the termination policy if there is one... - RepeatContext context = start(); - - // Make sure if we are already marked complete before we start then no - // processing takes place. - boolean running = !isMarkedComplete(context); - - for (RepeatListener interceptor : listeners) { - interceptor.open(context); - running = running && !isMarkedComplete(context); - if (!running) - break; - } - // Return value, default is to allow continued processing. - RepeatStatus result = RepeatStatus.CONTINUABLE; - - RepeatInternalState state = createInternalState(context); - // This is the list of exceptions thrown by all active callbacks - Collection throwables = state.getThrowables(); - // Keep a separate list of exceptions we handled that need to be - // rethrown - Collection deferred = new ArrayList<>(); - - try { - - while (running) { - - /* - * Run the before interceptors here, not in the task executor so - * that they all happen in the same thread - it's easier for - * tracking batch status, amongst other things. - */ - for (int i = 0; i < listeners.length; i++) { - RepeatListener interceptor = listeners[i]; - interceptor.before(context); - // Allow before interceptors to veto the batch by setting - // flag. - running = running && !isMarkedComplete(context); - } - - // Check that we are still running (should always be true) ... - if (running) { - - try { - - result = getNextResult(context, callback, state); - executeAfterInterceptors(context, result); - - } catch (Throwable throwable) { - doHandle(throwable, context, deferred); - } - - // N.B. the order may be important here: - if (isComplete(context, result) || isMarkedComplete(context) || !deferred.isEmpty()) { - running = false; - } - - } - - } - - result = result.and(waitForResults(state)); - for (Throwable throwable : throwables) { - doHandle(throwable, context, deferred); - } - // Explicitly drop any references to internal state... - state = null; - } - /* - * No need for explicit catch here - if the business processing threw an - * exception it was already handled by the helper methods. An exception - * here is necessarily fatal. - */ finally { - try { - if (!deferred.isEmpty()) { - Throwable throwable = deferred.iterator().next(); - if (logger.isDebugEnabled()) { - logger.debug("Handling fatal exception explicitly (rethrowing first of " + deferred.size() + "): " - + throwable.getClass().getName() + ": " + throwable.getMessage()); - } - rethrow(throwable); - } - } finally { - try { - for (int i = listeners.length; i-- > 0; ) { - RepeatListener interceptor = listeners[i]; - interceptor.close(context); - } - } finally { - context.close(); - } - } - } - return result; -} -``` - -> RepeatContext context = start(); - -```java -/** - * Delegate to the {@link CompletionPolicy}. - * - * @return a {@link RepeatContext} object that can be used by the implementation to store - * internal state for a batch step. - * @see org.springframework.batch.repeat.CompletionPolicy#start(RepeatContext) - */ -protected RepeatContext start() { - RepeatContext parent = RepeatSynchronizationManager.getContext(); - RepeatContext context = completionPolicy.start(parent); - RepeatSynchronizationManager.register(context); - logger.debug("Starting repeat context."); - return context; -} -``` - -往`RepeatSynchronizationManager#ThreadLocal contextHolder = new ThreadLocal<>()`中新增一个`RepeatContext` -。这里新增的是一个`RepeatContextSupport`对象。 - -> boolean running = !isMarkedComplete(context); - -确保我们在开始之前是否已标记为完成,则不会进行任何处理。 - -```java -private boolean isMarkedComplete(RepeatContext context) { - boolean complete = context.isCompleteOnly(); - if (context.getParent() != null) { - complete = complete || isMarkedComplete(context.getParent()); - } - if (complete) { - logger.debug("Repeat is complete according to context alone."); - } - return complete; - -} -``` - -`complete`默认为 false,取反`running`为 true。 - -> listeners - -这个对象是在 builder 中赋值的,我们没有进行别的处理。 - -> RepeatStatus result = RepeatStatus.CONTINUABLE; - -返回值,默认是允许继续处理。 - -> RepeatInternalState state = createInternalState(context); - -```java -protected RepeatInternalState createInternalState(RepeatContext context) { - return new RepeatInternalStateSupport(); -} -``` - -> Collection throwables = state.getThrowables(); - -这是所有活动回调抛出的异常列表 - -> Collection deferred = new ArrayList<>(); - -保留我们处理过的需要重新抛出的异常的单独列表 - -接下来进入 while 循环,`running`为 true,第一个 for 循环`listeners`值为空,跳过。 - -判断 if 进入。 - -> result = getNextResult(context, callback, state); - -```java -protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state) - throws Throwable { - update(context); - if (logger.isDebugEnabled()) { - logger.debug("Repeat operation about to start at count=" + context.getStartedCount()); - } - return callback.doInIteration(context); - -} -``` - -解释一下三个参数,`context`,前面方法`start()`中 new 出来的`RepeatContextSupport`,当前里面什么都没有。 -`callback`是 new 的一个`new StepContextRepeatCallback(stepExecution)`对象。 -`state`是` new RepeatInternalStateSupport()`对象。在前面调用`createInternalState(context)`创建出来的。 - -> update(context); - -更新`context`,这里更新就是把`RepeatContextSupport#count`++,次数加一。 - -> return callback.doInIteration(context); - -`StepContextRepeatCallback#doInIteration(RepeatContext context)` - -```java -@Override -public RepeatStatus doInIteration(RepeatContext context) throws Exception { - - // The StepContext has to be the same for all chunks, - // otherwise step-scoped beans will be re-initialised for each chunk. - StepContext stepContext = StepSynchronizationManager.register(stepExecution); - if (logger.isDebugEnabled()) { - logger.debug("Preparing chunk execution for StepContext: " + ObjectUtils.identityToString(stepContext)); - } - - ChunkContext chunkContext = attributeQueue.poll(); - if (chunkContext == null) { - chunkContext = new ChunkContext(stepContext); - } - - try { - if (logger.isDebugEnabled()) { - logger.debug("Chunk execution starting: queue size=" + attributeQueue.size()); - } - return doInChunkContext(context, chunkContext); - } finally { - // Still some stuff to do with the data in this chunk, - // pass it back. - if (!chunkContext.isComplete()) { - attributeQueue.add(chunkContext); - } - StepSynchronizationManager.close(); - } -} -``` - -> StepContext stepContext = StepSynchronizationManager.register(stepExecution); - -简单来说就是`SynchronizationManagerSupport#counts`++。 - -接着 `chunkContext = new ChunkContext(stepContext);` - -> doInChunkContext(context, chunkContext); - -```java -@Override -public RepeatStatus doInChunkContext(RepeatContext repeatContext, ChunkContext chunkContext) - throws Exception { - - StepExecution stepExecution = chunkContext.getStepContext().getStepExecution(); - - // Before starting a new transaction, check for - // interruption. - interruptionPolicy.checkInterrupted(stepExecution); - - RepeatStatus result; - try { - result = new TransactionTemplate(transactionManager, transactionAttribute) - .execute(new ChunkTransactionCallback(chunkContext, semaphore)); - } catch (UncheckedTransactionException e) { - // Allow checked exceptions to be thrown inside callback - throw (Exception) e.getCause(); - } - - chunkListener.afterChunk(chunkContext); - - // Check for interruption after transaction as well, so that - // the interrupted exception is correctly propagated up to - // caller - interruptionPolicy.checkInterrupted(stepExecution); - - return result == null ? RepeatStatus.FINISHED : result; -} -``` - -执行` doInChunkContext(RepeatContext repeatContext, ChunkContext chunkContext)` -。这个是在`TaskletStep#doExecute(StepExecution stepExecution)` -中,`stepOperations.iterate(new StepContextRepeatCallback(stepExecution) {` -里面的`doInChunkContext(RepeatContext repeatContext, ChunkContext chunkContext)`方法。 - -> `StepExecution stepExecution = chunkContext.getStepContext().getStepExecution();` - -获取`stepExecution`,`StepContext`里面存有`StepExecution`。 - -> interruptionPolicy.checkInterrupted(stepExecution); - -在开始新事务之前,检查是否有中断。 - -> result = new TransactionTemplate(transactionManager, transactionAttribute) - -创建一个`TransactionTemplate`。`transactionManager`是默认的`ResourcelessTransactionManager`。 - -`transactionAttribute`是`new DefaultTransactionAttribute(`。 - -> .execute(new ChunkTransactionCallback(chunkContext, semaphore)); - -执行 execute 方法。使用`ChunkTransactionCallback`包装`chunkContext, semaphore`俩个属性。 - -```java -@Override -@Nullable -public T execute(TransactionCallback action) throws TransactionException { - Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); - - if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { - return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); - } else { - TransactionStatus status = this.transactionManager.getTransaction(this); - T result; - try { - result = action.doInTransaction(status); - } catch (RuntimeException | Error ex) { - // Transactional code threw application exception -> rollback - rollbackOnException(status, ex); - throw ex; - } catch (Throwable ex) { - // Transactional code threw unexpected exception -> rollback - rollbackOnException(status, ex); - throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); - } - this.transactionManager.commit(status); - return result; - } -} -``` - -进入 else,里面大概逻辑就是成功就提交,失败就回滚。主要方法在`result = action.doInTransaction(status);`中。 - -> result = action.doInTransaction(status); - -`TransactionCallback action`对象为`ChunkTransactionCallback` - -```java -@Override -public RepeatStatus doInTransaction(TransactionStatus status) { - TransactionSynchronizationManager.registerSynchronization(this); - - RepeatStatus result = RepeatStatus.CONTINUABLE; - - StepContribution contribution = stepExecution.createStepContribution(); - - chunkListener.beforeChunk(chunkContext); - - // In case we need to push it back to its old value - // after a commit fails... - oldVersion = new StepExecution(stepExecution.getStepName(), stepExecution.getJobExecution()); - copy(stepExecution, oldVersion); - try { - try { - try { - result = tasklet.execute(contribution, chunkContext); - if (result == null) { - result = RepeatStatus.FINISHED; - } - } catch (Exception e) { - if (transactionAttribute.rollbackOn(e)) { - chunkContext.setAttribute(ChunkListener.ROLLBACK_EXCEPTION_KEY, e); - throw e; - } - } - } finally { - - // If the step operations are asynchronous then we need - // to synchronize changes to the step execution (at a - // minimum). Take the lock *before* changing the step - // execution. - try { - semaphore.acquire(); - locked = true; - } catch (InterruptedException e) { - logger.error("Thread interrupted while locking for repository update"); - stepExecution.setStatus(BatchStatus.STOPPED); - stepExecution.setTerminateOnly(); - Thread.currentThread().interrupt(); - } - - // Apply the contribution to the step - // even if unsuccessful - if (logger.isDebugEnabled()) { - logger.debug("Applying contribution: " + contribution); - } - stepExecution.apply(contribution); - - } - - stepExecutionUpdated = true; - - stream.update(stepExecution.getExecutionContext()); - - try { - // Going to attempt a commit. If it fails this flag will - // stay false and we can use that later. - getJobRepository().updateExecutionContext(stepExecution); - stepExecution.incrementCommitCount(); - if (logger.isDebugEnabled()) { - logger.debug("Saving step execution before commit: " + stepExecution); - } - getJobRepository().update(stepExecution); - } catch (Exception e) { - // If we get to here there was a problem saving the step - // execution and we have to fail. - String msg = "JobRepository failure forcing rollback"; - logger.error(msg, e); - throw new FatalStepExecutionException(msg, e); - } - } catch (Error e) { - if (logger.isDebugEnabled()) { - logger.debug("Rollback for Error: " + e.getClass().getName() + ": " + e.getMessage()); - } - rollback(stepExecution); - throw e; - } catch (RuntimeException e) { - if (logger.isDebugEnabled()) { - logger.debug("Rollback for RuntimeException: " + e.getClass().getName() + ": " + e.getMessage()); - } - rollback(stepExecution); - throw e; - } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug("Rollback for Exception: " + e.getClass().getName() + ": " + e.getMessage()); - } - rollback(stepExecution); - // Allow checked exceptions - throw new UncheckedTransactionException(e); - } - - return result; - -} -``` - -> TransactionSynchronizationManager.registerSynchronization(this); - -为当前线程注册一个新的事务同步。通常由资源管理代码调用。 - -> RepeatStatus result = RepeatStatus.CONTINUABLE; - -默认返回。 - -> StepContribution contribution = stepExecution.createStepContribution(); - -创建一个`StepContribution`。`return new StepContribution(this);` - -> chunkListener.beforeChunk(chunkContext); - -里面的`chunkListener`前处理器。 也是在 builder 中 set 的。与监听器逻辑一样。打开`ChunkListener`类,把注释翻译一下,对照这里就知道了。 - -> oldVersion = new StepExecution(stepExecution.getStepName(), stepExecution.getJobExecution()); copy(stepExecution, -> oldVersion); - -如果提交失败后我们需要将其推回到旧值 ......,赋值一份用来进行回滚。 - -> result = tasklet.execute(contribution, chunkContext); - -`tasklet`是`SimpleStepBuilder#createTasklet()`里面创建的。也就是`ChunkOrientedTasklet`。 - -```java -public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { - @SuppressWarnings("unchecked") - Chunk inputs = (Chunk) chunkContext.getAttribute(INPUTS_KEY); - if (inputs == null) { - inputs = chunkProvider.provide(contribution); - if (buffering) { - chunkContext.setAttribute(INPUTS_KEY, inputs); - } - } - chunkProcessor.process(contribution, inputs); - chunkProvider.postProcess(contribution, inputs); - // Allow a message coming back from the processor to say that we - // are not done yet - if (inputs.isBusy()) { - logger.debug("Inputs still busy"); - return RepeatStatus.CONTINUABLE; - } - chunkContext.removeAttribute(INPUTS_KEY); - chunkContext.setComplete(); - if (logger.isDebugEnabled()) { - logger.debug("Inputs not busy, ended: " + inputs.isEnd()); - } - return RepeatStatus.continueIf(!inputs.isEnd()); -} -``` - -这里又是使用`repeatOperations#.iterate(new RepeatCallback()`。这里有个点需要注意一下,在 builder -中。存在俩个`RepeatOperations`对象。一个是`SimpleStepBuilder#RepeatOperations chunkOperations`。名字中有个`chunk` -的。还有一个在父类`AbstractTaskletStepBuilder#RepeatOperations stepOperations`中。 -`chunkOperations`在这里使用的,父类的在`TaskletStep#doExecute(StepExecution stepExecution)`中使用的。 - -```java -@Override -public Chunk provide(final StepContribution contribution) throws Exception { - final Chunk inputs = new Chunk<>(); - repeatOperations.iterate(new RepeatCallback() { - @Override - public RepeatStatus doInIteration(final RepeatContext context) throws Exception { - I item = null; - Timer.Sample sample = Timer.start(Metrics.globalRegistry); - String status = BatchMetrics.STATUS_SUCCESS; - try { - item = read(contribution, inputs); - } catch (SkipOverflowException e) { - // read() tells us about an excess of skips by throwing an - // exception - status = BatchMetrics.STATUS_FAILURE; - return RepeatStatus.FINISHED; - } finally { - stopTimer(sample, contribution.getStepExecution(), status); - } - if (item == null) { - inputs.setEnd(); - return RepeatStatus.FINISHED; - } - inputs.add(item); - contribution.incrementReadCount(); - return RepeatStatus.CONTINUABLE; - } - - }); - - return inputs; - -} -``` - -这里的`repeatOperations`也是`RepeatTemplate`。逻辑与前面的都一致,只不过`RepeatCallback` -不同,直接进入`RepeatCallback#doInIteration(final RepeatContext context)`方法。 - -> chunkProvider.provide(contribution); - -这个就是读取数据接口了。先看看参数。`StepContribution`里面只有一个`stepExecution`。`chunkProvider` -是`SimpleChunkProvider chunkProvider = new SimpleChunkProvider<>(getReader(), repeatOperations);` -这样的,`repeatOperations`是一个`RepeatTemplate`。 - -进入方法。 - -```java -@Override -public Chunk provide(final StepContribution contribution) throws Exception { - - final Chunk inputs = new Chunk<>(); - repeatOperations.iterate(new RepeatCallback() { - - @Override - public RepeatStatus doInIteration(final RepeatContext context) throws Exception { - I item = null; - Timer.Sample sample = Timer.start(Metrics.globalRegistry); - String status = BatchMetrics.STATUS_SUCCESS; - try { - item = read(contribution, inputs); - } catch (SkipOverflowException e) { - // read() tells us about an excess of skips by throwing an - // exception - status = BatchMetrics.STATUS_FAILURE; - return RepeatStatus.FINISHED; - } finally { - stopTimer(sample, contribution.getStepExecution(), status); - } - if (item == null) { - inputs.setEnd(); - return RepeatStatus.FINISHED; - } - inputs.add(item); - contribution.incrementReadCount(); - return RepeatStatus.CONTINUABLE; - } - - }); - - return inputs; - -} -``` - -只关注一些关键代码,整体逻辑就是执行`item = read(contribution, inputs);`,出错返回` RepeatStatus.FINISHED` -,正常就把值放入`final Chunk inputs = new Chunk<>();`中,并且执行`contribution.incrementReadCount();`-> -把里面的`readCount++`。 - -关键点:`item == null`,就表示后面没有数据了,设置 end 为结束。这就是为什么我们返回 null,就结束,如果不返回 null,就会一直执行下去。 - -> item = read(contribution, inputs); - -进入 `doRead()` - -```java -/** - * Surrounds the read call with listener callbacks. - * - * @return the item or {@code null} if the data source is exhausted - * @throws Exception is thrown if error occurs during read. - */ -@Nullable -protected final I doRead() throws Exception { - try { - listener.beforeRead(); - I item = itemReader.read(); - if (item != null) { - listener.afterRead(item); - } - return item; - } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.debug(e.getMessage() + " : " + e.getClass().getName()); - } - listener.onReadError(e); - throw e; - } -} -``` - -listener.beforeRead(); `ItemReadListener#beforeRead()`方法。 - -> I item = itemReader.read(); - -这个就是我们自己写的类了。执行里面的方法,返回一个值。 - -> listener.afterRead(item); - -返回值不为空,执行`ItemReadListener#afterRead`方法。 - -接着`ChunkOrientedTasklet#execute(StepContribution contribution, ChunkContext chunkContext)`。 - -> chunkProcessor.process(contribution, inputs); - -`inputs` 就是我们自己写的返回的值。`contribution` 中的 `readCount++`。 - -`chunkProcessor` -是`SimpleChunkProcessor chunkProcessor = new SimpleChunkProcessor<>(getProcessor(), getWriter());` - -```java -@Override -public final void process(StepContribution contribution, Chunk inputs) throws Exception { - - // Allow temporary state to be stored in the user data field - initializeUserData(inputs); - - // If there is no input we don't have to do anything more - if (isComplete(inputs)) { - return; - } - - // Make the transformation, calling remove() on the inputs iterator if - // any items are filtered. Might throw exception and cause rollback. - Chunk outputs = transform(contribution, inputs); - - // Adjust the filter count based on available data - contribution.incrementFilterCount(getFilterCount(inputs, outputs)); - - // Adjust the outputs if necessary for housekeeping purposes, and then - // write them out... - write(contribution, inputs, getAdjustedOutputs(inputs, outputs)); - -} -``` - -> initializeUserData(inputs); - -允许临时状态存储在用户数据字段中, - -```java -protected void initializeUserData(Chunk inputs){ - inputs.setUserData(inputs.size()); -} -``` - -> isComplete(inputs) - -如果没有输入,我们不需要做任何事情 - -> Chunk outputs = transform(contribution, inputs); - -进行转换,如果过滤了任何项目,则在输入迭代器上调用 remove()。可能会抛出异常并导致回滚。 - -```java -protected Chunk transform(StepContribution contribution, Chunk inputs) throws Exception { - Chunk outputs = new Chunk<>(); - for (Chunk.ChunkIterator iterator = inputs.iterator(); iterator.hasNext(); ) { - final I item = iterator.next(); - O output; - Timer.Sample sample = BatchMetrics.createTimerSample(); - String status = BatchMetrics.STATUS_SUCCESS; - try { - output = doProcess(item); - } catch (Exception e) { - /* - * For a simple chunk processor (no fault tolerance) we are done - * here, so prevent any more processing of these inputs. - */ - inputs.clear(); - status = BatchMetrics.STATUS_FAILURE; - throw e; - } finally { - stopTimer(sample, contribution.getStepExecution(), "item.process", status, "Item processing"); - } - if (output != null) { - outputs.add(output); - } else { - iterator.remove(); - } - } - return outputs; -} -``` - -只看一个方法,output = doProcess(item); - -> doProcess(item) - -```java -protected final O doProcess(I item) throws Exception { - - if (itemProcessor == null) { - @SuppressWarnings("unchecked") - O result = (O) item; - return result; - } - - try { - listener.beforeProcess(item); - O result = itemProcessor.process(item); - listener.afterProcess(item, result); - return result; - } catch (Exception e) { - listener.onProcessError(item, e); - throw e; - } - -} -``` - -`itemProcessor`为空直接返回,如果存在。 -先执行`listener.beforeProcess(item);`(`ItemProcessListener#beforeProcess`)。 -然后存在`ItemProcessor`类,就执行。这个里面是一个数据转换,我读取数据,不能够直接写入就需要在这里进行一次转换,再进行写入。 -最后再执行一次`ItemProcessListener#afterProcess`。并返回结果。 如果没有`ItemProcessor`,里面的`ItemProcessListener` -就不会执行。这里需要注意。 -接着回到上面。 - -> contribution.incrementFilterCount(getFilterCount(inputs, outputs)); - -维护里面的`filterCount`。看看有没有过滤的数据。 - -> write(contribution, inputs, getAdjustedOutputs(inputs, outputs)); - -如果需要的话,调整输出以进行内务处理,然后将它们写出来 ...... - -```java -/** - * Simple implementation delegates to the {@link #doWrite(List)} method and - * increments the write count in the contribution. Subclasses can handle - * more complicated scenarios, e.g.with fault tolerance. If output items are - * skipped they should be removed from the inputs as well. - * - * @param contribution the current step contribution - * @param inputs the inputs that gave rise to the outputs - * @param outputs the outputs to write - * @throws Exception if there is a problem - */ -protected void write(StepContribution contribution, Chunk inputs, Chunk outputs) throws Exception { - Timer.Sample sample = BatchMetrics.createTimerSample(); - String status = BatchMetrics.STATUS_SUCCESS; - try { - doWrite(outputs.getItems()); - } catch (Exception e) { - /* - * For a simple chunk processor (no fault tolerance) we are done - * here, so prevent any more processing of these inputs. - */ - inputs.clear(); - status = BatchMetrics.STATUS_FAILURE; - throw e; - } finally { - stopTimer(sample, contribution.getStepExecution(), "chunk.write", status, "Chunk writing"); - } - contribution.incrementWriteCount(outputs.size()); -} -``` - -关键俩句,`doWrite(outputs.getItems());`和 `contribution.incrementWriteCount(outputs.size());`。后面就是添加 `WriteCount`。 - -> doWrite(outputs.getItems()) - -```java -protected final void doWrite(List items) throws Exception { - - if (itemWriter == null) { - return; - } - - try { - listener.beforeWrite(items); - writeItems(items); - doAfterWrite(items); - } catch (Exception e) { - doOnWriteError(e, items); - throw e; - } - -} -``` - -`listener.beforeWrite(items)`:首先`ItemWriteListener#beforeWrite`方法。 -`writeItems(items);`:自己写的`itemWriter`。执行里面的方法。 -`doAfterWrite(items)`:执行`ItemWriteListener#afterWrite`方法。 - -这里我们要注意,这只是一次,也就是我们写的 `stepBuilders.get("test-step").chunk(2)`, 读取俩次值放入 Chunk -中,再`RepeatTemplate#iterate(RepeatCallback callback)` -调用 `RepeatTemplate#executeInternal(final RepeatCallback callback)`里面。有个 while -循环,出去条件就是`isComplete(context, result) || isMarkedComplete(context) || !deferred.isEmpty()` -。这一段我们方法放在外面的`RepeatTemplate`中进行讲解。 - -> chunkProvider.postProcess(contribution, inputs); - -当前实现类没有处理。 - -> inputs.isBusy() - -值为 false。 - -下面就是删除当前`INPUTS_KEY`的值,设置`Complete`为 true。 - -> RepeatStatus.continueIf(!inputs.isEnd()); - -这是很重要的一点,是否已经结束。上面讲到过。是否已经结束了。 - -又回到`TaskletStep#doInTransaction(TransactionStatus status)` -,刚刚所有的方法都在`tasklet.execute(contribution, chunkContext)`中。 - -然后接着执行 finally 中的代码。 -semaphore.acquire(); 信号量,锁上。 -locked = true; 锁 =true。 - -如果步骤操作是异步的,那么我们需要将更改同步到步骤执行(至少)。在更改步骤执行之前锁定。 - -继续执行`stepExecution.apply(contribution);` - -```java -public synchronized void apply(StepContribution contribution) { - readSkipCount += contribution.getReadSkipCount(); - writeSkipCount += contribution.getWriteSkipCount(); - processSkipCount += contribution.getProcessSkipCount(); - filterCount += contribution.getFilterCount(); - readCount += contribution.getReadCount(); - writeCount += contribution.getWriteCount(); - exitStatus = exitStatus.and(contribution.getExitStatus()); -} -``` - -相加。 - -> stepExecutionUpdated = true; - -等到使用再讲解。 - -> stream.update(stepExecution.getExecutionContext()); - -现在到了`ItemStream#update(ExecutionContext executionContext)`了。 - -> getJobRepository().updateExecutionContext(stepExecution); - -`MapExecutionContextDao` 更新。 - -> getJobRepository().update(stepExecution); - -后面详细讲解。就是更新 map。数据库就更新数据库,同步 job 状态。 - -接着返回状态,回到了 TransactionTemplate#execute(TransactionCallback action)。提交事务,返回状态。 - -又回到`TaskletStep#doExecute(StepExecution stepExecution)` -中`stepOperations.iterate(new StepContextRepeatCallback(stepExecution) {`里面。 -这里面其实是在`RepeatTemplate#iterate(RepeatCallback callback)`调用的`executeInternal(callback)`这里。这里调用的具体逻辑下面仔细讲解。 - -先看`TaskletStep#doExecute(StepExecution stepExecution)`中`chunkListener.afterChunk(chunkContext);` -。执行`ChunkListener#afterChunk`。后面 interruptionPolicy.checkInterrupted(stepExecution); 判断线程是否被中短。 - -继续往下执行,回到 `RepeatTemplate#executeInternal(final RepeatCallback callback)`。 - -> executeAfterInterceptors(context, result); - -执行`RepeatListener#after`。 - -> isComplete(context, result) || isMarkedComplete(context) || !deferred.isEmpty() - -顺序在这里可能很重要。 - -```java -protected boolean isComplete(RepeatContext context, RepeatStatus result) { - boolean complete = completionPolicy.isComplete(context, result); - if (complete) { - logger.debug("Repeat is complete according to policy and result value."); - } - return complete; -} -``` - -状态是否为`RepeatStatus#CONTINUABLE`。 - -```java -private boolean isMarkedComplete(RepeatContext context) { - boolean complete = context.isCompleteOnly(); - if (context.getParent() != null) { - complete = complete || isMarkedComplete(context.getParent()); - } - if (complete) { - logger.debug("Repeat is complete according to context alone."); - } - return complete; - -} -``` - -检查上一层。 - -> deferred.isEmpty() - -在 `doHandle(throwable, context, deferred);`中的,如果有异常,这个里面就有值。 - -现在没有进入,`running`还是 true,所以继续执行。这个`RepeatTemplate`是外层的,里面真正执行`ItemReader`和`ItemWriter` -的是一个叫`RepeatOperations chunkOperations`的`RepeatTemplate` 执行的。 - -执行完了之后,就到了`result = result.and(waitForResults(state));`。为 ture,这个值在这个地方没有用的。 - -剩下就是`RepeatListener#close`。`context.close();` - -回到 `AbstractStep#execute(StepExecution stepExecution)`。剩下的都是一些资源清除,catch 块里面的内容了,感兴趣的可以自己看看。 -关键的就是 -`getJobRepository().updateExecutionContext(stepExecution);` -`getJobRepository().update(stepExecution);` -`close(stepExecution.getExecutionContext());` -`doExecutionRelease();` - -回到`SimpleStepHandler#handleStep(Step step, JobExecution execution)` -。刚刚我们所有方法都是在`step.execute(currentStepExecution);`中执行的。继续也是一些异常处理 , 关键就下面几句。 -`currentStepExecution.getExecutionContext().put("batch.executed", true);` -`jobRepository.updateExecutionContext(execution);` - -回到`SimpleJob#doExecute(JobExecution execution)`里面,我们把所有的`steps`执行完了。然后状态更改。 - -```java -execution.upgradeStatus(stepExecution.getStatus()); - execution.setExitStatus(stepExecution.getExitStatus()); -``` - -回到`AbstractJob#execute(JobExecution execution)`,里面的`doExecute(execution);`执行完成,后面又是一些状态更新,数据保存。 - -`listener.afterJob(execution);`:`JobExecutionListener#afterJob` -`jobRepository.update(execution);` -`JobSynchronizationManager.release();` - -至此就基本看完了里面大体流程。一些细节方面在下面类里面主要讲解。 - -## BatchAutoConfiguration - -## BatchConfigurerConfiguration - -### DefaultBatchConfigurer - -首先`Autowired`注入`DataSource`。这里就要区分是手动创建还是注入 spring 中的了。手动创建需要手动执行`initialize()`方法,spring -创建的因为加了`@PostConstruct`注解。会自动执行。 - -#### DefaultBatchConfigurer#initialize() - -```java -try { - if (dataSource == null) { - logger.warn("No datasource was provided...using a Map based JobRepository"); - - if (getTransactionManager() == null) { - logger.warn("No transaction manager was provided, using a ResourcelessTransactionManager"); - this.transactionManager = new ResourcelessTransactionManager(); - } - - MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(getTransactionManager()); - jobRepositoryFactory.afterPropertiesSet(); - this.jobRepository = jobRepositoryFactory.getObject(); - - MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(jobRepositoryFactory); - jobExplorerFactory.afterPropertiesSet(); - this.jobExplorer = jobExplorerFactory.getObject(); - } else { - this.jobRepository = createJobRepository(); - this.jobExplorer = createJobExplorer(); - } - - this.jobLauncher = createJobLauncher(); -} catch (Exception e) { - throw new BatchConfigurationException(e); -} -``` - -很简单的代码,`dataSource`不存在,`jobExplorer`和`jobRepository`就使用`MapJobRepositoryFactoryBean` -和`MapJobExplorerFactoryBean` -这里还有个`private PlatformTransactionManager transactionManager`,如果 spring 管理的`DefaultBatchConfigurer` -类,使用`DataSourceTransactionManager`,否则使用`ResourcelessTransactionManager`。这俩个类一个是 spring 事务相关的不做多讲解 - -## Listener - -里面涉及到一大堆的监听器,处理器。每个类基本都有讲到。 类上的注释非常重要~! - -首先校验,然后使用`stepExecution`保存数据。ecDao 保存 ExecutionContext。 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" index 29f8df2b..ff4b34c1 100644 --- "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" @@ -11,7 +11,7 @@ 我们每次添加 `spring-boot-starter-security`,启动的时候会有一条类似的日志: -``` +```txt Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430 This generated password is for development use only. Your springSecurity configuration must be updated before running your @@ -20,9 +20,9 @@ application in production. 找到 `UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager` 类,它是 springboot 自动装配的。 -下面这些都是 springboot 自动装配类,在 `spring-boot-autoconfigure-2.7.7.jar` > META-INF > spring > org.springframework.boot.autoconfigure.AutoConfiguration.imports 中。这些类就是 Spring Security 的全部了。 +下面这些都是 springboot 自动装配类: -```imports +```java org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration @@ -284,7 +284,7 @@ public @interface EnableWebSecurity { ### WebSecurityConfiguration -首先先看类注释(以后都默认翻译成简体中文.ali 翻译): +首先先看类注释 (以后都默认翻译成简体中文 .ali 翻译): 使用 WebSecurity 创建执行 Spring 安全 web 安全的 FilterChainProxy。然后导出必要的 bean。 可以通过实现 WebSecurityConfigurer WebSecurityConfigurer 并将其公开为 Configuration 或公开 WebSecurityCustomizer bean 来进行自定义。 @@ -476,7 +476,7 @@ protected final O doBuild() throws Exception { 剩下的都是一些创建一些 bean 了。 -`SecurityExpressionHandler`: 默认为`DefaultWebSecurityExpressionHandler`类(Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离) +`SecurityExpressionHandler`: 默认为`DefaultWebSecurityExpressionHandler`类 (Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离) `WebInvocationPrivilegeEvaluator`: 为 `WebSecurity#performBuild()`中创建的 `requestMatcherPrivilegeEvaluatorsEntries` 使用`RequestMatcherDelegatingWebInvocationPrivilegeEvaluator`包装。(允许用户确定他们是否具有给定 web URI 的特权。) @@ -506,7 +506,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 配置认证 http.formLogin(); - // 设置URL的授权问题 + // 设置 URL 的授权问题 // 多个条件取交集 http.authorizeRequests() // 匹配 / 控制器 permitAll() 不需要被认证就可以访问 @@ -516,7 +516,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // anyRequest() 所有请求 authenticated() 必须被认证 .anyRequest().authenticated(); // .accessDecisionManager(accessDecisionManager()); - // 关闭csrf + // 关闭 csrf http.csrf().disable(); return http.build(); } @@ -589,14 +589,14 @@ throw new IllegalArgumentException("The Filter class "+filter.getClass().getName 提示: 这个里面每次添加一个类,如果在`HttpSecurity`中调用`getOrApply` 。比如这个代码调用的是`exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));`。 打开`ExceptionHandlingConfigurer`类,发现是一个`HttpSecurityBuilder`, 这样只需要看`configure`方法大概就能明白这个是一个什么类。 -这个就是在 filter 中添加了一个`ExceptionTranslationFilter`filter.主要就是`SecurityConfigurer` +这个就是在 filter 中添加了一个`ExceptionTranslationFilter`filter. 主要就是`SecurityConfigurer` 的俩个方法。先调用`init(B builder)`,然后`configure(B builder)` 后面都是一样,就跳过了 > applyDefaultConfigurers(http); -这里的这一句,就是从"META-INF/spring.factories" 中加载并实例化给定类型的工厂实现 +这里的这一句,就是从 "META-INF/spring.factories" 中加载并实例化给定类型的工厂实现 `SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader)` 然后调用`http.apply(configurer);` 添加到`configurers`里面 @@ -637,7 +637,7 @@ throw new IllegalArgumentException("The Filter class "+filter.getClass().getName 进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入`AbstractConfiguredSecurityBuilder#doBuild()`方法 `beforeInit();`: 还是没有什么 -`init()`: 调用里面所有的`configurers`里面的`init方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 +`init()`: 调用里面所有的`configurers`里面的`init 方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 接下来`SecurityFilterChain`就已经创建好了,看一下里面的方法 @@ -689,22 +689,25 @@ public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProce } ``` -这里面返回了一个`AuthenticationManagerBuilder`的 bean,也就是上面`` -HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是一个`SecurityBuilder`。 +这里面返回了一个`AuthenticationManagerBuilder`的 bean,也就是上面`HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是一个`SecurityBuilder`。 -> LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); +```java +LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); +``` 首先创建了一个`LazyPasswordEncoder`,就是`PasswordEncoder`,用来管理密码的 -> AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); +```java +AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); +``` -这个就是在`SecurityAutoConfiguration`中创建的 springSecurity 的发布订阅,用来订阅事件 +这个就是在 `SecurityAutoConfiguration` 中创建的 springSecurity 的发布订阅,用来订阅事件 ```java DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); ``` -就是 `AuthenticationManagerBuilder`的真正实现了。接下来回到`getAuthenticationManager()`方法 +就是 `AuthenticationManagerBuilder` 的真正实现了。接下来回到`getAuthenticationManager()`方法 ```java public AuthenticationManager getAuthenticationManager() throws Exception { @@ -1128,7 +1131,7 @@ protected final void updateAuthenticationDefaults() { 2. `this.failureHandler`设置为`new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl)` 里面的`authenticationFailureUrl`是`/login + "?error"` -`getBuilder().getConfigurer(LogoutConfigurer.class);` 就是前面加入的那一堆`Configurer`中的一个.这个默认就是当前设置的值,不用理会 +`getBuilder().getConfigurer(LogoutConfigurer.class);` 就是前面加入的那一堆`Configurer`中的一个 . 这个默认就是当前设置的值,不用理会 > updateAccessDefaults(http); @@ -1144,8 +1147,8 @@ protected final void updateAuthenticationDefaults() { ```java /** * 开始一个身份验证方案。 - 在调用该方法之前, ExceptionTranslationFilter将使用请求的目标URL填充HttpSession属性abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 - 实现应根据需要修改ServletResponse上的标头,以开始身份验证过程。 + 在调用该方法之前, ExceptionTranslationFilter 将使用请求的目标 URL 填充 HttpSession 属性 abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 + 实现应根据需要修改 ServletResponse 上的标头,以开始身份验证过程。 */ private LinkedHashMap defaultEntryPointMappings=new LinkedHashMap<>(); 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" index 52da85f3..fc235f24 100644 --- "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" @@ -6,7 +6,7 @@ 为了方便起见,我们直接在`src/main/resources/resources`目录下创建一个`login.html`(不需要 Controller 跳转): -``` +```html @@ -27,7 +27,7 @@ ``` -要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在`BrowserSecurityConfig`的`configure`中添加一些配置: +要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在 `BrowserSecurityConfig` 的 `configure` 中添加一些配置: ```java @Configuration @@ -58,7 +58,7 @@ public class BrowserConfig extends WebSecurityConfigurerAdapter { Spring Security 默认会为我们生成一个用户名为 user,密码随机的用户实例,当然我们也可以定义自己用户信息的获取逻辑,只需要实现 Spring Security 提供的**_UserDetailService_**接口即可,该接口只有一个抽象方法**_loadUserByUsername_**,具体实现如下: -``` +```java @Service public class UserDetailService implements UserDetailsService { @Autowired @@ -76,13 +76,13 @@ public class UserDetailService implements UserDetailsService { ### BrowserConfig 配置解析 -我们首先来梳理下`BrowserConfig`中的配置是如何被 Spring Security 所加载的。 +我们首先来梳理下 `BrowserConfig` 中的配置是如何被 Spring Security 所加载的。 -首先找到调用`BrowserConfig`的`configure()`的地方,在其父类`WebSecurityConfigurerAdapter`的`getHttp()`中: +首先找到调用 `BrowserConfig` 的 `configure()` 的地方,在其父类 `WebSecurityConfigurerAdapter` 的 `getHttp()` 中: ![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png) -往上一步找到调用`getHttp()`的地方,在同个类的`init()`中: +往上一步找到调用 `getHttp()` 的地方,在同个类的 `init()` 中: ![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png) From 4787c419f31eab2f3cfb6e5fbc90486fbaf2539d Mon Sep 17 00:00:00 2001 From: Libin YANG Date: Tue, 15 Apr 2025 04:54:51 +0000 Subject: [PATCH 5/5] fix --- .github/workflows/{sync.yml => deploy.yml} | 0 docs/.vitepress/config.mts | 1 + .../clazz/Spring-SystemPropertyUtils.md | 2 +- ...ateTimeFormatAnnotationFormatterFactory.md | 2 +- docs/Spring/mvc/Spring-MVC-HandlerMapping.md | 2 +- docs/SpringBoot/Spring-Boot-Run.md | 24 +- .../SpringBoot-ConditionalOnBean.md | 12 +- .../SpringBoot-ConfigurationProperties.md | 28 +- docs/SpringBoot/SpringBoot-LogSystem.md | 10 +- .../SpringBoot/SpringBoot-application-load.md | 22 +- ...52\345\212\250\350\243\205\351\205\215.md" | 32 +- .../spring-cloud-gateway-source-note.md | 12 +- ...01\347\250\213\350\241\245\345\205\205.md" | 1291 ----------------- ...50\346\210\267\350\256\244\350\257\201.md" | 319 ---- ...07\347\250\213\350\247\243\346\236\220.md" | 40 +- ...20\347\240\201\350\265\217\346\236\220.md" | 2 +- docs/nacos/nacos-discovery.md | 8 +- 17 files changed, 99 insertions(+), 1708 deletions(-) rename .github/workflows/{sync.yml => deploy.yml} (100%) diff --git a/.github/workflows/sync.yml b/.github/workflows/deploy.yml similarity index 100% rename from .github/workflows/sync.yml rename to .github/workflows/deploy.yml diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 03139f16..40a4982b 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -8,6 +8,7 @@ export default defineConfig({ ['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' 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/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/mvc/Spring-MVC-HandlerMapping.md b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md index df07c90a..b70dac49 100644 --- a/docs/Spring/mvc/Spring-MVC-HandlerMapping.md +++ b/docs/Spring/mvc/Spring-MVC-HandlerMapping.md @@ -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/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 5a0e06ae..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/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-gateway-source-note.md b/docs/SpringCloud/spring-cloud-gateway-source-note.md index ab1a7897..e1697ac5 100644 --- a/docs/SpringCloud/spring-cloud-gateway-source-note.md +++ b/docs/SpringCloud/spring-cloud-gateway-source-note.md @@ -102,13 +102,13 @@ public class GatewayClassPathWarningAutoConfiguration { > > Route 是由 AsyncPredicate 和 GatewayFilter 组成的。而 AsyncPredicate 由 RoutePredicateFactory 生成,GatewayF 创建 ilter 由 GatewayFilterFactory -![RouteLocator](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteLocator.png) +![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](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png) +![Route](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/Route.png) ### 源码 @@ -633,7 +633,7 @@ public @interface ConditionalOnEnabledPredicate { 因为 @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](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png) +![OnEnabledComponent](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/OnEnabledComponent.png) ```java /** @@ -1121,7 +1121,7 @@ public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator 看 PredicateDefinition、FilterDefinition 的构造器,就能明白属性文件为啥可以写 `Weight=group1,8` -![image-20230428141218057](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/image-20230428141218057-1682662351478.png) +![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 @@ -1129,7 +1129,7 @@ InMemoryRouteDefinitionRepository 是由 [GatewayAutoConfiguration](#GatewayAuto RouteDefinitionRepository 的职责是通过缓存的方式记录 RouteDefinition,而不是通过属性 映射成 RouteDefinition。而 [AbstractGatewayControllerEndpoint](#AbstractGatewayControllerEndpoint) 会依赖 RouteDefinitionWriter 的实例,用来缓存通过接口方式注册的 RouteDefinition。 -![RouteDefinitionRepository](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/RouteDefinitionRepository.png) +![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 { @@ -1186,7 +1186,7 @@ GatewayControllerEndpoint 和 GatewayLegacyControllerEndpoint 是由 [GatewayAut 刷新 RouteDefinition 是会发布 RefreshRoutesEvent 事件,该事件会有 [CachingRouteLocator](#RouteLocator) 处理 -![AbstractGatewayControllerEndpoint](../../images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png) +![AbstractGatewayControllerEndpoint](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringCloud/spring-cloud-gateway-source-note_imgs/AbstractGatewayControllerEndpoint.png) ## RouteRefreshListener 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" index ff4b34c1..e69de29b 100644 --- "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" @@ -1,1291 +0,0 @@ -# SpringSecurity 流程补充 - -注意: - -1. 基于 spring-boot-dependencies:2.7.7 -2. 首先需要了解 [springboot2.7 升级](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes) - `Changes to Auto-configuration` 以后使用 `autoconfigure` 进行自动注入 -3. 代码地址 [io.github.poo0054](https://github.com/poo0054/security-study/blob/master/starter/src/main/java/io/github/poo0054/security/StarterApplication.java) - -## 启动 - -我们每次添加 `spring-boot-starter-security`,启动的时候会有一条类似的日志: - -```txt -Using generated springSecurity password: 1db8eb87-e2ee-4c72-88e7-9b85268c4430 - -This generated password is for development use only. Your springSecurity configuration must be updated before running your -application in production. -``` - -找到 `UserDetailsServiceAutoConfiguration#InMemoryUserDetailsManager` 类,它是 springboot 自动装配的。 - -下面这些都是 springboot 自动装配类: - -```java -org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration -org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration -org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration -org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration -org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration -.......... -org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration -``` - -## SecurityAutoConfiguration - -```java - -/** - * {@code EnableAutoConfiguration} for Spring Security. - * - * @author Dave Syer - * @author Andy Wilkinson - * @author Madhura Bhave - * @since 1.0.0 - */ -@AutoConfiguration -@ConditionalOnClass(DefaultAuthenticationEventPublisher.class) -@EnableConfigurationProperties(SecurityProperties.class) -@Import({SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class}) -public class SecurityAutoConfiguration { - - @Bean - @ConditionalOnMissingBean(AuthenticationEventPublisher.class) - public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) { - return new DefaultAuthenticationEventPublisher(publisher); - } - -} -``` - -### @EnableConfigurationProperties(SecurityProperties.class) - -这个是 springSecurity 的核心配置类`SecurityProperties`,里面能配置 -`filter`: 过滤,`user` : 用户信息 - -### @Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class }) - -这里导入了 2 个类 `SpringBootWebSecurityConfiguration`和`SecurityDataConfiguration`,`SecurityDataConfiguration`是 Spring -springSecurity 与 Spring 数据的集成,暂时不做讲解,重点是`SpringBootWebSecurityConfiguration` - -#### SpringBootWebSecurityConfiguration - -这个类就是一个 `Configuration` 类,条件必须为 `@ConditionalOnWebApplication(type = Type.SERVLET)` 才会注入 - -##### SecurityFilterChainConfiguration - -其中第一个子类`SecurityFilterChainConfiguration`添加了`@ConditionalOnDefaultWebSecurity`,这个类有个注解 -`@Conditional(DefaultWebSecurityCondition.class)`,而`DefaultWebSecurityCondition`类继承了`AllNestedConditions` - -所以下面代码就是判断该类是否生效,如果不存在`SecurityFilterChain`和`WebSecurityConfigurerAdapter` -的 bean,就生效。创建默认的`SecurityFilterChain` - -```java -/** - * {@link Condition} for - * {@link ConditionalOnDefaultWebSecurity @ConditionalOnDefaultWebSecurity}. - * - * @author Phillip Webb - */ -class DefaultWebSecurityCondition extends AllNestedConditions { - - DefaultWebSecurityCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnClass({SecurityFilterChain.class, HttpSecurity.class}) - static class Classes { - - } - - @ConditionalOnMissingBean({ - org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class, - SecurityFilterChain.class}) - @SuppressWarnings("deprecation") - static class Beans { - - } - -} -``` - -`SecurityFilterChain`就是整个 springsecurity 的流程了,有俩个方法,一个是`boolean matches(HttpServletRequest request);` -,是否匹配这次请求,匹配成功就获取当前所有`Filter`进行处理 - -`SecurityFilterChain`类会放在最下面单独讲解 - -##### ErrorPageSecurityFilterConfiguration - -这是第二个子类,主要就是通过`FilterRegistrationBean`注入了一个`ErrorPageSecurityFilter`。 用于拦截错误调度,以确保对错误页面的授权访问。 - -```java - /** -* Configures the {@link ErrorPageSecurityFilter} -*/ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass(WebInvocationPrivilegeEvaluator.class) -@ConditionalOnBean(WebInvocationPrivilegeEvaluator.class) -static class ErrorPageSecurityFilterConfiguration { - - @Bean - FilterRegistrationBean errorPageSecurityFilter(ApplicationContext context) { - FilterRegistrationBean registration = new FilterRegistrationBean<>( - new ErrorPageSecurityFilter(context)); - registration.setDispatcherTypes(DispatcherType.ERROR); - return registration; - } - -} -``` - -##### WebSecurityEnablerConfiguration - -这个类主要就是添加了`@EnableWebSecurity`注解,这个注解也很重要,后面跟`SecurityFilterChain`一起讲解 - -### DefaultAuthenticationEventPublisher - -在类中还存在`SecurityAutoConfiguration`bean,这个是属于 spring 的发布订阅。改装一下,就是 springSecurity 的成功和失败事件,可以订阅失败后的一些处理,如日志打印等 - -```java -/** - * @author Luke Taylor - * @since 3.0 - */ -public interface AuthenticationEventPublisher { - - void publishAuthenticationSuccess(Authentication authentication); - - void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication); - -} -``` - -## UserDetailsServiceAutoConfiguration - -注入条件 - -```java - -@ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean( - value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, - AuthenticationManagerResolver.class}, - type = {"org.springframework.security.oauth2.jwt.JwtDecoder", - "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", - "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", - "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository"}) -``` - -### InMemoryUserDetailsManager - -UserDetailsManager 的非持久化实现,支持内存映射。 -主要用于测试和演示目的,其中不需要完整的持久系统 - -## SecurityFilterAutoConfiguration - -SpringSecurity 的过滤器 - -自动配置。与 SpringBootWebSecurityConfiguration 分开配置,以确保在存在用户提供的 WebSecurityConfiguration 时,过滤器的顺序仍然被配置。 - -### DelegatingFilterProxyRegistrationBean - -这个类是继承了`AbstractFilterRegistrationBean`,`FilterRegistrationBean`也是继承的`AbstractFilterRegistrationBean` -。但是`DelegatingFilterProxyRegistrationBean`是使用的一个`targetBeanName` -找到 bean。进行注入。其中`private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;` -也就是`springSecurityFilterChain` - -其中有俩个属性, Order 和 DispatcherTypes - -- Order 默认为-100 - -- `DispatcherType`就是`DispatcherType`类 - - `private Set dispatcherTypes = new HashSet<>( -Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));` - -注意: 这里需要了解一下`DelegatingFilterProxyRegistrationBean`以及 spring 如何整合 filter 和 mvc 的。 springSecurity 核心就是 filter - -![img.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7_0.png) - -`DelegatingFilterProxyRegistrationBean`和`FilterRegistrationBean`都是继承的`RegistrationBean`,而`RegistrationBean` -又是`ServletContextInitializer`的实现类。其中`void onStartup(ServletContext servletContext)`方法是关键。 在`javax.servlet` -中,存在这样一个类 - -```java -public interface ServletContainerInitializer { - - /** - * Receives notification during startup of a web application of the classes - * within the web application that matched the criteria defined via the - * {@link javax.servlet.annotation.HandlesTypes} annotation. - * - * @param c The (possibly null) set of classes that met the specified - * criteria - * @param ctx The ServletContext of the web application in which the - * classes were discovered - * - * @throws ServletException If an error occurs - */ - void onStartup(Set> c, ServletContext ctx) throws ServletException; -``` - -springboot 中的`TomcatStarter`继承了这个类,而这个类又是 spring 启动的核心`AbstractApplicationContext#refresh()` -中的`onRefresh();`方法。 - -找到实现类`ServletWebServerApplicationContext`的`onRefresh()`方法。里面有`createWebServer();`方法。 -在里面有创建`webServer`的方法。`this.webServer = factory.getWebServer(getSelfInitializer());`这个就是创建`tomcat`的工厂。 -`TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)` -。里面就是创建 tomcat,并启动`TomcatWebServer#initialize()`(这就是 springboot 不用 tamcat 的原因) - -而把 filter 注入 servlet 中的是`TomcatServletWebServerFactory#prepareContext(Host host, ServletContextInitializer[] initializers)` -中的`TomcatServletWebServerFactory#configureContext(context, initializersToUse)` -方法,在里面创建了一个`TomcatStarter starter = new TomcatStarter(initializers);`。而`TomcatStarter` -继承了`ServletContainerInitializer`类。调用`ServletContainerInitializer#onStartup(ServletContext servletContext)` -时候会进入到`RegistrationBean`中。 -然后`AbstractFilterRegistrationBean#addRegistration`里面添加 filter -`return servletContext.addFilter(getOrDeduceName(filter), filter);`这样每次请求 servlet,tomcat 就会先使用 filter 过滤器进行拦截 - -简单来说就是`TomcatStarter`继承了`ServletContainerInitializer`。tomcat 会调用`onStartup` -方法,在这个方法里面会调用`ServletContextInitializer#onStartup`。在这个里面有`filter`和其余需要整合`ServletContext`的方法 - -比如`springSecurityFilterChain`使用的是`DelegatingFilterProxyRegistrationBean`,需要使用 bean 去获取`getFilter` -。而`ErrorPageFilter`使用的是`FilterRegistrationBean`。直接就可以注入 - -## @EnableWebSecurity - -这个就是 springSecurity 的核心注解 -需要注意,`@EnableWebMvcSecurity`已经弃用,请使用`@EnableWebSecurity` - -```java - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented -@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, - HttpSecurityConfiguration.class}) -@EnableGlobalAuthentication -@Configuration -public @interface EnableWebSecurity { - - /** - * Controls debugging support for Spring Security. Default is false. - * @return if true, enables debug support with Spring Security - */ - boolean debug() default false; - -} -``` - -### WebSecurityConfiguration - -首先先看类注释 (以后都默认翻译成简体中文 .ali 翻译): - -使用 WebSecurity 创建执行 Spring 安全 web 安全的 FilterChainProxy。然后导出必要的 bean。 -可以通过实现 WebSecurityConfigurer WebSecurityConfigurer 并将其公开为 Configuration 或公开 WebSecurityCustomizer bean 来进行自定义。 -使用 EnableWebSecurity 时会导入该配置。 - -#### setFilterChainProxySecurityConfigurer - -逐行解释: - -> this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor)); - -`ObjectPostProcessor`也就是`AutowireBeanFactoryObjectPostProcessor`。在`AuthenticationConfiguration` -类上`@Import(ObjectPostProcessorConfiguration.class)`. - -`AutowireBeanFactoryObjectPostProcessor`类里面创建`webSecurity` -`AutowireBeanFactoryObjectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));` -使用`AutowireCapableBeanFactory`创建出`WebSecurity` -`AutowireBeanFactoryObjectPostProcessor`把`SmartInitializingSingleton`和`DisposableBean`拿出来,使用自己的`destroy()` -和`afterSingletonsInstantiated()`执行 - -> List> webSecurityConfigurers = new -> AutowiredWebSecurityConfigurersIgnoreParents(beanFactory).getWebSecurityConfigurers(); - -`AutowiredWebSecurityConfigurersIgnoreParents`也就是获取所有的`WebSecurityConfigurerAdapter` - -这里有几个类需要了解`SecurityConfigurer`和`SecurityBuilder` - -先了解一下结构 -![img_1.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7_1.png) - -![img_2.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7-_2.png) - -使用`WebSecurity` -聚合了`private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();` -也就是`WebSecurityConfigurerAdapter`(当然还有别的,这里主要讲`WebSecurityConfigurerAdapter`) - -`WebSecurityConfigurerAdapter`也可以认为就是`SecurityConfigurer` - -`WebSecurity`也就是`SecurityBuilder` - -然后在`SecurityBuilder`的实现类`AbstractConfiguredSecurityBuilder`的`doBuild()`方法进行很多别的操作。 - -```java -protected final O doBuild() throws Exception { - synchronized (this.configurers) { - this.buildState = BuildState.INITIALIZING; - beforeInit(); - init(); - this.buildState = BuildState.CONFIGURING; - beforeConfigure(); - configure(); - this.buildState = BuildState.BUILDING; - O result = performBuild(); - this.buildState = BuildState.BUILT; - return result; - } -} -``` - -回到原来地方,返回的`webSecurityConfigurers`,里面的 - -```java -for (SecurityConfigurer webSecurityConfigurer : webSecurityConfigurers) { - this.webSecurity.apply(webSecurityConfigurer); -} -``` - -然后就到了`AbstractConfiguredSecurityBuilder#apply`方法,里面调用了`add(configurer);` 也就是把`SecurityConfigurer` -放入了`AbstractConfiguredSecurityBuilder#configurers`的一个 map 中,这样就使用`SecurityBuilder`聚合了`SecurityConfigurer`。 -在构建的时候可以做一些事情 - -也就是说使用`WebSecurity`聚合了`SecurityConfigurer`(包括`WebSecurityConfigurerAdapter`) - -> this.securityFilterChains = securityFilterChains; - -获取所有的`securityFilterChains` - -> this.webSecurityCustomizers = webSecurityCustomizers; - -获取所有`webSecurityCustomizers` - -#### public Filter springSecurityFilterChain() - -这个里面最关键的也就是这个了。 - -```java -for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { - this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain); - for (Filter filter : securityFilterChain.getFilters()) { - if (filter instanceof FilterSecurityInterceptor) { - this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); - break; - } - } -} -for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { - customizer.customize(this.webSecurity); -} -return this.webSecurity.build(); -``` - -首先使用根据获取到的`securityFilterChains`set 入`WebSecurity#securityFilterChainBuilders`的 List 属性 - -这里有个需要注意的地方,如果你继承了`WebSecurityConfigurerAdapter`。`this.securityFilterChains` 就是一个空的。 - -而且会由`WebSecurityConfigurerAdapter#getHttp()`进行创建`WebSecurity` -。这就跟 spring 的 order 有关了。 `@Order(SecurityProperties.BASIC_AUTH_ORDER)` - -其中`SpringBootWebSecurityConfiguration#SecurityFilterChainConfiguration`有一个注解`@ConditionalOnDefaultWebSecurity` - -```java - -@Configuration(proxyBeanMethods = false) -@ConditionalOnDefaultWebSecurity -static class SecurityFilterChainConfiguration { - - @Bean - @Order(SecurityProperties.BASIC_AUTH_ORDER) - SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); - http.formLogin(); - http.httpBasic(); - return http.build(); - } - -} -``` - -这里会创建`SecurityFilterChain`。 还会有一个`HttpSecurity`的注入 - -继续回到上面, - -```java -if (filter instanceof FilterSecurityInterceptor) { - this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); - break; -} -``` - -`FilterSecurityInterceptor`也在这里进行处理,也就是`SecurityMetadataSource`元数据 - -然后自定义的`WebSecurityCustomizer`也在这里。可以自行改变`webSecurity` - -```java -for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { - customizer.customize(this.webSecurity); -} -``` - -接下来就是构建了,来到 `AbstractConfiguredSecurityBuilder#doBuild()` - -```java -protected final O doBuild() throws Exception { - synchronized (this.configurers) { - this.buildState = BuildState.INITIALIZING; - beforeInit(); - init(); - this.buildState = BuildState.CONFIGURING; - beforeConfigure(); - configure(); - this.buildState = BuildState.BUILDING; - O result = performBuild(); - this.buildState = BuildState.BUILT; - return result; - } -} -``` - -> init(); -> 如果继承了`WebSecurityConfigurerAdapter`,就会在这里创建`HttpSecurity` - -注意: 这里个`buildState`,用来控制当前状态的 - -> beforeConfigure(); - -在当前是没有什么处理 - -> configure(); - -这行代码就是我们每次继承`WebSecurityConfigurerAdapter`的处理了 - -> O result = performBuild(); - -然后就到了`WebSecurity#performBuild()`。 - -1. 首先排除忽略的`RequestMatcher` -2. 添加入`securityFilterChain` 和`requestMatcherPrivilegeEvaluatorsEntries` -3. 创建出`FilterChainProxy`bean 的名称为`springSecurityFilterChain` (重点) - -剩下的都是一些创建一些 bean 了。 - -`SecurityExpressionHandler`: 默认为`DefaultWebSecurityExpressionHandler`类 (Facade 将 springSecurity 评估安全表达式的要求与基础表达式对象的实现隔离) - -`WebInvocationPrivilegeEvaluator`: 为 `WebSecurity#performBuild()`中创建的 `requestMatcherPrivilegeEvaluatorsEntries` -使用`RequestMatcherDelegatingWebInvocationPrivilegeEvaluator`包装。(允许用户确定他们是否具有给定 web URI 的特权。) - -这俩个类都是很重要的。 一个是解析器,一个是判断 uri 是否合格的类。 后面单独讲 - -### HttpSecurityConfiguration - -接下来到了`HttpSecurityConfiguration` - -根据上面`WebSecurityConfiguration`,可以得出。`WebSecurityConfiguration`创建`WebSecurity`,`WebSecurity` -创建了`FilterChainProxy`的 bean。 - -`HttpSecurityConfiguration`创建`HttpSecurity`。 而在`SecurityFilterChainConfiguration`类中,使用`HttpSecurity` -创建了`SecurityFilterChain`。这也就是我们使用了`WebSecurityConfigurerAdapter`。为什么会存在`SecurityFilterChain` -类的原因。是`SecurityFilterChainConfiguration#defaultSecurityFilterChain`创建了一个`SecurityFilterChain`。 - -得出结论,`FilterChainProxy`持有`SecurityFilterChain`。而`DelegatingFilterProxyRegistrationBean`又持有`FilterChainProxy` - -`DelegatingFilterProxyRegistrationBean`->`FilterChainProxy`->`SecurityFilterChain` - -其实到了这一步。后面的就是我们自己编写的代码了比如 - -```java -@Bean -public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // 配置认证 - http.formLogin(); - - // 设置 URL 的授权问题 - // 多个条件取交集 - http.authorizeRequests() - // 匹配 / 控制器 permitAll() 不需要被认证就可以访问 - .antMatchers("/login").permitAll() - .antMatchers("/error").permitAll() - .antMatchers("/fail").permitAll() - // anyRequest() 所有请求 authenticated() 必须被认证 - .anyRequest().authenticated(); - // .accessDecisionManager(accessDecisionManager()); - // 关闭 csrf - http.csrf().disable(); - return http.build(); -} -``` - -或者继承 WebSecurityConfigurerAdapter 的类了。 - -这里我们用 springSecurity 默认的`SpringBootWebSecurityConfiguration`来举例 - -```java -@Bean -@Order(SecurityProperties.BASIC_AUTH_ORDER) -SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { - http.authorizeRequests().anyRequest().authenticated(); - http.formLogin(); - http.httpBasic(); - return http.build(); -} -``` - -首先获取`HttpSecurity http`(这里的`HttpSecurity`是从`HttpSecurityConfiguration` -里面创建的,如果是自定义的`SecurityFilterChain`就是从自己写的里面来的) - -> 我们来到`HttpSecurityConfiguration#httpSecurity()` -> 先创建一个默认的密码管理器, - -接下来进入`authenticationBuilder.parentAuthenticationManager(authenticationManager());` -,这里就是`AuthenticationConfiguration` -里面的处理。这个类后面和 springaop 加载我们写的注释单独在`@EnableGlobalAuthentication`注解类说 - -接着创建` HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());`, - -> .csrf(withDefaults()) -> 启用 CSRF 保护,创建`CsrfConfigurer`类,并添加入添加入`AbstractConfiguredSecurityBuilder#configurers` - -> .addFilter(new WebAsyncManagerIntegrationFilter()) - -这个有一个很有意思的类`FilterOrderRegistration`。 前面问的根据 filter 是如何包装顺序的。就在这个类里面 - -```java -FilterOrderRegistration() { - Step order = new Step(INITIAL_ORDER, ORDER_STEP); - put(DisableEncodeUrlFilter.class, order.next()); - put(ForceEagerSessionCreationFilter.class, order.next()); - put(ChannelProcessingFilter.class, order.next()); - order.next(); // gh-8105 - put(WebAsyncManagerIntegrationFilter.class, order.next()); - put(SecurityContextHolderFilter.class, order.next()); - put(SecurityContextPersistenceFilter.class, order.next()); - put(HeaderWriterFilter.class, order.next()); - put(CorsFilter.class, order.next()); - put(CsrfFilter.class, order.next()); - put(LogoutFilter.class, order.next()); -} -``` - -springSecurity 事先使用这个类把预加载的类全部排序好,然后每次 add 一个新的 filter 就会使用这个里面的序号。如果我们有自定义的类,也要提前加载到里面去,不然就会 - -```java -throw new IllegalArgumentException("The Filter class "+filter.getClass().getName() - +" does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); -``` - -`WebAsyncManagerIntegrationFilter`: 与处理异步 web 请求相关的实用程序方法 - -> .exceptionHandling(withDefaults()) - -这个是异常的处理 - -提示: 这个里面每次添加一个类,如果在`HttpSecurity`中调用`getOrApply` -。比如这个代码调用的是`exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));`。 -打开`ExceptionHandlingConfigurer`类,发现是一个`HttpSecurityBuilder`, 这样只需要看`configure`方法大概就能明白这个是一个什么类。 -这个就是在 filter 中添加了一个`ExceptionTranslationFilter`filter. 主要就是`SecurityConfigurer` -的俩个方法。先调用`init(B builder)`,然后`configure(B builder)` - -后面都是一样,就跳过了 - -> applyDefaultConfigurers(http); - -这里的这一句,就是从 "META-INF/spring.factories" 中加载并实例化给定类型的工厂实现 -`SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader)` -然后调用`http.apply(configurer);` 添加到`configurers`里面 - -接下来回到`SecurityFilterChainConfiguration`类 - -> http.authorizeRequests().anyRequest().authenticated(); - -首先添加了`http.authorizeRequests()` -然后调用 `return getOrApply(new ExpressionUrlAuthorizationConfigurer<>(context)).getRegistry();`。 -先把`ExpressionUrlAuthorizationConfigurer`放入 config 中,返回一个调用了`getRegistry()`。 -也就是`ExpressionInterceptUrlRegistry`类。 - -后面调用的`.anyRequest()`,也就是`AbstractRequestMatcherRegistry#anyRequest()`。先了解一下结构图 - -![img_3.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/img-2023-6-7-_3.png) - -完整调用链就是`AbstractRequestMatcherRegistry#anyRequest()` -> `AbstractRequestMatcherRegistry#requestMatchers(RequestMatcher... requestMatchers)` -> `AbstractConfigAttributeRequestMatcherRegistry#chainRequestMatchers(List requestMatchers)` -> `ExpressionUrlAuthorizationConfigurer#chainRequestMatchersInternal(List requestMatchers)` -> `return new AuthorizedUrl(requestMatchers);` - -`http.authorizeRequests().anyRequest().authenticated();`是需要所有请求登录后才能访问 - -1. `authorizeRequests`是创建了一个`ExpressionUrlAuthorizationConfigurer`并添加入 configurer 中。 -2. `anyRequest`是创建了一个`new AuthorizedUrl(requestMatchers)`,其中 `requestMatchers` - 是`AnyRequestMatcher.INSTANCE;`也就是`AnyRequestMatcher`对象。里面`matches(HttpServletRequest request)`直接返回 ture -3. `authenticated()`也就是授权,`ExpressionInterceptUrlRegistry#addMapping`。里面放入了一个`UrlMapping`,`UrlMapping` - 的俩个属性,一个是`AnyRequestMatcher`(所有请求),`configAttrs`表示`SecurityConfig`。`SecurityConfig` - 的值为`private static final String authenticated = "authenticated"` - -> http.formLogin(); - -创建了一个`FormLoginConfigurer`,也就是`SecurityConfigurer`。关注`init`和`configure`方法。后面统一讲解 - -> http.httpBasic(); - -`HttpBasicConfigurer`类 - -> http.build() - -进行构建,这个就是非常重要的一个方法,build 对象,老规矩。进入`AbstractConfiguredSecurityBuilder#doBuild()`方法 -`beforeInit();`: 还是没有什么 - -`init()`: 调用里面所有的`configurers`里面的`init 方法`,后面`HttpSecurity#doBuild`统一讲解,先把流程捋一遍 - -接下来`SecurityFilterChain`就已经创建好了,看一下里面的方法 - -```java -/** - * Defines a filter chain which is capable of being matched against an - * {@code HttpServletRequest}. in order to decide whether it applies to that request. - *

- * Used to configure a {@code FilterChainProxy}. - * - * @author Luke Taylor - * @since 3.1 - */ -public interface SecurityFilterChain { - - boolean matches(HttpServletRequest request); - - List getFilters(); -} -``` - -肯定是先匹配,如果成功了,就返回里面所有的 filter 进行过滤,比如刚刚设置的所有请求需要登录,也还有我们需要排除的请求 - -`SecurityAutoConfiguration`类就已经大致讲完了, - -### @EnableGlobalAuthentication - -当前注解在`@EnableSecurity`中会自动加上 - -#### @Import(AuthenticationConfiguration.class) - -`AuthenticationConfiguration`上面`@Import(ObjectPostProcessorConfiguration.class)`。 以前使用的`ObjectPostProcessor` -就是在这里注入的,注入`AutowireBeanFactoryObjectPostProcessor`对象 - -#### AuthenticationManagerBuilder - -```java -@Bean -public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor objectPostProcessor, - ApplicationContext context) { - LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); - AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); - DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder( - objectPostProcessor, defaultPasswordEncoder); - if (authenticationEventPublisher != null) { - result.authenticationEventPublisher(authenticationEventPublisher); - } - return result; -} -``` - -这里面返回了一个`AuthenticationManagerBuilder`的 bean,也就是上面`HttpSecurityConfiguration#httpSecurity()`的时候需要的类,这个类也是一个`SecurityBuilder`。 - -```java -LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); -``` - -首先创建了一个`LazyPasswordEncoder`,就是`PasswordEncoder`,用来管理密码的 - -```java -AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context); -``` - -这个就是在 `SecurityAutoConfiguration` 中创建的 springSecurity 的发布订阅,用来订阅事件 - -```java -DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); -``` - -就是 `AuthenticationManagerBuilder` 的真正实现了。接下来回到`getAuthenticationManager()`方法 - -```java -public AuthenticationManager getAuthenticationManager() throws Exception { - if (this.authenticationManagerInitialized) { - return this.authenticationManager; - } - AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); - if (this.buildingAuthenticationManager.getAndSet(true)) { - return new AuthenticationManagerDelegator(authBuilder); - } - for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { - authBuilder.apply(config); - } - this.authenticationManager = authBuilder.build(); - if (this.authenticationManager == null) { - this.authenticationManager = getAuthenticationManagerBean(); - } - this.authenticationManagerInitialized = true; - return this.authenticationManager; -} -``` - -```java -AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class); -``` - -获取到 `DefaultPasswordEncoderAuthenticationManagerBuilder` - -```java -for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) { - authBuilder.apply(config); -} -``` - -需要注意的是,`this.globalAuthConfigurers`就是上面三个类, - -```java -@Bean -public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( - ApplicationContext context) { - return new EnableGlobalAuthenticationAutowiredConfigurer(context); -} - -@Bean -public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer( - ApplicationContext context) { - return new InitializeUserDetailsBeanManagerConfigurer(context); -} - -@Bean -public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer( - ApplicationContext context) { - return new InitializeAuthenticationProviderBeanManagerConfigurer(context); -} -``` - -调用了`apply`也就是`add`方法。添加到`configurers`中 - -然后调用`build`并返回。 又是到了 `doBuild()` 这里 - -> beforeInit(); - -没有 - -> init(); - -上面三个类的`init`方法 - -1. `EnableGlobalAuthenticationAutowiredConfigurer#init` -2. `InitializeUserDetailsBeanManagerConfigurer#init` 调用了`auth.apply(new InitializeUserDetailsManagerConfigurer());` - 这个类比上面类名字少了一个 bean,并且没有后 init 方法 只有`configure`方法。 里面创建的`DaoAuthenticationProvider` - ,里面默认有一个`passwordEncoder`,在无参构造方法里面。而`UserDetailsService`和`DaoAuthenticationProvider` - 是同一个,也就是在`UserDetailsServiceAutoConfiguration#inMemoryUserDetailsManager`这里创建的。里面继承了,所以是同一个 -3. `InitializeAuthenticationProviderBeanManagerConfigurer#init` - 跟 2 一样,apply 了一个`InitializeAuthenticationProviderManagerConfigurer` - -> beforeConfigure(); - -没有 - -> configure(); - -调用里面的`configure`方法 - -2: `InitializeUserDetailsManagerConfigurer#configure`方法 - -```java -@Override -public void configure(AuthenticationManagerBuilder auth) throws Exception { - if (auth.isConfigured()) { - return; - } - UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class); - if (userDetailsService == null) { - return; - } - PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); - UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(userDetailsService); - if (passwordEncoder != null) { - provider.setPasswordEncoder(passwordEncoder); - } - if (passwordManager != null) { - provider.setUserDetailsPasswordService(passwordManager); - } - provider.afterPropertiesSet(); - auth.authenticationProvider(provider); -} -``` - -获取所有`UserDetailsService`和`PasswordEncoder`和`UserDetailsPasswordService`,使用`DaoAuthenticationProvider` -进行管理,然后添加到`AuthenticationManagerBuilder#authenticationProviders`中 - -3: `InitializeAuthenticationProviderManagerConfigurer#configure`方法,把 spring 中的所有`AuthenticationProvider` -添加到`AuthenticationManagerBuilder#authenticationProviders`中 - -然后又到了熟悉的`AuthenticationManagerBuilder#performBuild` - -```java -ProviderManager providerManager = new ProviderManager(this.authenticationProviders, - this.parentAuthenticationManager); -if (this.eraseCredentials != null) { - providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials); -} -if (this.eventPublisher != null) { - providerManager.setAuthenticationEventPublisher(this.eventPublisher); -} -providerManager = postProcess(providerManager); -return providerManager; -``` - -首先使用`ProviderManager`管理`authenticationProviders`和`parentAuthenticationManager`,这里的`eraseCredentials` -和`CredentialsContainer`类有关,也就是敏感数据。接着的`eventPublisher`就是发布订阅了,默认会创建的 -然后`providerManager = postProcess(providerManager);`就是注入 spring 容器中,接着返回 -,这里返回的其实是`ProviderManager`对象了,接着就是到了`HttpSecurity`的创建了,后面`HttpSecurity#doBuild()` -时候再讲`HttpSecurity`的构建 - -这里面的`LazyPasswordEncoder`这个类也很有意思,手动制造一个懒加载类 - -## @EnableGlobalMethodSecurity - -这里有个很坑的地方,里面的`prePostEnabled`,`securedEnabled`这些属性,不是直接在`GlobalMethodSecuritySelector` -中进行处理的,放在了`GlobalMethodSecuritySelector#methodSecurityMetadataSource` -这个 bean 里面进行处理,然后开启`prePostEnabled`之后,就会加载`PrePostAnnotationSecurityMetadataSource`类。 这个我找了半天,后面无意中才发现 - -这个注解也添加了`@EnableGlobalAuthentication`注解 - -主要是看`GlobalMethodSecuritySelector`类 -里面加载了`AutoProxyRegistrar`,这个就是 springaop 的类,创建代理对象的一个类。会创建`InfrastructureAdvisorAutoProxyCreator` -类来创建代理对象。关键是`GlobalMethodSecurityConfiguration`和`MethodSecurityMetadataSourceAdvisorRegistrar`这俩个类 - -`MethodSecurityMetadataSourceAdvisorRegistrar`类里面都用到了`GlobalMethodSecurityConfiguration`。我就放在一起了 - -### MethodSecurityMetadataSourceAdvisorRegistrar - -这个类里面就是往 spring 中注册了一个`MethodSecurityMetadataSourceAdvisor`对象 - -#### MethodSecurityMetadataSourceAdvisor - -这个类就是`PointcutAdvisor`,使用`AbstractAutoProxyCreator`创建代理对象的是,会获取`Pointcut` -来判断是否需要代理对象,然后使用`Advice`来进行其余操作。这是 springaop 的内容就不过多讲解了 - -aop 首先获取`pointcut`,进行匹配,当前的为 - -```java -class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { - - @Override - public boolean matches(Method m, Class targetClass) { - MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource; - return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass)); - } - -} -``` - -也就是`StaticMethodMatcherPointcut`,`ClassFilter` -默认都是 true,方法匹配为`MethodSecurityMetadataSourceAdvisor#attributeSource`进行匹配。而`methodSecurityMetadataSource` -是在`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource`里面进行创建的。这俩个类后面讲。只要匹配成功就和 aop 一样流程了 - -这里的`Advice`就是`MethodSecurityInterceptor`类。在`GlobalMethodSecurityConfiguration#methodSecurityInterceptor`中创建 - -##### MethodSecurityInterceptor - -> isPrePostEnabled - -添加`PrePostAnnotationSecurityMetadataSource`类,主要关注`getAttributes`方法后面会讲。 -这里面就是我们常用的注解了,然后构建成`ConfigAttribute`并返回。里面的构建主要用的是`PrePostInvocationAttributeFactory` -的实现,只有一个实现 - -> isSecuredEnabled - -这个就是`@Secured`注解的处理。 逻辑基本和上面一样 - -最后返回一个`DelegatingMethodSecurityMetadataSource`对象,就是`MethodSecurityInterceptor`中用到的对象 - -匹配成功的 aop 都会进入`MethodSecurityInterceptor#invoke` - -```java -@Override -public Object invoke(MethodInvocation mi) throws Throwable { - InterceptorStatusToken token = super.beforeInvocation(mi); - Object result; - try { - result = mi.proceed(); - } finally { - super.finallyInvocation(token); - } - return super.afterInvocation(token, result); -} -``` - -这个一看就是标准 aop - -###### super.beforeInvocation(mi) - -这个里面就有授权了,`Authorization`和`authentication`不一样,一个是认证一个是授权。这个是授权,简单说就是角色 - -> Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); - -接着来到了`DelegatingMethodSecurityMetadataSource#getAttributes` - -```java -for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { - attributes = s.getAttributes(method, targetClass); - if (attributes != null && !attributes.isEmpty()) { - break; - } -} -``` - -`this.methodSecurityMetadataSources`里面的值,就是`GlobalMethodSecurityConfiguration#methodSecurityMetadataSource` -里面的`sources`. -构建出来返回`attributes`。 - -> Authentication authenticated = authenticateIfRequired(); - -这个就是获取当前认证信息 - -> attemptAuthorization(object, attributes, authenticated); - -使用`accessDecisionManager`进行授权。放到`MethodInterceptor`中进行讲解 -里面还有授权失败发布事件`publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex));` - -接着就是授权成功发送事件,接着就是返回一个`InterceptorStatusToken`对象 - -###### result = mi.proceed(); - -执行业务 - -###### super.finallyInvocation(token); - -是否刷新`InterceptorStatusToken`,前面传参是 false - -###### return super.afterInvocation(token, result); - -后处理器,与前处理器基本一样。剩下的`MethodInterceptor`中进行讲解 - -#### MethodInterceptor - -创建出`MethodSecurityInterceptor`对象给`MethodInterceptor`用也就是`securityMetadataSource`属性 - -##### this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor(): new MethodSecurityInterceptor(); - -一般都是`MethodSecurityInterceptor` - -##### accessDecisionManager() - -这个就是`AbstractSecurityInterceptor#attemptAuthorization`的授权方法 - -```java -protected AccessDecisionManager accessDecisionManager(){ - List>decisionVoters=new ArrayList<>(); - if(prePostEnabled()){ - ExpressionBasedPreInvocationAdvice expressionAdvice=new ExpressionBasedPreInvocationAdvice(); - expressionAdvice.setExpressionHandler(getExpressionHandler()); - decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); - } - if(jsr250Enabled()){ - decisionVoters.add(new Jsr250Voter()); - } - RoleVoter roleVoter=new RoleVoter(); - GrantedAuthorityDefaults grantedAuthorityDefaults=getSingleBeanOrNull(GrantedAuthorityDefaults.class); - if(grantedAuthorityDefaults!=null){ - roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix()); - } - decisionVoters.add(roleVoter); - decisionVoters.add(new AuthenticatedVoter()); - return new AffirmativeBased(decisionVoters); -} -``` - -前提条件必须开启`prePostEnabled` - -这里面返回的也是`AffirmativeBased`,有时候我们自定义也会使用这个,只要有一个`AccessDecisionVoter`通过就认为是有权限的,这里就不过多讲解了 - -里面的`GrantedAuthorityDefaults`对象,也可以是我们自定义的一个前缀,默认前缀为`ROLE_` - -我们一般自定义的,会使用`.accessDecisionManager(accessDecisionManager())`,在`HttpSecurity#doBuild()`中进行讲解 - -##### afterInvocationManager - -与上面前处理一样 - -##### methodSecurityMetadataSource - -这个就是`MethodSecurityMetadataSource`对象了 - -总结一下这里,就是实现 springaop 的`AbstractPointcutAdvisor`对象`MethodSecurityMetadataSourceAdvisor`, 进行 aop 加载,处理 - -## EnableMethodSecurity - -这个注解没有`@EnableGlobalMethodSecurity`这么强大,代码基本跟`@EnableGlobalMethodSecurity`一样 - -## HttpSecurity#doBuild() - -接下来是最后一块内容了,主要是看里面初始化,构建了哪些类 - -又是熟悉的`AbstractConfiguredSecurityBuilder#doBuild()` - -首先看看我们一开始创建`HttpSecurity`的时候添加了哪些类 `HttpSecurityConfiguration#httpSecurity()` - -```java -@Bean(HTTPSECURITY_BEAN_NAME) -@Scope("prototype") -HttpSecurity httpSecurity() throws Exception { - WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder( - this.context); - AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder( - this.objectPostProcessor, passwordEncoder); - authenticationBuilder.parentAuthenticationManager(authenticationManager()); - authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher()); - HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects()); - // @formatter:off - http - .csrf(withDefaults()) - .addFilter(new WebAsyncManagerIntegrationFilter()) - .exceptionHandling(withDefaults()) - .headers(withDefaults()) - .sessionManagement(withDefaults()) - .securityContext(withDefaults()) - .requestCache(withDefaults()) - .anonymous(withDefaults()) - .servletApi(withDefaults()) - .apply(new DefaultLoginPageConfigurer<>()); - http.logout(withDefaults()); - // @formatter:on - applyDefaultConfigurers(http); - return http; -} -``` - -`CsrfConfigurer`,`ExceptionHandlingConfigurer`,`HeadersConfigurer`,`SessionManagementConfigurer`,`SecurityContextConfigurer`,`RequestCacheConfigurer`,`AnonymousConfigurer`,`ServletApiConfigurer`,`DefaultLoginPageConfigurer`,`LogoutConfigurer` -还有我们添加到`META-INF/spring.factories`中的`AbstractHttpConfigurer.class`类 - -接着回到这里, 我们是自定义了一个`SecurityFilterChain`,所以在这里面进行构建 - -首先` http.formLogin();`添加了`FormLoginConfigurer` - -`http.authorizeRequests()` 添加了`ExpressionUrlAuthorizationConfigurer`,这个只有`configure` 没有`init` - -` http.csrf()` 添加了`CsrfConfigurer` - -`http.userDetailsService(userDetailsService())` 添加了一个自定义的`UserDetailsService` - -### FormLoginConfigurer - -也是一个`SecurityConfigurer` - -首先创建对象给属性赋值 - -```java -authFilter = UsernamePasswordAuthenticationFilter() -loginPage = "/login" -this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage); -``` - -接着来到`init` - -```java -@Override -public void init(B http) throws Exception { - updateAuthenticationDefaults(); - updateAccessDefaults(http); - registerDefaultAuthenticationEntryPoint(http); -} -``` - -> updateAuthenticationDefaults(); - -```java -/** - * Updates the default values for authentication. - * - * @throws Exception - */ -protected final void updateAuthenticationDefaults() { - if (this.loginProcessingUrl == null) { - loginProcessingUrl(this.loginPage); - } - if (this.failureHandler == null) { - failureUrl(this.loginPage + "?error"); - } - LogoutConfigurer logoutConfigurer = getBuilder().getConfigurer(LogoutConfigurer.class); - if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) { - logoutConfigurer.logoutSuccessUrl(this.loginPage + "?logout"); - } -} -``` - -`loginProcessingUrl(this.loginPage);` - -1. 设置登录页面 -2. `this.authFilter`也就是`UsernamePasswordAuthenticationFilter`的`RequestMatcher` - 设置为`new AntPathRequestMatcher(loginProcessingUrl, "POST")` - -`failureUrl(this.loginPage + "?error");` - -1. 设置失败页面 -2. `this.failureHandler`设置为`new SimpleUrlAuthenticationFailureHandler(authenticationFailureUrl)` - 里面的`authenticationFailureUrl`是`/login + "?error"` - -`getBuilder().getConfigurer(LogoutConfigurer.class);` 就是前面加入的那一堆`Configurer`中的一个 . 这个默认就是当前设置的值,不用理会 - -> updateAccessDefaults(http); - -里面默认为 false - -> registerDefaultAuthenticationEntryPoint(http); - -获取上面`Configurer`里面的`ExceptionHandlingConfigurer` - -在`ExceptionHandlingConfigurer`中有俩个属性, `defaultEntryPointMappings`和`defaultDeniedHandlerMappings`, 基本看注释就能知道是做什么的 -,这个注释是 map 的 value 类上的注释 - -```java -/** - * 开始一个身份验证方案。 - 在调用该方法之前, ExceptionTranslationFilter 将使用请求的目标 URL 填充 HttpSession 属性 abstractathenticationprocessingfilter.SPRING_SECURITY_SAVED_REQUEST _key。 - 实现应根据需要修改 ServletResponse 上的标头,以开始身份验证过程。 - */ -private LinkedHashMap defaultEntryPointMappings=new LinkedHashMap<>(); - -/** - * 处理拒绝访问失败。 - */ -private LinkedHashMap defaultDeniedHandlerMappings=new LinkedHashMap<>(); -``` - -我们这里的是添加`defaultDeniedHandlerMappings`, `key`是`RequestMatcher`,是否匹配。 `value`是匹配成功就执行 -这个类里面也是只有`configure()`,没有`init()`, 后面讲解 - -先说里面的`value`,就是当前类的`this.authenticationEntryPoint` 也就是创建类时候的`LoginUrlAuthenticationEntryPoint` - -`key`就是`AndRequestMatcher`,但是里面聚合了俩个`RequestMatcher`, 一个是`MediaTypeRequestMatcher` -,还有一个是`NegatedRequestMatcher` - -其实到了这一步,我们只需要了解其中一个,剩下的都大同小异了 - -举例: `ExceptionHandlingConfigurer` - -`ExceptionHandlingConfigurer`这个类就是刚刚在`FormLoginConfigurer` -中处理的那个,往这个里面添加了`defaultEntryPointMappings`属性 - -然后我们找到`ExceptionHandlingConfigurer`中的`configure(H http)`方法, 里面就是创建了一个`ExceptionTranslationFilter` -过滤器,添加到了`http`中, 代码是这一段 - -```java -@Override -public void configure(H http) { - AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); - ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, - getRequestCache(http)); - AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); - exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); - exceptionTranslationFilter = postProcess(exceptionTranslationFilter); - http.addFilter(exceptionTranslationFilter); -} -``` - -接着我们打开`ExceptionTranslationFilter`,这就是一个`Filter` -,找到`doFilter(ServletRequest request, ServletResponse response, FilterChain chain)` -方法,就是在处理`catch (Exception ex) {` -的时候,做的一些事情,接着继续打开`handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception)` -方法, - -```java -private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, - FilterChain chain, RuntimeException exception) throws IOException, ServletException { - if (exception instanceof AuthenticationException) { - handleAuthenticationException(request, response, chain, (AuthenticationException) exception); - } else if (exception instanceof AccessDeniedException) { - handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception); - } -} -``` - -发现最后还是到了 - -```java -protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, - AuthenticationException reason) throws ServletException, IOException { - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContext context = SecurityContextHolder.createEmptyContext(); - SecurityContextHolder.setContext(context); - this.requestCache.saveRequest(request, response); - this.authenticationEntryPoint.commence(request, response, reason); -} -``` - -里面就有`this.authenticationEntryPoint.commence(request, response, reason);`这段代码, -创建的是`DelegatingAuthenticationEntryPoint` - -```java -@Override -public void commence(HttpServletRequest request, HttpServletResponse response, - AuthenticationException authException) throws IOException, ServletException { - for (RequestMatcher requestMatcher : this.entryPoints.keySet()) { - logger.debug(LogMessage.format("Trying to match using %s", requestMatcher)); - if (requestMatcher.matches(request)) { - AuthenticationEntryPoint entryPoint = this.entryPoints.get(requestMatcher); - logger.debug(LogMessage.format("Match found! Executing %s", entryPoint)); - entryPoint.commence(request, response, authException); - return; - } - } - logger.debug(LogMessage.format("No match found. Using default entry point %s", this.defaultEntryPoint)); - // No EntryPoint matched, use defaultEntryPoint - this.defaultEntryPoint.commence(request, response, authException); -} -``` - -基本上流程就完成了,请求先走过滤器,然后走不同的 filter,报错就到了这一步,进行错误处理,其余都基本一致了 - -## 备注 - -剩下的一些处理基本和上面这个流程一致,还有几个注解需要注意下 - -`@CsrfToken` - -`@CurrentSecurityContext` - -`@AuthenticationPrincipal` - -这三个注解是在`WebMvcSecurityConfiguration`类进行处理的,只要启动了`@EnableWebSecurity`注解,就会启动 - -```java - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Documented -@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, - HttpSecurityConfiguration.class}) -@EnableGlobalAuthentication -@Configuration -public @interface EnableWebSecurity { - - /** - * Controls debugging support for Spring Security. Default is false. - * @return if true, enables debug support with Spring Security - */ - boolean debug() default false; - -} -``` - -使用方法 - -```java -@GetMapping("/get") -// public Users getUser(String username,@CsrfToken CsrfToken token, @AuthenticationPrincipal Users customUser, @CurrentSecurityContext Authentication authentication) { -public Users getUser(String username, @AuthenticationPrincipal Users customUser, @CurrentSecurityContext SecurityContext securityContext) { - return userInfoService.getUsers(username); -} -``` - -里面的`SpringWebMvcImportSelector`类注入了`WebMvcSecurityConfiguration`,这就是 springmvc 中`HandlerMethodArgumentResolver` -的处理,也就是参数的处理,比如我们添加的`@PathVariable`,`@RequestBody`等,都是`HandlerMethodArgumentResolver` -的实现类处理的,当然还有`HandlerMethodReturnValueHandler`,这些就是`DispatcherServlet`里面的处理了 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" index fc235f24..e69de29b 100644 --- "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" @@ -1,319 +0,0 @@ -# Spring Security 自定义用户认证 - -在**Spring Boot 中开启 Spring Security**一节中我们简单地搭建了一个 Spring Boot + Spring Security 的项目,其中登录页、用户名和密码都是由 Spring Security 自动生成的。Spring Security 支持我们自定义认证的过程,如使用自定义的登录页替换默认的登录页,用户信息的获取逻辑、登录成功或失败后的处理逻辑等。这里将在上一节的源码基础上进行改造。 - -## 配置自定义登录页 - -为了方便起见,我们直接在`src/main/resources/resources`目录下创建一个`login.html`(不需要 Controller 跳转): - -```html - - - - - 登录 - - - - - - -``` - -要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在 `BrowserSecurityConfig` 的 `configure` 中添加一些配置: - -```java -@Configuration -public class BrowserConfig extends WebSecurityConfigurerAdapter { - - @Override - protected void configure(HttpSecurity http) throws Exception { - http.formLogin() // 表单登录 - .loginPage("/login.html") // 自定义登录页 - .loginProcessingUrl("/login") // 登录认证路径 - .and() - .authorizeRequests() // 授权配置 - .antMatchers("/login.html", "/css/**", "/error").permitAll() // 无需认证 - .anyRequest().authenticated() // 其他所有请求都需要认证 - .and() - .csrf().disable(); // 禁用 CSRF - } -} -``` - -上面代码中`.loginPage("/login.html")`指定了跳转到登录页面的请求 URL,`.loginProcessingUrl("/login")`对应登录页面 form 表单的`action="/login"`,`.antMatchers("/login.html", "/css/", "/error").permitAll()`表示跳转到登录页面的请求不被拦截。 - -这时候启动系统,访问`http://localhost:8080/hello`,会看到页面已经被重定向到了`http://localhost:8080/login.html`: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg) - -## 配置用户信息的获取逻辑 - -Spring Security 默认会为我们生成一个用户名为 user,密码随机的用户实例,当然我们也可以定义自己用户信息的获取逻辑,只需要实现 Spring Security 提供的**_UserDetailService_**接口即可,该接口只有一个抽象方法**_loadUserByUsername_**,具体实现如下: - -```java -@Service -public class UserDetailService implements UserDetailsService { - @Autowired - private PasswordEncoder passwordEncoder; - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.createAuthorityList("admin")); - } -} -``` - -通过以上配置,我们定义了一个用户名随机,密码统一为 123456 的用户信息的获取逻辑。这样,当我们启动项目,访问`http://localhost:8080/login`,只需要输入任意用户名以及 123456 作为密码即可登录系统。 - -## 源码解析 - -### BrowserConfig 配置解析 - -我们首先来梳理下 `BrowserConfig` 中的配置是如何被 Spring Security 所加载的。 - -首先找到调用 `BrowserConfig` 的 `configure()` 的地方,在其父类 `WebSecurityConfigurerAdapter` 的 `getHttp()` 中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png) - -往上一步找到调用 `getHttp()` 的地方,在同个类的 `init()` 中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png) - -往上一步找到调用`init()`的地方,在 `AbstractConfiguredSecurityBuilder` 的`init()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png) - -在`init()`被调用时,它首先会遍历`getConfigurers()`返回的集合中的元素,调用其`init()`,点击`getConfigurers()`查看,发现其读取的是`configurers`属性的值,那么`configurers`是什么时候被赋值的呢?我们在同个类的`add()`中找到`configurers`被赋值的代码: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png) - -往上一步找到调用`add()`的地方,在同个类的`apply()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png) - -往上一步找到调用`apply()`的地方,在`WebSecurityConfiguration`的`setFilterChainProxySecurityConfigurer()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png) - -我们可以看到,在`setFilterChainProxySecurityConfigurer()`中,首先会实例化一个`WebSecurity`(`AbstractConfiguredSecurityBuilder`的实现类)的实例,遍历参数`webSecurityConfigurers`,将存储在其中的元素作为参数传递给`WebSecurity`的`apply()`,那么`webSecurityConfigurers`是什么时候被赋值的呢?我们根据`@Value`中的信息找到`webSecurityConfigurers`被赋值的地方,在`AutowiredWebSecurityConfigurersIgnoreParents`的`getWebSecurityConfigurers()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png) - -我们重点看第二句代码,可以看到这里会提取存储在 bean 工厂中类型为`WebSecurityConfigurer.class`的 bean,而`BrowserConfig`正是`WebSecurityConfigurerAdapter`的实现类。 - -解决完`configurers`的赋值问题,我们回到`AbstractConfiguredSecurityBuilder`的`init()`处,找到调用该方法的地方,在同个类的`doBuild()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png) - -往上一步找到调用`doBuild()`的地方,在`AbstractSecurityBuilder`的`build()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png) - -往上一步找到调用`doBuild()`的地方,在`WebSecurityConfiguration`的`springSecurityFilterChain()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png) - -至此,我们分析完了`BrowserConfig`被 Spring Security 加载的过程。现在我们再来看看当我们自定义的配置被加载完后,`http`各属性的变化,在`BrowserConfig`的`configure()`末尾打上断点,当程序走到断点处时,查看`http`属性: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png) - -我们配置的`.loginPage("/login.html")`和`.loginProcessingUrl("/login")`在`FormLoginConfigurer`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png) - -配置的`.antMatchers("/login.html", "/css/", "/error").permitAll()`在`ExpressionUrlAuthorizationConfigurer`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png) - -这样,当我们访问除`"/login.html", "/css/", "/error"`以外的路径时,在`AbstractSecurityInterceptor`(`FilterSecurityInterceptor`的父类)的`attemptAuthorization()`中会抛出`AccessDeniedException`异常(最终由`AuthenticationTrustResolverImpl`的isAnonymous()`进行判断) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png) - -当我们访问`"/login.html", "/css/", "/error"`这几个路径时,在`AbstractSecurityInterceptor`(`FilterSecurityInterceptor`的父类)的`attemptAuthorization()`中正常执行(最终由`SecurityExpressionRoot`的`permitAll()`进行判断) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png) - -### login.html 路径解析 - -当我们请求的资源需要经过认证时,Spring Security 会将请求重定向到我们自定义的登录页,那么 Spring 又是如何找到我们自定义的登录页的呢?下面就让我们来解析一下: - -我们首先来到`DispatcherServlet`中,`DispatcherServlet`是 Spring Web 处理请求的入口。当 Spring Web 项目启动后,第一次接收到请求时,会调用其`initStrategies()`进行初始化: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png) - -我们重点关注`initHandlerMappings(context);`这句,`initHandlerMappings()`用于初始化处理器映射器(处理器映射器可以根据请求找到对应的资源),我们来到`initHandlerMappings()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png) - -可以看到,当程序走到`initHandlerMappings()`中时,会从 bean 工厂中找出`HandlerMapping.class`类型的 bean,将其存储到`handlerMappings`属性中。这里看到一共找到 5 个,分别是:`requestMappingHandlerMapping`(将请求与标注了`@RequestMapping`的方法进行关联)、`weclomePageHandlerMapping`(将请求与主页进行关联)、`beanNameHandlerMapping`(将请求与同名的 bean 进行关联)、`routerFunctionMapping`(将请求与`RouterFunction`进行关联)、`resourceHandlerMapping`(将请求与静态资源进行关联),这 5 个 bean 是在`WebMvcAutoConfiguration$EnableWebMvcConfiguration`中配置的: - -`requestMappingHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png) - -`weclomePageHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png) - -`beanNameHandlerMapping`、`routerFunctionMapping`、`resourceHandlerMapping`在`EnableWebMvcConfiguration`的父类(`WebMvcConfigurationSupport`)中配置: - -`beanNameHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png) - -`routerFunctionMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png) - -`resourceHandlerMapping:` - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png) - -我们将目光锁定在`resourceHandlerMapping`上,当`resourceHandlerMapping`被初始化时,会调用`addResourceHandlers()`为`registry`添加资源处理器,我们找到实际被调用的`addResourceHandlers()`,在`DelegatingWebMvcConfiguration`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png) - -可以看到这里实际调用的是`configurers`属性的`addResourceHandlers()`,而`configurers`是一个 final 类型的成员变量,其值是`WebMvcConfigurerComposite`的实例,我们来到`WebMvcConfigurerComposite`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png) - -可以看到这里实际调用的是`delegates`属性的`addResourceHandlers()`,`delegates`是一个 final 类型的集合,集合的元素由`addWebMvcConfigurers()`负责添加: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png) - -我们找到调用`addWebMvcConfigurers()`的地方,在`DelegatingWebMvcConfiguration`的`setConfigurers()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png) - -可以看到当`setConfigurers()`被初始化时,Spring 会往参数`configurers`中传入两个值,我们关注第一个值,是一个`WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter`的实例,注意它的属性`resourceProperties`,是一个`WebProperties$Resources`的实例,默认情况下,在实例化`WebMvcAutoConfigurationAdapter`时,由传入参数`webProperties`进行赋值:`webProperties.getResources()`: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png) - -我们进入参数`webProperties`的类中,可以看到`getResources()`是直接实例化了一个`Resources`,其属性`staticLocations`是一个含有 4 个值的 final 类型的字符串数组,这 4 个值正是 Spring 寻找静态文件的地方: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png) - -我们回到`WebMvcAutoConfiguration`的`addResourceHandlers()`中:![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png) - -在`addResourceHandlers()`中,会为`registry`添加两个资源处理器,当请求路径是“/webjars/”时,会在”classpath:/META-INF/resources/webjars/“路径下寻找对应的资源,当请求路径是“/\*\*”时,会在”classpath:/META-INF/resources/“、”classpath:/resources/“、”classpath:/static/“、”classpath:/public/“路径下寻找对应的资源。 - -现在我们通过访问`http://localhost:8080/login.html`来验证这个过程。 - -请求首先来到`DispatcherServlet`的`doDispatch()`中,由于是对静态资源的请求,当程序走到`mappedHandler = getHandler(processedRequest);`时,通过`getHandler()`返回`SimpleUrlHandlerMapping`(即`resourceHandlerMapping`的类型)的`HandlerExecutionChain`: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png) - -然后由实际的处理器进行处理: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png) - -程序一路调试,来到`ResourceHttpRequestHandler`的`handleRequest()`中,通过调用`Resource resource = getResource(request);`找到请求对应的资源: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png) - -而在`getResource()`中,实际是将请求路径(即`login.html`)与前面配置的路径进行拼接(组合成`/resources/login.html`这样的路径),再通过类加载器来寻找资源。 - -后面源码深入过深,就不一一展开了,只截取其中比较重要的几段代码: - -`PathResourceResolver`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png) - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png) - -`ClassPathResource`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png) - -`ClassLoader`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png) - -`URLClassLoader`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png) - -最终,类加载器会在如上两个路径下找到登录页并返回。 - -### UserDetailService 配置解析 - -我们定义的用户信息的获取逻辑是如何被 Spring Security 应用的呢?让我们通过阅读源码来了解一下。 - -还记得前面我们讲**_BrowserConfig 配置_**被加载的过程吗?**_UserDetailService_**也是在这个过程中被一起加载完成的,回到**BrowserConfig 配置解析**的第一幅图中,如下: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png) - -在断点处位置,`authenticationManager()`会返回一个**_AuthenticationManager_**实例,我们进入`authenticationManager()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png) - -在`authenticationManager()`中,**_AuthenticationManager_**转由**_AuthenticationConfiguration_**中获取,我们进入`getAuthenticationManager()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png) - -程序来到**_AuthenticationConfiguration_**的`getAuthenticationManager()`中,**_AuthenticationManager_**转由**_AuthenticationManagerBuilder_**中获取,我们进入`build()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png) - -程序来到**_AbstractConfiguredSecurityBuilder_**的`doBuild()`中,这里在构建**_AuthenticationManager_**实例时,需要初始化 3 个配置类,我们重点关注第 3 个配置类:**_org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer_**,这个配置类是在**_AuthenticationConfiguration_**中引入的: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png) - -我们来到**_InitializeUserDetailsBeanManagerConfigurer_**的`init()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png) - -这里会新建一个**_InitializeUserDetailsManagerConfigurer_**实例添加到**_AuthenticationManagerBuilder_**中。我们回到`doBuild()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png) - -可以看到配置类变成了 5 个,其中就有刚刚新建的**_InitializeUserDetailsManagerConfigurer_**,程序接下来会调用各个配置类的`configure()`进行配置,我们来到**_InitializeUserDetailsManagerConfigurer_**的`configure()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png) - -可以看到在`configure()`中,就会去 bean 工厂中寻找**_UserDetailsService_**类型的 bean,若是我们没有自定义**_UserDetailsService_**的实现类的话,Spring Security 默认会生成一个**_InMemoryUserDetailsManager_**的实例: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png) - -**_InMemoryUserDetailsManager_**是在**_UserDetailsServiceAutoConfiguration_**类中配置的: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png) - -解决完**_UserDetailsService_**的加载问题,现在我们来看看 Spring Security 是如何通过**_UserDetailsService_**获取用户信息的。 - -通过**Spring Boot 中开启 Spring Security**一节的学习我们知道,登录判断的逻辑是在**_UsernamePasswordAuthenticationFilter_**中进行的,因此我们在**_UsernamePasswordAuthenticationFilter_**的`attemptAuthenticatio()`中打上断点,然后启动项目,访问登录页,输入用户名和密码点击登录后,程序来到**_UsernamePasswordAuthenticationFilter_**中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png) - -这里将验证的逻辑交由**_AuthenticationManager_**进行,我们进入`authenticate()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png) - -程序来到**_ProviderManager_**的`authenticate()`中,这里将验证的逻辑委托给其父类进行,再次点击进入`authenticate()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png) - -这里将验证的逻辑交由**_AuthenticationProvider_**进行,我们进入`authenticate()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png) - -程序来到**_AbstractUserDetailsAuthenticationProvider_**的`authenticate()`中,这里会根据用户名去寻找对应的用户实例,我们进入`retrieveUser()`中: - -![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png) - -程序来到**_DaoAuthenticationProvider_**的`retrieveUser()`中,可以看到正是在这里,会从**_UserDetailsService_**的`loadUserByUsername()`中寻找对应的用户信息。 - -## 参考 - -1. [Spring Security 自定义用户认证](https://mrbird.cc/Spring-Security-Authentication.html) 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" index 4d28186a..c7d0f160 100644 --- "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" @@ -34,7 +34,7 @@ public class HelloController { 这时候我们直接启动项目,访问http://localhost:8080/hello,可以看到页面跳转到一个登陆页面: -![image-20210811091508157](../../images/SpringSecurity/image-20210811091508157.png) +![image-20210811091508157](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091508157.png) 默认的用户名为 user,密码由 Sping Security 自动生成,回到 IDEA 的控制台,可以找到密码信息: @@ -50,11 +50,11 @@ Spring Security 默认为我们开启了一个简单的安全配置,下面让 当 Spring Boot 项目配置了 Spring Security 后,Spring Security 的整个加载过程如下图所示: -![image-20210811091633434](../../images/SpringSecurity/image-20210811091633434.png) +![image-20210811091633434](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091633434.png) 而当我们访问http://localhost:8080/hello时,代码的整个执行过程如下图所示: -![image-20210811091659121](../../images/SpringSecurity/image-20210811091659121.png) +![image-20210811091659121](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091659121.png) 如上图所示,Spring Security 包含了众多的过滤器,这些过滤器形成了一条链,所有请求都必须通过这些过滤器后才能成功访问到资源。 @@ -62,69 +62,69 @@ Spring Security 默认为我们开启了一个简单的安全配置,下面让 首先,通过前面可以知道,当有请求来到时,最先由**_DelegatingFilterProxy_**负责接收,因此在**_DelegatingFilterProxy_**的doFilter()的首行打上断点: -![image-20210811091719470](../../images/SpringSecurity/image-20210811091719470.png) +![image-20210811091719470](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091719470.png) 接着**_DelegatingFilterProxy_**会将请求委派给**_FilterChainProxy_**进行处理,在**_FilterChainProxy_**的首行打上断点: -![img](../../images/SpringSecurity/56ac5128-eab7-4b92-912f-ff50bac68a4f.png) +![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](../../images/SpringSecurity/image-20210811091755498.png) +![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](../../images/SpringSecurity/image-20210811091815473.png) +![image-20210811091815473](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091815473.png) 准备完毕后,我们启动项目,然后访问http://localhost:8080/hello,程序首先跳转到**_DelegatingFilterProxy_**的断点上: -![image-20210811091833065](../../images/SpringSecurity/image-20210811091833065.png) +![image-20210811091833065](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091833065.png) 此时**_delegate_**还是 null 的,接下来依次执行代码,可以看到**_delegate_**最终被赋值一个**_FilterChainProxy_**的实例: -![img](../../images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/f045b025-bd97-4222-8a02-51634be6745b.png) 接下来程序依次跳转到**_FilterChainProxy_**的doFilter()和**_VirtualFilterChain_**的doFilter()中: -![img](../../images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png) +![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/90d3e369-510f-45cb-982d-241d2eedb55c.png) -![image-20210811092048784](../../images/SpringSecurity/image-20210811092048784.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](../../images/SpringSecurity/2e5440bc-9488-4213-a030-0d25153bb2ea.png) +![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](../../images/SpringSecurity/47a7bca4-d858-4cb1-b126-347805b74053.png) +![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](../../images/SpringSecurity/6b1aded6-5229-47ba-b192-78a7c2622b8c.png) +![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](../../images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png)** +**![img](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/8efa0b1c-2b32-4d5b-9655-985374326e10.png)** 接着程序跳转到**_FilterSecurityInterceptor_**的doFilter()中,依次执行代码后程序停留在其父类(**_AbstractSecurityInterceptor_**)的attemptAuthorization()中: -![img](../../images/SpringSecurity/d6e99143-6207-43a5-8d04-f0c81baa11b4.png) +![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](../../images/SpringSecurity/6724647c-34ee-4a57-8cfa-b46f57400d14.png) +![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](../../images/SpringSecurity/4beaa02f-a93d-4d95-9ad1-0d7213cb0e46.png) +![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](../../images/SpringSecurity/8e1ac9db-5987-484d-abf4-4c6535c60cc6.png) +![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](../../images/SpringSecurity/1b03bdd4-6773-4b39-a664-fdf65d104403.png) +![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_**,也就是我们开头看到的登录页面。 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/nacos/nacos-discovery.md b/docs/nacos/nacos-discovery.md index 797560ea..e72a14d1 100644 --- a/docs/nacos/nacos-discovery.md +++ b/docs/nacos/nacos-discovery.md @@ -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 对象