【Mybatis】Mybatis源码之SqlSessionFactory对象的创建

367 阅读4分钟

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

时序图

sequenceDiagram
participant S as Client
participant A as SqlSessionFactoryBuilder
participant B as XMLConfigBuilder
participant C as XPathParser
S ->>+ A: build()
A ->>+ B: new
B ->>+ C: new
C ->> C: createDocument():Document
C -->>- B: XPathParser
A ->> B: parse()
B ->> B : parseConfiguration()
B -->>- A : Configuration
A ->> A : build()
A -->>- S : DefaultSqlSessionFactory

步骤详解

获取SqlSessionFactory的测试代码

public SqlSessionFactory getSqlSessionFactory() {
    // 指定配置文件所处的位置
    String resource = "resources/mybatis-config.xml";
    // 读取配置文件为输入流
    InputStream inputStream = null;
    try {
        inputStream = Resources.getResourceAsStream(resource);
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 使用输入流创建SqlSessionFactory
    return new SqlSessionFactoryBuilder().build(inputStream);
}

进入到SqlSessionFactoryBuilderbuild方法中

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 使用输入流创建XMLConfigBuilder对象,在此过程中进行XML文档进行DOM解析,保存在XMLConfigBuilder对象中
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 调用解析方法获取Configuration对象,并使用Configuration对象创建一个DefaultSqlSessionFactory对象并返回
        return build(parser.parse());
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        ErrorContext.instance().reset();
        try {
            inputStream.close();
        } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
        }
    }
}

进入XMLConfigBuilder的构造方法中

/**
 * 根据传入的输入流、环境参数、属性参数构建XMLConfigBuilder
 */
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    /**
     * 1. 根据输入流创建XPathParser对象,并生成Document存放在XPathParser对象中
     * 2. 根据XPathParser及环境参数、属性参数构建XMLConfigBuilder
     */
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

/**
 * 1. 调用父类BaseBuilder的构造方法,并传入Configuration对象
 * 2. 将参数中的环境参数、属性参数传入创建的Configuration对象中
 */
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
}

BaseBuilder类的字段及构造方法

public abstract class BaseBuilder {
    protected final Configuration configuration;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final TypeHandlerRegistry typeHandlerRegistry;

    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }
}

进入XMLConfigBuilderparse方法中,再到parseConfiguration方法,这里就是解析配置文件的地方

/**
 * 从根节点configuration开始解析下层节点
 * @param root 根节点configuration节点
 */
private void parseConfiguration(XNode root) {
    try {
        /**
         * 所有的配置在解析完成后都会放入到此对象内部的Configuration对象中保存
         */
        // 首先解析properties,以保证在解析其他节点时便可以生效
        //issue #117 read properties first
        propertiesElement(root.evalNode("properties"));
        // 扫描settings标签
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        //加载自定义类扫描器
        loadCustomVfs(settings);
        //加载自定义日志实现
        loadCustomLogImpl(settings);
        //别名扫描注册
        typeAliasesElement(root.evalNode("typeAliases"));
        //插件扫描
        pluginElement(root.evalNode("plugins"));
        //解析Pojo对象工厂类
        objectFactoryElement(root.evalNode("objectFactory"));
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        reflectorFactoryElement(root.evalNode("reflectorFactory"));
        //解析settings标签
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        //解析环境标签
        environmentsElement(root.evalNode("environments"));
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        //解析类型转换器
        typeHandlerElement(root.evalNode("typeHandlers"));
        //解析mappers
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

解析properties标签

private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // 读取properties标签下的property标签
        Properties defaults = context.getChildrenAsProperties();
        // 根据配置的resource属性或url属性的值读取配置内容
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
        }
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 将读取到的配置内容存放在XPathParser对象的variables变量中和Configuration对象的variables变量中
        parser.setVariables(defaults);
        configuration.setVariables(defaults);
    }
}

解析typeAliases标签,别名扫描注册

  • XMLConfigBuilder#typeAliasesElement

private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // 解析配置的包路径
                String typeAliasPackage = child.getStringAttribute("name");
                // 调用TypeAliasRegistry#registerAliases方法进行别名注册
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                // 解析单独配置的别名typeAlias标签
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    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);
                }
            }
        }
    }
}


  • TypeAliasRegistry#registerAliases,在注册别名时,Mybatis将所有的别名都转换成了小写,因此在Mybatis中,别名是大小写不敏感的
public void registerAliases(String packageName) {
    // 首次进入,默认superType为Object
    registerAliases(packageName, Object.class);
}

public void registerAliases(String packageName, Class<?> superType) {
    // 工具类ResolverUtil
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    /**
     * 1. 通过superType构建ResolverUtil工具类的内部类IsA对象
     * 2. 调用ResolverUtil类的find方法筛选所有的类,并保存在matches属性中
     */
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 获取matches属性中保存的类
    Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
    // 为所有类注册别名
    for (Class<?> type : typeSet) {
        // Ignore inner classes and interfaces (including package-info.java)
        // Skip also inner classes. See issue #6
        if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
        }
    }
}

public void registerAlias(Class<?> type) {
    // 获取类名
    String alias = type.getSimpleName();
    // 解析类上的Alias注解
    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");
    }
    // issue #748
    // 将别名全部转换为小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
        throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    // 注册
    typeAliases.put(key, value);
}
  • ResolverUtil#find
/**
 * 筛选出指定路径下符合一定条件的类
 * @param test 测试条件
 * @param packageName 路径
 * @return 本身
 */
public ResolverUtil<T> find(Test test, String packageName) {
    // 获取起始包路径
    String path = getPackagePath(packageName);

    try {
        /**
         * 1. 通过单例模式获取VFS实例
         * 2. 调用VFS接口的list方法获取包中所有的文件
         */
        List<String> children = VFS.getInstance().list(path);
        for (String child : children) {
            // 对类文件进行测试
            if (child.endsWith(".class")) { // 必须是类文件
                // 测试是否满足测试条件,如果满足,则将该类文件记录下来
                addIfMatching(test, child);
            }
        }
    } catch (IOException ioe) {
        log.error("Could not read package: " + packageName, ioe);
    }

    return this;
}
  • VFS扫描文件夹
// 将url转换为InputStream
InputStream is = url.openStream();
// 将InputStream包装成BufferedReader
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<>();
// 利用BufferedReader的readLine方法,读取文件夹下的文件
for (String line; (line = reader.readLine()) != null; ) {
    if (log.isDebugEnabled()) {
        log.debug("Reader entry: " + line);
    }
    lines.add(line);
    if (getResources(path + "/" + line).isEmpty()) {
        lines.clear();
        break;
    }
}

解析typeHandlers标签

private void typeHandlerElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // 根据配置的包名扫描,获取TypeHandler接口所有的实现类,并将这些类型转换器进行注册
                String typeHandlerPackage = child.getStringAttribute("name");
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                // 解析typeHandler标签属性
                String javaTypeName = child.getStringAttribute("javaType");
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                String handlerTypeName = child.getStringAttribute("handler");
                Class<?> javaTypeClass = resolveClass(javaTypeName);
                JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
                Class<?> typeHandlerClass = resolveClass(handlerTypeName);
                /** 
                 * 注册类型转换器到Map<Type, Map<JdbcType, TypeHandler<?>>>容器中
                 * 容器中将JavaType,JdbcType,TypeHandler进行映射
                 */
                if (javaTypeClass != null) {
                    if (jdbcType == null) {
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
                    }
                } else {
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}

解析mappers标签

  • 下文详解

SqlSessionFactoryBuilder#build方法,根据Configuration对象创建并返回一个DefaultSqlSessionFactory对象

/**
 * 根据配置信息建造一个SqlSessionFactory对象
 * @param config 配置信息
 * @return SqlSessionFactory对象
 */
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

以上便是对Mybatis中SqlSessionFactory对象的创建过程源码的分析。这部分内容比较简单,只需要了解一下大致的流程步骤即可。