vlambda博客
学习文章列表

源码解读之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 { @Nullable 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则不用考虑这些小问题

<!DOCTYPE html><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

    // params    private 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进入if      if ("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_METHODS              if (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+o @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return HandlerInterceptor.super.preHandle(request, response, handler); }
@Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); }
@Override 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, @Nullable 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