注意:文章内容较多,请耐心阅读。
1 概述
前面我们分析了BaseBuilder,BaseBuilder提供的主要功能是处理类型的转换。现在我们就来分心XMLConfigBuilder,这个类的主要作用就是解析mybatis的配置文件。
2 属性
2.1 parsed
private boolean parsed;
这个属性用于控制XMLConfigBuilder是否被使用,因为一个XMLConfigBuilder仅仅能够被使用一次。
2.2 parser
private XPathParser parser;
parser提供文档解析功能,将文档document解析成xnode节点。
接下来我们来看一下XPathParser的重要属性和函数。
2.2.1 document
Document接口代表HTML或者XML文档,我们可以通过Document对象来获取对应的HTML或者XML文档的内容。
2.2.2 validation
用来设置解析器在解析文档的时候是否进行文档的验证。
2.2.3 entityResolver
针对EntityResolver接口,mybatis中使用的其实是XMLMapperEntityResolver。所以这里我们仅仅分析下XMLMapperEntityResolver的用处。
我们先来看一下XMLMapperEntityResolver的属性。
public class XMLMapperEntityResolver implements EntityResolver {
private static final Map<String, String> doctypeMap = new HashMap<String, String>();
private static final String IBATIS_CONFIG_PUBLIC = "-//ibatis.apache.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_CONFIG_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-config.dtd".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_MAPPER_PUBLIC = "-//ibatis.apache.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String IBATIS_MAPPER_SYSTEM = "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_PUBLIC = "-//mybatis.org//DTD Config 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_SYSTEM = "http://mybatis.org/dtd/mybatis-3-config.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_MAPPER_PUBLIC = "-//mybatis.org//DTD Mapper 3.0//EN".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_MAPPER_SYSTEM = "http://mybatis.org/dtd/mybatis-3-mapper.dtd".toUpperCase(Locale.ENGLISH);
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
...
针对上面的属性内容,前开你八项分别是Mybatis中可以使用的XML文件(配置文件和mapper文件)对应的public_id和system_id。
例如Mybatis的配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
可以看见这里和上面的DOCTYPE刚好和MYBATIS_CONFIG_PUBLIC与MYBATIS_CONFIG_SYSTEM对应。通过public_id或者system_id可以获取mybatis对应的dtd文件,这里简单说名义下dtd文件。DTD 是一套关于标记符的语法规则。它是XML1.0版规格得一部分,是XML文件的验证机制,属于XML文件组成的一部分,简单说这里的dtd文件就起到了保证配置文件和mapper文件符合mybatis的使用要求。
接下来我们来看一下对应的函数。
(1) resolveEntity函数
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
if (publicId != null) {
publicId = publicId.toUpperCase(Locale.ENGLISH);
}
if (systemId != null) {
systemId = systemId.toUpperCase(Locale.ENGLISH);
}
InputSource source = null;
try {
//获取path(mapper或者config配置文件对应的dtd的路径)
String path = doctypeMap.get(publicId);
//获取资源流
source = getInputSource(path, source);
if (source == null) {
path = doctypeMap.get(systemId);
source = getInputSource(path, source);
}
} catch (Exception e) {
throw new SAXException(e.toString());
}
return source;
}
(2) getInputSource
private InputSource getInputSource(String path, InputSource source) {
if (path != null) {
InputStream in;
try {
//这里再次使用到了Resource来获取输入流
in = Resources.getResourceAsStream(path);
source = new InputSource(in);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
2.2.4 variables
private Properties variables;
存储properties属性文件的内容。
2.2.5 xpath
针对XPath属性,这里有需要稍稍详细一点说明了。
XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。
也就是我们可以使用XPath来在XML文档中查找数据。具体关于XPath的信息我们可以查询网上的相关资料。
2.2.6 commonConstructor
XPathParser的构造函数都是调用这个函数来进行属性的赋值,我们来看一看commonConstructor函数做了什么。
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
//工厂方法生成XPath
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
2.2.7 createDocument
这个函数主要用于将InputSource转换成Document。
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
//转换成Document
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
2.2.8 evaluate
这个函数其实就是依靠XPath来获取document中的内容。
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
第一个参数时节点内容的表达式,第二个参数时获取内容的来源,最后一个参数时返回的类型。
在Mybatis中常见的返回类型有:XPathConstants.STRING,XPathConstants.NUMBER,XPathConstants.BOOLEAN等。
针对XPathParser的分析就到这里,接下来我们继续分析我们的XMLConfigBuilder。
2.3 environment
private String environment;
这个属性时用于设置当前的服务环境。如果没有指定,默认就为default。
2.4 localReflectorFactory
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
ReflectorFactory用于获取Reflector的,这个Reflector其实就是对类定义信息的封装,里面包含了属性名和get/set函数。
后面我们将对Mybatis中的反射工具进行详细分析。
上面就是对XMLConfigBuilder的属性的分析,接下来我们将继续分析他的重要函数。
3 函数
3.1 构造函数
针对构造函数我们看一个最基础的就行了。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//调用积累的构造函数来设置Configuration属性。
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
可以看出来构造函数比较简单,其实就是对属性的设置。
3.2 parse
public Configuration parse() {
//这里就体现了XMLConfigBuilder初始化之后就只能使用一次。
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//这里首先调用了XPathParser的evalNode函数来获取到configuration对应的XNode对象。然后又调用 parseConfiguration函数进行属性解析,
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
在调用parseConfiguration函数进行属性解析的时候,会将解析的属性直接设置到Configutaion对象中。
3.3 parseConfiguration
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(root.evalNode("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);
}
}
这里我们简单看一下evalNode函数干了什么。
public XNode evalNode(String expression) {
return xpathParser.evalNode(node, expression);
}
这里其实调用了XPathParser的evalNode函数来将node中对应expression的节点解析成XNode。
parseConfiguration函数其实就是调用了解析mybatis不同功能节点的函数来获取配置文件中的不同内容并且设置到configuration对象中。
下面我们一个函数一个函数来看一看这些解析配置文件的函数到底做了什么。
3.4 propertiesElement函数
针对这个函数的学习我们有必要查看一下properties节点的内容。
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
从上面可以看出我们可以通过resource指定属性文件的获取路径,另外也可以通过url指定。
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//获取context下的子节点property组成的XNode,然后获取里面的name和value来组成Properties
Properties defaults = context.getChildrenAsProperties();
//获取节点context的resources属性
String resource = context.getStringAttribute("resource");
//获取节点的url属性
String url = context.getStringAttribute("url");
//从这里我们可以看出properties节点不能够同时通过url或者resource来指定属性文件的来源。
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
//将从属性文件中获取到的properties属性设置到defaults中
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);
}
//将属性设置到XParser和Configuration中
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
3.5 settingsElement函数
在parseConfiguration函数中,有如下代码:
settingsElement(root.evalNode("settings"));
从这行代码我们可以看出传入settingsElement函数的参数其实就是configuration节点的settings子节点。
我们来看一下settings子节点的内容:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
</settings>
这个节点的作用其实即使对mybatis特性的设置。
private void settingsElement(XNode context) throws Exception {
if (context != null) {
//获取setting子节点的name和value属性转换成Properties对象
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
//检查setting中的name是否都包含在Configutration中
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
//使用setting中的value设置到对应的configuration属性中
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
}
3.6 typeAliasesElement函数
这个函数的作用就是解析类型别名节点(typeAliases),来获取里面的子节点的内容。
我们来看一下这个节点在XML配置文件中是什么样子的。
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>
或者
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
至于typeAliases的具体使用我们可以查看mybatis的官方文档。
private void typeAliasesElement(XNode parent) {
if (parent != null) {
//遍历typeAliases节点的子节点
for (XNode child : parent.getChildren()) {
//子节点为package
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
//调用configuration的类型别名注册器进行类型别名的注册
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
//通过反射获取类型的Class对象
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);
}
}
}
}
}
3.7 pluginElement函数
这个函数的作用是解析mybatis的插件节点。我们这个还是来看一下插件节点在xml配置文件中是什么样子的。
<plugins>
<plugin interceptor="com.liutao.mybatis.util.PagingPlugin">
<property name="defaultPage" value="1" />
<property name="defaultPageSize" value="5" />
</plugin>
</plugins>
传入plugins节点对应的XNode作为参数后来遍历子节点进行插件信息的获取。下面我们看一下具体的函数逻辑。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//遍历子节点(即plugin节点)
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
//获取plugin节点对应的属性
Properties properties = child.getChildrenAsProperties();
//生成插件对应的实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//设置属性
interceptorInstance.setProperties(properties);
//将插件添加到Configuration的插件容器中
configuration.addInterceptor(interceptorInstance);
}
}
}
这里我们需要注意的是mybatis设计插件使用到了责任链模式。
3.8 objectFactoryElement函数
这个函数的作用是读取自定义对象工厂的配置,并生成自定义对象工厂。我们来简单看一下配置。
<objectFactory type="com.liutao.mybatis.util.MyObjectFactory">
<property name="username" value="liutao"/>
</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).newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
其实对象工厂节点的解析也比较简单,利用反射生成对象工厂对象,然后设置对应的属性就行。
3.9 objectWrapperFactoryElement函数
这里我们需要说明一下ObjectWrapperFactory和ObjectWrapper的用处。
ObjectWrapper里面封装了对对象的get/set方法的相关操作,包括判断有无和获取名称。也可以通过ObjectWapper来获取对象的属性和的属性设置。
调用ObjectWrapperFactory的getWrapperFor函数就可以生成对应的ObjectWapper。
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
这里的函数逻辑就比较简单了,获取到类名,然后使用反射生成对应的对象并设置到configuration中即可。
3.10 reflectionFactoryElement函数
这个函数的逻辑和objectWrapperFactoryElement函数类似,这里我们就不进一步说明了。
3.11 environmentsElement函数
这里还是先看一下xml配置文件中对应的配置信息。
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
从上面的配置节点environments,我们可以看出这里可以配置多种环境数据。
环境节点里面包含了事务管理和数据库连接信息。我们来看一下具体的函数逻辑。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
//当没有设置环境变量的时候,这里默认使用default
if (environment == null) {
environment = context.getStringAttribute("default");
}
//遍历environments节点的子节点
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
//如果environment节点的id和XMLConfigBuilder的environment属性一致则获取对应的节点内容创建环境变量
if (isSpecifiedEnvironment(id)) {
//根据transactionManager子节点来创建对应的TransactionFactory对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//根据dataSource子节点来创建对应的DataSourceFactory对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取dataSource对象
DataSource dataSource = dsFactory.getDataSource();
//使用Environment的内部类创建环境对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
这里针对上面的部分逻辑我们来看一下具体的执行函数。
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
//获取节点的type属性,这个其实就是事务管理类型的事务工厂的类的别名
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
dataSourceElement的实现和transactionManagerElement类似,这里就不进一步说明了。
3.12 databaseIdProviderElement函数
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。而为了支持多厂商,我们就需要在 mybatis-config.xml 文件中加入 databaseIdProvider。
在XML配置文件中的具体形式如下:
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
下面我们来看看具体的函数逻辑:
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
//当type设置成“VENDOR”的时候自动替换成“DB_VENDOR”
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
//获取DB_VENDOR对应的DatabaseIdProvider对象
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
//这里先从environment中获取到数据库信息,然后在调用getDatabaseId函数来获取到dataBaseId,针对这里我们需要细看一下。
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
我们来看一看getDatabaseId函数是怎样获取到databaseId的。
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
从上面这个函数我们可以看出最终调用的是getDatabaseName直接返回返回值。
private String getDatabaseName(DataSource dataSource) throws SQLException {
//获取到数据库的产品名称,例如当我们使用的MySQL数据库的时候就可以看见这个地方是MySQL
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
//遍历属性值获取productName对应的value,如果有就直接返回
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
return productName;
}
从这里我们可以看出,最终是通过databaseIdProvider节点下面的属性的键值对来确定数据库厂商的id。
关于类型处理器的函数typeHandlerElement我们这里就不进一步分析了,比较简单。接下来我们重点看一下mapperElement函数。
3.13 mapperElement函数
我们还是先来看看mybatis配置文件里面是咋个配置的。
1、使用相对于类路径的资源引用
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
2、使用完全限定资源定位符(URL)
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
3、使用映射器接口实现类的完全限定类名
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
4、将包内的映射器接口实现全部注册为映射器
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
从上面可以看出我们可以通过resource、url、class和name属性来告知mapper配置文件的位置。但是我们需要注意的是一个人mapper节点中仅仅只能有一种属性。
下面我们来看一看具体的函数逻辑。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//针对子节点为package的情况
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
//直接将包名传入configuration的addMappers函数。
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//针对resource和url的情况,我们在分析XMLMapperBuilder类的源码的时候进行详细的说明
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) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
//针对mapperClass的情况,调用configuration的addMapper函数
} else if (resource == null && url == null && mapperClass != null) {
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.");
}
}
}
}
}
接下来我们具体看一看addMappers函数。直接查看MapperRegistry的addMappers函数。
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
//遍历包下面的类
for (Class<?> mapperClass : mapperSet) {
//调用addMapper函数。
addMapper(mapperClass);
}
}
接下来看下addMapper函数:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
//如果已经有接口对应的mapper代理工厂则抛出异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//添加接口与对应的mapper代理工厂
knownMappers.put(type, new MapperProxyFactory<T>(type));
//解析指定的mapper接口对应的Class对象中,包含的所有mybatis框架中定义的注解,并生成Cache、ResultMap、MappedStatement三种类型对象,
//这里我们将在后面详细分析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
通过上面的函数就完成了mybatis配置文件和mapper文件的解析,并且设置到了configuration对象中。下面我们将继续分析其余的BaseBuilder子类,欢迎交流。