SpringFramework WebMvc 总结

小龙 1,111 2022-07-05

Mvc三层架构

image

Servlet3.0 规范的运行时可插拔性

Servlet 3.0 规范的这个运行时可插拔性的特性,它里面定义了一个 ServletContainerInitializer 的东西,这个 ServletContainerInitializer 会借助 Java 的 SPI 技术,可以从项目或者项目依赖的 jar 包中,找到一个 /META-INF/services/javax.servlet.ServletContainerInitializer 文件,并加载项目中所有它的实现类,并配合 @HandlesTypes 注解一起使用,传入一些我们所需要的接口 / 抽象类,以此来实现动态 web 项目组件的装载。

这个规范设计的用法,核心的目的之一是组件的可插拔,有了组件的可插拔,就可以在导入一个带 ServletContainerInitializer 的实现类的 jar 包时,自动加载对应的实现类。

Serlvet-mapping

有关 DIspatcherServlet 在配置时的 servlet-mapping 配置,一般有三种情况

  • /:表示拦截所有请求、静态资源、但是不拦截.jsp
  • /:表示拦截所有的请求、静态资源、.jsp页面*
  • *.do 或者 *.action:只会拦截结尾为 .do 或者 .action 的请求

WebApplicationInitializer

SpringWebMvc 支持 Servlet 3.0 的方式,是提供了一个 WebApplicationInitializer 的接口,以及一组抽象子类供我们扩展,我们最常使用的 AbstractAnnotationConfigDispatcherServletInitializer ,只需要继承它,并且重写三个方法,提供父容器和子容器的注解配置类,以及配置 servlet-mapping 即可。

2.2 页面数据传递方案

从后端 Controller,到前端的页面,进行数据传递,无外乎就是利用 HttpServletRequest 域,以此来承载的数据传递方式有三种:

  • HttpServletRequest
  • ModelAndView
  • ModelMap / Model

参数绑定

请求参数的绑定,一般会使用原生类型、或者模型来收集,通常都直接对应字段名即可,SpringWebMvc 会帮我们自动封装,不过这里面有几个注解和Api是比较常用的

  • @RequestParam:可以指定绑定的参数名、是否必传、参数默认值
  • @PathVaiable:可以从 url 上取参数值
  • Converter:自定义参数类型转换器使用的核心API接口,配合 FormattingConversionServiceFactoryBean 可以完成自定义参数类型转换的配置

json支持

ajax 异步请求的请求与响应,通常都需要 json 支持,SpringWebMvc 可以整合 json 转换工具框架,来实现 json 的数据转换。

开启 json 转换的支持,可以直接声明 mvc:annotation-driven/ 标签,或者使用 @EnableWebMvc 注解实现,也可以通过配置 RequestMappingHandlerAdapter ,并向其中传入 messageConverters 。不过在实际开发中,一般都是使用标签或者注解开启的方式,简单而且够用。

如果 ajax 请求的数据是 json ,则可以在请求参数上标注 @RequestBody 注解;如果需要响应 json 数据,可以在方法上标注 @ResponseBody 注解。

静态资源配置

当 DispatcherServlet 配置的 servlet-mapping 为 / 或者 /* 时,会导致静态资源无法加载的异常,这种情况就需要配置静态资源的放行规则,同时也可以对这些静态资源的浏览器缓存设置过期时间。

WebMvc的父子容器

image-1657005560619

这张图可以很明显的体现出 ApplicationContext 的层次性特征!对于一个基于 SpringWebMvc 的应用,它希望把 Service 、Dao 等类都放到根容器,把表现层的 Controller 及相关的组件都放到 Servlet 的子容器中,以此形成一个层级关系。

这种父子容器的设计主要有两个好处:第一,形成层级关系后,Controller 可以拿到 Service ,而 Service 拿不到 Controller ,可以以此形成一道隔离;第二,如果真的出现特殊情况,需要注册多个 DispatcherServlet 的时候,不必注册多套 Service 和 Dao ,每个 WeBMvc 的子容器都从这一个根容器中取 Service 和 Dao 即可。

WebMvc的核心组件

  • DispatcherServlet :核心的中央处理器,负责接收请求、分发,并给予客户端响应
  • HandlerMapping :处理器映射器,根据 uri ,去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器,和 Handler 一起封装
  • HandlerAdapter :处理器适配器,根据 HandlerMapping 找到的 Handler ,适配执行对应的 Handler
  • ViewResolver :视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给 DispatcherServlet 响应客户端
  • Handler :处理实际请求的处理器

RESTful编码风格

  • GET :获取资源 / 数据
  • POST :新建资源 / 数据
  • PUT :更新资源 / 数据
  • DELETE :删除资源 / 数据

数据校验

SpringWebMvc 支持的后端数据校验,是基于 JSR - 303 规范的数据校验。通常情况下 SpringWebMvc 会配合 Hibernate-Validator 来实现数据校验,通过在请求的参数或者实体上标注相应的校验注解,并配置校验器,就可以实现注解式的数据校验了。

此外,也可以通过声明不同的分组接口,实现分组校验,以及校验错误信息的外部化。

文件上传与下载

SpringWebMvc 支持的文件上传,需要一个 CommonsMultipartResolver 的文件上传处理器,配合 Controller 方法中传入的 MultipartFile 类型参数,就可以实现文件上传的接收。

SpringWebMvc 支持的文件下载,只需要在 Controller 方法的返回时,返回 ResponseEntity ,并配置好对应的文件名等请求头信息,即可实现文件下载。

统一异常处理

SpringWebMvc 推荐使用的注解式统一异常处理,采用 @ControllerAdvice + @ExceptionHandler ,给 Controller 织入异常处理的逻辑,即可在 Controller 方法执行出现异常时予以处理。在实际的异常处理中,SpringWebMvc 遵循一个原则:更细粒度的异常处理,优先级高于粗粒度的异常处理。

拦截器

有关 SpringWebMvc 拦截器的使用也很简单,通过实现 HandlerInterceptor 接口,并实现对应的方法逻辑即可。这里面的执行机制是有先后顺序的,
image-1657005867020

拦截器是 SpringWebMvc 的概念,而过滤器是 Servlet 的概念。Servlet 、Filter 、Listener 共同称为 Servlet 三大组件,它们都需要依赖 Servlet 的 API 。而拦截器不同,拦截器是 SpringWebMvc 提出,它只是 SpringWebMvc 框架中的一个 API 设计罢了。所以我们可以总结出第一个区别:拦截器是框架的概念,而过滤器是 Servlet 的概念

它们的拦截范围也不一样。过滤器 Filter 是在 web.xml 或者借助 Servlet 3.0 规范来注册,任何来自于 Servlet 容器(Tomcat)的请求都会走这些过滤器;拦截器是SpringWebMvc框架的概念,而 SpringWebMvc 的核心是一个 DispatcherServlet ,所以拦截器实际上是在 DispatcherServlet 接收到请求后才有机会工作的,对于 DispatcherServlet 没有接收到的请求,拦截器只能干瞪眼。所以接下来总结出的第二个区别:过滤器可以拦截几乎所有请求,而拦截器只能拦截到被 DispatcherServlet 接收处理的请求。

来源不同还造成一个不同的现象:过滤器由 Servlet 容器创建,与 SpringFramework 的 IOC 没有任何关系,所以无法借助依赖注入,给过滤器中注入属性;而拦截器是被 SpringFramework 的 IOC 统一管理的,它就是一个一个的普通的 bean ,所以可以任意注入需要的 bean 。所以第三条区别:拦截器可以借助依赖注入获取所需要的 bean ,而过滤器无法使用正常手段获取。

过滤器的调用是一层一层的函数回调,而拦截器是 SpringWebMvc 在底层借助反射调用的( invokeForRequest 方法最终是反射的 Controller 方法)。

跨域问题

跨域是一种现象,跨域是由浏览器的同源策略引起的,而同源策略,是在浏览器上对资源的一种保护策略,最初同源策略只是用于保护网页的 cookie ,后演变的越来越严格,下面会提到。同源策略规定了三个相同:协议相同、域名 / 主机相同、端口相同。当这三个因素都相同时,则浏览器会认为访问的资源是同一个来源。

而跨域现象中,只要触发以下三种情况之一,都会引起跨域问题:

  • http 访问 https ,或者 https 访问 http
  • 不同域名 / 服务器主机之间的访问
  • 不同端口之间的访问

SpringWebMvc 给出的解决方案,是在需要允许跨域的方法上标注 @CrossOrigin 注解即可,并且可以在注解上指定跨域范围。@CrossOrigin 注解的核心底层,是给响应头中添加了一个 Access-Control-Allow-Origin: * 。

异步请求

Servlet 3.0 规范中支持了异步请求,借助 HttpServletRequest ,可以从中拿到一个 AsyncContext ,使用这个 AsyncContext ,就可以实现异步请求处理。

SpringWebMvc 支持两种方式的异步请求,分别是基于 Callable 的异步请求支持,以及基于 DeferredResult 的异步请求支持。前者操作简单,底层的异步线程池由我们提供,并由 SpringWebMvc 加以使用,后者设计相对复杂,但一切都由我们来控制。


# web # mvc