首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【小家Spring】Spring MVC容器启动时,web九大组件初始化详解(Spring MVC的运行机制)

【小家Spring】Spring MVC容器启动时,web九大组件初始化详解(Spring MVC的运行机制)

作者头像
YourBatman
发布2019-09-03 16:34:22
1.6K0
发布2019-09-03 16:34:22
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

为了更好的去理解Spring MVC的工作机制,这边博文我们主要讲述Spring MVC在初始化的时候(容器启动的时候),做的一些准备工作。

比如URL和Controller的绑定,以及URL和方法method的绑定,拦截器的初始化,视图解析器的初始化等Spring MVC web9大组件的初始化

在上篇博文: 【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析) 从源码层面,我已经很清楚的了解到了父容器以及Spring MVC子容器的一个初始化的过程。当时还留下两大悬念:

  1. refresh()刷新容器的详细过程
  2. web子容器中,初始化web9大组件的onRefresh()方法的详解

本文因为主讲web环境,因此主要剖析onRefresh()这个方法所做的事。(其实是DispatcherServlet里的initStrategies()这个方法)

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 * 子类若有需要,还可以复写此方法,去初始化自己的其余组件(比如要和它集成等等)
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		
		// 注意,下面是复数,有s。注意区别哦~
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

Spring源码基于的Spring版本为:5.0.6.RELEASE(下同) Spring源码基于的Spring版本为:5.0.6.RELEASE(下同) Spring源码基于的Spring版本为:5.0.6.RELEASE(下同)

Spring MVC九大组件(按照源代码顺序解释)

DispatcherServlet的默认配置文件DispatcherServlet.properties里都有定义这九大组件的默认值

从上面源码中,可能很清楚的看到9大组件的名称,那么我们先来介绍介绍他们各有什么作用:

这里顺便解释一下SpringMVC中的Servlet的三个层次:

  1. HttpServletBean直接继承自java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的Bean属性上
  2. FrameworkServlet初始化了WebApplicationContext
  3. DispatcherServlet初始化了自身的9个组件(本文重点)
MultipartResolver
public interface MultipartResolver {
	boolean isMultipart(HttpServletRequest request);
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
	void cleanupMultipart(MultipartHttpServletRequest request);
}

MultipartResolver 用于处理文件上传,当收到请求时 DispatcherServlet#checkMultipart() 方法会调用 MultipartResolver#isMultipart() 方法判断请求中是否包含文件。如果请求数据中包含文件,则调用 MultipartResolver#resolveMultipart() 方法对请求的数据进行解析。

然后将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest(继承了 HttpServletRequest) 对象中,最后传递给 Controller

它的两个实现类:

CommonsMultipartResolver 使用 commons Fileupload 来处理 multipart 请求,所以在使用时,必须要引入相应的 jar 包

StandardServletMultipartResolver 是基于 Servlet 3.0来处理 multipart 请求的(基于request.getParts()方法),使用支持 Servlet 3.0的容器

不一样的是,配置StandardServletMultipartResolver这个Bean的时候,它的初始化参数都在web.xml的<multipart-config>里面配置

LocaleResolver
public interface LocaleResolver {
	//根据request对象根据指定的方式获取一个Locale,如果没有获取到,则使用用户指定的默认的Locale
	Locale resolveLocale(HttpServletRequest request);
	//用于实现Locale的切换。比如SessionLocaleResolver获取Locale的方式是从session中读取,但如果
	//户想要切换其展示的样式(由英文切换为中文),那么这里的setLocale()方法就提供了这样一种可能
	void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);

}

对于LocaleResolver,其主要作用在于根据不同的用户区域展示不同的视图,而用户的区域也称为Locale,该信息是可以由前端直接获取的。通过这种方式,可以实现一种国际化的目的,比如针对美国用户可以提供一个视图,而针对中国用户则可以提供另一个视图。

解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情

  • FixedLocaleResolver:在声明该resolver时,需要指定一个默认的Locale,在进行Locale获取时,始终返回该Locale,并且调用其setLocale()方法也无法改变其Locale;
  • CookieLocaleResolver:其读取Locale的方式是在session中通过Cookie来获取其指定的Locale的,如果修改了Cookie的值,页面视图也会同步切换;
  • SessionLocaleResolver:其会将Locale信息存储在session中,如果用户想要修改Locale信息,可以通过修改session中对应属性的值即可;
  • AcceptHeaderLocaleResolver:其会通过用户请求中名称为Accept-Language的header来获取Locale信息,如果想要修改展示的视图,只需要修改该header信息即可。

对于Locale的切换,Spring是通过拦截器来实现的,其提供了一个**LocaleChangeInterceptor**,若要生效,这个Bean需要自己配置

ThemeResolver

主题就是系统的整体样式或风格,可通过Spring MVC框架提供的主题(theme)设置应用的整体样式风格,提高用户体验。Spring MVC的主题就是一些静态资源的集合,即包括样式及图片,用来控制应用的视觉风格。

SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。

主题使用得太少了,特别现在前后端分离了。此处省略~

HandlerMapping(非常非常重要)

用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事

作用是根据当前请求的找到对应的 Handler,并将 Handler(执行程序)与一堆 HandlerInterceptor(拦截器,也是他来处理的)封装到 HandlerExecutionChain 对象中。返回给中央调度器

public interface HandlerMapping {
	@Nullable
	HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

该接口有很多的实现,同时它也是最复杂的一个类。因此为了更好的解释它的作用和原理,后面会出专门的博文深入分析,感兴趣可持续关注

HandlerMapping可以有多个

HandlerAdapter

因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人

public interface HandlerAdapter {
	//当前 HandlerAdapter 是否支持这个 Handler
	boolean supports(Object handler);
	//调用handle处理这个请求,然后返回ModelAndView 对象
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
	long getLastModified(HttpServletRequest request, Object handler);
}

因为它和HandlerMapping联系紧密,因此且听下文分解

HandlerAdapter可以有多个

HandlerExceptionResolver

其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。

以前我们可以用web.xml的<error-page>标签来捕获状态码500 400的异常,但是这个已经out了,现在全局的异常都可以交给HandlerExceptionResolver去捕获处理

public interface HandlerExceptionResolver {
	@Nullable
	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

这个接口捕获的是所有异常,而官方推荐的是使用@ExceptionHandler注解去捕获固定的异常

这个类建议交给Spring子容器管理(我们可以多实现),因为它就像一个特殊的controller

关于这块Spring MVC的全局异常的处理的一些技巧(比如页面、get、post、ajax等),请关注后面博文

RequestToViewNameTranslator

Spring MVC是通过ViewName来找到对应的视图的,而此接口的作用就是从request中获取viewName。

public interface RequestToViewNameTranslator {
	@Nullable
	String getViewName(HttpServletRequest request) throws Exception;
}

它只有一个实现,默认实现:DefaultRequestToViewNameTranslator

	@Override
	public String getViewName(HttpServletRequest request) {
		String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
		return (this.prefix + transformPath(lookupPath) + this.suffix);
	}

	@Nullable
	protected String transformPath(String lookupPath) {
		String path = lookupPath;
		if (this.stripLeadingSlash && path.startsWith(SLASH)) {
			path = path.substring(1);
		}
		if (this.stripTrailingSlash && path.endsWith(SLASH)) {
			path = path.substring(0, path.length() - 1);
		}
		if (this.stripExtension) {
			path = StringUtils.stripFilenameExtension(path);
		}
		if (!SLASH.equals(this.separator)) {
			path = StringUtils.replace(path, SLASH, this.separator);
		}
		return path;
	}

主要实现就是调用UrlPathHelpergetLookupPathForRequest的方法获取一个looup路径。transformPath方法主要是对获取的路径字符串再做个简单处理罢了。

所以核心是UrlPathHelpergetLookupPathForRequest的实现:

(解析路径这块,这里就不做太多的解释了,自己读读源码,相对来说比较简单)

ViewResolver

ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作

ViewResolver需要找到渲染所用的模板所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

public interface ViewResolver {
	@Nullable
	View resolveViewName(String viewName, Locale locale) throws Exception;
}
  1. AbstractCachingViewResolver 基于缓存的抽象视图解析器
  2. UrlBasedViewResolver 实现了缓存 提供了prefix suffix拼接的url视图解析器。
  3. InternalResourceViewResolver 基于url 的内部资源视图解析器。
  4. XmlViewResolver 基于xml的缓存视图解析器
  5. BeanNameViewResolver beanName来自容器,并且不支持缓存。
  6. ResourceBundleViewResolver 这个有点复杂
  7. reeMarkerViewResolver、VolocityViewResolver 都基于url 但会解析成特定的view 实现类也非常的多,在Spring MVC里是一个非常非常重要的概念(比如什么时候返回页面,什么时候返回json呢?),因此后面会有专门的文章进行深入解读

ViewResolverComposite简单来说就是使用简单的List来保存你配置使用的视图解析器。

ViewResolvers可以有多个

FlashMapManager

用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

public interface FlashMapManager {
    @Nullable
    FlashMap retrieveAndUpdate(HttpServletRequest var1, HttpServletResponse var2);
    void saveOutputFlashMap(FlashMap var1, HttpServletRequest var2, HttpServletResponse var3);
}

可以看出结构图非常简单,抽象类采用模板模式定义整个流程,具体实现类用SessionFlashMapManager通过模板方法提供了具体操作FlashMap的功能。

功能说明:

  • 实际的Session中保存的FlashMap是List类型,也就是说一个Session可以保存多个FlashMap,一个FlashMap保存着一套Redirect转发所传递的参数
  • FlashMap继承自HashMap,除了用于HashMap的功能和设置有效期,还可以保存Redirect后的目标路径和通过url传递的参数,这两项内容主要用来从Session保存的多个FlashMap中查找当前的FalshMap

具体请持续关注吧,后面再详说

至此,SpringMVC中的9大组件也就简单地概述了一遍。通过对此9大组件的宏观认识,对分析SpringMVC的设计、原理与实现都会有很大的帮助作用。

onRefresh(wac) / initStrategies(wac)详解

这可以说是DisparcherServlet初始化时的核心逻辑

initMultipartResolver
	private void initMultipartResolver(ApplicationContext context) {
		try {
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
		} catch (NoSuchBeanDefinitionException ex) {
			this.multipartResolver = null;
		}
	}

这个很简单,若我们向容器里配置了此Bean就有,否则默认是不支持文件上传的

备注:注意配置此些配型Bean的名称,都是有固定值的,请必须保证一样,否则你的配置将不生效。下同

initLocaleResolver
	private void initLocaleResolver(ApplicationContext context) {
		try {
			this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
		} catch (NoSuchBeanDefinitionException ex) {
			// We need to use the default.
			this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
		}
	}

若我们自己配置配置LocaleResolver,会调用getDefaultStrategy去获取默认的处理器:

默认处理器在DispatcherServlet.properties这个文件里,如下:

initThemeResolver

逻辑一样,默认值如下:

initHandlerMappings

原理同下,默认值为:

开启了@EnableMvc注解后,拿到的HandlerMapping为:

不开启这个注解:解析配置文件得到默认值:

initHandlerAdapters

原理同下,默认值为:

initHandlerExceptionResolvers

处理方式同initViewResolvers,因此此处不再解释。默认会配置如下三个Bean(若我们自己都没有配置的话):

关于详细时候,后面会专门有所概述

initRequestToViewNameTranslator

逻辑同上,默认值为:

initViewResolvers

视图解析器,稍微复杂点。

private void initViewResolvers(ApplicationContext context) {
	this.viewResolvers = null;
	if (this.detectAllViewResolvers) {
		// 如果detectAllViewResolvers为true,那么就会去容器里找所有的(包含所有祖先上下文)容器里的所有的此接口下的此类的bean,最后都放进去(可以有多个嘛)
		Map<String, ViewResolver> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.viewResolvers = new ArrayList<>(matchingBeans.values());
			// We keep ViewResolvers in sorted order.  保持排序性
			AnnotationAwareOrderComparator.sort(this.viewResolvers);
		}
	} else {
		try {
			ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
			// 这一步,使用了singletonList,是为了性能考虑,节约内存
			this.viewResolvers = Collections.singletonList(vr);
		}
	}
		
	// 若还为null,就采用默认配置的视图解析器InternalResourceViewResolver
	if (this.viewResolvers == null) {
		this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
	}
}

下面说说,什么时候detectAllViewResolvers会为false呢?

默认值为true,回去容器里找到所有的视图解析器的Bean。我们可以通过init-param配置为false,来关闭这个(不建议)

另外,需要注意的是,我们发现虽然我们没有自己注册Bean进去,但是在matchingBeans这一步时,已经有值了,怎么回事呢?继续扣源码发现:我有这个注解@EnableWebMvc

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

会发现这个注解导入了DelegatingWebMvcConfiguration,而它是

public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}

的子类,而WebMvcConfigurationSupport我们就比较熟悉了,它默认配置注册了很多东西到MVC的配置中,所以我们才会发现那个时候就已经matchingBeans有值了。那我们去掉@EnableWebMvc再试试呢?

我们发现,从容器里就拿不出Bean了,只能读取配置文件里的默认值了~

initFlashMapManager

默认值为:

总结

DispatcherServlet的Spring MVC9大组件的介绍,以及他们的初始化的一个流程就到这了。还是那句话,理解了这些来龙去脉,会更有助于我们的流畅的使用、定制Spring MVC的一些功能~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年02月27日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Spring MVC九大组件(按照源代码顺序解释)
    • MultipartResolver
      • LocaleResolver
        • ThemeResolver
          • HandlerMapping(非常非常重要)
            • HandlerAdapter
              • HandlerExceptionResolver
                • RequestToViewNameTranslator
                  • ViewResolver
                    • FlashMapManager
                    • onRefresh(wac) / initStrategies(wac)详解
                      • initMultipartResolver
                        • initLocaleResolver
                          • initThemeResolver
                            • initHandlerMappings
                              • initHandlerAdapters
                                • initHandlerExceptionResolvers
                                  • initRequestToViewNameTranslator
                                    • initViewResolvers
                                      • initFlashMapManager
                                      • 总结
                                      相关产品与服务
                                      容器服务
                                      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档