通过源码分析MyBatis中Param注解的定义与解析过程

标签: Java

保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/51/ )。

前面的文章中,我们已经对Java注解的创建、使用进行了介绍。接下来,我们将介绍如何在程序中解析注解,从而获取注解中承载的信息。

我们以MyBatis中最常用的Param注解为例,介绍下它如何被解析然后发挥作用。(均参考自书籍《通用源码阅读指导书——MyBatis源码详解》)

Param注解带注释的源码如下面代码所示。

@Documented // 表明该注解会保留在API文档中
@Retention(RetentionPolicy.RUNTIME) // 表明注解会保留到运行阶段
@Target(ElementType.PARAMETER) // 表明注解可以应用在参数上
public @interface Param {
  String value(); // 整个注解只有一个属性,名为value
}

使用时,我们只需要在相关参数上使用该注解对参数进行命名,便可以在映射文件中引用该参数。例如下面的示例中,我们使用该注解将userId属性命名为id

User queryById(@Param("id") Integer userId);

这样,我们便可以在Mapper引用id所指代的变量,如下所示。

<select id="queryById" resultType="User">
    SELECT *
    FROM user
    WHERE `id` = #{id}
</select>

接下来我们着重分析该注解是如何生效的。

借助于开发工具,我们在ParamNameResolver的构造方法中找到了MyBatis对引Param的引用。相关源码如下所示。

/**
 * 参数名解析器的构造方法
 * @param config 配置信息
 * @param method 要被分析的方法
 */
public ParamNameResolver(Configuration config, Method method) {
  // 获取参数类型列表
  final Class<?>[] paramTypes = method.getParameterTypes();
  // 准备存取所有参数的注解,是二维数组
  final Annotation[][] paramAnnotations = method.getParameterAnnotations();
  final SortedMap<Integer, String> map = new TreeMap<>();
  int paramCount = paramAnnotations.length;
  // 循环处理各个参数
  for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
    if (isSpecialParameter(paramTypes[paramIndex])) {
      // 跳过特别的参数
      continue;
    }
    // 参数名称
    String name = null; 
    for (Annotation annotation : paramAnnotations[paramIndex]) {
      // 找出参数的注解
      if (annotation instanceof Param) {
        // 如果注解是Param
        hasParamAnnotation = true;
        // 那就以Param中值作为参数名
        name = ((Param) annotation).value();
        break;
      }
    }

    if (name == null) {
      // 否则,保留参数的原有名称
      if (config.isUseActualParamName()) {
        name = getActualParamName(method, paramIndex);
      }
      if (name == null) {
        // 参数名称取不到,则按照参数index命名
        name = String.valueOf(map.size());
      }
    }
    map.put(paramIndex, name);
  }
  names = Collections.unmodifiableSortedMap(map);
}

在上述代码中,首先使用语句“final Annotation[][] paramAnnotations = method.getParameterAnnotations()”得到了目标方法的所有参数的注解。假设我们定义如下面代码所示的方法:

User queryById(@Param("id") Integer userId, String userName, @NonNull @Param("sName") String schoolName);

则会在paramAnnotations得到如图所示的结果:

paramAnnotations变量值示意图

然后在分析到每个参数时,循环遍历它的每个注解并使用“annotation instanceof Param”判断当前注解是否为“Param”注解。如果当前注解为“Param”注解,则会使用“((Param) annotation).value()”操作获取该注解的value值作为参数的名称。

这样,我们对Integer userId参数使用了@Param("id")后,“id”便成了实参的名称,因此我们能够使用“id”索引到对应的实参。

可见,阅读源码对我们理解编程知识的使用十分有用。推荐大家阅读《通用源码阅读指导书——MyBatis源码详解》。

通用源码阅读指导书-京东自营

《通用源码阅读指导书》

《通用源码阅读指导书——MyBatis源码详解》是一本以MyBatis的源码为实例讲述源码阅读方法的书籍,并且附带有示例项目源码,MyBatis的全中文注解。书籍还总结了大量的编程知识和架构经验,对提升编程和架构能力十分有用,非常推荐。

本文首发于个人知乎:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。

作者书籍推荐