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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 接上一篇MyBatis 学习笔记(四)—源码分析篇—配置文件的解析过程(一) 。上一篇我们介绍了properties 和settings配置的解析过程,今天我们接着来看看其他常用属性的解析过程,重点介绍typeAliases,environments等配置的解析。

概述

接上一篇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>

如上,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.根据节点名称判断是否是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.通过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;
  }

如上,核心是通过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);
    }
  }

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

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>

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

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

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());
        }
      }
    }
  }

如上,解析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);

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

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

3492e69bab8c4ac3a7a23c661f874893_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.jpg

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

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 + "}";
    }
  }

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

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

总结

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

源代码

https://github.com/XWxiaowei/mybatis

相关文章
|
6天前
|
XML Java 数据库连接
三、MyBatis核心配置文件详解
三、MyBatis核心配置文件详解
42 14
|
7天前
|
Java 关系型数据库 数据库连接
Javaweb之Mybatis入门程序的详细解析
本文详细介绍了一个MyBatis入门程序的创建过程,从环境准备、Maven项目创建、MyBatis配置、实体类和Mapper接口的定义,到工具类和测试类的编写。通过这个示例,读者可以了解MyBatis的基本使用方法,并在实际项目中应用这些知识。
45 11
|
3月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
4月前
|
SQL Java 数据库连接
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
|
6月前
|
人工智能 前端开发 Java
【Tomcat源码分析】启动过程深度解析 (二)
本文深入探讨了Tomcat启动Web应用的过程,重点解析了其加载ServletContextListener及Servlet的机制。文章从Bootstrap反射调用Catalina的start方法开始,逐步介绍了StandardServer、StandardService、StandardEngine、StandardHost、StandardContext和StandardWrapper的启动流程。每个组件通过Lifecycle接口协调启动,子容器逐层启动,直至整个服务器完全启动。此外,还详细分析了Pipeline及其Valve组件的作用,展示了Tomcat内部组件间的协作机制。
【Tomcat源码分析】启动过程深度解析 (二)
|
5月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
283 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
7月前
|
安全 Java 数据库连接
后端框架的学习----mybatis框架(3、配置解析)
这篇文章详细介绍了MyBatis框架的核心配置文件解析,包括环境配置、属性配置、类型别名设置、映射器注册以及SqlSessionFactory和SqlSession的生命周期和作用域管理。
后端框架的学习----mybatis框架(3、配置解析)
|
9月前
|
存储 并行计算 算法
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
|
9月前
|
SQL Java 数据库连接
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
MyBatis插件深度解析:功能、原理、使用、应用场景与最佳实践
|
9月前
|
SQL Java 数据库连接
Mybatis日志SQL解析
Mybatis日志SQL解析

推荐镜像

更多