MyBatis 学习笔记(四)---源码分析篇---配置文件的解析过程(二)

举报
码农飞哥 发表于 2021/05/29 14:13:34 2021/05/29
【摘要】 概述 接上一篇MyBatis 学习笔记(四)—源码分析篇—配置文件的解析过程(一) 。上一篇我们介绍了properties 和settings配置的解析过程,今天我们接着来看看其他常用属性的解析过程,重点介绍typeAliases,environments等配置的解析。 typeAliases的解析过程 一个简单的别名配置如下: <typeAliases...

概述

接上一篇MyBatis 学习笔记(四)—源码分析篇—配置文件的解析过程(一) 。上一篇我们介绍了properties 和settings配置的解析过程,今天我们接着来看看其他常用属性的解析过程,重点介绍typeAliases,environments等配置的解析。

typeAliases的解析过程

一个简单的别名配置如下:

 <typeAliases> <typeAlias type="com.jay.chapter2.entity.ClassRoom" alias="ClassRoom"/> <typeAlias type="com.jay.chapter2.entity.Student" alias="Student"/> </typeAliases>
	
//or <typeAliases>
  <package name="domain.blog"/>
</typeAliases> 
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如上,typeAliases配置的使用也比较简单,该配置主要是减少在映射文件中填写全限定名的冗余。下面我们来看看解析过程

//* XMLConfigBuilder
  private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { //如果是package String typeAliasPackage = child.getStringAttribute("name"); //(一)调用TypeAliasRegistry.registerAliases,去包下找所有类,然后注册别名(有@Alias注解则用,没有则取类的simpleName) configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { //如果是typeAlias String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); //根据Class名字来注册类型别名 //(二)调用TypeAliasRegistry.registerAlias if (alias == null) { //alias可以省略 typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } }
  }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

如上,该入口程序方法执行流程如下:

  1. 根据节点名称判断是否是package,如果是的话则调用TypeAliasRegistry.registerAliases,去包下找所有类,然后注册别名(有@Alias注解则用,没有则取类的simpleName)
  2. 如果不是的话,则进入另外一个分支,则根据Class名字来注册类型别名。
    接下来我们按照两个分支进行分析。

package解析分支

按照前面说的如果配置的是package的话,那么首先去包下找所有的类,然后注册别名。
那么它是如何找到包下的所有类的呢?带着疑问我们来看看源码。

//* TypeAliasRegistry
  public void registerAliases(String packageName){ registerAliases(packageName, Object.class);
  } public void registerAliases(String packageName, Class<?> superType){ ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); //扫描并注册包下所有继承于superType的类型别名 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); } }
  } //注册类型别名
  public void registerAlias(Class<?> type) { //如果没有类型别名,用Class.getSimpleName来注册 String alias = type.getSimpleName();
	//或者通过Alias注解来注册(Class.getAnnotation) Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); } registerAlias(alias, type);
  }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

如上,流程主要有两部分

  1. 通过ResolverUtil的find方法找到该包下所有的类,传入的父类是Object
  2. 循环注册别名,只有非匿名类及非接口及内部类及非成员类才能注册。
  3. 注册别名最终还是调用registerAlias(alias, type)完成的。
    接着我们再来看看ResolverUtil到底是如何查找包下的所有类的。
 //主要的方法,找一个package下满足条件的所有类,被TypeHanderRegistry,MapperRegistry,TypeAliasRegistry调用
  public ResolverUtil<T> find(Test test, String packageName) { String path = getPackagePath(packageName); try { //通过VFS来深入jar包里面去找一个class List<String> children = VFS.getInstance().list(path); for (String child : children) { if (child.endsWith(".class")) { //将.class的class对象放入Set集合中,供后面调用 addIfMatching(test, child); } } } catch (IOException ioe) { log.error("Could not read package: " + packageName, ioe); } return this;
  }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

如上,核心是通过VFS来找到packageName下面的子包,包下面的class以及子包下面的class。PS: VFS 是虚拟文件系统,用来读取服务器里的资源。在此处我们不做分析。

根据Class名字注册解析分支

	//注册类型别名
  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); //如果已经存在key了,且value和之前不一致,报错 //这里逻辑略显复杂,感觉没必要,一个key对一个value呗,存在key直接报错不就得了(与系统内置的类型别名相同的别名直接报错) 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);
  } public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } //这里转个小写也有bug?见748号bug(在google code上)   https://code.google.com/p/mybatis/issues //比如如果本地语言是Turkish,那i转成大写就不是I了,而是另外一个字符(İ)。这样土耳其的机器就用不了mybatis了!这是一个很大的bug,但是基本上每个人都会犯...... String key = string.toLowerCase(Locale.ENGLISH); Class<T> value; //原理就很简单了,从HashMap里找对应的键值,找到则返回类型别名对应的Class if (TYPE_ALIASES.containsKey(key)) { value = (Class<T>) TYPE_ALIASES.get(key); } else { //找不到,再试着将String直接转成Class(这样怪不得我们也可以直接用java.lang.Integer的方式定义,也可以就int这么定义) value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e); }
  }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

至此,我们的类型别名就注册和解析就全部完成了。

environments的解析过程

我们都知道MyBatis中environments配置主要是用来配置数据源信息,是MyBatis中一定会有的配置。首先我们还是来看看environments配置的使用。

 <!-- 设置一个默认的连接环境信息 -->
	<environments default="development">
	 <!--连接环境信息,取一个任意唯一的名字 --> <environment id="development"> <!-- mybatis使用jdbc事务管理方式 --> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <!-- mybatis使用连接池方式来获取连接 --> <dataSource type="POOLED"> <!-- 配置与数据库交互的4个必要属性 --> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment>
	</environments>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

如上,配置了连接环境信息,我们心中肯定会有个疑问,${} 这种参数是如何解析的?我一会再分析。
下面我们就来看看这个配置的解析过程。

private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id");
		//循环比较id是否就是指定的environment if (isSpecifiedEnvironment(id)) { //1 创建事务工厂TransactionFactory TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); //2创建数据源 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); //3.构建Environment对象 Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource);
// 将创建的Environment对象设置到configuration中 configuration.setEnvironment(environmentBuilder.build()); } } }
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

如上,解析environments 的流程有三个:

  1. 创建事务工厂TransactionFactory
  2. 创建数据源
  3. 创建Environment对象
    我们看看第一步和第二步的代码
//* XMLConfigBuilder
 private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties();
		//根据type="JDBC"解析返回适当的TransactionFactory TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory.");
  } protected Class<?> resolveClass(String alias) { if (alias == null) { return null; } try { return resolveAlias(alias); } catch (Exception e) { throw new BuilderException("Error resolving class. Cause: " + e, e); }
  }
  //*Configuration typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

JDBC 通过别名解析器解析之后会JdbcTransactionFactory工厂实例。数据源的解析与此类似最终得到的是PooledDataSourceFactory工厂实例。
下面我们来看看之前说过的类似${driver}的解析。其实是通过PropertyParser的parse来处理的。下面我们来看个时序图。
在这里插入图片描述
这里最核心的就是第五步,我们来看看源码

  public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string);
  } //就是一个map,用相应的value替换key
  private static class VariableTokenHandler implements TokenHandler { private Properties variables; public VariableTokenHandler(Properties variables) { this.variables = variables; } @Override public String handleToken(String content) { if (variables != null && variables.containsKey(content)) { return variables.getProperty(content); } return "${" + content + "}"; }
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

如上,在VariableTokenHandler 会将${driver} 作为key,其需要被替换的值作为value。传入GenericTokenParser中。然后通过GenericTokenParser 类的parse进行替换。
至此,我们environments配置就解析完了。

总结

本文主要介绍typeAliases和environments配置的解析。然后还说下${driver} 这种属性的处理。希望对读者朋友们有所帮助。

源代码

https://github.com/XWxiaowei/mybatis

文章来源: feige.blog.csdn.net,作者:码农飞哥,版权归原作者所有,如需转载,请联系作者。

原文链接:feige.blog.csdn.net/article/details/89464974

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。