擴展SpringMVC解決攔截器Interceptor.preCheck沒有Controller入參的問題

問題

接上一篇: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處理,理解上更簡單一些。

解決方案

前提

  1. SpringBoot環境下(我這裏是2.1.6,和之前的2.2.2沒什麼區別)
  2. 不要使用{@link org.springframework.web.servlet.config.annotation.EnableWebMvc},由SpringBoot自動裝配{@link org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration}
  3. 如果是@EnableWebMvc,那麼可以參考以下WebMvcAutoConfiguration的方式進行擴展

步驟

  1. WebMvcAutoConfiguration.EnableWebMvcConfiguration#EnableWebMvcConfiguration(ObjectProvider\<WebMvcProperties\> mvcPropertiesProvider, ObjectProvider\<WebMvcRegistrations\> mvcRegistrationsProvider, ListableBeanFactory beanFactory)可以提供一個ObjectProvider,第二個參數是WebMvcRegistrations,可以提供一個RequestMappingHandlerAdapter,那麼就通過這裏擴展。
  2. 創建一個WebMvcRegistrations的實現類,並提供自定義的RequestMappingHandlerAdapter
  3. 覆蓋RequestMappingHandlerAdapter#createInvocableHandlerMethod方法,返回自定義的ServletInvocableHandlerMethod
  4. 創建一個自定義的擴展類ExtendServletInvocableHandlerMethod,擴展ServletInvocableHandlerMethod,覆蓋invokeForRequest方法,在其中增加攔截器操作
  5. 自定義新的攔截器(帶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();
    }
}

使用方法
  1. 自己實現ExtendInterceptorPreHandler攔截實現
  2. 將這個攔截器註冊到SpringMVC中,通過ExtendInterceptor註冊(繼承自BaseInterceptor,見上一篇文章),其他普通攔截器註冊方式一樣
  3. 確保使用的是SpringBoot的SpringMVC初始化方式,不要直接使用SpringMVC的(不要使用@EnableWebMvc)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章