mybatis原始碼配置檔案解析之三:解析typeAliases標籤
在前邊的部落格在分析了mybatis解析settings標籤,《mybatis原始碼配置檔案解析之二:解析settings標籤》。下面來看解析typeAliases標籤的過程。
一、概述
在mybatis核心配置檔案(mybatis-config.xml)中有關typeAliases的配置如下,
<typeAliases> <package name="cn.com.mybatis.bean"></package> <typeAlias name="user" type="cn.com.mybatis.bean.User"></typeAlias> </typeAliases>
上面給出了兩種配置typeAlias的放式,一種是配置package標籤,一種是typeAlias表。
我上面的配置是有問題的,在測試的時候一直報下面的錯誤,
上面的問題困擾了筆者好久,沒找到原因,因為解析typeAliases標籤的原始碼中找不到任何的原因,最後排查日誌,原來是在載入核心配置檔案的時候要把配置和mybatis的dtd檔案進行驗證,這裡是驗證出錯了,具體的錯誤是typeAlias標籤必須在package標籤的前邊,也就是標籤是有順序的。把配置改為下面的順序,程式正常,
<typeAliases> <typeAlias alias="user" type="cn.com.mybatis.bean.User"></typeAlias> <package name="cn.com.mybatis.bean"/> </typeAliases>
1、配置<package>標籤
<package>標籤配置的是一個包名,mybatis會掃描該包下的所有類,並註冊一個別名,這裡在標籤中無法為某個類指定一個自定義的別名,mybatis提供了另外一種方式可以使用自定義的別名,即@Alias註解,在類上標記該註解,如下,
package cn.com.mybatis.bean; import org.apache.ibatis.type.Alias; //配置別名為myMenu @Alias(value="myMenu") public class Menu { private String menuId; private String menuName; private String url; }
上面為Menu類配置了別名,在掃描該包的時候會使用自定義的別名,不會使用mybatis預設的別名規則(Class.getSimpleName())
2、配置<typeAlias>標籤
這種配置是單獨為某個類配置別名,其中alias屬性可以不配置,不配置則使用mybatis預設的別名規則,如下
<typeAlias alias="MyUser" type="cn.com.mybatis.bean.User"></typeAlias>
上面看了typeAlias的兩種配置方式,那麼何為typeAlias,意思就是給一個類配置一個別名,如這裡有一個cn.com.mybatis.bean.User類,可以為其配置別名為MyUser,
那麼在配置檔案中便可以使用別名代替類的全限類名,目的是簡便。這裡需要注意的是配置的別名的使用範圍僅限於mybatis的配置檔案中(包含核心配置檔案和Mpper對映檔案)
二、詳述
上面,瞭解了typeAlias的配置及作用,下面看mybatis是如何解析的。
在XMLConfigBuilder類中的parseConfiguration方法,
private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析properties標籤 propertiesElement(root.evalNode("properties")); //解析settings標籤,1、把<setting>標籤解析為Properties物件 Properties settings = settingsAsProperties(root.evalNode("settings")); /*2、對<settings>標籤中的<setting>標籤中的內容進行解析,這裡解析的是<setting name="vfsImpl" value=","> * VFS是mybatis中用來表示虛擬檔案系統的一個抽象類,用來查詢指定路徑下的資源。上面的key為vfsImpl的value可以是VFS的具體實現,必須 * 是許可權類名,多個使用逗號隔開,如果存在則設定到configuration中的vfsImpl屬性中,如果存在多個,則設定到configuration中的僅是最後一個 * */ loadCustomVfs(settings); //解析別名標籤,例<typeAlias alias="user" type="cn.com.bean.User"/> typeAliasesElement(root.evalNode("typeAliases")); //解析外掛標籤 pluginElement(root.evalNode("plugins")); //解析objectFactory標籤,此標籤的作用是mybatis每次建立結果物件的新例項時都會使用ObjectFactory,如果不設定 //則預設使用DefaultObjectFactory來建立,設定之後使用設定的 objectFactoryElement(root.evalNode("objectFactory")); //解析objectWrapperFactory標籤 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //解析reflectorFactory標籤 reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 //解析environments標籤 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //解析<mappers>標籤 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
從上面可以看出typeAliasesElement方法,此方法用來解析typeAliases標籤及其子標籤,
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { //1、解析package標籤 if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { //2、解析typeAlias標籤 String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
typeAliasesElement方法會分別解析typeAliases標籤的package和typeAlias子標籤。通過上面的分析知道在配置的時候<typeAlias>標籤要在<package>標籤前邊,但這裡按照原始碼的順序先分析<package>標籤的解析。
1、解析<package>標籤
下面看typeAliasesElement方法中對package標籤的解析,
if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); }
從上面可以看到獲取<package>標籤的name屬性,也就配置的包名,然後呼叫下面的方法,
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
可以看到從Configuration中獲得TypeAliasRegistry,然後呼叫其registerAliases方法,
public void registerAliases(String packageName){ registerAliases(packageName, Object.class); }
又呼叫另外一個方法,如下,
/** * * 為包下的所有java bean註冊別名 * @param packageName * @param superType */ public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); //把該包下的所有類進行載入,把其Class物件放到resolverUtil的matches中 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); } } }
上面方法的作用是遍歷給的的包名,把該包下的所有的類進行載入,並放到resolverUtil中的matches中,這裡具體的遍歷方法暫且不看。遍歷完成後取出resolverUtil中的所有Class物件,只要不是匿名類、介面則執行registerAlias方法,
public void registerAlias(Class<?> type) { //獲得類的簡單類名,如cn.com.mybatis.bean.User 則其簡單名稱為User String alias = type.getSimpleName(); //判斷類上是否存在@Alias註解 Alias aliasAnnotation = type.getAnnotation(Alias.class); //如果存在@Alias註解,則使用註解上配置的value屬性作為別名 if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); }
看上面的方法,上面的方法先獲得Class的簡單類名,
//獲得類的簡單類名,如cn.com.mybatis.bean.User 則其簡單名稱為User String alias = type.getSimpleName();
然後會判斷類上是否有@Alias註解,如果有則取其value值作為類的別名,
//判斷類上是否存在@Alias註解 Alias aliasAnnotation = type.getAnnotation(Alias.class); //如果存在@Alias註解,則使用註解上配置的value屬性作為別名 if (aliasAnnotation != null) { alias = aliasAnnotation.value(); }
進行上面的判斷,存在@Alias註解,使用其value值作為別名,否則使用類的簡單類名(Class.getSimpleName()),然後執行registerAlias方法,
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); //如果已經註冊了改別名則會拋異常 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); }
上面的程式碼會把別名轉化為英文的小寫作為存入的key,使用對應的Class存入TYPE_ALIASES中。如果已經註冊過該key則會丟擲異常,也就是不允許重複註冊或者相同的key是無法覆蓋的。這裡還有一個問題,如果我們配置的是別名中含有大寫,那麼註冊的時候是小寫的,在使用的時候是用配置的還是用註冊的,例,上面的例子,
package cn.com.mybatis.bean; import org.apache.ibatis.type.Alias; //配置別名為myMenu @Alias(value="myMenu") public class Menu { private String menuId; private String menuName; private String url; }
這裡配置的是myMenu,註冊的確實下面的
可以看到註冊之後的是mymenu。其實在使用的時候是大小寫不敏感的,在匹配的時候會統一轉化為小寫,這樣就可以對應TYPE_ALIASES中已註冊的別名。
2、解析<typeAlias>標籤
上面分析了<package>標籤的解析過程,下面看有關<typeAlias>標籤的解析,
解析<typeAlias>標籤即是獲取alias和type兩個屬性,可以看到對alias進行了判斷,也就說可以不配置alias屬性,那麼會使用下面的方法處理
public void registerAlias(Class<?> type) { //獲得類的簡單類名,如cn.com.mybatis.bean.User 則其簡單名稱為User String alias = type.getSimpleName(); //判斷類上是否存在@Alias註解 Alias aliasAnnotation = type.getAnnotation(Alias.class); //如果存在@Alias註解,則使用註解上配置的value屬性作為別名 if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type); }
該方法前面已分析,會判斷配置的類是否含有@Alias註解,如果有則使用註解上的value值。這裡存在一個問題,如果在<typeAlias>標籤中配置了alias,在類上也有@Alias註解,且不一樣,以哪個為準,通過上面的分析,得出下面的結論,
在使用<typeAlias alias="myAlias">標籤的時候,配置了alias屬性,在類上也有@Alias(value="myAlias2"),已配置的為準(最終別名為myAlias)
下面看registerAlias(alias,type)方法,
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); //如果已經註冊了改別名則會拋異常 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); }
此方法上面分析過,如果存在相同的key會拋異常,最終存入TYPE_ALIASES中。
三、總結
本文分析了mybatis核心配置檔案(mybatis-config.xml)的<typeAlias>標籤的配置及原始碼解析。
另在寫Mapper對映檔案和核心配置檔案的時候會使用一些自定義的別名,這些別名是怎麼註冊的那,在Configuration、TypeAliasRegistry類中進行了註冊,如下Configuration,
public Configuration() { typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); }
在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); registerAlias("ResultSet", ResultSet.class); }
上面兩個類註冊了系統內建的別名,在核心配置檔案和Mapper對映檔案中可使用,mybatis會自動對映其註冊型別,且大小寫不區分。
原創不易,有不正之處歡迎指