原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6705769.html

1、回顾

  前面几篇讲了数据源模块,这和之前的事务模块都是environment中的组成部分,而Environgment是Configuration的基础部分,是构建Configuration的基石,但是有了基石只是能构建一个简单的配置对象,要适应实际的使用环境,还需要额外的组件,这些东西都需要添加到配置对象中,这一次就介绍类型模块—Type。

  类型模块的具体内容基本上都在org.apache.ibatis.type包下,类型模块也包括不少内容,主要的就是类型的别名注册器与类型处理器注册器,这两个注册器是用来统筹规划所有类型别名和类型处理器的,之后会将这两个注册器的实例配置到Configuration对象中;除此之外基本上是具体的类型处理器的实现类了,MyBatis内置了非常全面的类型处理器实现,当然我们也可以自定义实现类型处理器,经过简单的配置就可以使之生效。

  这一篇我们只研究类型别名注册器TypeAliasRegistry。

2、类型别名TypeAlias

  什么是类型别名呢?

  MyBatis中的类型别名就是针对MyBatis中常用的类型进行别名设置,使用别名来代替具体的类型,简单点说就是,将具体的类型以别名为键,保存到一个HashMap之中,方便存取。

  类型别名的用途是什么?

  MyBatis中的类型别名主要用于取代复杂的类型全限定名,用于映射器配置文件中进行参数类型与返回结果类型的设置,MyBatis会在进行数据库操作之前进行参数类型别名的解析操作获取具体的参数类型,又会在数据库操作之后进行结果类型别名的解析获取具体的结果类型,再通过之后要研究的类型处理器进行类型处理来将参数和结果分别进行匹配映射。

2.1 基础类型别名

  那么MyBatis中内置的别名到底有哪些呢?我们来看看源码:

 //构造函数里注册系统内置的类型别名
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);
}

  从上面的源码中我们可以看出,在类型别名注册器类TypeAliasRegistry的无参构造器中进行了大量的基础类型别名的注册(设置),涉及到的有:

    1.字符串类型(别名类似string)

    2.基本类型包装器类型及其数组类型(别名类似byte、byte[])

    3.基本类型及其数组类型(别名类似_byte、_byte[])

    4.日期类型及其数组类型(别名类似date、date[])

    5.大数字类型及其数组类型(别名类似bigdecimal、bigdecimal[])

    6.Object类型及其数组类型(别名类似object、object[])

    7.集合类型(别名类似collection、map、list、hsahmap、arraylist、iterator)

    8.ResultSet结果集类型(别名为ResultSet)

  注意:这并不是全部的MyBatis内置的类型别名,还有一部分类型别名是在创建Configuration实例的时候在其无参构造器中进行注册的,这里暂不介绍。

2.2 内置方法

  MyBatis在TypeAliasRegistry类中定义了类型别名集合HashMap的存取方法:

2.2.1 存方法——注册方法

  类中最核心的类型别名注册方法就是:registerAlias(String alias, Class<?> value)方法:

     //注册类型别名
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
//如果已经存在key了,且value和之前不一致,报错
//这里逻辑略显复杂,感觉没必要,一个key对一个value呗,存在key直接报错不就得了
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
TYPE_ALIASES.put(key, value);
}

  上面的核心注册方法非常简单,参数分别为要设置的别名名称和要匹配的类类型,其中TYPE_ALIASES就是注册器中定义的用于保存类型别名的HashMap集合:

   private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  但这只是核心方法,其外围还包裹着两种注册方式,一种是包统一注册方式、一种是逐个注册的方式。这两种方式都是针对用户自定义别名注册而设计的。

2.2.1.1 包统一注册方式

  该种方式对应的是如下的设置方式:

  <typeAliases>
<package name="com.xx.xx.xx"/>
</typeAliases>

  注册细节详见下方源码:

   public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
} //扫描并注册包下所有继承于superType的类型别名
public void registerAliases(String packageName, Class<?> superType){
//TODO ResolverUtil
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
} //注册类型别名
public void registerAlias(Class<?> type) {
//如果没有类型别名,用Class.getSimpleName来注册
String alias = type.getSimpleName();
//或者通过Alias注解来注册(Class.getAnnotation)
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type);
}

  解析:

  上面的三个方法逐个调用,最后再调用核心注册方法进行最终的注册工作,让我们来看看这三个方法的目的所在。

  第一个方法中只有一句代码,表示注册指定包名下的所有类,它调用了第二个方法,其第二个参数目的是限定要注册的类的来源,只有继承自给定类型的类才能被注册,这里赋值为Object.class表示其下的所有类均在别名注册的考虑范围。

  第二个方法中内容较为复杂,这个方法的目的是为了限制要注册别名的类的范围,首先使用参数superClass来限制只有继承自该类的类才能进行类型别名注册,然后再排除内部类(包括匿名内部类、普通内部类)、接口类,通过调用第三个方法将剩余的指定类型(type)进行类型别名注册。

  注意:第二个方法中使用了MyBatis中定义的一个工具类ResolverUtil来进行实现,主要使用其进行指定包名下方类型的搜索find()与getClasses()方法

  第三个方法主要用于设置别名,其中有两种情况,第一种是针对未显式指定别名名称的类型,通过Class.getSimpleName()方法来获取类型的别名名称,其获取到的其实是类型的首字母小写形式的名称,另一种情况是针对使使用@Alias注解显式指定别名名称的类型(value的值),直接获取该注解中value的值作为别名名称即可,最后调用核心注册方法来实现类型别名的注册。

  上面的三个方法各有作用,第一个对外获取包名,第二个限制范围,第三个设置别名,其中我们可以不指定别名也可以使用注解来显式指定别名,不指定别名最后别名会调用本地方法来获取。

2.2.1.2 逐个注册方式

  该方式针对的是如下的设置方式:

  <typeAliases>
<typeAlias type="com.xxx.xx.Role" alias="role" />
3 </typeAliases>

  注册细节详见源码:

   public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);
}
}

  解析:

  这种方式很简单,主要是因为在设置中已经将目标与所要设置的别名名称都指定好了,因此只需要直接执行核心注册方法即可完成注册工作,这种方式适合少量的类型注册情况,但是一旦需要注册的类型较多,工作就会显得很是复杂繁琐,为了简化工作我们可以采用之前第一种方式,要采用这种方式就要在架构编码时有意的将需要进行类型别名注册的类放置到统一的包下。

2.2.2 取方法—解析方法

  首先罗列方法源码:

   public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
//先转成小写再解析
//这里转个小写也有bug?见748号bug(在google code上)
//https://code.google.com/p/mybatis/issues
//比如如果本地语言是Turkish,那i转成大写就不是I了,而是另外一个字符(İ)。这样土耳其的机器就用不了mybatis了!这是一个很大的bug,但是基本上每个人都会犯......
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
//原理就很简单了,从HashMap里找对应的键值,找到则返回类型别名对应的Class
if (TYPE_ALIASES.containsKey(key)) {
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
//找不到,再试着将String直接转成Class(这样怪不得我们也可以直接用java.lang.Integer的方式定义,也可以就int这么定义)
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}

  解析:

  结合注释内容,我们可以看出,所谓解析别名,就是通过别名获取集合中保存的别名对应的值,即类类型。

  需要注意的就是这里如果能够在集合中找到给定的别名(别名为键),则直接将对应的类型返回,但如果找不到别名,这里会尝试将别名直接转化成类型,能转化的别名必定是对应类型的全限定名的字符串形式(例如:"java.lang.Integer"模式的别名),这也就是我们在映射器文件中进行parameterType与ResultType设置的时候,可以写成"int"的方式,也可以写成"java.lang.Integer"的方式了。因为这里解析别名的时候会调用这个别名解析方法resolveAlias(),针对上述两种情况都可以实现正确解析。

3、模块分析

  所谓的模块分析是我新增的一个内容,主要研究一下设置某个模块的目的所在,这个模块在整个MyBatis系统架构中的地位与作用,这部分内容完全是本人理解,若有偏差与不足,还请不吝赐教,我会及时修正。

  类型别名也是MyBatis整个架构中比较重要的内容了,他一般是与类型处理器一起配合使用的。它的使用是在映射器配置文件中在配置SQL脚本的标签属性中。下面举个简单的示例:

 <delete id="delete" parameterType="int">
DELETE TB_USER u WHERE u.use_id = #{useId}
</delete>
<select id="select" parameterType="int" resulttype="com.xxx.xx.User">
SELECT * FROM TB_USER u WHERE u.use_id=#{useId}
</select>

  上面示例中的parameterType与resultType的值就是类型别名,他们都是以字符串的形式出现,会在解析方法中作为键获取HashMap中的对应类类型值,或者直接转化为对应的类型。

  这个小组件是映射器模块中的一个关键组件,因为我们采用XML形式进行映射器配置,那么不可避免的在XML文件中不可能出现真正的Java类型,多是字符串,这样我们就需要在MyBatis中增加一个解析XML映射配置中给定类型字符串的方法来获取其所代表的Java类型。(这里的字符串正是类型别名)

  XML映射器就是针对Java类型与数据库类型之间进行映射转换,以此来实现ORM功能。

  那么,这样一来,我们就明白了类型别名注册器在整个MyBatis中的位置及作用。

(作者:唯一浩哥)

浩哥解析MyBatis源码(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)的更多相关文章

  1. 浩哥解析MyBatis源码(十)——Type类型模块之类型处理器

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6715063.html 1.回顾 之前的两篇分别解析了类型别名注册器和类型处理器注册器,此二 ...

  2. 浩哥解析MyBatis源码(九)——Type类型模块之类型处理器注册器(TypeHandlerRegistry)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6709157.html 1.回顾 上一篇研究的是类型别名注册器TypeAliasRegist ...

  3. 浩哥解析MyBatis源码(十二)——binding绑定模块之MapperRegisty

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6758456.html 1.回顾 之前解析了解析模块parsing,其实所谓的解析模块就是为 ...

  4. 浩哥解析MyBatis源码(三)——Transaction事务模块

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634151.html 1.回顾 之前介绍了Environment环境类,这其实是一个单例类 ...

  5. 浩哥解析MyBatis源码(二)——Environment环境

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6625612.html 本应该先开始说Configuration配置类的,但是这个类有点过于 ...

  6. 浩哥解析MyBatis源码(四)——DataSource数据源模块

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6634880.html 1.回顾 上一文中解读了MyBatis中的事务模块,其实事务操作无非 ...

  7. 浩哥解析MyBatis源码(五)——DataSource数据源模块之非池型数据源

    1 回顾 上一篇中我解说了数据源接口DataSource与数据源工厂接口DataSourceFactory,这二者是MyBatis数据源模块的基础,包括本文中的非池型非池型数据源(UnpooledDa ...

  8. 浩哥解析MyBatis源码(六)——DataSource数据源模块之池型数据源

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6675674.html 1 回顾 上一文中解读了MyBatis中非池型数据源的源码,非池型也 ...

  9. 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

随机推荐

  1. 设计理念 : popup login 在前后台

    popup 意思是一个遮罩层顶在整个网页最前方,在前台设计是这样的,当用户想在那个界面登入时,就可以有一个遮罩层出现. 在employer或admin(后台)操作界面是同个理念,在所有的界面都是有co ...

  2. Easyui _treegrid 动态加载子节点

    <table id="dg" class="easyui-treegrid" title="数据字典列表" data-options= ...

  3. oracle 数据库删除表或表数据恢复问题

    oracle恢复误删除的数据:使用闪回,ORACLE 10G及以上版本! 1. flashback table table_name to timestamp systimestamp-1; (sys ...

  4. JS入门(四)

    接之前一篇的函数.写之前的函数的时候讲的比较笼统,在这重新写一下函数的内容. 函数: 之前提过,函数就是代码复用的一种机制或是将代码封装成功能的代码段.函数的声明在这边就不多提了,因为相对来说比较简单 ...

  5. 2020: [Usaco2010 Jan]Buying Feed, II

    2020: [Usaco2010 Jan]Buying Feed, II Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 220  Solved: 162[ ...

  6. 算法模板——sap网络最大流 2(非递归+邻接表)

    实现功能:同最大流 1 这里面主要是把前面的邻接矩阵改成了邻接表,相比之下速度大大提高——本人实测,当M=1000000 N=10000 时,暂且不考虑邻接矩阵会不会MLE,新的程序速度快了很多倍(我 ...

  7. 1588: [HNOI2002]营业额统计

    1588: [HNOI2002]营业额统计 Time Limit: 5 Sec  Memory Limit: 162 MBSubmit: 9203  Solved: 3097[Submit][Stat ...

  8. 485. Max Consecutive Ones

    题目 Given a binary array, find the maximum number of consecutive 1s in this array. Example 1: Input: ...

  9. ST 单元测试之maven引入junit包

    按照上篇博客,已经完成了mavne以及eclipse的安装配置,新建好了一个maven项目. 接下来打开项目,双击打开pom.xml,可以看到如下所示, 点击下方的pom.xml,然后添加如下代码,即 ...

  10. Servlet中的过滤器Filter详解

    加载执行顺序 context-param->listener->filter->servlet web.xml中元素执行的顺序listener->filter->stru ...