1. 程式人生 > >mybatis原始碼配置檔案解析之三:解析typeAliases標籤

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會自動對映其註冊型別,且大小寫不區分。

 

原創不易,有不正之處歡迎指