技术标签: XMLConfigBuilder源码 MyBatis
注意:文章内容较多,请耐心阅读。
前面我们分析了BaseBuilder,BaseBuilder提供的主要功能是处理类型的转换。现在我们就来分心XMLConfigBuilder,这个类的主要作用就是解析mybatis的配置文件。
private boolean parsed;
这个属性用于控制XMLConfigBuilder是否被使用,因为一个XMLConfigBuilder仅仅能够被使用一次。
private XPathParser parser;
parser提供文档解析功能,将文档document解析成xnode节点。
接下来我们来看一下XPathParser的重要属性和函数。
Document接口代表HTML或者XML文档,我们可以通过Document对象来获取对应的HTML或者XML文档的内容。
用来设置解析器在解析文档的时候是否进行文档的验证。
针对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;
}
private Properties variables;
存储properties属性文件的内容。
针对XPath属性,这里有需要稍稍详细一点说明了。
XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。
也就是我们可以使用XPath来在XML文档中查找数据。具体关于XPath的信息我们可以查询网上的相关资料。
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();
}
这个函数主要用于将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);
}
}
这个函数其实就是依靠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。
private String environment;
这个属性时用于设置当前的服务环境。如果没有指定,默认就为default。
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
ReflectorFactory用于获取Reflector的,这个Reflector其实就是对类定义信息的封装,里面包含了属性名和get/set函数。
后面我们将对Mybatis中的反射工具进行详细分析。
上面就是对XMLConfigBuilder的属性的分析,接下来我们将继续分析他的重要函数。
针对构造函数我们看一个最基础的就行了。
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;
}
可以看出来构造函数比较简单,其实就是对属性的设置。
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对象中。
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对象中。
下面我们一个函数一个函数来看一看这些解析配置文件的函数到底做了什么。
针对这个函数的学习我们有必要查看一下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);
}
}
在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")));
}
}
这个函数的作用就是解析类型别名节点(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);
}
}
}
}
}
这个函数的作用是解析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设计插件使用到了责任链模式。
这个函数的作用是读取自定义对象工厂的配置,并生成自定义对象工厂。我们来简单看一下配置。
<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);
}
}
其实对象工厂节点的解析也比较简单,利用反射生成对象工厂对象,然后设置对应的属性就行。
这里我们需要说明一下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中即可。
这个函数的逻辑和objectWrapperFactoryElement函数类似,这里我们就不进一步说明了。
这里还是先看一下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类似,这里就不进一步说明了。
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函数。
我们还是先来看看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子类,欢迎交流。
美国NASA如何能提前预知各种天文奇观?风力发电机和创业者开店如何选址?如何才能准确预测并对气象灾害进行预警?包括在未来的城镇化建设过程中,如何打造智能城市?等等,这一系列问题的背后,其实都隐藏着大数据的身影——不仅彰显着大数据的巨大价值,更直观地体现出大数据在各个行业的广阔应用。这些行业应用也都更直白地告诉人们,什么是大数据……其实,大数据不是突然出现的,在过去的几十年间,数学分析就已经涉猎金融...
转自:https://blog.csdn.net/houjian914/article/details/50762056 1、导论与用户交互的程序:基于文本的shell基于图标的图形化用户界面(GUI)操作系统所处的位置:多数计算机有两种运行模式:内核态...
用ajax往后台上传图片时,1. form表单标签里要加属性:enctype="multipart/form-data";图片预览 $('#logos').change(function () {previewImgEvents();});function previewImgEvents() {// 根据这个 获取文件的 HTML5 js 对象var files = event.targ...
本地分支推送至远程git checkout local_branchgit push origin local_branch:remote_branch1、查看远程分支使用以下Git命令查看全部远程分支:git branch -r列出本地分支:gitgit branch删除本地分支:githubgit branch -D BranchName其中-D也能够是--delete,如:服务器git br...
编写子程序——数值显示新人自己分析的,希望大神给予意见~!问题描述: 将数值显示到屏幕上子程序描述 名称:dtoc 功能:将word型数据转变为表示十进制数的字符串,字符串以0结尾 参数:(ax)=word数据, ds:si指向字符串的首地址 返回:无分析 个人认为这个子程序还是蛮简单的。 1、并不是内存中存的多少,在屏幕上就能显示多少,因为显卡遵循ASCII码,除非...
在开发中遇到一个需求,只要来了一个新订单,后台和对应的物流公司就进行语音播报。此方法只适合在window中使用,在Linux中也就是服务器上是运行不了的,我使用的是另外一种方法,有时间会写出来,但觉得该方法也不错,特此记录一下。
1. 题目2. 解答2.1 方法一假设返回 118 以内数的字典顺序,则为 1,10,100,101,102,…,109,11,110,111,112,…,118,12,13,…。根据这个序列,我们发现有以下几种情况。1,10,100,每次都乘以 10100,101,102,…,109,每次加 1109,11,末尾为 9 则先除以 10 直到末尾不为 9 再加 1118,12,排...
学校:东北农业大学专业:工学->农业工程->农业机械化工程年级:2019招生人数:1招生状态:正在招生中导师简介权龙哲,男,1980年生,工学博士,教授,中国农业工程学会青年工作委员会委员,中国农业机械学会青年工作委员会委员,中国机械工业协会教学委员会委员,中国农机学会现代物理农业工程分会会员,黑龙江省创新方法协会理事,黑龙江省力学协会委员。主要从事研究智能农业装备与农业机器人技术,人...
初入前端,在IOS上加载一个html项目,发现前端页面总是上下滑动,导航和下面的tabbar 在上拉和滑动时总是跟着滑动,如图:不明原因,以为是前端页面的问题,一直改,可是无论如何都不行,决定换个思路,在原生项目上做更改,因为是用UIWebView加载的,想着是否可以用它来控制滑动!一行代码完美解决! self.resultwebV.scrollView.bounces = NO...
<br /><br />EditText继承关系:View-->TextView-->EditText<br />EditText的属性很多,这里介绍几个:<br />android:hint="请输入数字!"//设置显示在空间上的提示信息<br />android:numeric="integer"//设置只能输入整数,如果是小数则是:decimal<br />android:singleLine="true"//设置单行输入,一旦设置为true,则文字不会自动换行。<br />android:passw
1、API和自注册机制Skia中编码解码图片都只需要一行代码:SkBitmap bitmap;SkImageDecoder::DecodeFile("test.xxx", &bitmap);//由文件名解码,自动推断图片类型//或者由流解码SkFILEStream stream("test.xxx");SkImageDecoder...
正确的语法:update tablename set '列名1'='属性值','列名2'='属性值','列名3'='属性值' where '主键名'='属性值';错误的语法:update tablename set '列名1'='属性值','列名2'='属性值','列名3'='属性值' where '主键名'='属性值';...