①、DispatcherServlet 是 SpringMVC 中的前端控制器(Front Controller),负责接收 Request 并将 Request 转发给对应的处理组件。
② 、 HanlerMapping 是 SpringMVC 中 完 成 url 到 Controller 映 射 的 组 件 。DispatcherServlet 接 收 Request, 然 后 从 HandlerMapping 查 找 处 理 Request 的Controller。
③、Controller 处理 Request,并返回 ModelAndView 对象,Controller 是 SpringMVC中负责处理 Request 的组件(类似于 Struts2 中的 Action),ModelAndView 是封装结果视图的组件。
④、⑤、⑥视图解析器解析 ModelAndView 对象并返回对应的视图给客户端。在前面的章节中我们已经大致了解到,容器初始化时会建立所有 url 和 Controller 中的Method 的对应关系,保存到 HandlerMapping 中,用户请求是根据 Request 请求的url 快速定位到 Controller 中的某个方法。在 Spring 中先将 url 和 Controller 的对应关系,保存到 Map中。Web 容器启动时会通知 Spring 初始化容器(加载Bean 的定义信息和初始化所有单例 Bean),然后 SpringMVC 会遍历容器中的 Bean,获取每一个 Controller 中的所有方法访问的 url,然后将 url 和 Controller 保存到一个 Map中;这样就可以根据 Request 快速定位到 Controller,因为最终处理 Request 的是Controller 中的方法,Map 中只保留了 url 和 Controller 中的对应关系,所以要根据Request 的 url 进一步确认 Controller 中的 Method,这一步工作的原理就是拼接Controller 的 url(Controller 上@RequestMapping 的值)和方法的 url(Method 上@RequestMapping 的值),与 request 的 url 进行匹配,找到匹配的那个方法;确定处理请求的 Method 后,接下来的任务就是参数绑定,把 Request 中参数绑定到方法的形式参数上,这一步是整个请求处理过程中最复杂的一个步骤。
Spring MVC 九大组件
HandlerMappings
HandlerMapping 是用来查找 Handler 的,也就是处理器,具体的表现形式可以是类也可以是方法。比如,标注了@RequestMapping 的每个 method 都可以看成是一个Handler,由 Handler 来负责实际的请求处理。 HandlerMapping 在请求到达之后,它的作用便是找到请求相应的处理器 Handler 和 Interceptors。
HandlerAdapters
从名字上看,这是一个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能够处理请求便行, 但是把请求交给 Servlet 的时候,由于 Servlet 的方法结构都是如doService(HttpServletRequest req, HttpServletResponse resp) 这样的形式,让固定的 Servlet 处理方法调用 Handler 来进行处理,这一步工作便是 HandlerAdapter 要做的事。
HandlerExceptionResolvers
从这个组件的名字上看,这个就是用来处理 Handler 过程中产生的异常情况的组件。 具体来说,此组件的作用是根据异常设置 ModelAndView, 之后再交给 render()方法进行渲 染 , 而 render() 便 将ModelAndView渲 染 成 页 面 。 不 过 有 一 点 ,HandlerExceptionResolver 只是用于解析对请求做处理阶段产生的异常,而渲染阶段的异常则不归他管了,这也是 Spring MVC 组件设计的一大原则分工明确互不干涉。
ViewResolvers
视图解析器,相信大家对这个应该都很熟悉了。因为通常在 SpringMVC 的配置文件中,都会配上一个该接口的实现类来进行视图的解析。 这个组件的主要作用,便是将 String类型的视图名和Locale解析为View类型的视图。这个接口只有一个resolveViewName()方法。从方法的定义就可以看出,Controller 层返回的 String 类型的视图名 viewName,最终会在这里被解析成为 View。View 是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,最终生成 html 文件。ViewResolver 在这个过程中,主要做两件大事,即,ViewResolver 会找到渲染所用的模板(使用什么模板来渲染?)和所用的技术(其实也就是视图的类型,如 JSP 啊还是其他什么 Blabla 的)填入参数。默认情况下,Spring MVC 会为我们自动配置一个 InternalResourceViewResolver,这个是针对 JSP 类型视图的。
RequestToViewNameTranslator
这个组件的作用,在于从 Request 中获取 viewName. 因为 ViewResolver 是根据ViewName 查找 View, 但有的 Handler 处理完成之后,没有设置 View 也没有设置ViewName, 便要通过这个组件来从 Request 中查找 viewName。LocaleResolver在上面我们有看到 ViewResolver 的 resolveViewName()方法,需要两个参数。那么第二个参数 Locale 是从哪来的呢,这就是 LocaleResolver 要做的事了。 LocaleResolver用于从 request 中解析出 Locale, 在中国大陆地区,Locale 当然就会是 zh-CN 之类,用来表示一个区域。这个类也是 i18n 的基础。
ThemeResolver
从名字便可看出,这个类是用来解析主题的。主题,就是样式,图片以及它们所形成的显示效果的集合。Spring MVC 中一套主题对应一个 properties 文件,里面存放着跟当前主题相关的所有资源,如图片,css 样式等。创建主题非常简单,只需准备好资源,然后新建一个 "主题名.properties" 并将资源设置进去,放在 classpath 下,便可以在页面中使用了。 Spring MVC 中跟主题有关的类有 ThemeResolver, ThemeSource 和Theme。ThemeResolver 负责从 request 中解析出主题名, ThemeSource 则根据主题名找到具体的主题, 其抽象也就是 Theme, 通过 Theme 来获取主题和具体的资源。
MultipartResolver
其实这是一个大家很熟悉的组件,MultipartResolver 用于处理上传请求,通过将普通的Request 包装成 MultipartHttpServletRequest 来实现。MultipartHttpServletRequest可以通过 getFile() 直接获得文件,如果是多个文件上传,还可以通过调用 getFileMap得到 Map 这样的结构。MultipartResolver 的作用就是用来封装普通的 request,使其拥有处理文件上传的功能。
FlashMapManager
说到 FlashMapManager,就得先提一下 FlashMap。FlashMap 用于重定向 Redirect 时的参数数据传递,比如,在处理用户订单提交时,为了避免重复提交,可以处理完 post 请求后 redirect 到一个 get 请求,这个 get 请求可以用来显示订单详情之类的信息。这样做虽然可以规避用户刷新重新提交表单的问题,但是在这个页面上要显示订单的信息,那这些数据从哪里去获取呢,因为 redirect 重定向是没有传递参数这一功能的,如果不想把参数写进 url(其实也不推荐这么做,url 有长度限制不说,把参数都直接暴露,感觉也不安全), 那么就可以通过 flashMap 来传递。只需 要 在 redirect 之 前 , 将 要 传 递 的 数 据 写 入 request ( 可 以 通 过ServletRequestAttributes.getRequest()获得)的属性
OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在 redirect 之后的 handler 中 Spring 就会自动将其设置到 Model 中,在显示订单信息的页面上,就可以直接从 Model 中取得数据了。而 FlashMapManager 就是用来管理 FlashMap 的。
最后我们再来梳理一下Spring MVC 核心组件的关联关系(如下图):
最后来一张时序图:
Spring MVC 使用优化建议:
1、Controller 如果能保持单例,尽量使用单例
这样可以减少创建对象和回收对象的开销。也就是说,如果 Controller 的类变量和实例变量可以以方法形参声明的尽量以方法的形参声明,不要以类变量和实例变量声明,这样可以避免线程安全问题。
2、处理 Request 的方法中的形参务必加上@RequestParam 注解这样可以避免 Spring MVC 使用 asm 框架读取 class 文件获取方法参数名的过程。即便Spring MVC 对读取出的方法参数名进行了缓存,如果不要读取 class 文件当然是更好。
3、缓存 URL
Spring MVC 并没有对处理 url 的方法进行缓存,也就是说每次都要根据请求 url 去匹配 Controller 中的方法 url,如果把 url 和 Method 的关系
缓存起来,会不会带来性能上的提升呢?有点恶心的是,负责解析 url 和 Method 对应关系的 ServletHandlerMethodResolver 是一个 private 的内部类,不能直接继承该类增强代码,必须要该代码后重新编译。当然,如果缓存起来,必须要考虑缓存的线程安全问题。