Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

分享一波:程序员赚外快-必看的巅峰干货

前言

笔者大概是从今年的5月份开始喜欢上源码阅读的,起初是阅读徐郡明前辈的《Mybatis技术内幕》入的坑,不得不说大佬就是大佬,书中讲得东西很细很全。半年过去了,笔者对mybatis略知一二,也开始在为公司搭架构,并且基于Mybatis写了一套框架,但是尽管如此还是感觉自己对于源码的理解稍微有点浅。好比是初高中学数学吧,光看例题不做题是记不住的,因此产生了为mybatis写注释的想法,想要通过写注释的过程,加强对mybatis的理解。虽然现在网上已经有了较全的mybatis中文注释,但是感觉还是经过自己手敲更能加强记忆,因此便挖下了这个大坑。笔者也希望可以在一年内把这个坑填完,后续关于其他技术的文章可能就比较少,大多数应该就都是mybatis源码阅读犀利了

在这里,附上我的码云地址(别问我为什么是码云而不是github,下半天代码下不动急死人)

mybatis中文注释

同时,我也很欢迎更多的初中级开发者投入到阅读源码的行列,并且很乐意大家在我的仓库上建立自己的分支,希望可以和大家一同进步。
入口

Mybatis

初始化入口文件是SqlSessionFactoryBuilder。该类通过调用XMLConfigBuilder.parse方法初始化配置文件。

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException(“Error building SqlSession.”, e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

在XMLConfigBuilder.parse方法中,会先校验配置文件是否已经解析过了,如果重复解析就抛出异常

public Configuration parse() {
    if (parsed) {
        // 已经解析过就不再解析。这里只解析一次
        throw new BuilderException("每个 XMLConfigBuilder 只能使用一次.");
    }
    parsed = true;
    // 获取configuration节点进行解析
    // mybatis解析配置文件使用的是XPathParser,这里的evalNode方法就是解析xml的代码
    // 这里对XPathParser不进行注释,这不属于mybatis的范畴(其实是懒。)
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

parseConfiguration方法中,传入configuration节点配置,对mybatis-config.xml文件中的该节点进行解析。解析结果会set到Configuration类中。今天只注释完了properties和settings两个节点的解析

/**
 * 解析mybatis-config.xml文件
 *
 * @param root
 */
private void parseConfiguration(XNode root) {
    try {
        // 解析properties节点。该节点用来引入外部的资源文件,如db.properties
        propertiesElement(root.evalNode("properties"));
        // 解析settings节点,校验配置中的配置项是否合法。该节点用来设置一些mybatis的配置项
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        // 加载用户自己配置的虚拟文件系统
        loadCustomVfs(settings);
        // 加载日志
        loadCustomLogImpl(settings);
        // TODO 解析typeAliases节点,下次继续
        typeAliasesElement(root.evalNode("typeAliases"));
        pluginElement(root.evalNode("plugins"));
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        typeHandlerElement(root.evalNode("typeHandlers"));
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

先看propertiesElement方法,该方法用于解析properties节点。

/**
 * 解析mybatis-config.xml的properties节点
 * 将节点中所有的配置set到Configuration中
 *
 * @param context
 * @throws Exception
 */
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // 解析拿到节点下的所有子节点配置
        Properties defaults = context.getChildrenAsProperties();
        // 获取properties节点的resource属性
        String resource = context.getStringAttribute("resource");
        // 获取properties节点的url属性。
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
            // resource和url属性只能同时存在一个。
            throw new BuilderException("properties节点不能同时指定resource和url两个属性.");
        }
        // url和resource属性只能同时存在一个
        // 读取引入的资源文件所有属性,put到properties节点之下
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        // 如果configuration之前已经有了配置,也put进去
        // put这些设置是为了能够保证后面set回configuration时可以set所有的配置
        if (vars != null) {
            defaults.putAll(vars);
        }
        parser.setVariables(defaults);
        // 将Properties节点下所有的配置set到configuration
        configuration.setVariables(defaults);
    }
}

接着就是解析settings节点,该节点用于配置一些mybatis配置项

/**
 * 解析settings节点
 *
 * @param context
 * @return
 */
private Properties settingsAsProperties(XNode context) {
    if (context == null) {
        return new Properties();
    }
    // 获取settings节点下所有的setting节点
    Properties props = context.getChildrenAsProperties();
    // 通过Configuration获取metaClass,用于方便对Configuration进行操作
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
        // 遍历setting配置
        // 如果Configuration没有这个set方法,说明该配置是无效的
        if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("配置 " + key + " 无效. 请检查拼写是否正确.");
        }
    }
    // 校验完settings之后返回
    return props;
}

解析完settings节点后,程序会加载用户配置的虚拟文件系统和日志。

/**
 * 加载用户自己设置的虚拟文件系统
 *
 * @param props
 * @throws ClassNotFoundException
 */
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    // 从settings中拿到name是vfsImpl的配置节点
    String value = props.getProperty("vfsImpl");
    if (value != null) {
        String[] clazzes = value.split(",");
        for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
                @SuppressWarnings("unchecked")
                Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
                // 加载文件系统,set到Configuration中
                configuration.setVfsImpl(vfsImpl);
            }
        }
    }
}

/**
 * 加载日志。代码比较简单
 * 就是从settings中拿到name为logImpl的配置项
 * 然后set到Configuration中去
 *
 * @param props
 */
private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
}

引申

上面的代码中使用到了MetaClass类和Configuration类。下面对这两个类进行解释。

首先是Configuration类。该类通过名称可以很明显的知道这是mybatis的配置类,对应的是mybatis-config.xml文件的配置。其中今天将properties和settings节点对应的字段进行注释。

public class Configuration {

/**
 * mybatis-config.xml属性
 * settings节点
 * 允许嵌套语句中使用分页
 */
protected boolean safeRowBoundsEnabled;


/**
 * mybatis-config.xml属性
 * settings节点
 * 是否开启自动驼峰命名规则映射
 * 即从经典数据库列名 a_column 到经典 Java 属性名 aColumn 的类似映射。
 */
protected boolean mapUnderscoreToCamelCase;
/**
 * mybatis-config.xml文件下
 * settings节点
 * 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;
 * 反之,每种属性将会按需加载。
 */
protected boolean aggressiveLazyLoading;

/**
 * mybatis-config.xml文件下
 * settings节点
 * 是否允许单一语句返回多条结果集
 */
protected boolean multipleResultSetsEnabled = true;

/**
 * mybatis-config.xml文件
 * settings节点
 * 允许 JDBC 支持自动生成主键
 */
protected boolean useGeneratedKeys;

/**
 * mybatis-config.xml文件
 * settings节点
 * 使用列标签代替列名
 */
protected boolean useColumnLabel = true;
/**
 * mybatis-config.xml文件
 * settings节点
 * 该配置影响的所有映射器中配置的缓存的全局开关
 */
protected boolean cacheEnabled = true;

/**
 * mybatis-config.xml文件
 * settings节点
 * 指定当结果集中值为null的时候是否调用映射对象的set方法
 */
protected boolean callSettersOnNulls;


/**
 * mybatis-config.xml文件
 * settings节点
 * 指定MyBatis增加到日志名称的前缀
 */
protected String logPrefix;

/**
 * mybatis-config.xml文件
 * settings节点
 * 指定MyBatis所用日志的具体实现
 */
protected Class<? extends Log> logImpl;

/**
 * mybatis-config.xml文件
 * settings节点
 * VFS含义是虚拟文件系统;
 * 主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。
 * Mybatis中提供了VFS这个配置。
 * 主要是通过该配置可以加载自定义的虚拟文件系统应用程序
 * 多个文件系统使用逗号隔开
 */
protected Class<? extends VFS> vfsImpl;

/**
 * mybatis-config.xml文件
 * settings节点
 * mybatis利用本地缓存机制防止循环引用的加速重复嵌套查询。
 * 默认是SESSION,这种情况会缓存一个会话中执行的所有查询
 * 如果是STATEMENT,本地会话仅用在语句执行上
 * 对相同的SqlSession的不同调用将不会共享数据
 */
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;

/**
 * mybatis-config.xml文件
 * settings节点
 * 当没有为菜蔬提供特定的JDBC类型时
 * 为空值制定JDBC类型
 */
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;

/**
 * mybatis-config.xml
 * settings节点
 * 指定哪个对象的方法触发一次延迟加载
 */
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));

/**
 * mybatis-config.xml文件
 * settings节点
 * 设置超时时间
 */
protected Integer defaultStatementTimeout;

/**
 * mybatis-config.xml文件
 * settings节点
 * 为驱动程序设置提示以控制返回结果的获取大小
 */
protected Integer defaultFetchSize;

/**
 * mybatis-config.xml文件
 * settings节点
 * 配置默认的执行器。
 * SIMPLE 就是普通的执行器;
 * REUSE 执行器会重用预处理语句(PreparedStatements);
 * BATCH 执行器将重用语句并执行批量更新。
 */
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

/**
 * mybatis-config.xml文件
 * settings节点
 * 指定 MyBatis 应如何自动映射列到字段或属性。
 * NONE 表示取消自动映射;
 * PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
 * FULL 会自动映射任意复杂的结果集
 */
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;

/**
 * mybatis-config.xml文件下
 * properties节点的所有配置
 * 以及该节点对应的resource和url的所有配置
 * 在XMLConfigBuilder.propertiesElement方法中进行初始化
 */
protected Properties variables = new Properties();

/**
 * mybatis-config.xml文件
 * settings节点属性
 * 延迟加载的全局开关。
 * 当开启时,所有关联对象都会延迟加载。
 * 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
 */
protected boolean lazyLoadingEnabled = false;

/**
 * mybatis-config.xml文件
 * settings节点
 * 指定Mybatis创建具有延迟加载能力对象所用到的代理工厂
 */
protected ProxyFactory proxyFactory = new JavassistProxyFactory();

/**
 * 将数据库类型转换成Java类型
 */
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

/**
 * 存储扫包得到的别名
 */
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

}

而MetaClass是反射工具箱里的一个类。Reflector是Mybatis中反射模块的基础,每个Reflector对象都对应一个类,在该对象中缓存了反射操作需要使用的元信息,如:可读属性、可写属性、get、set方法等。ReflectorFactory接口主要实现了对Reflector对象的创建和缓存。而MetaClass则是对Reflector和reflectorFactory的封装,使其更方便通过反射去操作一个类。

这里就不帖MetaClass的代码了,感兴趣可以自行阅读。
结语

今天因为时间充裕所以写的博客比较清晰,后面可能会因为加班所以博客仅仅是对代码注释的复制粘贴,还希望读者可以谅解。这个坑我会继续填下去的。

最后需要提一下java里的一个容易被忽视的规范,也是面试、大学考试经常喜欢问的内容。

类中定义的成员变量也称之为“字段”,而属性则是指get和set方法。属性只与方法有关而与字段无关。如一个类中存在getName()和setName(String name)方法,不管该类中有没有name字段,我们都认为它有name这个属性。反之如果只有字段name而没有对应的get/set方法,则该类仅仅是有name这个字段而没有name属性。后面对于get/set方法我不会称之为属性,但是有必要分清楚这两个概念。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值