1. SpringMVC 容器启动

双十二之 Spring 整合 springmvc

https://www.bilibili.com/video/BV1AJ411y72k?p=4&spm_id_from=pageDriver

image-20210919170344220

image-20210919171033804

1.1. 逻辑综述

我们知道根据 Servlet3.0规范,web 容器(比如 Tomcat 服务器 (7 及以上版本))在启动应用的时候,会扫描当前应用 每一个jar包 里面的 META-INF/services/javax.servlet.ServletContainerInitializer 文件中指定的实现类,然后再运行该实现类中的方法

而在我们整合 SpringMVC 用到的 spring-web-xxx.RELEASE.jar 中的 META-INF/services/目录里面有一个 javax.servlet.ServletContainerInitializer 文件,并且在该文件中指定的实现类就是 org.springframework.web.SpringServletContainerInitializer,打开该实现类,发现它上面标注了 @HandlesTypes(WebApplicationInitializer.class) 这样一个注解。

image.png

因此,==web 容器在启动应用的时候==,便会来扫描并加载 org.springframework.web.SpringServletContainerInitializer 实现类,而且会传入我们感兴趣的类型(即 WebApplicationInitializer接口)的所有后代类型,最终再运行其 onStartup 方法。

具体来说,我们可以看到它会遍历感兴趣的类型(即 WebApplicationInitializer 接口)的所有后代类型,然后利用反射技术创建 WebApplicationInitializer 类型的对象而我们自定义的 (整合需要)MyWebAppInitializer 就是 WebApplicationInitializer 这种类型的。而且创建完之后,都会存储到名为 initializers 的 LinkedList<WebApplicationInitializer> 集合中。接着,又会遍历该集合,并调用每一个 WebApplicationInitializer 接口实现对象的 onStartup 方法。

image-20210919165159997

遍历到每一个 WebApplicationInitializer 接口实现对象,调用其 onStartup 方法,实际上就是先调用其(例如我们自定义的 MyWebAppInitializer)最高父类 Abstract ContextLoaderInitializer 的 onStartup 方法,创建根容器;然后再调用其次高父类 AbstractDispatcherServletInitializer 的 onStartup 方法,创建 web 容器以及 DispatcherServlet;接着,根据其重写的 getServletMappings 方法来为 DispatcherServlet 配置映射信息

https://liayun.blog.csdn.net/article/details/114968293

1.2. 扫描感兴趣类

1.2.1. Servlet3.0 规范

Shared libraries(共享库)/ runtimes pluggability(运行时插件能力)
%%
▶1.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230330-1347%%
❕ ^yqskms

1、Servlet 容器启动会扫描,当前应用里面每一个 jar 包的 ServletContainerInitializer 的实现
2、提供 ServletContainerInitializer 的实现类;
必须绑定在,META-INF/services/javax.servlet.ServletContainerInitizer
文件的内容就是 ServletContainerInitializer 实现类的全类名

image.png

总结:
servlet 容器 (比如 tomcat) 启动时,会扫描 jar 包下 MATE-INF/service 路径下的 javax.servlet.ServletContainerInitializer 文件中的类,比如整合 SpringMVC 的是:SpringServletContainerInitializer,在 jar 包 spring-web-xxx.RELEASE.jar 中可以看到里面的全限定名是 org.springframework.web.SpringServletContainerInitializer容器启动时会调用SpringServletContainerInitializer 的 onStartup() 方法

1.2.2. @HandlesTypes

在 SpringServletContainerInitializer 的 onStartup() 方法
执行过程中会把 WebApplicationInitializer 的 (由 @HandlesTypes 注解标注的) 所有实现类,通过反射创建实例并放到 List 集合中(接口和抽象类会在反射实例化的时候排除掉),然后循环遍历这些实现类并调用其 onStartup() 方法。子类中有就调用子类中的,否则调用父类中的 onStartup() 方法。

image-20210919130511692
接口和抽象类会在反射实例化的时候排除掉
image-20210919130826992

将感兴趣的类放入初始化集合中,然后遍历执行 onstarup() 方法

image-20210920062831249

1.2.3. MyWebAppIntializer-AbstractAnnotationConfigDispatcherServletInitializer

我们在整合 SpringMVC 时,需要编写继承 AbstractAnnotationConfigDispatcherServletInitializer 的实现类,比如 MyWebAppIntializer,也会被收集到 List<WebApplicationInitializer> 中,随后触发这个实现类的 onStartup() 方法 ❕%%
▶15.🏡⭐️◼️与 SpringBoot 外置 Tomcat 启动原理不同的是 ?🔜MSTM📝 外置 Tomcat 启动需要一个 SpringBootServletInitializer 的子类,重写 Configurer 方法,将 SpringBoot 的主启动类传入;而 Spring 整合 SpringMVC 需要一个 AbstractAnnotationDispatcherServletInitializer 的子类,并重写 3 个方法,分别配置父容器、子容器的配置类,以及配置 mapping◼️⭐️-point-20230302-1941%%

image.png

实现了 WebApplicationInitializer 的类,执行 onstartup() 方法,使用模板方法,调用父类 AbstractDispatcherServletInitializer–>AbstractContextLoaderInitializer 中的 onstartup() 方法

image.png
image.png

image.png

image-20210919172252681

1.3. 创建父容器

1.3.1. 注册上下文监听器对象

image-20210920060115516

相当于 xml 配置中的 ContextLoaderLIstener 节点的作用
image-20210920135818734

1.3.2. AbstractContextLoaderInitializer⭐️🔴

创建空的父容器,此时没有 service 和 dao 组件
image-20210919151224644

1.3.3. 配置父容器

根据 Servlet3.0 规范,父容器中只包含 Services 和 Repositories 的 beans

image-20210920141622667

image-20210919152108852

getRootConfigClasses(),是获取根容器配置类信息,相当于 xml 配置中的

image-20210920060243843

而在我们自己整合 SpringMVC 时,需要指定 2 个容器的配置类 RootConfig.class 和 WebAppConfig.class

image-20210920061256353

根据官网图示,父容器配置类配置的时候排除 Controller 的注解

1.3.3.1. 排除 controller

image-20210920064859118

1.3.4. 把空的父容器放到监听器中

1.3.5. 把初始化器放到监听器中

1.3.6. 把监听器放到应用上下文中

1.3.7. 刷新填充 Bean⭐️🔴

ContextLoaderListener.contextInitialized()–>ContextLoader.initWebApplicationContext()

image-20210920065412110

image-20210920143223675

1.3.8. 把父容器放到 servletContext 中

image-20210920070531699

1.4. 创建子容器

https://www.processon.com/diagraming/641d94b7b13bd654f028beeb

image.png

1.4.1. AbstractDispatcherServletInitializer⭐️🔴

image-20210920062635793

image-20210920064437655

image-20210920064538223

1.4.2. 子容器配置类

1.4.2.1. 禁用 default⭐️🔴⭐️🔴

%%
▶28.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230304-1012%%
❕❕ ^7a3bp3

PS: 父容器的排除规则不需要特别处理,而子容器扫描规则,需要加禁用默认过滤规则的注解useDefaultFilters=false,否则不生效,导致把所有的都扫进去!!
如果此时父容器中有 AOP 的话,就会导致 AOP 失效。案例:经验专题-框架使用-1、SpringMVC

image-20210920065136644

1.4.3. set 父容器

image-20210919172729747

1.4.4. 刷新填充 Bean⭐️🔴

DispatcherServlet–>FrameworkServlet–>HttpServletBean 中的 init() 方法

image-20210920071024334

获取上面父容器步骤中放入 servletContext 中的父容器
image-20210920071143137

1.4.5. 设置容器父子关系⭐️🔴

将父容器引用赋值给子容器的 parent 属性
image-20210920071223743

父子容器的类型都是一样的,都是 AnnotationConfigWebApplicationContext,只是子容器的 parent 属性设置的是父容器的引用。

%%
▶16.🏡⭐️◼️父子容器的关系,Bean 之间的关系,获取 Bean 的顺序◼️⭐️-point-20230302-1956%%

所以子容器可以访问父容器的对象,而反之不行

image-20210920071713257

1.5. 父子容器 bean 关系

1.6. 获取 bean 的顺序-先子后父⭐️🔴

优先从子容器中获取
image.png

2. Servlet3.0 整合逻辑

https://www.bilibili.com/video/BV1gW411W7wy?p=56&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
https://liayun.blog.csdn.net/article/details/114915111
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#spring-web

2.1. 步骤

2.1.1. 初始化配置

  1. 编写一个 WebApplicationInitializer 接口的实现类,比如 AbstractAnnotationConfigDispatcherServletInitializer 的实现类,叫 MyWebAppIntializer,在里面配置好 getServletMappings
  2. 分别编写父子容器组件扫描配置类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
    return new String[] { "/app1/*" };
    }
    }


需要我们指定 2 个容器的配置类 RootConfig.class 和 WebAppConfig.class

2.1.2. 开启 MVC config

1)、@EnableWebMvc: 开启 SpringMVC 定制配置功能;
相当于原来 XML 配置的 mvc:annotation-driven/

2.1.3. 设置 MVC config

https://liayun.blog.csdn.net/article/details/114991617

2)、配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。)
配置类如果实现 WebMvcConfigurer 接口,那么就得一个一个来实现其中的方法了,所以要使用模板的思想,继承一个实现了所有或者部分接口的类来避免这个问题

extends WebMvcConfigurerAdapter

2.2. 原理概述

2.2.1. Servlet3.0 支持

image-20210920133303352

2.2.2. 层次结构

image-20210920133649757

2.2.3. 逻辑概述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1、web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
2、加载这个文件指定的类SpringServletContainerInitializer
3、spring的应用一启动会加载感兴趣的【实现了WebApplicationInitializer接口】的所有组件;
4、并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)而WebApplicationInitializer有3个子抽象类
1)、AbstractContextLoaderInitializer:创建根容器;createRootApplicationContext();
2)、AbstractDispatcherServletInitializer:
创建一个web的ioc容器;createServletApplicationContext();
创建了DispatcherServlet;createDispatcherServlet();
将创建的DispatcherServlet添加到ServletContext中;
getServletMappings();
3)、AbstractAnnotationConfigDispatcherServletInitializer:注解方式配置的DispatcherServlet初始化器
创建根容器:createRootApplicationContext()
getRootConfigClasses();传入一个配置类
创建web的ioc容器: createServletApplicationContext();
获取配置类;getServletConfigClasses();

总结:
以注解方式来启动SpringMVC;继承AbstractAnnotationConfigDispatcherServletInitializer;
实现抽象方法指定DispatcherServlet的配置信息;

2.3. 经验总结

2.3.1. 禁用默认过滤规则⭐️🔴

PS: 父容器的排除规则 (excludeFilters)不需要特别处理,但是子容器的只扫描规则 (includeFilters)需要加禁用默认过滤规则的注解,否则不生效,导致把所有的都扫进去!!

image-20210920061716788

则 SpringMVC 容器不仅仅扫描并注册带有 @Controller 注解的 Bean,而且还扫描并注册了带有 @Component 的子注解 @Service@Reposity 的 Bean。因为 useDefaultFilters 默认为 true。所以如果不需要默认的,则 useDefaultFilters=false 禁用掉。

image.png

当我们进行上面的配置时,SpringMVC 容器会把 service、dao 层的 bean 重新加载,从而造成新加载的 bean 覆盖了老的 bean,但事务的 AOP 代理没有配置在 spring-mvc.xml 配置文件中,造成事务失效。解决办法是:在 spring-mvc.xml 配置文件中的 context:component-scan 标签中使用 use-default-filters=“false”禁用掉默认的行为。

链接: https://www.imooc.com/article/21845

3. SpringMVC 工作原理

https://www.bilibili.com/video/BV1uF411L73Q?p=78&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

https://blog.csdn.net/weixin_48922154/article/details/113867510

可以参考 SpringBoot 中的 SpringMVC 流程:
https://www.bilibili.com/video/BV1Et411Y7tQ?p=139&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

[[3、SpringBoot-基础#^ijjhr0]]

3.1. 总体概述

image.png

(1)用户发送请求至前端控制器 DispatcherServlet
(2) DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取 Handle;
(3)处理器映射器==根据请求 url 找到具体的处理器==,生成处理器对象及处理器拦截器 (如果有则生成) 一并返回给 DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter 处理器适配器;
(5)HandlerAdapter 经过适配调用具体处理器 (Handler,也叫后端控制器);
(6)Handler 执行完成返回 ModelAndView;
(7)HandlerAdapter 将 Handler 执行结果 ModelAndView 返回给 DispatcherServlet;
(8)DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器进行解析;
(9)ViewResolver 解析后返回具体 View;
(10)DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet 响应用户。

image.png

3.2. 初始化阶段

image.png

3.2.1. 初始化流程

https://www.bilibili.com/video/BV1c3411s7aB/?spm_id_from=333.788&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

Servlet.init → GenericServlet.init → HttpServletBean.init →
HttpServletBean.initServletBean → FreemarkerServlet.initServletBean
FreemarkerServlet.initWebApplicationContext → configureAndRefreshWebApplicationContext(cwac)

3.3. 匹配阶段

设计模式-8、适配器模式

image-20211011090131565
能识别 @RequestMappingRequestMappingHandlerMapping 优先级最高
请求进来,挨个尝试所有的 HandlerMapping 看是否有请求信息。
○ 如果有就找到这个请求对应的 handler
○ 如果没有就是下一个 HandlerMapping

image.png

image.png

image.png

image.png

image.png

image.png

3.4. 执行阶段

image.png

image.png

4. SpringMVC 怎么和 AJAX 相互调用的

(1)加入 Jackson.jar
(2)在配置文件中配置 json 的消息转换器 (jackson 不需要该配置 HttpMessageConverter),如果是其他 JSON 处理器,需要手动@Bean 注入相应的转换器

1
2
<!‐‐它就会帮我们配置了默认json映射‐‐>
<mvc:annotation‐driven conversion‐service="conversionService" ></mvc:annotation‐driven>

(3)在接受 Ajax 方法里面可以直接返回 Object,List 等,但方法前面要加上@ResponseBody 注解

image.png

5. Spring 和 SpringMVC 为什么需要父子容器

就功能性来说不用子父容器也可以完成(参考:SpringBoot 就没用子父容器)
1. 所以父子容器的主要作用应该是划分框架边界。有点单一职责的味道。service、dao 层我们一般使用 spring 框架来管理、controller 层交给 springmvc 管理
2. 规范整体架构使父容器 service 无法访问子容器 controller、子容器 controller 可以访问父容器 service
3. 方便子容器的切换。如果现在我们想把 web 层从 spring mvc 替换成 struts,那么只需要将 spring­mvc.xml 替换成 Struts 的配置文件 struts.xml 即可,而 spring­core.xml 不需要改变。
4. 为了节省重复 bean 创建

6. 与 Servlet 关系

[[Servlet 到 Spring MVC 的简化之路 - 掘金]]

7. 面试题相关

7.1. 是否可以把所有 Bean 都通过 Spring 容器来管理?

(Spring 的 applicationContext.xml 中配置全局扫描)
不可以,这样会导致我们请求接口的时候产生 404。如果所有的 Bean 都交给父容器,SpringMVC 在初始化 HandlerMethods 的时候(initHandlerMethods)并没有 getBean 的方式获取到所需要的 Bean,而是通过 getBeanNameByType 获取到 BD 的名字,并没有去查找父容器的 bean,所以无法根据 Controller 的 handler 方法注册 HandlerMethod,也就无法根据请求 URI 获取到 HandlerMethod 来进行匹配

image.png

7.2. 是否可以把我们所需的 Bean 都放入 Spring­mvc 子容器里面来管理

(springmvc 的 springservlet.xml 中配置全局扫描)?
可以,因为父容器的体现无非是为了获取子容器不包含的 bean,  如果全部包含在子容器完全用不到父容器了,所以是可以全部放在 springmvc 子容器来管理的。
虽然可以这么做不过一般应该是不推荐这么去做的,一般人也不会这么干的。如果你的项目里有用到事物、或者 aop 记得也需要把这部分配置需要放到 Spring-mvc 子容器的配置文件来,不然一部分内容在子容器和一部分内容在父容器, 可能就会导致你的事物或者 AOP 不生效。     
所以如果 aop 或事物如果不生效也有可能是通过父容器 (spring) 去增强子容器 (Springmvc),也就无法增强。

7.3. 如何实现无 XML 零配置的 SpringMVC

1. 省略 web.xml
image.png

a. servlet3.0 之后规范中提供了 SPI 扩展机制 :
META-INF/services/javax.servlet.ServletContainerInitializer
b. SpringMVC 通过实现 ServletContainerInitializer 接口
c. 动态注册 ContextLoaderListener 和 DispatcherServlet 并创建子父容器 (ApplicationContext)
2. 省略 spring.xml 和 spring-mvc.xml(只是 sprinmvc 方式 ,springboot 在自动配置类完成)   配置类 –xml
a. 实现一个继承 AbstractAnnotationConfigDispatcherServletInitializer 的类
b. 该类就实现了 ServletContainerInitializer,它会创建 ContextLoaderListener 和 DispatcherServlet
c. 还会创建父子容器, 创建容器时传入父子容器配置类则可以替代 spring.xml 和 spring-mvc.xml

7.4. SpringMVC 的拦截器和过滤器有什么区别?执行顺序?

拦截器不依赖于 servlet 容器,过滤器依赖于 servlet 容器。
拦截器只能对 action 请求 (DispatcherServlet 映射的请求) 起作用,而过滤器则可以对几乎所有的请求起作用。
拦截器可以访问容器中的 Bean(DI),而过滤器不能访问(基于 spring 注册的过滤器也可以访问容器中的 bean)。
执行顺序:
image.png
多个过滤器的执行顺序跟 xml 文件中定义的先后关系有关
当然,对于多个拦截器它们之间的执行顺序跟在 SpringMVC 的配置文件中定义的先后顺序有关。

7.5. 各种容器关系

https://blog.csdn.net/u012060033/article/details/104953011
image.png

7.5.1. 各种服务器

https://www.cnblogs.com/guanghe/p/15174643.html
image.png

8. 实战经验

9. 参考与感谢

9.1. 图灵徐庶

https://www.bilibili.com/video/BV1mf4y1c7cV/?spm_id_from=..search-card.all.click&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204

9.2. 网络笔记

[[../../../../cubox/006-ChromeCapture/SpringMVC 启动流程及相关源码分析 - 简书]]
https://www.jianshu.com/p/dc64d02e49ac