mybatis源码分析-环境搭建 一文中,我们的测试代码如下:

public static void main(String[] args) throws IOException { 
    String resource = "mybatis-config.xml";  
    InputStream inputStream = Resources.getResourceAsStream(resource);  
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);  
    SqlSession sqlSession = sqlSessionFactory.openSession();  
    try {  
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);  
        List<Dept> deptList = deptMapper.getAllDept();  
        System.out.println(deptList);  
    } finally {  
        sqlSession.close();  
    }  
 }  

mybatis源码分析-SqlSessionFactory构建过程 一文中探究了 SqlSessionFactory 对象的生成方式,但是那里还有两行代码没有仔细研究,因为这两行代码涉及的东西有些多,这篇文章主要研究这两行代码背后的细节。代码再贴一遍:

  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);  
  return build(parser.parse());  

XMLConfigBuilder做了什么

需要研究的第一行代码只是生成了一个 XMLConfigBuilder 对象而已。

public XMLConfigBuilder(InputStream inputStream) {  
    this((InputStream)inputStream, (String)null, (Properties)null);  
}

继续看构造重载函数:

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {  
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);  
}

这里继续调用了另一个构造函数,只是入参变为 XPathParser对象。

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {  
    super(new Configuration());  
    this.localReflectorFactory = new DefaultReflectorFactory();  
    ErrorContext.instance().resource("SQL Mapper Configuration");  
    this.configuration.setVariables(props);  
    this.parsed = false;  
    this.environment = environment;  
    this.parser = parser;  
}

这个构造函数主要是赋值功能,给 XMLConfigBuilder 对象的属性赋值。注意 XMLConfigBuilder extends BaseBuilder ,而 BaseBuilder 含有 Configuration 属性 , 因此 XMLConfigBuilder 需要给一个默认的 Configuration 值,就是下面这行代码的功能:

super(new Configuration());  

看上面的流程,似乎也不复杂,这里漏了一点 XPathParser 的创建,只有创建好了 XPathParser 才能进行文件解析,然后生成对应的 Configuration 对象。

XPathParser 使用了 xPath 解析技术。

xml解析的技术有很多,以前用过 dom4j,当使用dom4j查询比较深的层次结构的节点(标签,属性,文本)时比较麻烦!使用xPath主要是用于快速获取所需的节点对象。

本文不打算讲解如何把 inputStream 转为 XPathParser 对象,有兴趣可以自学一下。

上面讲解的代码主要掌握创建 XMLConfigBuilder 对象时有一个特别重要的属性 XPathParser ,这个属性可以快速获取 xml 的各种元素,方便后续操作。

parser.parse() 方法做了什么?

根据上下文,这里的 parser 就是 XMLConfigBuilder,我们来看下 parse() 方法做了什么:

public Configuration parse() {  
    if (this.parsed) {  
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");  
    } else {  
        this.parsed = true;  
        this.parseConfiguration(this.parser.evalNode("/configuration"));  
        return this.configuration;  
    }  
}

注意这段代码的核心是:

this.parseConfiguration(this.parser.evalNode("/configuration"));  

注意this.parser 指的是 XMLConfigBuilder 里的 XPathParser 对象。evalNode 方法获取全局配置文件里面 Configuration 下面的所有内容。现在想想全局配置文件的结构吧。

再看下 parseConfiguration 方法:

private void parseConfiguration(XNode root) {  
  try {  
    propertiesElement(root.evalNode("properties"));  
    Properties settings = settingsAsProperties(root.evalNode("settings"));  
    loadCustomVfs(settings);  
    loadCustomLogImpl(settings);  
    typeAliasesElement(root.evalNode("typeAliases"));  
    pluginElement(root.evalNode("plugins"));  
    objectFactoryElement(root.evalNode("objectFactory"));  
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));  
    reflectorFactoryElement(root.evalNode("reflectorFactory"));  
    settingsElement(settings);  
    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);  
  }  
}

全局配置文件里面的每一个属性,都有对应的方法进行解析。解析成一个一个对象赋值给最大的那个 Configuration 对象,到此就完事了。

解析插件代码

上面的解析配置文件的方法很多,本文不会把所有的解析代码都一一探究,就使用插件解析代码进行举例说明吧。也就是下面这行代码:

    pluginElement(root.evalNode("plugins"));  

在研究源码之前,我们先了解下插件机制,下面的内容都是从官网复制的:


MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java  
@Intercepts({@Signature(  
        type= Executor.class,  
        method = "update",  
        args = {MappedStatement.class,Object.class})})  
public class ExamplePlugin implements Interceptor {  
    private Properties properties = new Properties();  
    public Object intercept(Invocation invocation) throws Throwable {  
        // implement pre processing if need  
        Object returnObject = invocation.proceed();  
        // implement post processing if need  
        return returnObject;  
    }  
    public void setProperties(Properties properties) {  
        this.properties = properties;  
    }  
}

下面是如何配置:

<!-- mybatis-config.xml -->  
<plugins>  
    <plugin interceptor="org.mybatis.example.ExamplePlugin">  
        <property name="someProperty" value="100"/>  
    </plugin>  
</plugins>

现在我们想如何把上面配置文件的代码解析出来,源码如下:

private void pluginElement(XNode parent) throws Exception {  
    if (parent != null) {  
        for (XNode child : parent.getChildren()) {  
            String interceptor = child.getStringAttribute("interceptor");  
            Properties properties = child.getChildrenAsProperties();  
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();  
            interceptorInstance.setProperties(properties);  
            configuration.addInterceptor(interceptorInstance);  
        }  
    }  
}

由于 plugin 可以有多个,因此代码循环解析,对于每一个 plugin,首先拿到属性 interceptor,也就是自定义插件的实现类,如上面官网的例子 ExamplePlugin ,通过反射生成对象实例,该对象有个属性 Properties,也就是 mybatis-config.xml 中的

 <property name="someProperty" value="100"/>  

内容,只不过被

child.getChildrenAsProperties()

进行解析成键值对形式的 Properties 对象,代码如下

public Properties getChildrenAsProperties() {  
    Properties properties = new Properties();  
    Iterator var2 = this.getChildren().iterator();  
  
    while(var2.hasNext()) {  
        XNode child = (XNode)var2.next();  
        String name = child.getStringAttribute("name");  
        String value = child.getStringAttribute("value");  
        if (name != null && value != null) {  
            properties.setProperty(name, value);  
        }  
    }  
  
    return properties;  
}

这段代码比较简单,循环取 name 和 value 的值给 Properties 对象而已。

对于mybatis-config.xml其它配置,也是通过类似的方式解析成相关对象,最终都赋值给 Configuration对象而已。

指尖改变世界
27 声望6 粉丝