mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-08-05 20:31:37 +08:00
Compare commits
19 Commits
5af33e9c33
...
3684aac82c
Author | SHA1 | Date | |
---|---|---|---|
|
3684aac82c | ||
|
2b7f4afb43 | ||
|
ba8eb3a3aa | ||
|
db60285c1e | ||
|
57b064453c | ||
|
e0f4eaedd9 | ||
|
831658bfa2 | ||
|
fdfa5c336b | ||
|
d2da8bff81 | ||
|
431a122a81 | ||
|
8739559a36 | ||
|
581573b82e | ||
|
b30b149d3d | ||
|
9e4b0d1c29 | ||
|
1067e9ac4e | ||
|
217ce16333 | ||
|
20b64fc58b | ||
|
81febff7e5 | ||
|
f1de7ff5cb |
@ -294,6 +294,7 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle
|
||||
|
||||
**重要知识点详解**:
|
||||
|
||||
- [IoC & AOP详解(快速搞懂)](./docs/system-design/framework/spring/ioc-and-aop.md)
|
||||
- [Spring 事务详解](./docs/system-design/framework/spring/spring-transaction.md)
|
||||
- [Spring 中的设计模式详解](./docs/system-design/framework/spring/spring-design-patterns-summary.md)
|
||||
- [SpringBoot 自动装配原理详解](./docs/system-design/framework/spring/spring-boot-auto-assembly-principles.md)
|
||||
|
@ -401,6 +401,7 @@ export default sidebar({
|
||||
icon: "star",
|
||||
collapsible: true,
|
||||
children: [
|
||||
"ioc-and-aop",
|
||||
"spring-transaction",
|
||||
"spring-design-patterns-summary",
|
||||
"spring-boot-auto-assembly-principles",
|
||||
|
@ -20,7 +20,7 @@ tag:
|
||||
|
||||
首先,我们需要了解布隆过滤器的概念。
|
||||
|
||||
布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于 1970 年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
|
||||
布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于 1970 年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的 List、Map、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
|
||||
|
||||
Bloom Filter 会使用一个较大的 bit 数组来保存所有的数据,数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1(代表 false 或者 true),这也是 Bloom Filter 节省内存的核心所在。这样来算的话,申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 KB ≈ 122KB 的空间。
|
||||
|
||||
|
@ -176,8 +176,8 @@ public void postOrder(TreeNode root){
|
||||
if(root == null){
|
||||
return;
|
||||
}
|
||||
postOrder(root.left);
|
||||
postOrder(root.right);
|
||||
postOrder(root.left);
|
||||
system.out.println(root.data);
|
||||
}
|
||||
```
|
||||
|
@ -55,7 +55,7 @@ tag:
|
||||
|
||||
1. **计算机网络(简称网络)把许多计算机连接在一起,而互联网把许多网络连接在一起,是网络的网络。**
|
||||
2. 小写字母 i 开头的 internet(互联网)是通用名词,它泛指由多个计算机网络相互连接而成的网络。在这些网络之间的通信协议(即通信规则)可以是任意的。大写字母 I 开头的 Internet(互联网)是专用名词,它指全球最大的,开放的,由众多网络相互连接而成的特定的互联网,并采用 TCP/IP 协议作为通信规则,其前身为 ARPANET。Internet 的推荐译名为因特网,现在一般流行称为互联网。
|
||||
3. 路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
|
||||
3. 路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据段的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。
|
||||
4. 互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成核心部分,其作用是提供连通性和交换。
|
||||
5. 计算机通信是计算机中进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户-服务器方式(C/S 方式)和对等连接方式(P2P 方式)。
|
||||
6. 客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务提供方。
|
||||
@ -74,12 +74,12 @@ tag:
|
||||
|
||||
### 2.1. 基本术语
|
||||
|
||||
1. **数据(data)** :运送消息的实体。
|
||||
1. **数据(data)**:运送消息的实体。
|
||||
2. **信号(signal)**:数据的电气的或电磁的表现。或者说信号是适合在传输介质上传输的对象。
|
||||
3. **码元( code)**:在使用时间域(或简称为时域)的波形来表示数字信号时,代表不同离散数值的基本波形。
|
||||
4. **单工(simplex )** : 只能有一个方向的通信而没有反方向的交互。
|
||||
4. **单工(simplex )**:只能有一个方向的通信而没有反方向的交互。
|
||||
5. **半双工(half duplex )**:通信的双方都可以发送信息,但不能双方同时发送(当然也就不能同时接收)。
|
||||
6. **全双工(full duplex)** : 通信的双方可以同时发送和接收信息。
|
||||
6. **全双工(full duplex)**:通信的双方可以同时发送和接收信息。
|
||||
|
||||

|
||||
|
||||
@ -87,12 +87,12 @@ tag:
|
||||
|
||||

|
||||
|
||||
8. **奈氏准则** : 在任何信道中,码元的传输的效率是有上限的,传输速率超过此上限,就会出现严重的码间串扰问题,使接收端对码元的判决(即识别)成为不可能。
|
||||
8. **奈氏准则**:在任何信道中,码元的传输的效率是有上限的,传输速率超过此上限,就会出现严重的码间串扰问题,使接收端对码元的判决(即识别)成为不可能。
|
||||
9. **香农定理**:在带宽受限且有噪声的信道中,为了不产生误差,信息的数据传输速率有上限值。
|
||||
10. **基带信号(baseband signal)** : 来自信源的信号。指没有经过调制的数字信号或模拟信号。
|
||||
10. **基带信号(baseband signal)**:来自信源的信号。指没有经过调制的数字信号或模拟信号。
|
||||
11. **带通(频带)信号(bandpass signal)**:把基带信号经过载波调制后,把信号的频率范围搬移到较高的频段以便在信道中传输(即仅在一段频率范围内能够通过信道),这里调制过后的信号就是带通信号。
|
||||
12. **调制(modulation )** : 对信号源的信息进行处理后加到载波信号上,使其变为适合在信道传输的形式的过程。
|
||||
13. **信噪比(signal-to-noise ratio )** : 指信号的平均功率和噪声的平均功率之比,记为 S/N。信噪比(dB)=10\*log10(S/N)。
|
||||
12. **调制(modulation )**:对信号源的信息进行处理后加到载波信号上,使其变为适合在信道传输的形式的过程。
|
||||
13. **信噪比(signal-to-noise ratio )**:指信号的平均功率和噪声的平均功率之比,记为 S/N。信噪比(dB)=10\*log10(S/N)。
|
||||
14. **信道复用(channel multiplexing )**:指多个用户共享同一个信道。(并不一定是同时)。
|
||||
|
||||

|
||||
@ -101,7 +101,7 @@ tag:
|
||||
16. **波特率(baud rate)**:单位时间载波调制状态改变的次数。针对数据信号对载波的调制速率。
|
||||
17. **复用(multiplexing)**:共享信道的方法。
|
||||
18. **ADSL(Asymmetric Digital Subscriber Line )**:非对称数字用户线。
|
||||
19. **光纤同轴混合网(HFC 网)** :在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网
|
||||
19. **光纤同轴混合网(HFC 网)**:在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网
|
||||
|
||||
### 2.2. 重要知识点总结
|
||||
|
||||
@ -318,4 +318,4 @@ HTTP 协议的本质就是一种浏览器与服务器之间约定好的通信格
|
||||
3. 访问一个网站大致的过程
|
||||
4. 系统调用和应用编程接口概念
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
@ -82,7 +82,7 @@ ER 图由下面 3 个要素组成:
|
||||
|
||||
> 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
|
||||
>
|
||||
> 说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风 险; 外键影响数据库的插入速度
|
||||
> 说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度
|
||||
|
||||
为什么不要用外键呢?大部分人可能会这样回答:
|
||||
|
||||
@ -155,4 +155,4 @@ Tips:你应该更多地关注在使用场景上,而不是执行效率。
|
||||
- <https://www.zhihu.com/question/24696366/answer/29189700>
|
||||
- <https://blog.csdn.net/bieleyang/article/details/77149954>
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
@ -502,9 +502,9 @@ SQL 允许在 `JOIN` 左边加上一些修饰性的关键词,从而形成不
|
||||
|
||||

|
||||
|
||||
如果不加任何修饰词,只写 `JOIN`,那么默认为 `INNER JOIIN`
|
||||
如果不加任何修饰词,只写 `JOIN`,那么默认为 `INNER JOIN`
|
||||
|
||||
对于 `INNER JOIIN` 来说,还有一种隐式的写法,称为 “**隐式内连接**”,也就是没有 `INNER JOIIN` 关键字,使用 `WHERE` 语句实现内连接的功能
|
||||
对于 `INNER JOIN` 来说,还有一种隐式的写法,称为 “**隐式内连接**”,也就是没有 `INNER JOIN` 关键字,使用 `WHERE` 语句实现内连接的功能
|
||||
|
||||
```sql
|
||||
# 隐式内连接
|
||||
|
@ -21,7 +21,7 @@ category: 分布式
|
||||
|
||||
## 网关能提供哪些功能?
|
||||
|
||||
绝大部分网关可以提供下面这些功能:
|
||||
绝大部分网关可以提供下面这些功能(有一些功能需要借助其他框架或者中间件):
|
||||
|
||||
- **请求转发**:将请求转发到目标微服务。
|
||||
- **负载均衡**:根据各个微服务实例的负载情况或者具体的负载均衡策略配置对请求实现动态的负载均衡。
|
||||
@ -37,6 +37,7 @@ category: 分布式
|
||||
- **异常处理**:对于业务服务返回的异常响应,可以在网关层在返回给用户之前做转换处理。这样可以把一些业务侧返回的异常细节隐藏,转换成用户友好的错误提示返回。
|
||||
- **API 文档:** 如果计划将 API 暴露给组织以外的开发人员,那么必须考虑使用 API 文档,例如 Swagger 或 OpenAPI。
|
||||
- **协议转换**:通过协议转换整合后台基于 REST、AMQP、Dubbo 等不同风格和实现技术的微服务,面向 Web Mobile、开放平台等特定客户端提供统一服务。
|
||||
- **证书管理**:将 SSL 证书部署到 API 网关,由一个统一的入口管理接口,降低了证书更换时的复杂度。
|
||||
|
||||
下图来源于[百亿规模 API 网关服务 Shepherd 的设计与实现 - 美团技术团队 - 2021](https://mp.weixin.qq.com/s/iITqdIiHi3XGKq6u6FRVdg)这篇文章。
|
||||
|
||||
|
@ -50,11 +50,20 @@ category: 高可用
|
||||
|
||||
重试的核心思想是通过消耗服务器的资源来尽可能获得请求更大概率被成功处理。由于瞬态故障和偶然性故障是很少发生的,因此,重试对于服务器的资源消耗几乎是可以被忽略的。
|
||||
|
||||
### 常见的重试策略有哪些?
|
||||
|
||||
常见的重试策略有两种:
|
||||
|
||||
1. **固定间隔时间重试**:每次重试之间都使用相同的时间间隔,比如每隔1.5秒进行一次重试。这种重试策略的优点是实现起来比较简单,不需要考虑重试次数和时间的关系,也不需要维护额外的状态信息。但是这种重试策略的缺点是可能会导致重试过于频繁或过于稀疏,从而影响系统的性能和效率。如果重试间隔太短,可能会对目标系统造成过大的压力,导致雪崩效应;如果重试间隔太长,可能会导致用户等待时间过长,影响用户体验。
|
||||
2. **梯度间隔重试**:根据重试次数的增加去延长下次重试时间,比如第一次重试间隔为1秒,第二次为2秒,第三次为4秒,以此类推。这种重试策略的优点是能够有效提高重试成功的几率(随着重试次数增加,但是重试依然不成功,说明目标系统恢复时间比较长,因此可以根据重试次数延长下次重试时间),也能通过柔性化的重试避免对下游系统造成更大压力。但是这种重试策略的缺点是实现起来比较复杂,需要考虑重试次数和时间的关系,以及设置合理的上限和下限值。另外,这种重试策略也可能会导致用户等待时间过长,影响用户体验。
|
||||
|
||||
这两种适合的场景各不相同。固定间隔时间重试适用于目标系统恢复时间比较稳定和可预测的场景,比如网络波动或服务重启。梯度间隔重试适用于目标系统恢复时间比较长或不可预测的场景,比如网络故障和服务故障。
|
||||
|
||||
### 重试的次数如何设置?
|
||||
|
||||
重试的次数不宜过多,否则依然会对系统负载造成比较大的压力。
|
||||
|
||||
重试的次数通常建议设为 3 次。并且,我们通常还会设置重试的间隔,比如说我们要重试 3 次的话,第 1 次请求失败后,等待 1 秒再进行重试,第 2 次请求失败后,等待 2 秒再进行重试,第 3 次请求失败后,等待 3 秒再进行重试。
|
||||
重试的次数通常建议设为 3 次。大部分情况下,我们还是更建议使用梯度间隔重试策略,比如说我们要重试 3 次的话,第 1 次请求失败后,等待 1 秒再进行重试,第 2 次请求失败后,等待 2 秒再进行重试,第 3 次请求失败后,等待 3 秒再进行重试。
|
||||
|
||||
### 重试幂等
|
||||
|
||||
|
@ -123,6 +123,12 @@ Java 领域主流的微服务框架 Dubbo、Spring Cloud 等都内置了开箱
|
||||
|
||||
最小连接法可以尽可能最大地使请求分配更加合理化,提高服务器的利用率。不过,这种方法实现起来也最复杂,需要监控每一台服务器处理的请求连接数。
|
||||
|
||||
### 两次随机法
|
||||
|
||||
两次随机法在随机法的基础上多增加了一次随机,多选出一个服务器。随后再根据两台服务器的负载等情况,从其中选择出一个最合适的服务器。
|
||||
|
||||
两次随机法的好处是可以动态地调节后端节点的负载,使其更加均衡。如果只使用一次随机法,可能会导致某些服务器过载,而某些服务器空闲。
|
||||
|
||||
## 七层负载均衡可以怎么做?
|
||||
|
||||
简单介绍两种项目中常用的七层负载均衡解决方案:DNS 解析和反向代理。
|
||||
|
@ -164,9 +164,9 @@ RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都
|
||||
|
||||
## 说说 Broker 服务节点、Queue 队列、Exchange 交换器?
|
||||
|
||||
- **Broker**:可以看做 RabbitMQ 的服务节点。一般请下一个 Broker 可以看做一个 RabbitMQ 服务器。
|
||||
- **Queue** :RabbitMQ 的内部对象,用于存储消息。多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
|
||||
- **Exchange** : 生产者将消息发送到交换器,由交换器将消息路由到一个或者多个队列中。当路由不到时,或返回给生产者或直接丢弃。
|
||||
- **Broker**:可以看做 RabbitMQ 的服务节点。一般情况下一个 Broker 可以看做一个 RabbitMQ 服务器。
|
||||
- **Queue**:RabbitMQ 的内部对象,用于存储消息。多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
|
||||
- **Exchange**:生产者将消息发送到交换器,由交换器将消息路由到一个或者多个队列中。当路由不到时,或返回给生产者或直接丢弃。
|
||||
|
||||
## 什么是死信队列?如何导致的?
|
||||
|
||||
@ -244,4 +244,4 @@ Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用
|
||||
|
||||
RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
@ -290,6 +290,7 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8](https://docs.oracle.
|
||||
|
||||
**重要知识点详解**:
|
||||
|
||||
- [IoC & AOP详解(快速搞懂)](./system-design/framework/spring/ioc-and-aop.md)
|
||||
- [Spring 事务详解](./system-design/framework/spring/spring-transaction.md)
|
||||
- [Spring 中的设计模式详解](./system-design/framework/spring/spring-design-patterns-summary.md)
|
||||
- [SpringBoot 自动装配原理详解](./system-design/framework/spring/spring-boot-auto-assembly-principles.md)
|
||||
|
@ -178,9 +178,10 @@ value is JavaGuide
|
||||
```
|
||||
|
||||
**注意** : 有读者提到上面代码运行会抛出 `ClassNotFoundException` 异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 `TargetObject` 所在的包 。
|
||||
可以继续参考:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html
|
||||
|
||||
```java
|
||||
Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");
|
||||
```
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
@ -91,7 +91,7 @@ Java 中的 NIO 可以看作是 **I/O 多路复用模型**。也有很多人认
|
||||
|
||||
这个时候,**I/O 多路复用模型** 就上场了。
|
||||
|
||||

|
||||

|
||||
|
||||
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。
|
||||
|
||||
@ -112,7 +112,7 @@ AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO
|
||||
|
||||
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
|
||||
|
||||

|
||||

|
||||
|
||||
目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。
|
||||
|
||||
|
@ -314,7 +314,7 @@ Tomcat 这四个自定义的类加载器对应的目录如下:
|
||||
|
||||
- `CommonClassLoader`作为 `CatalinaClassLoader` 和 `SharedClassLoader` 的父加载器。`CommonClassLoader` 能加载的类都可以被 `CatalinaClassLoader` 和 `SharedClassLoader` 使用。因此,`CommonClassLoader` 是为了实现公共类库(可以被所有 Web 应用和 Tomcat 内部组件使用的类库)的共享和隔离。
|
||||
- `CatalinaClassLoader` 和 `SharedClassLoader` 能加载的类则与对方相互隔离。`CatalinaClassLoader` 用于加载 Tomcat 自身的类,为了隔离 Tomcat 本身的类和 Web 应用的类。`SharedClassLoader` 作为 `WebAppClassLoader` 的父加载器,专门来加载 Web 应用之间共享的类比如 Spring、Mybatis。
|
||||
- 每个 Web 应用都会创建一个单独的 `WebAppClassLoader`,并在启动 Web 应用的线程里设置线程上下文加载器为 `WebAppClassLoader`。各个 `WebAppClassLoader` 实例之间相互隔离,进而实现 Web 应用之间的类隔。
|
||||
- 每个 Web 应用都会创建一个单独的 `WebAppClassLoader`,并在启动 Web 应用的线程里设置线程线程上下文类加载器为 `WebAppClassLoader`。各个 `WebAppClassLoader` 实例之间相互隔离,进而实现 Web 应用之间的类隔。
|
||||
|
||||
单纯依靠自定义类加载器没办法满足某些场景的要求,例如,有些情况下,高层的类加载器需要加载低层的加载器才能加载的类。
|
||||
|
||||
@ -322,15 +322,15 @@ Tomcat 这四个自定义的类加载器对应的目录如下:
|
||||
|
||||
再比如,假设我们的项目中有 Spring 的 jar 包,由于其是 Web 应用之间共享的,因此会由 `SharedClassLoader` 加载(Web 服务器是 Tomcat)。我们项目中有一些用到了 Spring 的业务类,比如实现了 Spring 提供的接口、用到了 Spring 提供的注解。所以,加载 Spring 的类加载器(也就是 `SharedClassLoader`)也会用来加载这些业务类。但是业务类在 Web 应用目录下,不在 `SharedClassLoader` 的加载路径下,所以 `SharedClassLoader` 无法找到业务类,也就无法加载它们。
|
||||
|
||||
如何解决这个问题呢? 这个时候就需要用到 **线程上下文加载器(`ThreadContextClassLoader`)** 了。
|
||||
如何解决这个问题呢? 这个时候就需要用到 **线程上下文类加载器(`ThreadContextClassLoader`)** 了。
|
||||
|
||||
拿 Spring 这个例子来说,当 Spring 需要加载业务类的时候,它不是用自己的类加载器,而是用当前线程的上下文类加载器。还记得我上面说的吗?每个 Web 应用都会创建一个单独的 `WebAppClassLoader`,并在启动 Web 应用的线程里设置线程上下文加载器为 `WebAppClassLoader`。这样就可以让高层的类加载器(`SharedClassLoader`)借助子类加载器( `WebAppClassLoader`)来加载业务类,破坏了 Java 的类加载委托机制,让应用逆向使用类加载器。
|
||||
拿 Spring 这个例子来说,当 Spring 需要加载业务类的时候,它不是用自己的类加载器,而是用当前线程的上下文类加载器。还记得我上面说的吗?每个 Web 应用都会创建一个单独的 `WebAppClassLoader`,并在启动 Web 应用的线程里设置线程线程上下文类加载器为 `WebAppClassLoader`。这样就可以让高层的类加载器(`SharedClassLoader`)借助子类加载器( `WebAppClassLoader`)来加载业务类,破坏了 Java 的类加载委托机制,让应用逆向使用类加载器。
|
||||
|
||||
线程上下文加载器的原理是将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。这个类加载器通常是由应用程序或者容器(如 Tomcat)设置的。
|
||||
线程线程上下文类加载器的原理是将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。这个类加载器通常是由应用程序或者容器(如 Tomcat)设置的。
|
||||
|
||||
`Java.lang.Thread` 中的`getContextClassLoader()`和 `setContextClassLoader(ClassLoader cl)`分别用来获取和设置线程的上下文类加载器。如果没有通过`setContextClassLoader(ClassLoader cl)`进行设置的话,线程将继承其父线程的上下文类加载器。
|
||||
|
||||
Spring 获取线程上下文加载器的代码如下:
|
||||
Spring 获取线程线程上下文类加载器的代码如下:
|
||||
|
||||
```java
|
||||
cl = Thread.currentThread().getContextClassLoader();
|
||||
|
@ -227,7 +227,7 @@ public class ReferenceCountingGc {
|
||||
|
||||
**哪些对象可以作为 GC Roots 呢?**
|
||||
|
||||
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
|
||||
- 虚拟机栈(栈帧中的局部变量表)中引用的对象
|
||||
- 本地方法栈(Native 方法)中引用的对象
|
||||
- 方法区中类静态属性引用的对象
|
||||
- 方法区中常量引用的对象
|
||||
@ -511,4 +511,4 @@ $ java -XX:+UseZGC className
|
||||
- https://my.oschina.net/hosee/blog/644618
|
||||
- <https://docs.oracle.com/javase/specs/jvms/se8/html/index.html>
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
@ -16,6 +16,8 @@ head:
|
||||
<!-- @include: @small-advertisement.snippet.md -->
|
||||
|
||||
> 本篇文章由 JavaGuide 收集自网络,原出处不明。
|
||||
>
|
||||
> 比起这些枯燥的面试题,我更建议你看看文末推荐的 MyBatis 优质好文。
|
||||
|
||||
### #{} 和 \${} 的区别是什么?
|
||||
|
||||
@ -298,4 +300,12 @@ MyBatis 提供了 9 种动态 sql 标签:
|
||||
|
||||
面试题看似都很简单,但是想要能正确回答上来,必定是研究过源码且深入的人,而不是仅会使用的人或者用的很熟的人,以上所有面试题及其答案所涉及的内容,在我的 MyBatis 系列博客中都有详细讲解和原理分析。
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
||||
### 文章推荐
|
||||
|
||||
- [2W字全面剖析Mybatis中的9种设计模式](https://juejin.cn/post/7273516671574687759)
|
||||
- [从零开始实现一个MyBatis加解密插件](https://mp.weixin.qq.com/s/WUEAdFDwZsZ4EKO8ix0ijg)
|
||||
- [MyBatis最全使用指南](https://juejin.cn/post/7051910683264286750)
|
||||
- [脑洞打开!第一次看到这样使用MyBatis的,看得我一愣一愣的。](https://juejin.cn/post/7269390456530190376)
|
||||
- [MyBatis居然也有并发问题](https://juejin.cn/post/7264921613551730722)
|
213
docs/system-design/framework/spring/ioc-and-aop.md
Normal file
213
docs/system-design/framework/spring/ioc-and-aop.md
Normal file
@ -0,0 +1,213 @@
|
||||
---
|
||||
title: IoC & AOP详解(快速搞懂)
|
||||
category: 框架
|
||||
tag:
|
||||
- Spring
|
||||
---
|
||||
|
||||
这篇文章会从下面从以下几个问题展开对 IoC & AOP 的解释
|
||||
|
||||
- 什么是 IoC?
|
||||
- IoC 解决了什么问题?
|
||||
- IoC 和 DI 的区别?
|
||||
- 什么是 AOP?
|
||||
- AOP 解决了什么问题?
|
||||
- AOP 的应用场景有哪些?
|
||||
- AOP 为什么叫做切面编程?
|
||||
- AOP 实现方式有哪些?
|
||||
|
||||
首先声明:IoC & AOP 不是 Spring 提出来的,它们在 Spring 之前其实已经存在了,只不过当时更加偏向于理论。Spring 在技术层次将这两个思想进行了很好的实现。
|
||||
|
||||
## IoC (Inversion of control )
|
||||
|
||||
### 什么是 IoC?
|
||||
|
||||
IoC (Inversion of Control )即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。
|
||||
|
||||
例如:现有类 A 依赖于类 B
|
||||
|
||||
- **传统的开发方式** :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来
|
||||
- **使用 IoC 思想的开发方式** :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。
|
||||
|
||||
从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情)
|
||||
|
||||
**为什么叫控制反转?**
|
||||
|
||||
- **控制** :指的是对象创建(实例化、管理)的权力
|
||||
- **反转** :控制权交给外部环境(IoC 容器)
|
||||
|
||||

|
||||
|
||||
### IoC 解决了什么问题?
|
||||
|
||||
IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?
|
||||
|
||||
1. 对象之间的耦合度或者说依赖程度降低;
|
||||
2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
|
||||
|
||||
例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发
|
||||
|
||||
在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在`UserServiceImpl` 中手动 new 出 `IUserDao` 的具体实现类 `UserDaoImpl`(不能直接 new 接口类)。
|
||||
|
||||

|
||||
|
||||
很完美,这种方式也是可以实现的,但是我们想象一下如下场景:
|
||||
|
||||
开发过程中突然接到一个新的需求,针对对`IUserDao` 接口开发出另一个具体实现类。因为 Server 层依赖了`IUserDao`的具体实现,所以我们需要修改`UserServiceImpl`中 new 的对象。如果只有一个类引用了`IUserDao`的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了`IUserDao`的具体实现的话,一旦需要更换`IUserDao` 的实现方式,那修改起来将会非常的头疼。
|
||||
|
||||

|
||||
|
||||
使用 IoC 的思想,我们将对象的控制权(创建、管理)交有 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了
|
||||
|
||||

|
||||
|
||||
### IoC 和 DI 有区别吗?
|
||||
|
||||
IoC(Inverse of Control:控制反转)是一种设计思想或者说是某种模式。这个设计思想就是 **将原本在程序中手动创建对象的控制权交给第三方比如 IoC 容易。** 对于我们常用的 Spring 框架来说, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。不过,IoC 在其他语言中也有应用,并非 Spring 特有。
|
||||
|
||||
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
|
||||
|
||||
老马(Martin Fowler)在一篇文章中提到将 IoC 改名为 DI,原文如下,原文地址:<https://martinfowler.com/articles/injection.html> 。
|
||||
|
||||

|
||||
|
||||
老马的大概意思是 IoC 太普遍并且不表意,很多人会因此而迷惑,所以,使用 DI 来精确指名这个模式比较好。
|
||||
|
||||
## AOP(Aspect oriented programming)
|
||||
|
||||
这里不会涉及太多专业的术语,核心目的是将 AOP 的思想说清楚。
|
||||
|
||||
### 什么是 AOP?
|
||||
|
||||
AOP(Aspect Oriented Programming)即面向切面编程,AOP 是 OOP(面向对象编程)的一种延续,二者互补,并不对立。
|
||||
|
||||
AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。OOP 的目的是将业务逻辑按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),提高代码的可读性和可维护性。
|
||||
|
||||
### AOP 为什么叫面向切面编程?
|
||||
|
||||
AOP 之所以叫面向切面编程,是因为它的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的**切面(Aspect)**。
|
||||
|
||||
这里顺带总结一下 AOP 关键术语(不理解也没关系,可以继续往下看):
|
||||
|
||||
- **横切关注点(cross-cutting concerns)** :多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等)。
|
||||
- **切面(Aspect)**:对横切关注点进行封装的类,一个切面是一个类。切面可以定义多个通知,用来实现具体的功能。
|
||||
- **连接点(JoinPoint)**:连接点是方法调用或者方法执行时的某个特定时刻(如方法调用、异常抛出等)。
|
||||
- **通知(Advice)**:通知就是切面在某个连接点要执行的操作。通知有五种类型,分别是前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前四种通知都是在目标方法的前后执行,而环绕通知可以控制目标方法的执行过程。
|
||||
- **切点(Pointcut)**:一个切点是一个表达式,它用来匹配哪些连接点需要被切面所增强。切点可以通过注解、正则表达式、逻辑运算等方式来定义。比如 `execution(* com.xyz.service..*(..))`匹配 `com.xyz.service` 包及其子包下的类或接口。
|
||||
- **织入(Weaving)**:织入是将切面和目标对象连接起来的过程,也就是将通知应用到切点匹配的连接点上。常见的织入时机有两种,分别是编译期织入(AspectJ)和运行期织入(AspectJ)。
|
||||
|
||||
### AOP 解决了什么问题?
|
||||
|
||||
OOP 不能很好地处理一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等),这些行为通常被称为 **横切关注点(cross-cutting concerns)** 。如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。
|
||||
|
||||
AOP 可以将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从**核心业务逻辑(core concerns,核心关注点)**中分离出来,实现关注点的分离。
|
||||
|
||||

|
||||
|
||||
以日志记录为例进行介绍,假如我们需要对某些方法进行统一格式的日志记录,没有使用 AOP 技术之前,我们需要挨个写日志记录的逻辑代码,全是重复的的逻辑。
|
||||
|
||||
```java
|
||||
public CommonResponse<Object> method1() {
|
||||
// 业务逻辑
|
||||
xxService.method1();
|
||||
// 省略具体的业务处理逻辑
|
||||
// 日志记录
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
// 省略记录日志的具体逻辑 如:获取各种信息,写入数据库等操作...
|
||||
return CommonResponse.success();
|
||||
}
|
||||
|
||||
public CommonResponse<Object> method2() {
|
||||
// 业务逻辑
|
||||
xxService.method2();
|
||||
// 省略具体的业务处理逻辑
|
||||
// 日志记录
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
// 省略记录日志的具体逻辑 如:获取各种信息,写入数据库等操作...
|
||||
return CommonResponse.success();
|
||||
}
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
使用 AOP 技术之后,我们可以日志记录的逻辑封装成一个切面,然后通过切入点和通知来指定在哪些方法需要执行日志记录的操作。
|
||||
|
||||
```java
|
||||
|
||||
// 日志注解
|
||||
@Target({ElementType.PARAMETER,ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface Log {
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* 方法类型 INSERT DELETE UPDATE OTHER
|
||||
*/
|
||||
MethodType methodType() default MethodType.OTHER;
|
||||
}
|
||||
|
||||
// 日志切面
|
||||
@Component
|
||||
@Aspect
|
||||
public class LogAspect {
|
||||
// 切入点,所有被 Log 注解标注的方法
|
||||
@Pointcut("@annotation(cn.javaguide.annotation.Log)")
|
||||
public void webLog() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 环绕通知
|
||||
*/
|
||||
@Around("webLog()")
|
||||
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// 省略具体的处理逻辑
|
||||
}
|
||||
|
||||
// 省略其他代码
|
||||
}
|
||||
```
|
||||
|
||||
这样的话,我们一行注解即可实现日志记录:
|
||||
|
||||
```java
|
||||
@Log(description = "method1",methodType = MethodType.INSERT)
|
||||
public CommonResponse<Object> method1() {
|
||||
// 业务逻辑
|
||||
xxService.method1();
|
||||
// 省略具体的业务处理逻辑
|
||||
return CommonResponse.success();
|
||||
}
|
||||
```
|
||||
|
||||
### AOP 的应用场景有哪些?
|
||||
|
||||
- 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
|
||||
- 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
|
||||
- 事务管理:`@Transactional` 注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。`@Transactional`注解就是基于 AOP 实现的。
|
||||
- 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用`@PreAuthorize` 注解一行代码即可自定义权限校验。
|
||||
- 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
|
||||
- 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。
|
||||
- ......
|
||||
|
||||
### AOP 实现方式有哪些?
|
||||
|
||||
AOP 的常见实现方式有动态代理、字节码操作等方式。
|
||||
|
||||
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示:
|
||||
|
||||

|
||||
|
||||
当然你也可以使用 **AspectJ** !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
|
||||
|
||||
**Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
|
||||
|
||||
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
|
||||
|
||||
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
|
@ -191,7 +191,7 @@ public String[] selectImports(AnnotationMetadata annotationMetadata) {
|
||||
|
||||
该方法调用链如下:
|
||||
|
||||

|
||||

|
||||
|
||||
现在我们结合`getAutoConfigurationEntry()`的源码来详细分析一下:
|
||||
|
||||
@ -239,11 +239,11 @@ AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoC
|
||||
spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
从下图可以看到这个文件的配置内容都被我们读取到了。`XXXAutoConfiguration`的作用就是按需加载组件。
|
||||
|
||||

|
||||

|
||||
|
||||
不光是这个依赖下的`META-INF/spring.factories`被读取到,所有 Spring Boot Starter 下的`META-INF/spring.factories`都会被读取到。
|
||||
|
||||
@ -251,7 +251,7 @@ spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/MET
|
||||
|
||||
如果,我们自己要创建一个 Spring Boot Starter,这一步是必不可少的。
|
||||
|
||||

|
||||

|
||||
|
||||
**第 4 步**:
|
||||
|
||||
@ -259,7 +259,7 @@ spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/MET
|
||||
|
||||
很明显,这是不现实的。我们 debug 到后面你会发现,`configurations` 的值变小了。
|
||||
|
||||

|
||||

|
||||
|
||||
因为,这一步有经历了一遍筛选,`@ConditionalOnXXX` 中的所有条件都满足,该类才会生效。
|
||||
|
||||
@ -295,27 +295,27 @@ public class RabbitAutoConfiguration {
|
||||
|
||||
第一步,创建`threadpool-spring-boot-starter`工程
|
||||
|
||||

|
||||

|
||||
|
||||
第二步,引入 Spring Boot 相关依赖
|
||||
|
||||

|
||||

|
||||
|
||||
第三步,创建`ThreadPoolAutoConfiguration`
|
||||
|
||||

|
||||

|
||||
|
||||
第四步,在`threadpool-spring-boot-starter`工程的 resources 包下创建`META-INF/spring.factories`文件
|
||||
|
||||

|
||||

|
||||
|
||||
最后新建工程引入`threadpool-spring-boot-starter`
|
||||
|
||||

|
||||

|
||||
|
||||
测试通过!!!
|
||||
|
||||

|
||||

|
||||
|
||||
## 总结
|
||||
|
||||
|
@ -132,7 +132,7 @@ Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉
|
||||
相关阅读:
|
||||
|
||||
- [IoC 源码阅读](https://javadoop.com/post/spring-ioc)
|
||||
- [面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486938&idx=1&sn=c99ef0233f39a5ffc1b98c81e02dfcd4&chksm=cea24211f9d5cb07fa901183ba4d96187820713a72387788408040822ffb2ed575d28e953ce7&token=1736772241&lang=zh_CN#rd)
|
||||
- [IoC & AOP详解(快速搞懂)](./ioc-and-aop.md)
|
||||
|
||||
### 什么是 Spring Bean?
|
||||
|
||||
@ -359,7 +359,7 @@ Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某
|
||||
|
||||
当然你也可以使用 **AspectJ** !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
|
||||
|
||||
AOP 切面编程设计到的一些专业术语:
|
||||
AOP 切面编程涉及到的一些专业术语:
|
||||
|
||||
| 术语 | 含义 |
|
||||
| :---------------- | :-------------------------------------------------------------------: |
|
||||
@ -444,7 +444,7 @@ MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心
|
||||
|
||||
- Model:系统涉及的数据,也就是 dao 和 bean。
|
||||
- View:展示模型中的数据,只是用来展示。
|
||||
- Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。
|
||||
- Controller:接受用户请求,并将请求发送至 Model,最后返回数据给 JSP 并展示给用户
|
||||
|
||||

|
||||
|
||||
@ -463,7 +463,7 @@ MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring
|
||||
记住了下面这些组件,也就记住了 SpringMVC 的工作原理。
|
||||
|
||||
- **`DispatcherServlet`**:**核心的中央处理器**,负责接收请求、分发,并给予客户端响应。
|
||||
- **`HandlerMapping`**:**处理器映射器**,根据 uri 去匹配查找能处理的 `Handler` ,并会将请求涉及到的拦截器和 `Handler` 一起封装。
|
||||
- **`HandlerMapping`**:**处理器映射器**,根据 URL 去匹配查找能处理的 `Handler` ,并会将请求涉及到的拦截器和 `Handler` 一起封装。
|
||||
- **`HandlerAdapter`**:**处理器适配器**,根据 `HandlerMapping` 找到的 `Handler` ,适配执行对应的 `Handler`;
|
||||
- **`Handler`**:**请求处理器**,处理实际请求的处理器。
|
||||
- **`ViewResolver`**:**视图解析器**,根据 `Handler` 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 `DispatcherServlet` 响应客户端
|
||||
@ -479,7 +479,7 @@ MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring
|
||||
**流程说明(重要):**
|
||||
|
||||
1. 客户端(浏览器)发送请求, `DispatcherServlet`拦截请求。
|
||||
2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping` 。`HandlerMapping` 根据 uri 去匹配查找能处理的 `Handler`(也就是我们平常说的 `Controller` 控制器) ,并会将请求涉及到的拦截器和 `Handler` 一起封装。
|
||||
2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping` 。`HandlerMapping` 根据 URL 去匹配查找能处理的 `Handler`(也就是我们平常说的 `Controller` 控制器) ,并会将请求涉及到的拦截器和 `Handler` 一起封装。
|
||||
3. `DispatcherServlet` 调用 `HandlerAdapter`适配器执行 `Handler` 。
|
||||
4. `Handler` 完成对用户请求的处理后,会返回一个 `ModelAndView` 对象给`DispatcherServlet`,`ModelAndView` 顾名思义,包含了数据模型以及相应的视图的信息。`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。
|
||||
5. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。
|
||||
|
@ -176,7 +176,7 @@ Session-Cookie 方案在单体环境是一个非常好的身份认证方案。
|
||||
|
||||
一般是通过 `Cookie` 来保存 `SessionID` ,假如你使用了 `Cookie` 保存 `SessionID` 的方案的话, 如果客户端禁用了 `Cookie`,那么 `Session` 就无法正常工作。
|
||||
|
||||
但是,并不是没有 `Cookie` 之后就不能用 `Session` 了,比如你可以将 `SessionID` 放在请求的 `url` 里面`https://javaguide.cn/?Session_id=xxx` 。这种方案的话可行,但是安全性和用户体验感降低。当然,为了你也可以对 `SessionID` 进行一次加密之后再传入后端。
|
||||
但是,并不是没有 `Cookie` 之后就不能用 `Session` 了,比如你可以将 `SessionID` 放在请求的 `url` 里面`https://javaguide.cn/?Session_id=xxx` 。这种方案的话可行,但是安全性和用户体验感降低。当然,为了安全你也可以对 `SessionID` 进行一次加密之后再传入后端。
|
||||
|
||||
## 为什么 Cookie 无法防止 CSRF 攻击,而 Token 可以?
|
||||
|
||||
@ -255,4 +255,4 @@ OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了
|
||||
- Introduction to JSON Web Tokens:https://jwt.io/introduction
|
||||
- JSON Web Token Claims:https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-token-claims
|
||||
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
<!-- @include: @article-footer.snippet.md -->
|
||||
|
Loading…
x
Reference in New Issue
Block a user