在 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
对象而已。
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
就是 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 允许使用插件来拦截的方法调用包括:
这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 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对象而已。