refactor: build site with vitepress (#177)

main
Libin YANG 5 months ago committed by GitHub
parent e9b6da5a0b
commit 1004a323dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,52 @@
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
env:
SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
with:
source-repo: git@github.com:doocs/source-code-hunter.git
destination-repo: git@gitee.com:Doocs/source-code-hunter.git

@ -1,26 +0,0 @@
name: Sync
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
if: github.repository == 'doocs/source-code-hunter'
steps:
- name: Sync to Gitee
uses: wearerequired/git-mirror-action@master
env:
SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
with:
source-repo: git@github.com:doocs/source-code-hunter.git
destination-repo: git@gitee.com:Doocs/source-code-hunter.git
# - name: Build Gitee Pages
# uses: yanglbme/gitee-pages-action@main
# with:
# gitee-username: yanglbme
# gitee-password: ${{ secrets.GITEE_PASSWORD }}
# gitee-repo: doocs/source-code-hunter
# branch: main

5
.gitignore vendored

@ -33,4 +33,7 @@ local.properties
gradle.properties gradle.properties
*.gh *.gh
.vscode .vscode
/node_modules /node_modules
docs/.vitepress/dist
docs/.vitepress/cache

@ -1 +0,0 @@
schunter.doocs.org

@ -118,10 +118,6 @@
- [SpringBoot 日志系统](/docs/SpringBoot/SpringBoot-LogSystem.md) - [SpringBoot 日志系统](/docs/SpringBoot/SpringBoot-LogSystem.md)
- [SpringBoot ConditionalOnBean](/docs/SpringBoot/SpringBoot-ConditionalOnBean.md) - [SpringBoot ConditionalOnBean](/docs/SpringBoot/SpringBoot-ConditionalOnBean.md)
### SpringBootBatch
- [SpringBootBatch 源码](/docs/SpringBootBatch/SpringBootBatch源码.md)
### Spring Cloud ### Spring Cloud
- [Spring Cloud Commons 源码](docs/SpringCloud/spring-cloud-commons-source-note.md) - [Spring Cloud Commons 源码](docs/SpringCloud/spring-cloud-commons-source-note.md)
@ -332,10 +328,6 @@
- [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md) - [初级开发者应该从 Spring 源码中学什么](docs/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么.md)
### 编码规范
- [一个程序员的自我修养](docs/LearningExperience/EncodingSpecification/一个程序员的自我修养.md)
### 设计模式 ### 设计模式
- [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)](<docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md>) - [从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)](<docs/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型).md>)

@ -0,0 +1,519 @@
import { defineConfig } from 'vitepress'
export default defineConfig({
title: "Source Code Hunter",
description: "读尽天下源码,心中自然无码——源码猎人",
head: [
['meta', { name: 'keywords', content: 'doc,docs,doocs,documentation,github,gitee,source-code-hunter' }],
['meta', { name: 'description', content: '读尽天下源码,心中自然无码——源码猎人' }],
['link', { rel: 'icon', type: 'image/png', href: 'https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/favicon-32x32.png' }]
],
ignoreDeadLinks: true,
themeConfig: {
search: {
provider: 'local'
},
footer: {
message: 'Released under the CC-BY-SA-4.0 license.',
copyright: `Copyright © 2018-${new Date().getFullYear()} <a href="https://github.com/doocs">Doocs</a>`
},
logo: 'https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/favicon-32x32.png',
docFooter: {
prev: '上一篇',
next: '下一篇'
},
editLink: {
pattern: 'https://github.com/doocs/source-code-hunter/edit/main/docs/:path',
text: '在 GitHub 编辑'
},
nav: [
{ text: '首页', link: '/' },
{ text: 'Spring系列', link: '/Spring/IoC/1、BeanDefinition的资源定位过程' },
{ text: 'Mybatis', link: '/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列' },
{ text: 'Netty', link: '/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍' },
{ text: 'Dubbo', link: '/Dubbo/architectureDesign/Dubbo整体架构' },
{ text: 'Tomcat', link: '/Tomcat/servlet-api源码赏析' },
{ text: 'Redis', link: '/Redis/redis-sds' },
{ text: 'JDK 1.8', link: '/JDK/basic/String' },
{ text: '学习心得', link: '/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么' }
],
sidebar: [
{
text: 'Spring 系列',
collapsed: true,
items: [
{
text: 'IoC 容器',
collapsed: true,
items: [
{ text: 'BeanDefinition 的资源定位过程', link: '/Spring/IoC/1、BeanDefinition的资源定位过程' },
{ text: '将 bean 解析封装成 BeanDefinition', link: '/Spring/IoC/2、将bean解析封装成BeanDefinition' },
{ text: '将 BeanDefinition 注册进 IoC 容器', link: '/Spring/IoC/3、将BeanDefinition注册进IoC容器' },
{ text: '依赖注入(DI)', link: '/Spring/IoC/4、依赖注入(DI)' },
{ text: 'BeanFactoryPostProcessor', link: '/Spring/IoC/BeanFactoryPostProcessor' },
{ text: 'BeanPostProcessor', link: '/Spring/IoC/BeanPostProcessor' },
{ text: 'Spring BeanFactory 源码解析', link: '/Spring/clazz/Spring-beanFactory' },
{ text: '循环依赖', link: '/Spring/IoC/循环依赖' },
],
},
{
text: 'AOP',
collapsed: true,
items: [
{ text: 'AOP 源码实现及分析', link: '/Spring/AOP/AOP源码实现及分析' },
{ text: 'JDK 动态代理的实现原理解析', link: '/Spring/AOP/JDK动态代理的实现原理解析' },
{ text: 'Spring AOP 如何生效', link: '/Spring/AOP/Spring-Aop如何生效' },
],
},
{
text: 'SpringMVC',
collapsed: true,
items: [
{ text: 'IoC 容器在 Web 环境中的启动', link: '/Spring/SpringMVC/IoC容器在Web环境中的启动' },
{ text: 'SpringMVC 的设计与实现', link: '/Spring/SpringMVC/SpringMVC的设计与实现' },
{ text: 'SpringMVC 跨域解析', link: '/Spring/SpringMVC/SpringMVC-CROS' },
{ text: 'Spring-MVC-HandlerMapping', link: '/Spring/mvc/Spring-MVC-HandlerMapping' },
{ text: 'Spring-mvc-MappingRegistry', link: '/Spring/mvc/Spring-mvc-MappingRegistry' },
],
},
{
text: 'SpringJDBC',
collapsed: true,
items: [
{ text: '努力编写中...', link: '' },
],
},
{
text: 'Spring 事务',
collapsed: true,
items: [
{ text: 'Spring 与事务处理', link: '/Spring/SpringTransaction/Spring与事务处理' },
{ text: 'Spring 声明式事务处理', link: '/Spring/SpringTransaction/Spring声明式事务处理' },
{ text: 'Spring 事务处理的设计与实现', link: '/Spring/SpringTransaction/Spring事务处理的设计与实现' },
{ text: 'Spring 事务管理器的设计与实现', link: '/Spring/SpringTransaction/Spring事务管理器的设计与实现' },
{ text: 'Spring 事务解析', link: '/Spring/TX/Spring-transaction' },
],
},
{
text: 'Spring 源码故事(瞎编版)',
collapsed: true,
items: [
{ text: '面筋哥 IoC 容器的一天(上)', link: '/Spring/Spring源码故事瞎编版/面筋哥IoC容器的一天(上)' },
],
},
{
text: 'Spring 整体脉络',
collapsed: true,
items: [
{ text: '16 张图解锁 Spring 的整体脉络', link: '/Spring/Spring整体脉络/16张图解锁Spring的整体脉络' },
],
},
{
text: 'Spring 类解析',
collapsed: true,
items: [
{ text: 'Spring 自定义标签解析', link: '/Spring/clazz/Spring-Custom-label-resolution' },
{ text: 'Spring Scan 包扫描', link: '/Spring/clazz/Spring-scan' },
{ text: 'Spring 注解工具类', link: '/Spring/clazz/Spring-AnnotationUtils' },
{ text: 'Spring 别名注册', link: '/Spring/clazz/Spring-SimpleAliasRegistry' },
{ text: 'Spring 标签解析类', link: '/Spring/clazz/Spring-BeanDefinitionParserDelegate' },
{ text: 'Spring ApplicationListener', link: '/Spring/clazz/Spring-ApplicationListener' },
{ text: 'Spring messageSource', link: '/Spring/clazz/Spring-MessageSource' },
{ text: 'Spring 自定义属性解析器', link: '/Spring/clazz/Spring-Custom-attribute-resolver' },
{ text: 'Spring 排序工具', link: '/Spring/clazz/Spring-OrderUtils' },
{ text: 'Spring-import 注解', link: '/Spring/clazz/Spring-Import' },
{ text: 'Spring-定时任务', link: '/Spring/clazz/Spring-Scheduling' },
{ text: 'Spring StopWatch', link: '/Spring/clazz/Spring-StopWatch' },
{ text: 'Spring 元数据', link: '/Spring/clazz/Spring-Metadata' },
{ text: 'Spring 条件接口', link: '/Spring/clazz/Spring-Conditional' },
{ text: 'Spring MultiValueMap', link: '/Spring/clazz/Spring-MultiValueMap' },
{ text: 'Spring MethodOverride', link: '/Spring/clazz/Spring-MethodOverride' },
{ text: 'Spring BeanDefinitionReaderUtils', link: '/Spring/clazz/Spring-BeanDefinitionReaderUtils' },
{ text: 'Spring PropertyPlaceholderHelper', link: '/Spring/clazz/Spring-PropertyPlaceholderHelper' },
{ text: 'Spring PropertySources', link: '/Spring/clazz/Spring-PropertySources' },
{ text: 'Spring-AnnotationFormatterFactory', link: '/Spring/clazz/format/Spring-AnnotationFormatterFactory' },
{ text: 'Spring-Formatter', link: '/Spring/clazz/format/Spring-Formatter' },
{ text: 'Spring-Parser', link: '/Spring/clazz/format/Spring-Parser' },
{ text: 'Spring-Printer', link: '/Spring/clazz/format/Spring-Printer' },
],
},
{
text: 'Spring5 新特性',
collapsed: true,
items: [
{ text: 'Spring5-spring.components 解析', link: '/Spring/Spring5新特性/Spring-spring-components' },
],
},
{
text: 'Spring RMI',
collapsed: true,
items: [
{ text: 'Spring RMI', link: '/Spring/RMI/Spring-RMI' },
],
},
{
text: 'Spring Message',
collapsed: true,
items: [
{ text: 'Spring EnableJMS', link: '/Spring/message/Spring-EnableJms' },
{ text: 'Spring JmsTemplate', link: '/Spring/message/Spring-JmsTemplate' },
{ text: 'Spring MessageConverter', link: '/Spring/message/Spring-MessageConverter' },
],
},
{
text: 'SpringBoot',
collapsed: true,
items: [
{ text: 'SpringBoot run 方法解析', link: '/SpringBoot/Spring-Boot-Run' },
{ text: 'SpringBoot 配置加载解析', link: '/SpringBoot/SpringBoot-application-load' },
{ text: 'SpringBoot 自动装配', link: '/SpringBoot/SpringBoot-自动装配' },
{ text: 'SpringBoot ConfigurationProperties', link: '/SpringBoot/SpringBoot-ConfigurationProperties' },
{ text: 'SpringBoot 日志系统', link: '/SpringBoot/SpringBoot-LogSystem' },
{ text: 'SpringBoot ConditionalOnBean', link: '/SpringBoot/SpringBoot-ConditionalOnBean' },
],
},
{
text: 'Spring Cloud',
collapsed: true,
items: [
{ text: 'Spring Cloud Commons 源码', link: '/SpringCloud/spring-cloud-commons-source-note' },
{ text: 'Spring Cloud OpenFeign 源码', link: '/SpringCloud/spring-cloud-openfeign-source-note' },
{ text: 'Spring Cloud Gateway 源码', link: '/SpringCloud/spring-cloud-gateway-source-note' },
],
},
{
text: 'SpringSecurity',
collapsed: true,
items: [
{ text: 'SpringSecurity 请求全过程解析', link: '/SpringSecurity/SpringSecurity请求全过程解析' },
// { text: 'SpringSecurity 自定义用户认证', link: '/SpringSecurity/SpringSecurity自定义用户认证' },
// { text: 'SpringSecurity 流程补充', link: '/SpringSecurity/SpringSecurity流程补充' },
],
},
],
},
{
text: 'MyBatis',
collapsed: true,
items: [
{
text: '基础支持层',
collapsed: true,
items: [
{ text: '反射工具箱和 TypeHandler 系列', link: '/Mybatis/基础支持层/1、反射工具箱和TypeHandler系列' },
{ text: 'DataSource 及 Transaction 模块', link: '/Mybatis/基础支持层/2、DataSource及Transaction模块' },
{ text: 'binding 模块', link: '/Mybatis/基础支持层/3、binding模块' },
{ text: '缓存模块', link: '/Mybatis/基础支持层/4、缓存模块' },
],
},
{
text: '核心处理层',
collapsed: true,
items: [
{ text: 'MyBatis 初始化', link: '/Mybatis/核心处理层/1、MyBatis初始化' },
{ text: 'SqlNode 和 SqlSource', link: '/Mybatis/核心处理层/2、SqlNode和SqlSource' },
{ text: 'ResultSetHandler', link: '/Mybatis/核心处理层/3、ResultSetHandler' },
{ text: 'StatementHandler', link: '/Mybatis/核心处理层/4、StatementHandler' },
{ text: 'Executor 组件', link: '/Mybatis/核心处理层/5、Executor组件' },
{ text: 'SqlSession 组件', link: '/Mybatis/核心处理层/6、SqlSession组件' },
],
},
{
text: '类解析',
collapsed: true,
items: [
{ text: 'Mybatis-Cache', link: '/Mybatis/基础支持层/Mybatis-Cache' },
{ text: 'Mybatis-log', link: '/Mybatis/基础支持层/Mybatis-log' },
{ text: 'Mybatis-Reflector', link: '/Mybatis/基础支持层/Mybatis-Reflector' },
{ text: 'Mybatis-Alias', link: '/Mybatis/核心处理层/Mybatis-Alias' },
{ text: 'Mybatis-Cursor', link: '/Mybatis/核心处理层/Mybatis-Cursor' },
{ text: 'Mybatis-DataSource', link: '/Mybatis/核心处理层/Mybatis-DataSource' },
{ text: 'Mybatis-DynamicSqlSource', link: '/Mybatis/核心处理层/Mybatis-DynamicSqlSource' },
{ text: 'Mybatis-MapperMethod', link: '/Mybatis/核心处理层/Mybatis-MapperMethod' },
{ text: 'Mybatis-MetaObject', link: '/Mybatis/核心处理层/Mybatis-MetaObject' },
{ text: 'Mybatis-MethodSignature', link: '/Mybatis/核心处理层/Mybatis-MethodSignature' },
{ text: 'Mybatis-ObjectWrapper', link: '/Mybatis/核心处理层/Mybatis-ObjectWrapper' },
{ text: 'Mybatis-ParamNameResolver', link: '/Mybatis/核心处理层/Mybatis-ParamNameResolver' },
{ text: 'Mybatis-SqlCommand', link: '/Mybatis/核心处理层/Mybatis-SqlCommand' },
{ text: 'Mybatis-GenericTokenParser', link: '/Mybatis/核心处理层/Mybatis-GenericTokenParser' },
],
},
],
},
{
text: 'Netty',
collapsed: true,
items: [
{
text: '网络 IO 技术基础',
collapsed: true,
items: [
{ text: '把被说烂的 BIO、NIO、AIO 再从头到尾扯一遍', link: '/Netty/IOTechnologyBase/把被说烂的BIO、NIO、AIO再从头到尾扯一遍' },
{ text: 'IO 模型', link: '/Netty/IOTechnologyBase/IO模型' },
{ text: '四种 IO 编程及对比', link: '/Netty/IOTechnologyBase/四种IO编程及对比' },
],
},
{
text: 'JDK1.8 NIO 包 核心组件源码剖析',
collapsed: true,
items: [
{ text: 'Selector、SelectionKey 及 Channel 组件', link: '/Netty/IOTechnologyBase/Selector、SelectionKey及Channel组件' },
],
},
{
text: 'Netty 粘拆包及解决方案',
collapsed: true,
items: [
{ text: 'TCP 粘拆包问题及 Netty 中的解决方案', link: '/Netty/TCP粘拆包/TCP粘拆包问题及Netty中的解决方案' },
],
},
{
text: 'Netty 多协议开发',
collapsed: true,
items: [
{ text: '基于 HTTP 协议的 Netty 开发', link: '/Netty/Netty多协议开发/基于HTTP协议的Netty开发' },
{ text: '基于 WebSocket 协议的 Netty 开发', link: '/Netty/Netty多协议开发/基于WebSocket协议的Netty开发' },
{ text: '基于自定义协议的 Netty 开发', link: '/Netty/Netty多协议开发/基于自定义协议的Netty开发' },
],
},
{
text: '基于 Netty 开发服务端及客户端',
collapsed: true,
items: [
{ text: '基于 Netty 的服务端开发', link: '/Netty/基于Netty开发服务端及客户端/基于Netty的服务端开发' },
{ text: '基于 Netty 的客户端开发', link: '/Netty/基于Netty开发服务端及客户端/基于Netty的客户端开发' },
],
},
{
text: 'Netty 主要组件的源码分析',
collapsed: true,
items: [
{ text: 'ByteBuf 组件', link: '/Netty/Netty主要组件源码分析/ByteBuf组件' },
{ text: 'Channel 组件 和 Unsafe 组件', link: '/Netty/Netty主要组件源码分析/Channel和Unsafe组件' },
{ text: 'EventLoop 组件', link: '/Netty/Netty主要组件源码分析/EventLoop组件' },
{ text: 'ChannelPipeline 和 ChannelHandler 组件', link: '/Netty/Netty主要组件源码分析/ChannelPipeline和ChannelHandler组件' },
{ text: 'Future 和 Promise 组件', link: '/Netty/Netty主要组件源码分析/Future和Promise组件' },
],
},
{
text: 'Netty 高级特性',
collapsed: true,
items: [
{ text: 'Netty 架构设计', link: '/Netty/AdvancedFeaturesOfNetty/Netty架构设计' },
{ text: 'Netty 高性能之道', link: '/Netty/AdvancedFeaturesOfNetty/Netty高性能之道' },
],
},
{
text: 'Netty 技术细节源码分析',
collapsed: true,
items: [
{ text: 'FastThreadLocal 源码分析', link: '/Netty/Netty技术细节源码分析/FastThreadLocal源码分析' },
{ text: 'Recycler 对象池原理分析', link: '/Netty/Netty技术细节源码分析/Recycler对象池原理分析' },
{ text: 'MpscLinkedQueue 队列原理分析', link: '/Netty/Netty技术细节源码分析/MpscLinkedQueue队列原理分析' },
{ text: 'HashedWheelTimer 时间轮原理分析', link: '/Netty/Netty技术细节源码分析/HashedWheelTimer时间轮原理分析' },
{ text: 'HashedWheelTimer & schedule', link: '/Netty/Netty技术细节源码分析/HashedWheelTimer&schedule' },
{ text: 'ByteBuf 的内存泄漏原因与检测原理', link: '/Netty/Netty技术细节源码分析/ByteBuf的内存泄漏原因与检测原理' },
{ text: '内存池之 PoolChunk 设计与实现', link: '/Netty/Netty技术细节源码分析/内存池之PoolChunk设计与实现' },
{ text: '内存池之从内存池申请内存', link: '/Netty/Netty技术细节源码分析/内存池之从内存池申请内存' },
],
},
],
},
{
text: 'Dubbo',
collapsed: true,
items: [
{
text: '架构设计',
collapsed: true,
items: [
{ text: 'Dubbo 整体架构', link: '/Dubbo/architectureDesign/Dubbo整体架构' },
],
},
{
text: 'SPI 机制',
collapsed: true,
items: [
{ text: 'Dubbo 与 Java 的 SPI 机制', link: '/Dubbo/SPI/Dubbo与Java的SPI机制' },
],
},
{
text: '注册中心',
collapsed: true,
items: [
{ text: 'Dubbo 注册中心模块简析', link: '/Dubbo/registry/Dubbo注册中心模块简析' },
{ text: '注册中心的 Zookeeper 实现', link: '/Dubbo/registry/注册中心的Zookeeper实现' },
],
},
{
text: '远程通信',
collapsed: true,
items: [
{ text: 'Dubbo 远程通信模块简析', link: '/Dubbo/remote/Dubbo远程通信模块简析' },
{ text: 'Transport 组件', link: '/Dubbo/remote/Transport组件' },
{ text: 'Exchange 组件', link: '/Dubbo/remote/Exchange组件' },
{ text: 'Buffer 组件', link: '/Dubbo/remote/Buffer组件' },
{ text: '基于 Netty 实现远程通信', link: '/Dubbo/remote/基于Netty实现远程通信' },
{ text: '基于 HTTP 实现远程通信', link: '/Dubbo/remote/基于HTTP实现远程通信' },
],
},
{
text: 'RPC',
collapsed: true,
items: [
{ text: 'RPC 模块简析', link: '/Dubbo/RPC/RPC模块简析' },
{ text: 'Protocol 组件', link: '/Dubbo/RPC/Protocol组件' },
{ text: 'Proxy 组件', link: '/Dubbo/RPC/Proxy组件' },
{ text: 'Dubbo 协议', link: '/Dubbo/RPC/Dubbo协议' },
{ text: 'Hessian 协议', link: '/Dubbo/RPC/Hessian协议' },
],
},
{
text: '集群',
collapsed: true,
items: [
{ text: 'Dubbo 集群模块简析', link: '/Dubbo/cluster/Dubbo集群模块简析' },
{ text: '负载均衡', link: '/Dubbo/cluster/负载均衡' },
{ text: '集群容错', link: '/Dubbo/cluster/集群容错' },
{ text: 'mock 与服务降级', link: '/Dubbo/cluster/mock与服务降级' },
],
},
],
},
{
text: 'Tomcat',
collapsed: true,
items: [
{
text: 'Servlet 与 Servlet 容器',
collapsed: true,
items: [
{ text: 'servlet-api 源码赏析', link: '/Tomcat/servlet-api源码赏析' },
{ text: '一个简单的 Servlet 容器', link: '/Tomcat/一个简单的servlet容器代码设计' },
{ text: 'Servlet 容器详解', link: '/Tomcat/servlet容器详解' },
],
},
{
text: 'Web 容器',
collapsed: true,
items: [
{ text: '一个简单的 Web 服务器', link: '/Tomcat/一个简单的Web服务器代码设计' },
],
},
],
},
{
text: 'Redis',
collapsed: true,
items: [
{ text: '深挖 Redis 6.0 源码——SDS', link: '/Redis/redis-sds' },
],
},
{
text: 'Nacos',
collapsed: true,
items: [
{ text: 'nacos 服务注册', link: '/nacos/nacos-discovery' },
],
},
{
text: 'Sentinel',
collapsed: true,
items: [
{ text: 'sentinel 时间窗口实现', link: '/Sentinel/Sentinel时间窗口的实现' },
{ text: 'Sentinel 底层 LongAdder 的计数实现', link: '/Sentinel/Sentinel底层LongAdder的计数实现' },
{ text: 'Sentinel 限流算法的实现', link: '/Sentinel/Sentinel限流算法的实现' },
],
},
{
text: 'RocketMQ',
collapsed: true,
items: [
{ text: 'RocketMQ NameServer 与 Broker 的通信', link: '/rocketmq/rocketmq-nameserver-broker' },
{ text: 'RocketMQ 生产者启动流程', link: '/rocketmq/rocketmq-producer-start' },
{ text: 'RocketMQ 消息发送流程', link: '/rocketmq/rocketmq-send-message' },
{ text: 'RocketMQ 消息发送存储流程', link: '/rocketmq/rocketmq-send-store' },
{ text: 'RocketMQ MappedFile 内存映射文件详解', link: '/rocketmq/rocketmq-mappedfile-detail' },
{ text: 'RocketMQ ConsumeQueue 详解', link: '/rocketmq/rocketmq-consumequeue' },
{ text: 'RocketMQ CommitLog 详解', link: '/rocketmq/rocketmq-commitlog' },
{ text: 'RocketMQ IndexFile 详解', link: '/rocketmq/rocketmq-indexfile' },
{ text: 'RocketMQ 消费者启动流程', link: '/rocketmq/rocketmq-consumer-start' },
{ text: 'RocketMQ 消息拉取流程', link: '/rocketmq/rocketmq-pullmessage' },
{ text: 'RocketMQ Broker 处理拉取消息请求流程', link: '/rocketmq/rocketmq-pullmessage-processor' },
{ text: 'RocketMQ 消息消费流程', link: '/rocketmq/rocketmq-consume-message-process' },
],
},
{
text: '番外篇JDK 1.8',
collapsed: true,
items: [
{
text: '基础类库',
collapsed: true,
items: [
{ text: 'String 类 源码赏析', link: '/JDK/basic/String' },
{ text: 'Thread 类 源码赏析', link: '/JDK/basic/Thread' },
{ text: 'ThreadLocal 类 源码赏析', link: '/JDK/basic/ThreadLocal' },
],
},
{
text: '集合',
collapsed: true,
items: [
{ text: 'HashMap 类 源码赏析', link: '/JDK/collection/HashMap' },
{ text: 'ConcurrentHashMap 类 源码赏析', link: '/JDK/collection/ConcurrentHashMap' },
{ text: 'LinkedHashMap 类 源码赏析', link: '/JDK/collection/LinkedHashMap' },
{ text: 'ArrayList 类 源码赏析', link: '/JDK/collection/ArrayList' },
{ text: 'LinkedList 类 源码赏析', link: '/JDK/collection/LinkedList' },
{ text: 'HashSet 类 源码赏析', link: '/JDK/collection/HashSet' },
{ text: 'TreeSet 类 源码赏析', link: '/JDK/collection/TreeSet' },
],
},
{
text: '并发编程',
collapsed: true,
items: [
{ text: 'JUC 并发包 UML 全量类图', link: '/JDK/concurrentCoding/JUC并发包UML全量类图' },
{ text: 'Executor 线程池组件 源码赏析', link: '/JDK/concurrentCoding/Executor线程池组件' },
{ text: 'Lock 锁组件 源码赏析', link: '/JDK/concurrentCoding/Lock锁组件' },
{ text: '详解 AbstractQueuedSynchronizer 抽象类', link: '/JDK/concurrentCoding/详解AbstractQueuedSynchronizer' },
{ text: 'Semaphore 类 源码赏析', link: '/JDK/concurrentCoding/Semaphore' },
],
},
],
},
{
text: '学习心得',
collapsed: true,
items: [
{
text: '个人经验',
collapsed: true,
items: [
{ text: '初级开发者应该从 Spring 源码中学什么', link: '/LearningExperience/PersonalExperience/初级开发者应该从spring源码中学什么' },
],
},
{
text: '设计模式',
collapsed: true,
items: [
{ text: '从 Spring 及 Mybatis 框架源码中学习设计模式(创建型)', link: '/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(创建型)' },
{ text: '从 Spring 及 Mybatis 框架源码中学习设计模式(行为型)', link: '/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(行为型)' },
{ text: '从 Spring 及 Mybatis 框架源码中学习设计模式(结构型)', link: '/LearningExperience/DesignPattern/从Spring及Mybatis框架源码中学习设计模式(结构型)' },
],
},
{
text: '多线程',
collapsed: true,
items: [
{ text: 'Java 并发编程在各主流框架中的应用', link: '/LearningExperience/ConcurrentProgramming/Java并发编程在各主流框架中的应用' },
],
},
],
},
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/doocs/source-code-hunter' }
],
}
})

@ -14,7 +14,7 @@ dubbo 自己实现了一套 SPI 机制,并对 JDK 的 SPI 进行了改进。
下面我们看一下 Dubbo 的 SPI 扩展机制实现的结构目录。 下面我们看一下 Dubbo 的 SPI 扩展机制实现的结构目录。
![avatar](../../../images/Dubbo/SPI组件目录结构.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/SPI组件目录结构.png)
### SPI 注解 ### SPI 注解

@ -2,7 +2,7 @@
首先从 GitHub 上 clone 下来 Dubbo 项目,我们根据其中各子项目的项目名,也能大概猜出来各个模块的作用。 首先从 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 ### 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)
### 工作原理 ### 工作原理

@ -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 源码解析
从上图应该也能看出其核心 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)
各核心接口的源码如下。 各核心接口的源码如下。

@ -309,15 +309,15 @@ public class LeastActiveLoadBalance extends AbstractLoadBalance {
大致效果如下图所示(引用一下官网的图)。每个缓存节点在圆环上占据一个位置,如果缓存项 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项,这里有两个概念不要弄混,缓存节点就好比 dubbo 中的服务提供者,会有很多的服务提供者,而缓存项就好比是服务引用的消费者。比如下面绿色点对应的缓存项也就是服务消费者将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项也就是服务消费者最终会存储到 cache-4 节点中,也就是调用 cache-4 这个服务提供者。 大致效果如下图所示(引用一下官网的图)。每个缓存节点在圆环上占据一个位置,如果缓存项 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 值低的一直往高的存储,会面临一个不平衡的现象,见下图: 但 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 值 排列,如下图。 可以看到最后会变成类似不平衡的现象,那我们应该怎么避免这样的事情,做到平衡性,那就需要引入 “虚拟节点”,“虚拟节点” 是实际节点在 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)
可以看到各个节点都被均匀分布在圆环上,且一个服务提供者有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。 可以看到各个节点都被均匀分布在圆环上,且一个服务提供者有多个节点存在,分别跟其他节点交错排列,这样做的目的就是避免数据倾斜问题,也就是由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。类似第二张图的情况。

@ -2,23 +2,23 @@
服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者 Provider 会往注册中心注册服务,而消费者 Consumer 会从注册中心中订阅自己关注的服务,并在关注的服务发生变更时 得到注册中心的通知。Provider、Consumer 以及 Registry 之间的依赖关系 如下图所示。 服务治理框架可以大致分为 服务通信 和 服务管理 两部分,服务管理可以分为服务注册、服务订阅以及服务发现,服务提供者 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-registry 模块 结构分析
dubbo 的注册中心有多种实现方案zookeeper、redis、multicast 等,本章先看一下 dubbo-registry 模块的核心部分 dubbo-registry-api具体实现部分放到下章来讲。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 核心组件类图 ### Registry 核心组件类图
典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。 典型的 接口 -> 抽象类 -> 实现类 的结构设计,如下图所示。
![avatar](../../../images/Dubbo/Registry组件类图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Registry组件类图.png)
既然有 Registry 组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry 实例的 RegistryFactory其中用到了工厂方法模式不同的工厂类用于获取不同类型的实例。其类图结构如下。 既然有 Registry 组件,那么按照很多框架的套路,肯定也有一个用于获取 Registry 实例的 RegistryFactory其中用到了工厂方法模式不同的工厂类用于获取不同类型的实例。其类图结构如下。
![avatar](../../../images/Dubbo/RegistryFactory组件类图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/RegistryFactory组件类图.png)
## 源码详解 ## 源码详解

@ -2,7 +2,7 @@ Dubbo 的注册中心虽然提供了多种实现,但生产上的事实标准
由于 Dubbo 是一个分布式 RPC 开源框架,各服务之间单独部署,往往会出现资源之间数据不一致的问题,比如:某一个服务增加或减少了几台机器,某个服务提供者变更了服务地址,那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo 服务是如何被 Zookeeper 的数据结构存储管理的呢zookeeper 采用的是树形结构来组织数据节点,它类似于一个标准的文件系统,如下图所示。 由于 Dubbo 是一个分布式 RPC 开源框架,各服务之间单独部署,往往会出现资源之间数据不一致的问题,比如:某一个服务增加或减少了几台机器,某个服务提供者变更了服务地址,那么服务消费者是很难感知到这种变化的。而 Zookeeper 本身就有保证分布式数据一致性的特性。那么 Dubbo 服务是如何被 Zookeeper 的数据结构存储管理的呢zookeeper 采用的是树形结构来组织数据节点,它类似于一个标准的文件系统,如下图所示。
![avatar](../../../images/Dubbo/dubbo注册中心在zookeeper中的结构.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/dubbo注册中心在zookeeper中的结构.png)
该图展示了 dubbo 在 zookeeper 中存储的形式以及节点层级。dubbo 的 Root 层是根目录,通过<dubbo:registry group="dubbo" />的“group”来设置 zookeeper 的根节点缺省值是“dubbo”。Service 层是服务接口的全名。Type 层是分类,一共有四种分类,分别是 providers 服务提供者列表、consumers 服务消费者列表、routes 路由规则列表、configurations 配置规则列表。URL 层 根据不同的 Type 目录:可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的 Type 关注的 URL 不同。 该图展示了 dubbo 在 zookeeper 中存储的形式以及节点层级。dubbo 的 Root 层是根目录,通过<dubbo:registry group="dubbo" />的“group”来设置 zookeeper 的根节点缺省值是“dubbo”。Service 层是服务接口的全名。Type 层是分类,一共有四种分类,分别是 providers 服务提供者列表、consumers 服务消费者列表、routes 路由规则列表、configurations 配置规则列表。URL 层 根据不同的 Type 目录:可以有服务提供者 URL 、服务消费者 URL 、路由规则 URL 、配置规则 URL 。不同的 Type 关注的 URL 不同。
@ -10,7 +10,7 @@ zookeeper 以斜杠来分割每一层的 znode 节点,比如第一层根节点
dubbo-registry-zookeeper 模块的工程结构如下图所示,里面就俩类,非常简单。 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 ### ZookeeperRegistry

@ -2,13 +2,13 @@
服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分,前面我们分析了有关注册中心的源码,也就是服务管理,接下来要分析的就是跟服务通信有关的源码,也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能,而在对 NIO 框架选型上dubbo 交由用户选择,它集成了 mina、netty、grizzly 等各类 NIO 框架来搭建 NIO 服务器和客户端,并且利用 dubbo 的 SPI 扩展机制可以让用户自定义选择。dubbo-remoting 的工程结构如下。 服务治理框架 大致可分为 “服务通信” 和 “服务管理” 两部分,前面我们分析了有关注册中心的源码,也就是服务管理,接下来要分析的就是跟服务通信有关的源码,也就是远程通讯模块。该模块中提供了多种客户端和服务端通信的功能,而在对 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-api 模块整体结构设计
本篇我们先来看一下 dubbo-remoting 中 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 的解读会分为如下五个部分,其中第五部分会在本文介绍。 dubbo-remoting-api 定义了远程通信模块最核心的 API对于 dubbo-remoting-api 的解读会分为如下五个部分,其中第五部分会在本文介绍。
@ -20,7 +20,7 @@ dubbo-remoting-api 定义了远程通信模块最核心的 API对于 dubbo-re
结合 dubbo-remoting-api 模块 的外层类和包划分,我们看看下面的官方架构图。 结合 dubbo-remoting-api 模块 的外层类和包划分,我们看看下面的官方架构图。
![在这里插入图片描述](../../../images/Dubbo/Dubbo整体架构图.png) ![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Dubbo/Dubbo整体架构图.png)
红框标注的部分是 dubbo 整体架构中的 远程通讯架构,其中 Exchange 组件 和 Transport 组件 在框架设计中起到了很重要的作用,也是支撑 Remoting 的核心。 红框标注的部分是 dubbo 整体架构中的 远程通讯架构,其中 Exchange 组件 和 Transport 组件 在框架设计中起到了很重要的作用,也是支撑 Remoting 的核心。

@ -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)

@ -260,7 +260,7 @@ public class ThreadLocal<T> {
简单画个图总结一下 ThreadLocal 的原理,如下。 简单画个图总结一下 ThreadLocal 的原理,如下。
![avatar](../../../images/JDK1.8/ThreadLocal原理.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/JDK1.8/ThreadLocal原理.png)
最后强调一下 ThreadLocal 的使用注意事项: 最后强调一下 ThreadLocal 的使用注意事项:

@ -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 处的元素值(这是要删除的值) 1、先进行下标是否越界的判断获取 index 处的元素值(这是要删除的值)

@ -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 该组件中Executor 和 ExecutorService 接口 定义了线程池最核心的几个方法,提交任务 submit
()、关闭线程池 shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor 接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。 ()、关闭线程池 shutdown()。抽象类 AbstractExecutorService 主要对公共行为 submit()系列方法进行了实现,这些 submit()方法 的实现使用了 模板方法模式,其中调用的 execute()方法 是未实现的 来自 Executor 接口 的方法。实现类 ThreadPoolExecutor 则对线程池进行了具体而复杂的实现。
@ -218,7 +218,7 @@ public class ThreadPoolExecutor extends AbstractExecutorService {
ThreadPoolExecutor 中的 execute()方法 执行 Runnable 任务 的流程逻辑可以用下图表示。 ThreadPoolExecutor 中的 execute()方法 执行 Runnable 任务 的流程逻辑可以用下图表示。
![avatar](../../../images/ConcurrentProgramming/线程池流程.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/ConcurrentProgramming/线程池流程.png)
### 工具类 Executors ### 工具类 Executors

@ -2,4 +2,4 @@
根据功能主要划分了六个部分其中比较重要的是线程池及其相关类、并发容器、AQS 与锁与同步工具类、原子类。图可能整理的不够细致,但看着这些类,回想一下其中的源码实现,感觉能侃一天。 根据功能主要划分了六个部分其中比较重要的是线程池及其相关类、并发容器、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)

@ -2,11 +2,11 @@
J.U.C 的锁组件中 类相对较少,从 JDK 相应的包中也能看出来,下图标记了其中最主要的几个接口和类,也是本文要分析的重点。 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 组件 ## Lock 组件

@ -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 种线程池 #### Executors 提供的 4 种线程池

@ -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)
该模式的主要角色如下: 该模式的主要角色如下:

@ -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 (组件):组件接口定义了全部 “组件实现类” 以及所有 “装饰器实现” 的行为。 - Component (组件):组件接口定义了全部 “组件实现类” 以及所有 “装饰器实现” 的行为。
- ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。 - ConcreteComponent (具体组件实现类):通常情况下,具体组件实现类就是被装饰器装饰的原始对象,该类提供了 Component 接口中定义的最基本的功能,其他高级功能或后续添加的新功能,都是通过装饰器的方式添加到该类的对象之上的。

@ -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<E> extends AbstractList<E>
这个模式也是平时很少使用的,所以就简单介绍一下,然后结合 JDK 中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下: 这个模式也是平时很少使用的,所以就简单介绍一下,然后结合 JDK 中的源码加深理解。该模式用于定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知,然后自动更新。类图和主要角色如下:
![avatar](../../../images/DesignPattern/观察者模式类图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/DesignPattern/观察者模式类图.png)
- Subject 主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的; - Subject 主题:具有注册、移除及通知观察者的功能,主题是通过维护一个观察者列表来实现这些功能的;
- Observer 观察者:其注册需要 Subject 的 registerObserver()方法。 - Observer 观察者:其注册需要 Subject 的 registerObserver()方法。
@ -1102,12 +1102,12 @@ public class Observable {
在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 Handler 处理类中,这样我们就可以根据业务需求,将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler 对象 都包含对下一个 Handler 对象 的引用,一个 Handler 对象 处理完请求消息(或不能处理该请求)时, 会把请求传给下一个 Handler 对象 继续处理,依此类推,直至整条责任链结束。简单看一下责任链模式的类图。 在责任链模式中,将上述臃肿的请求处理逻辑 拆分到多个 功能逻辑单一的 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 中的应用
在 Netty 中,将 Channel 的数据管道抽象为 ChannelPipeline消息在 ChannelPipeline 中流动和传递。ChannelPipeline 是 ChannelHandler 的容器,持有 I/O 事件拦截器 ChannelHandler 的链表,负责对 ChannelHandler 的管理和调度。由 ChannelHandler 对 I/O 事件 进行拦截和处理,并可以通过接口方便地新增和删除 ChannelHandler 来实现不同业务逻辑的处理。下图是 ChannelPipeline 源码中描绘的责任链事件处理过程。 在 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 中。 1. 底层 SocketChannel 的 read 方法 读取 ByteBuf触发 ChannelRead 事件,由 I/O 线程 NioEventLoop 调用 ChannelPipeline 的 fireChannelRead()方法,将消息传输到 ChannelPipeline 中。

@ -1,888 +0,0 @@
本文用于总结《阿里 Java 开发手册》、《用友技术 review 手册》及个人 Java 开发工作经验,并结合这半年来的源码阅读经验进行编写。回顾那些写过的和读过的代码,回顾自己。
## 第一章 基础编码规范
### 1.1 命名规范
- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
tipsJDK 动态代理生成的代理类 类名使用了\$符号开头,如\$Proxy1。
- 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
tips正确的英文拼写和语法可以让阅读者易于理解避免歧义。注意即使纯拼音命名方式也要避免采用。alibabayonyouBeijing 等国际通用的名称,可视同英文。
在我们的财务相关模块的工程代码及数据库表设计中可以看到一些拼音首字母缩写的命名方式arap_djzbarap 是“应收应付”的英文缩写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“缩写”命名成 AbsClasscondition“缩写”命名成 condi此类随 意缩写严重降低了代码的可阅读性。
- 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词 组合来表达其意。
正例:在 JDK 中表达原子更新的类名为AtomicReferenceFieldUpdater。
反例:变量 int a 的随意命名方式。
- 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
tips将设计模式体现在名字中有利于阅读者快速理解架构设计理念。 如Spring 框架的 BeanFactory工厂模式、JdkDynamicAopProxyJDK 动态代理模式)。
- 接口类中的方法和属性不要加任何修饰符号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 数据对象xxxDOxxx 即为数据表名。
2 数据传输对象xxxDTOxxx 为业务领域相关的名称。
3 展示对象xxxVOxxx 一般为网页名称。
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、IDEAformat 一下即可,省时省力。
### 1.4 OOP 规约
- 避免通过一个类的对象引用访问此类的静态变量或方法,增加编译器解析成本。
- 所有的覆写方法,必须加@Override 注解。
说明getObject()与 get0bject()的问题。一个是字母的 O一个是数字的 0加@Override 可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名(由方法名、参数的类型及**顺序** 确定唯一的方法签名)进行修改,其实现类会马上编译报错。
- 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。
说明:可变参数必须放置在参数列表的最后。(能用数组的就不要使用可变参数编程,可变参数在编译时会被编译成数组类型。可变参数能兼容数组类参数,但是数组类参数却无法兼容可变参数。可变参数类型必须作为参数列表的最后一项,且不能放在定长参数的前面。)
正例public List<User> listUsers(String type, Long... ids) {...}
- 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
tips
一方库:本工程范围内,各个模块和包之间的相互依赖。
二方库:引入的同一个公司内部的其他工程。
三方库:公司以外的其他依赖,比如 apachegoogle 等。
- 不能使用过时的类或方法。
说明java.net.URLDecoder 中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数 decode(String source, String encode)。接口提供方既然明确是过时接口, 那么有义务同时提供新的接口;作为调用方来说,有义务去考证过时方法的新实现是什么。
- Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
正例:"test".equals(object);
反例object.equals("test");
说明:推荐使用 java.util.Objects#equalsJDK7 引入的工具类)。**个人认为,当要比较两个不确定的对象时,可以考虑使用这个类,如果只是想确定某个对象是否为目标值,使用上面的方法并不差**。
- 所有的相同类型的包装类对象之间值的比较,全部使用 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<String> list = new ArrayList<String>(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)也会随之修改。
```
- 泛型通配符 `<? extends T>` 来接收返回的数据,此写法的泛型集合不能使用 add() 方 法,而 `<? super T>` 不能使用 get() 方法,作为接口调用赋值时易出错。
```java
// <? extends T>:上界通配符(Upper Bounds Wildcards
// <? super T>:下界通配符(Lower Bounds Wildcards)
List<? extends C> list1; // list1 的元素的类型只能是 C 和 C 的子类。
List<? super C> list2; // list2 的元素的类型只能是 C 和 C 的父类。
// 简单来说就是 <? extends C> 上界为 C 类型范围粗略理解为 [C,+∞)
// 不允许添加除 null 的元素,获取的元素类型是 C
// <? super C> 下界为 C 类型范围粗略理解为 (-∞,C],允许添加 C 以及 C 的子类类型元素,
// 获取的元素类型是 Object
// 扩展说一下 PECS(Producer Extends Consumer Super)原则。
// 第一、频繁往外读取内容的,适合用<? extends T>。
// 第二、经常往里插入的,适合用<? super T>。
```
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
```java
// 正例:
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator<String> 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 xy 的比较结果和 yx 的比较结果相反。 2 x>yy>z则 x>z。 3 x=y则 xz 比较结果和 yz 比较结果相同。
```java
// 反例:下例中没有处理相等的情况,实际使用中可能会出现异常:
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};
```
- 集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。
说明:菱形泛型,即 diamond直接使用<>来指代前边已经指定的类型。
```java
// 正例:
// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> 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<String, Object> infoMap = new HashMap<>();
infoMap.put("name", "Zebe");
infoMap.put("site", "www.zebe.me");
infoMap.put("email", "zebe@vip.qq.com");
// 传统的Map迭代方式
for (Map.Entry<String, Object> 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/unsortHashMap 是 unorder/unsortTreeSet 是 order/sort。
- 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的 contains 方法进行遍历、对比、去重操作。
### 1.6 并发处理
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明:资源驱动类、工具类、单例工厂类都需要注意。
- 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
```java
// 正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
}
```
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
tips:我们的代码中的很多线程都是自行显式创建的,很少见到通过线程池进行统一管理的。
- 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明Executors 返回的线程池对象的弊端如下:
1FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE可能会堆积大量的请求从而导致 OOM。
2CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE可能会创建大量的线程从而导致 OOM。
- SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static必须加锁或者使用 DateUtils 工具类。
```java
// 正例:注意线程安全,使用 DateUtils。亦推荐如下处理
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
// 说明:如果是 JDK8 的应用,可以使用 Instant 代替 DateLocalDateTime 代替 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 的方式来处理比如NullPointerExceptionIndexOutOfBoundsException 等等。
说明:无法通过预检查的异常除外,比如,**在解析字符串形式的数字时,不得不通过 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对于调用端解决问题的帮助不会太多。如果加了栈信息在频繁调用出错的情况下数据序列化和传输的性能损耗也是问题。
- 避免出现重复的代码Dont 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
<!-- 正例: -->
<logger name="com.taobao.dubbo.config" additivity="false">
```
- 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 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 前缀,所以,需要在&lt;resultMap&gt;设置从 is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。 正例:表达逻辑删除的字段名 is_deleted1 表示删除0 表示未删除。
tips我们使用的是 dr 字段代表逻辑删除,且 POJO 和布尔字段也未使用上述规范,这也与我们的 JDBC 框架有关,我们的 JDBC 框架是自己设计的,与 mybatis 等主流框架有很大不同。
- 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:**MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。**
正例aliyun_adminrdc_configlevel3_name
反例AliyunAdminrdcConfiglevel_3_name
- 表名不使用复数名词。
说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
- 禁用保留字,如 desc、range、match、delayed 等,请参考 MySQL 官方保留字。
- 主键索引名为 pk*字段名;唯一索引名为 uk*字段名;普通索引名则为 idx*字段名。
说明pk* 即 primary keyuk* 即 unique keyidx* 即 index 的简称。
- 小数类型为 decimal禁止使用 float 和 double。
说明float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到错误的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
- 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
tips公司这一点倒是做的比较规范。
- varchar 是不定长字符串,不预先分配存储空间,长度不要超过 5000如果存储长度大于此值定义字段类型为 text独立出来一张表用主键来对应避免影响其它字段索引效率。
tipsOracle 的 varchar 最大长度为 4000SQL 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 类以及数据库字段定义规定,在&lt;resultMap&gt;中增加映射,是必须的。 在 MyBatis Generator 生成的代码中,需要进行对应的修改。
- 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个 POJO 类与之对应。
说明:配置映射关系,使字段与 DO 类解耦,方便维护。
- sql.xml 配置参数使用:#{} #param# 不要使用\${} 此种方式容易出现 SQL 注入。
- iBATIS 自带的 queryForList(String statementName,int start,int size)不推 荐使用。
说明:其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList 取 start,size 的子集合。
```java
// 正例:
Map<String, Object> 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另外使用事务的地方需要考虑各方面的回滚方案包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。
- &lt;isEqual&gt;中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;&lt;isNotEmpty&gt;表示不为空且不为 null 时执行;&lt;isNotNull&gt;表示不为 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 最好。
说明:
1consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2ref 指的是使用普通的索引normal index
3range 对索引进行范围检索。
反例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 访问数据库。
- 用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
1page size 过大导致内存溢出;
2恶意 order by 导致数据库慢查询;
3任意重定向
4SQL 注入;
5反序列化注入
6正则输入源串拒绝服务 ReDoS
说明Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的结果。
- 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据。
- 表单、AJAX 提交必须执行 CSRF 安全验证。
说明CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL只要受害者用户一访问后台便在用户不知情的情况下对数据库中用户参数进行相应修改。
- 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损。
说明:如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其它用户,并造成短信平台资源浪费。
- 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略。

@ -357,7 +357,7 @@ public class PoolState {
PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。 PooledDataSource 管理的数据库连接对象 是由其持有的 UnpooledDataSource 对象 创建的,并由 PoolState 管理所有连接的状态。
PooledDataSource 的 getConnection()方法 会首先调用 popConnection()方法 获取 PooledConnection 对象,然后通过 PooledConnection 的 getProxyConnection()方法 获取数据库连接的代理对象。popConnection()方法 是 PooledDataSource 的核心逻辑之一,其整体的逻辑关系如下图: 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 ```java
public class PooledDataSource implements DataSource { public class PooledDataSource implements DataSource {

@ -51,7 +51,7 @@ public interface Cache {
如下图所示Cache 接口 的实现类有很多,但大部分都是装饰器,只有 PerpetualCache 提供了 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 ### 1.1 PerpetualCache

@ -151,13 +151,13 @@ class HfReflectorTest {
- 准备工作完成了开始进行 debug , 在`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`这个方法上打上断点 - 准备工作完成了开始进行 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`类中的构造方法. 观察`constructors`属性存在两个方法,这两个方法就是我在`People`类中的构造方法.
根据语法内容我们应该对`parameterTypes`属性进行查看 根据语法内容我们应该对`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`方法获取了空参构造 可以发现空参构造的`parameterTypes`长度是 0.因此可以确认`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`方法获取了空参构造
@ -283,17 +283,17 @@ class HfReflectorTest {
- 照旧我们进行 debug 当前方法为`toString`方法 - 照旧我们进行 debug 当前方法为`toString`方法
![1575891988804](../../../images/mybatis//1575891988804.png) ![1575891988804](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis//1575891988804.png)
从返回结果可以看到`sb.toString`返回的是: `返回值类型#方法名` 从返回结果可以看到`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` 命名规则:`返回值类型#方法名称:参数列表` 上图返回结果为`void#setName:java.lang.String` 命名规则:`返回值类型#方法名称:参数列表`
回过头看看`uniqueMethods`里面是什么 回过头看看`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()`是不是上一级的类 走到这一步我们来看看`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`应该存在父类的方法 通过断点可见这个`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)

@ -86,7 +86,7 @@ public interface SqlSession extends Closeable {
DefaultSqlSession 是单独使用 MyBatis 进行开发时,最常用的 SqISession 接口实现。其实现了 SqISession 接口中定义的方法及各方法的重载。select()系列方法、selectOne()系列方法、selectList()系列方法、selectMap()系列方法之间的调用关系如下图,殊途同归,它们最终都会调用 Executor 的 query()方法。 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()方法则是直接返回结果对象集合。 上述重载方法最终都是通过调用 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()方法完成数据库修改操作。 DefaultSqlSession 的 insert()方法、update()方法、delete()方法也有多个重载,它们最后都是通过调用 DefaultSqlSession 的 update(String, Object)方法实现的,该重载首先会将 dirty 字段置为 true然后再通过 Executor 的 update()方法完成数据库修改操作。

@ -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`中 - `setProperties`会将下列标签放入`datasource`中
@ -321,8 +321,8 @@ public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
从类图上或者代码中我们可以发现`PooledDataSourceFactory`是继承`UnpooledDataSourceFactory`那么方法应该也是`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)

@ -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 ```java
public class MixedSqlNode implements SqlNode { public class MixedSqlNode implements SqlNode {
@ -48,7 +48,7 @@ public class MixedSqlNode implements SqlNode {
`org.apache.ibatis.scripting.xmltags.IfSqlNode#apply` `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 ```java
/** /**
@ -85,7 +85,7 @@ public class StaticTextSqlNode implements SqlNode {
- 解析`trim`标签 - 解析`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`的时候会往下解析下级标签 - 在解析`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 ```java
@Override @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` 存在返回`true`
执行完成就得到了一个 sql 执行完成就得到了一个 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`方法 继续执行`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` - 发送 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.BaseExecutor#doQuery`
- `org.apache.ibatis.executor.SimpleExecutor#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 ```java
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 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.BaseStatementHandler#prepare`
- `org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement` - `org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement`
@ -317,7 +317,7 @@ public class StaticTextSqlNode implements SqlNode {
- 接下来需要考虑的问题是如何将`?`换成我们的参数`2` - 接下来需要考虑的问题是如何将`?`换成我们的参数`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.StatementHandler#parameterize`
- `org.apache.ibatis.executor.statement.RoutingStatementHandler#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.executor.parameter.ParameterHandler`
- `org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters` - `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`的值 这样就拿到了`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.ResultSetHandler#handleResultSets`
- `org.apache.ibatis.executor.resultset.DefaultResultSetHandler#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)
处理后结果如上 处理后结果如上

@ -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)

@ -190,7 +190,7 @@
HsSell[] list(@Param("ID") Integer id); HsSell[] list(@Param("ID") Integer id);
``` ```
![image-20191219092442456](../../../images/mybatis/image-20191219092442456.png) ![image-20191219092442456](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219092442456.png)
- 修改 mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试 - 修改 mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试
@ -198,4 +198,4 @@
LinkedList<HsSell> list(@Param("ID") Integer id); LinkedList<HsSell> 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)

@ -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 ```java
public interface ObjectWrapper { public interface ObjectWrapper {

@ -172,7 +172,7 @@ public class ParamNameResolver {
``` ```
如果不写`@Param`称则返回 如果不写`@Param`称则返回
![image-20191219083223084](assets/image-20191219083223084.png) ![image-20191219083223084](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/image-20191219083223084.png)
```java ```java
List<HsSell> list(@Param("ID") Integer id); List<HsSell> list(@Param("ID") Integer id);
@ -180,9 +180,9 @@ public class ParamNameResolver {
- 写`@Param`返回 - 写`@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` - `org.apache.ibatis.reflection.ParamNameResolver#getNamedParams`
@ -190,7 +190,7 @@ public class ParamNameResolver {
List<HsSell> list( Integer id); List<HsSell> 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 ```java
List<HsSell> list(@Param("ID") Integer id); List<HsSell> list(@Param("ID") Integer id);
@ -198,6 +198,6 @@ public class ParamNameResolver {
写上`@Param` 写上`@Param`
![image-20191219084943102](../../../images/mybatis/image-20191219084943102.png) ![image-20191219084943102](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219084943102.png)
![image-20191219085131167](../../../images/mybatis/image-20191219085131167.png) ![image-20191219085131167](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/mybatis/image-20191219085131167.png)

@ -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)

@ -5,7 +5,7 @@
Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。 Netty 采用了典型的三层网络架构进行设计和开发,其逻辑架构图如下所示。
![avatar](../../../images/Netty/Netty逻辑架构图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty逻辑架构图.png)
### 通信调度层 Reactor ### 通信调度层 Reactor

@ -114,7 +114,7 @@ Netty 主从多线程模型 代码示例如下。
为了尽可能提升性能Netty 对消息的处理 采用了串行无锁化设计,在 I/O 线程 内部进行串行操作避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。 为了尽可能提升性能Netty 对消息的处理 采用了串行无锁化设计,在 I/O 线程 内部进行串行操作避免多线程竞争导致的性能下降。Netty 的串行化设计工作原理图如下图所示。
![avatar](../../../images/Netty/Netty串行化设计工作原理.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty串行化设计工作原理.png)
Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的 Handler期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争从性能角度看是最优的。 Netty 的 NioEventLoop 读取到消息之后,直接调用 ChannelPipeline 的 fireChannelRead(Object msg),只要用户不主动切换线程,一直会由 NioEventLoop 调用到 用户的 Handler期间不进行线程切换。这种串行化处理方式避免了多线程操作导致的锁的竞争从性能角度看是最优的。

@ -6,7 +6,7 @@ Linux 的内核将所有外部设备都看做一个文件来操作,对一个
在内核将数据准备好之前系统调用会一直等待所有的套接字Socket传来数据默认的是阻塞方式。 在内核将数据准备好之前系统调用会一直等待所有的套接字Socket传来数据默认的是阻塞方式。
![avatar](../../../images/Netty/阻塞IO模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/阻塞IO模型.png)
Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom 方法OS 会判断来自网络的数据报是否准备好当数据报准备好了之后OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。 Java 中的 socket.read()方法 最终会调用底层操作系统的 recvfrom 方法OS 会判断来自网络的数据报是否准备好当数据报准备好了之后OS 就会将数据从内核空间拷贝到用户空间(因为我们的用户程序只能获取用户空间的内存,无法直接获取内核空间的内存)。拷贝完成之后 socket.read() 就会解除阻塞,并得到网络数据的结果。
@ -19,7 +19,7 @@ BIO 中的阻塞,就是阻塞在 2 个地方:
#### 2、非阻塞 IO 模型 #### 2、非阻塞 IO 模型
![avatar](../../../images/Netty/非阻塞IO模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/非阻塞IO模型.png)
每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。 每次应用程序询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备好的信号,等待应用进程的下一次询问。但是,轮寻对于 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。 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 模型 #### 4、信号驱动 IO 模型
首先开启套接口信号驱动 IO 功能,并通过系统调用 sigaction 执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个 SIGIO 信号,通过信号回调通知应用程序调用 recvfrom 来读取数据,并通知主循环函数处理数据。 首先开启套接口信号驱动 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 模型 #### 5、异步 IO 模型
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动 IO 由内核通知我们何时可以开始一个 IO 操作;异步 IO 模型 由内核通知我们 IO 操作何时已经完成。 告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别是:信号驱动 IO 由内核通知我们何时可以开始一个 IO 操作;异步 IO 模型 由内核通知我们 IO 操作何时已经完成。
![avatar](../../../images/Netty/异步IO模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/异步IO模型.png)
从这五种 IO 模型的结构 也可以看出,阻塞程度:阻塞 IO>非阻塞 IO>多路转接 IO>信号驱动 IO>异步 IO效率是由低到高的。 从这五种 IO 模型的结构 也可以看出,阻塞程度:阻塞 IO>非阻塞 IO>多路转接 IO>信号驱动 IO>异步 IO效率是由低到高的。
最后,我们看一下数据从客户端到服务器,再由服务器返回结果数据的整体 IO 流程,以便我们更好地理解上述的 IO 模型。 最后,我们看一下数据从客户端到服务器,再由服务器返回结果数据的整体 IO 流程,以便我们更好地理解上述的 IO 模型。
![avatar](../../../images/Netty/数据在客户端及服务器之间的整体IO流程.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/数据在客户端及服务器之间的整体IO流程.png)
## IO 多路复用技术 ## IO 多路复用技术

@ -1,6 +1,6 @@
Selector、SelectionKey 和 Channel 这三个组件构成了 Java nio 包的核心,也是 Reactor 模型在代码层面的体现。Selector 能让单线程同时处理多个客户端 Channel非常适用于高并发传输数据量较小的场景。要使用 Selector首先要将对应的 Channel 及 IO 事件(读、写、连接)注册到 Selector注册后会产生一个 SelectionKey 对象,用于关联 Selector 和 Channel及后续的 IO 事件处理。这三者的关系如下图所示。 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 的奥秘。 对 nio 编程不熟的同学可以搜索一些简单的 demo 跑一下,下面 我们直接进入源码,窥探一些 nio 的奥秘。
@ -160,7 +160,7 @@ public abstract class SelectionKey {
平时编码用的比较多的就是 SocketChannel 和 ServerSocketChannel而将 Channel 与 Selecor 关联到一起的核心 API 则定义在它们的公共父类 SelectableChannel 中,整个 Channel 组件的核心类图如下所示。 平时编码用的比较多的就是 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 #### SelectableChannel

@ -9,7 +9,7 @@
通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程 负责监听客户端的连接,它接收到客户 通过下面的通信模型图可以发现,采用 BIO 通信模型的服务端,通常由一个独立的 Acceptor 线程 负责监听客户端的连接,它接收到客户
端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。 端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 “一请求一应答” 通信模型。
![avatar](../../../images/Netty/BIO通信模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/BIO通信模型.png)
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈 1: 1 的正比关系,由于线程是 Java 虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。 该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈 1: 1 的正比关系,由于线程是 Java 虚拟机 非常宝贵的系统资源,当线程数膨胀之后,系统的性能将急剧下降,随着并发访问量的继续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
@ -23,7 +23,7 @@
采用线程池和任务队列可以实现一种叫做 伪异步的 IO 通信框架,其模型图下。当有新的客户端接入时,将客户端的 Socket 封装成一个 Task 对象 (该类实现了 java.lang.Runnable 接口)投递到后端的线程池中进行处理JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 采用线程池和任务队列可以实现一种叫做 伪异步的 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 通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。 伪异步 IO 通信框架 采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。
@ -105,14 +105,14 @@ Buffer 对象 包含了一些要写入或者要读出的数据。在 NIO 类库
缓冲区实质上是一个数组。通常它是一个字节数组ByteBuffer也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组缓冲区提供了对数据的结构化访问以及维护读写位置limit等信息。最常用的缓冲区是 ByteBuffer一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了 ByteBuffer还有其他的一些缓冲区事实上每一种 Java 基本类型(除了 boolean都对应有一种与之对应的缓冲区CharBuffer、IntBuffer、DoubleBuffer 等等。Buffer 组件中主要类的类图如下所示。 缓冲区实质上是一个数组。通常它是一个字节数组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所以它在具有一般缓冲区的操作之外还提供了一些特有的操作以方便网络读写。 除了 ByteBuffer每一个 Buffer 类 都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数 标准 IO 操作 都使用 ByteBuffer所以它在具有一般缓冲区的操作之外还提供了一些特有的操作以方便网络读写。
**2、通道 Channel** **2、通道 Channel**
Channel 是一个通道,它就像自来水管一样,网络数据通过 Channel 读取和写入。通道与流的不同之处在于通道是双向的,可以用于读、写,或者二者同时进行;流是单向的,要么是 InputStream要么是 OutputStream。因为 Channel 是全双工的,所以它可以比流更好地映射底层操作系统的 API。特别是在 UNIX 网络编程模型 中底层操作系统的通道都是全双工的同时支持读写操作。Channel 组件中 主要类的类图如下所示,从中我们可以看到最常用的 ServerSocketChannel 和 SocketChannel。 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** **3、多路复用器 Selector**
多路复用器 Selector 是 Java NIO 编程 的基础,熟练地掌握 Selector 对于 NIO 编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲Selector 会不断地轮询 “注册在其上的 Channel”如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO 操作。 多路复用器 Selector 是 Java NIO 编程 的基础,熟练地掌握 Selector 对于 NIO 编程 至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲Selector 会不断地轮询 “注册在其上的 Channel”如果某个 Channel 上面发生读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取 “就绪 Channel 的集合”,进行后续的 IO 操作。
@ -121,7 +121,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
### NIO 服务端序列图 ### NIO 服务端序列图
![avatar](../../../images/Netty/NIO服务端序列图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NIO服务端序列图.png)
下面,我们看一下 NIO 服务端 的主要创建过程。 下面,我们看一下 NIO 服务端 的主要创建过程。
@ -224,7 +224,7 @@ Channel 是一个通道,它就像自来水管一样,网络数据通过 Chann
### NIO 客户端序列图 ### NIO 客户端序列图
![avatar](../../../images/Netty/NIO客户端序列图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/NIO客户端序列图.png)
1、打开 SocketChannel绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。 1、打开 SocketChannel绑定客户端本地地址 (可选,默认系统会随机分配一个可用的本地地址),示例代码如下。
@ -356,7 +356,7 @@ NIO2.0 的异步套接字通道是真正的 异步非阻塞 IO对应于 UNIX
对比之前,这里再澄清一下 “伪异步 IO” 的概念。伪异步 IO 的概念完全来源于实践,并没有官方说法。在 JDK NIO 编程 没有流行之前,为了解决 Tomcat 通信线程同步 IO 导致业务线程被挂住的问题,大家想到了一个办法,在通信线程和业务线程之间做个缓冲区,这个缓冲区用于隔离 IO 线程 和业务线程间的直接访问,这样业务线程就不会被 IO 线程 阻塞。而对于后端的业务侧来说,将消息或者 Task 放到线程池后就返回了,它不再直接访问 IO 线程 或者进行 IO 读写,这样也就不会被同步阻塞。 对比之前,这里再澄清一下 “伪异步 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 开发项目的理由 ## 选择 Netty 开发项目的理由

@ -20,7 +20,7 @@ Java 中将输入输出抽象称为流,就好像水管,将两个容器连接
在内核将数据准备好之前系统调用会一直等待所有的套接字Socket默认的是阻塞方式。 在内核将数据准备好之前系统调用会一直等待所有的套接字Socket默认的是阻塞方式。
![avatar](../../../images/Netty/阻塞IO模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/阻塞IO模型.png)
Java 中的 socket.read()会调用 native read(),而 Java 中的 native 方法会调用操作系统底层的 dll而 dll 是 C/C++编写的,图中的 recvfrom 其实是 C 语言 socket 编程中的一个方法。所以其实我们在 Java 中调用 socket.read()最后也会调用到图中的 recvfrom 方法。 Java 中的 socket.read()会调用 native read(),而 Java 中的 native 方法会调用操作系统底层的 dll而 dll 是 C/C++编写的,图中的 recvfrom 其实是 C 语言 socket 编程中的一个方法。所以其实我们在 Java 中调用 socket.read()最后也会调用到图中的 recvfrom 方法。
@ -35,7 +35,7 @@ BIO 中的阻塞,就是阻塞在 2 个地方:
##### 2.2 非阻塞 IONoblocking I/O ##### 2.2 非阻塞 IONoblocking I/O
![avatar](../../../images/Netty/非阻塞IO模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/非阻塞IO模型.png)
每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮询。但是,轮询对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。 每次应用进程询问内核是否有数据报准备好,当有数据报准备好时,就进行拷贝数据报的操作,从内核拷贝到用户空间,和拷贝完成返回的这段时间,应用进程是阻塞的。但在没有数据报准备好时,并不会阻塞程序,内核直接返回未准备就绪的信号,等待应用进程的下一个轮询。但是,轮询对于 CPU 来说是较大的浪费,一般只有在特定的场景下才使用。
@ -58,7 +58,7 @@ serverSocketChannel.configureBlocking(false);
##### 2.3 IO 多路复用I/O Multiplexing ##### 2.3 IO 多路复用I/O Multiplexing
![avatar](../../../images/Netty/IO复用模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/IO复用模型.png)
传统情况下 client 与 server 通信需要 3 个 socket(客户端的 socket服务端的 server socket服务端中用来和客户端通信的 socket),而在 IO 多路复用中,客户端与服务端通信需要的不是 socket而是 3 个 channel通过 channel 可以完成与 socket 同样的操作channel 的底层还是使用的 socket 进行通信,但是多个 channel 只对应一个 socket(可能不只是一个,但是 socket 的数量一定少于 channel 数量),这样仅仅通过少量的 socket 就可以完成更多的连接,提高了 client 容量。 传统情况下 client 与 server 通信需要 3 个 socket(客户端的 socket服务端的 server socket服务端中用来和客户端通信的 socket),而在 IO 多路复用中,客户端与服务端通信需要的不是 socket而是 3 个 channel通过 channel 可以完成与 socket 同样的操作channel 的底层还是使用的 socket 进行通信,但是多个 channel 只对应一个 socket(可能不只是一个,但是 socket 的数量一定少于 channel 数量),这样仅仅通过少量的 socket 就可以完成更多的连接,提高了 client 容量。
@ -73,13 +73,13 @@ serverSocketChannel.configureBlocking(false);
##### 2.4 信号驱动Signal driven IO ##### 2.4 信号驱动Signal driven IO
![avatar](../../../images/Netty/信号驱动IO模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/信号驱动IO模型.png)
信号驱动 IO 模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对 SIGIO 信号进行捕捉,并且调用我的信号处理函数来获取数据报。 信号驱动 IO 模型,应用进程告诉内核:当数据报准备好的时候,给我发送一个信号,对 SIGIO 信号进行捕捉,并且调用我的信号处理函数来获取数据报。
##### 2.5 异步 IOAsynchronous I/O ##### 2.5 异步 IOAsynchronous 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 操作完成了。 Asynchronous IO 调用中是真正的无阻塞,其他 IO model 中多少会有点阻塞。程序发起 read 操作之后,立刻就可以开始去做其它的事。而在内核角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后kernel 会等待数据准备完成然后将数据拷贝到用户内存当这一切都完成之后kernel 会给用户进程发送一个 signal告诉它 read 操作完成了。

@ -257,4 +257,4 @@ ChannelHandler 负责对 I/O 事件 进行拦截处理,它可以选择性地
ChannelHandler 组件 的核心类及常用类的类图如下。 ChannelHandler 组件 的核心类及常用类的类图如下。
![在这里插入图片描述](../../../images/Netty/ChannelHandler组件.png) ![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ChannelHandler组件.png)

@ -5,7 +5,7 @@
Netty 的 **Channel 组件 是 Netty 对网络操作的封装****如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty 框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外Netty 并没有直接使用 java.nio 包 的 SocketChannel 和 ServerSocketChannel而是**使用 NioSocketChannel 和 NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel 接口 的 API 开始分析,然后看一下其重要子类的源码实现。 Netty 的 **Channel 组件 是 Netty 对网络操作的封装****如 网络数据的读写,与客户端建立连接**,主动关闭连接 等,也包含了 Netty 框架 相关的一些功能,如 获取该 Chanel 的 **EventLoop、ChannelPipeline** 等。另外Netty 并没有直接使用 java.nio 包 的 SocketChannel 和 ServerSocketChannel而是**使用 NioSocketChannel 和 NioServerSocketChannel 对其进行了进一步的封装**。下面我们先从 Channel 接口 的 API 开始分析,然后看一下其重要子类的源码实现。
为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。 为了便于后面的阅读源码,我们先看下 NioSocketChannel 和 NioServerSocketChannel 的继承关系类图。
![在这里插入图片描述](../../../images/Netty/Netty的Channel组件.png) ![在这里插入图片描述](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty的Channel组件.png)
#### Channel 接口 #### Channel 接口

@ -21,7 +21,7 @@
# HashedWheelTimer 实现图示 # 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<ScheduledFutureTask<?>> 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))。 - 大致意思就是你的任务如果插入到堆顶,时间复杂度为 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) - 中间插入后的事件复杂度为 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)。 - 复杂度可以降至 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。 - 时间复杂度是 O(n)n 是轮子的数量,除此之外还要计算一个轮子上的 bucket。
@ -451,7 +451,7 @@ PriorityQueue<ScheduledFutureTask<?>> scheduledTaskQueue() {
根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 bucket 数就会变得非常多从而导致内存占用持续升高CPU 空转时间还是不变的,仅仅是内存需求变高了),如下图: 根据上面的图其实不难理解,如果任务是很久之后才执行的、同时要保证任务低延迟,那么单个时间轮所需的 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 过多的内存占用。 Netty 对于单个时间轮的优化方式就是记录下 remainingRounds从而减少 bucket 过多的内存占用。

@ -11,12 +11,12 @@
- PoolSubpage 数组 tinySubpagePools默认情况下当申请的内存小于 512b 的时候的时候将会从 tinySubpagePools 中直接选择 subPage内存池中的最小单位返回 - PoolSubpage 数组 tinySubpagePools默认情况下当申请的内存小于 512b 的时候的时候将会从 tinySubpagePools 中直接选择 subPage内存池中的最小单位返回
- PoolSubpage 数组 smallSubpagePools默认情况下当申请的内存大于 512b 但是小于一个 page 的大小8kb的时候将会从 smallSubpagePools 返回一个 subPage。subPage 是由 poolChunk 中的 page 分配而来。 - PoolSubpage 数组 smallSubpagePools默认情况下当申请的内存大于 512b 但是小于一个 page 的大小8kb的时候将会从 smallSubpagePools 返回一个 subPage。subPage 是由 poolChunk 中的 page 分配而来。
- PoolChunkList<T> qInit存储内存利用率 0-25%的 poolChunk - `PoolChunkList<T> qInit`:存储内存利用率 0-25%的 poolChunk
- PoolChunkList<T> q000存储内存利用率 1-50%的 poolChunk - `PoolChunkList<T> q000`:存储内存利用率 1-50%的 poolChunk
- PoolChunkList<T> q025存储内存利用率 25-75%的 poolChunk - `PoolChunkList<T> q025`:存储内存利用率 25-75%的 poolChunk
- PoolChunkList<T> q050存储内存利用率 50-100%的 poolChunk - `PoolChunkList<T> q050`:存储内存利用率 50-100%的 poolChunk
- PoolChunkList<T> q075存储内存利用率 75-100%的 poolChunk - `PoolChunkList<T> q075`:存储内存利用率 75-100%的 poolChunk
- PoolChunkList<T> q100存储内存利用率 100%的 poolChunk、 - `PoolChunkList<T> q100`:存储内存利用率 100%的 poolChunk、
当申请的内存大于一个 page8kb但又小于一个 poolChunk2048kb总大小的时候将会从各个 PoolChunkList 中尝试获取一个 poolChunk 从中返回。PoolChunkList 是一个由 poolChunk 组成的链表。 当申请的内存大于一个 page8kb但又小于一个 poolChunk2048kb总大小的时候将会从各个 PoolChunkList 中尝试获取一个 poolChunk 从中返回。PoolChunkList 是一个由 poolChunk 组成的链表。
以上几个 PoolChunkList由符合各个内存利用率的 poolChunk 组成,这几个 PoolChunkList 之间又互相首尾连接组成队列,方便 PoolChunk 在各个队列中根据自己当前的利用率进行转移到对应的位置上。 以上几个 PoolChunkList由符合各个内存利用率的 poolChunk 组成,这几个 PoolChunkList 之间又互相首尾连接组成队列,方便 PoolChunk 在各个队列中根据自己当前的利用率进行转移到对应的位置上。
最后,当申请的内存大于一个 poolChunk 大小的时候将会直接申请一段非池化的内存返回,并不会占用内存池中的内存空间。 最后,当申请的内存大于一个 poolChunk 大小的时候将会直接申请一段非池化的内存返回,并不会占用内存池中的内存空间。

@ -6,7 +6,7 @@
TCP 是个 “流” 协议所谓流就是没有界限的一串数据。TCP 底层 并不了解上层(如 HTTP 协议)业务数据的具体含义,它会根据 TCP 缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。我们可以通过下面的示例图,对 TCP 粘包和拆包问题 进行说明。 TCP 是个 “流” 协议所谓流就是没有界限的一串数据。TCP 底层 并不了解上层(如 HTTP 协议)业务数据的具体含义,它会根据 TCP 缓冲区 的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的 TCP 粘包和拆包问题。我们可以通过下面的示例图,对 TCP 粘包和拆包问题 进行说明。
![avatar](../../../images/Netty/TCP粘包拆包问题.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/TCP粘包拆包问题.png)
假设客户端依次发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。 假设客户端依次发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。

@ -6,7 +6,7 @@ Netty 为了向使用者屏蔽 NIO 通信 的底层细节,在和用户交互
### 基于 Netty 创建客户端 时序图 ### 基于 Netty 创建客户端 时序图
![avatar](../../../images/Netty/基于Netty创建客户端时序图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/基于Netty创建客户端时序图.png)
### Netty 创建客户端 流程分析 ### Netty 创建客户端 流程分析

@ -4,7 +4,7 @@
### Netty 服务端创建时序图 ### Netty 服务端创建时序图
![avatar](../../../images/Netty/Netty服务端创建时序图.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/Netty服务端创建时序图.png)
下面我们对 Netty 服务端创建 的关键步骤和原理进行详细解析。 下面我们对 Netty 服务端创建 的关键步骤和原理进行详细解析。
@ -84,7 +84,7 @@ public final class NioEventLoop extends SingleThreadEventLoop {
8、**当轮询到 准备就绪的 Channel 之后,就由 Reactor 线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。 8、**当轮询到 准备就绪的 Channel 之后,就由 Reactor 线程 NioEventLoop 执行 ChannelPipeline 的相应方法,最终调度并执行 ChannelHandler**,接口如下图所示。
![avatar](../../../images/Netty/ChannelPipeline的调度相关方法.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ChannelPipeline的调度相关方法.png)
9、**执行 Netty 中 系统的 ChannelHandler 和 用户添加定制的 ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler相关代码如下。 9、**执行 Netty 中 系统的 ChannelHandler 和 用户添加定制的 ChannelHandler** 。ChannelPipeline 根据网络事件的类型,调度并执行 ChannelHandler相关代码如下。
@ -190,7 +190,7 @@ backlog 指定了内核为此套接口排队的最大连接个数,对于给定
TCP 参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。 TCP 参数 设置完成后,用户可以为启动辅助类和其父类分别指定 Handler。两者 Handler 的用途不同:子类中的 Handler 是 NioServerSocketChannel 对应的 ChannelPipeline 的 Handler父类中的 Handler 是客户端新接入的连接 SocketChannel 对应的 ChannelPipeline 的 Handler。两者的区别可以通过下图来展示。
![avatar](../../../images/Netty/ServerBootstrap的Handler模型.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/Netty/ServerBootstrap的Handler模型.png)
本质区别就是ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有连接该监听端口的客户端都会执行它;父类 AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。 本质区别就是ServerBootstrap 中的 Handler 是 NioServerSocketChannel 使用的,所有连接该监听端口的客户端都会执行它;父类 AbstractBootstrap 中的 Handler 是个工厂类,它为每个新接入的客户端都创建一个新的 Handler。
@ -326,7 +326,7 @@ NioServerSocketChannel 创建成功后,对它进行初始化,初始化工作
``` ```
到此Netty 服务端监听的相关资源已经初始化完毕,就剩下最后一步,注册 NioServerSocketChannel 到 Reactor 线程 的多路复用器上,然后轮询客户端连接事件。在分析注册代码之前,我们先通过下图,看看目前 NioServerSocketChannel 的 ChannelPipeline 的组成。 到此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 线程 的多路复用器上监听新客户端的接入,代码如下。 最后,我们看下 NioServerSocketChannel 的注册。当 NioServerSocketChannel 初始化完成之后,需要将它注册到 Reactor 线程 的多路复用器上监听新客户端的接入,代码如下。
```java ```java

@ -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 ```java
@Override @Override

@ -10,7 +10,7 @@ tip:
解决方法:当一个对象已经实例化完毕了,还未初始化的时候,将它注入到它所依赖的已经实例好的对象(提前暴露对象),使得它所依赖的对象是个完整对象(实例化+初始化),然后再将这个完整对象注入给它。 解决方法:当一个对象已经实例化完毕了,还未初始化的时候,将它注入到它所依赖的已经实例好的对象(提前暴露对象),使得它所依赖的对象是个完整对象(实例化+初始化),然后再将这个完整对象注入给它。
## 简单工程Spring-version-5.3.18) ## 简单工程Spring-version-5.3.18
我们就用下面两个类进行实践,多个类间依赖也是如此。 我们就用下面两个类进行实践,多个类间依赖也是如此。
@ -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)

@ -1,485 +0,0 @@
# Spring JDBC
- Author: [HuiFer](https://github.com/huifer)
- 源码阅读仓库: [SourceHot-Spring](https://github.com/SourceHot/spring-framework-read)
## 环境搭建
- 依赖
```gradle
compile(project(":spring-jdbc"))
compile group: 'com.alibaba', name: 'druid', version: '1.1.21'
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.47'
```
- db 配置
```properties
jdbc.url=
jdbc.driverClass=
jdbc.username=
jdbc.password=
```
- 实体对象
```java
public class HsLog {
private Integer id;
private String source;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
}
```
- DAO
```java
public interface HsLogDao {
List<HsLog> findAll();
void save(HsLog hsLog);
}
```
- 实现类
```java
public class HsLogDaoImpl extends JdbcDaoSupport implements HsLogDao {
@Override
public List<HsLog> 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<HsLog> {
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
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd"
>
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url"
value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="maxActive" value="20"/>
<property name="initialSize" value="1"/>
<property name="minIdle" value="1"/>
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- 打开PSCache并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true"/>
<property name="maxOpenPreparedStatements" value="20"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="hsLogDao" class="com.huifer.source.spring.dao.impl.HsLogDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
</beans>
```
- 运行方法
```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
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
```
- 从配置中可以知道 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<T>)
```java
@Override
public List<HsLog> findAll() {
return this.getJdbcTemplate().query("select * from hs_log", new HsLogRowMapper());
}
```
```java
@Override
@Nullable
public <T> T query(final String sql, final ResultSetExtractor<T> 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<T>, 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> T execute(StatementCallback<T> 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<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>());
int rowNum = 0;
while (rs.next()) {
// 调用自定义的 rowMapper 进行数据处理
T t = this.rowMapper.mapRow(rs, rowNum++);
results.add(t);
}
return results;
}
```
![image-20200109150841916](../../../images/spring/image-20200109150841916.png)
这样就可以获取到了
方法`result`没有什么操作直接返回即可
```java
private static <T> 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();
}
}
}));
}
```

@ -458,7 +458,7 @@ public class RMIClientSourceCode {
### RmiProxyFactoryBean ### 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`方法 - 该类实现了`InitializingBean`接口直接看`afterPropertiesSet`方法
@ -687,7 +687,7 @@ protected Remote lookupStub() throws RemoteLookupFailureException {
- `RmiInvocationHandler`类图 - `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`方法 最后的`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 ```java
public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor { public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor {
@ -793,77 +793,77 @@ public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor
- `org.springframework.remoting.rmi.RmiServiceExporter#afterPropertiesSet`打上断点 - `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 配置中一致 可以看到此时的数据字段和我们的 xml 配置中一致
- `org.springframework.remoting.rmi.RmiServiceExporter#prepare`断点 - `org.springframework.remoting.rmi.RmiServiceExporter#prepare`断点
![image-20200226084200428](../../../images/spring/image-20200226084200428.png) ![image-20200226084200428](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084200428.png)
往下一直走 往下一直走
![image-20200226084400939](../../../images/spring/image-20200226084400939.png) ![image-20200226084400939](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084400939.png)
这一行是 jdk 的就不进去看了 这一行是 jdk 的就不进去看了
执行完成就创建出了 `Registry` 执行完成就创建出了 `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` - `org.springframework.remoting.rmi.RmiBasedExporter#getObjectToExport`
直接看结果对象 直接看结果对象
![image-20200226084640683](../../../images/spring/image-20200226084640683.png) ![image-20200226084640683](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226084640683.png)
- 执行 bind - 执行 bind
![image-20200226084923783](../../../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 ## 客户端 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 对象 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 - serviceProxy
![image-20200226090042946](../../../images/spring/image-20200226090042946.png) ![image-20200226090042946](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090042946.png)
- 方法调用 - 方法调用
- 使用的是 AOP 技术进行的AOP 相关技术不在此处展开 - 使用的是 AOP 技术进行的AOP 相关技术不在此处展开
![image-20200226090315865](../../../images/spring/image-20200226090315865.png) ![image-20200226090315865](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200226090315865.png)
stub 对象 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` - `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` - `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`结束整个调用 - 反射执行`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 调用结束 此时得到结果 RMI 调用结束

@ -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`提供了帮助 - 该类给`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents`提供了帮助

@ -37,7 +37,7 @@ DispatchServlet 和 ContextLoaderListener 提供了在 Web 容器 中对 Spring
IoC 容器 的启动过程就是建立上下文的过程,该上下文是与 ServletContext 相伴而生的,同时也是 IoC 容器 在 Web 应用环境 中的具体表现之一。由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC 对象,作为根上下文的子上下文,构成一个层次化的上下文体系。在 Web 容器 中启动 Spring 应用程序 时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由 ContextLoder 来完成的,其 UML 时序图 如下图所示。 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 来完成的。 在 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 容器, 先从 Web 容器 中的上下文入手,看看 Web 环境 中的上下文设置有哪些特别之处,然后再到 ContextLoaderListener 中去了解整个容器启动的过程。为了方便在 Web 环境 中使用 IoC 容器,
Spring 为 Web 应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。 Spring 为 Web 应用 提供了上下文的扩展接口 WebApplicationContext 来满足启动过程的需要,其继承关系如下图所示。
![avatar](../../../images/springMVC/WebApplicationContext接口的类继承关系.png) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/WebApplicationContext接口的类继承关系.png)
在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex 接口 与 BeanFactory 接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。 在这个类继承关系中,可以从熟悉的 XmlWebApplicationContext 入手来了解它的接口实现。在接口设计中,最后是通过 ApplicationContex 接口 与 BeanFactory 接口 对接的,而对于具体的功能实现,很多都是封装在其基类 AbstractRefreshableWebApplicationContext 中完成的。

@ -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 ### 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`** - 解析完成后放入 `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 ## CorsConfiguration
@ -509,7 +509,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
- 经过跨域拦截器 **`CorsInterceptor`**之后会调用 - 经过跨域拦截器 **`CorsInterceptor`**之后会调用
![image-20200123093733129](../../../images/springMVC/clazz/image-20200123093733129.png) ![image-20200123093733129](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springMVC/clazz/image-20200123093733129.png)
```java ```java
@Override @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)

@ -10,13 +10,13 @@
为了解这个过程,可以从 DispatcherServlet 的父类 FrameworkServlet 的代码入手,去探寻 DispatcherServlet 的启动过程,它同时也是 SpringMVC 的启动过程。ApplicationContext 的创建过程和 ContextLoader 创建根上下文的过程有许多类似的地方。下面来看一下这个 DispatcherServlet 类 的继承关系。 为了解这个过程,可以从 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 通过继承 FrameworkServlet 和 HttpServletBean 而继承了 HttpServlet通过使用 Servlet API 来对 HTTP 请求 进行响应,成为 SpringMVC 的前端处理器,同时成为 MVC 模块 与 Web 容器 集成的处理前端。
DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法在这个方法里DispatcherServlet 对 MVC 模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP 请求 进行响应,作为一个 ServletWeb 容器 会调用 Servlet 的 doGet() 和 doPost()方法,在经过 FrameworkServlet 的 processRequest() 简单处理后,会调用 DispatcherServlet 的 doService()方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC 模式 的主要部分,下图为 DispatcherServlet 的处理过程时序图。 DispatcherServlet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()方法 启动,通过 initWebApplicationContext()方法 最终调用 DispatcherServlet 的 initStrategies()方法在这个方法里DispatcherServlet 对 MVC 模块 的其他部分进行了初始化,比如 handlerMapping、ViewResolver 等;另一个是对 HTTP 请求 进行响应,作为一个 ServletWeb 容器 会调用 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 的启动和初始化 ## 3 DispatcherServlet 的启动和初始化
@ -391,7 +391,7 @@ HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 W
在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并被排序,存储着 HTTP 请求 对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL 请求 到 Controller 的映射,而 SpringMVC 提供了一系列的 HandlerMapping 实现。 在初始化完成时,在上下文环境中已定义的所有 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 以 SimpleUrlHandlerMapping 为例来分析 HandlerMapping 的设计与实现。在 SimpleUrlHandlerMapping 中,定义了一个 Map 来持有一系列的映射关系。通过这些在 HandlerMapping 中定义的映射关系,即这些 URL 请求 和控制器的对应关系,使 SpringMVC
应用 可以根据 HTTP 请求 确定一个对应的 Controller。具体来说这些映射关系是通过 HandlerMapping 接口 来封装的,在 HandlerMapping 接口 中定义了一个 getHandler()方法,通过这个方法,可以获得与 HTTP 请求 对应的 HandlerExecutionChain在这个 HandlerExecutionChain 中,封装了具体的 Controller 对象。 应用 可以根据 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 的建立。 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 ```java
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping { public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {

@ -8,7 +8,7 @@ JavaEE 应用中的事务处理是一个重要并且涉及范围很广的领域
Spring 事务处理模块的类层次结构如下图所示。 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 的拦截功能,将事务处理的功能编织进来。 从上图可以看到Spring 事务处理模块 是通过 AOP 功能 来实现声明式事务处理的,比如事务属性的配置和读取,事务对象的抽象等。因此,在 Spring 事务处理 中,可以通过设计一个 TransactionProxyFactoryBean 来使用 AOP 功能,通过这个 TransactionProxyFactoryBean 可以生成 Proxy 代理对象,在这个代理对象中,通过 TransactionInterceptor 来完成对代理方法的拦截,正是这些 AOP 的拦截功能,将事务处理的功能编织进来。

@ -24,7 +24,7 @@
这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction 事务对象 的过程,在 AbstractTransactionManager 实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。 这个 TransactionAspectSupport 的 createTransactionIfNecessary()方法 作为事务创建的入口,其具体的实现时序如下图所示。在 createTransactionIfNecessary()方法 的调用中,会向 AbstractTransactionManager 执行 getTransaction()方法,这个获取 Transaction 事务对象 的过程,在 AbstractTransactionManager 实现 中需要对事务的情况做出不同的处理,然后,创建一个 TransactionStatus并把这个 TransactionStatus 设置到对应的 TransactionInfo 中去,同时将 TransactionInfo 和当前的线程绑定从而完成事务的创建过程。createTransactionIfNeccessary()方法 调用中,可以看到两个重要的数据对象 TransactionStatus 和 TransactionInfo 的创建,这两个对象持有的数据是事务处理器对事务进行处理的主要依据,对这两个对象的使用贯穿着整个事务处理的全过程。
![avatar](<images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png>) ![avatar](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/springTransaction/调用createTransactionIfNecessary()方法的时序图.png)
```java ```java
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

@ -4,7 +4,7 @@
可以看到,在 PlatformTransactionManager 组件 的设计中 ,通过 PlatformTransactionManager 接口 设计了一系列与事务处理息息相关的接口方法,如 getTransaction()、commit()、rollback() 这些和事务处理相关的统一接口。对于这些接口的实现,很大一部分是由 AbstractTransactionManager 抽象类 来完成的,这个类中的 doGetTransaction()、doCommit() 等方法和 PlatformTransactionManager 的方法对应,实现的是事务处理中相对通用的部分。在这个 AbstractPlatformManager 下,为具体的数据源配置了不同的事务处理器,以处理不同数据源的事务处理,从而形成了一个从抽象到具体的事务处理中间平台设计,使应用通过声明式事务处理,即开即用事务处理服务,隔离那些与特定的数据源相关的具体实现。 可以看到,在 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 的实现 ## 2 DataSourceTransactionManager 的实现
@ -14,7 +14,7 @@
上面介绍了使用 DataSourceTransactionManager 实现事务创建、提交和回滚的过程,基本上与单独使用 Connection 实现事务处理是一样的,也是通过设置 autoCommit 属性,调用 Connection 的 commit() 和 rollback()方法 来完成的。而我们在声明式事务处理中看到的那些事务处理属性,并不在 DataSourceTransactionManager 中完成,这和我们在前面分析中看到的是一致的。 上面介绍了使用 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 ```java
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager public class DataSourceTransactionManager extends AbstractPlatformTransactionManager

@ -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 ### 印象中的 Spring
@ -26,7 +26,7 @@
把 Spring 浓缩一下,就有了这么一点小东西 🐖 把 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 。 想了下,我们用 Spring ,其中最主要的一点,就是用它来帮我们管理,创建这个 Bean 。
@ -34,7 +34,7 @@
### Bean 解析流程 ### 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 。 如图所示,就是通过 **解析器**,对我们的 XML 文件或者注解进行解析,最后将这些信息封装在 BeanDefinition 类中,并通过 BeanDefinitionRegistry 接口将这些信息 **注册** 起来,放在 beanDefinitionMap 变量中, key : beanName , value BeanDefinition 。
@ -62,7 +62,7 @@
那么,结合我们从原料中获取的重要属性之一的 beanClass ,我们可以画出这么一张图 👇 那么,结合我们从原料中获取的重要属性之一的 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 叭 😄 那么我们再来看看这个 BeanFactory 叭 😄
@ -70,7 +70,7 @@
先来看看 作为 IOC 容器的**根接口** 的 BeanFactory 提供了什么方法吧 👇 先来看看 作为 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** 主要是这个 getBean 方法,以及 **别名获取****类型获取** 方法和其他一些判断方法如 **单例****多例****类型匹配****包含 bean**
@ -80,7 +80,7 @@
看源码的时候,一般就直接看这个**默认**接口 如这里的 DefaultListableBeanFactory 看源码的时候,一般就直接看这个**默认**接口 如这里的 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 这个大工厂管理的。 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
我们再来看看这个 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**,以及处理**国际化****事件****获取资源**等 可以看到它扩展了很多功能,除了 BeanFactory ,它还可以**创建 , 获取 Bean**,以及处理**国际化****事件****获取资源**等
@ -183,7 +183,7 @@ beanName 就是正常对象
我们可以在各个过程中合理应用这些 PostProcessor 来扩展,或者修改 Bean 定义信息等等 我们可以在各个过程中合理应用这些 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 的初始化,而这个过程还有很多细节 ,请往下看看 👇 可以看到在这个容器中,完成了 Bean 的初始化,而这个过程还有很多细节 ,请往下看看 👇
@ -211,11 +211,11 @@ Bean 的创建和管理有**标准化的流程**
这里在我们的工厂 BeanFactory 中写得很清楚 👇 这里在我们的工厂 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** 个步骤,是不是一下子就清晰多了 😄 总共 **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 安排上,就得到下图啦 👇 在实例化 和 初始化流程中,把这个 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 这里留意下 **实例化** 有扩展点 InstantiationAwareBeanPostProcessor **初始化** 扩展点 BeanPostProcessor 就非常多啦,我们主要来关注下这个 AOP
@ -240,11 +240,11 @@ ps: 别看快搞错了 哈哈 😝
那么 AOP 是在哪个步骤代理对象的呢?👇 那么 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 类中看到 👇 可以在 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 **发生的地方**。😋 还有这个核心机制: **工厂+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)

@ -1,32 +1,62 @@
引言:庞大的代码量让人心生怠倦,有趣的故事让技术也疯狂 # 引言:庞大的代码量让人心生怠倦,有趣的故事让技术也疯狂
大家好,我是 IoC 容器家族的第 17 代传人,我们家族世世代代在 spring 商业街上卖烤面筋,大家都叫我“面筋哥”,另外我爹还给我起了个高大上的英文名字,叫“FileSystemXmlApplicationContext”,但有群臭猴子嫌麻烦,就天天叫我的外号,害得我差点忘了自己的本名。不过无所谓咯,只要生意兴隆,这都是小事。 大家好,我是 IoC 容器家族的第 17 代传人,我们家族世世代代在 spring 商业街上卖烤面筋,大家都叫我“面筋哥”,另外我爹还给我起了个高大上的英文名字,叫 `FileSystemXmlApplicationContext`,但有群臭猴子嫌麻烦,就天天叫我的外号,害得我差点忘了自己的本名。不过无所谓咯,只要生意兴隆,这都是小事。
前几天出摊卖烤面筋时,灵感大作,即兴唱了一首“我的烤面筋”,被网友拍下来传到某站上 成了网红,现在我要趁势而上,把自己祖传的烤面筋工艺宣传出去,让我那个臭弟弟“ClassPathXmlApplicationContext”知道,谁才是 IoC 容器的正统传人! 前几天出摊卖烤面筋时,灵感大作,即兴唱了一首“我的烤面筋”,被网友拍下来传到某站上成了网红,现在我要趁势而上,把自己祖传的烤面筋工艺宣传出去,让我那个臭弟弟 `ClassPathXmlApplicationContext` 知道,谁才是 IoC 容器的正统传人!
## 第一阶段BeanDefinition 资源定位ReaderbeanDefinitionReaderdocumentReader ## 第一阶段BeanDefinition 资源定位ReaderbeanDefinitionReaderdocumentReader
新的一天从 new 开始,但我却还躺在床上各种伸懒腰,毕竟我现在也是个小老板了,很多杂七杂八的活雇几个小弟干就行咯。我拿起我的 iBanana11 看了看商业街董事(某程序员)发的“精选优质面筋批发市场地址”,然后深吸一口气 refresh(),闭上眼 obtainFreshBeanFactory(),气沉丹田 refreshBeanFactory(),大喊一声: 新的一天从 `new` 开始,但我却还躺在床上各种伸懒腰,毕竟我现在也是个小老板了,很多杂七杂八的活雇几个小弟干就行咯。我拿起我的 iBanana11 看了看商业街董事(某程序员)发的“精选优质面筋批发市场地址”,然后深吸一口气 `refresh()`,闭上眼 `obtainFreshBeanFactory()`,气沉丹田 `refreshBeanFactory()`,大喊一声:
“loadBeanDefinitions()!”
我虎背熊腰的小弟“beanDefinitionReader” 破门而入,尖声细语地问道: > loadBeanDefinitions()
“老板有何吩咐 ~ ?”
我虎背熊腰的小弟 `beanDefinitionReader` 破门而入,尖声细语地问道:
> 老板有何吩咐 ~
我起身叮嘱了他几件事后把自己的联系方式引用、面筋批发市场的地址spring 配置文件地址)交给他,就又躺回去盯着天花板上的钻石吊灯继续发呆。 我起身叮嘱了他几件事后把自己的联系方式引用、面筋批发市场的地址spring 配置文件地址)交给他,就又躺回去盯着天花板上的钻石吊灯继续发呆。
Reader 家有一对兄妹,哥哥 beanDefinitionReader 虎背熊腰大老粗,却尖声细语;妹妹 documentReader 心灵手巧,可惜比较宅,我们几乎没怎么见过。兄妹俩相互配合把上午的准备工作做了大半。
不要看我天天躺着彗星晒屁股了还眯着眼ta 们兄妹俩在几点几分打个喷嚏我都能算到,毕竟我基因里都写满了“烤面筋工艺完整详细流程”。 Reader 家有一对兄妹,哥哥 `beanDefinitionReader` 虎背熊腰大老粗,却尖声细语;妹妹 `documentReader` 心灵手巧,可惜比较宅,我们几乎没怎么见过。兄妹俩相互配合把上午的准备工作做了大半。
哥哥现在肯定在开着小面包车拿着我给他的地址locations到处找面筋等原材料然后把找到的面筋打包进 Document 对象,拉回来交给妹妹 documentReader 进行精心处理,连同 Document 给她的还有一个“神秘人”的联系方式。
妹妹会打开 Document 取出其中最大的几个箱子(&lt;beans>、&lt;import>、&lt;alias> 等一级标签),分别进行处理。其中 beans 箱最为重要,里面放满了夜市的主角,烤面筋的核心材料。 不要看我天天躺着,彗星晒屁股了还眯着眼,他们兄妹俩在几点几分打个喷嚏我都能算到,毕竟我基因里都写满了“烤面筋工艺完整详细流程”。
哥哥现在肯定在开着小面包车拿着我给他的地址(`locations`)到处找面筋等原材料,然后把找到的面筋打包进 `Document` 对象,拉回来交给妹妹 `documentReader` 进行精心处理,连同 `Document` 给她的还有一个“神秘人”的联系方式。
妹妹会打开 `Document` 取出其中最大的几个箱子(`<beans>`、`<import>`、`<alias>` 等一级标签),分别进行处理。其中 `beans` 箱最为重要,里面放满了夜市的主角,烤面筋的核心材料。
## 第二阶段:将 bean 解析封装成 BeanDefinitionHolderBeanDefinitionParserDelegate ## 第二阶段:将 bean 解析封装成 BeanDefinitionHolderBeanDefinitionParserDelegate
之后妹妹会拿起我们 IoC 家族祖传的面筋处理神器 BeanDefinitionParserDelegate从 beans 箱里面一个一个取出形态各异的面筋 bean 分别进行加工处理。刚拿出来的面筋 bean 是不会直接烤了卖的,我们会将 bean 用神器 ParserDelegate 进行九九八十一道细致处理,所以我们家烤出来的面筋才会如此劲道美味,世世代代延绵不断。 之后妹妹会拿起我们 IoC 家族祖传的面筋处理神器 `BeanDefinitionParserDelegate`,从 `beans` 箱里面一个一个取出形态各异的面筋 bean 分别进行加工处理。
不过处理程序再怎么细致复杂,也不过就是分为两大部分:第一,处理 bean 的属性信息,如 idclassscope 等;第二,处理 bean 的子元素,主要是 <property> 标签,而 <property> 标签又有属性和子元素,且子元素类型更加丰富复杂,可能是&lt;map>&lt;set>&lt;list>&lt;array> 等。所以如果你们想学我家的祖传秘方,开个同样的摊子干倒我,也不是这么容易的哦。
经过上面的步骤,一个配置文件中的面筋 bean 就被处理包装成了半成品 BeanDefinitionHolder。 刚拿出来的面筋 bean 是不会直接烤了卖的,我们会将 bean 用神器 `ParserDelegate` 进行九九八十一道细致处理,所以我们家烤出来的面筋才会如此劲道美味,世世代代延绵不断。
不过处理程序再怎么细致复杂,也不过就是分为两大部分:
1. 处理 bean 的属性信息,如 `id`、`class`、`scope` 等;
2. 处理 bean 的子元素,主要是 `<property>` 标签,而 `<property>` 标签又有属性和子元素,且子元素类型更加丰富复杂,可能是 `<map>`、`<set>`、`<list>`、`<array>` 等。
所以如果你们想学我家的祖传秘方,开个同样的摊子干倒我,也不是这么容易的哦。
经过上面的步骤,一个配置文件中的面筋 bean 就被处理包装成了半成品 `BeanDefinitionHolder`
## 第三阶段:将 BeanDefinition 注册进 IoC 容器BeanDefinitionReaderUtils ## 第三阶段:将 BeanDefinition 注册进 IoC 容器BeanDefinitionReaderUtils
妹妹在用神器 BeanDefinitionParserDelegate 经过一顿疯狂操作之后,将包装好的半成品 BeanDefinitionHolder 扔进传输机 BeanDefinitionReaderUtils并且输入哥哥给她的神秘人地址就继续处理下一个面筋 bean 咯。 妹妹在用神器 `BeanDefinitionParserDelegate` 经过一顿疯狂操作之后,将包装好的半成品 `BeanDefinitionHolder` 扔进传输机 `BeanDefinitionReaderUtils`,并且输入哥哥给她的神秘人地址,就继续处理下一个面筋 bean 咯。
之后,传输机将 BeanDefinitionHolder 的包装打开,分别取出 beanName面筋的唯一标识和 BeanDefinition面筋本筋传输的目的地是 BeanDefinitionRegistry 的工作室(这就是我前面给哥哥 beanDefinitionReader 的地址)。
这家工作室的 BeanDefinitionRegistry 其实就是我的影分身之一,因为我的祖先实现了这个接口。影分身 Registry 检查一下传输过来的 beanName面筋的唯一标识和 BeanDefinition面筋本筋如果没什么问题就把它们用根绳子系在一起扔进我的“王之面筋宝库”一个 ConcurrentHashMap<String, BeanDefinition>(64)也有人把我的“面筋宝库”称作“IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。 之后,传输机将 `BeanDefinitionHolder` 的包装打开,分别取出 `beanName`(面筋的唯一标识)和 `BeanDefinition`(面筋本筋),传输的目的地是 `BeanDefinitionRegistry` 的工作室(这就是我前面给哥哥 `beanDefinitionReader` 的地址)。
就这样,每一种取出来的面筋都会经过这些处理。等到所有的面筋处理完了,也差不多到了傍晚,每到这时我就会拿起梳子和发油,对着镶满钻石的镜子,梳理整齐与徐峥同款的明星发型,唱着魔性的“我的烤面筋 ~”,骑着小车车,出摊咯 ~
这家工作室的 `BeanDefinitionRegistry` 其实就是我的影分身之一,因为我的祖先实现了这个接口。
影分身 `Registry` 检查一下传输过来的 `beanName`(面筋的唯一标识)和 `BeanDefinition`(面筋本筋),如果没什么问题,就把它们用根绳子系在一起扔进我的“王之面筋宝库”,一个 `ConcurrentHashMap<String, BeanDefinition>(64)`,也有人把我的“面筋宝库”称作 “IoC 容器本器”,我也无可辩驳,谁让他们吃面筋付钱了呢。
就这样,每一种取出来的面筋都会经过这些处理。
等到所有的面筋处理完了,也差不多到了傍晚,每到这时我就会拿起梳子和发油,对着镶满钻石的镜子,梳理整齐与徐峥同款的明星发型,唱着魔性的:
> 我的烤面筋 ~
骑着小车车,出摊咯 ~
---
面筋等原材料基本上都已经处理完毕,但把这些原材料变成程序员手中的“烤面筋”也是一门复杂而精细的手艺,老铁们记得 watch、star、fork素质三连一波下一期我将带领你们走进 spring 商业街的夜市,一起烤出香喷喷的面筋,成为这条 gai 上最亮的仔! 面筋等原材料基本上都已经处理完毕,但把这些原材料变成程序员手中的“烤面筋”也是一门复杂而精细的手艺,老铁们记得 `watch``star``fork`,素质三连一波,下一期我将带领你们走进 spring 商业街的夜市,一起烤出香喷喷的面筋,成为这条 `gai` 上最亮的仔!

@ -257,7 +257,7 @@ public class ProxyTransactionManagementConfiguration extends AbstractTransaction
### TransactionInterceptor ### 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`接口的方法 - 实现了`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 代理对象 断点开始进行查阅. 再断点后执行一步会直接进入 cglib 代理对象
`org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept` 具体不展开,继续往下执行 `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`方法了 走到`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` - `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 定义了一些基础属性 以及一些需要子类实现的方法 - AbstractPlatformTransactionManager 定义了一些基础属性 以及一些需要子类实现的方法
@ -939,7 +939,7 @@ doCleanupAfterCompletion
- bean 的属性注入就不具体描述了 - 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` - `InitializingBean`
@ -1603,7 +1603,7 @@ public static void bindResource(Object key, Object value) throws IllegalStateExc
- debug 使用的是 druid 的数据源 - 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` 方法 - `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`接口的实现 - `org.springframework.beans.factory.InitializingBean`接口的实现

@ -24,4 +24,4 @@
- 类图如下 - 类图如下
![PropertyPlaceholderConfigurerResolver](/images/spring/PropertyPlaceholderConfigurerResolver.png) ![PropertyPlaceholderConfigurerResolver](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertyPlaceholderConfigurerResolver.png)

@ -55,11 +55,11 @@ public static <A extends Annotation> A getAnnotation(Method method, Class<A> ann
- method - 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 - 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 ```java
@Nullable @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 定义相同 处理结果和 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 ## findAnnotation
@ -402,7 +402,7 @@ private static <A extends Annotation> 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`方法就不再重复一遍了可以看上文 - `synthesizeAnnotation`方法就不再重复一遍了可以看上文

@ -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 发布 ## 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 ```java
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { 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)

@ -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 ## InstantiationAwareBeanPostProcessor
@ -361,13 +361,13 @@ public class DemoInstantiationAwareBeanPostProcessor implements InstantiationAwa
- 按照笔者的注释,可以知道`DemoInstantiationAwareBeanPostProcessor` 这个类是一个无序 Bean - 按照笔者的注释,可以知道`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`,此处就可以调用我们的自定义方法了 这个地方已经可以看到`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)

@ -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 ## DefaultBeanNameGenerator

@ -74,7 +74,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
- 直接在`DatePropertyRegister`打上断点进行查看注册流程 - 直接在`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` - `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`传递的 此处对象是通过`DatePropertyRegister`传递的
@ -167,7 +167,7 @@ public class DatePropertyEditor extends PropertyEditorSupport {
- 在`AbstractBeanFactory`中查看变量 - 在`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` - 为什么最后结果变成`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 ## 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 ```java
@Nullable @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<T>, org.springframework.core.convert.TypeDescriptor)` 该值也是这个方法的返回`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)`

@ -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`中有定义, - `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 ```java
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) { public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
@ -366,13 +366,13 @@ public class XSDDemo {
`public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";` `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 ```java
@Override @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 ## 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 ## 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 ### 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` 执行`com.huifer.source.spring.parser.UserBeanDefinitionParser#doParse`

@ -6,7 +6,7 @@
- 官方提供的测试类: `org.springframework.beans.factory.support.DefaultSingletonBeanRegistryTests` - 官方提供的测试类: `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)
## 注册方法解析 ## 注册方法解析

@ -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` - 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)
## 总结 ## 总结

@ -45,7 +45,7 @@
读取 xml 配置文件 读取 xml 配置文件
![image-20200119141937915](../../../images/spring/image-20200119141937915.png) ![image-20200119141937915](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/image-20200119141937915.png)
## getMessage ## 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 获取 获取方法`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)

@ -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 ## AnnotatedTypeMetadata
@ -775,7 +775,7 @@ qulifiter:transactionManager
readOnlay:false 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 ## SimpleMetadataReader

@ -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) - 在 Spring 中有两种可以重写的机制(XML)

@ -58,7 +58,7 @@ public interface MultiValueMap<K, V> extends Map<K, List<V>> {
类图 类图
![](/images/spring/MultiValueMap.png) ![](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/MultiValueMap.png)
## LinkedMultiValueMap ## LinkedMultiValueMap

@ -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 ```java
@Nullable @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 ! 最终`Integer.compare(i1, i2)`比较返回 OK !

@ -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` 三个 - 在 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` - 看一下 List 怎么实现`merge`

@ -484,4 +484,4 @@ public abstract class PropertySource<T> {
类图 类图
![PropertySource.png](/images/spring/PropertySource.png) ![PropertySource.png](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/PropertySource.png)

@ -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 时序图因为有递归所以看着有点长, 其核心方法最后会指向 PlaceholderResolver

@ -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` - 获取到的对象是`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 ### doGetBean
@ -299,7 +299,7 @@ private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
aliasMap 和 别名标签的对应关系 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 alias 标签的 alias 值作为别名的 key alias 标签的 name 值作为 value
@ -704,7 +704,7 @@ protected void clearMergedBeanDefinition(String beanName) {
- 这个方法获取一个`RootBeanDefinition`对象 这个对象也是 bean 的一种定义。 - 这个方法获取一个`RootBeanDefinition`对象 这个对象也是 bean 的一种定义。
- 从目前的几个方法名称来看,暂且认为这是一个合并了多个 `BeanDefinition`的对象吧 - 从目前的几个方法名称来看,暂且认为这是一个合并了多个 `BeanDefinition`的对象吧
![rootBeanDefinition](../../../images/spring/RootBeanDefinition.png) ![rootBeanDefinition](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/RootBeanDefinition.png)
```java ```java
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException { protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
@ -1009,7 +1009,7 @@ private boolean isDependent(String beanName, String dependentBeanName, @Nullable
</bean> </bean>
``` ```
![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 #### 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 ###### 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` - `parseExpressions`
@ -2175,7 +2175,7 @@ try {
pvs 属性如下 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 ###### 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 ###### 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`方法 我们现在的 bean 不是`InitializingBean` 会走自定义的`init-mthod`方法
@ -2483,15 +2483,15 @@ protected void invokeInitMethods(String beanName, final Object bean, @Nullable R
- 观察 `initMethodName` 会变成 标签属性`init-method` 的内容. 接下来就是通过反射执行方法 - 观察 `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 的信息先做一次截图 - 在执行方法前将 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 的属性会被覆盖 - 如果按照我们代码中的编写方式 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 ###### invokeCustomInitMethod

@ -53,7 +53,7 @@ public class ContextNamespaceHandler extends NamespaceHandlerSupport {
### org.springframework.context.annotation.ComponentScanBeanDefinitionParser ### 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`方法 - 实现`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 #### 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, org.springframework.beans.factory.support.BeanDefinitionRegistry)`
- `org.springframework.context.annotation.AnnotationBeanNameGenerator#buildDefaultBeanName(org.springframework.beans.factory.config.BeanDefinition)` - `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 #### org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition

@ -3,7 +3,7 @@
- 类全路径: `org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory` - 类全路径: `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 ```java
public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport

@ -24,4 +24,4 @@ public interface Parser<T> {
- 类图 - 类图
![Parser](/images/spring/Parser.png) ![Parser](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/spring/Parser.png)

@ -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)
- 注册完成后是否立即启动 - 注册完成后是否立即启动

@ -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 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 ### fromMessage
@ -181,6 +181,6 @@
- 两种创建方式基本相同,如果出现异常组装异常消息对象`ErrorMessage`,成功创建`GenericMessage` - 两种创建方式基本相同,如果出现异常组装异常消息对象`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`的子类 从类图上看`ErrorMessage`是`GenericMessage`的子类

@ -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 ```java
@Override @Override
@ -65,7 +65,7 @@ public final HandlerExecutionChain getHandler(HttpServletRequest request) throws
存在的实现方法 存在的实现方法
![image-20200915135933146](images/image-20200915135933146.png) ![image-20200915135933146](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/image-20200915135933146.png)
- 先看`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`方法是怎么一回事. - 先看`org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal`方法是怎么一回事.

@ -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 ## createHandlerMethod

@ -141,11 +141,11 @@ private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] par
- `SpringFactoriesLoader.loadFactoryNames(type, classLoader)` 是 spring 提供的方法,主要目的是读取`spring.factories`文件 - `SpringFactoriesLoader.loadFactoryNames(type, classLoader)` 是 spring 提供的方法,主要目的是读取`spring.factories`文件
- 读取需要创建的内容 - 读取需要创建的内容
![image-20200318080601725](../../images/SpringBoot/image-20200318080601725.png) ![image-20200318080601725](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318080601725.png)
- 创建完成 - 创建完成
![image-20200318080901881](../../images/SpringBoot/image-20200318080901881.png) ![image-20200318080901881](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200318080901881.png)
- `AnnotationAwareOrderComparator.sort(instances)`排序 - `AnnotationAwareOrderComparator.sort(instances)`排序
@ -153,21 +153,21 @@ private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] par
`SharedMetadataReaderFactoryContextInitializer` `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` - 同样的再找一个`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 数值返回 - 下图中的所有类都有 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() ### listeners.starting()
@ -360,7 +360,7 @@ private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] par
### exceptionReporters ### 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 ### prepareContext
@ -439,9 +439,9 @@ private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] par
context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); 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 ### applyInitializers
@ -466,7 +466,7 @@ private <T> List<T> createSpringFactoriesInstances(Class<T> 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 <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] par
- `primarySources` 就是我们的项目启动类,在`SpringApplication`的构造器中有`this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources))` - `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 ### load
@ -552,7 +552,7 @@ private int load(Object source) {
- 通过前文我们已经知道 `source`就是一个 class - 通过前文我们已经知道 `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 ```java
private int load(Class<?> source) { private int load(Class<?> source) {

@ -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`的源码 在看这部分源码之前需要先了解 `Conditional`和`Condition`的源码
@ -421,7 +421,7 @@ for (String type : spec.getTypes()) {
- 在忽略 bean 找到之后做一个类型移除的操作. - 在忽略 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); 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)` 继续进行 - 到此结果封装完毕.回到方法`org.springframework.boot.autoconfigure.condition.SpringBootCondition#matches(org.springframework.context.annotation.ConditionContext, org.springframework.core.type.AnnotatedTypeMetadata)` 继续进行
- 再往后就继续执行 spring 的 bean 初始化咯 - 再往后就继续执行 spring 的 bean 初始化咯
@ -492,7 +492,7 @@ public static ConditionOutcome noMatch(ConditionMessage message) {
- 根据类的注解信息我们可以找到有`ResourceBundleCondition` - 根据类的注解信息我们可以找到有`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`全路径 - 获取类名或者方法名的结果是`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)

@ -33,7 +33,7 @@ public @interface ConfigurationPropertiesScan {}
## ConfigurationPropertiesScanRegistrar ## 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 没有抓到后续补充 - debug 没有抓到后续补充
@ -137,11 +137,11 @@ public @interface EnableConfigurationProperties {
- 先看输入参数 **metadata** - 先看输入参数 **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 结果 - 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` - 源码开始,先找出刚才的对象`org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration`
@ -192,7 +192,7 @@ public @interface EnableConfigurationProperties {
## ConfigurationPropertiesBindingPostProcessor ## 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 ### postProcessBeforeInitialization
@ -301,15 +301,15 @@ public @interface EnableConfigurationProperties {
- `annotation` - `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` - `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 ##### 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 ##### getBindHandler
@ -464,7 +464,7 @@ private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperti
- 最终获取得到的处理器 - 最终获取得到的处理器
![image-20200323110603959](../../images/SpringBoot/image-20200323110603959.png) ![image-20200323110603959](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200323110603959.png)
- 最后的 bind - 最后的 bind
@ -498,7 +498,7 @@ private <T> BindHandler getBindHandler(Bindable<T> 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)
配置信息到此绑定成功,关于如何处理集合相关的配置请各位读者自行学习 配置信息到此绑定成功,关于如何处理集合相关的配置请各位读者自行学习

@ -19,7 +19,7 @@
- `org.springframework.boot.logging.java.JavaLoggingSystem` - `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 ```java
static { 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` - 默认日志: `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)
- 此时配置文件地址出现了 - 此时配置文件地址出现了

@ -9,17 +9,17 @@
2. 全局搜索 yml 2. 全局搜索 yml
![image-20200319083048849](../../images/SpringBoot/image-20200319083048849.png) ![image-20200319083048849](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200319083048849.png)
3. 换成`properties`搜索 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`为例打上断点开始源码追踪 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` `org.springframework.boot.context.config.ConfigFileApplicationListener#addPropertySources`
@ -68,13 +68,13 @@ protected void addPropertySources(ConfigurableEnvironment environment, ResourceL
- 搜索目标: `org.springframework.boot.env.PropertySourceLoader` - 搜索目标: `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 字符串的时候找到的类是一样的。说明搜索方式没有什么问题。 观察发现里面有一个`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 ### initializeProfiles
- 初始化`private Deque<Profile> profiles;` 属性 - 初始化`private Deque<Profile> 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 ### 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`解析同理不在次展开描述了 - `PropertiesPropertySourceLoader`解析同理不在次展开描述了

@ -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() ## 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";` - `protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";`
@ -131,11 +131,11 @@ public @interface EnableAutoConfiguration {
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\ 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 同样找一下 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`类 - 仔细看`org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration`类
@ -213,13 +213,13 @@ public class RedisProperties {
- `org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process` - `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()`的相关操作 再此之前我们看过了`getAutoConfigurationMetadata()`的相关操作
关注 `AnnotationMetadata annotationMetadata` 存储了一些什么 关注 `AnnotationMetadata annotationMetadata` 存储了一些什么
![image-20200320164145286](../../images/SpringBoot/image-20200320164145286.png) ![image-20200320164145286](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringBoot/image-20200320164145286.png)
这里简单理解 这里简单理解
@ -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 ### 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 ### checkExcludedClasses
@ -418,7 +418,7 @@ public class RedisProperties {
- `getAutoConfigurationImportFilters()` 从`spring.factories` 获取 `AutoConfigurationImportFilter`的接口 - `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`系列接口 - 循环内执行`Aware`系列接口
@ -426,7 +426,7 @@ public class RedisProperties {
- `filter.match(candidates, autoConfigurationMetadata)` 比较判断哪些是需要自动注入的类 - `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 ### 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);` - `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`** - `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` - `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)
- 后续的一些行为相对简单,直接放个源码了. - 后续的一些行为相对简单,直接放个源码了.

File diff suppressed because it is too large Load Diff

@ -102,13 +102,13 @@ public class GatewayClassPathWarningAutoConfiguration {
> >
> Route 是由 AsyncPredicate 和 GatewayFilter 组成的。而 AsyncPredicate 由 RoutePredicateFactory 生成GatewayF 创建 ilter 由 GatewayFilterFactory > 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>` ,遍历执行`Route.getPredicate().apply(ServerWebExchange)` 返回`true`说明命中了路由规则,将命中的 Route 存到 ServerWebExchange 中,然后执行 FilteringWebHandler 。 > RoutePredicateHandlerMapping 通过 RouteLocator 得到的 `Flux<Route>` ,遍历执行`Route.getPredicate().apply(ServerWebExchange)` 返回`true`说明命中了路由规则,将命中的 Route 存到 ServerWebExchange 中,然后执行 FilteringWebHandler 。
> >
> FilteringWebHandler 的逻辑就是执行 GlobalFilter + GatewayFilter > 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 中 因为 @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 ```java
/** /**
@ -1121,7 +1121,7 @@ public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator
看 PredicateDefinition、FilterDefinition 的构造器,就能明白属性文件为啥可以写 `Weight=group1,8` 看 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 ## InMemoryRouteDefinitionRepository
@ -1129,7 +1129,7 @@ InMemoryRouteDefinitionRepository 是由 [GatewayAutoConfiguration](#GatewayAuto
RouteDefinitionRepository 的职责是通过缓存的方式记录 RouteDefinition而不是通过属性 映射成 RouteDefinition。而 [AbstractGatewayControllerEndpoint](#AbstractGatewayControllerEndpoint) 会依赖 RouteDefinitionWriter 的实例,用来缓存通过接口方式注册的 RouteDefinition。 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 ```java
public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
@ -1186,7 +1186,7 @@ GatewayControllerEndpoint 和 GatewayLegacyControllerEndpoint 是由 [GatewayAut
刷新 RouteDefinition 是会发布 RefreshRoutesEvent 事件,该事件会有 [CachingRouteLocator](#RouteLocator) 处理 刷新 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 ## RouteRefreshListener

File diff suppressed because it is too large Load Diff

@ -1,317 +0,0 @@
# Spring Security 自定义用户认证
在**Spring Boot 中开启 Spring Security**一节中我们简单地搭建了一个 Spring Boot + Spring Security 的项目,其中登录页、用户名和密码都是由 Spring Security 自动生成的。Spring Security 支持我们自定义认证的过程,如使用自定义的登录页替换默认的登录页,用户信息的获取逻辑、登录成功或失败后的处理逻辑等。这里将在上一节的源码基础上进行改造。
## 配置自定义登录页
为了方便起见,我们直接在<strong><i>src/main/resources/resources</strong></i>目录下创建一个<strong><i>login.html</strong></i>(不需要 Controller 跳转):
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="css/login.css" type="text/css">
</head>
<body>
<form class="login-page" action="/login" method="post">
<div class="form">
<h3>账户登录</h3>
<input type="text" placeholder="用户名" name="username" required="required" />
<input type="password" placeholder="密码" name="password" required="required" />
<button type="submit">登录</button>
</div>
</form>
</body>
</html>
```
要怎么做才能让 Spring Security 跳转到我们自己定义的登录页面呢?很简单,只需要在<strong><i>BrowserSecurityConfig</strong></i><strong><i>configure</strong></i>中添加一些配置:
```
@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/</strong></i>", "/error").permitAll() // 无需认证
.anyRequest().authenticated() // 除antMatchers中配置路径外其他所有请求都需要认证
.and().csrf().disable();
}
}
```
上面代码中<strong><i>.loginPage("/login.html")</strong></i>指定了跳转到登录页面的请求 URL<strong><i>.loginProcessingUrl("/login")</strong></i>对应登录页面 form 表单的<strong><i>action="/login"</strong></i><strong><i>.antMatchers("/login.html", "/css/", "/error").permitAll()</strong></i>表示跳转到登录页面的请求不被拦截。
这时候启动系统,访问<strong><i>http://localhost:8080/hello</strong></i>,会看到页面已经被重定向到了<strong><i>http://localhost:8080/login.html</strong></i>
![img](../../images/SpringSecurity/d6bd19a2-08d3-4ba6-921c-5b5f57370a16.jpg)
## 配置用户信息的获取逻辑
Spring Security 默认会为我们生成一个用户名为 user密码随机的用户实例当然我们也可以定义自己用户信息的获取逻辑只需要实现 Spring Security 提供的**_UserDetailService_**接口即可,该接口只有一个抽象方法**_loadUserByUsername_**,具体实现如下:
```
@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 的用户信息的获取逻辑。这样,当我们启动项目,访问<strong><i>http://localhost:8080/login</strong></i>,只需要输入任意用户名以及 123456 作为密码即可登录系统。
## 源码解析
### BrowserConfig 配置解析
我们首先来梳理下<strong><i>BrowserConfig</strong></i>中的配置是如何被 Spring Security 所加载的。
首先找到调用<strong><i>BrowserConfig</strong></i><strong><i>configure()</strong></i>的地方,在其父类<strong><i>WebSecurityConfigurerAdapter</strong></i><strong><i>getHttp()</strong></i>中:
![img](../../images/SpringSecurity/12629a18-56ef-4286-9ab9-c124dc3d6791.png)
往上一步找到调用<strong><i>getHttp()</strong></i>的地方,在同个类的<strong><i>init()</strong></i>中:
![img](../../images/SpringSecurity/2b54af34-7d68-4f40-8726-d02d18e03dea.png)
往上一步找到调用<strong><i>init()</strong></i>的地方,在<strong><i>AbstractConfiguredSecurityBuilder</strong></i><strong><i>init()</strong></i>中:
![img](../../images/SpringSecurity/6e009bf1-aba3-4b89-8e86-d3d110e0f4a7.png)
<strong><i>init()</strong></i>被调用时,它首先会遍历<strong><i>getConfigurers()</strong></i>返回的集合中的元素,调用其<strong><i>init()</strong></i>,点击<strong><i>getConfigurers()</strong></i>查看,发现其读取的是<strong><i>configurers</strong></i>属性的值,那么<strong><i>configurers</strong></i>是什么时候被赋值的呢?我们在同个类的<strong><i>add()</strong></i>中找到<strong><i>configurers</strong></i>被赋值的代码:
![img](../../images/SpringSecurity/ed65fd2a-8a9f-4808-bc16-36128b4af47a.png)
往上一步找到调用<strong><i>add()</strong></i>的地方,在同个类的<strong><i>apply()</strong></i>中:
![img](../../images/SpringSecurity/1e929fec-d1ab-44b5-89bc-6e5ebcda1daf.png)
往上一步找到调用<strong><i>apply()</strong></i>的地方,在<strong><i>WebSecurityConfiguration</strong></i><strong><i>setFilterChainProxySecurityConfigurer()</strong></i>中:
![img](../../images/SpringSecurity/1840f96a-6a31-4fce-8a98-02fa7fc60fbf.png)
我们可以看到,在<strong><i>setFilterChainProxySecurityConfigurer()</strong></i>中,首先会实例化一个<strong><i>WebSecurity</strong></i><strong><i>AbstractConfiguredSecurityBuilder</strong></i>的实现类)的实例,遍历参数<strong><i>webSecurityConfigurers</strong></i>,将存储在其中的元素作为参数传递给<strong><i>WebSecurity</strong></i><strong><i>apply()</strong></i>,那么<strong><i>webSecurityConfigurers</strong></i>是什么时候被赋值的呢?我们根据<strong><i>@Value</strong></i>中的信息找到<strong><i>webSecurityConfigurers</strong></i>被赋值的地方,在<strong><i>AutowiredWebSecurityConfigurersIgnoreParents</strong></i><strong><i>getWebSecurityConfigurers()</strong></i>中:
![img](../../images/SpringSecurity/eb7a5916-4049-4682-9a11-10f1f1f94c74.png)
我们重点看第二句代码,可以看到这里会提取存储在 bean 工厂中类型为<strong><i>WebSecurityConfigurer.class</strong></i>的 bean<strong><i>BrowserConfig</strong></i>正是<strong><i>WebSecurityConfigurerAdapter</strong></i>的实现类。
解决完<strong><i>configurers</strong></i>的赋值问题,我们回到<strong><i>AbstractConfiguredSecurityBuilder</strong></i><strong><i>init()</strong></i>处,找到调用该方法的地方,在同个类的<strong><i>doBuild()</strong></i>中:
![img](../../images/SpringSecurity/522574e3-bacc-4794-a17e-492bc2b4457d.png)
往上一步找到调用<strong><i>doBuild()</strong></i>的地方,在<strong><i>AbstractSecurityBuilder</strong></i><strong><i>build()</strong></i>中:
![img](../../images/SpringSecurity/fb22f2ca-3d9a-420f-b77a-f9c0f737d9ad.png)
往上一步找到调用<strong><i>doBuild()</strong></i>的地方,在<strong><i>WebSecurityConfiguration</strong></i><strong><i>springSecurityFilterChain()</strong></i>中:
![img](../../images/SpringSecurity/a5c61feb-ca72-4768-94bd-1b0a8cf8af70.png)
至此,我们分析完了<strong><i>BrowserConfig</strong></i>被 Spring Security 加载的过程。现在我们再来看看当我们自定义的配置被加载完后,<strong><i>http</strong></i>各属性的变化,在<strong><i>BrowserConfig</strong></i><strong><i>configure()</strong></i>末尾打上断点,当程序走到断点处时,查看<strong><i>http</strong></i>属性:
![img](../../images/SpringSecurity/6c72c09b-742c-4415-851a-8ca5292a4969.png)
我们配置的<strong><i>.loginPage("/login.html")</strong></i><strong><i>.loginProcessingUrl("/login")</strong></i><strong><i>FormLoginConfigurer</strong></i>中:
![img](../../images/SpringSecurity/51ba02f0-bae6-4c08-9adf-7ee0f12b05d3.png)
配置的<strong><i>.antMatchers("/login.html", "/css/", "/error").permitAll()</strong></i><strong><i>ExpressionUrlAuthorizationConfigurer</strong></i>中:
![img](../../images/SpringSecurity/52390725-d87d-42b1-9071-ea21e445e1e6.png)
这样,当我们访问除<strong><i>"/login.html", "/css/", "/error"</strong></i>以外的路径时,在<strong><i>AbstractSecurityInterceptor</strong></i><strong><i>FilterSecurityInterceptor</strong></i>的父类)的<strong><i>attemptAuthorization()</strong></i>中会抛出<strong><i>AccessDeniedException</strong></i>异常(最终由<strong><i>AuthenticationTrustResolverImpl</strong></i><strong></i>isAnonymous()</strong></i>进行判断)
![img](../../images/SpringSecurity/558b8b1c-be32-44c4-8d8f-5f0d231741f8.png)
当我们访问<strong><i>"/login.html", "/css/", "/error"</strong></i>这几个路径时,在<strong><i>AbstractSecurityInterceptor</strong></i><strong><i>FilterSecurityInterceptor</strong></i>的父类)的<strong><i>attemptAuthorization()</strong></i>中正常执行(最终由<strong><i>SecurityExpressionRoot</strong></i><strong><i>permitAll()</strong></i>进行判断)
![img](../../images/SpringSecurity/4612c27e-dd9f-4e60-92dc-fc9858496ec5.png)
### login.html 路径解析
当我们请求的资源需要经过认证时Spring Security 会将请求重定向到我们自定义的登录页,那么 Spring 又是如何找到我们自定义的登录页的呢?下面就让我们来解析一下:
我们首先来到<strong><i>DispatcherServlet</strong></i>中,<strong><i>DispatcherServlet</strong></i>是 Spring Web 处理请求的入口。当 Spring Web 项目启动后,第一次接收到请求时,会调用其<strong><i>initStrategies()</strong></i>进行初始化:
![img](../../images/SpringSecurity/964ec4a4-6039-4205-8a87-ea2febcc00b6.png)
我们重点关注<strong><i>initHandlerMappings(context);</strong></i>这句,<strong><i>initHandlerMappings()</strong></i>用于初始化处理器映射器(处理器映射器可以根据请求找到对应的资源),我们来到<strong><i>initHandlerMappings()</strong></i>中:
![img](../../images/SpringSecurity/0a97b011-34ed-4d57-945e-95c8e6bafc8e.png)
可以看到,当程序走到<strong><i>initHandlerMappings()</strong></i>中时,会从 bean 工厂中找出<strong><i>HandlerMapping.class</strong></i>类型的 bean将其存储到<strong><i>handlerMappings</strong></i>属性中。这里看到一共找到 5 个,分别是:<strong><i>requestMappingHandlerMapping</strong></i>(将请求与标注了<strong><i>@RequestMapping</strong></i>的方法进行关联)、<strong><i>weclomePageHandlerMapping</strong></i>(将请求与主页进行关联)、<strong><i>beanNameHandlerMapping</strong></i>(将请求与同名的 bean 进行关联)、<strong><i>routerFunctionMapping</strong></i>(将请求与<strong><i>RouterFunction</strong></i>进行关联)、<strong><i>resourceHandlerMapping</strong></i>(将请求与静态资源进行关联),这 5 个 bean 是在<strong><i>WebMvcAutoConfiguration$EnableWebMvcConfiguration</strong></i>中配置的:
<strong><i>requestMappingHandlerMapping:</strong></i>
![img](../../images/SpringSecurity/cd7aee86-66f8-4197-99d1-1c9275e33bee.png)
<strong><i>weclomePageHandlerMapping:</strong></i>
![img](../../images/SpringSecurity/ef28372b-de89-46ff-8679-8b8feca04a7a.png)
<strong><i>beanNameHandlerMapping</strong></i><strong><i>routerFunctionMapping</strong></i><strong><i>resourceHandlerMapping</strong></i><strong><i>EnableWebMvcConfiguration</strong></i>的父类(<strong><i>WebMvcConfigurationSupport</strong></i>)中配置:
<strong><i>beanNameHandlerMapping:</strong></i>
![img](../../images/SpringSecurity/8d05ac54-f034-47d4-b750-67b2e3b3cd14.png)
<strong><i>routerFunctionMapping:</strong></i>
![img](../../images/SpringSecurity/481b88aa-028d-4392-8c0a-365f1d0e2ae9.png)
<strong><i>resourceHandlerMapping:</strong></i>
![img](../../images/SpringSecurity/fcdab503-2735-46bd-a5b6-226dc348e78c.png)
我们将目光锁定在<strong><i>resourceHandlerMapping</strong></i>上,当<strong><i>resourceHandlerMapping</strong></i>被初始化时,会调用<strong><i>addResourceHandlers()</strong></i><strong><i>registry</strong></i>添加资源处理器,我们找到实际被调用的<strong><i>addResourceHandlers()</strong></i>,在<strong><i>DelegatingWebMvcConfiguration</strong></i>中:
![img](../../images/SpringSecurity/6eca7b58-80f9-4e98-8483-62b4ef751854.png)
可以看到这里实际调用的是<strong><i>configurers</strong></i>属性的<strong><i>addResourceHandlers()</strong></i>,而<strong><i>configurers</strong></i>是一个 final 类型的成员变量,其值是<strong><i>WebMvcConfigurerComposite</strong></i>的实例,我们来到<strong><i>WebMvcConfigurerComposite</strong></i>中:
![img](../../images/SpringSecurity/c365e5ab-a7a3-4ebf-8a25-09cd2e049f22.png)
可以看到这里实际调用的是<strong><i>delegates</strong></i>属性的<strong><i>addResourceHandlers()</strong></i><strong><i>delegates</strong></i>是一个 final 类型的集合,集合的元素由<strong><i>addWebMvcConfigurers()</strong></i>负责添加:
![img](../../images/SpringSecurity/895afead-ea6b-4ac6-8138-fbe0a223daf9.png)
我们找到调用<strong><i>addWebMvcConfigurers()</strong></i>的地方,在<strong><i>DelegatingWebMvcConfiguration</strong></i><strong><i>setConfigurers()</strong></i>中:
![img](../../images/SpringSecurity/a20e06a4-5a43-4edf-bea1-53fc9bc929e9.png)
可以看到当<strong><i>setConfigurers()</strong></i>被初始化时Spring 会往参数<strong><i>configurers</strong></i>中传入两个值,我们关注第一个值,是一个<strong><i>WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter</strong></i>的实例,注意它的属性<strong><i>resourceProperties</strong></i>,是一个<strong><i>WebProperties$Resources</strong></i>的实例,默认情况下,在实例化<strong><i>WebMvcAutoConfigurationAdapter</strong></i>时,由传入参数<strong><i>webProperties</strong></i>进行赋值:<strong><i>webProperties.getResources()</strong></i>
![img](../../images/SpringSecurity/97b6f22c-414d-4a16-aa8e-c3deece2f7cd.png)
我们进入参数<strong><i>webProperties</strong></i>的类中,可以看到<strong><i>getResources()</strong></i>是直接实例化了一个<strong><i>Resources</strong></i>,其属性<strong><i>staticLocations</strong></i>是一个含有 4 个值的 final 类型的字符串数组,这 4 个值正是 Spring 寻找静态文件的地方:
![img](../../images/SpringSecurity/4d84fe43-2646-4a6f-a580-f39f6416d02d.png)
![img](../../images/SpringSecurity/25899949-dac3-4873-a2af-7abfe0e97615.png)
我们回到<strong><i>WebMvcAutoConfiguration</strong></i><strong><i>addResourceHandlers()</strong></i>中:![img](../../images/SpringSecurity/003ff2fa-022e-47cb-8aa9-343ed7c40c4a.png)
![img](../../images/SpringSecurity/ccd16b38-d724-4c03-949e-3b6ba03268a9.png)
<strong><i>addResourceHandlers()</strong></i>中,会为<strong><i>registry</strong></i>添加两个资源处理器,当请求路径是“/webjars/”时会在”classpath:/META-INF/resources/webjars/“路径下寻找对应的资源,当请求路径是“/\*\*”时会在”classpath:/META-INF/resources/“、”classpath:/resources/“、”classpath:/static/“、”classpath:/public/“路径下寻找对应的资源。
现在我们通过访问<strong><i>http://localhost:8080/login.html</strong></i>来验证这个过程。
请求首先来到<strong><i>DispatcherServlet</strong></i><strong><i>doDispatch()</strong></i>中,由于是对静态资源的请求,当程序走到<strong><i>mappedHandler = getHandler(processedRequest);</strong></i>时,通过<strong><i>getHandler()</strong></i>返回<strong><i>SimpleUrlHandlerMapping</strong></i>(即<strong><i>resourceHandlerMapping</strong></i>的类型)的<strong><i>HandlerExecutionChain</strong></i>
![img](../../images/SpringSecurity/4c9c302d-2ce0-4b5b-beb6-c76d2e94038f.png)
然后由实际的处理器进行处理:
![img](../../images/SpringSecurity/1590bae4-2e3a-4f97-b02d-d918c49cac22.png)
程序一路调试,来到<strong><i>ResourceHttpRequestHandler</strong></i><strong><i>handleRequest()</strong></i>中,通过调用<strong><i>Resource resource = getResource(request);</strong></i>找到请求对应的资源:
![img](../../images/SpringSecurity/0879e131-da5f-491e-a153-42770ce8b975.png)
而在<strong><i>getResource()</strong></i>中,实际是将请求路径(即<strong><i>login.html</strong></i>)与前面配置的路径进行拼接(组合成<strong><i>/resources/login.html</strong></i>这样的路径),再通过类加载器来寻找资源。
后面源码深入过深,就不一一展开了,只截取其中比较重要的几段代码:
<strong><i>PathResourceResolver</strong></i>中:
![img](../../images/SpringSecurity/6c58e27d-dd29-48fc-b597-8067e1c97786.png)
![img](../../images/SpringSecurity/c7ef78df-c2ab-4f89-b5ad-45561a91ffcc.png)
<strong><i>ClassPathResource</strong></i>中:
![img](../../images/SpringSecurity/42c5125e-0dc2-4c0c-9434-af4a9efd2d5d.png)
<strong><i>ClassLoader</strong></i>中:
![img](../../images/SpringSecurity/7842f83d-5417-4cb2-bb30-d70f98c3053f.png)
<strong><i>URLClassLoader</strong></i>中:
![img](../../images/SpringSecurity/870891e9-f8ea-4097-98c9-829b1cdcf145.png)
最终,类加载器会在如上两个路径下找到登录页并返回。
### UserDetailService 配置解析
我们定义的用户信息的获取逻辑是如何被 Spring Security 应用的呢?让我们通过阅读源码来了解一下。
还记得前面我们讲**_BrowserConfig 配置_**被加载的过程吗?**_UserDetailService_**也是在这个过程中被一起加载完成的,回到**BrowserConfig 配置解析**的第一幅图中,如下:
![img](../../images/SpringSecurity/68490740-e03c-4353-b5fc-ac99c0cf0435.png)
在断点处位置,<strong><i>authenticationManager()</strong></i>会返回一个**_AuthenticationManager_**实例,我们进入<strong><i>authenticationManager()</strong></i>中:
![img](../../images/SpringSecurity/e7a1a684-64db-41d0-a5c0-d9a841d86cc1.png)
<strong><i>authenticationManager()</strong></i>中,**_AuthenticationManager_**转由**_AuthenticationConfiguration_**中获取,我们进入<strong><i>getAuthenticationManager()</strong></i>中:
![img](../../images/SpringSecurity/d9ae84ae-d60c-4d9c-a7fd-cddeb1142f95.png)
程序来到**_AuthenticationConfiguration_**的<strong><i>getAuthenticationManager()</strong></i>中,**_AuthenticationManager_**转由**_AuthenticationManagerBuilder_**中获取,我们进入<strong><i>build()</strong></i>中:
![img](../../images/SpringSecurity/19f71152-f456-4db7-a1d5-f79aaa37253b.png)
程序来到**_AbstractConfiguredSecurityBuilder_**的<strong><i>doBuild()</strong></i>中,这里在构建**_AuthenticationManager_**实例时,需要初始化 3 个配置类,我们重点关注第 3 个配置类:**_org.springframework.security.config.annotation.authentication.configuration.InitializeUserDetailsBeanManagerConfigurer_**,这个配置类是在**_AuthenticationConfiguration_**中引入的:
![img](../../images/SpringSecurity/9f06c823-645d-413b-8bb6-1d81b8f329ea.png)
我们来到**_InitializeUserDetailsBeanManagerConfigurer_**的<strong><i>init()</strong></i>中:
![img](../../images/SpringSecurity/cd7d6cb9-c6e7-4570-aab8-309adcb15e16.png)
这里会新建一个**_InitializeUserDetailsManagerConfigurer_**实例添加到**_AuthenticationManagerBuilder_**中。我们回到<strong><i>doBuild()</strong></i>中:
![img](../../images/SpringSecurity/3ea76980-417d-4c0c-9330-e0bb241c6a47.png)
可以看到配置类变成了 5 个,其中就有刚刚新建的**_InitializeUserDetailsManagerConfigurer_**,程序接下来会调用各个配置类的<strong><i>configure()</strong></i>进行配置,我们来到**_InitializeUserDetailsManagerConfigurer_**的<strong><i>configure()</strong></i>中:
![img](../../images/SpringSecurity/ec39a9f6-c97d-4b7d-8843-d20358c1d194.png)
可以看到在<strong><i>configure()</strong></i>中,就会去 bean 工厂中寻找**_UserDetailsService_**类型的 bean若是我们没有自定义**_UserDetailsService_**的实现类的话Spring Security 默认会生成一个**_InMemoryUserDetailsManager_**的实例:
![img](../../images/SpringSecurity/c6a7370c-4afb-4c5d-aa35-ba9c3406b1ed.png)
**_InMemoryUserDetailsManager_**是在**_UserDetailsServiceAutoConfiguration_**类中配置的:
![img](../../images/SpringSecurity/476f8954-abe3-4e26-bfe1-8c5b4abbf0e0.png)
解决完**_UserDetailsService_**的加载问题,现在我们来看看 Spring Security 是如何通过**_UserDetailsService_**获取用户信息的。
通过**Spring Boot 中开启 Spring Security**一节的学习我们知道,登录判断的逻辑是在**_UsernamePasswordAuthenticationFilter_**中进行的,因此我们在**_UsernamePasswordAuthenticationFilter_**的<strong><i>attemptAuthenticatio()</strong></i>中打上断点,然后启动项目,访问登录页,输入用户名和密码点击登录后,程序来到**_UsernamePasswordAuthenticationFilter_**中:
![img](../../images/SpringSecurity/1282014b-fc29-4c2b-9316-9fdd638653c9.png)
这里将验证的逻辑交由**_AuthenticationManager_**进行,我们进入<strong><i>authenticate()</strong></i>中:
![img](../../images/SpringSecurity/5d511c93-3614-40e0-b3c9-9673c573d60f.png)
程序来到**_ProviderManager_**的<strong><i>authenticate()</strong></i>中,这里将验证的逻辑委托给其父类进行,再次点击进入<strong><i>authenticate()</strong></i>中:
![img](../../images/SpringSecurity/8dddd63e-c567-4b41-a9d9-8ef8aa6f2a92.png)
这里将验证的逻辑交由**_AuthenticationProvider_**进行,我们进入<strong><i>authenticate()</strong></i>中:
![img](../../images/SpringSecurity/ec796a9b-7c65-49a2-9f9f-7685af7bd57b.png)
程序来到**_AbstractUserDetailsAuthenticationProvider_**的<strong><i>authenticate()</strong></i>中,这里会根据用户名去寻找对应的用户实例,我们进入<strong><i>retrieveUser()</strong></i>中:
![img](../../images/SpringSecurity/3980e264-c073-456a-b808-715edd85633a.png)
程序来到**_DaoAuthenticationProvider_**的<strong><i>retrieveUser()</strong></i>中,可以看到正是在这里,会从**_UserDetailsService_**的<strong><i>loadUserByUsername()</strong></i>中寻找对应的用户信息。
## 参考
1. [Spring Security 自定义用户认证](https://mrbird.cc/Spring-Security-Authentication.html)

@ -34,7 +34,7 @@ public class HelloController {
这时候我们直接启动项目,访问<strong><i>http://localhost:8080/hello</i></strong>,可以看到页面跳转到一个登陆页面: 这时候我们直接启动项目,访问<strong><i>http://localhost:8080/hello</i></strong>,可以看到页面跳转到一个登陆页面:
![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 的控制台,可以找到密码信息: 默认的用户名为 user密码由 Sping Security 自动生成,回到 IDEA 的控制台,可以找到密码信息:
@ -50,11 +50,11 @@ Spring Security 默认为我们开启了一个简单的安全配置,下面让
当 Spring Boot 项目配置了 Spring Security 后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)
而当我们访问<strong><i>http://localhost:8080/hello</i></strong>时,代码的整个执行过程如下图所示: 而当我们访问<strong><i>http://localhost:8080/hello</i></strong>时,代码的整个执行过程如下图所示:
![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 包含了众多的过滤器,这些过滤器形成了一条链,所有请求都必须通过这些过滤器后才能成功访问到资源。 如上图所示Spring Security 包含了众多的过滤器,这些过滤器形成了一条链,所有请求都必须通过这些过滤器后才能成功访问到资源。
@ -62,69 +62,69 @@ Spring Security 默认为我们开启了一个简单的安全配置,下面让
首先,通过前面可以知道,当有请求来到时,最先由**_DelegatingFilterProxy_**负责接收,因此在**_DelegatingFilterProxy_**的<strong><i>doFilter()</i></strong>的首行打上断点: 首先,通过前面可以知道,当有请求来到时,最先由**_DelegatingFilterProxy_**负责接收,因此在**_DelegatingFilterProxy_**的<strong><i>doFilter()</i></strong>的首行打上断点:
![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_**的首行打上断点: 接着**_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_**会在<strong><i>doFilterInternal()</i></strong>中生成一个内部类**_VirtualFilterChain_**的实例,以此来调用 Spring Security 的整条过滤器链,在**_VirtualFilterChain_**的<strong><i>doFilter()</i></strong>首行打上断点: **_FilterChainProxy_**会在<strong><i>doFilterInternal()</i></strong>中生成一个内部类**_VirtualFilterChain_**的实例,以此来调用 Spring Security 的整条过滤器链,在**_VirtualFilterChain_**的<strong><i>doFilter()</i></strong>首行打上断点:
![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_**,我们依次在这些过滤器的<strong><i>doFilter()</i></strong>的首行打上断点: 接下来**_VirtualFilterChain_**会通过**_currentPosition_**依次调用存在**_additionalFilters_**中的过滤器,其中比较重要的几个过滤器有:**_UsernamePasswordAuthenticationFilter_**、**_DefaultLoginPageGeneratingFilter_**、**_AnonymousAuthenticationFilter_**、**_ExceptionTranslationFilter_**、**_FilterSecurityInterceptor_**,我们依次在这些过滤器的<strong><i>doFilter()</i></strong>的首行打上断点:
![image-20210811091815473](../../images/SpringSecurity/image-20210811091815473.png) ![image-20210811091815473](https://fastly.jsdelivr.net/gh/doocs/source-code-hunter@main/images/SpringSecurity/image-20210811091815473.png)
准备完毕后,我们启动项目,然后访问<strong><i>http://localhost:8080/hello</i></strong>,程序首先跳转到**_DelegatingFilterProxy_**的断点上: 准备完毕后,我们启动项目,然后访问<strong><i>http://localhost:8080/hello</i></strong>,程序首先跳转到**_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_**的实例: 此时**_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_**的<strong><i>doFilter()</i></strong>和**_VirtualFilterChain_**的<strong><i>doFilter()</i></strong>中: 接下来程序依次跳转到**_FilterChainProxy_**的<strong><i>doFilter()</i></strong>和**_VirtualFilterChain_**的<strong><i>doFilter()</i></strong>中:
![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_**的父类)的<strong><i>doFilter()</i></strong>中,通过<strong><i>requiresAuthentication()</i></strong>判定为 false是否是 POST 请求): 接着程序跳转到**_AbstractAuthenticationProcessingFilter_****_UsernamePasswordAuthenticationFilter_**的父类)的<strong><i>doFilter()</i></strong>中,通过<strong><i>requiresAuthentication()</i></strong>判定为 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_**的<strong><i>doFilter()</i></strong>中,通过<strong><i>isLoginUrlRequest()</i></strong>判定为 false请求路径是否是<strong><i>/login</i></strong> 接着程序跳转到**_DefaultLoginPageGeneratingFilter_**的<strong><i>doFilter()</i></strong>中,通过<strong><i>isLoginUrlRequest()</i></strong>判定为 false请求路径是否是<strong><i>/login</i></strong>
![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_**的<strong><i>doFilter()</i></strong>中,由于是首次请求,此时<strong><i>SecurityContextHolder.getContext().getAuthentication()</i></strong>为 null因此会生成一个**_AnonymousAuthenticationToken_**的实例: 接着程序跳转到**_AnonymousAuthenticationFilter_**的<strong><i>doFilter()</i></strong>中,由于是首次请求,此时<strong><i>SecurityContextHolder.getContext().getAuthentication()</i></strong>为 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_**的<strong><i>doFilter()</i></strong>中,**_ExceptionTranslationFilter_**负责处理**_FilterSecurityInterceptor_**抛出的异常,我们在 catch 代码块的首行打上断点: 接着程序跳转到**_ExceptionTranslationFilter_**的<strong><i>doFilter()</i></strong>中,**_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_**的<strong><i>doFilter()</i></strong>中,依次执行代码后程序停留在其父类(**_AbstractSecurityInterceptor_**)的<strong><i>attemptAuthorization()</i></strong>中: 接着程序跳转到**_FilterSecurityInterceptor_**的<strong><i>doFilter()</i></strong>中,依次执行代码后程序停留在其父类(**_AbstractSecurityInterceptor_**)的<strong><i>attemptAuthorization()</i></strong>中:
![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_**的<strong><i>decide()</i></strong>中: **_accessDecisionManager_**是**_AccessDecisionManager_**(访问决策器)的实例,**_AccessDecisionManager_**主要有 3 个实现类:**_AffirmativeBased_**(一票通过)**ConsensusBased**(少数服从多数)、UnanimousBased(一票否决),此时**_AccessDecisionManager_**的的实现类是**_AffirmativeBased_**,我们可以看到程序进入**_AffirmativeBased_**的<strong><i>decide()</i></strong>中:
![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)
从上图可以看出,决策的关键在<strong><i>voter.vote(authentication, object, configAttributes)</i></strong>这句代码上,通过跟踪调试,程序最终进入**_AuthenticationTrustResolverImpl_**的<strong><i>isAnonymous()</i></strong>中: 从上图可以看出,决策的关键在<strong><i>voter.vote(authentication, object, configAttributes)</i></strong>这句代码上,通过跟踪调试,程序最终进入**_AuthenticationTrustResolverImpl_**的<strong><i>isAnonymous()</i></strong>中:
![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)
<strong><i>isAssignableFrom()</i></strong>判断前者是否是后者的父类,而**_anonymousClass_**被固定为**_AnonymousAuthenticationToken.class_**,参数**_authentication_**由前面**_AnonymousAuthenticationFilter_**可以知道是**_AnonymousAuthenticationToken_**的实例,因此<strong><i>isAnonymous()</i></strong>返回 true**_FilterSecurityInterceptor_**抛出**_AccessDeniedException_**异常,程序返回**_ExceptionTranslationFilter_**的 catch 块中: <strong><i>isAssignableFrom()</i></strong>判断前者是否是后者的父类,而**_anonymousClass_**被固定为**_AnonymousAuthenticationToken.class_**,参数**_authentication_**由前面**_AnonymousAuthenticationFilter_**可以知道是**_AnonymousAuthenticationToken_**的实例,因此<strong><i>isAnonymous()</i></strong>返回 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_**的<strong><i>commence()</i></strong>决定重定向到<strong><i>/login</i></strong> 接着程序会依次进入**_DelegatingAuthenticationEntryPoint_**、**_LoginUrlAuthenticationEntryPoint_**中,最后由**_LoginUrlAuthenticationEntryPoint_**的<strong><i>commence()</i></strong>决定重定向到<strong><i>/login</i></strong>
![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)
后续对<strong><i>/login</i></strong>的请求同样会经过之前的执行流程,在**_DefaultLoginPageGeneratingFilter_**的<strong><i>doFilter()</i></strong>中,通过<strong><i>isLoginUrlRequest()</i></strong>判定为 true请求路径是否是<strong><i>/login</i></strong>,直接返回**_login.html_**,也就是我们开头看到的登录页面。 后续对<strong><i>/login</i></strong>的请求同样会经过之前的执行流程,在**_DefaultLoginPageGeneratingFilter_**的<strong><i>doFilter()</i></strong>中,通过<strong><i>isLoginUrlRequest()</i></strong>判定为 true请求路径是否是<strong><i>/login</i></strong>,直接返回**_login.html_**,也就是我们开头看到的登录页面。

@ -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。其源码如下。 下面看一下 javax.servlet.http 包下的内容,它提供了很多 我经常用到的类和接口比如HttpServlet、HttpServletRequest、HttpServletResponse。其源码如下。

@ -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: 分布式系统中的注册、限流、消息中间件核心源码拆解
---

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save