Cynicism Cynicism
首页
  • 前端学习笔记

    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript
    • JS设计模式总结
    • 小程序笔记
  • 后端学习笔记

    • 《JavaWeb》
    • 《SSM》
    • 《瑞吉外卖》
    • 《Git》
    • 《SpringCloud》
    • 《黑马点评》
    • 《Spring原理》
    • 《JVM》
    • 《Java并发编程》
    • 《学成在线》
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 面试
  • 常见问题
  • 实用技巧
  • 友情链接
实习
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Cynicism

Java后端学习中的IKUN
首页
  • 前端学习笔记

    • 《Vue》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript
    • JS设计模式总结
    • 小程序笔记
  • 后端学习笔记

    • 《JavaWeb》
    • 《SSM》
    • 《瑞吉外卖》
    • 《Git》
    • 《SpringCloud》
    • 《黑马点评》
    • 《Spring原理》
    • 《JVM》
    • 《Java并发编程》
    • 《学成在线》
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 面试
  • 常见问题
  • 实用技巧
  • 友情链接
实习
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Bean生命周期
  • AOP
  • WEB
    • 1. SpringMVC配置方式
      • 1.1 WEB-INF
      • 1.2 类路径
    • 2. SpringIOC容器和Web容器
    • 3. ContextLoaderListener
    • 4. Spring IOC容器初始化
    • 5. createWebApplicationContext
    • 6. mvc 处理流程
  • SpringBoot
  • 策略模式
  • 初始化IOC容器
  • 《Spring原理》
cynicism
2024-01-05
目录

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>
1
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>  
1
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  1. 调用ContextLoaderListener中的initWebApplicationContext方法并且将获取到的servletContext作为参数传入
  2. initWebApplicationContext首先会尝试从servletContext中获取根容器(IOC容器),如果容器不为空,则容器初始化失败,因为web.xml中可能定义了多个IoC容器的加载器。假如此时容器还未初始化,则调用createWebApplicationContext方法创建一个IOC容器(默认是XMLWebApplicationContext)。
  3. 创建完容器之后,将会调用一个非常重要的configureAndRefreshWebApplicationContext方法。在执行这个方法的时候,会将从ApplicationContext.xml配置文件中获取到的内容配置到已经创建好了的XmlWebApplicationContext容器中去,并调用refresh方法来完成IOC容器的初始化。
  4. 然后,再将已经完成初始化的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());
		}
	}
1
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 后,请求到达服务器,其处理流程是:

  1. 服务器提供了 DispatcherServlet,它使用的是标准 Servlet 技术

    • 路径:默认映射路径为 /,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器
      • jsp 不会匹配到 DispatcherServlet
      • 其它有路径的 Servlet 匹配优先级也高于 DispatcherServlet
    • 创建:在 Boot 中,由 DispatcherServletAutoConfiguration 这个自动配置类提供 DispatcherServlet 的 bean
    • 初始化:DispatcherServlet 初始化时会优先到容器里寻找各种组件,作为它的成员变量
      • HandlerMapping,初始化时记录映射关系
      • HandlerAdapter,初始化时准备参数解析器、返回值处理器、消息转换器
      • HandlerExceptionResolver,初始化时准备参数解析器、返回值处理器、消息转换器
      • ViewResolver
  2. DispatcherServlet 会利用 RequestMappingHandlerMapping 查找控制器方法

    • 例如根据 /hello 路径找到 @RequestMapping("/hello") 对应的控制器方法

    • 控制器方法会被封装为 HandlerMethod 对象,并结合匹配到的拦截器一起返回给 DispatcherServlet

    • HandlerMethod 和拦截器合在一起称为 HandlerExecutionChain(调用链)对象

  3. DispatcherServlet 接下来会:

    1. 调用拦截器的 preHandle 方法
    2. 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 步走视图解析及渲染流程
    3. 调用拦截器的 postHandle 方法
    4. 处理异常或视图渲染
      • 如果 1~3 出现异常,走 ExceptionHandlerExceptionResolver 处理异常流程
        • @ControllerAdvice 全局增强点5️⃣:@ExceptionHandler 异常处理
      • 正常,走视图解析及渲染流程
    5. 调用拦截器的 afterCompletion 方法
编辑 (opens new window)
#spring
上次更新: 2025/05/12, 04:51:03
AOP
SpringBoot

← AOP SpringBoot→

最近更新
01
JVM调优
06-03
02
Linux篇
03-30
03
Kafka篇
03-30
更多文章>
Theme by Vdoing | Copyright © 2023-2025 Cynicism | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式