回顾
上一篇分析了几个反射工具,还有对类数据的封装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的字段
- jdbcTypeHandlerMap:JdbcType与TypeHandler之间的对应关系,其中JdbcType是一个枚举类型(MySQL的数据类型有限的,可以使用枚举来表示),TypeHandler负责就是将JdbcType转换成Java类型
- typeHandlerMap:Type与Map<JdbcType,TypeHandler>之间的对应关系,这里的Type是一个接口,代表了所有的Java对象,而Type对应的Map,则是说明这个Java对象需要使用哪些Jdbc的TypeHandler来转换,说白了就是SQL结果集转换成这个Java对象需要用到的Jdbc的TypeHandler
- unknownTypeHandler:顾名思义,针对不知道Java具体类型的TypeHandler,所以这里使用的是Object为泛型
- allTypeHandlersMap:Class对象与TypeHandler的关系,这个容器记录了每一个Class类型对应的TypeHandler,说白了就是所有的TypeHandler,里面存储着转换成各种Java类型的TypeHandler
- NULL_TYPE_HANDLER_MAP:空的TypeHandler的集合,一个EmptyMap
- 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统一了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了相应的适配器
而在日志模块里面采用了适配器模式,下面先回忆一点设计模式方面的东西
设计模式——六大原则
- 单一职责原则:一个类只负责唯一项职责,不要存在多个导致类变更的原因
- 里氏替换原则:
- 依赖倒置原则:依赖抽象,而不是依赖具体
- 接口隔离原则:一个类对另一个类的依赖应该建立在最小的接口上,说白了就是让接口单一职责
- 迪米特法则:减低类间的耦合,一个对象应该对其他对象保持最少的了解
- 开放-封闭原则:程序对扩展开放,对修改关闭
日志适配器
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的功能主要分为三类,其他的都是该方法的重载方法或者其他
- getResourceAsURL:获取指定路径的URL资源
- getResourceAsStream:获取指定路径的资源
- classForName:获取类的名字
ResolverUtil
ResolverUtil可以根据指定的条件查找指定包下的类,其中使用的条件由Test接口表示,也就是说Test接口用来校验当前类是否符合天条件的
可以看到,Test接口是ResolverUtil的一个内部接口,并且只有一个返回布尔类型的matches方法,该方法就是用来校验是否符合规则的
并且该接口的实现类也只有两个,同样也是位于ResolverUtil下的
两个实现类分别起如下作用
- IsA:检测类是否继承了指定类或者实现了指定接口
- 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
有兴趣可以看下是如何实现的。。。。。。这里就不看了