本文节选自《Spring 5核心原理》
1 什么是Spring MVC
Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。Spring MVC 角色划分清晰,分工明细。由于 Spring MVC 本身就是 Spring 框架的一部分,可以说和 Spring 框架是无缝集成。性能方面具有先天的优越性,是当今业界最主流的 Web 开发框架,最热门的开发技能。首先从一个由Spring提供的DispatcherServlet开始,重写了Serlvet的init()方法、service()方法和destroy()方法,SpringMVC九大组件在DispatcherServlet的init()方法中初始化,在service()方法中执行。下面,我们先来看Spring MVC九大组件的初始化。
2 SpringMVC九大组件名称解释
Spring MVC九大组件在DispatcherServlet的init()方法中初始化,下面我详细介绍一下Spring MVC九大组件的名称和作用。
序号 | 组件名 | 解释 |
---|---|---|
1 | MultipartResolver | 用于处理多文件上传请求。 |
2 | LocaleResolver | 用于从请求中解析出 Locale,是i18n的基础。 |
3 | ThemeResolver | 用来解析样式、图片及它们所形成的显示效果的集合。 |
4 | HandlerMapping | 保存Url和逻辑处理的映射关系, |
5 | HandlerAdapter | 动态参数适配器,让固定的Servlet处理方法调用Handler来进行处理 |
6 | HandlerExceptionResolver | 用来处理Handler产生的异常情况的组件。 |
7 | RequestToViewNameTranslator | 从请求中获取ViewName |
8 | ViewResolvers | 主要作用是将String类型的视图名和Locale解析为View类型的视图 |
9 | FlashMapManager | 用于重定向时的参数传递。 |
具体详细介绍如下:
2.1 MultipartResolver
MultipartResolver是一个大家很熟悉的组件,用于处理上传请求,通过将普通的请求包装成MultipartHttpServletRequest来实现。MultipartHttpServletRequest可以通过getFile()方法直接获得文件。如果上传多个文件,还可以调用getFileMap()方法得到 Map< FileName, File> 这样的结构。MultipartResolver的作用就是封装普通的请求,使其拥有文件上传的功能。
2.2 LocaleResolver
ViewResolver组件的resolveViewName()方法需要两个参数,一个是视图名,另一个就是Locale。参数Locale是从哪来的呢?这就是LocaleResolver组件要做的事。LocaleResolver用于从请求中解析出 Locale,比如在中国Locale当然就是zh-CN,用来表示一个区域。这个组件也是i18n的基础。
2.3 ThemeResolver
从名字便可看出,ThemeResolver组件是用来解析主题的。主题就是样式、图片及它们所形成的显示效果的集合。Spring MVC中一套主题对应一个properties文件,里面存放着与当前主题相关的所有资源,如图片、CSS样式等。创建主题非常简单,只需准备好资源,然后新建一个“主题名.properties”并将资源设置进去,放在classpath下,之后便可以在页面中使用了。Spring MVC中与主题有关的类有ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名,ThemeSource则根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和具体的资源。
2.4 HandlerMapping
HandlerMapping是用来查找Handler的,也就是处理器,具体的表现形式可以是类,也可以是方法。比如,标注了@RequestMapping的每个方法都可以看成一个Handler。Handler负责实际的请求处理,在请求到达后,HandlerMapping的作用便是找到请求相应的处理器Handler和Interceptor。
2.5 HandlerAdapter
从名字上看,HandlerAdapter是一个适配器。因为Spring MVC中Handler可以是任意形式的,只要能够处理请求便可。但是把请求交给Servlet的时候,由于Servlet的方法结构都是doService(HttpServletRequest req, HttpServletResponse resp)形式的,要让固定的Servlet处理方法调用Handler来进行处理,这一步工作便是HandlerAdapter要做的事。
2.6 HandlerExceptionResolver
从组件的名字上看,HandlerExceptionResolver是用来处理Handler产生的异常情况的组件。具体来说,此组件的作用是根据异常设置ModelAndView,之后交给渲染方法进行渲染,渲染方法会将ModelAndView渲染成页面。不过要注意,HandlerExceptionResolver只用于解析对请求做处理阶段产生的异常,渲染阶段的异常不归它管,这也是Spring MVC 组件设计的一大原则—分工明确、互不干涉。
2.7 RequestToViewNameTranslator
RequestToViewNameTranslator组件的作用是从请求中获取ViewName。因为ViewResolver根据ViewName查找View,但有的Handler处理完成之后,没有设置View,也没有设置ViewName,便要通过这个组件来从请求中查找ViewName。
2.8 ViewResolver
ViewResolver即视图解析器,相信大家对这个组件应该很熟悉了。通常在Spring MVC的配置文件中,都会配上一个实现类来进行视图解析。这个组件的主要作用是将String类型的视图名和Locale解析为View类型的视图,只有一个resolveViewName()方法。从方法的定义可以看出,Controller层返回的String类型的视图名viewName最终会在这里被解析成为View。View是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,生成HTML文件。ViewResolver在这个过程中主要做两件大事:ViewResolver会找到渲染所用的模板(第一件大事)和所用的技术(第二件大事,其实也就是找到视图的类型,如JSP)并填入参数。默认情况下,Spring MVC会为我们自动配置一个InternalResourceViewResolver,是针对JSP类型视图的。
2.9 FlashMapManager
说到FlashMapManager组件,得先说一下FlashMap。FlashMap用于重定向时的参数传递,比如在处理用户订单时,为了避免重复提交,可以处理完post请求后重定向到一个get请求,这个get请求可以用来显示订单详情之类的信息。这样做虽然可以规避用户重新提交订单的问题,但是在这个页面上要显示订单的信息,这些数据从哪里获取呢?因为重定向是没有传递参数这一功能的,如果不想把参数写进URL(其实也不推荐这么做,除了URL有长度限制,把参数都直接暴露也不安全),那么就可以通过FlashMap来传递。只需要在重定向之前将要传递的数据写入请求(可以通过ServletRequestAttributes.getRequest()方法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE中,这样在重定向之后的Handler中Spring就会自动将其设置到Model中,在显示订单信息的页面上就可以直接从Model中获得数据。FlashMapManager就是用来管理FlashMap的。
3 Spring MVC关键组件的执行流程
Spring MVC九大组件的执行在DispatcherServlet的service()方法中完成。在这里,我重点介绍几个关键组件HandlerMapping、HandlerAdapter、ViewResolver在service()方法中的执行流程,具体调用分为以下几个步骤:
1、HandlerMapping回到调用HandlerAdapter
2、HandlerAdapter会返回ModelAndView
3、ModelAndView根据用户传入参数得到ViewResolvers
4、ViewResolvers会将用户传入的参数封装为View,交给引擎进行渲染。
下面给大家分享一张Spring MVC关键组件的执行流程图,以帮助大家更好地理解:
注意:上图中有大家最熟悉的两个类:ModelAndView和View类并不属于Spring MVC九大组件之列。
4 Spring MVC优化建议
前面我们已经对Spring MVC的工作原理和源码进行了分析,在这个过程中有几个优化点。
1. Controller如果能保持单例模式,尽量使用单例模式
这样可以减小创建对象和回收对象的开销。也就是说,如果Controller的类变量和实例变量可以以方法形参声明就尽量以方法形参声明,不要以类变量和实例变量声明,这样可以避免线程安全问题。
2. 处理请求的方法中的形参务必加上@RequestParam注解
这样可以避免Spring MVC使用asm框架读取.class文件获取方法参数名。即便Spring MVC对读取出的方法参数名进行了缓存,如果能不读取.class文件当然更好。
3. 缓存URL
在阅读源码的过程中,我们发现Spring MVC并没有对处理URL的方法进行缓存,也就是说,每次都要根据请求URL去匹配Controller中的方法的URL,如果把URL和方法的关系缓存起来,会不会带来性能上的提升呢?不幸的是,负责解析URL和方法对应关系的ServletHandlerMethodResolver是一个私有的内部类,不能直接通过继承该类增强代码,必须在代码后重新编译。当然,如果将URL缓存起来,必须考虑缓存的线程安全问题。