SpringMVC源码解析HandlerMethod

简介: SpringMVC源码解析HandlerMethod

被 RequestMapping 注解封印的方法模型类。


封装了关于处理器方法信息的方法和bean类 。 提供了对方法参数,方法返回值,方法注释等方便地访问入口。


该类可以使用bean实例或具有bean名称(例如lazy-init bean,prototype bean)来创建。 使用createWithResolvedBean()获得HandlerMethod实例,被BeanFactory解析过的。

1 字段

  // Object类型,可以是Bean,也可以是个String 的 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就是一个入参
  private final MethodParameter[] parameters;
  @Nullable
  private HttpStatus responseStatus; // http状态码(负责处理和返回)
  @Nullable
  private String responseStatusReason; // 状态码原因
  // 通过createWithResolvedBean()解析此handlerMethod实例的handlerMethod。
  @Nullable
  private HandlerMethod resolvedFromHandlerMethod;
  // 接口入参上的注解
  @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

API

createWithResolvedBean

如果所提供的实例包含一个bean的名称,而不是一个对象实例。

则从 bean 工厂解析该名称得到 bean 类(@Component 注解的类),再创建HandlerMethod并返回

image.png

  // 初始化该方法所有的入参,此处使用的是内部类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;
  }
  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());
    }
  }
}

继承树

image.png

子类都有invoke能力

目录
相关文章
|
8月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
2123 1
|
8月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
831 29
|
8月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
331 4
|
8月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
8月前
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
8月前
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
345 2
|
8月前
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
9月前
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
2351 1
|
11月前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多
  • DNS