MyBatis 源码解析(四)MyBatis如何解析配置 ?(四)

这篇博客是对 MyBatis
解析配置文件的第三部分,不出意外应该是最后一篇,今天我们来分析 类型处理器
Mapper
映射

在看 类型处理器
之前,我们需要了解一个小知识,那就是 JdbcType
JdbcType
MyBatis
里面的一个枚举类型,看源代码其实就是将 java.sql.Types
封装了一遍,那这个类是用来干嘛的呢?

MyBatis
底层是通过 JDBC
来实现的,当通过 JDBC
插入一段数据的时候,如果这个数据为 null
,那么有两种写法:

//第一种方法
PreparedStatement##setString(index,null);
//第二种方法
PreparedStatement##setNull(index, Types.VARCHAR);

按道理来说,第一种方法其实比较方便,第二种方法需要指定 Types
MyBatis
需要指定 JdbcType
的缘由就来源于此,那为什么不用第一种方法呢?

查看 PreparedStatement
的方法,当涉及到基本数据类型的时候,它的申明如下

void setLong(int parameterIndex, long x)

此时如果你传入一个 Null
,那么 Long
会自动拆箱,然后抛出 NullPointerException
,则便是问题所在。

因此 MyBatis
说明,当在增加,插入,更新数据的时候,如果这个数据可能为 Null
,那么应该指定 JdbcType

继续看源码

XMLConfiguration###parseConfiguration()

//调用各个方法进行解析成Configuration对象 
private void parseConfiguration(XNode root) {
    try {
      //读取用户自定义属性
      propertiesElement(root.evalNode("properties"));
      //读取用户的设置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //加载用户自定义的VFS实现  
      loadCustomVfs(settings);
      //加载日志设置
      loadCustomLogImpl(settings);
      //加载用户定义的别名  
      typeAliasesElement(root.evalNode("typeAliases"));
      //加载用户定义的插件
      pluginElement(root.evalNode("plugins"));
      //加载用户定义的对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //加载用户定义的反射对象工厂
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //加载用户定义的其他设置
      settingsElement(settings);
      //加载用户定义的环境变量
      environmentsElement(root.evalNode("environments"));
      //读取databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //处理类型处理器  
      typeHandlerElement(root.evalNode("typeHandlers"));、
      //处理mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

XMLConfigBuilder###typeHandlerElement()

//<typeHandlers>
//    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.dengchengchao.demo.type.Enabled"/>
//    <typeHandler handler="com.dengchengchao.demo.Handler"/>
//</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");
          //调用resolveClass加载Class
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          //调用resolveJdbcType加载JdbcType  
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          //加载类型处理  
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);

          if (javaTypeClass != null) {  
            if (jdbcType == null) {  
              //如果只指定了javaType 
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              // 当XML中指定了javaTypeClass,并且指定了jdbcType的时候,直接使用这两个值注册
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            //如果只指定了typeHandler  
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

首先,源代码中有两点疑问:

第一:我们可以发现 typeHandler
是可以通过注解配置其他信息,并且通过指定 package
MyBatis
自动扫描,那么 MyBatis
是如果获取到类上的注解信息的

第二:我们从后面的各种 if-else
中发现, MyBatis
TypeHandler
的处理是非常宽松的,哪怕仅仅指定一个 typeHandlerClass
也行,那 MyBatis
是如何处理其他的默认值的呢?

带着这两个疑问,我们来看后面的代码:

TypeHandlerRegister###register(packageName)

public void register(String packageName) {
    //和前面解析别名一样,通过`VFS`读取包中所有的类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    //使用IsA进行过滤,只需要实现了`TypeHandler`接口的类
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    //遍历,然后注册。同理,和解析别名一样,不需要匿名类,接口,以及抽象类  
    for (Class<?> type : handlerSet) {
      //Ignore inner classes and interfaces (including package-info.java) and abstract classes
      if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
        register(type);
      }
    }
  }

可以看到,这里获取到类型之后,直接调用了 register(type)
,这里调用的方法估计和上面只指定 typeHandler
调用的方法一样,下面详细看看:

TypeHandlerRegister###register(typeHandlerClass)

public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    //获取MappedType注解
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    //如果找到了,则获取它所指定的`javaTypeClass`,然后进行注册
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    //如果没有找到注解
    if (!mappedTypeFound) {
      //直接加载`typeHandlerClass`
      register(getInstance(null, typeHandlerClass));
    }
  }

TypeHandlerRegister###getInstance()

public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    //如果javaType不是null
    if (javaTypeClass != null) {
        try {
            //将javaType作为构造参数初始化`typeHandler`
            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);
        }
    }
    //如果javaType是null,那么直接调用空构造方法
    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);
    }
}

这里简单看下,就是通过反射创建一个新的对象而已

更加疑惑了,新建对象干什么???

接着往下看

TypeHandlerRegister###register(TypeHandler )

@SuppressWarnings("unchecked")
  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;
      }
    }
    //如果没有找到,则判断其能否强制转换为`TypeReference`
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        //如果能,则转换为TypeReference并调用getRawType获取其javaType  
        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
      }
    }
    //如果没有找到,则JavaType为null  
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }

不指定 JavaClassType
TypeHandler
的源码到这里就结束了,说到底还是通过各种方法查找 JavaType
,然后调用 register
注册,那这个“各种方法”究竟是什么方法?

MappedType

第二种方式就比较新奇了,泛型不是通过擦除实现的么?怎么能和反射联系起来呢?

首先我们看看如何自己实现 TypeHandler

一般来说,需要实现自定义的 TypeHandler
,只需要直接继承 BaseTypeHandler<T>

public class Handler extends BaseTypeHandler<String> {

    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, String integer, JdbcType jdbcType) throws SQLException {
        System.out.println("**********set**********");
        log.info("***********************************");
        preparedStatement.setString(i, integer);
    }

    //.....其他方法
}

我们也可以直接实现 TypeHandler<T>
接口:

public interface TypeHandler<T> {
    void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;

    T getResult(ResultSet var1, String var2) throws SQLException;

    T getResult(ResultSet var1, int var2) throws SQLException;

    T getResult(CallableStatement var1, int var2) throws SQLException;
}

可以看到, TypeHandler<T>
是一个泛型方法,而这个 T
即使真正的对应的 JavaType
,我们都知道 Java
的泛型是通过擦除实现的,也就是运行过程中是找不到泛型的方法的,但是如果有什么方法能够知道这个 T
具体的类型,那么我们就能够知道 JavaType
了。

那这 BaseTypeHandler<T>
相比 TypeHandler
有什么默认的方法呢?

找到 BaseTypeHandler<T>
的定义,我们可以发现 BaseTypeHandler<T>
继承自 TypeReference
,而且里面实现了一些通用的方法,继续看 TypeReference
的定义,里面仅仅有一个方法:

Type getSuperclassTypeParameter(Class<?> clazz) {
    Type genericSuperclass = clazz.getGenericSuperclass();
    if (genericSuperclass instanceof Class) {
        if (TypeReference.class != genericSuperclass) {
            return this.getSuperclassTypeParameter(clazz.getSuperclass());
        } else {
            throw new TypeException("'" + this.getClass() + "' extends TypeReference but misses the type parameter. " + "Remove the extension or add a type parameter to it.");
        }
    } else {
        Type rawType = ((ParameterizedType)genericSuperclass).getActualTypeArguments()[0];
        if (rawType instanceof ParameterizedType) {
            rawType = ((ParameterizedType)rawType).getRawType();
        }

        return rawType;
    }
}

TypeHandlerRegister
register
方法中我们可以发现, register
过程中一直在努力找到 TypeHandler
JavaType
,最后有一步便是判断 TypeHander
实例能否强制转换为 TypeReference
,如果能转换,则直接转换为 TypeReference
,然后调用 getRawType()
方法即可。

也就是说这个类可以获取其 泛型的具体类型
,这便是如果继承这个类,那么就可以不指定 JavaType
了的奥秘。

题外话:这个类是如何工作的呢?

这里不详细解释,先放两个链接:

Java为什么要添加运行时获取泛型的方法? – RednaxelaFX的回答 – 知乎

Class的 getSuperclass与getGenericSuperclass区别

简单解释下就是如果一个类继承一个泛型类,并且这个类不是泛型,那么 Java
编译过程中是会保留这个类的具体类型的,并且可以通过反射获取到具体的泛型类型。

而继承 BaseTypeHandler<T>
实现 TypeHandler
是需要指定具体的类型的,因此可以通过此方法获取到具体泛型的类型。

那么,如果你不是继承 TypeReference
,而是直接实现的 TypeHandler
接口,那么 MyBatis
是无法自动获取 JavaType
的,此时如果不指定 JavaType
,那么 MyBatis
便会注册一个 null
到注册器中

总结一下 register()
流程:

首先,获取 XML
的配置信息,拿到 TypeHandler
JavaType
JDBCType

如果, JavaType
JDBCType
为空,则先想办法获取到 JavaType

获取 JavaType
的方式首先检查注解,如果注解没有,则如果 TypeHandler
是通过继承 TypeReference
实现,那么可以通过反射自动获取 JavaType
,如果不是继承 TypeReference
,则注册 javaType
null

接下来如果 JDBCType
也没有指定,那么会先查看是否有 MappedJdbcTypes
注解,如果没有注解,则将 JDBCType
注册为 null

TypeHandlerRegistry###register()

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);
    }
  }

TypeHandlerRegistry###register()

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<>();
        typeHandlerMap.put(javaType, map);
      }
      map.put(jdbcType, handler);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

关于 TypeHandler
的配置解析就到这里,关于 TypeHandler
的具体使用的代码后续会专门写一篇文章。这里先不赘述。下面简单总结一下:

  • 第一,首先 MyBatis
    的优先级依然是 XML
    文件,当 XML
    文件中指定了 JavaType
    时,则会先忽略掉注解的配置
  • 第二, MyBatis
    可以通过反射自动装载 JavaType
    ,因此如果是继承自 TypeReference
    ,那么可以不指定 JavaType
  • 第三,当注册 TypeHandler
    不指定 JdbcType
    的时候, TypeHandler
    能够匹配所有不指定 jdbcType
    的字段,比如 {#id,jdbcType=LONGVARCHAR}
    便无法匹配。如果注册时候指定了 JdbcType
    ,则只能匹配指定了对应的字段
  • 第四,不知道为什么,通过注解配置的时候,值是一个数组,也就是说可以同时配置多个字, @MappedTypes({ String.class,Long.class,})
    ,但是貌似并没有什么用,因为 Type
    不对应实际转换的时候会报错。

原文 

http://dengchengchao.com/?p=1177

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » MyBatis 源码解析(四)MyBatis如何解析配置 ?(四)

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址