从原理层面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【享学Spring MVC】(上)

简介: 从原理层面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【享学Spring MVC】(上)

前言


HandlerMethod它作为Spring MVC的非公开API,可能绝大多数小伙伴都对它比较陌生,但我相信你对它又不是那么的生疏,因为你可能没用过但肯定见过。

比如Spring MVC的拦截器HandlerInterceptor的拦截方法的第三个入参Object handler,虽然它是Object类型,但其实绝大部分情况下我们都会当作HandlerMethod来使用;又比如我之前的这篇讲RequestMappingHandlerMapping的文章也大量的提到过HandlerMethod这个类。


经由我这么“忽悠”,你是否觉得它还是相对比较重要的一个类了呢?不管你信不信,反正我是这么认为的:HandlerMethod它是理解Spring MVC不可或缺的一个类,甚至可以说是你希望参与到Spring MVC的定制化里面来不可忽略的一个关键API。


HandlerMethod


HandlerMethod它不是一个接口,也不是个抽象类,且还是public的。HandlerMethod封装了很多属性,在访问请求方法的时候可以方便的访问到方法、方法参数、方法上的注解、所属类等并且对方法参数封装处理,也可以方便的访问到方法参数的注解等信息


// @since 3.1
public class HandlerMethod {
  // Object类型,既可以是个Bean,也可以是个BeanName
  private final Object bean;
  // 如果是BeanName,拿就靠它拿出Bean实例了~
  @Nullable
  private final BeanFactory beanFactory;
  private final Class<?> beanType; // 该方法所属的类
  private final Method method; // 该方法本身
  private final Method bridgedMethod; // 被桥接的方法,如果method是原生的,它的值同method
  // 封装方法参数的类实例,**一个MethodParameter就是一个入参**
  // MethodParameter也是Spring抽象出来的一个非常重要的概念
  private final MethodParameter[] parameters;
  @Nullable
  private HttpStatus responseStatus; // http状态码(毕竟它要负责处理和返回)
  @Nullable
  private String responseStatusReason; // 如果状态码里还要复数原因,就是这个字段  可以为null
  // 通过createWithResolvedBean()解析此handlerMethod实例的handlerMethod。
  @Nullable
  private HandlerMethod resolvedFromHandlerMethod;
  // 标注在**接口入参**上的注解们(此处数据结构复杂,List+二维数组)
  @Nullable
  private volatile List<Annotation[][]> interfaceParameterAnnotations;
  // 它的构造方法众多  此处我只写出关键的步骤
  public HandlerMethod(Object bean, Method method) {
    ...
    this.beanType = ClassUtils.getUserClass(bean);
    this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
    this.parameters = initMethodParameters();
    ...
    evaluateResponseStatus();
  }
  // 这个构造方法抛出了一个异常NoSuchMethodException 
  public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
    ...
    this.method = bean.getClass().getMethod(methodName, parameterTypes);
    this.parameters = initMethodParameters();
    ...
    evaluateResponseStatus();
  }
  // 此处传的是BeanName
  public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
    ...
    // 这部判断:这个BeanName是必须存在的
    Class<?> beanType = beanFactory.getType(beanName);
    if (beanType == null) {
      throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
    }
    this.parameters = initMethodParameters();
    ...
    evaluateResponseStatus();
  }
  // 供给子类copy使用的
  protected HandlerMethod(HandlerMethod handlerMethod) { ... }
  // 所有构造都执行了两个方法:initMethodParameters和evaluateResponseStatus
  // 初始化该方法所有的入参,此处使用的是内部类HandlerMethodParameter
  // 注意:处理了泛型的~~~
  private MethodParameter[] initMethodParameters() {
    int count = this.bridgedMethod.getParameterCount();
    MethodParameter[] result = new MethodParameter[count];
    for (int i = 0; i < count; i++) {
      HandlerMethodParameter parameter = new HandlerMethodParameter(i);
      GenericTypeResolver.resolveParameterType(parameter, this.beanType);
      result[i] = parameter;
    }
    return result;
  }
  // 看看方法上是否有标注了@ResponseStatus注解(接口上或者父类 组合注解上都行)
  // 若方法上没有,还会去所在的类上去看看有没有标注此注解
  // 主要只解析这个注解,把它的两个属性code和reason拿过来,最后就是返回它俩了~~~
  // code状态码默认是HttpStatus.INTERNAL_SERVER_ERROR-->(500, "Internal Server Error")
  private void evaluateResponseStatus() {
    ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class);
    if (annotation == null) {
      annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class);
    }
    if (annotation != null) {
      this.responseStatus = annotation.code();
      this.responseStatusReason = annotation.reason();
    }
  }
  ... // 省略所有属性的get方法(无set方法)
  // 返回方法返回值的类型  此处也使用的MethodParameter 
  public MethodParameter getReturnType() {
    return new HandlerMethodParameter(-1);
  }
  // 注意和上面的区别。举个列子:比如方法返回的是Object,但实际return “fsx”字符串
  // 那么上面返回永远是Object.class,下面你实际的值是什么类型就是什么类型
  public MethodParameter getReturnValueType(@Nullable Object returnValue) {
    return new ReturnValueMethodParameter(returnValue);
  }
  // 该方法的返回值是否是void
  public boolean isVoid() {
    return Void.TYPE.equals(getReturnType().getParameterType());
  }
  // 返回标注在方法上的指定类型的注解   父方法也成
  // 子类ServletInvocableHandlerMethod对下面两个方法都有复写~~~
  @Nullable
  public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
    return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
  }
  public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
    return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
  }
  // resolvedFromHandlerMethod虽然它只能被构造进来,但是它实际是铜鼓调用下面方法赋值
  @Nullable
  public HandlerMethod getResolvedFromHandlerMethod() {
    return this.resolvedFromHandlerMethod;
  }
  // 根据string类型的BeanName把Bean拿出来,再new一个HandlerMethod出来~~~这才靠谱嘛
  public HandlerMethod createWithResolvedBean() {
    Object handler = this.bean;
    if (this.bean instanceof String) {
      Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
      String beanName = (String) this.bean;
      handler = this.beanFactory.getBean(beanName);
    }
    return new HandlerMethod(this, handler);
  }
  public String getShortLogMessage() {
    return getBeanType().getName() + "#" + this.method.getName() + "[" + this.method.getParameterCount() + " args]";
  }
  // 这个方法是提供给内部类HandlerMethodParameter来使用的~~ 它使用的数据结构还是蛮复杂的
  private List<Annotation[][]> getInterfaceParameterAnnotations() {
    List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
    if (parameterAnnotations == null) {
      parameterAnnotations = new ArrayList<>();
      // 遍历该方法所在的类所有的实现的接口们(可以实现N个接口嘛)
      for (Class<?> ifc : this.method.getDeclaringClass().getInterfaces()) {
        // getMethods:拿到所有的public的方法,包括父接口的  接口里的私有方法可不会获取来
        for (Method candidate : ifc.getMethods()) {
          // 判断这个接口方法是否正好是当前method复写的这个~~~
          // 刚好是复写的方法,那就添加进来,标记为接口上的注解们~~~
          if (isOverrideFor(candidate)) {
            // getParameterAnnotations返回的是个二维数组~~~~
            // 因为参数有多个,且每个参数前可以有多个注解
            parameterAnnotations.add(candidate.getParameterAnnotations());
          }
        }
      }
      this.interfaceParameterAnnotations = parameterAnnotations;
    }
    return parameterAnnotations;
  }
  // 看看内部类的关键步骤
  protected class HandlerMethodParameter extends SynthesizingMethodParameter {
    @Nullable
    private volatile Annotation[] combinedAnnotations;
    ...
    // 父类只会在本方法拿,这里支持到了接口级别~~~
    @Override
    public Annotation[] getParameterAnnotations() {
      Annotation[] anns = this.combinedAnnotations;
      if (anns == null) { // 都只需要解析一次
        anns = super.getParameterAnnotations();
        int index = getParameterIndex();
        if (index >= 0) { // 有入参才需要去分析嘛
          for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) {
            if (index < ifcAnns.length) {
              Annotation[] paramAnns = ifcAnns[index];
              if (paramAnns.length > 0) {
                List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
                merged.addAll(Arrays.asList(anns));
                for (Annotation paramAnn : paramAnns) {
                  boolean existingType = false;
                  for (Annotation ann : anns) {
                    if (ann.annotationType() == paramAnn.annotationType()) {
                      existingType = true;
                      break;
                    }
                  }
                  if (!existingType) {
                    merged.add(adaptAnnotation(paramAnn));
                  }
                }
                anns = merged.toArray(new Annotation[0]);
              }
            }
          }
        }
        this.combinedAnnotations = anns;
      }
      return anns;
    }
  }
  // 返回值的真正类型~~~
  private class ReturnValueMethodParameter extends HandlerMethodParameter {
    @Nullable
    private final Object returnValue;
    public ReturnValueMethodParameter(@Nullable Object returnValue) {
      super(-1); // 此处传的-1哦~~~~ 比0小是很有意义的
      this.returnValue = returnValue;
    }
    ...
    // 返回值类型使用returnValue就行了~~~
    @Override
    public Class<?> getParameterType() {
      return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
    }
  }
}


可以看到HandlerMethod它持有的属性是非常多的,提供的能力也是很强的。

但是不知道小伙伴有没有发现,虽然它持有了目标的Method,但是它并没有提供invoke执行它的能力,如果你要执行它还得自己把Method拿去自己执行。


@ResponseStatus注解在执行目标处理方法的时候会去计算此注解,因此我个人建议此注解请慎用。一般使用在Advice的全局异常处理上~


所以总的来说它的职责还是很单一的:HandlerMethod它只负责准备数据,封装数据,而而不提供具体使用的方式方法~


看看它的继承树:

image.png

它主要有两个子类:InvocableHandlerMethod和ServletInvocableHandlerMethod,从命名就知道他俩都是有invoke调用能力的~


相关文章
|
25天前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
5月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
2576 114
|
2月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
177 0
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
111 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
110 0
|
2月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
148 0
|
2月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
|
4月前
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
165 32
|
4月前
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
187 9
|
5月前
|
安全 前端开发 Java
Spring Boot 项目中触发 Circular View Path 错误的原理与解决方案
在Spring Boot开发中,**Circular View Path**错误常因视图解析与Controller路径重名引发。当视图名称(如`login`)与请求路径相同,Spring MVC无法区分,导致无限循环调用。解决方法包括:1) 明确指定视图路径,避免重名;2) 将视图文件移至子目录;3) 确保Spring Security配置与Controller路径一致。通过合理设定视图和路径,可有效避免该问题,确保系统稳定运行。
343 0

热门文章

最新文章