原創作品,可以轉載,但是請標注出處地址: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中的位置及作用。
(作者:唯一浩哥)