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版本: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,因此可以用下面的方法實現。
SpringBoot下注冊攔截器:org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addInterceptors
結尾
其他類型的實現,可以自行實現。