代码的调试,我们可以使用上一章的任意一个测试代码作为 Debug 载体,本章我们研究的其实是这两句代码:
InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
也就是如何加载 MyBatis 全局配置文件,以及如何由全局配置文件构建出 SqlSessionFactory
。
1. 全局配置文件的加载
首先我们来看配置文件的加载,这个 Resources.getResourceAsStream
方法,只从方法名上,想必小伙伴也能猜出来,它应该是借助类加载器吧,我们快速的看一眼源码:
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
这个地方看上去没有传入 ClassLoader
,实际上取 ClassLoader
的地方在另外一个位置:
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader
};
}
一下子取这么多 ClassLoader
,图个啥咧?很明显,它是想挨个 ClassLoader
都试一遍,只要能取到资源,就 OK 。下面是实际利用 ClassLoader
加载全局配置文件的底层源码:
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
逻辑很清晰吧,所以使用这种方式,就可以很简单的获取到全局配置文件的二进制流了。
2. 解析配置文件
下面就是解析的过程了,我们的测试代码是直接 new 了一个 SqlSessionFactoryBuilder
,随后调 build
方法构造出 SqlSessionFactory
:
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
而 build
方法最终来到了一个三参数的重载方法中:
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} // catch finally ......
}
可见,这里面用到的第一个底层核心组件,是 XMLConfigBuilder
,直译为基于 xml 的配置建造器(建造器模式的体现)。而这个 XMLConfigBuilder
,首先继承了一个叫 BaseBuilder
的东西:
public class XMLConfigBuilder extends BaseBuilder {
// ......
}
我们先来研究这两个类的构造。
2.1 BaseBuilder
BaseBuilder
顾名思义,它是一个基础的构造器,它的初始化需要传入 MyBatis 的全局配置对象 Configuration
:
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();
}
这个 Configuration
我们都知道,最终 MyBatis 初始化完成后,所有的配置项、Mapper 、statement 都会存放到这里,小伙伴们能回忆起来就 OK 。
往下大致扫一眼定义的方法,大多数都是一些解析、获取之类的方法,看上去更像是提供基础的工具类方法支撑:(下面是引用的 BaseBuilder
中定义的两个方法)
protected Boolean booleanValueOf(String value, Boolean defaultValue) {
return value == null ? defaultValue : Boolean.valueOf(value);
}
protected JdbcType resolveJdbcType(String alias) {
if (alias == null) {
return null;
}
try {
return JdbcType.valueOf(alias);
} catch (IllegalArgumentException e) {
throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
}
}
那照这样来看,核心的处理逻辑并不在 BaseBuilder
中,我们回到实现类 XMLConfigBuilder
中。
2.2 XMLConfigBuilder
照例,我们还是先看一眼内部成员,以及构造方法的定义。
2.2.1 构造方法定义
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// 注意这里new了一个XPathParser
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
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;
}
源码中的构造方法重载的特别多,而上面在 SqlSessionFactoryBuilder
中调用的 build
方法,最终是调用的上面代码中的三参数构造方法,这个构造方法又调用了下面重载的构造方法。注意源码中,它重载的构造方法不再需要 InputStream
,而是构造了一个 XPathParser
,这个家伙虽然我们也没见过,但也能大概猜出来,它就是解析 xml 全局配置文件的解析器,这个东西我们没有必要先去研究,到下面用到的时候再顺道着看就 OK 。
另外,最下面的构造方法中,可以发现 Configuration
对象是在这里 new 出来的,而且是非常朴实无华的、用空参构造方法 new 出来,所以各位小伙伴可以先了解一个事情:如果真要我们自己去操作,去初始化 MyBatis 的 Configuration
,也不是不行,我们自己操作都无所谓。
其余的,最下面的构造方法中,都是普通的赋值操作,也没什么好说的。
2.2.2 核心parse方法
接下来的代码就是 return build(parser.parse());
了,这一行代码实际上是两个方法,首先它先调用 XMLConfigBuilder
的 parse
方法,生成 Configuration
,之后才是 SqlSessionFactoryBuilder
的 build
方法。我们先来看 XMLConfigBuilder
是如何解析配置文件的。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
最核心的方法还是中间的 parseConfiguration
方法,不过在此之前,我们关注一下 parser.evalNode("/configuration")
这个动作。
2.2.2.1 XPathParser#evalNode
XPathParser
的作用就是将 xml 配置文件转为 Document
对象,并提供对应的 xml 标签节点。它的 evalNode
方法,就是用来获取 xml 中指定的标签:
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 使用javax的XPath解析xml
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
至于 XPath
内部怎么做的,那是 xml 解析的机制了,我们不关心,我们唯一要关心的是,中间的 evalNode
方法解析出 Node
之后,return 处又封装成了一个 XNode
。它到底是图个啥呢?
2.2.2.2 Node封装为XNode的意图
注意看 XNode
的构造方法,它额外传入了一个 variables
对象,而这个 variables
实际上就是我们在全局配置文件中,定义的那些 <properties>
标签,以及引入的 .properties
文件。可为什么又跟这些配置属性值牵扯上了呢?我们回忆一下,之前我们在全局配置文件中写过这样的代码吧:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
如果真的要取这里面的 driver 属性,肯定不能把 ${jdbc.driverClassName}
拿出来吧,得动态替换配置属性的值。但是 javax 原生的 Node
可实现不了这玩意,所以 MyBatis 就基于 javax 的 Node
封装了一个 XNode
,并组合 XPathParser
,就可以实现动态解析配置属性值的效果了。
可能这样解释的话,很多小伙伴会一脸茫然,小册举个例子。
以上面的 xml 为例,当我们取到
<dataSource>
标签后,要解析其中的driver
、url
等属性,而这些属性的 value 是一些占位符,在解析的时候,MyBatis 会先跟平常一样,解析出<property>
标签,然后获取 value 属性(比方说解析driver
属性吧,这样返回的 value 是${jdbc.driverClassName}
),然后!它会使用一个占位符解析器,去解析一下这个占位符,并根据properties>
标签中定义 / 加载的配置属性,替换为对应的属性值。通过这个步骤,${jdbc.driverClassName}
就被替换为了com.mysql.jdbc.Driver
,这样也就实现了动态配置属性值的解析。
这样解释一下之后是不是就好理解一些了呢?下面我们再来看源码:
// XNode
public String evalString(String expression) {
// 自己不解析,委托XPathParser去解析
return xpathParser.evalString(node, expression);
}
// XPathParser
public String evalString(Object root, String expression) {
// 先从标签中取出明文属性值
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// 交由占位服务解析器,处理占位符,替换为真实配置值
result = PropertyParser.parse(result, variables);
return result;
}
处理思路一目了然,所以这也就能明白,为什么 MyBatis 选择自己额外封一层 XNode
,而不是直接用 javax 的那个 Node
了吧。
2.2.3 parseConfiguration
回到上面的 parse
方法中,parse
方法的本质,是解析 <configuration>
标签的内容,所以下面我们进入 parseConfiguration
方法中:
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
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);
// read it after objectFactory and objectWrapperFactory issue #631
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);
}
}
呀,哎呀呀呀呀呀呀呀,这不就是把整个 MyBatis 全局配置文件,从头到尾顺序解析了一遍吗?这也太朴实无华了吧!OK ,那既然我们挖掘到了这个地方,下面我们就可以逐个来看了。
2.2.3.1 propertiesElement-解析properties
首先解析的是 <properties>
标签,这里面它就会解析内部定义的 <property>
,以及配置的 resource
、url
属性:(关键注释已标注在源码中)
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 先加载内部定义的<property>
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
// 二者不可兼得
// throw ex ......
}
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);
}
// 将配置属性值放入解析器、全局配置中
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
纵读整段源码,可以发现整个加载的流程,就是我们在上一章中解释的流程。而且通过源码的走读,我们也能明白为什么配置的优先级会是编程式的最高,properties 文件次之,配置文件内定义的最低了。
这个方法很简单,源码中也标注了注释,小册就不过多展开讲解了。
2.2.3.2 settingsAsProperties-加载配置项
下面是解析 <settings>
标签了,这个标签的解析涉及到 3 行代码:
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
也不难理解,这个操作很明显是将 <settings>
标签中的一行行配置,封装为一个 Properties
,然后额外处理一下 VFS 和 Log 组件。关于 VFS 的内容小册暂时不提,我们先来看看底层如何处理 Log 组件的配置:
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
嚯,这么简单的逻辑吗?它直接从配置中取到 logImpl
的配置值,然后设置到 Configuration
中就完事了,而这个 resolveClass
方法,其实是用别名解析的:
// BaseBuilder
protected <T> Class<? extends T> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} // catch ......
}
protected <T> Class<? extends T> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
而这些别名的注册,早在 Configuration
创建的时候,就全部初始化好了:
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
// ......
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
// ......
}
so 我们在配置那些 setting 配置项的时候,可供填充的内容,其实是参考自这里 MyBatis 预定的别名(这也能解释为什么我们如果在 settings 中配置 log4j
不好使,必须配置 LOG4J
才可以)。
2.2.3.3 typeAliasesElement-注册类型别名
OK ,下面一个要解析的是 typeAliases
标签了,这里面我们知道可以用 <package>
直接扫描,也可以使用 <typeAlias>
直接声明指定类型的别名。底层针对这两种情况分别做了处理:
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 处理package的包扫描指定别名
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
// 注意这里调用的是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);
}
}
}
}
}
注意在源码中,如果是处理 <package>
标签声明的包扫描,此处调用的方法也不一样了!进入到 registerAliases
方法中,我们会发现,这里会使用一个 ResolverUtil
的工具类,来扫描所有类(父类是 Object
),扫描完成后,在下面的 for 循环中,判断这些类是否为普通的类(非接口、非匿名内部类、非内部类),是则注册别名。
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
// 上面传入的是Object
// 注意这个扫描动作是全层次扫描,会扫描到子包
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
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);
}
}
}
注意一点!这里面有一个全层次包扫描的动作!所以配置的 package 实际上是扫描的指定包及其子包下的所有类,并将他们全部注册 alias 别名。
2.2.3.4 pluginElement-注册插件
接下来注册的是 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);
}
}
}
整体下来,它就是简单的把拦截器创建出来,注册进全局 Configuration 中。当然我们要意识到的一件事:拦截器是 MyBatis 自行创建的,如果我们要用 Spring 整合 MyBatis ,并且想让 Spring 管理 MyBatis 的拦截器,似乎不太现实。
2.2.3.5 注册一堆Factory
下面的 3 行代码,注册的都是一些 Factory :
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
这 3 种 Factory 的注册,在底层几乎是一模一样(以 ObjectFactory
为例):
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
可以发现,跟上面初始化拦截器的逻辑几乎一模一样吧!所以这几个逻辑咱也都知道一下就 OK 。
2.2.3.6 settingsElement-应用配置项
接下来的动作是应用之前初始化的配置项,这个方法那是又长又宽(每行代码都好长啊!实在不想全部贴过来),所以我们只截取其中几行代码象征性的看一眼就行:
private void settingsElement(Properties props) {
// ......
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
// ......
}
emmmm ,得了,这不就是把全局配置文件中的那些 <settings>
都应用进全局的 Configuration
对象中嘛,那这也没什么神秘的了,小伙伴们快速扫一遍就 OK 。
2.2.3.7 environmentsElement-数据源环境配置
下面是解析数据库环境配置的部分了,这里面因为存在嵌套标签 <transactionManager>
与 <dataSource>
,所以这里面的源码会稍微复杂一点(也仅仅是一点而已):
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 从default中取出默认的数据库环境配置标识
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
// 只会构造默认的数据库环境配置
if (isSpecifiedEnvironment(id)) {
// 解析transactionManager标签,生成TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析dataSource标签,生成DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
// 简单的建造器,构造出Environment对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
纵读整段源码,思路还是非常清晰的吧,它干的事情就是把事务管理器,以及数据源的配置加载好,构造进 Environment
对象中,完事。而解析 transactionManager
标签,以及 dataSource
标签的逻辑,跟上面解析那一堆 Factory 也都几乎完全一致,所以小册也不重复贴源码了,小伙伴们跟着 IDE 翻一下源码就好。
另外,Environment
的结构,也仅仅是组合了上面的 TransactionFactory
与 DataSource
:
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
// ......
}
2.2.3.8 databaseIdProviderElement-数据库厂商标识解析
接下来是 <databaseIdProvider>
标签的解析,这个东西我们说如果要用的话,就是声明 "DB_VENDOR" ,然后根据不同的数据库厂商,定义好别名即可。这个逻辑反映到源码中也不难理解:
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
// 写 VENDOR 跟写 DB_VENDOR 一样
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
前半段就是正常的初始化 DatabaseIdProvider
类型的对象,关键是下面还有个 configuration.setDatabaseId(databaseId);
的动作,它是干什么呢?
仔细想一下,我们上一章提过,databasseIdProvider
是配合 mapper.xml
中定义 statement 用的,而源码走到这个位置的时候,还没有轮到 mapper.xml
解析,如果像我们上一章那样,在一个 mapper.xml
中定义了两个一样的 statement ,那后面轮到 mapper.xml
解析的时候,MyBatis 一看,呦,两个 statement 竟然 id 一模一样? 这不相当于撞车了吗?不行,这我得给他挂了!所以就会抛出 statement 的 id 相同的异常。但是!这逻辑不对啊,虽然两个 statement 的 id 一致,但 databaseId 不一样啊,一个 SqlSessionFactory
只能连一个数据源,而这个数据源的数据库厂商是确定的,所以这两个 statement 只能有一个可用,所以在解析 mapper.xml
,读取 statement 的时候还要比对一下 databaseId 呢!那这个 databaseId 从哪来呢?很明显可以从全局 Configuration
得到,那上面的这个 setDatabaseId
的动作就可以理解了吧!这个动作就是为了提前确定好数据源对应的数据库厂商,为后面解析 mapper.xml 做准备。
2.2.3.9 typeHandlerElement-注册类型处理器
下面一个解析的是 TypeHandler
了,这里面的逻辑也是比较简单,要么包扫描,要么逐个注册,但最终都是注册到了 typeHandlerRegistry
中:
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 包扫描
if ("package".equals(child.getName())) {
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);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
逻辑很简单,小伙伴们自己翻一下源码就 OK 了,小册不再啰嗦。
2.2.3.10 mapperElement-解析mapper.xml
最后一个节点的解析是 mapper 了,这里面乍一看似乎不是很复杂:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 包扫描Mapper接口
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 处理resource加载的mapper.xml
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 处理url加载的mapper.xml
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 注册单个Mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
但请不要忘记!这个方法完事之后,整个 MyBatis 的初始化工作就完成了!但此时 mapper.xml
还没有加载呢!所以这个环节也是相当重要的!源码中,可以发现,除了包扫描 Mapper 接口,以及单个注册 Mapper 接口之外,其余两个都是解析 mapper.xml
文件。至于解析 mapper.xml
的底层是如何处理,我们放到映射文件的讲解之后再展开讲解,这里我们先知道一点即可:mapper.xml
的解析是使用 XMLMapperBuilder
完成的。
到此为止,整个 MyBatis 全局配置文件的加载就完成了,处理流程相对不算复杂