SpringMVC攔截器HandlerInterceptor攔截後返回數據或視圖View

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可以從容器中獲取到。這裏我們主要用到的只有兩個RequestResponseBodyMethodProcessorViewNameMethodReturnValueHandler

返回純數據

返回純數據,適用於返回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

結尾

其他類型的實現,可以自行實現。

發佈了91 篇原創文章 · 獲贊 73 · 訪問量 68萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章