源码解读之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
// 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
public 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