深入分析 resultMap元素解析过程

264 阅读18分钟

本文为稀土掘金技术社区首发签约文章,30 天内禁止转载,30 天后未获授权禁止转载,侵权必究!
大家好,我是 王有志,一个分享硬核 Java 技术的金融摸鱼侠,欢迎大家加入 Java 人自己的交流群“共同富裕的 Java 人”。

上一篇文章 中,我们已经对 MyBatis 应用程序的初始化流程做了整体的分析,不过还是留下了两个坑:

  • MyBaits 映射器文件中 resultMap 元素的解析;
  • MyBatis 映射器文件中 SQL 语句的解析。

那么今天我们继续学习 MyBatis 应用程序初始化的过程,深入分析 MyBatis 映射器文件中 resultMap 元素的解析过程。

Tips:如果你已经忘了 resultMap 元素的功能,可以回顾下这两篇文章:《MyBatis映射器:一对一关联查询》《MyBatis映射器:一对多关联查询》

解析 resultMap 元素的调用链路

上一篇文章 中,我们已经知道在XMLMapperBuilder#configurationElement方法中,通过调用XMLMapperBuilder#resultMapElements方法实现了 resultMap 元素的解析。

我们先来看XMLMapperBuilder#configurationElement方法中关于解析 resultMap 元素的部分,部分源码如下:

private void configurationElement(XNode context) {
  String namespace = context.getStringAttribute("namespace");
  // 省略解析其它元素的部分
  resultMapElements(context.evalNodes("/mapper/resultMap"));
  // 省略解析其它元素的部分
}

第 4 行的代码中,调用XNode#evalNodes方法获取到 mapper 元素下的全部 resultMap 元素,接着调用了XMLMapperBuilder#resultMapElements方法,该方法部分源码如下:

private void resultMapElements(List<XNode> list) {
  for (XNode resultMapNode : list) {
    resultMapElement(resultMapNode);
  }
}

这段代码非常简单,遍历 MyBatis 映射器中所有 resultMap 元素的配置,并调用XMLMapperBuilder#resultMapElement方法进行解析,源码如下:

private ResultMap resultMapElement(XNode resultMapNode) {
  return resultMapElement(resultMapNode, Collections.emptyList(), null);
}

XMLMapperBuilder#resultMapElement方法直接调用了它的重载方法,下面我们重点来看下这个重载方法。

XMLMapperBuilder#resultMapElement 方法分析

我们稍微修改下XMLMapperBuilder#resultMapElement方法的源码,如下:

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
  String type = resultMapNode.getStringAttribute("type");
  Class<?> typeClass = resolveClass(type);
  // 声明鉴别器
  Discriminator discriminator = null;
  // 声明用于记录解析结果的容器
  List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
  // 获取 resultMap 元素的子元素
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    if ("constructor".equals(resultChild.getName())) {
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  // 构建 ResultMapResolver 实例
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    return resultMapResolver.resolve();
  } catch (IncompleteElementException e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

XMLMapperBuilder#resultMapElement方法声明了返回值类型 ResultMap,在 MyBatis 中,每个 resultMap 元素都会被解析成为一个 ResultMap 实例,也就是说,ResultMap 是 MyBatis 映射器中 resultMap 元素在 MyBatis 应用程序中的体现,关于 ResultMap 的结构,我们下面还会介绍。

第 2 行代码,获取 resultMap 元素的 type 属性, 该属性配置的是 resultMap 元素对应的 Java 持久化对象的全限名

第 3 行代码,调用XMLMapperBuilder#resolveClass方法解析类型。XMLMapperBuilder#resolveClass方法中使用了 MyBatis 的别名注册器,也就是说在 resultMap 元素的配置中,允许使用 MyBatis 核心配置中的别名配置映射 Java 持久化对象,如果没有配置别名,这里会使用 Java 的反射技术,通过全限名获取 Java 类型

第 10 行代码,这里循环 resultMap 元素的子元素,根据子元素的类型进行处理:

  • 第 11 行的条件语句中,处理 constructor 元素,即构造器的配置;
  • 第 13 行的条件语句中,处理 discriminator 元素,即鉴别器映射的配置;
  • 第 15 行的条件语句中,处理 id 元素,result 元素,association 元素和 collection 元素等的配置。

由于 constructor 元素和 discriminator 元素使用较少,而且我们前面在 《MyBatis映射器:一对一关联查询》《MyBatis映射器:一对多关联查询》 中也没有提及它们的用法,所以本文中也不会分析这两个元素的解析过程。

第 20 行代码,调用 XMLMapperBuilder#buildResultMappingFromContext 方法生成 ResultMapping 实例,并添加到第 7 行代码声明的容器中。

第 23 行代码,获取 resultMap 元素的 id 属性。

第 24 行代码,获取 resultMap 元素的 extends 属性,该属性配置了 resultMap 元素的继承关系。

第 25 行代码,获取 resultMap 元素的 autoMapping 属性,该属性配置为 true 时,启动自动映射,自动查找与数据库列名称相同的 Java 持久化对象的字段,并调用 setter 方法进行赋值,设置为 false 时,则需要在 resultMap 元素中配置数据库列与 Java 持久化对象的字段的映射关系

第 26 行代码,创建了 ResultMapResolver 实例,ResultMapResolver 的结构比较简单,重点是第 29 行代码调用的ResultMapResolver#resolve方法。

到这里,我们已经对XMLMapperBuilder#resultMapElement方法的逻辑有了一个整体的了解,接下来我们就对XMLMapperBuilder#resultMapElement方法中出现的 ResultMapping 对象和 ResultMap 对象以及调用的XMLMapperBuilder#buildResultMappingFromContext方法和ResultMapResolver#resolve方法做一个重点分析。

ResultMapping 的结构

ResultMapping 对象用于存储 resultMap 元素中每项配置(如 id 元素,result 元素和 collection 元素等)的解析结果,它的结构如下:

public class ResultMapping {  
  private Configuration configuration;  
  private String property;  
  private String column;  
  private Class<?> javaType;  
  private JdbcType jdbcType;  
  private TypeHandler<?> typeHandler;  
  private String nestedResultMapId;  
  private String nestedQueryId;  
  private Set<String> notNullColumns;  
  private String columnPrefix;  
  private List<ResultFlag> flags;  
  private List<ResultMapping> composites;  
  private String resultSet;  
  private String foreignColumn;  
  private boolean lazy;
}

除了以上的字段外,ResultMapping 内部还定义了 Builder 类,同样也应用了建造者模式。Builder 类主要的功能是创建 ResultMapping 实例和对构建 ResultMapping 实例的参数进行校验,整体逻辑非常简单,源码就留给大家自行了解了。

下面我们介绍下每个字段的含义:

  • Configuration configuration,用于记录 Configuration 实例;
  • String property,对应 id 元素,result 元素, collection 元素和 association 元素的 property 属性,表示与数据库列映射的 Java 持久化对象的字段;
  • String column,对应 id 元素,result 元素, collection 元素和 association 元素的 column 属性,表示与 Java 持久化对象映射的数据库列;
  • Class<?> javaType,对应 id 元素,result 元素, collection 元素和 association 元素的 javaType 属性,表示 Java 持久化对象的字段的类型;
  • JdbcType jdbcType,对应 id 元素,result 元素, collection 元素和 association 元素的 jdbcType 属性,表示数据库列的类型;
  • TypeHandler<?> typeHandler,对应 id 元素,result 元素, collection 元素和 association 元素的 typeHandler 属性,表示类型处理器,配置该属性后会覆盖默认的类型处理器;
  • String nestedResultMapId,对应 collection 元素和 association 元素的 resultMap 属性,通过 resultMap 属性配置 resultMap 元素的 id,实现引入另一个 resultMap 元素的配置,将 SQL 语句查询的结果集的一部分列映射为其它的结果对象,这样就可以实现关联查询,并同时设置关联查询结果的关系;
  • String nestedQueryId,对应 collection 元素和 association 元素的 select 属性,通过 select 属性配置 select 元素的 id,实现引入 select 元素的配置;
  • Set<String> notNullColumns,对应 collection 元素和 association 元素的 notNullColumn 属性,对应节点的 notNullColumn 属性拆分后的结果;
  • String columnPrefix,对应 collection 元素和 association 元素的 columnPrefix 属性,表示数据库列的前缀;
  • List<ResultFlag> flags,处理标志,该标志共两类:ResultFlag.IDResultFlag.CONSTRUCTOR,分别表示 id 元素,idArg 元素和 constructor 元素
  • List<ResultMapping> composites,对应 id 元素,result 元素, collection 元素和 association 元素的 column 属性的复杂情况;
  • String resultSet,对应 collection 元素和 association 元素的 resultSet 属性;
  • String foreignColumn,对应 collection 元素和 association 元素的 foreignColumn 属性,用于指定外键的名称;
  • boolean lazy,对应 collection 元素和 association 元素的 fetchType 属性,表示是否延迟加载,取值为 lazy 和 eager,默认为 eager。

ResulMapping 中的字段主要是用于记录 id 元素,result 元素, collection 元素和 association 元素的属性值的,整体上并不复杂,只不过 MyBatis 中使用一个 ResultMapping 对象表示了多种元素的解析结果,所以这个对象看起来会稍微有些复杂。

这部分,我们通过一张图来看下 MyBatis 映射器中 resultMap 元素的子元素是如何与 ResultMap 对象的主要字段进行关联的:

Drawing 2024-08-11 18.20.42.excalidraw.png

XMLMapperBuilder#buildResultMappingFromContext 方法分析

了解完 ResultMapping 的结构之后,我们来看用于创建 ResultMapping 实例的XMLMapperBuilder#buildResultMappingFromContext方法。

在 MyBatis 中,XMLMapperBuilder#buildResultMappingFromContext方法分别在XMLMapperBuilder#resultMapElement 方法和XMLMapperBuilder#processConstructorElement方法中调用,XMLMapperBuilder#resultMapElement方法我们已经了解过了,这里先来简单的看一下XMLMapperBuilder#processConstructorElement方法,源码如下:

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
  List<XNode> argChildren = resultChild.getChildren();
  for (XNode argChild : argChildren) {
    List<ResultFlag> flags = new ArrayList<>();
    flags.add(ResultFlag.CONSTRUCTOR);
    if ("idArg".equals(argChild.getName())) {
      flags.add(ResultFlag.ID);
    }
    resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
  }
}

XMLMapperBuilder#processConstructorElement方法在XMLMapperBuilder#resultMapElement方法中处理 constructor 元素的配置时被调用,根据元素类型标记元素,标记完成后调用XMLMapperBuilder#buildResultMappingFromContext方法。

接着来看XMLMapperBuilder#buildResultMappingFromContext方法,修改后的源码如下:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
  // 获取 column 属性,javaType 属性,jdbcType 属性,select 属性,notNullColumn 属性,columnPrefix 属性,typeHandler 属性,resultSet 属性和 foreignColumn 属性
  String column = context.getStringAttribute("column");
  String javaType = context.getStringAttribute("javaType");
  String jdbcType = context.getStringAttribute("jdbcType");
  String nestedSelect = context.getStringAttribute("select");
  String notNullColumn = context.getStringAttribute("notNullColumn");
  String columnPrefix = context.getStringAttribute("columnPrefix");
  String typeHandler = context.getStringAttribute("typeHandler");
  String resultSet = context.getStringAttribute("resultSet");
  String foreignColumn = context.getStringAttribute("foreignColumn");

  // 获取 property 属性
  String property;
  if (flags.contains(ResultFlag.CONSTRUCTOR)) {
    property = context.getStringAttribute("name");
  } else {
    property = context.getStringAttribute("property");
  }

  // 获取 resultMap 属性
  String nestedResultMap = context.getStringAttribute("resultMap", () -> processNestedResultMappings(context, Collections.emptyList(), resultType));
  // 获取 fetchType 属性
  boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
  // 解析 Java 持久化对象的字段的类型
  Class<?> javaTypeClass = resolveClass(javaType);
  // 解析数据库列的类型
  JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
  // 解析类型处理器
  Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);

  // 调用 MapperBuilderAssistant#buildResultMapping 方法创建 ResultMapping 实例
  return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

XMLMapperBuilder#buildResultMappingFromContext方法非常简单,相信大家直接看源码就可以了解该方法的功能,只有 3 处需要额外的注意下:

  • 第 14 行到第 19 行代码获取 property 属性的部分,这里的条件语句是为了兼容由XMLMapperBuilder#processConstructorElement方法调用进来的场景;
  • 第 22 行代码获取 resultMap 属性的部分,这里调用了XMLMapperBuilder#processNestedResultMappings方法处理引入的嵌套结果集映射;
  • 第 33 行代码调用MapperBuilderAssistant#buildResultMapping方法创建 ResultMapping 实例部分。

XMLMapperBuilder#processNestedResultMappings 方法解析

XMLMapperBuilder#processNestedResultMappings方法在 XMLMapperBuilder 中有两处调用,另一处如下:

private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) {
  // 省略
  for (XNode caseChild : context.getChildren()) {
    // 省略
    String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings, resultType));
    // 省略
  }
  return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}

可以看到,两处都是用于处理 resultMap 属性引入的嵌套结果集映射,我们来看XMLMapperBuilder#processNestedResultMappings方法,源码如下:

private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings, Class<?> enclosingType) {
  if (Arrays.asList("association", "collection", "case").contains(context.getName()) && context.getStringAttribute("select") == null) {
    validateCollection(context, enclosingType);
    ResultMap resultMap = resultMapElement(context, resultMappings, enclosingType);
    return resultMap.getId();
  }
  return null;
}

可以看到,第 2 行代码是针对于 association 元素,collection 元素和 case 元素,并且没有配置 select 属性时的处理,也就是说这里只处理嵌套结果集映射,并不会处理嵌套子查询。

第 4 行代码调用了XMLMapperBuilder#resultMapElement方法,这里是一处递归调用,将 association 元素,collection 元素和 case 元素当做是 resultMap 进行处理。

MapperBuilderAssistant#buildResultMapping 方法解析

上一篇文章 中,我们已经聊过了 MapperBuilderAssistant,所以这里我们直接看MapperBuilderAssistant#buildResultMapping方法,源码如下:

public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType, JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) {
  Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
  // 通过传入的 javaTypeClass 和 typeHandler,获取 TypeHandler 实例,底层使用 TypeHandlerRegistry 实例实现
  TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
  // 解析 {prop1=col1,prop2=co12} 形式的 column 属性,将其解析成 ResultMapping
  List<ResultMapping> composites;
  if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
    composites = Collections.emptyList();
  } else {
    composites = parseCompositeColumnName(column);
  }
  // 通过建造者模式创建 ResultMapping 实例
  return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
  .jdbcType(jdbcType)
  .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
  .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
  .resultSet(resultSet)
  .typeHandler(typeHandlerInstance)
  .flags(flags == null ? new ArrayList<>() : flags)
  .composites(composites)
  .notNullColumns(parseMultipleColumnNames(notNullColumn))
  .columnPrefix(columnPrefix)
  .foreignColumn(foreignColumn)
  .lazy(lazy).build();
}

第 2 行代码,调用MapperBuilderAssistant#resolveResultJavaType方法解析 resultType 中 property 字段的类型,实际上传入的 javaType 就是该字段的类型,这里只有在javaType == null时,才会通过 resultType 去解析,因此不存在重复解析的场景。

ResultMap 的结构

ResultMap 用于存储每个 resultMap 元素的配置,它的结构如下:

public class ResultMap {
  private Configuration configuration;
  private String id;
  private Class<?> type;
  private List<ResultMapping> resultMappings;
  private List<ResultMapping> idResultMappings;
  private List<ResultMapping> constructorResultMappings;
  private List<ResultMapping> propertyResultMappings;
  private Set<String> mappedColumns;
  private Set<String> mappedProperties;
  private Discriminator discriminator;
  private boolean hasNestedResultMaps;
  private boolean hasNestedQueries;
  private Boolean autoMapping;
}

与 ResultMapping 相同,ResultMap 内部定义了 Builder 类实现了建造者模式

下面我们介绍 ResultMap 类中每个字段的作用:

  • Configuration configuration,用于记录 Configuration 实例;
  • String id,对应 resultMap 元素的 id 属性;
  • Class<?> type,对应 resultMap 元素的 type 属性;
  • List<ResultMapping> resultMappings,用于存储 resultMap 元素子元素的解析结果(除 discriminator 元素);
  • List<ResultMapping> idResultMappings,用于存储 resultMap 元素的子元素中标记为 ResultFlag.ID 的元素(即 id 元素和 idArg 元素)的解析结果;
  • List<ResultMapping> constructorResultMappings,用于存储 resultMap 元素的子元素中标记为 ResultFlag.CONSTRUCTOR 的元素(即 constructor 元素)的解析结果;
  • List<ResultMapping> propertyResultMappings,用于存储 resultMap 元素的子元素中除了标记为 ResultFlag.CONSTRUCTOR 的元素之外的所有元素的解析结果;
  • Set<String> mappedColumns,用于存储 resultMap 元素中,所有子元素中使用到的数据库列;
  • Set<String> mappedProperties,用于存储 resultMap 元素中,所有子元素中使用到的 Java 持久化对象的字段;
  • Discriminator discriminator,用于记录 discriminator 元素解析结果;
  • boolean hasNestedResultMaps,用于标记是否有嵌套映射;
  • boolean hasNestedQueries,用于标记是否有嵌套查询;
  • Boolean autoMapping,对应 resultMap 元素的 autoMapping 属性。

这部分,我们再通过一张图来看下 MyBatis 映射器中 resultMap 元素是如何与 ResultMap 对象的主要字段进行关联的:

Drawing 2024-08-11 18.20.42.excalidraw.png

ResultMapResolver#resolve 方法分析

解析 resultMap 元素的最后一步就是创建 ResultMapResolver 实例,并调用ResultMapResolver#resolve方法。创建 ResultMapResolver 实例的过程非常简单,我们直接来看ResultMapResolver#resolve方法,源码如下:

public ResultMap resolve() {
  return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}

ResultMapResolver#resolve方法本身非常简单,只是调用了MapperBuilderAssistant#addResultMap方法而已。

MapperBuilderAssistant#addResultMap 方法解析

MapperBuilderAssistant#addResultMap方法的部分源码如下:

public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator,  List<ResultMapping> resultMappings, Boolean autoMapping) {
  // 将 namespace 与 resultMap 元素的 id 组合
  id = applyCurrentNamespace(id, false);
  // 将 namesapce 与被继承的 resultMap 元素的 id 组合
  extend = applyCurrentNamespace(extend, true);

  if (extend != null) {
    // 通过 Configuration 实例获取被继承的 ResultMap 实例
    ResultMap resultMap = configuration.getResultMap(extend);
    // 获取 被继承的 ResultMap 实例中的 ResultMapping 集合
    List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
    extendedResultMappings.removeAll(resultMappings);
    // 标记当前 resultMap 元素中是否定义了 constructor 元素
    boolean declaresConstructor = false;
    for (ResultMapping resultMapping : resultMappings) {
      if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
        declaresConstructor = true;
        break;
      }
    }
    // 当前 resultMap 元素中定义了 constructor 元素,则删除被继承的 resultMap 元素中 constructor 元素对应 ResultMapping 实例
    if (declaresConstructor) {
      extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
    }
    // 将被继承的 resultMap 元素对应的 ResultMapping 实例添加到当前 resultMap 元素对应的 ResultMapping 集合中
    resultMappings.addAll(extendedResultMappings);
  }
  // 创建当前 resultMap 元素对应的 ResultMap 实例
  ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping).discriminator(discriminator).build();
  // 将当前 resultMap 元素对应的 ResultMap 实例添加到 Configuration 实例中
  configuration.addResultMap(resultMap);
  return resultMap;
}

上述的代码逻辑非常清晰明了,只需要额外注意下第 29 行代码调用的ResultMap.Builder#build方法,该方法中不仅仅做了字段的赋值工作,还会遍历 ResultMapping 集合,将他们分别存储到不同的List<ResultMapping>容器中。解析 resultMap 元素的过程还是比较简单,期间也没有生成什么“了不起”的对象,只是简单的将 XML 形式的配置映射成 Java 对象。

那么到这里,关于解析 resultMap 元素的源码我们也已经分析完了,下一篇文章中,我们将要分析 MyBatis 应用程序在初始化的过程中,是如何解析 SQL 语句的。

回顾与思考

到这里,我们已经从完成了 resultMap 元素解析过程的分析,现在我们用一张图来总结下源码的执行流程,如下:

03.MyBatis映射器文件解析:resultMap元素.png

相较于前面的内容,解析 resultMap 元素的过程就复杂了很多,当然了由 resultMap 元素提供的结果集映射是 MyBatis 中最为强大的功能,因此复杂也是能理解的。

最后,我们来看一下通过上述的源码,我们能够借鉴哪些技巧并运用到我们日常的开发工作中。

为什么要阅读源码

今天我们先来解答一个私信中的问题。

私信.png

我学习源码的目的有 3 个:

  • 提高面试通过率
  • 深入理解原理
  • 提高编程能力

提高面试通过率

在近几年的面试经历中,常用框架的源码几乎成为了面试中的“必考题”,如果没有充分的准备,那么你很可能会在面试环节被淘汰掉。因此,很多人(包括我)学习源码的第一目的便是为了提高面试通过率。

这无可厚非,毕竟面试的好坏直接决定着你是否能够拿到 Offer,以及 Offer 的大小。大部分人都是出来打工,也都是为了碎银几两,以挣钱为目的而学习,不寒碜。

当然,如果你真的是因为热爱,这还是值得敬佩的。

深入理解原理

学习源码的另一点好处就是能够让我们更加深入的了解框架的原理和实现过程。

大部分人学习框架都是从框架的应用开始的,这期间可用的资料非常多,如官方文档,各类博客等,但是它们无法做到事无巨细(包括官方文档),而关于实现原理的资料又相对较少,并且很多也可能已经过时了,这就会导致你在使用框架时踩到一些坑。

那么通过源码去学习框架的原理和实现过程,可以帮助我们在应用过程中少踩坑。

提高编程能力

最后就是提升编程能力了,大部分市面上流行的框架都是由经验丰富的开发者编写,他们在设计这些框架时,往往会遵循良好的编程实践和设计原则,以及会使用到的一些巧妙的开发技巧。

通过学习这些框架的源码,我们可以学习到很多设计技巧,比如说,通常我们都会学习设计模式,但是实践时却无从下手,而框架中会大量的应用设计模式,这对于我们来说就是非常好的学习资料。

该学习哪些内容?

首先,要带着常见的面试问题去学习源码,保证你能流畅的解答出来这些问题,但是不建议直接背答案。

其次就是针对框架的常用功能要深入学习它的原理,尽可能的达到在使用时不踩坑。

最后就是学习框架中的设计和技巧,优秀的框架对于设计模式的应用可以说是最佳实践了,学习它的使用方式并模仿者应用到自己的设计中,最终转化为自己的知识。

这也就是郭德纲老师所说的学习的两个步骤:

模仿与转换.png

使用缓存“加速”反射

我们都知道,Java 中的反射技术几乎是每个开源框架中都会使用到的技术,但是,我们也总是说,反射的会拖慢程序的执行速度,那么如何提升反射的速度呢?

MyBatis 的解决方案是使用本地缓存来“加速”反射,在XMLMapperBuilder#resolveClass方法中,MyBatis 借助别名注册器来实现通过类型名称来获取 Class 对象,部分源码如下:

public abstract class BaseBuilder {
  protected <T> Class<? extends T> resolveClass(String alias) {
    return alias == null ? null : resolveAlias(alias);
  }

  protected <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
  }
}

public class TypeAliasRegistry {

  private final Map<String, Class<?>> typeAliases = new HashMap<>();

  public <T> Class<T> resolveAlias(String string) {
    String key = string.toLowerCase(Locale.ENGLISH);
    Class<T> value;
    if (typeAliases.containsKey(key)) {
      value = (Class<T>) typeAliases.get(key);
    } else {
      value = (Class<T>) Resources.classForName(string);
    }
    return value;
  }
}

TypeAliasRegistry 的底层使用 HashMap 存储别名与 Class 对象的映射关系,当可以通过别名注册器获取到类型名称对应的 Class 对象时,MyBaits 直接使用别名注册器中返回的结果,这样可以显著的提高通过名称获取 Class 对象的速度。

MyBatis 中还提供了反射工具包 org.apache.ibatis.reflection,其中核心类 DefaultReflectorFactory 与 Reflector 共同组成了一套反射的缓存,DefaultReflectorFactory 中使用 ConcurrentMap 容器存储了 Class 对象与 Reflector 实例之间的关系,部分源码如下:

public class DefaultReflectorFactory implements ReflectorFactory {
  private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
  @Override
  public Reflector findForClass(Class<?> type) {
    if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
      return MapUtil.computeIfAbsent(reflectorMap, type, Reflector::new);
    }
    return new Reflector(type);
  }
}

而 Reflector 对象中,则使用多个 HashMap 容器存储了 Getter 方法和 Setter 方法等,部分源码如下:

public class Reflector {

  private final Class<?> type;

  private final Map<String, Invoker> setMethods = new HashMap<>();

  private final Map<String, Invoker> getMethods = new HashMap<>();

  private final Map<String, Class<?>> setTypes = new HashMap<>();

  private final Map<String, Class<?>> getTypes = new HashMap<>();

  private Constructor<?> defaultConstructor;
}

MyBatis 中利用缓存的机制,不仅仅“加速”了通过类名获取 Class 对象的速度,还“加速”了通过 Class 对象获取构造方法,Getter 方法和 Setter 方法的速度,这是可以借鉴到我们自己的程序设计中的方案。


尾图.png