在 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对象而已。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。