WEB
提示
本文主要记录springMVC的web容器启动过程中,如何加载初始化Spring的IOC容器
# 1. SpringMVC配置方式
# 1.1 WEB-INF
在web容器初始化过程中,会在WEB-INF文件夹下寻找名为[servlet-name]-servlet.xml的配置文件作为SpringMVC的配置文件,如下springMVC的配置文件就是放在WEB-INF下名为dispatcherServlet-servlet.xml的配置文件
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2
3
4
5
6
7
8
9
# 1.2 类路径
直接在类路径(Resource文件夹)下配置SpringMVC配置文件
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvcconfig.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2
3
4
5
6
7
8
9
10
11
12
13
在web.xml下可以看到 DispatcherServlet 是SpringMVC的核心,下面将重点讲解DispatcherServlet的原理以及作用。
# 2. SpringIOC容器和Web容器
SpringIOC是一个独立的模块,它并不是直接在Web容器中发挥作用的
如果要在Web容器中使用IOC容器,需要Spring为IOC设计一个启动过程,把IOC容器导入,并将Web容器中建立起来。
具体来说,SpringIOC容器的启动过程是和Web容器的启动过程集成在一起的。在这个启动过程中,一方面处理Web容器的启动,另一方面处理SpringIOC容器的启动过程,对于SpringIOC容器的启动过程需要设计特定的Web容器拦截器: ContextLoaderListener,将SpringIOC容器集成到Web容器中,并将其初始化。
完成了上述过程,SpringIOC容器才能正常工作,而SpringMVC是建立在IOC容器的基础上的,这样才能建立起MVC框架的运行机制,从而响应从容器传递的HTTP请求。
# 3. ContextLoaderListener
这是一个实际项目场景的springmvc的配置文件web.xml
contextConfigLocation 对应的value是Spring配置文件的绝对路径 监听器主要用来对Servlet容器(这里指Tomcat)的行为进行监听,这里主要关注ContextLoaderListener
笔记
ContextLoaderListener 继承自 ContextLoader,并且还实现了 ServletContextListener 并且它的构造函数中传入了一个WebApplicationContext,它是继承自ApplicationContext接口的高级IoC容器
🔎ServletContextListener 是 Servlet 中比较重要的一个接口:监听 Servlet 容器的启动和销毁事件
所以在 ContextLoaderListener 中:
contextInitialized方法 :参数为所要监听的ServletContextEvent,也就是Tomcat启动加载完web.xml会产生的事件,ServletContextEvent 持有从web.xml加载的初始化配置的 ServletContext 上下文
contextDestroyed方法 :在Tomcat关闭的时候执行该方法,启动时,ServletContextListener 的执行顺序与web.xml中的配置顺序一致,停止时执行顺序正相反
梳理流程 当Servlet容器启动事件发生时,将被ContextLoaderLister 监听。此时 ContextLoaderListener 会调用实现 ServletContextListener 接口后实现的 contextInitialized 方法,并把在web.xml加载初始化后获取的 ServletContext 传入initWebApplicationContext方法中进行IoC容器的初始化
# 4. Spring IOC容器初始化
主要是通过 initWebApplicationContext方法对IOC容器进行初始化
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 判断在web容器中是否存在WebApplicationContext,因为在配置中只允许申明一次ServletContextListener,多次声明会扰乱Spring的执行逻辑。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) //抛出异常
// 1. 创建WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 确保该容器是可配置的web容器
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// 上下文尚未刷新 - >提供诸如设置父上下文,设置应用程序上下文ID等服务
if (cwac.getParent() == null) {
// 在Web容器中建立起双亲IOC容器
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 2. 经过上面两个步骤,现在开始配置并初始化WebApplicationContext。
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 3. 将已经完成初始化的XmlWebApplicationContext容器注册到servletContext中去
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
return this.context;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 调用ContextLoaderListener中的initWebApplicationContext方法并且将获取到的servletContext作为参数传入
- initWebApplicationContext首先会尝试从servletContext中获取根容器(IOC容器),如果容器不为空,则容器初始化失败,因为web.xml中可能定义了多个IoC容器的加载器。假如此时容器还未初始化,则调用createWebApplicationContext方法创建一个IOC容器(默认是
XMLWebApplicationContext
)。 - 创建完容器之后,将会调用一个非常重要的configureAndRefreshWebApplicationContext方法。在执行这个方法的时候,会将从ApplicationContext.xml配置文件中获取到的内容配置到已经创建好了的XmlWebApplicationContext容器中去,并调用refresh方法来完成IOC容器的初始化。
- 然后,再将已经完成初始化的XmlWebApplicationContext容器注册到servletContext中去。
其实在Web容器中,ServletContext为Spring的IoC容器提供了宿主环境,对应的建立起一个IoC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象、数据存取对象、资源、事务管理器等各种中间层对象。
在这个上下文的基础上,与Web MVC相关还会有一个上下文来保持控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。因为在initWebApplicationContext方法中我们可以看到其实创建ApplicationContext容器的工作是交由createWebApplicationContext方法来实现的,下面我们来看看这个方法
# 5. createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 这里判断使用什么样的类在Web容器中作为IOC容器
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 直接实例化需要产生的IOC容器,默认的是`XMLWebApplicationContext`
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
// 判断是否存在指定的IOC
if{...}
else {
// 如果没有指定的IOC容器,则properties中获取默认的IOC容器,也就是XMLWebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
此方法功能
- 决定要创建的ApplicationContext类型
- 实例化一个ApplicationContext
那么它是如何决定要创建的ApplicationContext类型的呢?
起作用的是determineContextClass方法
# 6. mvc 处理流程
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术
- 路径:默认映射路径为
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器- jsp 不会匹配到 DispatcherServlet
- 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
- 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
- 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
- HandlerMapping,初始化时记录映射关系
- HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
- HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
- ViewResolver
- 路径:默认映射路径为
DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法
例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法
控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet
HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象
DispatcherServlet 接下来会:
- 调用拦截器的 preHandle 方法
- RequestMappingHandlerAdapter 调用 handle 方法,准备数据绑定工厂、模型工厂、ModelAndViewContainer、将 HandlerMethod 完善为 ServletInvocableHandlerMethod
- @ControllerAdvice 全局增强点1️⃣:补充模型数据
- @ControllerAdvice 全局增强点2️⃣:补充自定义类型转换器
- 使用 HandlerMethodArgumentResolver 准备参数
- @ControllerAdvice 全局增强点3️⃣:RequestBody 增强
- 调用 ServletInvocableHandlerMethod
- 使用 HandlerMethodReturnValueHandler 处理返回值
- @ControllerAdvice 全局增强点4️⃣:ResponseBody 增强
- 根据 ModelAndViewContainer 获取 ModelAndView
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 例如,有的返回值处理器调用了 HttpMessageConverter 来将结果转换为 JSON,这时 ModelAndView 就为 null
- 如果返回的 ModelAndView 不为 null,会在第 4 步走视图解析及渲染流程
- 如果返回的 ModelAndView 为 null,不走第 4 步视图解析及渲染流程
- 调用拦截器的 postHandle 方法
- 处理异常或视图渲染
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
- 正常,走视图解析及渲染流程
- 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
- 调用拦截器的 afterCompletion 方法