MyBatis(技术NeiMu):基础支持层(类型转换、日志模块与类加载模块)

回顾

上一篇分析了几个反射工具,还有对类数据的封装MetaClass,对实例数据的封装ObjectWrapper和创建ObjectWrappe的ObjectWrapperFactory

MetaObject

ObjectWrapper对实例信息进行了封装,并且其封装的实例统一抽象成了一个MetaObject
在这里插入图片描述
也就是说Java Bean其实位于MetaObject里面

在这里插入图片描述
MetaObject里面拥有的成员属性

  • originalObject:原始JavaBean对象
  • objectWrapper:封装该MetaObject的wrapper
  • ObjectFactory:创建originalObject的工厂对象
  • ObejctWrapperFactory:创建ObjectWrapper的
  • ReflectorFactory:创建并缓存Reflector的

构造方法如下

  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
      //初始化上面提到的字段
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

      //如果本身就是一个ObjectWrapper直接使用
    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } 
      //判断ObjectWrapperFactory能不能建立该实例的ObjectWrapper
      else if (objectWrapperFactory.hasWrapperFor(object)) {
          //可以创建就使用ObjectWrapperFactory来创建ObjectWrapper
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } 
      //后面根据类型来进行创建ObjectWrapper
      else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }

类型转换

JDBC的数据类型与Java语言中的数据类型并不是完全对应的,所以两者需要进行互相转换

  • JDBC类型转化为Java类型:从结果集中获取数据的时候
  • Java类型转化为JDBC类型:在prepareStatement为SQL语句绑定参数时

对于类型转换,MyBatis采用了TypeHandler来进行处理

TypeHandler

在这里插入图片描述
这是一个接口,里面有4个方法

  • setParameter:通过PreparedStatement为SQL语句绑定参数时,会将数据由JdbcType转换成Java类型
  • getResult:从ResultSet结果集中获取数据,然后转化成对应JavaBean

在这里插入图片描述
可以看到有很多的TypeHandler,可以看到MyBatis给各种数据类型都创建了一个TypeHandler,比如String、Boolean、Float、Byte等。。。。。。

可以看到,这里就采用了一个模板方法了!!!!!!

比如BaseTypeHandler的setParameter方法,源码如下

  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
      //判断是否为空
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
          //如果参数为空,setNull即可
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
              + "Cause: " + e, e);
      }
    } else {
      try {
          //如果不为空,使用setNonNullParameter方法
          //该方法为抽象方法,延迟到子类去实现的!!!!!!
          //所以setParameter方法就是一个模板
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different configuration property. "
              + "Cause: " + e, e);
      }
    }
  }

在这里插入图片描述
可以看到,MyBatis对于Java Bean转换为SQL参数,采用的是模板方法,交由子类去实现setNonNullParamerer(Java Bean不为Null时的转换)

下面再看一下getResult方法,这个方法时是用来获取CallableStatement的

先说明一下getResult方法有两个,一个是ResultSet的,另外一个是CallableStatement的

  • ResultSet:普通的结果集
  • CallableStatement:回调函数、也就是触发器的结果集
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
  try {
      //调用getNullableResult
    return getNullableResult(rs, columnIndex);
  } catch (Exception e) {
    throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
  }
}
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
  try {
      //调用getNullableResult
    return getNullableResult(cs, columnIndex);
  } catch (Exception e) {
    throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
  }
}

在这里插入图片描述
可以看到,对于从SQL结果集中获取值并且转换为JavaBean,同样是延迟到子类去实现的,具体来说就是子类去实现getNullableResult方法,所以,对于getResult也是一个模板方法

TypeHandler用于完成单个参数以及单个列值的类型转换,并且BaseTypeHandler是所有的类型的TypeHandler一个模板,具体有两个方法,一个是Java类型映射到SQL参数上的setParameter方法,这个方法对于非空的Java类型会委派到setNonNullParameter方法去进行,而该方法是延迟到子类去实现的;还有一个是SQL结果集映射成Java类型的getResult方法,该方法会执行getNullableResult方法,而getNullableResult方法也是延迟到子类去实现的,所以setParameter和getResult是一个模板方法

这样的好处就是不用写冗余的代码,像这里具体的冗余代码就是捕捉异常处理,也就是映射失败,子类只需要关心映射逻辑即可。

TypeHandlerRegistry

有了一系列的TypeHandler之后,MyBatis是如何管理这一系列的TypeHandler的,又是如何去调用这一系列的TypeHandle的?

MyBatis对于TypeHandler的管理和调用是交由TypeHandlerRegistry来负责的

在这里插入图片描述
先来看一下TypeHandlerRegistry的字段

  1. jdbcTypeHandlerMap:JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型(MySQL的数据类型有限的,可以使用枚举来表示),TypeHandler负责就是将JdbcType转换成Java类型
  2. typeHandlerMap:Type与Map<JdbcType,TypeHandler>之间的对应关系,这里的Type是一个接口,代表了所有的Java对象,而Type对应的Map,则是说明这个Java对象需要使用哪些Jdbc的TypeHandler来转换,说白了就是SQL结果集转换成这个Java对象需要用到的Jdbc的TypeHandler
  3. unknownTypeHandler:顾名思义,针对不知道Java具体类型的TypeHandler,所以这里使用的是Object为泛型
  4. allTypeHandlersMap:Class对象与TypeHandler的关系,这个容器记录了每一个Class类型对应的TypeHandler,说白了就是所有的TypeHandler,里面存储着转换成各种Java类型的TypeHandler
  5. NULL_TYPE_HANDLER_MAP:空的TypeHandler的集合,一个EmptyMap
  6. defaultEnumTypeHandler:默认的枚举类型的TypeHandler类型
注册TypeHandler对象

可以看到,在TypeHandlerRegistry里面有众多的容器来管理TypeHandler,下面就来看看TypeHandler是如何注册进来的

对应的方法就是registry方法

在这里插入图片描述
不过可以看到,registry方法有好多种重载

而大多数的registry方法都会调用以下这个重载registry

在这里插入图片描述
这个方法就是给对应的java类型添加对应的JDBC的TypeHandler,该方法源码如下

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    //判断传进来的Java类型是否为Null
    if (javaType != null) {
        //如果不为Null,可以进行添加
        //先获取该Java类型的所有JDBC的TypeHandler的集合
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        //判空
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
          //如果为空,代表第一次添加,直接新创建一个
        map = new HashMap<>();
      }
        //如果不为空,往该Java类型的所有JDBC的TypeHandler集中去添加新的Handler
      map.put(jdbcType, handler);
        //同时再次去更新typeHandlerMap,重新put该javaType与TypeHandler集合的映射关系
        //因为当第一次创建的时候,是没有这个映射关系的
      typeHandlerMap.put(javaType, map);
    }
    //然后更新所有的TypeHandler容器,去添加新加入的TypeHandler
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

可以看到,这个Registry方法逻辑并不难仅仅只是维护了typeHandlerMap与allTypeHandlerMap容器而已,也就完成了注册功能了

接下来我们看看其构造方法,有点恐怖,其实构造方法仅仅是提前将基本的TypeHandler注册进去而已

 public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    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 StringTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new StringTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, unknownTypeHandler);
    register(Object.class, JdbcType.OTHER, unknownTypeHandler);
    register(JdbcType.OTHER, unknownTypeHandler);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

    register(Instant.class, new InstantTypeHandler());
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
    register(LocalDate.class, new LocalDateTypeHandler());
    register(LocalTime.class, new LocalTimeTypeHandler());
    register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
    register(OffsetTime.class, new OffsetTimeTypeHandler());
    register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
    register(Month.class, new MonthTypeHandler());
    register(Year.class, new YearTypeHandler());
    register(YearMonth.class, new YearMonthTypeHandler());
    register(JapaneseDate.class, new JapaneseDateTypeHandler());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

当然,除了MyBatis自身提供的TypeHandler实现,我们也可以添加自定义的TypeHandler,然后在配置文件的标签下进行添加配置即可,在MyBatis初始化这个节点时就会将自定义的TypeHandler注册进TypeHandlerRegistry中去

TypeAliasRegistry

还记得我们在MyBatis的配置文件下,可以开启别名,然后在SQL的映射文件中,就可以使用该类的别名了,不需要使用类的全限定类名来进行映射,那么这些别名在哪里管理的呢?

MyBatis使用TypeAliasRegistry来进行管理别名

在这里插入图片描述
可以看到,TypeAliasRegistry是使用HashMap来管理别名的,并且Key为别名,而Value为对应的类,这样设计的好处可以让类与别名产生一对多的关系

添加别名的方法为registerAlias,当然这个方法也有着很多重载

注册别名

我们先看用的最多的registerAlias,源码如下

public void registerAlias(String alias, Class<?> value) {
    //判断别名是否为空,如果为空,就不能添加了
    if (alias == null) {
        //抛错
      throw new TypeException("The parameter alias cannot be null");
    }
    // 将别名转为小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    //判断是否已经有该别名了,如果有并且别名对应的类还不是同一个类,抛错
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    //添加进typeAlias中
    typeAliases.put(key, value);
  }

可以看到,添加别名就是简单做一些校验然后添加进底层的typeAlias里面中去

看完这个方法,我们就可以知道该TypeAliasRegistry唯一的构造方法究竟干了什么了

public TypeAliasRegistry() {
    //字符串别名
    registerAlias("string", String.class);
	//基本数据类型
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
	//八大基本数据类型的数组类型
    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);
	//支持八大基本数据类型的下划线别名
    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);
	//支持八大基本数据类型数组的下划线别名
    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);
	//对容日期、精确数据类型支持
    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);
	//对应的数组支持
    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);
	//集合支持
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);
	//ResultSet支持
    registerAlias("ResultSet", ResultSet.class);
  }

可以看到,在构造方法上会将字符串、基本数组类型、对应的基本类型数组和集合类的别名给注册进去,并且支持下划线命名

日志模块

MyBatis统一了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了相应的适配器

而在日志模块里面采用了适配器模式,下面先回忆一点设计模式方面的东西

设计模式——六大原则

  1. 单一职责原则:一个类只负责唯一项职责,不要存在多个导致类变更的原因
  2. 里氏替换原则:
  3. 依赖倒置原则:依赖抽象,而不是依赖具体
  4. 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上,说白了就是让接口单一职责
  5. 迪米特法则:减低类间的耦合,一个对象应该对其他对象保持最少的了解
  6. 开放-封闭原则:程序对扩展开放,对修改关闭

日志适配器

MyBatis使用适配器模式来完成日志模块,适配器的统一接口就是Log接口

在这里插入图片描述
可以看到,该日志适配器接口其实就是定义了几种等级的输出而已

  • error
  • debug
  • tarce
  • warn

实现了日志适配器接口的类就成为了日志适配器

在这里插入图片描述
可以看到,MyBatis默认已经有一堆适配器了,包括Log4j2、Slf4j还有Jdk等

那么适配器是在哪里创建并使用的呢?

MyBatis将日志适配器的创建交由了LogFactory来负责

在这里插入图片描述
可以看到,里面仅仅有一个具有意义的Constructor成员属性,该成员属性就是日志适配器的Constructor
在这里插入图片描述
我们先看这个static代码块,因为这个会优先执行,源码如下

static {
    //尝试加载每种日志组件,并且最后加载的为useNoLogging
    //并且可以看到,参数为一个Runable接口,只有一个void的无参run方法
    //参数使用的是Lambda表达式,传进来的是LogFactory的各种日志加载方法
    tryImplementation(LogFactory::useSlf4jLogging);
    tryImplementation(LogFactory::useCommonsLogging);
    tryImplementation(LogFactory::useLog4J2Logging);
    tryImplementation(LogFactory::useLog4JLogging);
    tryImplementation(LogFactory::useJdkLogging);
    tryImplementation(LogFactory::useNoLogging);
  }

可以看到,这个静态代码块,对各个日志适配器都加载了一次

在这里插入图片描述
而tryImplementation方法仅仅只是调用了传来的Runnable的run方法,所以具体的实际逻辑位于各种use方法上

在这里插入图片描述
可以看到,各种use方法仅仅只是调用setImplementation方法,并且给了各种日志适配器的Class对象为参数而已

setImplementation方法源码如下所示

private static void setImplementation(Class<? extends Log> implClass) {
    try {
        //获取候选构造器,并且参数只有一个String的构造器
      Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
        //使用上面的构造器进行实例化,并且参数为LogFactory.class.getName
      Log log = candidate.newInstance(LogFactory.class.getName());
        //开启日志
      if (log.isDebugEnabled()) {
        log.debug("Logging initialized using '" + implClass + "' adapter.");
      }
        //让logConstructor为候选构造器
      logConstructor = candidate;
    } catch (Throwable t) {
      throw new LogException("Error setting Log implementation.  Cause: " + t, t);
    }
  }

可以看到,对于setImplementation方法仅仅只是尝试进行实例化了一下,然后将当前的日志适配器的构造器赋值给logConstructor,那么对于static块里面的代码,最后一个赋值给logConstructor的是useNoLogging

在这里插入图片描述
那么logConstructor就等于这个NoLoggingImpl类的构造器了,这个日志适配器从名字大概可以知道,是不会支持日志功能的

在这里插入图片描述
事实上也是如此,重写了log接口里面的各种等级的日志输出功能,却什么都不干。。。。。。。

从LogFactory就体现出了一个适配器模式了,日志适配器使用了一个Log接口抽象了起来,然后LogFactory组装了Log接口的构造器来进行使用日志适配器,通过注入日志适配器,LogerFactory就能使用并创建该日志适配器,直接使用LoggerFactory.getLog方法就可以获取日志适配器来进行使用了,并且只要在配置文件上修改LoggerFactory的注入,所有的logger就会被改变

类加载模块

MyBatis对于类加载器也进行了封装,暴露了一些对应类加载器的API来方便自己使用,对应的是ClassLoaderWrapper

在这里插入图片描述
之前在JVM文章中,我们知道类加载器是使用双亲委派原型的,并且默认的系统类加载器为应用加载器,支持大部分的类进行加载的
在这里插入图片描述
ClassLoader的功能主要分为三类,其他的都是该方法的重载方法或者其他

  1. getResourceAsURL:获取指定路径的URL资源
  2. getResourceAsStream:获取指定路径的资源
  3. classForName:获取类的名字

ResolverUtil

ResolverUtil可以根据指定的条件查找指定包下的类,其中使用的条件由Test接口表示,也就是说Test接口用来校验当前类是否符合天条件的

可以看到,Test接口是ResolverUtil的一个内部接口,并且只有一个返回布尔类型的matches方法,该方法就是用来校验是否符合规则的

在这里插入图片描述
并且该接口的实现类也只有两个,同样也是位于ResolverUtil下的
在这里插入图片描述
两个实现类分别起如下作用

  1. IsA:检测类是否继承了指定类或者实现了指定接口
  2. AnnotatedWith:检测类是否添加了指定的注解

IsA

IsA用来判断是否继承了指定父类或者实现了指定接口

该类的实现也很简单

在这里插入图片描述
通过构造方法注入指定父类或者指定接口,然后调用isAssignableForm方法来进行判断(isAssignableForm是一个native方法,用于判断该Class是不是参数Class的父类或者实现的接口)

AnnotatedWith

AnnotatedWith用来判断指定类是否被贴上了指定注解

在这里插入图片描述
可以看到,其也只是简单通过构造方法注入指定注解,然后通过isAnnotationPresent的方式进行判断

所以,ResolverUtil支持寻找特定的子类或实现类、同时也支持寻找贴上了特定注解的类

下面就来看看ResolverUtil是怎么实现这些功能的

下面是findImplementations方法,用于寻找指定类的子类或者指定接口的实现类

在这里插入图片描述
下面是findAnnotated方法,用于寻找指定注解被贴上的类
在这里插入图片描述
我们可以看到这两个方法都是基于find方法来实现的,本身仅仅只是做了一个遍历操作而已,所以重点还是在find方法上,是如何使用Test接口和pkg的(pkg是当前的路径)

find方法的源码如下

 public ResolverUtil<T> find(Test test, String packageName) {
    //根据报名获取对应的路径,注意这里是包名
    String path = getPackagePath(packageName);

    try {
        //通过VFS.list方法查找packageName包下的所有资源
      List<String> children = VFS.getInstance().list(path);
        //遍历这些资源
      for (String child : children) {
          //如果后缀是.class文件,代表是Java资源
        if (child.endsWith(".class")) {
            //使用addIfMatching来进行判断是否符合条件
            //并且传进来的是一个Class资源的路径!!!!
          addIfMatching(test, child);
        }
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }

    return this;
  }

步骤如下

  • 包名转换为路径
  • 通过VFS.list方法查找路径下的所有资源
  • 遍历所有资源,筛选出.class的资源出来
  • 通过addIfMatching方法来进行检测筛选出的.class资源是否符合条件

在这里插入图片描述
可以看到,包名转换为路径很简单,仅仅只是将.替换成/而已,下面再来看看是如何使用Test来进行校验的

addIfMatching的源码如下

 protected void addIfMatching(Test test, String fqn) {
    try {
        //解析传来的Class资源路径,到后缀的.的部分,将斜杆变回.
        //相当于将资源路径又变回了一个包路径
      String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
        //获取ClassLoader
      ClassLoader loader = getClassLoader();
      if (log.isDebugEnabled()) {
        log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
      }
		//适应ClassLoader来进行加载Class文件资源,得到Class对象
      Class<?> type = loader.loadClass(externalName);
        //使用传进来的Test接口来进行校验
      if (test.matches(type)) {
          //如果满足就添加进matches集合中
        matches.add((Class<T>) type);
      }
    } catch (Throwable t) {
      log.warn("Could not examine class '" + fqn + "'" + " due to a "
          + t.getClass().getName() + " with message: " + t.getMessage());
    }
  }

该方法的逻辑如下

  • 解析资源的路径名字,转换为包名,具体的做法是将截取后缀前面的字符串,然后将斜杠替换成点
  • 使用ClassLoader来进行加载包名,得到Class对象
  • 使用Test接口进行校验,如果符合规则就添加进matches集合中去

对于解析包名到路径名、然后再进行路径名转换为包名然后给Test接口进行解析,都是比较简单的。

重点在于VFS这个对象,是如何进行寻找的,并且使用getInstance方法来获取,难道VFS是一个单例吗?

VFS

根据前面的代码,我们可以得知,VFS可以帮我们根据路径名来找到所有的资源

之前我们学习过的单例模式

  • 懒汉:延迟加载
  • 饿汉:立刻加载
  • 静态内部类:使用类机制来保证并发问题,并且可以延迟加载(因为只有访问了类才会加载)

什么是VFS呢?

VFS是Virtual File System的缩写,专门用来查找指定路径下的资源的,并且在MyBatis中,VFS是全局单例的

下面就来看看专家是怎么使用单例的[旺柴]

首先我们可以看到,这个类是一个抽象类

在这里插入图片描述
并且其getInstance方法是直接返回静态私有内部类(VFS.Holder)的静态成员属性的,所以从这里我们就可以知道,MyBatis使用的是内部类的形式来保证单例
在这里插入图片描述
在这里插入图片描述
可以看到,MyBatis保证单例使用的是静态内部类的方式,其实这种方式感觉简洁明了,并且交由类加载机制去保证并发创建的问题,可以说是最好的方式也不为过

下面就来看看create方法

static VFS createVFS() {
      // 此时进来的是第一次创建
     //优先使用用户自定义的VFS实现,如果没有自定义VFS实现,则使用MyBatis提供的VFS实现
      List<Class<? extends VFS>> impls = new ArrayList<>();
    //USER_IMPLEMENTATIONS就是用户自定义的
      impls.addAll(USER_IMPLEMENTATIONS);
    //IMPLEMENTATIONS就是默认实现的
      impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));

      //遍历找到implas集合,依次实例化VFS对象并且检测VFS对象是否有效
    //一旦得到有效的VFS对象,则结束循环
      VFS vfs = null;
    //可以看到,当vfs.isValid返回true时,就会结束for循环
    //所以这里是取第一个校验通过的vfs
      for (int i = 0; vfs == null || !vfs.isValid(); i++) {
        Class<? extends VFS> impl = impls.get(i);
        try {
          vfs = impl.getDeclaredConstructor().newInstance();
          if (!vfs.isValid() && log.isDebugEnabled()) {
            log.debug("VFS implementation " + impl.getName()
                + " is not valid in this environment.");
          }
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
          log.error("Failed to instantiate " + impl, e);
          return null;
        }
      }

      if (log.isDebugEnabled()) {
        log.debug("Using VFS adapter " + vfs.getClass().getName());
      }

      return vfs;
    }

可以看到,MyBatis对于VFS会优先采用USER_implementation,也就是用户实现的VFS,然后再去考虑MyBatis的默认实现IMPLEMENTATION,优先选择的实现是先把用户实现的进行添加到List,后续再添加默认的实现,然后遍历List,只需要第一个校验通过的VFS

下面我们再看一下VFS是如何进行搜索的,对应的也就是list方法

可以看到,对于list方法是延迟到VFS的子类去实现的

在这里插入图片描述
而默认的VFS子类有两个
在这里插入图片描述
分别是

  • DefaultVFS
  • JBoss6VFS

有兴趣可以看下是如何实现的。。。。。。这里就不看了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值