MyBatis源码解析(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)
原创作品,可以转载,但是请标注出处地址: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中内置的别名到底有哪些呢?我们来看看源码:
1 //构造函数里注册系统内置的类型别名 2 public TypeAliasRegistry() { 3 //字符串类型 4 registerAlias("string", String.class); 5 6 //基本包装类型 7 registerAlias("byte", Byte.class); 8 registerAlias("long", Long.class); 9 registerAlias("short", Short.class); 10 registerAlias("int", Integer.class); 11 registerAlias("integer", Integer.class); 12 registerAlias("double", Double.class); 13 registerAlias("float", Float.class); 14 registerAlias("boolean", Boolean.class); 15 16 //基本数组包装类型 17 registerAlias("byte[]", Byte[].class); 18 registerAlias("long[]", Long[].class); 19 registerAlias("short[]", Short[].class); 20 registerAlias("int[]", Integer[].class); 21 registerAlias("integer[]", Integer[].class); 22 registerAlias("double[]", Double[].class); 23 registerAlias("float[]", Float[].class); 24 registerAlias("boolean[]", Boolean[].class); 25 26 //加个下划线,就变成了基本类型 27 registerAlias("_byte", byte.class); 28 registerAlias("_long", long.class); 29 registerAlias("_short", short.class); 30 registerAlias("_int", int.class); 31 registerAlias("_integer", int.class); 32 registerAlias("_double", double.class); 33 registerAlias("_float", float.class); 34 registerAlias("_boolean", boolean.class); 35 36 //加个下划线,就变成了基本数组类型 37 registerAlias("_byte[]", byte[].class); 38 registerAlias("_long[]", long[].class); 39 registerAlias("_short[]", short[].class); 40 registerAlias("_int[]", int[].class); 41 registerAlias("_integer[]", int[].class); 42 registerAlias("_double[]", double[].class); 43 registerAlias("_float[]", float[].class); 44 registerAlias("_boolean[]", boolean[].class); 45 46 //日期数字型 47 registerAlias("date", Date.class); 48 registerAlias("decimal", BigDecimal.class); 49 registerAlias("bigdecimal", BigDecimal.class); 50 registerAlias("biginteger", BigInteger.class); 51 registerAlias("object", Object.class); 52 53 registerAlias("date[]", Date[].class); 54 registerAlias("decimal[]", BigDecimal[].class); 55 registerAlias("bigdecimal[]", BigDecimal[].class); 56 registerAlias("biginteger[]", BigInteger[].class); 57 registerAlias("object[]", Object[].class); 58 59 //集合型 60 registerAlias("map", Map.class); 61 registerAlias("hashmap", HashMap.class); 62 registerAlias("list", List.class); 63 registerAlias("arraylist", ArrayList.class); 64 registerAlias("collection", Collection.class); 65 registerAlias("iterator", Iterator.class); 66 67 //还有个ResultSet型 68 registerAlias("ResultSet", ResultSet.class); 69 }
从上面的源码中我们可以看出,在类型别名注册器类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)方法:
1 //注册类型别名 2 public void registerAlias(String alias, Class<?> value) { 3 if (alias == null) { 4 throw new TypeException("The parameter alias cannot be null"); 5 } 6 // issue #748 7 String key = alias.toLowerCase(Locale.ENGLISH); 8 //如果已经存在key了,且value和之前不一致,报错 9 //这里逻辑略显复杂,感觉没必要,一个key对一个value呗,存在key直接报错不就得了 10 if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { 11 throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); 12 } 13 TYPE_ALIASES.put(key, value); 14 }
上面的核心注册方法非常简单,参数分别为要设置的别名名称和要匹配的类类型,其中TYPE_ALIASES就是注册器中定义的用于保存类型别名的HashMap集合:
1 private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();
但这只是核心方法,其外围还包裹着两种注册方式,一种是包统一注册方式、一种是逐个注册的方式。这两种方式都是针对用户自定义别名注册而设计的。
2.2.1.1 包统一注册方式
该种方式对应的是如下的设置方式:
1 <typeAliases> 2 <package name="com.xx.xx.xx"/> 3 </typeAliases>
注册细节详见下方源码:
1 public void registerAliases(String packageName){ 2 registerAliases(packageName, Object.class); 3 } 4 5 //扫描并注册包下所有继承于superType的类型别名 6 public void registerAliases(String packageName, Class<?> superType){ 7 //TODO ResolverUtil 8 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); 9 resolverUtil.find(new ResolverUtil.IsA(superType), packageName); 10 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); 11 for(Class<?> type : typeSet){ 12 // Ignore inner classes and interfaces (including package-info.java) 13 // Skip also inner classes. See issue #6 14 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { 15 registerAlias(type); 16 } 17 } 18 } 19 20 //注册类型别名 21 public void registerAlias(Class<?> type) { 22 //如果没有类型别名,用Class.getSimpleName来注册 23 String alias = type.getSimpleName(); 24 //或者通过Alias注解来注册(Class.getAnnotation) 25 Alias aliasAnnotation = type.getAnnotation(Alias.class); 26 if (aliasAnnotation != null) { 27 alias = aliasAnnotation.value(); 28 } 29 registerAlias(alias, type); 30 }
解析:
上面的三个方法逐个调用,最后再调用核心注册方法进行最终的注册工作,让我们来看看这三个方法的目的所在。
第一个方法中只有一句代码,表示注册指定包名下的所有类,它调用了第二个方法,其第二个参数目的是限定要注册的类的来源,只有继承自给定类型的类才能被注册,这里赋值为Object.class表示其下的所有类均在别名注册的考虑范围。
第二个方法中内容较为复杂,这个方法的目的是为了限制要注册别名的类的范围,首先使用参数superClass来限制只有继承自该类的类才能进行类型别名注册,然后再排除内部类(包括匿名内部类、普通内部类)、接口类,通过调用第三个方法将剩余的指定类型(type)进行类型别名注册。
注意:第二个方法中使用了MyBatis中定义的一个工具类ResolverUtil来进行实现,主要使用其进行指定包名下方类型的搜索find()与getClasses()方法
第三个方法主要用于设置别名,其中有两种情况,第一种是针对未显式指定别名名称的类型,通过Class.getSimpleName()方法来获取类型的别名名称,其获取到的其实是类型的首字母小写形式的名称,另一种情况是针对使使用@Alias注解显式指定别名名称的类型(value的值),直接获取该注解中value的值作为别名名称即可,最后调用核心注册方法来实现类型别名的注册。
上面的三个方法各有作用,第一个对外获取包名,第二个限制范围,第三个设置别名,其中我们可以不指定别名也可以使用注解来显式指定别名,不指定别名最后别名会调用本地方法来获取。
2.2.1.2 逐个注册方式
该方式针对的是如下的设置方式:
1 <typeAliases> 2 <typeAlias type="com.xxx.xx.Role" alias="role" /> 3 </typeAliases>
注册细节详见源码:
1 public void registerAlias(String alias, String value) { 2 try { 3 registerAlias(alias, Resources.classForName(value)); 4 } catch (ClassNotFoundException e) { 5 throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e); 6 } 7 }
解析:
这种方式很简单,主要是因为在设置中已经将目标与所要设置的别名名称都指定好了,因此只需要直接执行核心注册方法即可完成注册工作,这种方式适合少量的类型注册情况,但是一旦需要注册的类型较多,工作就会显得很是复杂繁琐,为了简化工作我们可以采用之前第一种方式,要采用这种方式就要在架构编码时有意的将需要进行类型别名注册的类放置到统一的包下。
2.2.2 取方法—解析方法
首先罗列方法源码:
1 public <T> Class<T> resolveAlias(String string) { 2 try { 3 if (string == null) { 4 return null; 5 } 6 // issue #748 7 //先转成小写再解析 8 //这里转个小写也有bug?见748号bug(在google code上) 9 //https://code.google.com/p/mybatis/issues 10 //比如如果本地语言是Turkish,那i转成大写就不是I了,而是另外一个字符(İ)。这样土耳其的机器就用不了mybatis了!这是一个很大的bug,但是基本上每个人都会犯...... 11 String key = string.toLowerCase(Locale.ENGLISH); 12 Class<T> value; 13 //原理就很简单了,从HashMap里找对应的键值,找到则返回类型别名对应的Class 14 if (TYPE_ALIASES.containsKey(key)) { 15 value = (Class<T>) TYPE_ALIASES.get(key); 16 } else { 17 //找不到,再试着将String直接转成Class(这样怪不得我们也可以直接用java.lang.Integer的方式定义,也可以就int这么定义) 18 value = (Class<T>) Resources.classForName(string); 19 } 20 return value; 21 } catch (ClassNotFoundException e) { 22 throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); 23 } 24 }
解析:
结合注释内容,我们可以看出,所谓解析别名,就是通过别名获取集合中保存的别名对应的值,即类类型。
需要注意的就是这里如果能够在集合中找到给定的别名(别名为键),则直接将对应的类型返回,但如果找不到别名,这里会尝试将别名直接转化成类型,能转化的别名必定是对应类型的全限定名的字符串形式(例如:"java.lang.Integer"模式的别名),这也就是我们在映射器文件中进行parameterType与ResultType设置的时候,可以写成"int"的方式,也可以写成"java.lang.Integer"的方式了。因为这里解析别名的时候会调用这个别名解析方法resolveAlias(),针对上述两种情况都可以实现正确解析。
3、模块分析
所谓的模块分析是我新增的一个内容,主要研究一下设置某个模块的目的所在,这个模块在整个MyBatis系统架构中的地位与作用,这部分内容完全是本人理解,若有偏差与不足,还请不吝赐教,我会及时修正。
类型别名也是MyBatis整个架构中比较重要的内容了,他一般是与类型处理器一起配合使用的。它的使用是在映射器配置文件中在配置SQL脚本的标签属性中。下面举个简单的示例:
1 <delete id="delete" parameterType="int"> 2 DELETE TB_USER u WHERE u.use_id = #{useId} 3 </delete> 4 <select id="select" parameterType="int" resulttype="com.xxx.xx.User"> 5 SELECT * FROM TB_USER u WHERE u.use_id=#{useId} 6 </select>
上面示例中的parameterType与resultType的值就是类型别名,他们都是以字符串的形式出现,会在解析方法中作为键获取HashMap中的对应类类型值,或者直接转化为对应的类型。
这个小组件是映射器模块中的一个关键组件,因为我们采用XML形式进行映射器配置,那么不可避免的在XML文件中不可能出现真正的Java类型,多是字符串,这样我们就需要在MyBatis中增加一个解析XML映射配置中给定类型字符串的方法来获取其所代表的Java类型。(这里的字符串正是类型别名)
XML映射器就是针对Java类型与数据库类型之间进行映射转换,以此来实现ORM功能。
那么,这样一来,我们就明白了类型别名注册器在整个MyBatis中的位置及作用。
(作者:唯一浩哥)