diff --git a/source/_posts/复习1.md b/source/_posts/复习1.md index 2de9f91e..5053fbc3 100644 --- a/source/_posts/复习1.md +++ b/source/_posts/复习1.md @@ -704,32 +704,32 @@ void transfer(Entry[] newTable, boolean rehash) { * e 和 next 都是局部变量,用来指向当前节点和下一个节点 * 线程1(绿色)的临时变量 e 和 next 刚引用了这俩节点,还未来得及移动节点,发生了线程切换,由线程2(蓝色)完成扩容和迁移 -![image-20210831084325075](img/image-20210831084325075.png) +![image-20210831084325075](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831084325075.png) * 线程2 扩容完成,由于头插法,链表顺序颠倒。但线程1 的临时变量 e 和 next 还引用了这俩节点,还要再来一遍迁移 -![image-20210831084723383](img/image-20210831084723383.png) +![image-20210831084723383](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831084723383.png) * 第一次循环 * 循环接着线程切换前运行,注意此时 e 指向的是节点 a,next 指向的是节点 b * e 头插 a 节点,注意图中画了两份 a 节点,但事实上只有一个(为了不让箭头特别乱画了两份) * 当循环结束是 e 会指向 next 也就是 b 节点 -![image-20210831084855348](img/image-20210831084855348.png) +![image-20210831084855348](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831084855348.png) * 第二次循环 * next 指向了节点 a * e 头插节点 b * 当循环结束时,e 指向 next 也就是节点 a -![image-20210831085329449](img/image-20210831085329449.png) +![image-20210831085329449](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831085329449.png) * 第三次循环 * next 指向了 null * e 头插节点 a,**a 的 next 指向了 b**(之前 a.next 一直是 null),b 的 next 指向 a,死链已成 * 当循环结束时,e 指向 next 也就是 null,因此第四次循环时会正常退出 -![image-20210831085543224](img/image-20210831085543224.png) +![image-20210831085543224](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831085543224.png) **数据错乱(1.7,1.8 都会存在)** diff --git a/source/_posts/复习2.md b/source/_posts/复习2.md index 1e50132f..77b16f7c 100644 --- a/source/_posts/复习2.md +++ b/source/_posts/复习2.md @@ -35,7 +35,7 @@ aside: **六种状态及转换** -![image-20210831090722658](img/image-20210831090722658.png) +![image-20210831090722658](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831090722658.png) 分别是 @@ -69,7 +69,7 @@ aside: 五种状态的说法来自于操作系统层面的划分 -![image-20210831092652602](img/image-20210831092652602.png) +![image-20210831092652602](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831092652602.png) * 运行态:分到 cpu 时间,能真正执行线程内代码的 * 就绪态:有资格分到 cpu 时间,但还未轮到它的 @@ -100,7 +100,7 @@ aside: 3. 丢弃任务 java.util.concurrent.ThreadPoolExecutor.DiscardPolicy 4. 丢弃最早排队任务 java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy -![image-20210831093204388](img/image-20210831093204388.png) +![image-20210831093204388](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831093204388.png) > ***代码说明*** > diff --git a/source/_posts/复习3.md b/source/_posts/复习3.md index fd94db78..c535cec6 100644 --- a/source/_posts/复习3.md +++ b/source/_posts/复习3.md @@ -34,7 +34,7 @@ aside: **结合一段 java 代码的执行理解内存划分** -![image-20210831165728217](img/image-20210831165728217.png) +![image-20210831165728217](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831165728217.png) * 执行 javac 命令编译源代码为字节码 * 执行 java 命令 @@ -70,7 +70,7 @@ aside: * **永久代**是 Hotspot 虚拟机对 JVM 规范的实现(1.8 之前) * **元空间**是 Hotspot 虚拟机对 JVM 规范的另一种实现(1.8 以后),使用本地内存作为这些信息的存储空间 -![image-20210831170457337](img/image-20210831170457337.png) +![image-20210831170457337](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831170457337.png) 从这张图学到三点 @@ -78,7 +78,7 @@ aside: * X,Y 的类元信息是存储于元空间中,无法直接访问 * 可以用 X.class,Y.class 间接访问类元信息,它们俩属于 java 对象,我们的代码中可以使用 -![image-20210831170512418](img/image-20210831170512418.png) +![image-20210831170512418](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831170512418.png) 从这张图可以学到 @@ -95,7 +95,7 @@ aside: **堆内存,按大小设置** -![image-20210831173130717](img/image-20210831173130717.png) +![image-20210831173130717](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831173130717.png) 解释: @@ -110,7 +110,7 @@ aside: **堆内存,按比例设置** -![image-20210831173045700](img/image-20210831173045700.png) +![image-20210831173045700](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831173045700.png) 解释: @@ -121,7 +121,7 @@ aside: **元空间内存设置** -![image-20210831173118634](img/image-20210831173118634.png) +![image-20210831173118634](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831173118634.png) 解释: @@ -137,7 +137,7 @@ aside: **代码缓存内存设置** -![image-20210831173148816](img/image-20210831173148816.png) +![image-20210831173148816](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831173148816.png) 解释: @@ -151,7 +151,7 @@ aside: **线程内存设置** -![image-20210831173155481](img/image-20210831173155481.png) +![image-20210831173155481](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831173155481.png) > ***官方参考文档*** > @@ -172,7 +172,7 @@ aside: 标记清除法 -![image-20210831211008162](img/image-20210831211008162.png) +![image-20210831211008162](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831211008162.png) 解释: @@ -190,7 +190,7 @@ aside: 标记整理法 -![image-20210831211641241](img/image-20210831211641241.png) +![image-20210831211641241](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831211641241.png) 解释: @@ -208,7 +208,7 @@ aside: 标记复制法 -![image-20210831212125813](img/image-20210831212125813.png) +![image-20210831212125813](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831212125813.png) 解释: @@ -244,39 +244,39 @@ GC 要点: 1. 伊甸园 eden,最初对象都分配到这里,与幸存区 survivor(分成 from 和 to)合称新生代, -![image-20210831213622704](img/image-20210831213622704.png) +![image-20210831213622704](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213622704.png) 2. 当伊甸园内存不足,标记伊甸园与 from(现阶段没有)的存活对象 -![image-20210831213640110](img/image-20210831213640110.png) +![image-20210831213640110](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213640110.png) 3. 将存活对象采用复制算法复制到 to 中,复制完毕后,伊甸园和 from 内存都得到释放 -![image-20210831213657861](img/image-20210831213657861.png) +![image-20210831213657861](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213657861.png) 4. 将 from 和 to 交换位置 -![image-20210831213708776](img/image-20210831213708776.png) +![image-20210831213708776](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213708776.png) 5. 经过一段时间后伊甸园的内存又出现不足 -![image-20210831213724858](img/image-20210831213724858.png) +![image-20210831213724858](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213724858.png) 6. 标记伊甸园与 from(现阶段没有)的存活对象 -![image-20210831213737669](img/image-20210831213737669.png) +![image-20210831213737669](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213737669.png) 7. 将存活对象采用复制算法复制到 to 中 -![image-20210831213804315](img/image-20210831213804315.png) +![image-20210831213804315](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213804315.png) 8. 复制完毕后,伊甸园和 from 内存都得到释放 -![image-20210831213815371](img/image-20210831213815371.png) +![image-20210831213815371](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213815371.png) 9. 将 from 和 to 交换位置 -![image-20210831213826017](img/image-20210831213826017.png) +![image-20210831213826017](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210831213826017.png) 10. 老年代 old,当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升) @@ -304,23 +304,23 @@ GC 要点: 1. 起始的三个对象还未处理完成,用灰色表示 -image-20210831215016566 +image-20210831215016566 2. 该对象的引用已经处理完成,用黑色表示,黑色引用的对象变为灰色 -image-20210831215033510 +image-20210831215033510 3. 依次类推 -image-20210831215105280 +image-20210831215105280 4. 沿着引用链都标记了一遍 -image-20210831215146276 +image-20210831215146276 5. 最后为标记的白色对象,即为垃圾 -image-20210831215158311 +image-20210831215158311 **并发漏标问题** @@ -328,19 +328,19 @@ GC 要点: 1. 如图所示标记工作尚未完成 -image-20210831215846876 +image-20210831215846876 2. 用户线程同时在工作,断开了第一层 3、4 两个对象之间的引用,这时对于正在处理 3 号对象的垃圾回收线程来讲,它会将 4 号对象当做是白色垃圾 -image-20210831215904073 +image-20210831215904073 3. 但如果其他用户线程又建立了 2、4 两个对象的引用,这时因为 2 号对象是黑色已处理对象了,因此垃圾回收线程不会察觉到这个引用关系的变化,从而产生了漏标 -image-20210831215919493 +image-20210831215919493 4. 如果用户线程让黑色对象引用了一个新增对象,一样会存在漏标问题 -image-20210831220004062 +image-20210831220004062 因此对于**并发标记**而言,必须解决漏标问题,也就是要记录标记过程中的变化。有两种解决方法: @@ -383,53 +383,53 @@ GC 要点: 1. 初始时,所有区域都处于空闲状态 -image-20210831222639754 +image-20210831222639754 2. 创建了一些对象,挑出一些空闲区域作为伊甸园区存储这些对象 -image-20210831222653802 +image-20210831222653802 3. 当伊甸园需要垃圾回收时,挑出一个空闲区域作为幸存区,用复制算法复制存活对象,需要暂停用户线程 -image-20210831222705814 +image-20210831222705814 4. 复制完成,将之前的伊甸园内存释放 -image-20210831222724999 +image-20210831222724999 5. 随着时间流逝,伊甸园的内存又有不足 -image-20210831222737928 +image-20210831222737928 6. 将伊甸园以及之前幸存区中的存活对象,采用复制算法,复制到新的幸存区,其中较老对象晋升至老年代 -image-20210831222752787 +image-20210831222752787 7. 释放伊甸园以及之前幸存区的内存 -image-20210831222803281 +image-20210831222803281 **G1 回收阶段 - 并发标记与混合收集** 1. 当老年代占用内存超过阈值后,触发并发标记,这时无需暂停用户线程 -image-20210831222813959 +image-20210831222813959 2. 并发标记之后,会有重新标记阶段解决漏标问题,此时需要暂停用户线程。这些都完成后就知道了老年代有哪些存活对象,随后进入混合收集阶段。此时不会对所有老年代区域进行回收,而是根据**暂停时间目标**优先回收价值高(存活对象少)的区域(这也是 Gabage First 名称的由来)。 -image-20210831222828104 +image-20210831222828104 3. 混合收集阶段中,参与复制的有 eden、survivor、old,下图显示了伊甸园和幸存区的存活对象复制 -image-20210831222841096 +image-20210831222841096 4. 下图显示了老年代和幸存区晋升的存活对象的复制 -image-20210831222859760 +image-20210831222859760 5. 复制完成,内存得到释放。进入下一轮的新生代回收、并发标记、混合收集 -image-20210831222919182 +image-20210831222919182 ## 4. 内存溢出 @@ -529,7 +529,7 @@ GC 要点: 下面面试题的回答是错误的 -![image-20210901110910016](img/image-20210901110910016.png) +![image-20210901110910016](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210901110910016.png) 错在哪了? @@ -563,7 +563,7 @@ GC 要点: 2. 通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收 -image-20210901111903574 +image-20210901111903574 **软引用(SoftReference)** @@ -575,7 +575,7 @@ GC 要点: 4. 典型例子是反射数据 -image-20210901111957328 +image-20210901111957328 @@ -589,7 +589,7 @@ GC 要点: 4. 典型例子是 ThreadLocalMap 中的 Entry 对象 -image-20210901112107707 +image-20210901112107707 @@ -601,7 +601,7 @@ GC 要点: 3. 典型例子是 Cleaner 释放 DirectByteBuffer 关联的直接内存 -image-20210901112157901 +image-20210901112157901 @@ -634,13 +634,13 @@ GC 要点: 1. 对 finalize 方法进行处理的核心逻辑位于 java.lang.ref.Finalizer 类中,它包含了名为 unfinalized 的静态变量(双向链表结构),Finalizer 也可被视为另一种引用对象(地位与软、弱、虚相当,只是不对外,无法直接使用) 2. 当重写了 finalize 方法的对象,在构造方法调用之时,JVM 都会将其包装成一个 Finalizer 对象,并加入 unfinalized 链表中 -![image-20210901121032813](img/image-20210901121032813.png) +![image-20210901121032813](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210901121032813.png) 3. Finalizer 类中还有另一个重要的静态变量,即 ReferenceQueue 引用队列,刚开始它是空的。当狗对象可以被当作垃圾回收时,就会把这些狗对象对应的 Finalizer 对象加入此引用队列 4. 但此时 Dog 对象还没法被立刻回收,因为 unfinalized -> Finalizer 这一引用链还在引用它嘛,为的是【先别着急回收啊,等我调完 finalize 方法,再回收】 5. FinalizerThread 线程会从 ReferenceQueue 中逐一取出每个 Finalizer 对象,把它们从链表断开并真正调用 finallize 方法 -![image-20210901122228916](img/image-20210901122228916.png) +![image-20210901122228916](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210901122228916.png) 6. 由于整个 Finalizer 对象已经从 unfinalized 链表中断开,这样没谁能引用到它和狗对象,所以下次 gc 时就被回收了 diff --git a/source/_posts/复习4.md b/source/_posts/复习4.md index 9afa3073..cc9c45a7 100644 --- a/source/_posts/复习4.md +++ b/source/_posts/复习4.md @@ -81,7 +81,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * systemEnvironment - 保存系统环境键值 * 自定义 PropertySource - 保存自定义键值,例如来自于 *.properties 文件的键值 -![image-20210902181639048](img/image-20210902181639048.png) +![image-20210902181639048](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902181639048.png) **2. obtainFreshBeanFactory** @@ -91,7 +91,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * BeanDefinition 的来源有多种多样,可以是通过 xml 获得、配置类获得、组件扫描获得,也可以是编程添加 * 所有的 BeanDefinition 会存入 BeanFactory 中的 beanDefinitionMap 集合 -![image-20210902182004819](img/image-20210902182004819.png) +![image-20210902182004819](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902182004819.png) **3. prepareBeanFactory** @@ -105,7 +105,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * ApplicationContextAwareProcessor 用来解析 Aware 接口 * ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean -![image-20210902182541925](img/image-20210902182541925.png) +![image-20210902182541925](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902182541925.png) **4. postProcessBeanFactory** @@ -122,7 +122,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * PropertySourcesPlaceHolderConfigurer – 替换 BeanDefinition 中的 ${ } * MapperScannerConfigurer – 补充 Mapper 接口对应的 BeanDefinition -![image-20210902183232114](img/image-20210902183232114.png) +![image-20210902183232114](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902183232114.png) **6. registerBeanPostProcessors** @@ -132,14 +132,14 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * CommonAnnotationBeanPostProcessor 功能有:解析 @Resource,@PostConstruct,@PreDestroy * AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理 -![image-20210902183520307](img/image-20210902183520307.png) +![image-20210902183520307](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902183520307.png) **7. initMessageSource** * 这一步是为 ApplicationContext 添加 messageSource 成员,实现国际化功能 * 去 beanFactory 内找名为 messageSource 的 bean,如果没有,则提供空的 MessageSource 实现 -![image-20210902183819984](img/image-20210902183819984.png) +![image-20210902183819984](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902183819984.png) **8. initApplicationContextEventMulticaster** @@ -148,7 +148,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * 去 beanFactory 找名为 applicationEventMulticaster 的 bean 作为事件广播器,若没有,会创建默认的事件广播器 * 之后就可以调用 ApplicationContext.publishEvent(事件对象) 来发布事件 -![image-20210902183943469](img/image-20210902183943469.png) +![image-20210902183943469](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902183943469.png) **9. onRefresh** @@ -165,7 +165,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * 来自于 @EventListener 的解析 * 要实现事件监听器,只需要实现 ApplicationListener 接口,重写其中 onApplicationEvent(E e) 方法即可 -![image-20210902184343872](img/image-20210902184343872.png) +![image-20210902184343872](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902184343872.png) **11. finishBeanFactoryInitialization** @@ -175,7 +175,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * singletonObjects 即单例池,缓存所有单例对象 * 对象的创建都分三个阶段,每一阶段都有不同的 bean 后处理器参与进来,扩展功能 -![image-20210902184641623](img/image-20210902184641623.png) +![image-20210902184641623](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902184641623.png) **12. finishRefresh** @@ -186,7 +186,7 @@ refresh 是 AbstractApplicationContext 中的一个方法,负责初始化 Appl * 调用 context 的 stop,即可触发所有实现 LifeCycle 接口 bean 的 stop * 发布 ContextRefreshed 事件,整个 refresh 执行完成 -![image-20210902185052433](img/image-20210902185052433.png) +![image-20210902185052433](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210902185052433.png) @@ -316,7 +316,7 @@ bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean * 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱 -image-20210903085238916 +image-20210903085238916 * set 方法(包括成员变量)的循环依赖如图所示 @@ -325,11 +325,11 @@ bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean * a 的顺序,及 b 的顺序都能得到保障 -image-20210903085454603 +image-20210903085454603 * 构造方法的循环依赖如图所示,显然无法用前面的方法解决 -image-20210903085906315 +image-20210903085906315 **构造循环依赖的解决** @@ -337,13 +337,13 @@ bean 的生命周期从调用 beanFactory 的 getBean 开始,到这个 bean * a 注入 b 的代理对象,这样能够保证 a 的流程走通 * 后续需要用到 b 的真实对象时,可以通过代理间接访问 -image-20210903091627659 +image-20210903091627659 * 思路2 * a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通 * 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问 -image-20210903091743366 +image-20210903091743366 * 示例1:用 @Lazy 为构造方法参数生成代理 @@ -561,7 +561,7 @@ public class App60_4 { **一级缓存** -image-20210903100752165 +image-20210903100752165 作用是保证单例对象仅被创建一次 @@ -570,7 +570,7 @@ public class App60_4 { **一级缓存与循环依赖** -image-20210903100914140 +image-20210903100914140 一级缓存无法解决循环依赖问题,分析如下 @@ -581,7 +581,7 @@ public class App60_4 { **二级缓存** -image-20210903101849924 +image-20210903101849924 解决思路如下: @@ -599,7 +599,7 @@ public class App60_4 { **二级缓存与创建代理** -image-20210903103030877 +image-20210903103030877 二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下 @@ -609,7 +609,7 @@ public class App60_4 { **三级缓存** -![image-20210903103628639](img/image-20210903103628639.png) +![image-20210903103628639](https://imgs-1302910354.cos.ap-shanghai.myqcloud.com/images/image-20210903103628639.png) 简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂: @@ -1024,7 +1024,7 @@ public class Service7 { 4. 容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet 的成员变量,留待后用 -image-20210903140657163 +image-20210903140657163 **匹配阶段** @@ -1038,21 +1038,21 @@ public class Service7 { ③ 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好 -image-20210903141017502 +image-20210903141017502 3. 将 HandlerMethod 连同匹配到的拦截器,生成调用链对象 HandlerExecutionChain 返回 -image-20210903141124911 +image-20210903141124911 4. 遍历HandlerAdapter 处理器适配器,找到能处理 HandlerMethod 的适配器对象,开始调用 -image-20210903141204799 +image-20210903141204799 **调用阶段** 1. 执行拦截器 preHandle -image-20210903141445870 +image-20210903141445870 2. 由 HandlerAdapter 调用 HandlerMethod @@ -1060,7 +1060,7 @@ public class Service7 { ② 调用后处理不同类型的返回值 -image-20210903141658199 +image-20210903141658199 3. 第 2 步没有异常 @@ -1070,11 +1070,11 @@ public class Service7 { ③ 解析视图,得到 View 对象,进行视图渲染 -image-20210903141749830 +image-20210903141749830 4. 第 2 步有异常,进入 HandlerExceptionResolver 异常处理流程 -image-20210903141844185 +image-20210903141844185 5. 最后都会执行拦截器的 afterCompletion 方法