Spring MVC 五 - DispatcherServlet初始化过程(续)

news2024/11/18 20:42:47

今天的内容是SpringMVC的初始化过程,其实也就是DispatcherServilet的初始化过程。

Special Bean Types

DispatcherServlet委托如下一些特殊的bean来处理请求、并渲染正确的返回。这些特殊的bean是Spring MVC框架管理的bean、按照Spring框架的约定处理相关请求,一般情况下是框架内置的,我们当然也可以定制或扩展他们的功能。

这些特殊bean包括:

  1. HandlerMapping:根据一定的规则把请求映射到对应的HandlerMapping去处理,HandlerMapping可以包含一系列拦截器,进行前置或后置处理。框架默认提供了RequestMappingHandlerMapping(处理@RequestMapping注解方法的)和SimpleUrlHandlerMapping两个HandlerMapping。
  2. HandlerAdapter:HandlerMapping匹配到请求之后,调用HandlerAdapter具体处理请求。
  3. HandlerExceptionResolver:发生异常后的异常处理器。
  4. ViewResolver:处理返回
  5. LocaleResolver, LocaleContextResolver:本地化处理器
  6. ThemeResolver:Theme渲染处理器
  7. MultipartResolver:Multipart处理器,文件上传下载的处理。
  8. FlashMapManager:跨请求存储和获取“input”和“output”的处理器

Web MVC Config

DispatcherServlet初始化过程中会根据WebApplicationContext的配置(xml或注解方式,前面两篇文章分析过)完成上述特殊bean的初始化,如果DispatcherServlet在WebApplicationContext中没有发现相应的配置,则采用DispatcherServlet.properties文件中的默认配置完成初始化。

DispatcherServlet.properties文件在Spring web mvc包下:
在这里插入图片描述

我们猜想Spring MVC框架是通过DispatcherServlet的init方法完成上述各特殊bean的初始化的,下面我们要详细分析一下具体的初始化过程。

Servlet Config

通过注解方式、或通过xml方式初始化DispatcherServlet的具体方法,前面两篇文章已经做过分析,此处不在赘述。

DispatcherServlet的初始化

众所周知,Servlet容器(比如Tomcat)会通过调用Servlet的init方法完成Servlet的初始化。

我们接下来看一下DispatcherServlet的初始化过程,也就是DispatcherServlet的init方法。

先来看一眼DispatcherServlet的类结构:
![(https://img-blog.csdnimg.cn/bcd50bfa29b04ff3a49b607bc945a2bb.png)

init方法在他的父类HttpServletBean中:

	@Override
	public final void init() throws ServletException {

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();
	}

上面的代码是对当前Servlet属性的处理,与我们的目标无关,初始化逻辑在最下面的方法initServletBean中,在他的子类(也是DispatcherServlet的直接父类)FrameworkServlet中:

	protected final void initServletBean() throws ServletException {
        ...省略部分代码
		try {
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

该方法中有很多打印log的代码,忽略掉,剩下的就是两个方法的调用:一个是创建webApplicationContext的,一个是initFrameworkServlet,这个initFrameworkServlet是空方法,所以,DispatcherServlet的初始化逻辑,关键就在这个initWebApplicationContext()方法中。

initWebApplicationContext方法很长,我们分段分析一下。

	protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
        ...

首先获取当前ServletContext的RootContext,有关RootContext,参见前面的文章 Spring MVC 四:Context层级。

然后:

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}

判断如果DispatcherServlet对象创建的时候,如果在构造方法中已经初始化过WebApplicationContext了,那么就使用该WebApplicationContext,设置上面获取到的RootContext为当前WebApplicationContext的父容器。并且判断该Context是否已经刷新过,如果没有刷新过的话,调用configureAndRefreshWebApplicationContext方法配置并刷新该Context。

前面文章Spring MVC 三 :基于注解配置中我们分析过DispatcherServlet的创建过程,确实在创建的时候就通过构造函数的参数传过来已经创建好的ServletContext了:

protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return null or empty");

        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

   ...省略代码

所以如果是通过注解方式配置的话,会通过createServletApplicationContext()方法创建ServletContext:

	@Override
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}

最终创建的ServletContext是AnnotationConfigWebApplicationContext。

所以如果通过注解方式配置,那就是要走到上面这段逻辑中来的。

否则,如果不是通过注解、而是通过xml配置,也就是说DispactherServlet创建的时候并没有ServletContext,会走到下面的逻辑中:

		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

如果wac为空(DispatcherServlet创建的时候没有设置),那么就判断容器中是否已经注册进来了,如果已经注册了的话,那么Spring framework就会认为其父容器已经设置过了,也做过初始化以及refresh了,直接拿过来用就OK。(我们的应用如果不主动注册的话,就不会有注册进来的Context,所以这段代码就跑不到)。

然后看下面的代码,如果没有发现,就调用createWebApplicationContext创建,createWebApplicationContext方法在创建WebApplicationContext之后,也会设置其父容器为RootContext,之后也会调用configureAndRefreshWebApplicationContext配置和刷新容器,走到和上面第一步(通过注解方式配置,DispatcherServlet创建的时候已经通过构造器设置了一个Context)一致的逻辑中了。

createWebApplicationContext:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

首先调用getContextClass()方法获取contextClass:

	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
	private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

public Class<?> getContextClass() {
		return this.contextClass;
	}

可以看到,如果不是通过注解方式启动、而是通过xml配置方式启动的话,创建的ServletContext应该就是这个XmlWebApplicationContext。

创建ServletContext之后,与xml配置方式一样:设置父容器,然后调用configureAndRefreshWebApplicationContext方法配置及刷新容器。

接下来我们看configureAndRefreshWebApplicationContext方法。

configureAndRefreshWebApplicationContext

目前为止,我们前面的猜测:通过DispatcherServlet的init方法初始化各个特殊bean。尚未的到证实 — 在DispatcherServlet的init方法中,我们尚未看到相关的初始化代码。

不过代码还没分析完,还有一个configureAndRefreshWebApplicationContext,我们继续分析。

代码比较长,我们还是分段分析:

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}

为WebApplicationContext设置Id,无关紧要,继续看下面的代码:

		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

设置ServletContext、ServletConfig、以及namespace,之后新增了一个监听器:ContextRefreshListener()。

然后:

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}

		postProcessWebApplicationContext(wac);
		applyInitializers(wac);
		wac.refresh();
	}

设置环境变量,以及获取初始化参数,最后调用WebApplicationContext的refresh方法。

依然没有看到DispatcherServlet对特殊bean的初始化!而且现在的代码逻辑是转到了ApplicationContext中,是Spring Framework的内容、并不是Spring MVC的内容。

别急,马上就要摸到开关了!

目前的代码确实是转悠到Spring Framework中来了。所以说Spring全家桶,不管是Spring MVC、还是SpringBoot、还是Spring Security,统统都是以Spring Framework为基础的。掌握Spring Framework是掌握Spring全家桶的基础。

ApplicationContext的refresh方法我们很熟悉了,是Spring Framework的关键方法,在AbstractApplicationContext类中实现,该方法最后会调用到finishRefresh()方法:
在这里插入图片描述

finishRefresh()方法最后会发布ContextRefreshedEvent事件。

没错,前面代码分析过程中,我们确实是在WebApplicationContext容器中注册了一个针对该事件的监听器ContextRefreshListener:

	private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

该监听器是定义在FrameworkServlet中的一个内部类,其onApplicationEvent方法会调用到FrameworkServlet的onApplicationEvent方法,这样,通过监听机制,代码逻辑就再次转回到了DispatcherServlet(确切说是他的父类FrameworkServlet)中来了:

	public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		synchronized (this.onRefreshMonitor) {
			onRefresh(event.getApplicationContext());
		}
	}

最终会调用到DispatcherServlet中来:

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

查看DispatcherServlet代码我们会发现,这个initStrategies正式我们要找的方法,方法参数Context是通过事件传递过来的,因此,DispatcherSerlet在进行初始化的时候可以持有ApplicationContext对象,然后,随心所欲地完成Spring MVC特殊bean的初始化。

篇幅原因,关于DispatcherServlet的具体初始化过程,我们后面分析。

上一篇 Spring MVC 五 - Spring MVC的配置和DispatcherServlet初始化过程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/972012.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mybatis-generator-maven-plugin使用

前提说明 数据库&#xff1a;MYSQL57Mybatis : http://mybatis.org/generator/index.html 操作说明 引入插件 <plugins><!-- MyBatis 逆向工程 插件 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generat…

NVM与Node的创建安装与使用(Nodejs在多个版本中切换使用)

下载NVM Releases coreybutler/nvm-windows GitHub 下载对应二进制文件后自定义文件夹创建Node和NVM文件夹一个存储Node一个存储NVM 把NVM下载的二进制文件解压进NVM文件夹后删除压缩包 以管理员身份运行install.cmd 弹出路径界面直接回车生成txt文件 文本文件另存为到NVM文…

[unity]三角形顶点顺序

序 详见官方文档&#xff1a;Unity - Manual: Mesh data (unity3d.com) Topology&#xff1a;拓扑结构 翻译&#xff1a; 拓扑描述网格具有的面类型。 网格的拓扑定义了索引缓冲区的结构&#xff0c;索引缓冲区又描述了顶点位置如何组合成面。每种类型的拓扑都使用索引数组中…

算法模型嵌入式 Mendix应用的开发示例

一、前言 根据埃森哲最新一项调查&#xff0c;2023年67%的企业持续加大在技术方面的投入&#xff0c;其中数据和AI应用是重中之重。AI在企业内部应用这个话题已经保持了十多年的热度&#xff0c;随着ChatGPT为代表的生成式AI技术的出现&#xff0c;这一话题迎来又一波的高潮。…

ios 运行ipa包 日志查看方式

方法一&#xff1a; 使用ideviceinstaller工具 # 安装ipa命令 brew install ideviceinstaller ideviceinstaller -i xxx.ipa# 查看运行日志 idevicesyslog# idevicesyslog 查找命令 idevicesyslog | grep test -A 3 -B 2 # 输出关键字所在行后3行&#xff0c;前2行) idevic…

海康威视二次开发适配安卓电视盒子

收到一个需求&#xff0c;需要在安卓电视盒子上适配海康威视摄像头视频&#xff1a; 1.类似电视家app界面&#xff0c;左边滑动菜单显示通道列表、设置按钮&#xff0c;遥控器呼出菜单。 2.遥控器操作&#xff1a;切换视频通道、云台上下左右控制、缩放等。 3.服务器域名、用…

计算机网络的故事——简单的HTTP协议

简单的HTTP协议 文章目录 简单的HTTP协议一、简单的HTTP协议 一、简单的HTTP协议 HTTP是不保存状态的协议&#xff0c;为了实现保存状态功能于是引入了Cookie技术。 method: get:获取资源 post:传输实体主体 put:传输文件 head:获取报文首部&#xff0c;用于确认URI的有效性以…

vue前端解决跨域

1,首先 axios请求&#xff0c;看后端接口路径&#xff0c;http://122.226.146.110:25002/api/xx/ResxxList&#xff0c;所以baseURL地址改成 ‘/api’ let setAxios originAxios.create({baseURL: /api, //这里要改掉timeout: 20000 // request timeout}); export default s…

C++day7模板、异常、auto关键字、lambda表达式、数据类型转换、STL、list、文件操作

作业 封装一个学生的类&#xff0c;定义一个学生这样类的vector容器, 里面存放学生对象&#xff08;至少3个&#xff09; 再把该容器中的对象&#xff0c;保存到文件中。 再把这些学生从文件中读取出来&#xff0c;放入另一个容器中并且遍历输出该容器里的学生。 #include …

【人月神话】深入了解软件工程和项目管理

文章目录 &#x1f468;‍⚖️《人月神话》的主要观点&#x1f468;‍&#x1f3eb;《人月神话》的主要内容&#x1f468;‍&#x1f4bb;作者介绍 &#x1f338;&#x1f338;&#x1f338;&#x1f337;&#x1f337;&#x1f337;&#x1f490;&#x1f490;&#x1f490;&a…

pytest笔记2: fixture

1. fixture 通常是对测试方法和测试函数&#xff0c;测试类整个测试文件进行初始化或是还原测试环境 # 功能函数 def multiply(a, b):return a * b # ------------ fixture---------------def setup_module(module):print("setup_module 在当前文件中所有测试用例之前&q…

Redis的数据类型到底有什么奥秘

这里我们先只介绍五种常用的数据类型~ 目录 1、string 2、hash 3、list 4、set 5、zset 6、示例 1、string 数据类型&#xff1a;string内部编码&#xff1a;raw、int、embstr 说明&#xff1a; raw是最基本的字符串--底层是一个char数组&#xff08;此处的char是一个字…

UNIX网络编程卷一 学习笔记 第三十章 客户/服务器程序设计范式

开发一个Unix服务器程序时&#xff0c;我们本书做过的进程控制&#xff1a; 1.迭代服务器&#xff08;iterative server&#xff09;&#xff0c;它的适用情形极为有限&#xff0c;因为这样的服务器在完成对当前客户的服务前无法处理已等待服务的新客户。 2.并发服务器&#x…

肖sir__设计测试用例方法之边界值03_(黑盒测试)

设计测试用例方法之边界值 边界点定义 上点&#xff1a;边界上的点 离点&#xff1a;离上点最近的点&#xff08;即上点左右两边最邻近的点&#xff09; 内点&#xff1a;在域范围内的点 案例&#xff1a;qq号&#xff1a;5-12位 闭区间&#xff1a; 离点&#xff1a;5 位 &…

go语言基本操作---三

变量的内存和变量的地址 指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。Go语言对指针的支持介于java语言和C/C语言之间&#xff0c;它即没有想Java语言那样取消了代码对指针的直接操作的能力&#xff0c;也避免了C/C语言中由…

[Rust GUI]0.10.0版本iced代码示例 - progress_bar

-1 字体支持 iced0.10.0 仅支持指定系统内置字体(用iced的默认字体是乱码) iced0.10.0 手动加载字体的功能已经砍了&#xff0c;想手动加载就用0.9.0版本&#xff0c;文档0.9.0版本 想显示中文则需要运行在一个自带字体的Windows系统上。而且这个字体最好不要钱。 (Windows闲着…

《计算机算法设计与分析》第一章:算法概述

第一章 算法概述 1.1 算法复杂性分析 公共标准&#xff1a;渐进时间复杂度 &#xff08;1&#xff09;大O表示法&#xff1a; 例如&#xff1a; 大O表示法和前面的最坏时间复杂度的区别在于&#xff1a;大O表示法表示的更为简洁&#xff0c; 而最坏时间复杂度相对就比较繁琐&am…

【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)

1 简介 Fragment是一个历史悠久的组件&#xff0c;从API 11引入至今&#xff0c;已经成为Android开发中最常用的组件之一。 Fragment表示应用界面中可重复使用的一部分。Fragment定义和管理自己的布局&#xff0c;具有自己的生命周期&#xff0c;并且可以处理自己的输入事件。…

processflow流程图多人协作预热

前言 在线上办公如火如荼的今天&#xff0c;多人协作功能是每个应用绕不开的门槛。processflow在线流程图&#xff08;前身基于drawio二次开发&#xff09;沉寂两年之久&#xff0c;经过长时间设计开发&#xff0c;调整&#xff0c;最终完成了多人协作的核心模块设计。废话不多…

SeaTunnel扩展Transform插件,自定义转换插件

代码结构 在seatunnel-transforms-v2中新建数据包名&#xff0c;新建XXXTransform&#xff0c;XXXTransformConfig&#xff0c;XXXTransformFactory三个类 自定义转换插件功能说明 这是个适配KafkaSource的转换插件&#xff0c;接收到的原文格式为&#xff1a; {"path&…