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
结尾
其他类型的实现,可以自行实现。