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

 

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM