扩展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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章