文章目录
- 【README】
- 【1】文件上传与MultipartResolver
- 【1.1】使用MultipartResolver进行文件上传
- 【1.2】springmvc处理multipart多部件请求流程
- 【1.3】使用springmvc上传文件代码实现(springmvc6.10版本):
- 【2】Handler与HandlerAdaptor(处理器与处理器适配器)
- 【2.1】概述
- 【2.1.1】Handler及HandlerAdapter处理web请求过程
- 【3】web请求处理拦截与HandlerInterceptor拦截器
- 【3.1】springmvc提供的HandlerInterceptor实现类
- 【3.2】自定义 HandlerInterceptor(统计执行耗时)
- 【3.2.1】HandlerInterceptor装配
- 【3.2.2】拦截器作用范围(HandlerMapping)
- 【3.3】过滤器Filter
- 【3.3.1】springmvc配置过滤器代码实践
- 【3.3.2】底层原理
- 【4】springmvc异常处理与HandlerExceptionResolver(处理器异常解析器)
- 【4.1】HandlerExceptionResolver-处理器异常解析器
- 【4.2】SimpleMappingExceptionResolver
- 【4.2.1】SimpleMappingExceptionResolver处理异常代码实践
- 【4.3】HandlerExceptionResolver异常处理代码调试
【README】
本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
1)springmvc其他组件如下:
- MultipartResolver(多部件解析器): 在 HandlerMapping之前执行, 处理文件上传请求;
- HandlerInterceptor(处理器拦截器): 对处理流程进行拦截;
- HandlerAdapter(处理器适配器): 帮助我们使用其他类型的Handler;(而不仅仅只使用Controller这一种Handler)
- HandlerExceptionResolver(处理器异常解析器): 处理器异常解析器; 提供处理器异常处理的标准框架;
2)web.xml (web应用部署描述符,servlet容器加载时读取的xml文件)
<?xml version="1.0" encoding="UTF-8"?>
<web-appxmlns = "https://jakarta.ee/xml/ns/jakartaee"xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation = "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"version = "5.0"metadata-complete = "false"
><display-name>springmvcDiscover</display-name><!-- 指定ContextLoaderListener加载web容器时使用的多个xml配置文件(默认使用/WEB-INF/applicationContext.xml) --><context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/applicationContext.xml,/WEB-INF/applicationContext-module1.xml</param-value></context-param><filter><filter-name>encodingFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>encodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 注册过滤器代理 --><filter><filter-name>customFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>customFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 配置监听器ContextLoaderListener,其加载顶层WebApplicationContext web容器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) --><servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件--><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value></init-param><load-on-startup>2</load-on-startup><!-- 新增multipart-config 子元素,该servlet才启用文件上传功能(必须) --><multipart-config><!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 --><location>D:\temp\springmvcUploadDir</location><!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M --><max-file-size>20971520</max-file-size><!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M --><max-request-size>1048576000</max-request-size><!-- 临时保存到磁盘的文件大小最小值(超过该值就保存);默认0 --><file-size-threshold>-1</file-size-threshold></multipart-config></servlet><servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping><welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list></web-app>
3)配置文件目录结构:
- springmvc顶级web容器配置文件: applicationContext.xml , applicationContext-module1.xml
- DispatcherServlet次顶级web容器配置文件:dispatcher-servlet.xml, dispatcher-servlet2.xml, dispatcher-servlet3-upload.xml
【1】文件上传与MultipartResolver
1)RFC1867: 为html表单新增了一种MIME类型(multipart/formdata),表示表单的文件上传 ;
2)**MIME(Multipurpose Internet Mail Extensions)定义:**多用途互联网邮件扩展类型。媒体类型(也称为多用途互联网邮件扩展或 MIME 类型)表示文档、文件或字节组合的性质和格式。简单理解:MIME定义了文档,文件或字节组合的格式;MIME 类型在 IETF 的 RFC 6838 中定义并标准化, 参见 https://datatracker.ietf.org/doc/html/rfc6838
- 常见的MIME类型(通用型):
- 超文本标记语言文本 .html text/html
- xml文档 .xml text/xml
- XHTML文档 .xhtml application/xhtml+xml
- 普通文本 .txt text/plain
- RTF文本 .rtf application/rtf
- PDF文档 .pdf application/pdf
- Microsoft Word文件 .word application/msword
- PNG图像 .png image/png
- GIF图形 .gif image/gif
- JPEG图形 .jpeg,.jpg image/jpeg
- au声音文件 .au audio/basic
- MIDI音乐文件 mid,.midi audio/midi,audio/x-midi
- RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio
- MPEG文件 .mpg,.mpeg video/mpeg
- AVI文件 .avi video/x-msvideo
- GZIP文件 .gz application/x-gzip
- TAR文件 .tar application/x-tar
- 任意的二进制数据 application/octet-stream
- MIME作用: 显然,MIME是定义文档,文件或字节组合格式的一种标准;
- 有了标准,客户端(如浏览器)根据MIME某种标准格式封装请求报文;
- 有了标准, 服务器根据MIME格式解析请求报文(字节流),并做处理;
2)声明文件上传的html表单元素:
<!-- html文件上传表单元素 -->
<form method="post" action="busiFileUpload.do" enctype="multipart/form-data"><table><tr><td>选择上传文件: <input name="inputFile" type="file" /></td></tr><tr><td><input type="submit" value="提交" /></td></tr></table></form>
3)文件上传请求报文封装与解析:
- 客户端浏览器根据RFC1867定义的格式或标准,对文件上传表单内容进行编码;而服务器根据RFC1867对请求报文解码,就可以获取表单提交的数据,包括上传的文件流;
- 服务器端对multipart/form-data类型的报文解析,没必要自定义实现;可以复用已有的文件上传类库,如 CommonsFileUpload
4)springmvc提供了几种文件上传类库,通过MultipartResolver接口的抽象,我们可以自行选择使用哪种文件上传类库;
【1.1】使用MultipartResolver进行文件上传
1)web.xml 配置文件上传,参见 https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#a-basic-example (搜索multipart-config)
2)java的servlet规范能够处理multipart请求,并使得mime类型(多用途互联网邮件扩展)附件可用;但需要对servlet(springmvc中的DispatcherServlet)新增配置 ,使得该servlet启用处理Multipart请求功能,包括但不限于文件上传;
- 把 <multipart-config> 子元素添加到 DispatcherServlet 配置中;( 或使用注解MultipartConfig 标注某servlet,表明该servlet启用处理multipart请求 ) 参见 https://docs.oracle.com/javaee/7/tutorial/servlets011.htm
- 注册MultipartResolver(StandardServletMultipartResolver )到springmvc容器中,bean名称一定是 multipartResolver ; 参见 https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/multipart.html
【1.2】springmvc处理multipart多部件请求流程
1)springmvc处理multipart多部件请求流程(multipart请求包括但不限于文件上传):
- 浏览器提交Multipart请求到springmvc应用;
- DispatcherServlet接收到请求后,从自身的spring web容器WebApplicationContext中找到名为multipartResolver的多组件解析器实例;(本文用的是StandardServletMultipartResolver)
- 通过multipartResolver.isMultipart(request)判断该请求是否为 multipart 请求(请求报文的mime类型是否 multipart开头);
- 若不是,则直接返回原始HttpServletRequest;
- 若是,则通过multipartResolver.resolveMultipart(request) 把request封装为StandardMultipartHttpServletRequest , (HttpServletRequest子类) ;后续所有请求都使用 StandardMultipartHttpServletRequest 进行业务逻辑处理;
- multipart请求处理完成后,DispatcherServlet会调用multipartResolver的cleanupMultipart()方法释放文件上传处理时的系统资源;
【1.3】使用springmvc上传文件代码实现(springmvc6.10版本):
【fileUpload.jsp】文件上传页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.List" import="java.util.ArrayList" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传列表</title>
</head>
<body><form method="post" action="busiFileUpload.do" enctype="multipart/form-data"><table><tr><td>选择上传文件: <input name="inputFile" type="file" /></td></tr><tr><td><input type="submit" value="提交" /></td></tr></table></form>
</body>
</html>
【web.xml】
<!-- 注册一级控制器 DispatcherServlet,用于拦截所有请求(匹配url-pattern) -->
<servlet><servlet-name>dispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- DispatcherServlet启动读取xml配置文件加载组件,构建web容器(子),通过contextConfigLocation为其配置多个xml文件--><init-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/dispatcher-servlet.xml,/WEB-INF/dispatcher-servlet2.xml,/WEB-INF/dispatcher-servlet3-upload.xml</param-value></init-param><load-on-startup>2</load-on-startup><!-- 新增multipart-config 子元素,该servlet才启用处理Multipart请求,包括文件上传(必须) --><multipart-config><!-- 当上传文件被处理或文件超过fileSizeThreshold,文件的保存路径;默认为空串 --><location>D:\temp\springmvcUploadDir</location><!-- 上传文件字节最大值,若超过则抛出异常;默认无限;我们这里设置为20M --><max-file-size>20971520</max-file-size><!-- 请求报文字节最大值,若超过则抛出异常;默认无限;我们这里设置为1000M --><max-request-size>1048576000</max-request-size><!-- 临时保存到磁盘的文件字节最小阈值;默认0 --><file-size-threshold>0</file-size-threshold></multipart-config>
</servlet>
<servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern>
</servlet-mapping>
【文件临时保存】通过调试我们发现, 文件在处理过程中会被临时保存(因为保存的阈值为0,即所有文件都被临时暂存;当然可以调整为其他值); 如下;
【dispatcher-servlet3-upload.xml】DispatcherServlet的spring容器配置文件:注册Multipart解析器到spring容器(StandardServletMultipartResolver)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册多部件请求解析器 --><bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" /><bean id="/busiFileUpload.do" class="com.tom.springmvc.controller.upload.BusiFileUploadController" /><bean id="/fileUploadPage.do" class="com.tom.springmvc.controller.upload.BusiFileUploadPageController"/>
</beans>
【BusiFileUploadController】文件上传控制器
public class BusiFileUploadController extends AbstractController {@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {if (!(request instanceof MultipartHttpServletRequest multipartRequest)) {return new ModelAndView("error");}// 类型转换MultipartFile multipartFile = multipartRequest.getFile("inputFile");if (Objects.isNull(multipartFile)) {return new ModelAndView("error");}// 保存文件流到本地String fileName = multipartFile.getOriginalFilename();String path = request.getServletContext().getRealPath("/") + fileName;BusiIOUtils.saveToDiskFile(multipartFile, path);// 返回视图ModelAndView uploadSuccMv = new ModelAndView("fileUploadSucc");uploadSuccMv.addObject("fileName", multipartFile.getOriginalFilename());uploadSuccMv.addObject("path", path);return uploadSuccMv;}
}
【BusiIOUtils.java】
/*** @description 保存到本地磁盘文件* @author admin*/
public static void saveToDiskFile(MultipartFile multipartFile, String path) throws IOException {BufferedOutputStream targetBufferedOutputStream = new BufferedOutputStream(new FileOutputStream(path));BufferedInputStream bufferedInputStream = new BufferedInputStream(multipartFile.getInputStream());byte[] bufferArr = new byte[1024];while (bufferedInputStream.read(bufferArr, 0, bufferArr.length) != -1) {targetBufferedOutputStream.write(bufferArr);}targetBufferedOutputStream.flush();targetBufferedOutputStream.close();bufferedInputStream.close();
}
【上传效果】
【2】Handler与HandlerAdaptor(处理器与处理器适配器)
【2.1】概述
1)springmvc中:任何用于web请求处理的处理对象统称为Handler处理器; Controller是处理器的一种;
2)对于DispatcherServlet来说,有个问题:DispatcherServlet应该使用什么样的Handler, 又如何调用Handler的哪个方法来处理请求?
- DispatcherServlet把Handler调用职责转交给HandlerAdapter(为什么DispatcherServlet调用HandlerAdapter,再由HandlerAdapter调用具体Handler,而不是DispatcherServlet直接调用Handler;因为Handler可以有多种,如servlet,controller;他们要适配DispatcherServlet的调用,就需要拥有相同的方法名;而因为历史原因,servlet先于controller被发明,即Handler间没有相同的方法名;即对于没有相同方法的Handler需要适配DispatcherServlet的调用(即便通过实现新增接口,可能对存量Handler有侵入性,强耦合),就需要使用适配器模式;这是适配器模式的又一应用; );
- DispatcherServlet从 HandlerMapping获取Handler后,通过HandlerAdapter#supports(handler)方法判断当前HandlerAdapter是否支持对该handler的调用;
- 返回true,则表示支持,则把handler作为参数传给 HandlerAdapter#handle()方法进行请求处理;
- 若遍历所有HandlerAdapter,所有HandlerAdapter都不支持该handler的调用,则抛出异常;
- DispatcherServlet从 HandlerMapping获取Handler后,通过HandlerAdapter#supports(handler)方法判断当前HandlerAdapter是否支持对该handler的调用;
- 想让DispatcherServlet支持新的Handler类型, 只需要提供对应的新的HandlerAdapter实现类 ;
- 如 Controller这种处理器对应的HandlerAdapter是SimpleControllerHandlerAdapter;
public interface HandlerAdapter {boolean supports(Object handler);@NullableModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;/** @deprecated */@Deprecatedlong getLastModified(HttpServletRequest request, Object handler);
}
【DispatcherServlet#doDispatch】 查找HandlerAdapter及调用handle()方法的过程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {try {try {ModelAndView mv = null;Exception dispatchException = null;// ... try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 传入请求到HanderMapping获取二级控制器(或处理器)如Controller(DispatcherServlet是一级控制器) mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}// 传入处理器获取HandlerAdapter处理器适配器 (底层调用 adapter.supports()方法,若为true,则返回adapter )HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 调用处理器适配器的handle() 方法,并获取处理结果ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}// ...... }
【getHandlerAdapter()】
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {Iterator var2 = this.handlerAdapters.iterator();while(var2.hasNext()) {HandlerAdapter adapter = (HandlerAdapter)var2.next();// 判断当前处理器适配器是否支持对该handler的调用 if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}
【2.1.1】Handler及HandlerAdapter处理web请求过程
1)我们以Controller这种handler为例,对Handler及HandlerAdapter的工作细节进行说明;(注意: Controller是二级控制器或二级处理器,DispatcherServlet是一级处理器 )
2)Controller对应的HandlerAdapter是SimpleControllerHandlerAdapter; 定义如下;
【SimpleControllerHandlerAdapter】
我想这个HandlerAdapter的代码非常简单了,不再展开赘述;逻辑是:判断当前handler是否为二级控制器类型Controller,若是,则通过SimpleControllerHandlerAdapter本身的handle方法调用handler.handleRequest()方法处理请求;
public class SimpleControllerHandlerAdapter implements HandlerAdapter {public SimpleControllerHandlerAdapter() {}public boolean supports(Object handler) {return handler instanceof Controller;}@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return ((Controller)handler).handleRequest(request, response); // 调用具体的处理器的handleRequest()方法 }public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified lastModified) {return lastModified.getLastModified(request);} else {return -1L;}}
}
【3】web请求处理拦截与HandlerInterceptor拦截器
1)拦截器: 在web请求处理过程中,新增业务拦截逻辑,如拦截切面;应用场景如前置参数校验, 报文解析与参数类型转换, 收集日志等;
2)springmvc中使用HandlerInterceptor抽象拦截器,有3个方法(preHandle, postHandle, afterCompletion) ;
- preHandle:在调用HandlerAdapter#handle()之前执行; (应用场景,如前置参数校验)
- 返回true,则继续执行后续步骤;
- 返回false, 不允许执行后续步骤; 包括HandlerInterceptor链中其他 HandlerInterceptor以及之后的Handler;
- postHandle: 在调用HandlerAdapter#handle()之后,但在视图渲染之前执行; (应用场景,如统计处理耗时)
- afterCompletion:无论是否抛出异常,该方法在请求被处理完成后都被执行;
public interface HandlerInterceptor {// 前置处理 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}// 后置处理 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}// 无论是否抛出异常,该方法在请求被处理完成后都被执行 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}
【3.1】springmvc提供的HandlerInterceptor实现类
【3.2】自定义 HandlerInterceptor(统计执行耗时)
【TimeCostHandlerInterceptor】 执行耗时统计拦截器
public class TimeCostHandlerInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {request.setAttribute("startTime" , System.currentTimeMillis());return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {Long startTime = (Long) request.getAttribute("startTime");System.out.println("执行耗时统计(单位秒)=" + (System.currentTimeMillis() - startTime) / 1000);}
}
【dispatcher-servlet3-upload.xml】注册拦截器bean-timeCostHandlerInterceptor到spring容器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!-- 注册多部件请求解析器 --><bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver" /><bean id="/busiFileUpload.do" class="com.tom.springmvc.controller.upload.BusiFileUploadController" /><bean id="/fileUploadPage.do" class="com.tom.springmvc.controller.upload.BusiFileUploadPageController"/><!-- 注册自定义处理器拦截器 --><bean id="timeCostHandlerInterceptor" class="com.tom.springmvc.handlerinterceptor.TimeCostHandlerInterceptor"/></beans>
【把拦截器装配到 HandlerMapping】 dispatcher-servlet.xml 中的HandlerMapping中装配拦截器timeCostHandlerInterceptor
<!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 -->
<bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"><property name="interceptors"><list><ref bean="timeCostHandlerInterceptor" /></list></property>
</bean>
【3.2.1】HandlerInterceptor装配
1)为什么HandlerInterceptor要在HandlerMapping装配,而不是其他组件?
【DispatcherServlet#doDispatch】
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// 根据请求processedRequest, 获取映射后的处理器mappedHandler,类型为HandlerExecutionChain,HandlerExecutionChain是一个Handler包装类, 包装了具体处理器(如Controller), HandlerInterceptor列表 </font>mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;} // 根据Handler处理器获取 HandlerAdapter处理器适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// ...... // 调用拦截器前置处理方法(拦截器)if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 调用handle处理业务逻辑,处理完后返回ModelAndView对象mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// ... applyDefaultViewName(processedRequest, mv);// 调用拦截器后置处理方法(拦截器)mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new ServletException("Handler dispatch failed: " + err, err);}// 视图渲染,且渲染完成后调用拦截器执行完成后的处理方法 (拦截器) -- 不抛异常也会调用 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); }catch (Exception ex) {// 调用拦截器执行完成后的处理方法(拦截器) -- 异常时调用triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}// 根据请求processedRequest, 获取映射后的处理器,类型为HandlerExecutionChain
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;}
【HandlerExecutionChain】处理器执行链属性定义
显然, HandlerExecutionChain是一个Handler包装类, 包装了具体处理器(如Controller), HandlerInterceptor列表 ;
public class HandlerExecutionChain {private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);private final Object handler;private final List<HandlerInterceptor> interceptorList = new ArrayList<>();private int interceptorIndex = -1;// ......
}
【DispatcherServlet#getHandler()调试时的内存信息】
1)getHandler():遍历所有HandlerMapping列表,通过request找出处理器(二级控制器, 如Controller);
- 返回类型是HandlerExecutionChain,而HandlerExecutionChain包装了具体处理器和拦截器列表;
【补充】
- AbstractHandlerMapping#getHandler()方法, 调用getHandlerExecutionChain()获取HandlerExecutionChain;
- 而 getHandlerExecutionChain()方法新建HandlerExecutionChain对象,并把AbstractHandlerMapping中adaptedInterceptors收集到HandlerExecutionChain中;
- 而adaptedInterceptor是由initInterceptors()方法遍历this.interceptors 并执行适配方法收集得到的;
- 这就是为什么要在BeanNameUrlHandlerMapping的bean注册配置信息中,装配interceptors属性,并引用timeCostHandlerInterceptor的原因 ;
【dispatcher-servlet.xml】装配拦截器到BeanNameUrlHandlerMapping
<!-- 注册HandllerMapping bean到springweb容器, BeanNameUrlHandlerMapping使用URL与Controller的bean名称进行匹配 -->
<bean id="beanNameUrlHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"><property name="interceptors"><list><ref bean="timeCostHandlerInterceptor" /></list></property>
</bean>
【3.2.2】拦截器作用范围(HandlerMapping)
1)由配置可知,拦截器的作用范围是HandlerMapping ; 如本文配置了2个HandlerMapping,包括 BeanNameUrlHandlerMapping, SimpleUrlHandlerMapping ;而只有BeanNameUrlHandlerMapping装配了timeCostHandlerInterceptor拦截器,而SimpleUrlHandlerMapping 没有;
- 所以:通过BeanNameUrlHandlerMapping找到的二级处理器,并调用该处理器时,才会有timeCostHandlerInterceptor拦截功能;而SimpleUrlHandlerMapping 没有;
【3.3】过滤器Filter
1)对web请求进行拦截,除了使用 HandlerInterceptor之外,还可以使用 Filter;
2)HandlerInterceptor与Filter过滤器区别:
- Filter是Servlet规范的标准组件, Filter在DispatcherServlet之前对servlet进行拦截(过滤);是servlet级别的拦截(过滤);
- HandlerInterceptor是在DispatcherServlet内部对handler做拦截(细粒度),包括请求处理前,请求处理后及完成后拦截;
3)Filter过滤器是一个接口,如下:
package jakarta.servlet;import java.io.IOException;public interface Filter {default public void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException;default public void destroy() {}
}
过滤器执行逻辑:
- 若请求通过拦截条件,则在 doFilter()方法中执行 chain.doFilter(request, response); 把请求透传给下一个处理步骤;
- 若不通过,则不调用 chain.doFilter,即请求处理流程终止(当然,终止请求处理时,需要封装响应报文,以提示错误信息);
【3.3.1】springmvc配置过滤器代码实践
【web.xml】注册DelegatingFilterProxy到servlet容器 【servlet容器】
<!-- 注册过滤器代理 -->
<filter><filter-name>customFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping><filter-name>customFilter</filter-name><url-pattern>/*</url-pattern> <!-- 对路径以/开头的所有servlet都进行过滤 -->
</filter-mapping>
【applicationContext.xml】注册名为customFilter的过滤器到spring容器【 spring 容器】, filter名称(customFilter)需要与DelegatingFilterProxy的filterName保持一致;
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><bean id="userAppService" class="com.tom.springmvc.model.UserAppService" /><bean id="bankCardAppService" class="com.tom.springmvc.model.bankcard.BankCardAppService" /><!-- 注册自定义过滤器 --><bean id="customFilter" class="com.tom.springmvc.filter.CustomFilter"/></beans>
【CustomFilter】过滤器定义
public class CustomFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println(request.getServletContext().getContextPath() + "CustomFilter 过滤器执行");chain.doFilter(request, response);}
}
【执行效果】
/springmvcDiscoverFirstDemo CustomFilter 过滤器执行
【3.3.2】底层原理
1)问题: 为什么要在web.xml中注册DelegatingFilterProxy ;
- DelegatingFilterProxy的作用:作为Filter的代理对象;当对请求拦截时,把拦截逻辑委派给具体的Filter(如本文中的CustomFilter);
- 物理结构上DelegatingFilterProxy在web.xml中注册,在servlet容器中;
- 而 CustomFilter 在 applicationContext.xml 中注册,在springmvc顶级WebApplicationContext容器中;
- 当然,我们讲,在web.xml中肯定可以注册CustomFilter来执行拦截逻辑;但无法装配spring容器的bean;
- 简单理解: 要把spring容器的bean装配到CustomFilter,则CustomerFilter必须注册到spring容器; 所以CustomerFilter在applicationContext.xml中注册(applicationContext.xml是springmvc顶级web容器加载的配置文件)
- 又引入新问题:把CustomFilter注册到spring的顶级web容器中,servlet容器是无法识别的;由上文可知,filter是servlet级别的拦截,又servlet容器无法识别spring容器中的CustomFilter,所以如果没有中介,servlet容器是无法调用spring容器中的CustomFilter执行过滤逻辑 ;
- 解决方法: DelegatingFilterProxy 就是连接servlet容器与spring容器的中介;DelegatingFilterProxy在web.xml中配置,注册到servlet容器,servlet容器执行DelegatingFilterProxy的doFilter()方法,doFilter方法内部根据filter名称从spring容器中取出目标filter并执行目标filter的过滤逻辑;
【注意】上述过程,在DelegatingFilterProxy#initFilterBean()方法中设置断点并调试,即可明了 ;
【4】springmvc异常处理与HandlerExceptionResolver(处理器异常解析器)
【4.1】HandlerExceptionResolver-处理器异常解析器
1)HandlerExceptionResolver定义: Handler处理器接口能够设计得如此灵活(如Handler的实现可以是servlet,也可以是Controller),除了HandlerAdapter适配器之外,还因为HandlerExceptionResolver提供的框架内统一的异常处理方式 ;
- 若handler处理请求没有异常,则handler返回ModelAndView,封装了后续处理流程要用的视图和模型数据信息;
- 若handler处理有异常,则由HandlerExceptionResolver接手处理异常 ,封装异常视图与异常提示信息到ModelAndView并返回;
public interface HandlerExceptionResolver { // 处理异常,并把处理结果封装到ModelAndView并返回 ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
2)HandlerExceptionResolver子类:
本文使用SimpleMappingExceptionResolver为例介绍HandlerExceptionResolver;
【4.2】SimpleMappingExceptionResolver
1)SimpleMappingExceptionResolver使用 Properties管理具体异常类型与所要转向的错误页面之间的映射关系;
- SimpleMappingExceptionResolver内部遍历exceptionMappings的所有元素,找出与当前抛出异常类型最接近的映射值,并将其映射值作为错误信息页面的逻辑视图名,然后封装到ModelAndView返回以供后续处理流程使用;
2)SimpleMappingExceptionResolver属性定义:
public class SimpleMappingExceptionResolver extends AbstractHandlerExceptionResolver {/** The default name of the exception attribute: "exception". */public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";@Nullableprivate Properties exceptionMappings;@Nullableprivate Class<?>[] excludedExceptions;@Nullableprivate String defaultErrorView;@Nullableprivate Integer defaultStatusCode;private final Map<String, Integer> statusCodes = new HashMap<>();@Nullableprivate String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;//...
}
3)SimpleMappingExceptionResolver属性:
- exceptionMappings: 异常类型与异常信息视图属性映射;
- defaultErrorView: 默认异常信息视图逻辑名;
- defaultStatusCode:默认状态码;
- exceptionAttribute:异常属性(前端可以通过该属性获取异常信息);
【4.2.1】SimpleMappingExceptionResolver处理异常代码实践
【applicationContext.xml】配置SimpleMappingExceptionResolver-异常处理器
<!-- 注册SimpleMappingExceptionResolver-处理器异常解析器 -->
<bean name="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="defaultErrorView" value="/error/defaultErrorPage" /><property name="exceptionAttribute" value="exceptionInfo" /><property name="exceptionMappings"><props><prop key="com.tom.springmvc.exception.TomWebException">/error/tomWebErrorPage</prop><prop key="java.lang.Exception">/error/exceptionBaseErrorPage</prop></props></property>
</bean>
【tomWebErrorPage.jsp】异常信息展示视图页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.List" import="java.util.ArrayList" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>tomWebErrorPage</title>
</head>
<body>tomWebErrorPage<p>异常信息: ${exceptionInfo}</p>
</body>
</html>
【TomWebException】自定义web异常
public class TomWebException extends RuntimeException {public TomWebException() {super();}public TomWebException(String message) {super("TomWebException-" + message);}
}
【TomWebThrowExceptionController】抛出异常控制器
public class TomWebThrowExceptionController extends AbstractController {@Overrideprotected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {if (Objects.isNull(request.getParameter("testParamKey"))) {throw new TomWebException("testParamKey查无记录");}return new ModelAndView("index");}
}
【异常处理效果】
【4.3】HandlerExceptionResolver异常处理代码调试
1)对于HandlerExceptionResolver处理器异常解析器提供的统一处理异常细节,还是需要从DispatcherServlet#doDispatch(HttpServletRequest request, HttpServletResponse response)说起;
【DispatcherServlet#doDispatch()】web请求处理入口
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 校验是否multipart请求(包括但不限于文件上传请求)processedRequest = checkMultipart(request);// 获取异常处理器,类型为HandlerExecutionChain,它是一个包装器,封装了实际的二级处理器(如Controller)与拦截器列表 mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 根据实际的二级处理器获取处理器适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// ... // 拦截器前置处理if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 实际调用二级处理器的处理方法,二级处理器也就是本文定义的TomWebThrowExceptionControllermv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);// 拦击器后置处理mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {// 若二级处理器在处理过程中抛出异常,则在这里被捕获,赋值给dispatchExceptiondispatchException = ex; }catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new ServletException("Handler dispatch failed: " + err, err);}// 无论是否抛出异常,都执行processDispatchResult()进行后续处理// 若处理逻辑成功,则dispatchException=null;若抛出异常,则dispatchException就是实际的业务异常 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {// 请求处理完成后触发拦截器afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler, ex); }catch (Throwable err) {// 请求处理完成后触发拦截器afterCompletion方法triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}
由上文可知, 二级控制器(或二级处理器,Controller)抛出异常,被捕获后,把异常对象作为入参,调用processDispatchResult方法;
【DispatcherServlet#processDispatchResult()】加工二级控制器的请求处理结果(主要包括处理异常,视图渲染,再执行处理结束的拦截器方法)
// 若二级控制器抛出异常,则exception不为空;若处理流程成功,则exception为null
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) { // 异常则进入这个分支if (exception instanceof ModelAndViewDefiningException mavDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = mavDefiningException.getModelAndView();}else { // 有异常,类型为TomWebException,非ModelAndViewDefiningException类型,进入这个分支 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?// 视图渲染(本文不展开)if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}// ...if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null); // 触发执行拦截器的处理结束方法}
}
由上文可知,本文抛出类型为TomWebException的异常,非ModelAndViewDefiningException类型,执行processHandlerException(request, response, handler, exception);
【DispatcherServlet#processHandlerException()】加工处理器异常(调用处理器异常解析器)
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// ...// Check registered HandlerExceptionResolvers...// 遍历注册的HandlerExceptionResolver (本文在applicationContext.xml注册的SimpleMappingExceptionResolver)ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 遍历处理器异常解析器列表,并调用其resolveException方法,获取处理器异常解析器根据异常封装的ModelAndView;// 异常解析器按照Ordered语义排序(值越小,优先级越高) exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {if (exMv.isEmpty()) {request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}// We might still need view name translation for a plain error model...// 若没有视图,则使用默认视图 if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}// ... WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv; }throw ex;
}
由上文可知;异常处理的传播链如下:
- DispatcherServlet#doDispatch():web请求处理入口 ;
- handlerAdapter.handle(processedRequest, response, mappedHandler.getHandler()): DispatcherServlet调用处理器适配器的handle方法执行实际处理器的业务处理逻辑(业务处理逻辑抛出异常);
- DispatcherServlet#processDispatchResult():加工二级控制器的请求处理结果(主要包括处理异常,视图渲染,再执行处理结束的拦截器方法);
- DispatcherServlet#processHandlerException():加工处理器异常(调用处理器异常解析器);
- HandlerExceptionResolver#resolveException(request, response, handler, ex): 处理器异常解析器解析异常,返回解析后的封装了异常信息的ModelAndView对象 ; (因SimpleMappingExceptionResolver继承自AbstractHandlerExceptionResolver,实际调用的是AbstractHandlerExceptionResolver#resolveException)
【AbstractHandlerExceptionResolver#resolveException】处理器异常解析器解析异常方法
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);// 调用doResolveException() 方法解析异常; 调用子类的调用SimpleMappingExceptionResolver#doResolveException()ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {// ... }return result;}else {return null;}
}
【SimpleMappingExceptionResolver#doResolveException()】处理器异常解析器解析异常方法
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {// Expose ModelAndView for chosen error view.String viewName = determineViewName(ex, request);if (viewName != null) {// Apply HTTP status code for error views, if specified.// Only apply it if we're processing a top-level request.Integer statusCode = determineStatusCode(request, viewName); // 获取响应码 if (statusCode != null) {applyStatusCodeIfPossible(request, response, statusCode);}// 获取ModelAndView对象 return getModelAndView(viewName, ex, request);}else {return null;}
}protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {return getModelAndView(viewName, ex);}// 封装视图名与异常对象到ModelAndView,并返回 protected ModelAndView getModelAndView(String viewName, Exception ex) {ModelAndView mv = new ModelAndView(viewName);if (this.exceptionAttribute != null) {mv.addObject(this.exceptionAttribute, ex);}return mv;}
【SimpleMappingExceptionResolver#getModelAndView()】