Mybatis 源码学习-类型转换(TypeHandlerRegistry)

Posted 凉茶方便面

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis 源码学习-类型转换(TypeHandlerRegistry)相关的知识,希望对你有一定的参考价值。

历史文章:
Mybatis 源码学习(8)-类型转换(TypeHandler)


定义 TypeHandler 后,Mybatis 还需要对这些 TypeHandler 进行管理,Mybatis 是通过 TypeHandlerRegistry 来实现 TypeHandler 的管理的。TypeHandlerRegistry 的初始化是在 Configuration 中,Configuration 包含一个 private final 字段,直接初始化了 TypeHandlerRegistry,而 TypeHandlerRegistry 的初始化阶段,会构建 JDBC 数据类型、Java 数据类型、TypeHandler 之间的关系,便于索引查询。

TypeHandlerRegistry 中的核心字段主要是几个关系 Map:

// JdbcType 与TypeHandler 之间的映射关系,JdbcType 是枚举类型,它与 JDBC 规范中的数据类型一一对应
// 该集合的作用是从 ResultSet 中读取数据时,将 JdbcType 转化为 Java 类型
// (由于 JdbcType 数量已知,因此该集合中的 TypeHandler 数据量固定)
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = 
        new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// Java 类型与 TypeHandler 之间的映射关系,但是由于 Java 类型可以转化为多种 JdbcType,
// 因此 value 也是一个 Map(因为 String 可以转化为 char、varchar、text 等数据库类型)
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = 
        new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// 全部 TypeHandler 的类型与 TypeHandler 实例之间的映射关系(可以认为是缓存)
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = 
        new HashMap<Class<?>, TypeHandler<?>>();

// 空TypeHandler 集合的标志
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = 
        Collections.emptyMap();

注册 TypeHandler 的方法:register

TypeHandler.register() 方法实现了 TypeHandler 的注册逻辑,其注册过程主要是解析 TypeHandler 并向以上 Map 中存储对应的解析结果。register 方法是一系列重载的方法,其中大部分方法是做数据类型转化,并调用其他重载方法,比较核心的方法有 6 个。

以上方法中,方法 4 是最核心的方法,该方法的参数包括:该 TypeHandler 能处理的 JavaType,JdbcType以及TypeHandler 对象,该方法的核心逻辑是向 TYPE_HANDLER_MAP 和 ALL_TYPE_HANDLERS_MAP 注册对应的 TypeHandler。

// 方法 4 的实现
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) 
  // 是否明确指定能够处理的 javaType
  if (javaType != null) 
    // 获取 javaType 在 TYPE_HANDLER_MAP 中已注册的 TypeHandler
    Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
    // 如果之前尚未注册过 TypeHandler,则新建对应的 Map
    if (map == null || map == NULL_TYPE_HANDLER_MAP) 
      map = new HashMap<JdbcType, TypeHandler<?>>();
      TYPE_HANDLER_MAP.put(javaType, map);
    
    // 将 TypeHandler 注册至 TYPE_HANDLER_MAP
    map.put(jdbcType, handler);
  
  // 向 ALL_TYPE_HANDLERS_MAP 注册 TypeHandler 的类型和 TypeHandler 对象
  ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);

方法 1~3 最终都会调用方法 4 进行注册,它们做了一些前置的工作,主要是解析方法上的 @MappedTypes@MappedJdbcTypes 注解,@MappedTypes 表示 TypeHandler 可以用于的 Java 类,@MappedJdbcTypes 表示 TypeHandler 可以处理的 JDBC 数据类型。

// 方法 1,方法包含 typeHandlerClass 参数
public void register(Class<?> typeHandlerClass) 
  boolean mappedTypeFound = false;
  // 解析获取 @MappedTypes 注解
  MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
  if (mappedTypes != null) 
    // 根据 @MappedTypes 上指定的 Java 类型,逐个注册
    for (Class<?> javaTypeClass : mappedTypes.value()) 
      // 经过包含类型转化和实例化 TypeHandler 的 register 方法转发,最终交给方法 3 进行处理
      register(javaTypeClass, typeHandlerClass);
      mappedTypeFound = true;
    
  
  if (!mappedTypeFound) 
    // 未指定 @MappedTypes 注解,直接交给方法 2 处理
    register(getInstance(null, typeHandlerClass));
  


// 方法 2 
public <T> void register(TypeHandler<T> typeHandler) 
  boolean mappedTypeFound = false;
  // 检查 @MappedTypes 注解,根据指定的 Java 类型进行注册,逻辑同方法 1
  MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
  if (mappedTypes != null) 
    for (Class<?> handledType : mappedTypes.value()) 
      register(handledType, typeHandler);
      mappedTypeFound = true;
    
  
  // 从 3.1.0 开始,可以根据 TypeHandler 的类型自动查找对应 Java 类型,但是需要 TypeHandler 继承 TypeReference
  // 它会自动解析 TypeHandler 上指定的泛型参数,并以该泛型参数作为 Java 类型
  if (!mappedTypeFound && typeHandler instanceof TypeReference) 
    try 
      TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
      // 由重载方法 3 继续处理
      register(typeReference.getRawType(), typeHandler);
      mappedTypeFound = true;
     catch (Throwable t) 
    
  
  if (!mappedTypeFound) 
    register((Class<T>) null, typeHandler);
  


// 方法 3,解析 @MappedJdbcTypes 注解
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) 
  // 获取 @MappedJdbcTypes 注解
  MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
  if (mappedJdbcTypes != null) 
    // 根据 @MappedJdbcTypes 注解指定的JDBC 类型进行注册
    for (JdbcType handledJdbcType : mappedJdbcTypes.value()) 
      register(javaType, handledJdbcType, typeHandler); // 重载方法 4
    
    // 该 TypeHandler 能否处理 null 类型
    if (mappedJdbcTypes.includeNullJdbcType()) 
      register(javaType, null, typeHandler); // 重载方法 4
    
   else 
    register(javaType, null, typeHandler); // 重载方法 4
  

以上方法最终均调用了方法 4,向 TYPE_HANDLER_MAP 和 ALL_TYPE_HANDLERS_MAP 完成注册,重载方法 5 是唯一一个向 JDBC_TYPE_HANDLER_MAP 注册的方法。

// 方法 5
public void register(JdbcType jdbcType, TypeHandler<?> handler) 
  // 注册 jdbcType 对应的 TypeHandler
  JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);


除了能够注册单个 TypeHandler 外,TypeHandlerRegistry 还提供了扫描整个 package 下的 TypeHandler 接口实现类,并完成 TypeHandler 的自动注册工作。

// 方法 6,扫描整个 package 下的 TypeHandler 实现类
public void register(String packageName) 
  // 扫描整个 package 下实现 TypeHandler 的类
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
  resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
  Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
  for (Class<?> type : handlerSet) 
    // 过滤掉内部类、接口、抽象类,这些类无法被直接实例化
    if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) 
      register(type); // 由方法 1 完成注册
    
  

除了以上 register 方法外,TypeHandlerRegistry 的构造方法完成了基本的类型注册逻辑,由于数据类型过多,仅以 String 类型来说明问题。

public TypeHandlerRegistry() 
  // 省略其他类型的注册逻辑

  // 向 TYPE_HANDLER_MAP 和 ALL_TYPE_HANDLERS_MAP 完成注册,StringTypeHandler 能够正确处理String 的 null 到 JDBC 的 null 的转化
  register(String.class, new StringTypeHandler());
  register(String.class, JdbcType.CHAR, new StringTypeHandler());
  register(String.class, JdbcType.CLOB, new ClobTypeHandler());
  register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
  register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
  register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
  register(String.class, JdbcType.NCLOB, new NClobTypeHandler());

  // StringTypeHandler 能够完成 JDBC 的 varchar 到 Java 的 String 之间的相互转化
  register(JdbcType.CHAR, new StringTypeHandler());
  register(JdbcType.VARCHAR, new StringTypeHandler());
  register(JdbcType.CLOB, new ClobTypeHandler());
  register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
  register(JdbcType.NVARCHAR, new NStringTypeHandler());
  register(JdbcType.NCHAR, new NStringTypeHandler());
  register(JdbcType.NCLOB, new NClobTypeHandler());

  register(Character.class, new CharacterTypeHandler());
  register(char.class, new CharacterTypeHandler());

查找 TypeHandler 的方法

和注册方法相同,查找 TypeHandler 也存在多个重载的情况,TypeHandlerRegistry.getTypeHandler() 的核心逻辑是从以上四个集合中获取对应的元素。

getTypeHandler 的系列方法中,最核心的方法是方法 1:getTypeHandler(Type type, JdbcType jdbcType),其他方法都是经过类型转化最终调用该方法。

// 方法 1
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) 
  // 不解析 ParamMap 类型
  if (ParamMap.class.equals(type)) 
    return null;
  
  // 查找Java 类型对应的 TypeHandler 集合
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
  TypeHandler<?> handler = null;
  if (jdbcHandlerMap != null) 
    // 根据当前 jdbcType 类型获取对应的 TypeHandler
    handler = jdbcHandlerMap.get(jdbcType);
    if (handler == null) 
      // 如果未找到,则查找能够处理 null 值的 TypeHandler
      handler = jdbcHandlerMap.get(null);
    
    if (handler == null) 
      // 如果 jdbcHandlerMap 中只注册了一个 TypeHandler,则使用该 TypeHandler
      handler = pickSoleHandler(jdbcHandlerMap);
    
  
  return (TypeHandler<T>) handler;

getJdbcHandlerMap 方法会检查 TYPE_HANDLER_MAP 中是否存在对应的 TypeHandler 集合,如果当前类不存在对应的 TypeHandler,则继续向父类中搜索对应的 TypeHandler,如果最终仍未找到,则以 NULL_TYPE_HANDLER_MAP 初始化该 Java 类型对应的 TypeHandler。

private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) 
  // 查找 Java 类型对应的 TypeHandler 集合
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
  if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap))  // 检测是否为空集合标志
    return null;
  
  // 初始化 Java 类型对应的 TypeHandler 集合
  if (jdbcHandlerMap == null && type instanceof Class) 
    Class<?> clazz = (Class<?>) type;
    if (clazz.isEnum())  // 如果是枚举类型
      // 在枚举类接口上向父类搜索
      jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
      if (jdbcHandlerMap == null) 
        // 如果没有指定过任何 TypeHandler,则使用默认的 TypeHandler
        register(clazz, getInstance(clazz, defaultEnumTypeHandler));
        return TYPE_HANDLER_MAP.get(clazz);
      
     else 
      // 在父类上搜索是否已存在 TypeHandler 集合
      jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
    
  
  TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
  return jdbcHandlerMap;


// 查找 enum 类型实现接口的情况
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) 
  // 遍历所有实现的接口
  for (Class<?> iface : clazz.getInterfaces()) 
    // 当前接口是否已经有 TypeHandler,如果没有,则递归向父接口查询
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(iface);
    if (jdbcHandlerMap == null) 
      jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
    
    if (jdbcHandlerMap != null) 
      // 找到一个父接口上注册过的 TypeHandler
      HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<JdbcType, TypeHandler<?>>();
      for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) 
        // 尝试使用当前的 enum 作为参数,尝试覆盖接口对应的 TypeHandler
        newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
      
      return newMap;
    
  
  return null;


// 查找普通类父类的 TypeHandler
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) 
  Class<?> superclass =  clazz.getSuperclass();
  if (superclass == null || Object.class.equals(superclass)) 
    return null; // Object 或 null,则查找结束
  
  Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
  if (jdbcHandlerMap != null) 
    return jdbcHandlerMap;
   else 
    // 继续查找父类对应的 TypeHandler
    return getJdbcHandlerMapForSuperclass(superclass);
  

TypeHandlerRegistry.getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) 方法可以根据 handlerType 的类型,直接从 ALL_TYPE_HANDLERS_MAP 中获取对应的 TypeHandler。

TypeHandlerRegistry.getTypeHandler(JdbcType jdbcType) 方法可以根据指定的jdbcType 从 JDBC_TYPE_HANDLER_MAP 中获取对应的 TypeHandler。

Mybatis 除了支持默认的 TypeHandler 外,还支持从配置文件中配置 <typeHandlers>节点,自定义添加 TypeHandler 实现类。Mybatis 初识化时,会解析对应的配置节点,并将对应的 TypeHandler 注册至 TypeHandlerRegistry 中。

总结

TypeHandlerRegistry 可以管理所有的 TypeHandler,支持注册和获取对应的 Java 类和 JDBC 类型对应的 TypeHandler。TypeHandlerRegistry 作为 Configuration 的一个字段,在 Mybatis 的执行过程中的任何阶段都可以被方便的获取和使用。另外,TypeHandler 支持扩展,允许自定义 TypeHandler,Mybatis 可以根据泛型参数,自动解析其能够处理的 Java 类。


参考文档:《Mybatis 技术内幕》

本文的基本脉络参考自《Mybatis 技术内幕》,编写文章的原因是希望能够系统地学习 Mybatis 的源码,但是如果仅阅读源码或者仅从官方文档很难去系统地学习,因此希望参考现成的文档,按照文章的脉络逐步学习。


欢迎关注我的公众号:我的搬砖日记,我会定时分享自己的学习历程。

以上是关于Mybatis 源码学习-类型转换(TypeHandlerRegistry)的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis 源码学习(10)-类型转换(TypeAliasRegistry)

Mybatis 源码学习(10)-类型转换(TypeAliasRegistry)

Mybatis 源码学习-类型转换(TypeHandler)

Mybatis 源码学习-类型转换(TypeHandler)

MyBatis源码阅读: MyBatis基础模块-类型转换模块

Mybatis 源码学习(11)-日志模块