在 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对象而已。
联系客服