在前边的博客在分析了 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 会自动映射其注册类型, 且大小写不区分.
原创不易, 有不正之处欢迎指正.
来源: https://www.cnblogs.com/teach/p/12766760.html