框架源码专题-Spring-4、SpringMVC
1. SpringMVC 容器启动
双十二之 Spring 整合 springmvc
https://www.bilibili.com/video/BV1AJ411y72k?p=4&spm_id_from=pageDriver
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)
这样一个注解。
因此,==web 容器在启动应用的时候==,便会来扫描并加载 org.springframework.web.SpringServletContainerInitializer 实现类,而且会传入我们感兴趣的类型(即 WebApplicationInitializer接口
)的所有后代类型,最终再运行其 onStartup
方法。
具体来说,我们可以看到它会遍历感兴趣的类型(即 WebApplicationInitializer 接口)的所有后代类型,然后利用反射技术创建 WebApplicationInitializer 类型的对象,而我们自定义的 (整合需要)MyWebAppInitializer 就是 WebApplicationInitializer 这种类型的。而且创建完之后,都会存储到名为 initializers 的 LinkedList<WebApplicationInitializer>
集合中。接着,又会遍历该集合,并调用每一个 WebApplicationInitializer 接口实现对象的 onStartup 方法。
遍历到每一个 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(运行时插件能力)
❕ ^yqskms
1、Servlet 容器启动会扫描,当前应用里面每一个 jar 包的 ServletContainerInitializer 的实现
2、提供 ServletContainerInitializer 的实现类;
必须绑定在,META-INF/services/javax.servlet.ServletContainerInitizer
文件的内容就是 ServletContainerInitializer 实现类的全类名
总结:
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()
方法。
接口和抽象类会在反射实例化的时候排除掉
将感兴趣的类放入初始化集合中,然后遍历执行 onstarup() 方法
1.2.3. MyWebAppIntializer-AbstractAnnotationConfigDispatcherServletInitializer
我们在整合 SpringMVC 时,需要编写继承 AbstractAnnotationConfigDispatcherServletInitializer
的实现类,比如 MyWebAppIntializer,也会被收集到 List<WebApplicationInitializer>
中,随后触发这个实现类的 onStartup() 方法 ❕
实现了 WebApplicationInitializer
的类,执行 onstartup() 方法,使用模板方法,调用父类 AbstractDispatcherServletInitializer
–>AbstractContextLoaderInitializer
中的 onstartup() 方法
1.3. 创建父容器
1.3.1. 注册上下文监听器对象
相当于 xml 配置中的 ContextLoaderLIstener 节点的作用
1.3.2. AbstractContextLoaderInitializer⭐️🔴
创建空的父容器,此时没有 service 和 dao 组件
1.3.3. 配置父容器
根据 Servlet3.0 规范,父容器中只包含 Services 和 Repositories 的 beans
getRootConfigClasses(),是获取根容器配置类信息,相当于 xml 配置中的
而在我们自己整合 SpringMVC 时,需要指定 2 个容器的配置类 RootConfig.class 和 WebAppConfig.class
根据官网图示,父容器配置类配置的时候排除 Controller 的注解
1.3.3.1. 排除 controller
1.3.4. 把空的父容器放到监听器中
1.3.5. 把初始化器放到监听器中
1.3.6. 把监听器放到应用上下文中
1.3.7. 刷新填充 Bean⭐️🔴
ContextLoaderListener.contextInitialized()–>ContextLoader.initWebApplicationContext()
1.3.8. 把父容器放到 servletContext 中
1.4. 创建子容器
https://www.processon.com/diagraming/641d94b7b13bd654f028beeb
1.4.1. AbstractDispatcherServletInitializer⭐️🔴
1.4.2. 子容器配置类
1.4.2.1. 禁用 default⭐️🔴⭐️🔴
❕❕ ^7a3bp3
PS: 父容器的排除规则不需要特别处理,而子容器扫描规则,需要加禁用默认过滤规则的注解useDefaultFilters=false,否则不生效,导致把所有的都扫进去!!
如果此时父容器中有 AOP 的话,就会导致 AOP 失效。案例:经验专题-框架使用-1、SpringMVC
1.4.3. set 父容器
1.4.4. 刷新填充 Bean⭐️🔴
DispatcherServlet–>FrameworkServlet–>HttpServletBean 中的 init() 方法
获取上面父容器步骤中放入 servletContext 中的父容器
1.4.5. 设置容器父子关系⭐️🔴
将父容器引用赋值给子容器的 parent 属性
父子容器的类型都是一样的,都是 AnnotationConfigWebApplicationContext,只是子容器的 parent 属性设置的是父容器的引用。
❕
所以子容器可以访问父容器的对象,而反之不行
1.5. 父子容器 bean 关系
1.6. 获取 bean 的顺序-先子后父⭐️🔴
优先从子容器中获取
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. 初始化配置
- 编写一个 WebApplicationInitializer 接口的实现类,比如 AbstractAnnotationConfigDispatcherServletInitializer 的实现类,叫 MyWebAppIntializer,在里面配置好 getServletMappings
- 分别编写父子容器组件扫描配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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 支持
2.2.2. 层次结构
2.2.3. 逻辑概述
1 |
|
2.3. 经验总结
2.3.1. 禁用默认过滤规则⭐️🔴
PS: 父容器的排除规则 (excludeFilters)不需要特别处理,但是子容器的只扫描规则 (includeFilters),需要加禁用默认过滤规则的注解,否则不生效,导致把所有的都扫进去!!
则 SpringMVC 容器不仅仅扫描并注册带有 @Controller
注解的 Bean,而且还扫描并注册了带有 @Component
的子注解 @Service
、@Reposity
的 Bean。因为 useDefaultFilters
默认为 true。所以如果不需要默认的,则 useDefaultFilters=false
禁用掉。
当我们进行上面的配置时,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. 总体概述
(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 响应用户。
3.2. 初始化阶段
3.2.1. 初始化流程
Servlet.init → GenericServlet.init → HttpServletBean.init →
HttpServletBean.initServletBean → FreemarkerServlet.initServletBean
FreemarkerServlet.initWebApplicationContext → configureAndRefreshWebApplicationContext(cwac)
3.3. 匹配阶段
设计模式-8、适配器模式
能识别 @RequestMapping
的 RequestMappingHandlerMapping
优先级最高
请求进来,挨个尝试所有的 HandlerMapping 看是否有请求信息。
○ 如果有就找到这个请求对应的 handler
○ 如果没有就是下一个 HandlerMapping
3.4. 执行阶段
4. SpringMVC 怎么和 AJAX 相互调用的
(1)加入 Jackson.jar
(2)在配置文件中配置 json 的消息转换器 (jackson 不需要该配置 HttpMessageConverter),如果是其他 JSON 处理器,需要手动@Bean 注入相应的转换器
1 |
|
(3)在接受 Ajax 方法里面可以直接返回 Object,List 等,但方法前面要加上@ResponseBody 注解
5. Spring 和 SpringMVC 为什么需要父子容器
就功能性来说不用子父容器也可以完成(参考:SpringBoot 就没用子父容器)
1. 所以父子容器的主要作用应该是划分框架边界。有点单一职责的味道。service、dao 层我们一般使用 spring 框架来管理、controller 层交给 springmvc 管理
2. 规范整体架构使父容器 service 无法访问子容器 controller、子容器 controller 可以访问父容器 service
3. 方便子容器的切换。如果现在我们想把 web 层从 spring mvc 替换成 struts,那么只需要将 springmvc.xml 替换成 Struts 的配置文件 struts.xml 即可,而 springcore.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 来进行匹配
7.2. 是否可以把我们所需的 Bean 都放入 Springmvc 子容器里面来管理
(springmvc 的 springservlet.xml 中配置全局扫描)?
可以,因为父容器的体现无非是为了获取子容器不包含的 bean, 如果全部包含在子容器完全用不到父容器了,所以是可以全部放在 springmvc 子容器来管理的。
虽然可以这么做不过一般应该是不推荐这么去做的,一般人也不会这么干的。如果你的项目里有用到事物、或者 aop 记得也需要把这部分配置需要放到 Spring-mvc 子容器的配置文件来,不然一部分内容在子容器和一部分内容在父容器, 可能就会导致你的事物或者 AOP 不生效。
所以如果 aop 或事物如果不生效也有可能是通过父容器 (spring) 去增强子容器 (Springmvc),也就无法增强。
7.3. 如何实现无 XML 零配置的 SpringMVC
1. 省略 web.xml
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)。
执行顺序:
多个过滤器的执行顺序跟 xml 文件中定义的先后关系有关
当然,对于多个拦截器它们之间的执行顺序跟在 SpringMVC 的配置文件中定义的先后顺序有关。
7.5. 各种容器关系
https://blog.csdn.net/u012060033/article/details/104953011
7.5.1. 各种服务器
https://www.cnblogs.com/guanghe/p/15174643.html
8. 实战经验
9. 参考与感谢
9.1. 图灵徐庶
9.2. 网络笔记
[[../../../../cubox/006-ChromeCapture/SpringMVC 启动流程及相关源码分析 - 简书]]
https://www.jianshu.com/p/dc64d02e49ac