MyBatis 源码分析 - 配置文件解析过程 (9)

第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:

<typeAliases> <typeAlias alias="article" type="xyz.coolblog.model.Article" /> <typeAlias type="xyz.coolblog.model.Author" /> </typeAliases>

对比这两种方式,第一种自动扫描的方式配置起来比较简单,缺点也不明显。唯一能想到缺点可能就是 MyBatis 会将某个包下所有符合要求的类的别名都解析出来,并形成映射关系。如果你不想让某些类被扫描,
这个好像做不到,没发现 MyBatis 提供了相关的排除机制。不过我觉得这并不是什么大问题,最多是多解析并缓存了一些别名到类型的映射,在时间和空间上产生了一些额外的消耗而已。当然,如果无法忍受这些消耗,可以使用第二种配置方式,通过手工的方式精确配置某些类型的别名。不过这种方式比较繁琐,特别是配置项比较多时。至于两种方式怎么选择,这个看具体的情况了。配置项非常少时,两种皆可。比较多的话,还是让 MyBatis 自行扫描吧。

以上介绍了两种不同的别名配置方式,下面我们来看一下两种不同的别名配置是怎样解析的。代码如下:

// -☆- XMLConfigBuilder private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // ⭐️ 从指定的包中解析别名和类型的映射 if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); // ⭐️ 从 typeAlias 节点中解析别名和类型的映射 } else { // 获取 alias 和 type 属性值,alias 不是必填项,可为空 String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { // 加载 type 对应的类型 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); } } } } }

如上,上面的代码通过一个if-else条件分支来处理两种不同的配置,这里我用⭐️标注了出来。下面我们来分别看一下这两种配置方式的解析过程,首先来看一下手动配置方式的解析过程。

2.5.1 从 typeAlias 节点中解析并注册别名

在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。这个在配置文件的 DTD 中有规定。如果使用者未配置 alias 属性,则需要 MyBatis 自行为目标类型生成别名。对于别名为空的情况,注册别名的任务交由void registerAlias(Class<?>)方法处理。若不为空,则由void registerAlias(String, Class<?>)进行别名注册。这两个方法的分析如下:

private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>(); public void registerAlias(Class<?> type) { // 获取全路径类名的简称 String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { // 从注解中取出别名 alias = aliasAnnotation.value(); } // 调用重载方法注册别名和类型映射 registerAlias(alias, type); } public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // 将别名转成小写 String key = alias.toLowerCase(Locale.ENGLISH); /* * 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致, * 不一致则抛出异常,不允许一个别名对应两种类型 */ 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); }

如上,若用户为明确配置 alias 属性,MyBatis 会使用类名的小写形式作为别名。比如,全限定类名xyz.coolblog.model.Author的别名为author。若类中有@Alias注解,则从注解中取值作为别名。

上面的代码不是很复杂,注释的也比较清楚了,就不多说了。继续往下看。

2.5.2 从指定的包中解析并注册别名

从指定的包中解析并注册别名过程主要由别名的解析和注册两步组成。下面来看一下相关代码:

public void registerAliases(String packageName) { // 调用重载方法注册别名 registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); /* * 查找某个包下的父类为 superType 的类。从调用栈来看,这里的 * superType = Object.class,所以 ResolverUtil 将查找所有的类。 * 查找完成后,查找结果将会被缓存到内部集合中。 */ resolverUtil.find(new ResolverUtil.IsA(superType), packageName); // 获取查找结果 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for (Class<?> type : typeSet) { // 忽略匿名类,接口,内部类 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { // 为类型注册别名 registerAlias(type); } } }

上面的代码不多,相关流程也不复杂,可简单总结为下面两个步骤:

查找指定包下的所有类

遍历查找到的类型集合,为每个类型注册别名

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzyxw.html