1. Web基础回顾
1.1 MVC与三层架构
对于 MVC 与三层架构而言,这是 Web 开发的基础,小伙伴们一定要熟记于心。
1.2 Servlet3.0规范的运行时可插拔性
Servlet 3.0 规范的这个运行时可插拔性的特性,它里面定义了一个 ServletContainerInitializer
的东西,这个 ServletContainerInitializer
会借助 Java 的 SPI 技术,可以从项目或者项目依赖的 jar 包中,找到一个 /META-INF/services/javax.servlet.ServletContainerInitializer
文件,并加载项目中所有它的实现类,并配合 @HandlesTypes
注解一起使用,传入一些我们所需要的接口 / 抽象类,以此来实现动态 web 项目组件的装载。
这个规范设计的用法,核心的目的之一是组件的可插拔,有了组件的可插拔,就可以在导入一个带 ServletContainerInitializer
的实现类的 jar 包时,自动加载对应的实现类。
2. WebMvc基础
2.1 DispatcherServlet相关
WebMvc 的基础部分,我们最先接触到的就是这个 DispatcherServlet
,这里面有一些细节,我们也来回顾一下。
2.1.1 servlet-mapping
有关 DispatcherServlet
在配置时的 servlet-mapping
配置,一般有三种情况:
- / ,它代表的含义是:拦截所有请求、静态资源,但不拦截 .jsp
- /* ,它的含义是:拦截所有请求、静态资源、.jsp 页面
*.do
或者*.action
:只会拦截所有结尾为.do
或者.action
的请求
2.1.2 WebApplicationInitializer
SpringWebMvc 支持 Servlet 3.0 的方式,是提供了一个 WebApplicationInitializer
的接口,以及一组抽象子类供我们扩展,我们最常使用的 AbstractAnnotationConfigDispatcherServletInitializer
,只需要继承它,并且重写三个方法,提供父容器和子容器的注解配置类,以及配置 servlet-mapping
即可。
2.2 页面数据传递方案
从后端 Controller ,到前端的页面,进行数据传递,无外乎是利用 HttpServletRequest
域,以此来承载的数据传递方案介质有三种:
HttpServletRequest
ModelAndView
ModelMap
/Model
2.3 参数绑定相关
请求的参数绑定,一般我们会使用原生类型,或者模型来收集,通常都是直接对应字段名即可,SpringWebMvc 会帮我们自动封装,不过这里面有几个注解和 API ,是比较常用的:
@RequestParam
:可指定绑定的参数名、是否必传、参数默认值@PathVariable
:可从 url 上取参数值Converter
:自定义参数类型转换器使用的核心 API 接口,配合FormattingConversionServiceFactoryBean
可以完成自定义参数类型转换的配置
2.4 json支持
ajax 异步请求的请求与响应,通常都需要 json 支持,SpringWebMvc 可以整合 json 转换工具框架,来实现 json 的数据转换。
开启 json 转换的支持,可以直接声明 <mvc:annotation-driven/>
标签,或者使用 @EnableWebMvc
注解实现,也可以通过配置 RequestMappingHandlerAdapter
,并向其中传入 messageConverters
。不过在实际开发中,一般都是使用标签或者注解开启的方式,简单而且够用。
如果 ajax 请求的数据是 json ,则可以在请求参数上标注 @RequestBody
注解;如果需要响应 json 数据,可以在方法上标注 @ResponseBody
注解。
2.5 静态资源配置
当 DispatcherServlet
配置的 servlet-mapping
为 / 或者 /* 时,会导致静态资源无法加载的异常,这种情况就需要配置静态资源的放行规则,同时也可以对这些静态资源的浏览器缓存设置过期时间。
3. WebMvc的设计
3.1 WebMvc的父子容器
这也是一个容易被讨论起来的问题,我们还是借助官网的那张图来解释:
上面这张图可以很明显的体现出 ApplicationContext
的层次性特征!对于一个基于 SpringWebMvc 的应用,它希望把 Service 、Dao 等类都放到根容器,把表现层的 Controller 及相关的组件都放到 Servlet 的子容器中,以此形成一个层级关系。
这种父子容器的设计主要有两个好处:第一,形成层级关系后,Controller 可以拿到 Service ,而 Service 拿不到 Controller ,可以以此形成一道隔离;第二,如果真的出现特殊情况,需要注册多个 DispatcherServlet
的时候,不必注册多套 Service 和 Dao ,每个 WeBMvc 的子容器都从这一个根容器中取 Service 和 Dao 即可。
3.2 WebMvc的核心组件
SpringWebMvc 中的组件职责划分是相当清晰的:
- DispatcherServlet :核心的中央处理器,负责接收请求、分发,并给予客户端响应
- HandlerMapping :处理器映射器,根据 uri ,去匹配查找能处理的 Handler ,并会将请求涉及到的拦截器,和 Handler 一起封装
- HandlerAdapter :处理器适配器,根据
HandlerMapping
找到的 Handler ,适配执行对应的 Handler - ViewResolver :视图解析器,根据 Handler 返回的逻辑视图 / 视图,解析并渲染真正的视图,并传递给
DispatcherServlet
响应客户端 - Handler :处理实际请求的处理器
4. Web开发的其他知识点
4.1 RESTful编码风格
RESTful 只是一种编码的风格,不是标准、不是协议、不是规范,仅仅是风格而已。RESTful 的编码风格将 HTTP 协议中定义的 8 种请求方式中的四种方式,赋予了一个真正的意义:
- GET :获取资源 / 数据
- POST :新建资源 / 数据
- PUT :更新资源 / 数据
- DELETE :删除资源 / 数据
如果用这种编码风格的话,设计的 API 请求路径看上去更简洁、有层次性。就其本身,与传统的参数拼接而言,无好坏之分。
4.2 数据校验
SpringWebMvc 支持的后端数据校验,是基于 JSR - 303 规范的数据校验。通常情况下 SpringWebMvc 会配合 Hibernate-Validator 来实现数据校验,通过在请求的参数或者实体上标注相应的校验注解,并配置校验器,就可以实现注解式的数据校验了。
此外,也可以通过声明不同的分组接口,实现分组校验,以及校验错误信息的外部化。
4.3 文件上传与下载
SpringWebMvc 支持的文件上传,需要一个 CommonsMultipartResolver
的文件上传处理器,配合 Controller 方法中传入的 MultipartFile 类型参数,就可以实现文件上传的接收。
SpringWebMvc 支持的文件下载,只需要在 Controller 方法的返回时,返回 ResponseEntity
,并配置好对应的文件名等请求头信息,即可实现文件下载。
4.4 统一异常处理
SpringWebMvc 推荐使用的注解式统一异常处理,采用 @ControllerAdvice
+ @ExceptionHandler
,给 Controller 织入异常处理的逻辑,即可在 Controller 方法执行出现异常时予以处理。在实际的异常处理中,SpringWebMvc 遵循一个原则:更细粒度的异常处理,优先级高于粗粒度的异常处理。
4.5 拦截器
有关 SpringWebMvc 拦截器的使用也很简单,通过实现 HandlerInterceptor
接口,并实现对应的方法逻辑即可。这里面的执行机制是有先后顺序的,之前在 75 章中小册也给过一张图:
除了这个机制之外,还有一个面试题可能会问到的,就是拦截器与过滤器的对比。小册把之前 75 章中给小伙伴们的分析搬到这里来,这样小伙伴也不用来回切换章节了。
首先我们要明白一点,拦截器是 SpringWebMvc 的概念,而过滤器是 Servlet 的概念。Servlet
、Filter
、Listener
共同称为 Servlet 三大组件,它们都需要依赖 Servlet 的 API 。而拦截器不同,拦截器是 SpringWebMvc 提出,它只是 SpringWebMvc 框架中的一个 API 设计罢了。所以我们可以总结出第一个区别:拦截器是框架的概念,而过滤器是 Servlet 的概念。
那既然两方的概念来源不同,那它们的拦截范围也就不一样了。过滤器 Filter
是在 web.xml
或者借助 Servlet 3.0 规范来注册,任何来自于 Servlet 容器(Tomcat)的请求都会走这些过滤器;拦截器既然是框架的概念,而 SpringWebMvc 的核心是一个 DispatcherServlet
,所以拦截器实际上是在 DispatcherServlet
接收到请求后才有机会工作的,对于 DispatcherServlet
没有接收到的请求,拦截器只能干瞪眼。所以接下来总结出的第二个区别:过滤器可以拦截几乎所有请求,而拦截器只能拦截到被 DispatcherServlet
接收处理的请求。
继续,来源不同还造成一个不同的现象:过滤器由 Servlet 容器创建,与 SpringFramework 的 IOC 没有任何关系,所以无法借助依赖注入,给过滤器中注入属性;而拦截器是被 SpringFramework 的 IOC 统一管理的,它就是一个一个的普通的 bean ,所以可以任意注入需要的 bean 。所以第三条区别:拦截器可以借助依赖注入获取所需要的 bean ,而过滤器无法使用正常手段获取。
如果深入到底层调用,可以发现,过滤器的调用是一层一层的函数回调,而拦截器是 SpringWebMvc 在底层借助反射调用的( invokeForRequest
方法最终是反射的 Controller 方法)。
4.6 跨域问题
跨域是一种现象,跨域是由浏览器的同源策略引起的,而同源策略,是在浏览器上对资源的一种保护策略,最初同源策略只是用于保护网页的 cookie ,后演变的越来越严格,下面会提到。同源策略规定了三个相同:协议相同、域名 / 主机相同、端口相同。当这三个因素都相同时,则浏览器会认为访问的资源是同一个来源。
而跨域现象中,只要触发以下三种情况之一,都会引起跨域问题:
- http 访问 https ,或者 https 访问 http
- 不同域名 / 服务器主机之间的访问
- 不同端口之间的访问
SpringWebMvc 给出的解决方案,是在需要允许跨域的方法上标注 @CrossOrigin
注解即可,并且可以在注解上指定跨域范围。@CrossOrigin
注解的核心底层,是给响应头中添加了一个 Access-Control-Allow-Origin: *
。
4.7 异步请求
Servlet 3.0 规范中支持了异步请求,借助 HttpServletRequest
,可以从中拿到一个 AsyncContext
,使用这个 AsyncContext
,就可以实现异步请求处理。
SpringWebMvc 支持两种方式的异步请求,分别是基于 Callable
的异步请求支持,以及基于 DeferredResult
的异步请求支持。前者操作简单,底层的异步线程池由我们提供,并由 SpringWebMvc 加以使用,后者设计相对复杂,但一切都由我们来控制。