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

作者: java高级架构F六 | 来源:发表于2020-01-02 16:09 被阅读0次

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

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

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

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

    按道理来说,第一种方法其实比较方便,第二种方法需要指定TypesMyBatis需要指定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是可以通过注解配置其他信息,并且通过指定packageMyBatis自动扫描,那么MyBatis是如果获取到类上的注解信息的

    第二:我们从后面的各种if-else中发现,MyBatisTypeHandler的处理是非常宽松的,哪怕仅仅指定一个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<t style="box-sizing: border-box;">)</t>

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

    不指定JavaClassTypeTypeHandler的源码到这里就结束了,说到底还是通过各种方法查找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;
        }
    }
    
    

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

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


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

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

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


    总结一下register()流程:

    首先,获取XML的配置信息,拿到TypeHandlerJavaTypeJDBCType

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

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


    接下来如果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不对应实际转换的时候会报错。

    相关文章

      网友评论

        本文标题:MyBatis 源码解析MyBatis如何解析配置 ?(四)

        本文链接:https://www.haomeiwen.com/subject/adjooctx.html