問題
接上一篇:SpringMVC異常統一處理並返回數據或視圖View
我們知道SpringMVC可以通過攔截器處理preHandle,用來提前攔截權限、攔截登錄,攔截很多業務邏輯。但是這個preHandle是沒有Controller的入參的,因爲org.springframework.web.servlet.DispatcherServlet#doDispatch
的實現中,調用preHandle後才執行Controller方法,這個時候纔會有參數。
那麼,如果需要這個參數,怎麼辦呢?
解決問題
思路
方案一
通過跟蹤org.springframework.web.servlet.DispatcherServlet#doDispatch
,發現一直執行到org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
是,才能獲取到Controller的入參,這個入參的獲取會經過MessageConverter、DataBinder,因此一定不能從外面獲取參數,否則其他配置都失效了,那麼就只能在這個位置擴展,也就是自己實現InvocableHandlerMethod
。
通過查看代碼發現是RequestMappingHandlerAdapter創建的InvocableHandlerMethod,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#createInvocableHandlerMethod
,而且是return new ServletInvocableHandlerMethod(handlerMethod);
,那麼就只能自己實現RequestMappingHandlerAdapter
進行適配。
方案二
直接通過Aspect切面攔截Controller處理,理解上更簡單一些。
解決方案
前提
- SpringBoot環境下(我這裏是2.1.6,和之前的2.2.2沒什麼區別)
- 不要使用{@link org.springframework.web.servlet.config.annotation.EnableWebMvc},由SpringBoot自動裝配{@link org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration}
- 如果是@EnableWebMvc,那麼可以參考以下WebMvcAutoConfiguration的方式進行擴展
步驟
WebMvcAutoConfiguration.EnableWebMvcConfiguration#EnableWebMvcConfiguration(ObjectProvider\<WebMvcProperties\> mvcPropertiesProvider, ObjectProvider\<WebMvcRegistrations\> mvcRegistrationsProvider, ListableBeanFactory beanFactory)
可以提供一個ObjectProvider,第二個參數是WebMvcRegistrations
,可以提供一個RequestMappingHandlerAdapter
,那麼就通過這裏擴展。- 創建一個
WebMvcRegistrations
的實現類,並提供自定義的RequestMappingHandlerAdapter
- 覆蓋
RequestMappingHandlerAdapter#createInvocableHandlerMethod
方法,返回自定義的ServletInvocableHandlerMethod
- 創建一個自定義的擴展類
ExtendServletInvocableHandlerMethod
,擴展ServletInvocableHandlerMethod
,覆蓋invokeForRequest
方法,在其中增加攔截器操作 - 自定義新的攔截器(帶Controller入參的攔截器),同時實現SpringMVC原始攔截器接口
HandlerInterceptor
,將這個攔截器也註冊到SpringMVC中,既可以使用攔截器的各種功能,又可以執行帶參數的攔截方法。注:在HandlerInterceptor的preHandle中寫requestAttribute,在新攔截器的preHandle裏面判斷並執行,即可實現在這個位置進行攔截(這裏邏輯比較複雜,具體看下面的實現)
擴展RequestMappingHandlerAdapter
package com.xxxxx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 唯一的配置{@link RequestMappingHandlerAdapter},增加一個擴展攔截器,獲取到參數之後,執行controller之前
*
* @author obiteaaron
* @see WebMvcAutoConfiguration.EnableWebMvcConfiguration#EnableWebMvcConfiguration(org.springframework.beans.factory.ObjectProvider, org.springframework.beans.factory.ObjectProvider, org.springframework.beans.factory.ListableBeanFactory)
* @since 2020/1/7
*/
@Component
public class WebMvcRegistrationsInterceptorExtend implements WebMvcRegistrations {
@Autowired
private ExtendHandlerInterceptor extendHandlerInterceptor;
@Override
public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return new RequestMappingHandlerAdapter() {
@Override
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
return super.invokeHandlerMethod(request, response, handlerMethod);
}
@Override
protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
return new ExtendServletInvocableHandlerMethod(handlerMethod, extendHandlerInterceptor);
}
};
}
/**
* 自定義方法處理器,可以在執行時攔截到參數
*/
public static class ExtendServletInvocableHandlerMethod extends ServletInvocableHandlerMethod {
private ExtendHandlerInterceptor extendHandlerInterceptor;
public ExtendServletInvocableHandlerMethod(Object handler, Method method) {
super(handler, method);
}
public ExtendServletInvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
}
public ExtendServletInvocableHandlerMethod(HandlerMethod handlerMethod, ExtendHandlerInterceptor extendHandlerInterceptor) {
super(handlerMethod);
this.extendHandlerInterceptor = extendHandlerInterceptor;
}
/**
* copy from super
*/
@Override
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
// 由於springMVC攔截器並沒有參數,這裏提供帶參數的攔截器(當然,用AOP代理Controller也是一種實現方式)
boolean interceptor = doInterceptor(request.getNativeRequest(HttpServletRequest.class), request.getNativeRequest(HttpServletResponse.class), args);
if (!interceptor) {
return null;
}
return doInvoke(args);
}
private boolean doInterceptor(HttpServletRequest request, HttpServletResponse response, Object[] args) throws Exception {
return extendHandlerInterceptor.handle(request, response, this, args);
}
}
/**
* 依然使用攔截器做,這樣可以註冊到Spring中,可以使用攔截器的包含、排除功能。
* 這個攔截器,既要配置到ExtendServletInvocableHandlerMethod中,也要配置到SpringMVC的攔截器中。
*/
public interface ExtendHandlerInterceptor extends HandlerInterceptor {
@Override
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
setNeedInterceptor(request);
return true;
}
default boolean handle(HttpServletRequest request, HttpServletResponse response, ExtendServletInvocableHandlerMethod extendServletInvocableHandlerMethod, Object[] args) throws Exception {
try {
if (isNeedInterceptor(request)) {
return preHandle(request, response, extendServletInvocableHandlerMethod, args);
}
return true;
} finally {
clearInterceptor(request);
}
}
/**
* 這裏如果錯誤,直接拋出異常
*
* @see #isNeedInterceptor(HttpServletRequest)
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Object[] args) throws Exception;
/**
* 攔截器處,僅包含寫入攔截信息
*/
default void setNeedInterceptor(HttpServletRequest request) {
request.setAttribute("NEED_INTERCEPTOR", true);
}
/**
* 如果有攔截信息,才進行攔截
*/
default boolean isNeedInterceptor(HttpServletRequest request) {
return Boolean.TRUE.equals(request.getAttribute("NEED_INTERCEPTOR"));
}
/**
* 使用完成後刪除
*/
default void clearInterceptor(HttpServletRequest request) {
request.removeAttribute("NEED_INTERCEPTOR");
}
}
}
攔截器擴展
package com.xxxxx;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author obiteaaron
* @since 2020/1/7
*/
public class ExtendInterceptor extends BaseInterceptor implements WebMvcRegistrationsInterceptorExtend.ExtendHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return WebMvcRegistrationsInterceptorExtend.ExtendHandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler, Object[] args) throws Exception {
boolean checkResult = ((ExtendInterceptorPreHandler) interceptorPreHandler).check(request, response, handler, args);
if (!checkResult) {
postInterceptor(request, response, handler);
return false;
} else {
return true;
}
}
@Override
public void setInterceptorPreHandler(InterceptorPreHandler interceptorPreHandler) {
throw new UnsupportedOperationException("");
}
public void setExtendInterceptorPreHandler(ExtendInterceptorPreHandler extendInterceptorPreHandler) {
this.interceptorPreHandler = extendInterceptorPreHandler;
}
public interface ExtendInterceptorPreHandler extends InterceptorPreHandler {
@Override
default boolean check(HttpServletRequest request, HttpServletResponse response, Object handler) {
throw new UnsupportedOperationException("");
}
/**
* @see WebMvcRegistrationsInterceptorExtend.ExtendHandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Object[])
*/
boolean check(HttpServletRequest request, HttpServletResponse response, Object handler, Object[] args) throws Exception;
/**
* 攔截後返回的視圖名稱
*
* @see ModelAndView
* @see ViewNameMethodReturnValueHandler
*/
String getViewName();
/**
* 攔截後返回的對象
*
* @see ResponseBody
* @see RequestResponseBodyMethodProcessor
*/
Object getResponseBody();
}
}
使用方法
- 自己實現ExtendInterceptorPreHandler攔截實現
- 將這個攔截器註冊到SpringMVC中,通過ExtendInterceptor註冊(繼承自BaseInterceptor,見上一篇文章),其他普通攔截器註冊方式一樣
- 確保使用的是SpringBoot的SpringMVC初始化方式,不要直接使用SpringMVC的(不要使用@EnableWebMvc)