SpringBoot版本:2.1.6.RELEASE
SpringMVC版本:5.1.8.RELEASE
SpringMVC攔截器
比如說在SpringMVC Web環境下,需要實現一個權限攔截的功能,一般情況下,大家都是實現了org.springframework.web.servlet.AsyncHandlerInterceptor
或者org.springframework.web.servlet.HandlerInterceptor
接口,從而實現的SpringMVC攔截。而要實現攔截功能,通常都是通過preHandle
方法返回false攔截。
攔截器的preHandle
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return false;
}
那麼攔截後,如果你什麼都不做,會直接返回空頁面,頁面上什麼也沒有。如果要返回結果,需要自己給response寫數據。簡單的寫法網上很多,這裏不贅述,這裏將會講解如何通過SpringMVC本身的處理機制在攔截後返回結果。
攔截後返回結果
攔截後返回數據通常是兩種,第一種如果是返回的Restful接口,那麼返回一個json數據即可,第二種如果返回的是一個頁面,那麼需要返回錯誤頁面(比如無權限頁面)。
SpringMVC的所有結果都是通過HandlerMethodReturnValueHandler
接口的實現類返回的,無論是Json,還是View。因此可以通過具體的實現類返回我們想要的數據。與之對應的還有``,這些所有的參數和返回結果的處理器,都定義在RequestMappingHandlerAdapter
中,這個Adapter可以從容器中獲取到。這裏我們主要用到的只有兩個RequestResponseBodyMethodProcessor
和ViewNameMethodReturnValueHandler
。
返回純數據
返回純數據,適用於返回Controller的方法通過@ResponseBody
標註了。因此需要用到RequestResponseBodyMethodProcessor
。
RequestResponseBodyMethodProcessor
裏面對不同的數據會有不同的處理方式,一般都是處理爲json,具體實現可以看HttpMessageConverter
的實現類。這裏是直接將結果寫到了response中。實現代碼在文末。
返回視圖
返回視圖,適用於返回Controller的方法通過是個String,其實是ViewName。因此需要用到ViewNameMethodReturnValueHandler
。
通過查看DispatcherServlet
代碼會發現,其實preHandle方法執行在RequestMappingHandlerAdapter
執行前,所以沒有ModelAndView生成,因此需要自己向Response裏面寫數據。這裏只是藉助了RequestMappingHandlerAdapter
生產需要寫入的數據。然後通過拋出異常ModelAndViewDefiningException
,從而將我們的生產的ModeAndView透出給Spring進行渲染DispatcherServlet#processDispatchResult
。
實現代碼在文末。
直接使用視圖解析器方法
如果你知道自己的視圖解析器是誰,那麼還有一個方法,比如,我用的是Velocity的視圖解析器,Velocity的視圖解析器配置的beanName是velocityViewResolver,因此可以用下面的方法實現。
String viewName = "yourViewName";
UrlBasedViewResolver viewResolver = (UrlBasedViewResolver) SpringContextHolder.getBean("velocityViewResolver");
View view = viewResolver.resolveViewName(viewName, Locale.CHINA);
if (view == null) {
throw new RuntimeException("cannot find " + viewName + " view.");
}
view.render(null, request, response);
完整代碼實現
package com.xxxx.interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.ModelAndViewDefiningException;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
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;
import java.util.Objects;
/**
* 基礎攔截器,通過@Configuration自行配置爲Bean,可以配置成多個攔截器。
*
* @author obiteaaron
* @since 2019/12/26
*/
public class BaseInterceptor implements AsyncHandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseInterceptor.class);
private ApplicationContext applicationContext;
protected InterceptorPreHandler interceptorPreHandler;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean checkResult = interceptorPreHandler.check(request, response, handler);
if (!checkResult) {
postInterceptor(request, response, handler);
return false;
} else {
return true;
}
}
/**
* 攔截後處理
*/
protected void postInterceptor(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果被攔截,返回信息
if (((HandlerMethod) handler).getMethodAnnotation(ResponseBody.class) != null) {
// 返回json
HandlerMethod handlerMethod = new HandlerMethod(((HandlerMethod) handler).getBean(), ((HandlerMethod) handler).getMethod());
Object returnValue = interceptorPreHandler.getResponseBody();
MethodParameter returnValueType = handlerMethod.getReturnValueType(returnValue);
applicationContext.getBean(RequestMappingHandlerAdapter.class).getReturnValueHandlers();
RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = findRequestResponseBodyMethodProcessor();
requestResponseBodyMethodProcessor.handleReturnValue(returnValue, returnValueType, new ModelAndViewContainer(), new ServletWebRequest(request, response));
// end
} else {
// 返回頁面
HandlerMethod handlerMethod = new HandlerMethod(((HandlerMethod) handler).getBean(), ((HandlerMethod) handler).getMethod());
String viewName = interceptorPreHandler.getViewName();
MethodParameter returnValueType = handlerMethod.getReturnValueType(viewName);
ViewNameMethodReturnValueHandler viewNameMethodReturnValueHandler = findViewNameMethodReturnValueHandler();
ModelAndViewContainer modelAndViewContainer = new ModelAndViewContainer();
// viewNameMethodReturnValueHandler 內的實現非常簡單,其實可以不用這個的,直接new ModelAndViewContainer()就好了。
viewNameMethodReturnValueHandler.handleReturnValue(viewName, returnValueType, modelAndViewContainer, new ServletWebRequest(request, response));
// 拋出異常由Spring處理
ModelMap model = modelAndViewContainer.getModel();
ModelAndView modelAndView = new ModelAndView(modelAndViewContainer.getViewName(), model, modelAndViewContainer.getStatus());
throw new ModelAndViewDefiningException(modelAndView);
// end
}
}
private RequestResponseBodyMethodProcessor findRequestResponseBodyMethodProcessor() {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
for (HandlerMethodReturnValueHandler value : Objects.requireNonNull(requestMappingHandlerAdapter.getReturnValueHandlers())) {
if (value instanceof RequestResponseBodyMethodProcessor) {
return (RequestResponseBodyMethodProcessor) value;
}
}
// SpringMVC的環境下一定不會走到這裏
throw new UnsupportedOperationException("cannot find RequestResponseBodyMethodProcessor from RequestMappingHandlerAdapter by Spring Context.");
}
private ViewNameMethodReturnValueHandler findViewNameMethodReturnValueHandler() {
RequestMappingHandlerAdapter requestMappingHandlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
for (HandlerMethodReturnValueHandler value : Objects.requireNonNull(requestMappingHandlerAdapter.getReturnValueHandlers())) {
if (value instanceof ViewNameMethodReturnValueHandler) {
return (ViewNameMethodReturnValueHandler) value;
}
}
// SpringMVC的環境下一定不會走到這裏
throw new UnsupportedOperationException("cannot find ViewNameMethodReturnValueHandler from RequestMappingHandlerAdapter by Spring Context.");
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void setInterceptorPreHandler(InterceptorPreHandler interceptorPreHandler) {
this.interceptorPreHandler = interceptorPreHandler;
}
public interface InterceptorPreHandler {
/**
* @see HandlerInterceptor#preHandle(HttpServletRequest, HttpServletResponse, Object)
*/
boolean check(HttpServletRequest request, HttpServletResponse response, Object handler);
/**
* 攔截後返回的視圖名稱
*
* @see ModelAndView
* @see ViewNameMethodReturnValueHandler
*/
String getViewName();
/**
* 攔截後返回的對象
*
* @see ResponseBody
* @see RequestResponseBodyMethodProcessor
*/
Object getResponseBody();
}
}
SpringBoot下注冊攔截器:org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addInterceptors
結尾
其他類型的實現,可以自行實現。