diff --git a/README.md b/README.md
index ae432bc1..18388cdf 100644
--- a/README.md
+++ b/README.md
@@ -16,9 +16,7 @@
- [一个月学Docker](zh-cn/docker/docker-4-weeks/)
-#### 5.云原生
+#### 5.专题课程
-#### 6.专题课程
-
-- [极客时间]()
\ No newline at end of file
+- [极客时间]()
\ No newline at end of file
diff --git "a/zh-cn/course/spring/01 Spring Bean \345\256\232\344\271\211\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/01 Spring Bean \345\256\232\344\271\211\345\270\270\350\247\201\351\224\231\350\257\257.md"
index 06f3ee0b..cd42a864 100644
--- "a/zh-cn/course/spring/01 Spring Bean \345\256\232\344\271\211\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/01 Spring Bean \345\256\232\344\271\211\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -10,7 +10,7 @@
在构建 Web 服务时,我们常使用 Spring Boot 来快速构建。例如,使用下面的包结构和相关代码来完成一个简易的 Web 版 HelloWorld:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/06dff389d2fe498f83b115052e2c37cc.jpg)
+![](assets/01_01.jpg)
其中,负责启动程序的 Application 类定义如下:
@@ -28,7 +28,7 @@ package com.spring.puzzle.class1.example1.application //省略 import @RestContr
但是,假设有一天,当我们需要添加多个类似的 Controller,同时又希望用更清晰的包层次和结构来管理时,我们可能会去单独建立一个独立于 application 包之外的 Controller 包,并调整类的位置。调整后结构示意如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/eec656c5109d470a854c41a74a725494.jpg)
+![](assets/01_02.jpg)
实际上,我们没有改变任何代码,只是改变了包的结构,但是我们会发现这个 Web 应用失效了,即不能识别出 HelloWorldController 了。也就是说,我们找不到 HelloWorldController 这个 Bean 了。这是为何?
@@ -52,7 +52,7 @@ public @interface ComponentScan { /** * Base packages to scan for annotated comp
而在我们的案例中,我们直接使用的是 SpringBootApplication 注解定义的 ComponentScan,它的 basePackages 没有指定,所以默认为空(即{})。此时扫描的是什么包?这里不妨带着这个问题去调试下(调试位置参考 ComponentScanAnnotationParser#parse 方法),调试视图如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/13447ea3fe70423cb7e44d66a33689b8.jpg)
+![](assets/01_03.jpg)
从上图可以看出,当 basePackages 为空时,扫描的包会是 declaringClass 所在的包,在本案例中,declaringClass 就是 Application.class,所以扫描的包其实就是它所在的包,即com.spring.puzzle.class1.example1.application。
@@ -124,7 +124,7 @@ return this.beanFactory.resolveDependency( new DependencyDescriptor(param, true)
如果用调试视图,我们则可以看到更多的信息:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/66f90d9085eb4658a12e4a526752d328.jpg)
+![](assets/01_04.jpg)
如图所示,上述的调用即是根据参数来寻找对应的 Bean,在本案例中,如果找不到对应的 Bean 就会抛出异常,提示装配失败。
@@ -214,7 +214,7 @@ protected void inject(Object bean, @Nullable String beanName, @Nullable Property
首先,我们可以通过调试方式看下方法的执行,参考下图:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/f4ff060783ff4b228c1a65563975eead.jpg)
+![](assets/01_05.jpg)
从上图我们可以看出,我们最终的执行因为标记了 Lookup 而走入了 CglibSubclassingInstantiationStrategy.LookupOverrideMethodInterceptor,这个方法的关键实现参考 LookupOverrideMethodInterceptor#intercept:
@@ -242,7 +242,7 @@ private final BeanFactory owner; public Object intercept(Object obj, Method meth
添加后效果图如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/4285e0e5eff4486ca6e718d0cdc9e291.jpg)
+![](assets/01_06.jpg)
以上即为 Lookup 的一些关键实现思路。还有很多细节,例如CGLIB子类如何产生,无法一一解释,有兴趣的话,可以进一步深入研究,留言区等你。
diff --git "a/zh-cn/course/spring/02 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md" "b/zh-cn/course/spring/02 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
index dd9a3192..7e2bac79 100644
--- "a/zh-cn/course/spring/02 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
+++ "b/zh-cn/course/spring/02 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
@@ -30,7 +30,7 @@ public interface DataService { void deleteStudent(int id); } @Repository @Slf4j
实际上,当我们完成支持多个数据库的准备工作时,程序就已经无法启动了,报错如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/b731c4a063eb453f880cb8eed3687567.jpg)
+![](assets/02_01.jpg)
很显然,上述报错信息正是我们这一小节讨论的错误,那么这个错误到底是怎么产生的呢?接下来我们具体分析下。
@@ -67,7 +67,7 @@ InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), p
为了更清晰地展示错误发生的位置,我们可以采用调试的视角展示其位置(即DefaultListableBeanFactory#doResolveDependency中代码片段),参考下图:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/96842c40dffc44cc818fc225c563c909.jpg)
+![](assets/02_02.jpg)
如上图所示,当我们根据DataService这个类型来找出依赖时,我们会找出2个依赖,分别为CassandraDataService和OracleDataService。在这样的情况下,如果同时满足以下两个条件则会抛出本案例的错误:
@@ -155,7 +155,7 @@ raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
一旦找出这些Bean的信息,就可以生成这些Bean的名字,然后组合成一个个BeanDefinitionHolder返回给上层。这个过程关键步骤可以查看下图的代码片段(ClassPathBeanDefinitionScanner#doScan):
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/96ad15af93994cdab1a43b3a03f9f286.jpg)
+![](assets/02_03.jpg)
基本匹配我们前面描述的过程,其中方法调用BeanNameGenerator#generateBeanName即用来产生Bean的名字,它有两种实现方式。因为DataService的实现都是使用注解标记的,所以Bean名称的生成逻辑最终调用的其实是AnnotationBeanNameGenerator#generateBeanName这种实现方式,我们可以看下它的具体实现,代码如下:
diff --git "a/zh-cn/course/spring/03 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md" "b/zh-cn/course/spring/03 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
index 82776c54..539dd6b3 100644
--- "a/zh-cn/course/spring/03 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
+++ "b/zh-cn/course/spring/03 Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
@@ -86,7 +86,7 @@ String strVal = resolveEmbeddedValue((String) value);
这里其实是在解析嵌入的值,实际上就是“替换占位符”工作。具体而言,它采用的是PropertySourcesPlaceholderConfigurer根据PropertySources来替换。不过当使用 ${username} 来获取替换值时,其最终执行的查找并不是局限在application.property文件中的。通过调试,我们可以看到下面的这些“源”都是替换依据:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/579184faf70a4425b7fbf42cd24aa3d5.jpg)
+![](assets/03_01.jpg)
```bash
[ConfigurationPropertySourcesPropertySource {name='configurationProperties'}, StubPropertySource {name='servletConfigInitParams'}, ServletContextPropertySource {name='servletContextInitParams'}, PropertiesPropertySource {name='systemProperties'}, OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}, RandomValuePropertySource {name='random'}, OriginTrackedMapPropertySource {name='applicationConfig: classpath:/application.properties]'}, MapPropertySource {name='devtools'}]
@@ -102,7 +102,7 @@ String strVal = resolveEmbeddedValue((String) value);
如果我们查看systemEnvironment这个源,会发现刚好有一个username和我们是重合的,且值不是pass。
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/ab560187bc08439b980ea314e04e9aca.jpg)
+![](assets/03_02.jpg)
所以,讲到这里,你应该知道问题所在了吧?这是一个误打误撞的例子,刚好系统环境变量(systemEnvironment)中含有同名的配置。实际上,对于系统参数(systemProperties)也是一样的,这些参数或者变量都有很多,如果我们没有意识到它的存在,起了一个同名的字符串作为@Value的值,则很容易引发这类问题。
diff --git "a/zh-cn/course/spring/04 Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/04 Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\270\270\350\247\201\351\224\231\350\257\257.md"
index b27dcd6e..9b36c7ec 100644
--- "a/zh-cn/course/spring/04 Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/04 Spring Bean \347\224\237\345\221\275\345\221\250\346\234\237\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -26,7 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.spring
然而事与愿违,我们得到的只会是 NullPointerException,错误示例如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/496f362c7239432d8f35c2b01e263ba9.jpg)
+![](assets/04_01.jpg)
这是为什么呢?
@@ -34,7 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.spring
显然这是新手最常犯的错误,但是问题的根源,是我们**对Spring类初始化过程没有足够的了解**。下面这张时序图描述了 Spring 启动时的一些关键结点:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/69de7d4cd2dc4b3084d219acfd3b594d.jpg)
+![](assets/04_02.jpg)
这个图初看起来复杂,我们不妨将其分为三部分:
diff --git "a/zh-cn/course/spring/05 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md" "b/zh-cn/course/spring/05 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
index d84a0259..deaec08f 100644
--- "a/zh-cn/course/spring/05 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
+++ "b/zh-cn/course/spring/05 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
@@ -38,11 +38,11 @@ Spring AOP是Spring中除了依赖注入外(DI)最为核心的功能,顾
我们可以从源码中找到真相。首先来设置个断点,调试看看this对应的对象是什么样的:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/5498c998bac045dc8ad374e868fcf8d1.jpg)
+![](assets/05_01.jpg)
可以看到,this对应的就是一个普通的ElectricService对象,并没有什么特别的地方。再看看在Controller层中自动装配的ElectricService对象是什么样:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/518ef134060c4de883bd2df066db9c40.jpg)
+![](assets/05_02.jpg)
可以看到,这是一个被Spring增强过的Bean,所以执行charge()方法时,会执行记录接口调用时间的增强操作。而this对应的对象只是一个普通的对象,并没有做任何额外的增强。
@@ -52,7 +52,7 @@ Spring AOP是Spring中除了依赖注入外(DI)最为核心的功能,顾
Spring AOP的底层是动态代理。而创建代理的方式有两种,**JDK的方式和CGLIB的方式**。JDK动态代理只能对实现了接口的类生成代理,而不能针对普通类。而CGLIB是可以针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,来实现代理对象。具体区别可参考下图:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/6dabec74d4d34deab987b094e480244c.jpg)
+![](assets/05_03.jpg)
**2\. 如何使用Spring AOP**
@@ -64,7 +64,7 @@ Spring AOP的底层是动态代理。而创建代理的方式有两种,**JDK
补充完最基本的Spring底层知识和使用知识后,我们具体看下创建代理对象的过程。先来看下调用栈:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/4a8c347cef6f47d5870cc590340d1378.jpg)
+![](assets/05_04.jpg)
创建代理对象的时机就是创建一个Bean的时候,而创建的的关键工作其实是由AnnotationAwareAspectJAutoProxyCreator完成的。它本质上是一种BeanPostProcessor。所以它的执行是在完成原始Bean构建后的初始化Bean(initializeBean)过程中。而它到底完成了什么工作呢?我们可以看下它的postProcessAfterInitialization方法:
@@ -100,7 +100,7 @@ protected Object createProxy(Class> beanClass, @Nullable String beanName, @Nul
不过使用这种方法有个小前提,就是需要在@EnableAspectJAutoProxy里加一个配置项exposeProxy = true,表示将代理对象放入到ThreadLocal,这样才可以直接通过 AopContext.currentProxy()的方式获取到,否则会报错如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/8c4f7f4dc86741c283289ba6de698700.jpg)
+![](assets/05_05.jpg)
按这个思路,我们修改下相关代码:
@@ -160,7 +160,7 @@ Electric charging ... admin user login... User pay num : 202101166 Pay with alip
本来一切正常的代码,因为引入了一个AOP切面,抛出了NullPointerException。这会是什么原因呢?我们先debug一下,来看看加入AOP后调用的对象是什么样子。
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/1238fcfd96a747688ef9be88a2f7f7d4.jpg)
+![](assets/05_06.jpg)
可以看出,加入AOP后,我们的对象已经是一个代理对象了,如果你眼尖的话,就会发现在上图中,属性adminUser确实为null。为什么会这样?为了解答这个诡异的问题,我们需要进一步理解Spring使用CGLIB生成Proxy的原理。
@@ -200,7 +200,7 @@ protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callb
这里我们可以了解到,Spring会默认尝试使用objenesis方式实例化对象,如果失败则再次尝试使用常规方式实例化对象。现在,我们可以进一步查看objenesis方式实例化对象的流程。
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/83462c65b9ae4cd1b866eee554137674.jpg)
+![](assets/05_07.jpg)
参照上述截图所示调用栈,objenesis方式最后使用了JDK的ReflectionFactory.newConstructorForSerialization()完成了代理对象的实例化。而如果你稍微研究下这个方法,你会惊讶地发现,这种方式创建出来的对象是不会初始化类成员变量的。
@@ -250,11 +250,11 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy
说到这里,我们已经解决了问题。但如果你看得仔细,就会发现,其实你改变一个属性,也可以让产生的代理对象的属性值不为null。例如修改启动参数spring.objenesis.ignore如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/b254a5540742452b8eeb60577f38095c.jpg)
+![](assets/05_07.jpg)
此时再调试程序,你会发现adminUser已经不为null了:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/eda0228b7bc34cfeb2297e82ccc60e4c.jpg)
+![](assets/05_08.jpg)
所以这也是解决这个问题的一种方法,相信聪明的你已经能从前文贴出的代码中找出它能够工作起来的原理了。
diff --git "a/zh-cn/course/spring/06 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md" "b/zh-cn/course/spring/06 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
index 3284e6fb..df5bf5d2 100644
--- "a/zh-cn/course/spring/06 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
+++ "b/zh-cn/course/spring/06 Spring AOP \345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
@@ -71,7 +71,7 @@ protected List findEligibleAdvisors(Class> beanClass, String beanName
后续 Bean 创建代理时,直接拿出这个排序好的候选 Advisors。候选 Advisors 排序发生在 Bean 构建这个结论时,我们也可以通过 AopConfig Bean 构建中的堆栈信息验证:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/7e622f4a23b640b4842497602ef22200.jpg)
+![](assets/06_01.jpg)
可以看到,排序是在 Bean 的构建中进行的,而最后排序执行的关键代码位于下面的方法中(参考 ReflectiveAspectJAdvisorFactory#getAdvisorMethods):
diff --git "a/zh-cn/course/spring/07 Spring\344\272\213\344\273\266\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/07 Spring\344\272\213\344\273\266\345\270\270\350\247\201\351\224\231\350\257\257.md"
index 59492ce5..f50bdc83 100644
--- "a/zh-cn/course/spring/07 Spring\344\272\213\344\273\266\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/07 Spring\344\272\213\344\273\266\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -6,7 +6,7 @@
Spring事件的设计比较简单。说白了,就是监听器设计模式在Spring中的一种实现,参考下图:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/998134895f5e421d8e1acba0ec46be96.jpg)
+![](assets/07_01.jpg)
从图中我们可以看出,Spring事件包含以下三大组件。
@@ -122,7 +122,7 @@ private void startBeans(boolean autoStartupOnly) { Map lifecy
现在我们调试下代码,你会发现这个方法在Spring启动时一定经由SpringApplication#prepareEnvironment方法调用,调试截图如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/16a124d3fef34f5dbcf738be5fef393c.jpg)
+![](assets/07_02.jpg)
表面上看,既然代码会被调用,事件就会抛出,那么我们在最开始定义的监听器就能处理,但是我们真正去运行程序时会发现,效果和案例1是一样的,都是监听器的处理并不执行,即拦截不了。这又是为何?
@@ -142,7 +142,7 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
如果继续查看代码,我们会发现这个事件的监听器就存储在SpringApplication#Listeners中,调试下就可以找出所有的监听器,截图如下:
-![](https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/Spring%e7%bc%96%e7%a8%8b%e5%b8%b8%e8%a7%81%e9%94%99%e8%af%af50%e4%be%8b/assets/9a29c0f2db8c4d47a538947896b97bde.jpg)
+![](assets/07_03.jpg)
从中我们可以发现并不存在我们定义的MyApplicationEnvironmentPreparedEventListener,这是为何?
diff --git "a/zh-cn/course/spring/08 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring Core \347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md" "b/zh-cn/course/spring/08 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring Core \347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md"
index 8debd9ce..94f32cd8 100644
--- "a/zh-cn/course/spring/08 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring Core \347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md"
+++ "b/zh-cn/course/spring/08 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring Core \347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md"
@@ -52,7 +52,7 @@
查看决策谁优先的源码,最终使用属性名来匹配的执行情况可参考DefaultListableBeanFactory#matchesBeanName方法的调试视图:
-![](08%20%E7%AD%94%E7%96%91%E7%8E%B0%E5%9C%BA%EF%BC%9ASpring%20Core%20%E7%AF%87%E6%80%9D%E8%80%83%E9%A2%98%E5%90%88%E9%9B%86/5ebdca5725f745d59e7b18fd160d3c19.jpg)
+![](assets/08_01.jpg)
我们可以看到实现的关键其实是下面这行语句:
@@ -182,11 +182,11 @@ public int compare(@Nullable Object o1, @Nullable Object o2) { return doCompare(
继续跟踪 getOrder() 的执行细节,我们会发现对于我们的案例,这个方法会找出配置切面的 Bean 的 Order值。这里可以参考 BeanFactoryAspectInstanceFactory#getOrder 的调试视图验证这个结论:
-![](08%20%E7%AD%94%E7%96%91%E7%8E%B0%E5%9C%BA%EF%BC%9ASpring%20Core%20%E7%AF%87%E6%80%9D%E8%80%83%E9%A2%98%E5%90%88%E9%9B%86/57691fdf7c94454a9da898113608d70d.jpg)
+![](assets/08_02.jpg)
上述截图中,aopConfig2 即是我们配置切面的 Bean 的名称。这里再顺带提供出调用栈的截图,以便你做进一步研究:
-![](08%20%E7%AD%94%E7%96%91%E7%8E%B0%E5%9C%BA%EF%BC%9ASpring%20Core%20%E7%AF%87%E6%80%9D%E8%80%83%E9%A2%98%E5%90%88%E9%9B%86/5da30f9691264c2eb3e71f15b7d04da6.jpg)
+![](assets/08_03.jpg)
现在我们就知道了,将不同的增强方法放置到不同的切面配置类中,使用不同的 Order 值来修饰是可以影响顺序的。相反,如果都是在一个配置类中,自然不会影响顺序,所以这也是当初我的方案中没有重点介绍 sortAdvisors 方法的原因,毕竟当时我们给出的案例都只有一个 AOP 配置类。
diff --git "a/zh-cn/course/spring/09 Spring Web URL \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/09 Spring Web URL \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md"
index 241d1b06..b13fbca7 100644
--- "a/zh-cn/course/spring/09 Spring Web URL \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/09 Spring Web URL \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -16,7 +16,7 @@
看起来顺风顺水,但是假设这个 name 中含有特殊字符/时(例如[http://localhost:8080/hi1/xiao/ming](http://localhost:8080/hi1/xiaoming) ),会如何?如果我们不假思索,或许答案是”xiao/ming”?然而稍微敏锐点的程序员都会判定这个访问是会报错的,具体错误参考:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/da282ca3e0c942c6a124b3672d9962b1.jpg)
+![](assets/09_01.jpg)
如图所示,当 name 中含有/,这个接口不会为 name 获取任何值,而是直接报Not Found错误。当然这里的“找不到”并不是指name找不到,而是指服务于这个特殊请求的接口。
@@ -38,7 +38,7 @@
这个步骤执行的代码语句是”this.mappingRegistry.getMappingsByUrl(lookupPath)“,实际上,它是查询 MappingRegistry#urlLookup,它的值可以用调试视图查看,如下图所示:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/e5228329c945474fa388b57f65e7c22f.jpg)
+![](assets/09_02.jpg)
查询 urlLookup 是一个精确匹配 Path 的过程。很明显,[http://localhost:8080/hi1/xiao/ming](http://localhost:8080/hi1/xiaoming) 的 lookupPath 是”/hi1/xiao/ming”,并不能得到任何精确匹配。这里需要补充的是,”/hi1/{name}“这种定义本身也没有出现在 urlLookup 中。
@@ -46,7 +46,7 @@
在步骤 1 匹配失败时,会根据请求来尝试模糊匹配,待匹配的匹配方法可参考下图:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/658f673383934de990aa676c755d9072.jpg)
+![](assets/09_03.jpg)
显然,”/hi1/{name}“这个匹配方法已经出现在待匹配候选中了。具体匹配过程可以参考方法 RequestMappingInfo#getMatchingCondition:
@@ -58,7 +58,7 @@ public RequestMappingInfo getMatchingCondition(HttpServletRequest request) { Req
在我们的案例中,当使用 [http://localhost:8080/hi1/xiaoming](http://localhost:8080/hi1/xiaoming) 访问时,其中 patternsCondition 是可以匹配上的。实际的匹配方法执行是通过 AntPathMatcher#match 来执行,判断的相关参数可参考以下调试视图:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/351194df4a6c441b86408010aea66437.jpg)
+![](assets/09_04.jpg)
但是当我们使用 [http://localhost:8080/hi1/xiao/ming](http://localhost:8080/hi1/xiaoming) 来访问时,AntPathMatcher 执行的结果是”/hi1/xiao/ming”匹配不上”/hi1/{name}“。
@@ -112,7 +112,7 @@ private AntPathMatcher antPathMatcher = new AntPathMatcher(); @RequestMapping(pa
很明显,对于喜欢追究极致简洁的同学来说,这个酷炫的功能是一个福音。但当我们换一个项目时,有可能上线后就失效了,然后报错 500,提示匹配不上。
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/76e5ba7f6d314bcba1bc7552bddaa35e.jpg)
+![](assets/09_05.jpg)
### 案例解析
@@ -124,7 +124,7 @@ private AntPathMatcher antPathMatcher = new AntPathMatcher(); @RequestMapping(pa
上述配置显示关闭了 parameters 和 debug,这 2 个参数的作用你可以参考下面的表格:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/873d203454bb488fbf5ea1f768dd0d69.jpg)
+![](assets/09_06.jpg)
通过上述描述,我们可以看出这 2 个参数控制了一些 debug 信息是否加进 class 文件中。我们可以开启这两个参数来编译,然后使用下面的命令来查看信息:
@@ -132,7 +132,7 @@ private AntPathMatcher antPathMatcher = new AntPathMatcher(); @RequestMapping(pa
执行完命令后,我们会看到以下 class 信息:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/455c0aa051114134bfaa848a2ed20978.jpg)
+![](assets/09_07.jpg)
debug 参数开启的部分信息就是 LocalVaribleTable,而 paramters 参数开启的信息就是 MethodParameters。观察它们的信息,你会发现它们都含有参数名name。
@@ -148,7 +148,7 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
所以这里我们就会尝试调用 parameter.getParameterName() 来获取参数名作为解析请求参数的名称。但是,很明显,关掉上面两个开关后,就不可能在 class 文件中找到参数名了,这点可以从下面的调试试图中得到验证:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/d9288d7982c648e2816dc181cf985069.jpg)
+![](assets/09_08.jpg)
当参数名不存在,@RequestParam 也没有指明,自然就无法决定到底要用什么名称去获取请求参数,所以就会报本案例的错误。
@@ -176,7 +176,7 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu
在访问 [http://localhost:8080/hi4?name=xiaoming&address=beijing](http://localhost:8080/hi2?name=xiaoming&address=beijing) 时并不会出问题,但是一旦用户仅仅使用 name 做请求(即 [http://localhost:8080/hi4?name=xiaoming](http://localhost:8080/hi4?name=xiaoming) )时,则会直接报错如下:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/87b6a2663d5148bc8c316a93f38a009a.jpg)
+![](assets/09_09.jpg)
此时,返回错误码 400,提示请求格式错误:此处缺少 address 参数。
@@ -208,7 +208,7 @@ public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAn
实际上就是 @RequestParam 的相关信息,我们调试下,就可以验证这个结论,具体如下图所示:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/e5c02e8275a6492b8bcefed577e07845.jpg)
+![](assets/09_10.jpg)
**2\. 在 @RequestParam 没有指明默认值时,会查看这个参数是否必须,如果必须,则按错误处理**
@@ -282,7 +282,7 @@ protected void handleMissingValue(String name, MethodParameter parameter) throws
然后,我们使用一些看似明显符合日期格式的 URL 来访问,例如 [http://localhost:8080/hi6?date=2021-5-1 20:26:53](http://localhost:8080/hi6?date=2021-5-1%2020:26:53),我们会发现 Spring 并不能完成转化,而是报错如下:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/0b3885184edf4b5a986f8613169ceabd.jpg)
+![](assets/09_11.jpg)
此时,返回错误码 400,错误信息为”Failed to convert value of type ‘java.lang.String’ to required type ‘java.util.Date”。
@@ -336,7 +336,7 @@ public Date(String s) { this(parse(s)); }
这是因为 AnnotationParserConverter 有目标类型的要求,这点我们可以通过调试角度来看下,参考 FormattingConversionService#addFormatterForFieldAnnotation 方法的调试试图:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/63dcc9f1056c4dc38d3a06d9511ba6bf.jpg)
+![](assets/09_12.jpg)
这是适应于 String 到 Date 类型的转化器 AnnotationParserConverter 实例的构造过程,其需要的 annototationType 参数为 DateTimeFormat。
@@ -348,7 +348,7 @@ annototationType 的作用正是为了帮助判断是否能用这个转化器,
最终构建出来的转化器相关信息可以参考下图:
-![](09%20Spring%20Web%20URL%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/df679f0f62984769b2457455626cf616.jpg)
+![](assets/09_13.jpg)
图中构造出的转化器是可以用来转化 String 到 Date,但是它要求我们标记 @DateTimeFormat。很明显,我们的参数 Date 并没有标记这个注解,所以这里为了使用这个转化器,我们可以使用上它并提供合适的格式。这样就可以让原来不工作的 URL 工作起来,具体修改代码如下:
diff --git "a/zh-cn/course/spring/10 Spring Web Header \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/10 Spring Web Header \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md"
index 03cdbea6..083a2fba 100644
--- "a/zh-cn/course/spring/10 Spring Web Header \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/10 Spring Web Header \350\247\243\346\236\220\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -42,7 +42,7 @@
对于一个 Header 的解析,主要有两种方式,分别实现在 RequestHeaderMethodArgumentResolver 和 RequestHeaderMapMethodArgumentResolver 中,它们都继承于 AbstractNamedValueMethodArgumentResolver,但是应用的场景不同,我们可以对比下它们的 supportsParameter(),来对比它们适合的场景:
-![](10%20Spring%20Web%20Header%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/0cbf5aa76e434cac8a00a95cfc52fe17.jpg)
+![](assets/10_01.jpg)
在上图中,左边是 RequestHeaderMapMethodArgumentResolver 的方法。通过比较可以发现,对于一个标记了 @RequestHeader 的参数,如果它的类型是 Map,则使用 RequestHeaderMapMethodArgumentResolver,否则一般使用的是 RequestHeaderMethodArgumentResolver。
@@ -208,7 +208,7 @@ private boolean checkSpecialHeader(String name, String value) { if (name.equalsI
最终我们获取到的 Response 如下:
-![](10%20Spring%20Web%20Header%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/e24f8fde8cdd4dd08be4fc3f337c74d2.jpg)
+![](assets/10_02.jpg)
从上图可以看出,Headers 里并没有 Content-Type,而我们设置的 Content-Type 已经作为 coyoteResponse 成员的值了。当然也不意味着后面一定不会返回,我们可以继续跟踪后续执行。
@@ -242,7 +242,7 @@ for (HttpMessageConverter> converter : this.messageConverters) { GenericHttpMe
如代码所示,即结合 targetType(String)、valueType(String)、selectedMediaType(MediaType#TEXT\_PLAIN)三个信息来决策可以使用哪种消息 Converter。常见候选 Converter 可以参考下图:
-![](10%20Spring%20Web%20Header%20%E8%A7%A3%E6%9E%90%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/4730528ead9b4c92a29dfa3ef10bb648.jpg)
+![](assets/10_03.jpg)
最终,本案例选择的是 StringHttpMessageConverter,在最终调用父类方法 AbstractHttpMessageConverter#write 执行转化时,会尝试添加 Content-Type。具体代码参考 AbstractHttpMessageConverter#addDefaultHeaders:
diff --git "a/zh-cn/course/spring/11 Spring Web Body \350\275\254\345\214\226\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/11 Spring Web Body \350\275\254\345\214\226\345\270\270\350\247\201\351\224\231\350\257\257.md"
index 04b3bbc6..99e28b10 100644
--- "a/zh-cn/course/spring/11 Spring Web Body \350\275\254\345\214\226\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/11 Spring Web Body \350\275\254\345\214\226\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -20,7 +20,7 @@
但是当我们运行起程序,执行测试代码,就会报错如下:
-![](11%20Spring%20Web%20Body%20%E8%BD%AC%E5%8C%96%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/ee996f8cfbd743f49719a4556be98195.jpg)
+![](assets/11_01.jpg)
从上述代码及配置来看,并没有什么明显的错误,可为什么会报错呢?难道框架不支持?
@@ -72,7 +72,7 @@ messageConverters.add(createConverterDefinition(ByteArrayHttpMessageConverter.cl
假设我们依赖了 Gson 包,我们就可以添加上 GsonHttpMessageConverter 这种转化器。但是可惜的是,我们的案例并没有依赖上任何 JSON 的库,所以最终在候选的转换器列表里,并不存在 JSON 相关的转化器。最终候选列表示例如下:
-![](11%20Spring%20Web%20Body%20%E8%BD%AC%E5%8C%96%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/9f0953d5943545d6a111cfcd38e952aa.jpg)
+![](assets/11_02.jpg)
由此可见,并没有任何 JSON 相关的编解码器。而针对 Student 类型的返回对象,上面的这些编解码器又不符合要求,所以最终走入了下面的代码行:
diff --git "a/zh-cn/course/spring/12 Spring Web \345\217\202\346\225\260\351\252\214\350\257\201\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/12 Spring Web \345\217\202\346\225\260\351\252\214\350\257\201\345\270\270\350\247\201\351\224\231\350\257\257.md"
index bbc0f4b1..eef824d3 100644
--- "a/zh-cn/course/spring/12 Spring Web \345\217\202\346\225\260\351\252\214\350\257\201\345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/12 Spring Web \345\217\202\346\225\260\351\252\214\350\257\201\345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -42,7 +42,7 @@ HTTP/1.1 400 Content-Type: application/json { "timestamp": "2021-01-03T00:47:23.
假设我们构建Web服务使用的是Spring Boot技术,我们可以参考下面的时序图了解它的核心执行步骤:
-![](12%20Spring%20Web%20%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/936435d2eaba4a30a37276cce2ca80d8.jpg)
+![](assets/12_01.jpg)
如上图所示,当一个请求来临时,都会进入 DispatcherServlet,执行其 doDispatch(),此方法会根据 Path、Method 等关键信息定位到负责处理的 Controller 层方法(即 addStudent 方法),然后通过反射去执行这个方法,具体反射执行过程参考下面的代码(InvocableHandlerMethod#invokeForRequest):
@@ -58,7 +58,7 @@ public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewC
那么如何构建出这个方法参数实例?Spring 内置了相当多的 HandlerMethodArgumentResolver,参考下图:
-![](12%20Spring%20Web%20%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/1e1b6c3f990d4554ad99711af7ca1ed4.jpg)
+![](assets/12_02.jpg)
当试图构建出一个方法参数时,会遍历所有支持的解析器(Resolver)以找出适合的解析器,查找代码参考HandlerMethodArgumentResolverComposite#getArgumentResolver:
@@ -161,7 +161,7 @@ private CascadingMetaDataBuilder getCascadingMetaData(Type type, AnnotatedElemen
当修正完问题后,我们会发现校验生效了。而如果此时去调试修正后的案例代码,会看到 phone 字段 MetaData 信息中的 cascading 确实为 true 了,参考下图:
-![](12%20Spring%20Web%20%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/61adff03683345cca71f42e95e6318f6.jpg)
+![](assets/12_03.jpg)
另外,假设我们不去解读源码,我们很可能会按照案例 1 所述的其他修正方法去修正这个问题。例如,使用 @Validated 来修正这个问题,但是此时你会发现,不考虑源码是否支持,代码本身也编译不过,这主要在于 @Validated 的定义是不允许修饰一个 Field 的:
@@ -199,7 +199,7 @@ private CascadingMetaDataBuilder getCascadingMetaData(Type type, AnnotatedElemen
如果我们稍微留心点的话,就会发现其实 @Size 的 Javadoc 已经明确了这种情况,参考下图:
-![](12%20Spring%20Web%20%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/7c04e256f690499b9dd2a30150988dac.jpg)
+![](assets/12_04.jpg)
如图所示,”null elements are considered valid” 很好地解释了约束不住null的原因。当然纸上得来终觉浅,我们还需要从源码级别解读下@Size 的校验过程。
diff --git "a/zh-cn/course/spring/13 Spring Web \350\277\207\346\273\244\345\231\250\344\275\277\347\224\250\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md" "b/zh-cn/course/spring/13 Spring Web \350\277\207\346\273\244\345\231\250\344\275\277\347\224\250\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
index 72a3dea7..bca6f5dd 100644
--- "a/zh-cn/course/spring/13 Spring Web \350\277\207\346\273\244\345\231\250\344\275\277\347\224\250\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
+++ "b/zh-cn/course/spring/13 Spring Web \350\277\207\346\273\244\345\231\250\344\275\277\347\224\250\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
@@ -50,7 +50,7 @@
本质上,过滤器被 @WebFilter 修饰后,TimeCostFilter 只会被包装为 FilterRegistrationBean,而 TimeCostFilter 自身,只会作为一个 InnerBean 被实例化,这意味着 **TimeCostFilter 实例并不会作为 Bean 注册到 Spring 容器**。
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/f812277c45e643979d8f42de4f68791a.jpg)
+![](assets/13_01.jpg)
所以当我们想自动注入 TimeCostFilter 时,就会失败了。知道这个结论后,我们可以带着两个问题去理清一些关键的逻辑:
@@ -70,7 +70,7 @@
这里我们直接检索对 @WebFilter 的使用,可以发现 WebFilterHandler 类使用了它,直接在 doHandle() 中加入断点,开始调试,执行调用栈如下:
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/8c61379a267547038b9644896d601764.jpg)
+![](assets/13_02.jpg)
从堆栈上,我们可以看出对@WebFilter 的处理是在 Spring Boot 启动时,而处理的触发点是 ServletComponentRegisteringPostProcessor 这个类。它继承了 BeanFactoryPostProcessor 接口,实现对 @WebFilter、@WebListener、@WebServlet 的扫描和处理,其中对于@WebFilter 的处理使用的就是上文中提到的 WebFilterHandler。这个逻辑可以参考下面的关键代码:
@@ -86,7 +86,7 @@ public void doHandle(Map attributes, AnnotatedBeanDefinition bea
从这里,我们第一次看到了 FilterRegistrationBean。通过调试上述代码的最后一行,可以看到,最终我们注册的 FilterRegistrationBean,其名字就是我们定义的 WebFilter 的名字:
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/e82362b2716845ae85cb106f3d36d3a6.jpg)
+![](assets/13_03.jpg)
后续这个 Bean 的具体创建过程,这里不再赘述,感兴趣的话你可以继续深入研究。
@@ -96,7 +96,7 @@ public void doHandle(Map attributes, AnnotatedBeanDefinition bea
关于这点,我们可以在 TimeCostFilter 的构造器中加个断点,然后使用调试的方式快速定位到它的初始化时机,这里我直接给出了调试截图:
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/40597bf10ce84d2bb619e2ec42d0511d.jpg)
+![](assets/13_04.jpg)
在上述的关键调用栈中,结合源码,你可以找出一些关键信息:
@@ -104,7 +104,7 @@ public void doHandle(Map attributes, AnnotatedBeanDefinition bea
2. FilterRegistrationBean 在被创建时(createBean)会创建 TimeCostFilter 来装配自身,TimeCostFilter 是通过 ResolveInnerBean 来创建的;
3. TimeCostFilter 实例最终是一种 InnerBean,我们可以通过下面的调试视图看到它的一些关键信息:
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/3ff7aa99e9d34b76a113e68f87069c8f.jpg)
+![](assets/13_05.jpg)
通过上述分析,你可以看出**最终 TimeCostFilter 实例是一种 InnerBean**,所以自动注入不到也就非常合理了。
@@ -167,7 +167,7 @@ Filter 处理中时发生异常 ......用户注册成功 ......用户注册成
但区别在于递归调用是同一个对象把子任务交给同一个方法本身去完成,而**职责链则是一个对象把子任务交给其他对象的同名方法去完成**。其核心在于上下文 FilterChain 在不同对象 Filter 间的传递与状态的改变,通过这种链式串联,我们就可以对同一种对象资源实现不同业务场景的处理,达到业务解耦。整个 FilterChain 的结构就像这张图一样:
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/8713ccd0e3534d9188943a389176523d.jpg)
+![](assets/13_06.jpg)
这里我们不妨还是带着两个问题去理解 FilterChain:
@@ -223,7 +223,7 @@ private void internalDoFilter(ServletRequest request, ServletResponse response){
执行完所有的过滤器后,代码调用了 servlet.service(request, response) 方法。从下面这张调用栈的截图中,可以看到,经历了一个很长的看似循环的调用栈,我们终于从 internalDoFilter() 执行到了Controller层的saveUser()。这个过程就不再一一细讲了。
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/7319f6f84e2440bdbb5bf4f96af07932.jpg)
+![](assets/13_07.jpg)
分析了这么多,最后我们再来思考一下这个问题案例。
@@ -253,7 +253,7 @@ DemoFilter 代码中的 doFilter() 在捕获异常的部分执行了一次,随
1. 我们在过滤器的执行中,一定要注意避免不要多次调用doFilter(),否则可能会出现业务代码执行多次的问题。这个问题出现的根源往往在于“不小心”,但是要理解这个问题呈现的现象,就必须对过滤器的流程有所了解。可以看过滤器执行的核心流程图:
-![](13%20Spring%20Web%20%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BD%BF%E7%94%A8%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/ccc002f660ed4c06ac30cd50a347c05e.jpg)
+![](assets/13_08.jpg)
结合这个流程图,我们还可以进一步细化出以下关键步骤:
diff --git "a/zh-cn/course/spring/15 Spring Security \345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/15 Spring Security \345\270\270\350\247\201\351\224\231\350\257\257.md"
index ada0acd7..e1b8f296 100644
--- "a/zh-cn/course/spring/15 Spring Security \345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/15 Spring Security \345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -26,7 +26,7 @@
但是当我们发送一个请求时(例如 [http://localhost:8080/admin](http://localhost:8080/admin) ),就会报错java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”,具体错误堆栈信息如下:
-![](15%20Spring%20Security%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/f54bd05200144399840b22c324300059.jpg)
+![](assets/15_01.jpg)
所以,如果我们不按照最新版本的 Spring Security 教程操作,就很容易忘记 PasswordEncoder 这件事。那么为什么缺少它就会报错,它的作用又在哪?接下来我们具体解析下。
@@ -56,7 +56,7 @@ public static PasswordEncoder createDelegatingPasswordEncoder() { String encodin
我们可以换一个视角来看下这个DelegatingPasswordEncoder长什么样:
-![](15%20Spring%20Security%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/c57ffae16b5a4bf3a83e3216c37f9539.jpg)
+![](assets/15_02.jpg)
通过上图可以看出,其实它是多个内置的 PasswordEncoder 集成在了一起。
@@ -86,7 +86,7 @@ auth.inMemoryAuthentication() .withUser("admin").password("{MD5}pass").roles("AD
此时,以调试方式运行程序,你会发现,这个时候已经有了 id,且取出了合适的 PasswordEncoder。
-![](15%20Spring%20Security%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/81854131bd3b4924ad6600f01d6927b9.jpg)
+![](assets/15_03.jpg)
说到这里,相信你已经知道问题的来龙去脉了。问题的根源还是在于我们需要一个PasswordEncoder,而当前案例没有给我们指定出来。
@@ -130,7 +130,7 @@ public final class NoOpPasswordEncoder implements PasswordEncoder { public Strin
然后我们从浏览器访问我们的接口 [http://localhost:8080/admin](http://localhost:8080/admin),使用上述 3 个用户登录,你会发现用户 admin1 可以登录,而 admin2 设置了同样的角色却不可以登陆,并且提示下面的错误:
-![](15%20Spring%20Security%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/ce47e1607eb64191aafc9af9203af18d.jpg)
+![](assets/15_04.jpg)
如何理解这个现象?
@@ -160,7 +160,7 @@ public static UserBuilder withUserDetails(UserDetails userDetails) { return with
此时我们可以得出一个结论:通过上述两种方式设置的相同 Role(即 ADMIN),最后存储的 Role 却不相同,分别为 ROLE\_ADMIN 和 ADMIN。那么为什么只有 ROLE\_ADMIN 这种用户才能通过授权呢?这里我们不妨通过调试视图看下授权的调用栈,截图如下:
-![](15%20Spring%20Security%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/cf2d14ec8f4049dd9bfa4813fb19dbd3.jpg)
+![](assets/15_05.jpg)
对于案例的代码,最终是通过 “UsernamePasswordAuthenticationFilter” 来完成授权的。而且从调用栈信息可以大致看出,授权的关键其实就是查找用户,然后校验权限。查找用户的方法可参考 InMemoryUserDetailsManager#loadUserByUsername,即根据用户名查找已添加的用户:
@@ -170,7 +170,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx
完成账号是否过期、是否锁定等检查后,我们会把这个用户转化为下面的 Token(即 UsernamePasswordAuthenticationToken)供后续使用,关键信息如下:
-![](15%20Spring%20Security%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/870762f5943147259bd77d8ce35ae399.jpg)
+![](assets/15_06.jpg)
最终在判断角色时,我们会通过 UsernamePasswordAuthenticationToken 的父类方法 AbstractAuthenticationToken#getAuthorities 来取到上述截图中的 ADMIN。而判断是否具备某个角色时,使用的关键方法是 SecurityExpressionRoot#hasAnyAuthorityName:
@@ -220,6 +220,6 @@ public int vote(Authentication authentication, FilterInvocation fi, CollectionN,即读和写的节点数之和需要大于备份数。
例如,假设我们的数据备份是 3 份,待写入的数据分别存储在 A、B、C 三个节点上。那么常见的搭配是 R(读)和 W(写)的一致性都是 LOCAL\_QURAM,这样可以保证能及时读到写入的数据;而假设在这种情况下,我们读写都是用 LOCAL\_ONE,那么则可能发生这样的情况,即用户写入一个节点 A 就返回了,但是用户 B 立马读的节点是 C,且由于是 LOCAL\_ONE 一致性,则读完 C 就可以立马返回。此时,就会出现数据读取可能落空的情况。
-![](18%20Spring%20Data%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/33837e984a8348839e534d8b0b531004.jpg)
+![](assets/18_02.jpg)
那么考虑一个问题,为什么 Cassandra driver 默认是使用 LOCAL\_ONE 呢?
@@ -118,7 +118,7 @@ basic.request { # The consistency level. # # Required: yes # Modifiable at runti
有时候,我们使用 Spring Data 做连接时,会比较在意我们的内存占用。例如我们使用 Spring Data Cassandra 操作 Cassandra 时,可能会发现类似这样的问题:
-![](18%20Spring%20Data%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/ff02bcc413d74e08b8ba4c797e0d6e2b.jpg)
+![](assets/18_03.jpg)
Spring Data Cassandra 在连接 Cassandra 之后,会获取 Cassandra 的 Metadata 信息,这个内存占用量是比较大的,因为它存储了数据的 Token Range 等信息。如上图所示,在我们的应用中,占用 40M 以上已经不少了,但问题是为什么有 4 个占用 40 多 M 呢?难道不是只建立一个连接么?
@@ -154,7 +154,7 @@ public class MyService { public MyService(String name){ System.err.println(name)
为了让程序启动,我们不能将 BaseConfig 和 Config 都放到 Application 的扫描范围。我们可以按如下结构组织代码:
-![](18%20Spring%20Data%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/d1da48ca1fa14810997f594c01f1c75e.jpg)
+![](assets/18_04.jpg)
最终我们会发现,当程序启动时,我们只有一个 MyService 的 Bean 产生,输出日志如下:
diff --git "a/zh-cn/course/spring/19 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md" "b/zh-cn/course/spring/19 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
index 2e5cb44c..a2c44ffc 100644
--- "a/zh-cn/course/spring/19 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
+++ "b/zh-cn/course/spring/19 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\212\357\274\211.md"
@@ -78,7 +78,7 @@ Exception in thread "main" java.lang.Exception: 该学生已存在 at com.spring
我们通过 debug 沿着 saveStudent 继续往下跟,得到了一个这样的调用栈:
-![](19%20Spring%20%E4%BA%8B%E5%8A%A1%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/4d7f14965e2949808041761ae9d8e95a.jpg)
+![](assets/19_01.jpg)
从这个调用栈中我们看到了熟悉的 CglibAopProxy,另外事务本质上也是一种特殊的切面,在创建的过程中,被 CglibAopProxy 代理。事务处理的拦截器是 TransactionInterceptor,它支撑着整个事务功能的架构,我们来分析下这个拦截器是如何实现事务特性的。
@@ -156,7 +156,7 @@ public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException
通过 debug,我们一步步寻找到了问题的根源,得到了以下调用栈。我们通过 Spring 的源码来解析一下完整的过程。
-![](19%20Spring%20%E4%BA%8B%E5%8A%A1%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8A%EF%BC%89/453b8c61db324119bdc9a9af4aa3e30c.jpg)
+![](assets/19_02.jpg)
前一段是 Spring 创建 Bean 的过程。当 Bean 初始化之后,开始尝试代理操作,这个过程是从 AbstractAutoProxyCreator 里的 postProcessAfterInitialization 方法开始处理的:
diff --git "a/zh-cn/course/spring/20 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md" "b/zh-cn/course/spring/20 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
index ffaeb7a5..03d32400 100644
--- "a/zh-cn/course/spring/20 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
+++ "b/zh-cn/course/spring/20 Spring \344\272\213\345\212\241\345\270\270\350\247\201\351\224\231\350\257\257\357\274\210\344\270\213\357\274\211.md"
@@ -324,7 +324,7 @@ public class JdbcConfig { //省略非关键代码 @Value("${card.driver}") priva
最后我们来看一下整个过程的调用栈,重新过一遍流程(这里我略去了不重要的部分)。
-![](20%20Spring%20%E4%BA%8B%E5%8A%A1%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%EF%BC%88%E4%B8%8B%EF%BC%89/7f3941a5a89d413bb46f9c100f787a0d.jpg)
+![](assets/20_01.jpg)
在创建了事务以后,会通过 DataSourceTransactionManager.doBegin()获取相应的数据库连接:
diff --git "a/zh-cn/course/spring/21 Spring Rest Template \345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/21 Spring Rest Template \345\270\270\350\247\201\351\224\231\350\257\257.md"
index 1f5c1ede..1b6296a3 100644
--- "a/zh-cn/course/spring/21 Spring Rest Template \345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/21 Spring Rest Template \345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -24,7 +24,7 @@ RestTemplate template = new RestTemplate(); Map paramMap = new H
测试后你会发现事与愿违,返回提示 400 错误,即请求出错:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/0429e8064d38450b801e6e49c35820c3.jpg)
+![](assets/21_01.jpg)
具体而言,就是缺少 para1 表单参数。为什么会出现这个错误呢?我们提交的表单最后又成了什么?
@@ -32,7 +32,7 @@ RestTemplate template = new RestTemplate(); Map paramMap = new H
在具体解析这个问题之前,我们先来直观地了解下,当我们使用上述的 RestTemplate 提交表单,最后的提交请求长什么样?这里我使用 Wireshark 抓包工具直接给你抓取出来:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/27c2706e547a476cbadb0b1dbe8ecd34.jpg)
+![](assets/21_02.jpg)
从上图可以看出,我们实际上是将定义的表单数据以 JSON 请求体(Body)的形式提交过去了,所以我们的接口处理自然取不到任何表单参数。
@@ -40,7 +40,7 @@ RestTemplate template = new RestTemplate(); Map paramMap = new H
首先,我们看下上述代码的调用栈:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/ad911c7026804f88848a70c0eeb0f711.jpg)
+![](assets/21_03.jpg)
确实可以验证,我们最终使用的是 Jackson 工具来对表单进行了序列化。使用到 JSON 的关键之处在于其中的关键调用 RestTemplate.HttpEntityRequestCallback#doWithRequest:
@@ -50,7 +50,7 @@ public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { su
上述代码看起来比较复杂,实际上功能很简单:根据当前要提交的 Body 内容,遍历当前支持的所有编解码器,如果找到合适的编解码器,就使用它来完成 Body 的转化。这里我们不妨看下 JSON 的编解码器对是否合适的判断,参考 AbstractJackson2HttpMessageConverter#canWrite:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/0786d0ddd08c41bca0f2f12d9894efa3.jpg)
+![](assets/21_04.jpg)
可以看出,当我们使用的 Body 是一个 HashMap 时,是可以完成 JSON 序列化的。所以在后续将这个表单序列化为请求 Body 也就不奇怪了。
@@ -78,7 +78,7 @@ public void write(MultiValueMap map, @Nullable MediaType contentType,
发送出的数据截图如下:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/da4722779e2f43fa9384a247855defe3.jpg)
+![](assets/21_05.jpg)
这样就满足我们的需求了。
@@ -118,7 +118,7 @@ String url = "http://localhost:8080/hi?para1=1#2"; HttpEntity> entity = new Ht
类似案例 1 解析的套路,在具体解析之前,我们可以先直观感受下问题出在什么地方。我们使用调试方式去查看解析后的 URL,截图如下:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/2593f32abd414ce7ac9be611c4a18c37.jpg)
+![](assets/21_06.jpg)
可以看出,para1 丢掉的 #2 实际是以 Fragment 的方式被记录下来了。这里顺便科普下什么是 Fragment,这得追溯到 URL 的格式定义:
@@ -138,7 +138,7 @@ String url = "http://localhost:8080/hi?para1=1#2"; HttpEntity> entity = new Ht
了解了这些补充知识后,我们其实就能知道问题出在哪了。不过本着严谨的态度,我们还是翻阅下源码。首先,我们先看下 URL 解析的调用栈,示例如下:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/99af6ab14c6e4663be065e0c2a6e0ddc.jpg)
+![](assets/21_07.jpg)
参考上述调用栈,解析 URL 的关键点在于 UriComponentsBuilder#fromUriString 实现:
@@ -168,7 +168,7 @@ String url = "http://localhost:8080/hi?para1=1#2"; UriComponentsBuilder builder
与之前的案例代码进行比较,你会发现 URL 的组装方式发生了改变。但最终可以获取到我们预期的效果,调试视图参考如下:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/540566ddd31d4cc2bff878ebe0063f3b.jpg)
+![](assets/21_08.jpg)
可以看出,参数 para1 对应的值变成了我们期待的”1#2”。
@@ -224,7 +224,7 @@ public final UriComponents encode() { return encode(StandardCharsets.UTF_8); }
查询调用栈,结果如下:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/b7e0b11ac6124e7187a08fafcfe3fde6.jpg)
+![](assets/21_09.jpg)
而当我们把 URL 转化成 String,再通过下面的语句来发送请求时:
@@ -232,7 +232,7 @@ public final UriComponents encode() { return encode(StandardCharsets.UTF_8); }
我们会发现,它会再进行一次编码:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/90dfd218844c40fa90eaddd6b2f38327.jpg)
+![](assets/21_10.jpg)
看到这里,你或许已经明白问题出在哪了,即我们按照案例的代码会执行 2 次编码(Encode),所以最终我们反而获取不到想要的结果了。
@@ -240,11 +240,11 @@ public final UriComponents encode() { return encode(StandardCharsets.UTF_8); }
1 次编码后:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/133ebd355a8f44c2a71ea9a774c4137e.jpg)
+![](assets/21_11.jpg)
2 次编码后:
-![](21%20Spring%20Rest%20Template%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/c723a22e198f4e20ac28470eef287d0f.jpg)
+![](assets/21_12.jpg)
### 问题修正
diff --git "a/zh-cn/course/spring/22 Spring Test \345\270\270\350\247\201\351\224\231\350\257\257.md" "b/zh-cn/course/spring/22 Spring Test \345\270\270\350\247\201\351\224\231\350\257\257.md"
index 02e744be..cb321e94 100644
--- "a/zh-cn/course/spring/22 Spring Test \345\270\270\350\247\201\351\224\231\350\257\257.md"
+++ "b/zh-cn/course/spring/22 Spring Test \345\270\270\350\247\201\351\224\231\350\257\257.md"
@@ -42,7 +42,7 @@ public class HelloWorldService { }
当我们运行上述测试的时候,会发现测试失败了,报错如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/38f5b914936d42b8b4e17d3520a92901.jpg)
+![](assets/22_01.jpg)
为什么单独运行应用程序没有问题,但是运行测试就不行了呢?我们需要研究一下 Spring 的源码,来找找答案。
@@ -54,15 +54,15 @@ public class HelloWorldService { }
首先看下调用栈:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/af9984e42f2243298c53406c846763d4.jpg)
+![](assets/22_02.jpg)
可以看出,它最终以 ClassPathResource 形式来加载,这个资源的情况如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/931cde4470dc40199d4f4eaa12aaa11f.jpg)
+![](assets/22_03.jpg)
而具体到加载实现,它使用的是 ClassPathResource#getInputStream 来加载spring.xml文件:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/4f7051d14486429d9fa44c3e6a78af8f.jpg)
+![](assets/22_04.jpg)
从上述调用及代码实现,可以看出最终是可以加载成功的。
@@ -70,11 +70,11 @@ public class HelloWorldService { }
首先看下调用栈:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/ffa6d903b5b844648aa64cea812de130.jpg)
+![](assets/22_05.jpg)
可以看出它是按 ServletContextResource 来加载的,这个资源的情况如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/d86a120159ec4d4cbc23aa743a5d83b6.jpg)
+![](assets/22_06.jpg)
具体到实现,它最终使用的是 MockServletContext#getResourceAsStream 来加载文件:
@@ -98,7 +98,7 @@ classpath:META-INF/resources classpath:resources classpath:static classpath:publ
那你肯定是忽略了一点:当程序运行起来后,src/main/resource 下的文件最终是不带什么resource的。关于这点,你可以直接查看编译后的目录(本地编译后是 target\\classes 目录),示例如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/5d0fc76d24b14e7989d427e192cdf319.jpg)
+![](assets/22_07.jpg)
所以,最终我们在所有的目录中都找不到spring.xml,并且会报错提示加载不了文件。报错的地方位于 ServletContextResource#getInputStream 中:
@@ -122,7 +122,7 @@ classpath:META-INF/resources classpath:resources classpath:static classpath:publ
这里,我们可以通过 Spring 的官方文档简单了解下不同加载方式的区别,参考 [https://docs.spring.io/spring-framework/docs/2.5.x/reference/resources.html](https://docs.spring.io/spring-framework/docs/2.5.x/reference/resources.html):
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/263887b108ee41ffbe1e44aec8d354b2.jpg)
+![](assets/22_08.jpg)
很明显,我们一般都不会使用本案例的方式(即locations = {“spring.xml”},无任何“前缀”的方式),毕竟它已经依赖于使用的 ApplicationContext。而 classPath 更为普适些,而一旦你按上述方式修正后,你会发现它加载的资源已经不再是 ServletContextResource,而是和应用程序一样的 ClassPathResource,这样自然可以加载到了。
@@ -132,7 +132,7 @@ classpath:META-INF/resources classpath:resources classpath:static classpath:publ
接下来,我们再来看一个非功能性的错误案例。有时候,我们会发现 Spring Test 运行起来非常缓慢,寻根溯源之后,你会发现主要是因为很多测试都启动了Spring Context,示例如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/dec97f5c4b0e4f8a876f69864f5c5da7.jpg)
+![](assets/22_09.jpg)
那么为什么有的测试会多次启动 Spring Context?在具体解析这个问题之前,我们先模拟写一个案例来复现这个问题。
@@ -172,11 +172,11 @@ public int hashCode() { int result = Arrays.hashCode(this.locations); result = 3
ServiceOneTests 的 MergedContextConfiguration 示例如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/56ee894be556464091ec93b13daf2d51.jpg)
+![](assets/22_10.jpg)
ServiceTwoTests 的 MergedContextConfiguration 示例如下:
-![](22%20Spring%20Test%20%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF/9e9377f67ed843ee89b51b76c9ec2c9a.jpg)
+![](assets/22_11.jpg)
很明显,MergedContextConfiguration(即 Context Cache 的 Key)的 ContextCustomizer 是不同的,所以 Context 没有共享起来。而追溯到 ContextCustomizer 的创建,我们可以具体来看下。
diff --git "a/zh-cn/course/spring/23 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring \350\241\245\345\205\205\347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md" "b/zh-cn/course/spring/23 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring \350\241\245\345\205\205\347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md"
index dcc6633d..57ba7b73 100644
--- "a/zh-cn/course/spring/23 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring \350\241\245\345\205\205\347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md"
+++ "b/zh-cn/course/spring/23 \347\255\224\347\226\221\347\216\260\345\234\272\357\274\232Spring \350\241\245\345\205\205\347\257\207\346\200\235\350\200\203\351\242\230\345\220\210\351\233\206.md"
@@ -8,7 +8,7 @@
实际上,当我们依赖 spring-boot-starter 时,我们就间接依赖了 spring-boot -autoconfigure。
-![](23%20%E7%AD%94%E7%96%91%E7%8E%B0%E5%9C%BA%EF%BC%9ASpring%20%E8%A1%A5%E5%85%85%E7%AF%87%E6%80%9D%E8%80%83%E9%A2%98%E5%90%88%E9%9B%86/2f0d2b7f22254ef7b191ece3c6545084.jpg)
+![](assets/23_01.jpg)
在这个 JAR 中,存在下面这样的一个类,即 RedisAutoConfiguration。
@@ -34,7 +34,7 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframe
当它使用了 AutoConfigurationImportSelector 这个类,这个类就会导入在META-INF/spring.factories定义的 RedisAutoConfiguration。那么 import 动作是什么时候执行的呢?实际上是在启动应用程序时触发的,调用堆栈信息如下:
-![](23%20%E7%AD%94%E7%96%91%E7%8E%B0%E5%9C%BA%EF%BC%9ASpring%20%E8%A1%A5%E5%85%85%E7%AF%87%E6%80%9D%E8%80%83%E9%A2%98%E5%90%88%E9%9B%86/8148a15e5a3d4342b364fffd1b9e5e45.jpg)
+![](assets/23_02.jpg)
结合上面的堆栈和相关源码,我们不妨可以总结下 RedisTemplate 被创建的过程。
@@ -76,7 +76,7 @@ RuntimeException 是 Exception 的子类,如果用 rollbackFor=Exception.class
不管是使用 Query 参数还是用 Form 参数来访问,对于案例程序而言,解析的关键逻辑都是类似的,都是通过下面的调用栈完成参数的解析:
-![](23%20%E7%AD%94%E7%96%91%E7%8E%B0%E5%9C%BA%EF%BC%9ASpring%20%E8%A1%A5%E5%85%85%E7%AF%87%E6%80%9D%E8%80%83%E9%A2%98%E5%90%88%E9%9B%86/8713edfa6bf14ee9989d05de1383716f.jpg)
+![](assets/23_03.jpg)
这里可以看出,负责解析的都是 RequestParamMethodArgumentResolver,解析最后的调用也都是一样的方法。在 org.apache.catalina.connector.Request#parseParameters 这个方法中,对于 From 的解析是这样的:
diff --git a/zh-cn/course/spring/README.md b/zh-cn/course/spring/README.md
index 489db8e6..add25882 100644
--- a/zh-cn/course/spring/README.md
+++ b/zh-cn/course/spring/README.md
@@ -1,53 +1,53 @@
# Spring编程常见错误50例
-[01 Spring Bean 定义常见错误]()
+[01 Spring Bean 定义常见错误](/zh-cn/course/spring/01%20Spring%20Bean%20%E5%AE%9A%E4%B9%89%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF.md)
-[02 Spring Bean 依赖注入常见错误(上)]()
+[02 Spring Bean 依赖注入常见错误(上)]()
-[03 Spring Bean 依赖注入常见错误(下)]()
+[03 Spring Bean 依赖注入常见错误(下)]()
-[04 Spring Bean 生命周期常见错误]()
+[04 Spring Bean 生命周期常见错误]()
-[05 Spring AOP 常见错误(上)]()
+[05 Spring AOP 常见错误(上)]()
-[06 Spring AOP 常见错误(下)]()
+[06 Spring AOP 常见错误(下)]()
-[07 Spring事件常见错误]()
+[07 Spring事件常见错误]()
-[08 答疑现场:Spring Core 篇思考题合集]()
+[08 答疑现场:Spring Core 篇思考题合集]()
-[导读 5分钟轻松了解一个HTTP请求的处理过程]()
+[导读 5分钟轻松了解一个HTTP请求的处理过程]()
-[09 Spring Web URL 解析常见错误]()
+[09 Spring Web URL 解析常见错误]()
-[10 Spring Web Header 解析常见错误]()
+[10 Spring Web Header 解析常见错误]()
-[11 Spring Web Body 转化常见错误]()
+[11 Spring Web Body 转化常见错误]()
-[12 Spring Web 参数验证常见错误]()
+[12 Spring Web 参数验证常见错误]()
-[13 Spring Web 过滤器使用常见错误(上)]()
+[13 Spring Web 过滤器使用常见错误(上)]()
-[14 Spring Web 过滤器使用常见错误(下)]()
+[14 Spring Web 过滤器使用常见错误(下)]()
-[15 Spring Security 常见错误]()
+[15 Spring Security 常见错误]()
-[16 Spring Exception 常见错误]()
+[16 Spring Exception 常见错误]()
-[17 答疑现场:Spring Web 篇思考题合集]()
+[17 答疑现场:Spring Web 篇思考题合集]()
-[18 Spring Data 常见错误]()
+[18 Spring Data 常见错误]()
-[19 Spring 事务常见错误(上)]()
+[19 Spring 事务常见错误(上)]()
-[20 Spring 事务常见错误(下)]()
+[20 Spring 事务常见错误(下)]()
-[21 Spring Rest Template 常见错误]()
+[21 Spring Rest Template 常见错误]()
-[22 Spring Test 常见错误]()
+[22 Spring Test 常见错误]()
-[23 答疑现场:Spring 补充篇思考题合集]()
+[23 答疑现场:Spring 补充篇思考题合集]()
-[知识回顾 系统梳理Spring编程错误根源]()
+[知识回顾 系统梳理Spring编程错误根源]()
-[结束语 问题总比解决办法多]()
\ No newline at end of file
+[结束语 问题总比解决办法多]()
\ No newline at end of file
diff --git a/zh-cn/course/spring/assets/01_01.jpg b/zh-cn/course/spring/assets/01_01.jpg
new file mode 100644
index 00000000..d6136c59
Binary files /dev/null and b/zh-cn/course/spring/assets/01_01.jpg differ
diff --git a/zh-cn/course/spring/assets/01_02.jpg b/zh-cn/course/spring/assets/01_02.jpg
new file mode 100644
index 00000000..e5ac43b4
Binary files /dev/null and b/zh-cn/course/spring/assets/01_02.jpg differ
diff --git a/zh-cn/course/spring/assets/01_03.jpg b/zh-cn/course/spring/assets/01_03.jpg
new file mode 100644
index 00000000..b77d1d70
Binary files /dev/null and b/zh-cn/course/spring/assets/01_03.jpg differ
diff --git a/zh-cn/course/spring/assets/01_04.jpg b/zh-cn/course/spring/assets/01_04.jpg
new file mode 100644
index 00000000..76227dda
Binary files /dev/null and b/zh-cn/course/spring/assets/01_04.jpg differ
diff --git a/zh-cn/course/spring/assets/01_05.jpg b/zh-cn/course/spring/assets/01_05.jpg
new file mode 100644
index 00000000..68bd3109
Binary files /dev/null and b/zh-cn/course/spring/assets/01_05.jpg differ
diff --git a/zh-cn/course/spring/assets/01_06.jpg b/zh-cn/course/spring/assets/01_06.jpg
new file mode 100644
index 00000000..87caeeb5
Binary files /dev/null and b/zh-cn/course/spring/assets/01_06.jpg differ
diff --git a/zh-cn/course/spring/assets/02_01.jpg b/zh-cn/course/spring/assets/02_01.jpg
new file mode 100644
index 00000000..7429705f
Binary files /dev/null and b/zh-cn/course/spring/assets/02_01.jpg differ
diff --git a/zh-cn/course/spring/assets/02_02.jpg b/zh-cn/course/spring/assets/02_02.jpg
new file mode 100644
index 00000000..05f5829c
Binary files /dev/null and b/zh-cn/course/spring/assets/02_02.jpg differ
diff --git a/zh-cn/course/spring/assets/02_03.jpg b/zh-cn/course/spring/assets/02_03.jpg
new file mode 100644
index 00000000..7c12fe4a
Binary files /dev/null and b/zh-cn/course/spring/assets/02_03.jpg differ
diff --git a/zh-cn/course/spring/assets/03_01.jpg b/zh-cn/course/spring/assets/03_01.jpg
new file mode 100644
index 00000000..56940bca
Binary files /dev/null and b/zh-cn/course/spring/assets/03_01.jpg differ
diff --git a/zh-cn/course/spring/assets/03_02.jpg b/zh-cn/course/spring/assets/03_02.jpg
new file mode 100644
index 00000000..657e4cf1
Binary files /dev/null and b/zh-cn/course/spring/assets/03_02.jpg differ
diff --git a/zh-cn/course/spring/assets/04_01.jpg b/zh-cn/course/spring/assets/04_01.jpg
new file mode 100644
index 00000000..21c07e08
Binary files /dev/null and b/zh-cn/course/spring/assets/04_01.jpg differ
diff --git a/zh-cn/course/spring/assets/04_02.jpg b/zh-cn/course/spring/assets/04_02.jpg
new file mode 100644
index 00000000..b2d1e773
Binary files /dev/null and b/zh-cn/course/spring/assets/04_02.jpg differ
diff --git a/zh-cn/course/spring/assets/05_01.jpg b/zh-cn/course/spring/assets/05_01.jpg
new file mode 100644
index 00000000..d8a75909
Binary files /dev/null and b/zh-cn/course/spring/assets/05_01.jpg differ
diff --git a/zh-cn/course/spring/assets/05_02.jpg b/zh-cn/course/spring/assets/05_02.jpg
new file mode 100644
index 00000000..40fd8ba5
Binary files /dev/null and b/zh-cn/course/spring/assets/05_02.jpg differ
diff --git a/zh-cn/course/spring/assets/05_03.jpg b/zh-cn/course/spring/assets/05_03.jpg
new file mode 100644
index 00000000..498be75c
Binary files /dev/null and b/zh-cn/course/spring/assets/05_03.jpg differ
diff --git a/zh-cn/course/spring/assets/05_04.jpg b/zh-cn/course/spring/assets/05_04.jpg
new file mode 100644
index 00000000..2b97e85a
Binary files /dev/null and b/zh-cn/course/spring/assets/05_04.jpg differ
diff --git a/zh-cn/course/spring/assets/05_05.jpg b/zh-cn/course/spring/assets/05_05.jpg
new file mode 100644
index 00000000..2b2c990b
Binary files /dev/null and b/zh-cn/course/spring/assets/05_05.jpg differ
diff --git a/zh-cn/course/spring/assets/05_06.jpg b/zh-cn/course/spring/assets/05_06.jpg
new file mode 100644
index 00000000..4b88231c
Binary files /dev/null and b/zh-cn/course/spring/assets/05_06.jpg differ
diff --git a/zh-cn/course/spring/assets/05_07.jpg b/zh-cn/course/spring/assets/05_07.jpg
new file mode 100644
index 00000000..cc95ffce
Binary files /dev/null and b/zh-cn/course/spring/assets/05_07.jpg differ
diff --git a/zh-cn/course/spring/assets/05_08.jpg b/zh-cn/course/spring/assets/05_08.jpg
new file mode 100644
index 00000000..0c4825f4
Binary files /dev/null and b/zh-cn/course/spring/assets/05_08.jpg differ
diff --git a/zh-cn/course/spring/assets/06_01.jpg b/zh-cn/course/spring/assets/06_01.jpg
new file mode 100644
index 00000000..05e8a6ff
Binary files /dev/null and b/zh-cn/course/spring/assets/06_01.jpg differ
diff --git a/zh-cn/course/spring/assets/07_01.jpg b/zh-cn/course/spring/assets/07_01.jpg
new file mode 100644
index 00000000..d3e7b414
Binary files /dev/null and b/zh-cn/course/spring/assets/07_01.jpg differ
diff --git a/zh-cn/course/spring/assets/07_02.jpg b/zh-cn/course/spring/assets/07_02.jpg
new file mode 100644
index 00000000..02c4a91b
Binary files /dev/null and b/zh-cn/course/spring/assets/07_02.jpg differ
diff --git a/zh-cn/course/spring/assets/07_03.jpg b/zh-cn/course/spring/assets/07_03.jpg
new file mode 100644
index 00000000..ef92d192
Binary files /dev/null and b/zh-cn/course/spring/assets/07_03.jpg differ
diff --git a/zh-cn/course/spring/assets/08_01.jpg b/zh-cn/course/spring/assets/08_01.jpg
new file mode 100644
index 00000000..3528c355
Binary files /dev/null and b/zh-cn/course/spring/assets/08_01.jpg differ
diff --git a/zh-cn/course/spring/assets/08_02.jpg b/zh-cn/course/spring/assets/08_02.jpg
new file mode 100644
index 00000000..8425f8df
Binary files /dev/null and b/zh-cn/course/spring/assets/08_02.jpg differ
diff --git a/zh-cn/course/spring/assets/08_03.jpg b/zh-cn/course/spring/assets/08_03.jpg
new file mode 100644
index 00000000..49f135eb
Binary files /dev/null and b/zh-cn/course/spring/assets/08_03.jpg differ
diff --git a/zh-cn/course/spring/assets/09_01.jpg b/zh-cn/course/spring/assets/09_01.jpg
new file mode 100644
index 00000000..214eaf50
Binary files /dev/null and b/zh-cn/course/spring/assets/09_01.jpg differ
diff --git a/zh-cn/course/spring/assets/09_02.jpg b/zh-cn/course/spring/assets/09_02.jpg
new file mode 100644
index 00000000..96141a89
Binary files /dev/null and b/zh-cn/course/spring/assets/09_02.jpg differ
diff --git a/zh-cn/course/spring/assets/09_03.jpg b/zh-cn/course/spring/assets/09_03.jpg
new file mode 100644
index 00000000..b4b0dacb
Binary files /dev/null and b/zh-cn/course/spring/assets/09_03.jpg differ
diff --git a/zh-cn/course/spring/assets/09_04.jpg b/zh-cn/course/spring/assets/09_04.jpg
new file mode 100644
index 00000000..15781be2
Binary files /dev/null and b/zh-cn/course/spring/assets/09_04.jpg differ
diff --git a/zh-cn/course/spring/assets/09_05.jpg b/zh-cn/course/spring/assets/09_05.jpg
new file mode 100644
index 00000000..333f381f
Binary files /dev/null and b/zh-cn/course/spring/assets/09_05.jpg differ
diff --git a/zh-cn/course/spring/assets/09_06.jpg b/zh-cn/course/spring/assets/09_06.jpg
new file mode 100644
index 00000000..e105679f
Binary files /dev/null and b/zh-cn/course/spring/assets/09_06.jpg differ
diff --git a/zh-cn/course/spring/assets/09_07.jpg b/zh-cn/course/spring/assets/09_07.jpg
new file mode 100644
index 00000000..9259b511
Binary files /dev/null and b/zh-cn/course/spring/assets/09_07.jpg differ
diff --git a/zh-cn/course/spring/assets/09_08.jpg b/zh-cn/course/spring/assets/09_08.jpg
new file mode 100644
index 00000000..40a8bfac
Binary files /dev/null and b/zh-cn/course/spring/assets/09_08.jpg differ
diff --git a/zh-cn/course/spring/assets/09_09.jpg b/zh-cn/course/spring/assets/09_09.jpg
new file mode 100644
index 00000000..38a96801
Binary files /dev/null and b/zh-cn/course/spring/assets/09_09.jpg differ
diff --git a/zh-cn/course/spring/assets/09_10.jpg b/zh-cn/course/spring/assets/09_10.jpg
new file mode 100644
index 00000000..67fed4cb
Binary files /dev/null and b/zh-cn/course/spring/assets/09_10.jpg differ
diff --git a/zh-cn/course/spring/assets/09_11.jpg b/zh-cn/course/spring/assets/09_11.jpg
new file mode 100644
index 00000000..ab7a544e
Binary files /dev/null and b/zh-cn/course/spring/assets/09_11.jpg differ
diff --git a/zh-cn/course/spring/assets/09_12.jpg b/zh-cn/course/spring/assets/09_12.jpg
new file mode 100644
index 00000000..51b82be0
Binary files /dev/null and b/zh-cn/course/spring/assets/09_12.jpg differ
diff --git a/zh-cn/course/spring/assets/09_13.jpg b/zh-cn/course/spring/assets/09_13.jpg
new file mode 100644
index 00000000..29fe8e2b
Binary files /dev/null and b/zh-cn/course/spring/assets/09_13.jpg differ
diff --git a/zh-cn/course/spring/assets/10_01.jpg b/zh-cn/course/spring/assets/10_01.jpg
new file mode 100644
index 00000000..2f2ef6b4
Binary files /dev/null and b/zh-cn/course/spring/assets/10_01.jpg differ
diff --git a/zh-cn/course/spring/assets/10_02.jpg b/zh-cn/course/spring/assets/10_02.jpg
new file mode 100644
index 00000000..cc853235
Binary files /dev/null and b/zh-cn/course/spring/assets/10_02.jpg differ
diff --git a/zh-cn/course/spring/assets/10_03.jpg b/zh-cn/course/spring/assets/10_03.jpg
new file mode 100644
index 00000000..356688be
Binary files /dev/null and b/zh-cn/course/spring/assets/10_03.jpg differ
diff --git a/zh-cn/course/spring/assets/11_01.jpg b/zh-cn/course/spring/assets/11_01.jpg
new file mode 100644
index 00000000..55e6a28d
Binary files /dev/null and b/zh-cn/course/spring/assets/11_01.jpg differ
diff --git a/zh-cn/course/spring/assets/11_02.jpg b/zh-cn/course/spring/assets/11_02.jpg
new file mode 100644
index 00000000..bbc3a45e
Binary files /dev/null and b/zh-cn/course/spring/assets/11_02.jpg differ
diff --git a/zh-cn/course/spring/assets/12_01.jpg b/zh-cn/course/spring/assets/12_01.jpg
new file mode 100644
index 00000000..7be2f6cc
Binary files /dev/null and b/zh-cn/course/spring/assets/12_01.jpg differ
diff --git a/zh-cn/course/spring/assets/12_02.jpg b/zh-cn/course/spring/assets/12_02.jpg
new file mode 100644
index 00000000..b13ffcac
Binary files /dev/null and b/zh-cn/course/spring/assets/12_02.jpg differ
diff --git a/zh-cn/course/spring/assets/12_03.jpg b/zh-cn/course/spring/assets/12_03.jpg
new file mode 100644
index 00000000..e9a47375
Binary files /dev/null and b/zh-cn/course/spring/assets/12_03.jpg differ
diff --git a/zh-cn/course/spring/assets/12_04.jpg b/zh-cn/course/spring/assets/12_04.jpg
new file mode 100644
index 00000000..71d3dd01
Binary files /dev/null and b/zh-cn/course/spring/assets/12_04.jpg differ
diff --git a/zh-cn/course/spring/assets/13_01.jpg b/zh-cn/course/spring/assets/13_01.jpg
new file mode 100644
index 00000000..802c5ce0
Binary files /dev/null and b/zh-cn/course/spring/assets/13_01.jpg differ
diff --git a/zh-cn/course/spring/assets/13_02.jpg b/zh-cn/course/spring/assets/13_02.jpg
new file mode 100644
index 00000000..6d0f834e
Binary files /dev/null and b/zh-cn/course/spring/assets/13_02.jpg differ
diff --git a/zh-cn/course/spring/assets/13_03.jpg b/zh-cn/course/spring/assets/13_03.jpg
new file mode 100644
index 00000000..cef11637
Binary files /dev/null and b/zh-cn/course/spring/assets/13_03.jpg differ
diff --git a/zh-cn/course/spring/assets/13_04.jpg b/zh-cn/course/spring/assets/13_04.jpg
new file mode 100644
index 00000000..b7b77577
Binary files /dev/null and b/zh-cn/course/spring/assets/13_04.jpg differ
diff --git a/zh-cn/course/spring/assets/13_05.jpg b/zh-cn/course/spring/assets/13_05.jpg
new file mode 100644
index 00000000..15bd1ca3
Binary files /dev/null and b/zh-cn/course/spring/assets/13_05.jpg differ
diff --git a/zh-cn/course/spring/assets/13_06.jpg b/zh-cn/course/spring/assets/13_06.jpg
new file mode 100644
index 00000000..234160d4
Binary files /dev/null and b/zh-cn/course/spring/assets/13_06.jpg differ
diff --git a/zh-cn/course/spring/assets/13_07.jpg b/zh-cn/course/spring/assets/13_07.jpg
new file mode 100644
index 00000000..38873042
Binary files /dev/null and b/zh-cn/course/spring/assets/13_07.jpg differ
diff --git a/zh-cn/course/spring/assets/13_08.jpg b/zh-cn/course/spring/assets/13_08.jpg
new file mode 100644
index 00000000..234160d4
Binary files /dev/null and b/zh-cn/course/spring/assets/13_08.jpg differ
diff --git a/zh-cn/course/spring/assets/15_01.jpg b/zh-cn/course/spring/assets/15_01.jpg
new file mode 100644
index 00000000..76f95b79
Binary files /dev/null and b/zh-cn/course/spring/assets/15_01.jpg differ
diff --git a/zh-cn/course/spring/assets/15_02.jpg b/zh-cn/course/spring/assets/15_02.jpg
new file mode 100644
index 00000000..9c2ae53e
Binary files /dev/null and b/zh-cn/course/spring/assets/15_02.jpg differ
diff --git a/zh-cn/course/spring/assets/15_03.jpg b/zh-cn/course/spring/assets/15_03.jpg
new file mode 100644
index 00000000..52452879
Binary files /dev/null and b/zh-cn/course/spring/assets/15_03.jpg differ
diff --git a/zh-cn/course/spring/assets/15_04.jpg b/zh-cn/course/spring/assets/15_04.jpg
new file mode 100644
index 00000000..8c27a962
Binary files /dev/null and b/zh-cn/course/spring/assets/15_04.jpg differ
diff --git a/zh-cn/course/spring/assets/15_05.jpg b/zh-cn/course/spring/assets/15_05.jpg
new file mode 100644
index 00000000..4b742912
Binary files /dev/null and b/zh-cn/course/spring/assets/15_05.jpg differ
diff --git a/zh-cn/course/spring/assets/15_06.jpg b/zh-cn/course/spring/assets/15_06.jpg
new file mode 100644
index 00000000..692f8b36
Binary files /dev/null and b/zh-cn/course/spring/assets/15_06.jpg differ
diff --git a/zh-cn/course/spring/assets/15_07.jpg b/zh-cn/course/spring/assets/15_07.jpg
new file mode 100644
index 00000000..2d203f9c
Binary files /dev/null and b/zh-cn/course/spring/assets/15_07.jpg differ
diff --git a/zh-cn/course/spring/assets/16_01.jpg b/zh-cn/course/spring/assets/16_01.jpg
new file mode 100644
index 00000000..2deea338
Binary files /dev/null and b/zh-cn/course/spring/assets/16_01.jpg differ
diff --git a/zh-cn/course/spring/assets/16_02.jpg b/zh-cn/course/spring/assets/16_02.jpg
new file mode 100644
index 00000000..f1523336
Binary files /dev/null and b/zh-cn/course/spring/assets/16_02.jpg differ
diff --git a/zh-cn/course/spring/assets/16_03.jpg b/zh-cn/course/spring/assets/16_03.jpg
new file mode 100644
index 00000000..8d0105ea
Binary files /dev/null and b/zh-cn/course/spring/assets/16_03.jpg differ
diff --git a/zh-cn/course/spring/assets/16_04.jpg b/zh-cn/course/spring/assets/16_04.jpg
new file mode 100644
index 00000000..e9456114
Binary files /dev/null and b/zh-cn/course/spring/assets/16_04.jpg differ
diff --git a/zh-cn/course/spring/assets/17_01.jpg b/zh-cn/course/spring/assets/17_01.jpg
new file mode 100644
index 00000000..27a1616d
Binary files /dev/null and b/zh-cn/course/spring/assets/17_01.jpg differ
diff --git a/zh-cn/course/spring/assets/17_02.jpg b/zh-cn/course/spring/assets/17_02.jpg
new file mode 100644
index 00000000..738d776f
Binary files /dev/null and b/zh-cn/course/spring/assets/17_02.jpg differ
diff --git a/zh-cn/course/spring/assets/17_03.jpg b/zh-cn/course/spring/assets/17_03.jpg
new file mode 100644
index 00000000..94b7676a
Binary files /dev/null and b/zh-cn/course/spring/assets/17_03.jpg differ
diff --git a/zh-cn/course/spring/assets/17_04.jpg b/zh-cn/course/spring/assets/17_04.jpg
new file mode 100644
index 00000000..cca77b41
Binary files /dev/null and b/zh-cn/course/spring/assets/17_04.jpg differ
diff --git a/zh-cn/course/spring/assets/17_05.jpg b/zh-cn/course/spring/assets/17_05.jpg
new file mode 100644
index 00000000..c0c8eb12
Binary files /dev/null and b/zh-cn/course/spring/assets/17_05.jpg differ
diff --git a/zh-cn/course/spring/assets/17_06.jpg b/zh-cn/course/spring/assets/17_06.jpg
new file mode 100644
index 00000000..4ee2f5d4
Binary files /dev/null and b/zh-cn/course/spring/assets/17_06.jpg differ
diff --git a/zh-cn/course/spring/assets/18_01.jpg b/zh-cn/course/spring/assets/18_01.jpg
new file mode 100644
index 00000000..98b0e796
Binary files /dev/null and b/zh-cn/course/spring/assets/18_01.jpg differ
diff --git a/zh-cn/course/spring/assets/18_02.jpg b/zh-cn/course/spring/assets/18_02.jpg
new file mode 100644
index 00000000..33983e40
Binary files /dev/null and b/zh-cn/course/spring/assets/18_02.jpg differ
diff --git a/zh-cn/course/spring/assets/18_03.jpg b/zh-cn/course/spring/assets/18_03.jpg
new file mode 100644
index 00000000..da0314a5
Binary files /dev/null and b/zh-cn/course/spring/assets/18_03.jpg differ
diff --git a/zh-cn/course/spring/assets/18_04.jpg b/zh-cn/course/spring/assets/18_04.jpg
new file mode 100644
index 00000000..cfe22ec2
Binary files /dev/null and b/zh-cn/course/spring/assets/18_04.jpg differ
diff --git a/zh-cn/course/spring/assets/19_01.jpg b/zh-cn/course/spring/assets/19_01.jpg
new file mode 100644
index 00000000..4f82d449
Binary files /dev/null and b/zh-cn/course/spring/assets/19_01.jpg differ
diff --git a/zh-cn/course/spring/assets/19_02.jpg b/zh-cn/course/spring/assets/19_02.jpg
new file mode 100644
index 00000000..0c938b97
Binary files /dev/null and b/zh-cn/course/spring/assets/19_02.jpg differ
diff --git a/zh-cn/course/spring/assets/20_01.jpg b/zh-cn/course/spring/assets/20_01.jpg
new file mode 100644
index 00000000..de1025e6
Binary files /dev/null and b/zh-cn/course/spring/assets/20_01.jpg differ
diff --git a/zh-cn/course/spring/assets/21_01.jpg b/zh-cn/course/spring/assets/21_01.jpg
new file mode 100644
index 00000000..8e6ebc77
Binary files /dev/null and b/zh-cn/course/spring/assets/21_01.jpg differ
diff --git a/zh-cn/course/spring/assets/21_02.jpg b/zh-cn/course/spring/assets/21_02.jpg
new file mode 100644
index 00000000..778a168d
Binary files /dev/null and b/zh-cn/course/spring/assets/21_02.jpg differ
diff --git a/zh-cn/course/spring/assets/21_03.jpg b/zh-cn/course/spring/assets/21_03.jpg
new file mode 100644
index 00000000..b3f53257
Binary files /dev/null and b/zh-cn/course/spring/assets/21_03.jpg differ
diff --git a/zh-cn/course/spring/assets/21_04.jpg b/zh-cn/course/spring/assets/21_04.jpg
new file mode 100644
index 00000000..0c266261
Binary files /dev/null and b/zh-cn/course/spring/assets/21_04.jpg differ
diff --git a/zh-cn/course/spring/assets/21_05.jpg b/zh-cn/course/spring/assets/21_05.jpg
new file mode 100644
index 00000000..31177b1b
Binary files /dev/null and b/zh-cn/course/spring/assets/21_05.jpg differ
diff --git a/zh-cn/course/spring/assets/21_06.jpg b/zh-cn/course/spring/assets/21_06.jpg
new file mode 100644
index 00000000..d61bdb3e
Binary files /dev/null and b/zh-cn/course/spring/assets/21_06.jpg differ
diff --git a/zh-cn/course/spring/assets/21_07.jpg b/zh-cn/course/spring/assets/21_07.jpg
new file mode 100644
index 00000000..c20c17d9
Binary files /dev/null and b/zh-cn/course/spring/assets/21_07.jpg differ
diff --git a/zh-cn/course/spring/assets/21_08.jpg b/zh-cn/course/spring/assets/21_08.jpg
new file mode 100644
index 00000000..b0416b75
Binary files /dev/null and b/zh-cn/course/spring/assets/21_08.jpg differ
diff --git a/zh-cn/course/spring/assets/21_09.jpg b/zh-cn/course/spring/assets/21_09.jpg
new file mode 100644
index 00000000..43765289
Binary files /dev/null and b/zh-cn/course/spring/assets/21_09.jpg differ
diff --git a/zh-cn/course/spring/assets/21_10.jpg b/zh-cn/course/spring/assets/21_10.jpg
new file mode 100644
index 00000000..e20feab1
Binary files /dev/null and b/zh-cn/course/spring/assets/21_10.jpg differ
diff --git a/zh-cn/course/spring/assets/21_11.jpg b/zh-cn/course/spring/assets/21_11.jpg
new file mode 100644
index 00000000..0be0d183
Binary files /dev/null and b/zh-cn/course/spring/assets/21_11.jpg differ
diff --git a/zh-cn/course/spring/assets/21_12.jpg b/zh-cn/course/spring/assets/21_12.jpg
new file mode 100644
index 00000000..0c3b22fb
Binary files /dev/null and b/zh-cn/course/spring/assets/21_12.jpg differ
diff --git a/zh-cn/course/spring/assets/22_01.jpg b/zh-cn/course/spring/assets/22_01.jpg
new file mode 100644
index 00000000..5ab8253b
Binary files /dev/null and b/zh-cn/course/spring/assets/22_01.jpg differ
diff --git a/zh-cn/course/spring/assets/22_02.jpg b/zh-cn/course/spring/assets/22_02.jpg
new file mode 100644
index 00000000..45622938
Binary files /dev/null and b/zh-cn/course/spring/assets/22_02.jpg differ
diff --git a/zh-cn/course/spring/assets/22_03.jpg b/zh-cn/course/spring/assets/22_03.jpg
new file mode 100644
index 00000000..792d4e12
Binary files /dev/null and b/zh-cn/course/spring/assets/22_03.jpg differ
diff --git a/zh-cn/course/spring/assets/22_04.jpg b/zh-cn/course/spring/assets/22_04.jpg
new file mode 100644
index 00000000..65a7c766
Binary files /dev/null and b/zh-cn/course/spring/assets/22_04.jpg differ
diff --git a/zh-cn/course/spring/assets/22_05.jpg b/zh-cn/course/spring/assets/22_05.jpg
new file mode 100644
index 00000000..518d761a
Binary files /dev/null and b/zh-cn/course/spring/assets/22_05.jpg differ
diff --git a/zh-cn/course/spring/assets/22_06.jpg b/zh-cn/course/spring/assets/22_06.jpg
new file mode 100644
index 00000000..35efbc09
Binary files /dev/null and b/zh-cn/course/spring/assets/22_06.jpg differ
diff --git a/zh-cn/course/spring/assets/22_07.jpg b/zh-cn/course/spring/assets/22_07.jpg
new file mode 100644
index 00000000..ed4665ca
Binary files /dev/null and b/zh-cn/course/spring/assets/22_07.jpg differ
diff --git a/zh-cn/course/spring/assets/22_08.jpg b/zh-cn/course/spring/assets/22_08.jpg
new file mode 100644
index 00000000..5605f995
Binary files /dev/null and b/zh-cn/course/spring/assets/22_08.jpg differ
diff --git a/zh-cn/course/spring/assets/22_09.jpg b/zh-cn/course/spring/assets/22_09.jpg
new file mode 100644
index 00000000..1d14c53d
Binary files /dev/null and b/zh-cn/course/spring/assets/22_09.jpg differ
diff --git a/zh-cn/course/spring/assets/22_10.jpg b/zh-cn/course/spring/assets/22_10.jpg
new file mode 100644
index 00000000..fd1d679d
Binary files /dev/null and b/zh-cn/course/spring/assets/22_10.jpg differ
diff --git a/zh-cn/course/spring/assets/22_11.jpg b/zh-cn/course/spring/assets/22_11.jpg
new file mode 100644
index 00000000..bb3cc428
Binary files /dev/null and b/zh-cn/course/spring/assets/22_11.jpg differ
diff --git a/zh-cn/course/spring/assets/23_01.jpg b/zh-cn/course/spring/assets/23_01.jpg
new file mode 100644
index 00000000..0a0b0c16
Binary files /dev/null and b/zh-cn/course/spring/assets/23_01.jpg differ
diff --git a/zh-cn/course/spring/assets/23_02.jpg b/zh-cn/course/spring/assets/23_02.jpg
new file mode 100644
index 00000000..5ae304c7
Binary files /dev/null and b/zh-cn/course/spring/assets/23_02.jpg differ
diff --git a/zh-cn/course/spring/assets/23_03.jpg b/zh-cn/course/spring/assets/23_03.jpg
new file mode 100644
index 00000000..8275c853
Binary files /dev/null and b/zh-cn/course/spring/assets/23_03.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_01.jpg b/zh-cn/course/spring/assets/daodu_01.jpg
new file mode 100644
index 00000000..1430d77c
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_01.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_02.jpg b/zh-cn/course/spring/assets/daodu_02.jpg
new file mode 100644
index 00000000..4964206b
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_02.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_03.jpg b/zh-cn/course/spring/assets/daodu_03.jpg
new file mode 100644
index 00000000..42dbfd42
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_03.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_04.jpg b/zh-cn/course/spring/assets/daodu_04.jpg
new file mode 100644
index 00000000..50d7131b
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_04.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_05.jpg b/zh-cn/course/spring/assets/daodu_05.jpg
new file mode 100644
index 00000000..206f4fae
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_05.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_06.jpg b/zh-cn/course/spring/assets/daodu_06.jpg
new file mode 100644
index 00000000..c4075888
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_06.jpg differ
diff --git a/zh-cn/course/spring/assets/daodu_07.jpg b/zh-cn/course/spring/assets/daodu_07.jpg
new file mode 100644
index 00000000..6aa9f8ff
Binary files /dev/null and b/zh-cn/course/spring/assets/daodu_07.jpg differ
diff --git a/zh-cn/course/spring/assets/huigu_01.jpg b/zh-cn/course/spring/assets/huigu_01.jpg
new file mode 100644
index 00000000..7e6aa5a3
Binary files /dev/null and b/zh-cn/course/spring/assets/huigu_01.jpg differ
diff --git a/zh-cn/course/spring/assets/huigu_02.jpg b/zh-cn/course/spring/assets/huigu_02.jpg
new file mode 100644
index 00000000..681431af
Binary files /dev/null and b/zh-cn/course/spring/assets/huigu_02.jpg differ
diff --git "a/zh-cn/course/spring/\345\257\274\350\257\273 5\345\210\206\351\222\237\350\275\273\346\235\276\344\272\206\350\247\243\344\270\200\344\270\252HTTP\350\257\267\346\261\202\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.md" "b/zh-cn/course/spring/\345\257\274\350\257\273 5\345\210\206\351\222\237\350\275\273\346\235\276\344\272\206\350\247\243\344\270\200\344\270\252HTTP\350\257\267\346\261\202\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.md"
index 2e18c0f8..15beae56 100644
--- "a/zh-cn/course/spring/\345\257\274\350\257\273 5\345\210\206\351\222\237\350\275\273\346\235\276\344\272\206\350\247\243\344\270\200\344\270\252HTTP\350\257\267\346\261\202\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.md"
+++ "b/zh-cn/course/spring/\345\257\274\350\257\273 5\345\210\206\351\222\237\350\275\273\346\235\276\344\272\206\350\247\243\344\270\200\344\270\252HTTP\350\257\267\346\261\202\347\232\204\345\244\204\347\220\206\350\277\207\347\250\213.md"
@@ -40,7 +40,7 @@ public class HttpRequestHandler{ Map mapper = new HashMap<>(
首先,解析 HTTP 请求。对于 Spring 而言,它本身并不提供通信层的支持,它是依赖于Tomcat、Jetty等容器来完成通信层的支持,例如当我们引入Spring Boot时,我们就间接依赖了Tomcat。依赖关系图如下:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/87f83fe678694b96afb78d62b461ba25.jpg)
+![](assets/daodu_01.jpg)
另外,正是这种自由组合的关系,让我们可以做到直接置换容器而不影响功能。例如我们可以通过下面的配置从默认的Tomcat切换到Jetty:
@@ -52,7 +52,7 @@ public class HttpRequestHandler{ Map mapper = new HashMap<>(
关于Tomcat如何被启动,你可以通过下面的调用栈来大致了解下它的过程:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/4b8b3a9eec9046149c4f6c8481a99de6.jpg)
+![](assets/daodu_02.jpg)
说白了,就是调用下述代码行就会启动Tomcat:
@@ -80,11 +80,11 @@ SpringApplication.run(Application.class, args);
上述代码会完成请求事件的监听和处理,最终在processKey中把请求事件丢入线程池去处理。请求事件的接收具体调用栈如下:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/88c01a6e7cfb404a8ab742b37559406c.jpg)
+![](assets/daodu_03.jpg)
线程池对这个请求的处理的调用栈如下:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/27da4f2b056b4c709656e95e0d37c27e.jpg)
+![](assets/daodu_04.jpg)
在上述调用中,最终会进入Spring Boot的处理核心,即DispatcherServlet(上述调用栈没有继续截取完整调用,所以未显示)。可以说,DispatcherServlet是用来处理HTTP请求的中央调度入口程序,为每一个 Web 请求映射一个请求的处理执行体(API controller/method)。
@@ -110,7 +110,7 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
寻找方法参考DispatcherServlet#getHandler,具体的查找远比开始给出的Map查找来得复杂,但是无非还是一个根据请求寻找候选执行方法的过程,这里我们可以通过一个调试视图感受下这种对应关系:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/107d4853543d466599f484fc26a98d4e.jpg)
+![](assets/daodu_05.jpg)
这里的关键映射Map,其实就是上述调试视图中的RequestMappingHandlerMapping。
@@ -118,7 +118,7 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
这点可以参考下面的调试视图来验证这个结论,参考代码org.springframework.web.method.support.InvocableHandlerMethod#doInvoke:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/70da4564e8b44f1682472b6d8a1a304c.jpg)
+![](assets/daodu_06.jpg)
最终我们是通过反射来调用执行方法的。
@@ -128,7 +128,7 @@ protected void doDispatch(HttpServletRequest request, HttpServletResponse respon
它的构建完成后,会调用afterPropertiesSet来做一些额外的事,这里我们可以先看下它的调用栈:
-![](%E5%AF%BC%E8%AF%BB%205%E5%88%86%E9%92%9F%E8%BD%BB%E6%9D%BE%E4%BA%86%E8%A7%A3%E4%B8%80%E4%B8%AAHTTP%E8%AF%B7%E6%B1%82%E7%9A%84%E5%A4%84%E7%90%86%E8%BF%87%E7%A8%8B/ec78729486f54bdea3735437ad3f59ea.jpg)
+![](assets/daodu_07.jpg)
其中关键的操作是AbstractHandlerMethodMapping#processCandidateBean方法:
diff --git "a/zh-cn/course/spring/\347\237\245\350\257\206\345\233\236\351\241\276 \347\263\273\347\273\237\346\242\263\347\220\206Spring\347\274\226\347\250\213\351\224\231\350\257\257\346\240\271\346\272\220.md" "b/zh-cn/course/spring/\347\237\245\350\257\206\345\233\236\351\241\276 \347\263\273\347\273\237\346\242\263\347\220\206Spring\347\274\226\347\250\213\351\224\231\350\257\257\346\240\271\346\272\220.md"
index eea124f3..f7ce14de 100644
--- "a/zh-cn/course/spring/\347\237\245\350\257\206\345\233\236\351\241\276 \347\263\273\347\273\237\346\242\263\347\220\206Spring\347\274\226\347\250\213\351\224\231\350\257\257\346\240\271\346\272\220.md"
+++ "b/zh-cn/course/spring/\347\237\245\350\257\206\345\233\236\351\241\276 \347\263\273\347\273\237\346\242\263\347\220\206Spring\347\274\226\347\250\213\351\224\231\350\257\257\346\240\271\346\272\220.md"
@@ -8,7 +8,7 @@
要想使用好 Spring,你就一**定要了解它的一些潜规则**,例如默认扫描Bean的范围、自动装配构造器等等。如果我们不了解这些规则,大多情况下虽然也能工作,但是稍微变化,则可能完全失效,例如在[第1课](https://time.geekbang.org/column/article/364761)的案例1中,我们使用 Spring Boot 来快速构建了一个简易的 Web 版 HelloWorld:
-![](%E7%9F%A5%E8%AF%86%E5%9B%9E%E9%A1%BE%20%E7%B3%BB%E7%BB%9F%E6%A2%B3%E7%90%86Spring%E7%BC%96%E7%A8%8B%E9%94%99%E8%AF%AF%E6%A0%B9%E6%BA%90/e110ee0ed8fa4cf3845c754dddfdbb0d.jpg)
+![](assets/huigu_01.jpg)
其中,负责启动程序的 Application 类定义如下:
@@ -24,7 +24,7 @@ package com.spring.puzzle.class1.example1.application //省略 import @RestContr
但是,假设有一天,当我们需要添加多个类似的 Controller,同时又希望用更清晰的包层次结构来管理时,我们可能会去单独建立一个独立于 application 包之外的 Controller 包,并调整类的位置。调整后结构示意如下:
-![](%E7%9F%A5%E8%AF%86%E5%9B%9E%E9%A1%BE%20%E7%B3%BB%E7%BB%9F%E6%A2%B3%E7%90%86Spring%E7%BC%96%E7%A8%8B%E9%94%99%E8%AF%AF%E6%A0%B9%E6%BA%90/7ea97f198edb4002ae5e7d69500d42db.jpg)
+![](assets/huigu_02.jpg)
这样就会工作不起来了,追根溯源,你可能忽略了Sping Boot中@SpringBootApplication是有一个默认的扫描包范围的。这就是一个隐私规则。如果你原本不知道,那么犯错概率还是很高的。类似的案例这里不再赘述。
diff --git "a/zh-cn/course/spring/\347\273\223\346\235\237\350\257\255 \351\227\256\351\242\230\346\200\273\346\257\224\350\247\243\345\206\263\345\212\236\346\263\225\345\244\232.md" "b/zh-cn/course/spring/\347\273\223\346\235\237\350\257\255 \351\227\256\351\242\230\346\200\273\346\257\224\350\247\243\345\206\263\345\212\236\346\263\225\345\244\232.md"
index 2845239f..35fadc7c 100644
--- "a/zh-cn/course/spring/\347\273\223\346\235\237\350\257\255 \351\227\256\351\242\230\346\200\273\346\257\224\350\247\243\345\206\263\345\212\236\346\263\225\345\244\232.md"
+++ "b/zh-cn/course/spring/\347\273\223\346\235\237\350\257\255 \351\227\256\351\242\230\346\200\273\346\257\224\350\247\243\345\206\263\345\212\236\346\263\225\345\244\232.md"
@@ -48,6 +48,4 @@
最后的最后,非常感谢各位同学的信任,这门课程我为你提供了很多的问题场景以及解决问题的思路,希望能帮助你在技术之路上越走越远!在学习的过程中,如果你有什么意见或者建议,也欢迎通过下方的**结课问卷**告知我,我会正视大家的声音。
-我是傅健,我们江湖再见!
-
-[![](%E7%BB%93%E6%9D%9F%E8%AF%AD%20%E9%97%AE%E9%A2%98%E6%80%BB%E6%AF%94%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95%E5%A4%9A/e08c145566e0453980b293d127a23b29.jpg)](https://jinshuju.net/f/KKizl7)
\ No newline at end of file
+我是傅健,我们江湖再见!
\ No newline at end of file
diff --git a/zh-cn/course/tech-management/README.md b/zh-cn/course/tech-management/README.md
index 7b3b7d5f..a59dc0b7 100644
--- a/zh-cn/course/tech-management/README.md
+++ b/zh-cn/course/tech-management/README.md
@@ -1,23 +1,23 @@
# 技术管理实战 36 讲
-[00 开篇词 你为什么需要学管理?]()
+[00 开篇词 你为什么需要学管理?]()
-[01 多年前的那些工程师都去哪了?]()
+[01 多年前的那些工程师都去哪了?]()
-[02 我要不要做管理呢?内心好纠结!]()
+[02 我要不要做管理呢?内心好纠结!]()
-[03 哪些人比较容易走上管理岗位?]()
+[03 哪些人比较容易走上管理岗位?]()
-[04 我要不要转回去做技术呢?]()
+[04 我要不要转回去做技术呢?]()
-[05 作为技术管理者,我如何保持技术判断力?]()
+[05 作为技术管理者,我如何保持技术判断力?]()
-[06 我这样的风格能做管理吗?]()
+[06 我这样的风格能做管理吗?]()
-[07 我能做好管理吗,大家服我吗?]()
+[07 我能做好管理吗,大家服我吗?]()
-[08 管理到底都做哪些事儿?]()
+[08 管理到底都做哪些事儿?]()
-[09 从工程师到管理者,角色都发生了哪些变化?]()
+[09 从工程师到管理者,角色都发生了哪些变化?]()
-[10 新经理常踩的坑儿有哪些?]()
\ No newline at end of file
+[10 新经理常踩的坑儿有哪些?]()
\ No newline at end of file