源码解读之SpringMVC(一)
内容简介:
1.CharacterEncodingFilter
2.HiddenHttpMethodFilter
3. HandlerInterceptor
一、CharacterEncodingFilter
(i)用途介绍
主要用途简单理解为解决中文乱码,该过滤器允许指定字符集处理用户请求或者响应。
(ii)直观使用
在web.xml配置文件添加如下代码
<filter><filter-name>char-filter</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><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>char-filter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
目的:request和response字符集设置为'UTF-8'
注:filter/servlet都需要mapping来指定过滤成分/“路由”
(iii)理解性使用
需要进入源码(Spring Web MVC » 5.3.1)
org.springframework.web.filter.CharacterEncodingFilter
思路:既然是过滤器,肯定需要找与doFilter相关内容
public class CharacterEncodingFilter extends OncePerRequestFilter {private String encoding;private boolean forceRequestEncoding;private boolean forceResponseEncoding;protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String encoding = this.getEncoding();if (encoding != null) {/**request.getCharacterEncoding() == null*本条件在无修改情况为空,所以条件成立能进入到if*request.setCharacterEncoding(encoding)*通过servlet原生api,设置encoding*/if (this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {request.setCharacterEncoding(encoding);}/** 对response设置encoding需要满足forceResponseEncoding是true*/if (this.isForceResponseEncoding()) {response.setCharacterEncoding(encoding);}}// 进行请求/响应的放行filterChain.doFilter(request, response);}public boolean isForceRequestEncoding() {return this.forceRequestEncoding;}public boolean isForceResponseEncoding() {return this.forceResponseEncoding;}}
综上所述,当前我们需要在web.xml 创建包含参数encoding(为了request)以及包含参数forceResponseEncoding (为了response)的 filter
<init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param><init-param><param-name>forceResponseEncoding</param-name><param-value>true</param-value></init-param>
二、HiddenHttpMethodFilter
(i) 用途
当浏览器只支持GET/POST的请求时,能使用它发出且不局限于PUT/DELETE
(ii)详细用法
1.首先需要在web.xml添加对应过滤器
<!--配置HiddenHttpMethodFilter过滤器--><filter><filter-name>hiddenHttpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class></filter><filter-mapping><filter-name>hiddenHttpMethodFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>
2.以更改举例(PUT),代码略去了bootstrap&js
没有以DELETE举例是因为单纯的以表单形式需要结合Vue来创造点击事件以及需要取消元素默认行为,但是如果使用Ajax则不用考虑这些小问题
<html lang="en" xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><title>update</title></head><body><div id="app"><div class="container"><div class="row"><div class="col-md-12"><h1>Update</h1></div></div><br><div class="row"><div class="col-md-12"><form th:action="@{/users}" method="post"><input type="hidden" name="_method" value="put"><input type="hidden" th:value="${user.userId}"><div class="form-group"><label th:for="userId">Current ID <i>(You cannot change your ID) </i></label><input type="text" class="form-control" id="userId" name="userId" th:value="${user.userId}" readonly></div><br><div class="form-group"><label th:for="userName">New Name</label><input type="text" class="form-control" th:id="userName" name="userName" th:value="${user.userName}" placeholder="please enter new Username"></div><br><div class="form-group"><label th:for="userPassword">New Password</label><input type="password" class="form-control" th:id="userPassword" name="userPassword" th:value="${user.userPassword}" placeholder="please enter new password"></div><br><button type="submit" class="btn btn-primary col-md-1 col-md-offset-5" >Submit</button><br><a href="/users" >Cancel</a></form></div></div></div></div></body>
显而易见,在表单中添加了隐藏元素
<input type="hidden" name="_method" value="put">
这样就可以做到除了GET/POST以外的请求了
(iii)为什么添加此隐藏元素就能实现功能?
需要进入源码(Spring Web MVC » 5.3.1)
org.springframework.web.filter.HiddenHttpMethodFilter
同样地,需要找doFilter
// paramsprivate static final List<String> ALLOWED_METHODS;public static final String DEFAULT_METHOD_PARAM = "_method";private String methodParam = "_method";
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {HttpServletRequest requestToUse = request;// 通过表单的POST进入ifif ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {// 获取name为 "_method"的参数(应为 PUT)String paramValue = request.getParameter(this.methodParam);// 确保"_method"的参数存在if (StringUtils.hasLength(paramValue)) {// 将"_method"的参数转成大写并本地化String method = paramValue.toUpperCase(Locale.ENGLISH);// 检查"_method"的参数存在于ALLOWED_METHODSif (ALLOWED_METHODS.contains(method)) {// 将请求重新包装,成为以ALLOWED_METHODS内对应请求方法的请求requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);}}}// 放行filterChain.doFilter((ServletRequest)requestToUse, response);}
ALLOWED_METHODS =
Collections.unmodifiableList(
Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.PATCH.name()));
三、HandlerInterceptor
(i)用途
拦截器
拦截器是Spring MVC中强大的控件,它可以在进入处理器之前做一些操作,或者在处理器完成后进行操作,甚至是在渲染视图后进行操作
Introduction to Spring MVC HandlerInterceptor
https://www.baeldung.com/spring-mvc-handlerinterceptor
(ii)How to use?
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
<bean class="package.MyInterceptor"></bean><ref bean="myInterceptor"></ref><!-- 以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截 --><mvc:interceptor><!-- ?一个字符*任意个字符**任意层数 --><mvc:mapping path="/**"/><!-- exclude --><mvc:exclude-mapping path="/test"/><ref bean="myInterceptor"></ref></mvc:interceptor>
实现HandlerInterceptor
public class MyInterceptor implements HandlerInterceptor {// ctl+opublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return HandlerInterceptor.super.preHandle(request, response, handler);}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}}
HandlerInterceptor interface
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 {}}
SpringMVC中的拦截器有三个抽象方法:
1. preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
2. postHandle:控制器方法执行之后执行postHandle()
3. afterCompletion:处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
(iii)源码解读
需要进入源码(Spring Web MVC » 5.3.1)
为什么要进这儿?Debug打断点找Frames
从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 {try {ModelAndView mv = null;Object dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}
本文只专注于pre的逻辑
因为如果整合并且关注其他点需要基本说明整个DispatchServlet...
希望下一篇关于springMVC的文章能码上所有前端控制器的内容orz
// mappedHandler.applyPreHandle返回false才能拦截(真正的拦截)if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}
点击进入applyPreHandle
/* private final Object handler;*private final List<HandlerInterceptor> interceptorList;*private int interceptorIndex;*/boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {// 转型HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);// 这就是接口中的preHandle!!// preHandle为false的时候才会让applyPreHandle返回false(目的)if (!interceptor.preHandle(request, response, this.handler)) {this.triggerAfterCompletion(request, response, (Exception)null);return false;}}return true;}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {for(int i = this.interceptorList.size() - 1; i >= 0; --i) {HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);interceptor.postHandle(request, response, this.handler, mv);}}
注:拦截器的执行顺序从此也不难看出,pre的执行为1-2-3,post的执行为3-2-1。applyPostHandle像stack的感觉,因为之前是先进,所以是后出,但是跟这个根本不搭边际,因为本身就是简单的i++和 --i 导致的
23 April 2022
