概述

今天我们接着来看看其他常用属性的解析过程,重点介绍typeAliases,environments等配置的解析。

typeAliases的解析过程

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

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

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

 1//* XMLConfigBuilder
2 private void typeAliasesElement(XNode parent) {
3 if (parent != null) {
4 for (XNode child : parent.getChildren()) {
5 if ("package".equals(child.getName())) {
6 //如果是package
7 String typeAliasPackage = child.getStringAttribute("name");
8 //(一)调用TypeAliasRegistry.registerAliases,去包下找所有类,然后注册别名(有@Alias注解则用,没有则取类的simpleName)
9 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
10 } else {
11 //如果是typeAlias
12 String alias = child.getStringAttribute("alias");
13 String type = child.getStringAttribute("type");
14 try {
15 Class<?> clazz = Resources.classForName(type);
16 //根据Class名字来注册类型别名
17 //(二)调用TypeAliasRegistry.registerAlias
18 if (alias == null) {
19 //alias可以省略
20 typeAliasRegistry.registerAlias(clazz);
21 } else {
22 typeAliasRegistry.registerAlias(alias, clazz);
23 }
24 } catch (ClassNotFoundException e) {
25 throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
26 }
27 }
28 }
29 }
30 }

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


  1. 根据节点名称判断是否是package,如果是的话则调用TypeAliasRegistry.registerAliases,去包下找所有类,然后注册别名(有@Alias注解则用,没有则取类的simpleName)

  2. 如果不是的话,则进入另外一个分支,则根据Class名字来注册类型别名。
    接下来我们按照两个分支进行分析。


package解析分支

按照前面说的如果配置的是package的话,那么首先去包下找所有的类,然后注册别名。

那么它是如何找到包下的所有类的呢?带着疑问我们来看看源码。

 1//* TypeAliasRegistry
2 public void registerAliases(String packageName){
3 registerAliases(packageName, Object.class);
4 }
5
6 public void registerAliases(String packageName, Class<?> superType){
7 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
8 //扫描并注册包下所有继承于superType的类型别名
9 resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
10 Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
11 for(Class<?> type : typeSet){
12 //
13 if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
14 registerAlias(type);
15 }
16 }
17 }
18
19 //注册类型别名
20 public void registerAlias(Class<?> type) {
21 //如果没有类型别名,用Class.getSimpleName来注册
22 String alias = type.getSimpleName();
23 //或者通过Alias注解来注册(Class.getAnnotation)
24 Alias aliasAnnotation = type.getAnnotation(Alias.class);
25 if (aliasAnnotation != null) {
26 alias = aliasAnnotation.value();
27 }
28 registerAlias(alias, type);
29 }

如上,流程主要有两部分

  1. 通过ResolverUtil的find方法找到该包下所有的类,传入的父类是Object

  2. 循环注册别名,只有非匿名类及非接口及内部类及非成员类才能注册。

  3. 注册别名最终还是调用registerAlias(alias, type)完成的。
    接着我们再来看看ResolverUtil到底是如何查找包下的所有类的。

 1 //主要的方法,找一个package下满足条件的所有类,被TypeHanderRegistry,MapperRegistry,TypeAliasRegistry调用
2 public ResolverUtil<T> find(Test test, String packageName) {
3 String path = getPackagePath(packageName);
4
5 try {
6 //通过VFS来深入jar包里面去找一个class
7 List<String> children = VFS.getInstance().list(path);
8 for (String child : children) {
9 if (child.endsWith(".class")) {
10 //将.class的class对象放入Set集合中,供后面调用
11 addIfMatching(test, child);
12 }
13 }
14 } catch (IOException ioe) {
15 log.error("Could not read package: " + packageName, ioe);
16 }
17
18 return this;
19 }

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

根据Class名字注册解析分支

 1    //注册类型别名
2 public void registerAlias(String alias, Class<?> value) {
3 if (alias == null) {
4 throw new TypeException("The parameter alias cannot be null");
5 }
6 // issue #748
7 String key = alias.toLowerCase(Locale.ENGLISH);
8 //如果已经存在key了,且value和之前不一致,报错
9 //这里逻辑略显复杂,感觉没必要,一个key对一个value呗,存在key直接报错不就得了(与系统内置的类型别名相同的别名直接报错)
10 if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
11 throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
12 }
13 TYPE_ALIASES.put(key, value);
14 }
15
16 public <T> Class<T> resolveAlias(String string) {
17 try {
18 if (string == null) {
19 return null;
20 }
21 //这里转个小写也有bug?见748号bug(在google code上) https://code.google.com/p/mybatis/issues
22 //比如如果本地语言是Turkish,那i转成大写就不是I了,而是另外一个字符(İ)。这样土耳其的机器就用不了mybatis了!这是一个很大的bug,但是基本上每个人都会犯......
23 String key = string.toLowerCase(Locale.ENGLISH);
24 Class<T> value;
25 //原理就很简单了,从HashMap里找对应的键值,找到则返回类型别名对应的Class
26 if (TYPE_ALIASES.containsKey(key)) {
27 value = (Class<T>) TYPE_ALIASES.get(key);
28 } else {
29 //找不到,再试着将String直接转成Class(这样怪不得我们也可以直接用java.lang.Integer的方式定义,也可以就int这么定义)
30 value = (Class<T>) Resources.classForName(string);
31 }
32 return value;
33 } catch (ClassNotFoundException e) {
34 throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
35 }
36 }

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

environments的解析过程

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

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

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

下面我们就来看看这个配置的解析过程。

 1private void environmentsElement(XNode context) throws Exception {
2 if (context != null) {
3 if (environment == null) {
4 environment = context.getStringAttribute("default");
5 }
6 for (XNode child : context.getChildren()) {
7 String id = child.getStringAttribute("id");
8 //循环比较id是否就是指定的environment
9 if (isSpecifiedEnvironment(id)) {
10 //1 创建事务工厂TransactionFactory
11 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
12 //2创建数据源
13 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
14 DataSource dataSource = dsFactory.getDataSource();
15 //3.构建Environment对象
16 Environment.Builder environmentBuilder = new Environment.Builder(id)
17 .transactionFactory(txFactory)
18 .dataSource(dataSource);
19// 将创建的Environment对象设置到configuration中
20 configuration.setEnvironment(environmentBuilder.build());
21 }
22 }
23 }
24 }

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

1.创建事务工厂TransactionFactory

2.创建数据源

3.创建Environment对象

我们看看第一步和第二步的代码

 1//* XMLConfigBuilder
2 private TransactionFactory transactionManagerElement(XNode context) throws Exception {
3 if (context != null) {
4 String type = context.getStringAttribute("type");
5 Properties props = context.getChildrenAsProperties();
6 //根据type="JDBC"解析返回适当的TransactionFactory
7 TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
8 factory.setProperties(props);
9 return factory;
10 }
11 throw new BuilderException("Environment declaration requires a TransactionFactory.");
12 }
13
14 protected Class<?> resolveClass(String alias) {
15 if (alias == null) {
16 return null;
17 }
18 try {
19 return resolveAlias(alias);
20 } catch (Exception e) {
21 throw new BuilderException("Error resolving class. Cause: " + e, e);
22 }
23 }
24 //*Configuration
25 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

JDBC 通过别名解析器解析之后会JdbcTransactionFactory工厂实例。数据源的解析与此类似最终得到的是PooledDataSourceFactory工厂实例。

下面我们来看看之前说过的类似​​${driver}​​的解析。其实是通过PropertyParser的parse来处理的。下面我们来看个时序图。

MyBatis 源码分析篇---配置文件的解析过程(二)_数据源类似​​${driver}​​的解析时序图

这里最核心的就是第五步,我们来看看源码

 1  public static String parse(String string, Properties variables) {
2 VariableTokenHandler handler = new VariableTokenHandler(variables);
3 GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
4 return parser.parse(string);
5 }
6
7 //就是一个map,用相应的value替换key
8 private static class VariableTokenHandler implements TokenHandler {
9 private Properties variables;
10
11 public VariableTokenHandler(Properties variables) {
12 this.variables = variables;
13 }
14
15 @Override
16 public String handleToken(String content) {
17 if (variables != null && variables.containsKey(content)) {
18 return variables.getProperty(content);
19 }
20 return "${" + content + "}";
21 }
22 }

如上,在VariableTokenHandler 会将​​${driver}​​作为key,其需要被替换的值作为value。传入GenericTokenParser中。然后通过GenericTokenParser 类的parse进行替换。

至此,我们environments配置就解析完了。

总结

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

源代码

https://github.com/XWxiaowei/mybatis

MyBatis 源码分析篇---配置文件的解析过程(二)_时序图_02