mybatis源码配置文件解析之三:解析typeAliases标签

栏目: IT技术 · 发布时间: 5年前

内容简介:在前边的博客在分析了mybatis解析settings标签,《在mybatis核心配置文件(mybatis-config.xml)中有关typeAliases的配置如下,上面给出了两种配置typeAlias的放式,一种是配置package标签,一种是typeAlias表。

在前边的博客在分析了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表。

我上面的配置是有问题的,在测试的时候一直报下面的错误,

mybatis源码配置文件解析之三:解析typeAliases标签

上面的问题困扰了笔者好久,没找到原因,因为解析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,注册的确实下面的

mybatis源码配置文件解析之三:解析typeAliases标签

可以看到注册之后的是mymenu。其实在 使用的时候是大小写不敏感 的,在匹配的时候会统一转化为小写,这样就可以对应TYPE_ALIASES中已注册的别名。

2、解析<typeAlias>标签

上面分析了<package>标签的解析过程,下面看有关<typeAlias>标签的解析,

mybatis源码配置文件解析之三:解析typeAliases标签

解析<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会自动映射其注册类型,且大小写不区分。

原创不易,有不正之处欢迎指正。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First HTML and CSS

Head First HTML and CSS

Elisabeth Robson、Eric Freeman / O'Reilly Media / 2012-9-8 / USD 39.99

Tired of reading HTML books that only make sense after you're an expert? Then it's about time you picked up Head First HTML and really learned HTML. You want to learn HTML so you can finally create th......一起来看看 《Head First HTML and CSS》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器