美文网首页
springmvc比HandlerInterceptor更靠近目

springmvc比HandlerInterceptor更靠近目

作者: 二哈_8fd0 | 来源:发表于2023-11-06 14:19 被阅读0次
    springmvc中常见的请求拦截器
    1. javax.servlet.Filter 的实现(默认tomcat的实现)
    2. org.springframework.web.servlet.HandlerInterceptor
      那么Filter是在更外层,也就是在请求目标方法之前先执行Filter的拦截器,再执行HandlerInterceptor
      那么当这两个拦截器都不满足某些场景,而这个场景又想要更靠近目标方法执行时机的时候,就想要找到一个更靠近请求目标方法的扩展点,下述为寻找思路。

    首先从HandlerInterceptor#preHandle入手

    直接看这个方法由谁调用,如果没有下载对应源码包,可以通过debug查看


    直接找到堆栈 可以看到preHandle和postHandle的执行
    // 将要调用目标方法
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    

    跟踪堆栈后进入AbstractHandlerMethodAdapter#handle ,并且当前实例是RequestMappingHandlerAdapter


    AbstractHandlerMethodAdapter#handle

    继续跟踪


    AbstractHandlerMethodAdapter#handleInternal

    继续跟踪


    RequestMappingHandlerAdapter#invokeHandlerMethod

    然后我们可以看到执行目标方法 又委托给了ServletInvocableHandlerMethod

    ServletInvocableHandlerMethod#invokeAndHandle
    然后委托给invokeForRequest方法 再到doInvoke,走到这里我们已经看到invokeForRequest中的逻辑,先通过
    getMethodArgumentValues获取到目标方法的参数,然后真正的执行doInvoke方法带上了参数,可以预计到doInvoke方法已经是非常靠近执行目标方法了,所有准备工作已经完成,参数也构造完了,并且所有方法public,protect也都没有final,也就是这些方法都可以被重写!
    如果是重写的话doInvoke是一个比较好的时机,因为目标方法的参数也构造完毕。并且protect还不带final真的太像是设计为被重写的方法一样。。。
        @Nullable
        public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                Object... providedArgs) throws Exception {
    
            Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
            if (logger.isTraceEnabled()) {
                logger.trace("Arguments: " + Arrays.toString(args));
            }
            return doInvoke(args);
        }
    
        @Nullable
        protected Object doInvoke(Object... args) throws Exception {
            Method method = getBridgedMethod();
            try {
                if (KotlinDetector.isSuspendingFunction(method)) {
                    return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
                }
                return method.invoke(getBean(), args);
            }
            catch (IllegalArgumentException ex) {
                assertTargetBean(method, getBean(), args);
                String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
                throw new IllegalStateException(formatInvokeError(text, args), ex);
            }
            catch (InvocationTargetException ex) {
                // Unwrap for HandlerExceptionResolvers ...
                Throwable targetException = ex.getTargetException();
                if (targetException instanceof RuntimeException) {
                    throw (RuntimeException) targetException;
                }
                else if (targetException instanceof Error) {
                    throw (Error) targetException;
                }
                else if (targetException instanceof Exception) {
                    throw (Exception) targetException;
                }
                else {
                    throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
                }
            }
        }
    

    如上述重写 doInvoke那么我们需要能够继承到当前类InvocableHandlerMethod
    突然又想到了上面debug过程中看到了构建InvocableHandlerMethod这一行代码如下

        @Nullable
        protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    
            ServletWebRequest webRequest = new ServletWebRequest(request, response);
            try {
                WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
                ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    // 如果经常阅读源码,上面所描述的 doInvoke是protect非final并且这里又构建了对应实例,这就很明显了写代码的人就是这样设计的,继续进入这个方法看个究竟
                ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    // 省略代码 ...
    

    那么我们进入到 createInvocableHnadlerMethod方法查看

    // 看到这里,继续是protect,非final,方法名,以及这简单粗暴的return一个new 实例,坚定了前面的所有想法!
        protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
            return new ServletInvocableHandlerMethod(handlerMethod);
        }
    
    

    到这里我们已经非常确定以及肯定,doInvoke 以及ServletInvocableHandlerMethod 实例获取方法createInvocableHandlerMethod就是被设计为让我们可以扩展的一系列操作

    当前实例为RequestMappingHandlerAdapter,那么我们就需要继承它来重写RequestMappingHandlerAdapter#createInvocableHandlerMethod就可以返回一个我们自己的ServletInvocableHandlerMethod来重写ServletInvocableHandlerMethod#doInvoke

    查找RequestMappingHandlerAdapter实例如果被创建

    还是两种方式,如果下载了对应的包源码直接ctrl点击,或者debug到。

    // 很明显的一点是这个类是启动时就实例化了,InitializingBean这个接口是spring的bean注入过程中的钩子之一(大部分带有spring钩子的类都是启动时注入spring容器,真的很少很少见动态注入)
    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
            implements BeanFactoryAware, InitializingBean
    

    我们直接debug构造方法


    RequestMappingHandlerAdapter 构造方法

    直接来到 WebMvcConfigurationSupport#createRequestMappingHandlerAdapter
    和上面的那些方法一样,protect,非final,并且简单粗暴,看起来就是让我们重写的啊,那么我们就先切入这个点是否可以重写


    WebMvcConfigurationSupport
    上面忘了说一点,最主要的是要看每个类的注释,尤其是类上面的注释,多多少少可以理解作者的设计意图,以及相关联的其他类
    WebMvcConfigurationSupport

    上图可以看到一个非常熟悉的东西,WebMvcConfigurer springmvc的各种扩展点入口。既然WebMvcConfigurationSupport标准了可能有所关联,先看一眼。结果就是,没啥可以重写WebMvcConfigurationSupport的迹象。那么我们换个思路

    如果你有下载了很多包的源码可以看到 WebMvcConfigurationSupport#createRequestMappingHandlerAdapter已经被重写了


    image.png

    或者找到堆栈的上一个方法调用


    WebMvcAutoConfiguration
            @Override
            protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
    // 这里我们眼前一亮,终于找到了真正的扩展点!
                if (this.mvcRegistrations != null) {
                    RequestMappingHandlerAdapter adapter = this.mvcRegistrations.getRequestMappingHandlerAdapter();
                    if (adapter != null) {
                        return adapter;
                    }
                }
    // 这里就是WebMvcConfigurationSupport#createRequestMappingHandlerAdapter 目前走的就是这个case
                return super.createRequestMappingHandlerAdapter();
            }
    
    

    如下在EnableWebMvcConfiguration的构造方法中可以看到 mvcRegistrations 我们只需要注入这个实现即可

            private final WebMvcRegistrations mvcRegistrations;
    
            private ResourceLoader resourceLoader;
    
            public EnableWebMvcConfiguration(WebMvcProperties mvcProperties, WebProperties webProperties,
                    ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
                    ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
                    ListableBeanFactory beanFactory) {
                this.resourceProperties = webProperties.getResources();
                this.mvcProperties = mvcProperties;
                this.webProperties = webProperties;
                this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
                this.beanFactory = beanFactory;
            }
    
    
    我们的重写链路已经浮现!WebMvcRegistrations(子类)#getRequestMappingHandlerAdapter返回RequestMappingHandlerAdapter(子类) --> RequestMappingHandlerAdapter(子类)#createInvocableHandlerMethod返回ServletInvocableHandlerMethod(子类) --> ServletInvocableHandlerMethod(子类)重写 doInovke方法即可

    下面看代码

    定义一个观察者接口由客户端程序可插入逻辑

    
    
    
    import org.springframework.web.method.HandlerMethod;
    
    /**
     * @author ASDL3DL00676
     */
    public interface ServletInvocableHandlerMethodInterceptor extends Ordered {
    
      /**
       * 通过参数来决定是否执行
       *
       * @param args args
       * @param handlerMethod 目标方法
       * @return boolean
       */
      boolean supports(HandlerMethod handlerMethod, Object[] args);
    
      /**
       * 需要目标方法考虑异常
       * 前置方法
       *
       * @param args args
       * @param handlerMethod 目标方法
       */
      default void beforeInvoke(HandlerMethod handlerMethod, Object[] args) {
    
      }
    
    
      /**
       * 异常时
       *
       * @param args args
       * @param throwable 抛出的异常
       * @param handlerMethod 目标方法
       */
      default void afterThrows(HandlerMethod handlerMethod, Object[] args, Throwable throwable) {
    
      }
    
      /**
       * 成功时需要目标方法考虑异常
       *
       * @param args args
       * @param result 结果值
       * @param handlerMethod 目标方法
       */
      default void afterSuccess(HandlerMethod handlerMethod, Object[] args, Object result) {
    
      }
    
      /**
       * 函数式包裹
       *
       * @param target 目标方法
       * @param handlerMethod controller 代理的方法
       * @param args 参数
       * @return 结果
       */
      default Supplier<Object> wrapperDoInvoke(Supplier<Object> target, HandlerMethod handlerMethod,
          Object[] args) {
        return target;
      }
    
      /**
       * 默认排序
       *
       * @return int
       */
      @Override
      default int ordered() {
        return 0;
      }
    
    }
    
    

    初始化WebMvcRegistrations 注入spring

      @Bean
    // 同时允许依赖当前jar的业务服务可以继续重写,如果想要保留当前jar的逻辑需要继承这里的WebMvcRegistrationsSimpleImpl
      @ConditionalOnMissingBean
      public WebMvcRegistrations customWebMvcRegistrations(
          @Autowired(required = false) List<ServletInvocableHandlerMethodInterceptor> interceptors) {
        if (CollectionUtils.isNotEmpty(interceptors)) {
          interceptors.sort(Comparator.comparing(ServletInvocableHandlerMethodInterceptor::ordered));
        }
        return new WebMvcRegistrationsSimpleImpl(interceptors);
      }
    
    

    重写WebMvcRegistrations 并带上观察者

    
    
    import java.util.List;
    import javax.annotation.Nullable;
    import lombok.RequiredArgsConstructor;
    import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
    
    /**
     * @author ASDL3DL00676
     */
    @RequiredArgsConstructor
    public class WebMvcRegistrationsSimpleImpl implements WebMvcRegistrations {
      
      @Nullable
      private final List<ServletInvocableHandlerMethodInterceptor> interceptors;
    
      /**
       * Return the custom {@link RequestMappingHandlerAdapter} that should be used and
       * processed by the MVC configuration.
       *
       * @return the custom {@link RequestMappingHandlerAdapter} instance
       */
      @Override
      public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
        return new RequestMappingHandlerInterceptorImpl(interceptors);
      }
    
    
    }
    
    

    重写RequestMappingHandlerAdapter 并带上观察者

    
    
    import java.util.List;
    import javax.annotation.Nonnull;
    import javax.annotation.Nullable;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
    import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
    
    /**
     * @author ASDL3DL00676
     */
    @RequiredArgsConstructor
    public class RequestMappingHandlerInterceptorImpl extends RequestMappingHandlerAdapter {
    
      @Nullable
      private final List<ServletInvocableHandlerMethodInterceptor> interceptors;
      @Nonnull
      @Override
      protected ServletInvocableHandlerMethod createInvocableHandlerMethod(@Nonnull HandlerMethod handlerMethod) {
        return new ServletInvocableHandlerMethodWithPlugin(handlerMethod, interceptors);
      }
    
    }
    
    

    重写ServletInvocableHandlerMethod 并带上观察者,对doInvoke方法提供多种拦截,以及函数包裹逻辑

    
    import java.util.Arrays;
    import java.util.List;
    import javax.annotation.Nonnull;
    import lombok.EqualsAndHashCode;
    import org.apache.commons.collections4.CollectionUtils;
    import org.springframework.lang.Nullable;
    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.mvc.method.annotation.ServletInvocableHandlerMethod;
    
    
    /**
     * @author ASDL3DL00676
     */
    @EqualsAndHashCode(callSuper = true)
    public class ServletInvocableHandlerMethodWithPlugin extends ServletInvocableHandlerMethod {
    
    
      @javax.annotation.Nullable
      private final List<ServletInvocableHandlerMethodInterceptor> interceptors;
    
      private final HandlerMethod currentMethod;
    
    
      public ServletInvocableHandlerMethodWithPlugin(
          HandlerMethod handlerMethod,
          @Nullable List<ServletInvocableHandlerMethodInterceptor> interceptors) {
        super(handlerMethod);
        this.interceptors = interceptors;
        this.currentMethod = handlerMethod;
      }
    
      @Nullable
      @Override
      public Object invokeForRequest(
          @Nonnull NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
          @Nonnull Object... providedArgs) throws Exception {
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
          logger.trace("Arguments: " + Arrays.toString(args));
        }
        try {
          Supplier<Object> target = () -> doInvoke(args);
          if (CollectionUtils.isNotEmpty(interceptors)) {
            for (ServletInvocableHandlerMethodInterceptor interceptor : interceptors) {
              if (interceptor.supports(currentMethod, args)) {
                interceptor.beforeInvoke(currentMethod, args);
                target = interceptor.wrapperDoInvoke(target, currentMethod, args);
              }
            }
          }
          Object result = target.get();
          if (CollectionUtils.isNotEmpty(interceptors)) {
            for (ServletInvocableHandlerMethodInterceptor interceptor : interceptors) {
              if (interceptor.supports(currentMethod, args)) {
                interceptor.afterSuccess(currentMethod, args, result);
              }
            }
          }
          return result;
        } catch (Throwable throwable) {
          if (CollectionUtils.isNotEmpty(interceptors)) {
            for (ServletInvocableHandlerMethodInterceptor interceptor : interceptors) {
              if (interceptor.supports(currentMethod, args)) {
                interceptor.afterThrows(currentMethod, args, throwable);
              }
            }
          }
          throw throwable;
        }
      }
    
    
    }
    
    

    到此我们找到了扩展点,并通过重写对doInvoke添加观察者,函数式包裹等,也可以通过其他的扩展方法对

    ServletInvocableHandlerMethod ,RequestMappingHandlerAdapter进行扩展

    相关文章

      网友评论

          本文标题:springmvc比HandlerInterceptor更靠近目

          本文链接:https://www.haomeiwen.com/subject/wxztwdtx.html