springmvc比HandlerInterceptor更靠近目

2023-11-06  本文已影响0人  二哈_8fd0
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进行扩展

上一篇下一篇

猜你喜欢

热点阅读