Skip to main content

TypeHandler注册方式

源码mybatisconfigurationtypeHandlerAbout 6 min

本文对typeHandlers节点的解析进行详细的说明.

配置方式

mybatis中对类型转换器有两种配置方式

  1. 使用package方式配置

    <typeHandlers>
        <package name="org.mybatis.example"/>
    </typeHandlers>
    
  2. 使用typeHandler指定handler等信息.

     <typeHandlers>
         <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
     </typeHandlers>
    

其使用规范可以在``中获取:

<!ELEMENT typeHandlers (typeHandler*,package*)>

<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>

对于package方式的配置, 必须指定类的全限定路径名. 对于typeHandler方式必须指定handler.

程序中的解析

typeHandlerElement是解析typeHandlers配置的入口, 其方法见下:

private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                JdbcType jdbcType = `resolveJdbcType`(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                if (javaTypeClass != null) {
                if (jdbcType == null) {
                    typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                } else {
                    typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}

typeHandler配置方式

对于typeHandler这种配置方式, 直接获取节点的javaType, jdbcType, handler参数, 并通过取对应的类型.

resolveClass 方法, 会查看别名中是否存在配置的名字, 如果存在获取其对应的类型, 不存在则使用Resources.classForName(string);通过反射获取.

JdbcType是枚举类型, resolveJdbcType通过枚举的valueOf方法获取字符串对应的枚举类型.

由于javaType, jdbcType可有可无, 使用三种重载方法进行注册:

  • java类型及类型转换器 void register(Class<?> javaTypeClass, Class<?> typeHandlerClass)
  • java类型,jdbc类型及类型转换器 void register(Class<?> javaTypeClass, JdbcType jdbcType, Class<?> typeHandlerClass)
  • 只配置类型转换器 void register(Class<?> typeHandlerClass)

需要注意的是, 单独使用jdbcType而不配置javaType. jdbcType不起作用.

对于只配置类型转换器的注册方式, 可以继续划分, 其处理逻辑如下:

  public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      register(getInstance(null, typeHandlerClass));
    }
  }

只配置了转换器, 这种方式需要先看@MappedTypes注解, 该注解表示类型转换器映射的java类型. 该配置为数组类型, 因此需要通过遍历调用第一种注册方式(使用java类型和类型转换器进行注册的重载方法).

如果没有配置@MappedTypes注解, 或者该注解中没有设置映射的java类型, 那么使用register(getInstance(null, typeHandlerClass))进行注册.

关于getInstance

进入typeHandlerRegistry.register. 会看到一个getInstance方法.

public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
}

public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
        try {
            Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
            return (TypeHandler<T>) c.newInstance(javaTypeClass);
        } catch (NoSuchMethodException ignored) {
            // ignored
        } catch (Exception e) {
            throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
        }
    }
    try {
        Constructor<?> c = typeHandlerClass.getConstructor();
        return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
        throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }

getInstance方法的返回值为TypeHandler, 其目的在于通过Class<?> javaTypeClass, Class<?> typeHandlerClass通过反射创建typeHandlerClass对应的实例.

如果javaTypeClass非空, 尝试将其作为构造器参数创建转换器对象. 如果创建失败, 则使用TypeHandler的无参构造器创建转换器实例.

三种重载方法

上面提到三种重载方法分别对应三种后续注册实例的重载方法

  1. <T> void register(Type javaType, TypeHandler<? extends T> typeHandler)
  2. void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)
  3. <T> void register(TypeHandler<T> typeHandler)
<T> void register(Type javaType, TypeHandler<? extends T> typeHandler)

第一种的方法的具体内容为:

  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }

因为没有显示传入JdbcType参数, 因此需要通过类型型转换器的注解@MappedJdbcTypes间接获取.
比如可以在类型转换器上配置@MappedJdbcTypes(JdbcType.VARCHAR).

当类型转换器上存在映射的jdbc类型注解时, 对注解的两个参数进行处理.

  1. value: 映射的jdbc类型, 可以有多个映射, 因此需要通过遍历进行注册.
  2. includeNullJdbcType: 是否可以映射为空的jdbc类型.

如果不存在注解配置, 则使用2中的逻辑, 即认为jdbc类型为空.

void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)

等同于<T> void register(Type javaType, TypeHandler<? extends T> typeHandler) 存在注解jdbc类型映射的配置.
即直接调用register(javaType, handledJdbcType, typeHandler)

<T> void register(TypeHandler<T> typeHandler)
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }
  1. 方法首先获取@MappedTypes配置的java类型映射, 并遍历调用第一种注册方式<T> void register(Type javaType, TypeHandler<? extends T> typeHandler), 即使用java类型和类型转换器的注册方式.
  2. 如果没有java类型映射, 并且类型转换器继承了TypeReference, 则使用指定的泛型类作为映射的java类.
  3. 如果仍旧没有java类型映射, 则使用第一种方式注册, java映射类型传入空. 因此, 使用java类型和转换器作为参数的注册方式, 在没有java映射配置的时候, 对应的参数是会为空的.

最后的注册

以上三种注册的情况, 最后都会调用下面的方法进行注册:

  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        map = new HashMap<>();
      }
      map.put(jdbcType, handler);
      typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

只不过外层的方法, 会通过各种途径获取该方法的三个参数.

  1. javaType: 可以通过typeHandler子节点直接指定, @MappedTypes指定, 获取TypeReference泛型参数指定.
  2. jdbcType: 可以通过typeHandler子节点直接指定, @MappedJdbcTypes指定
  3. handler: 通过package, typeHandler节点进行指定.

但是仍旧不能保证使用者配置了前两个参数. 因此进入该方法也就会有为空的情况.

此处的register方法, 使用到了两个Map类型, 他们的定义分别为:

  • private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
  • private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();

如果javaType存在, typeHandlerMap中需要缓存其映射的jdbc类型以及转换器.
最后allTypeHandlersMap, 缓存类型转换器类型以及其实例.

总结

  1. 外层三个重载方法之间的关系
  2. 内部三个重载方法之间的关系
  3. typeHandler如何实例化, getInstance的逻辑
  4. MappedTypesMappedJdbcTypes的作用, 与作用时机
  5. 存储映射关系的数据结构
What do you think?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v3.0.0-alpha.10