【小盘子看源码-MyBatis-1】MyBatis配置文件的加载流程
众所周知,Mybatis
有一个全局的配置,在程序启动时会加载XML配置文件,将配置信息映射到org.apache.ibatis.session.Configuration
类中,例如如下配置文件。
<?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">
<configuration>
<!--resource="db.properties"-->
<properties resource="db.properties">
<property name="test" value="123456"></property>
</properties>
<settings>
<!-- 控制全局缓存(二级缓存)-->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
</settings>
<typeAliases>
<typeAlias type="com.fanpan26.source.code.mybatis.UserMapper" alias="userMapper"/>
<package name="com.fanpan26.source.code.mybatis.model" />
</typeAliases>
<plugins>
<plugin interceptor="com.fanpan26.source.code.mybatis.plugin.MyPlugin"></plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<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>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
这里我们要注意的是,每个配置项目的顺序不能变,否则在做XML解析的时候会抛异常。
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
那么它是如何做到的呢?下面跟着我揭开它的神秘面纱吧。
代码分析
Configuration
对象是通过XMLConfigBuilder
的parse()
方法得到的,示例代码如下:
XMLConfigBuilder parser = new XMLConfigBuilder(Resources.getResourceAsStream("mybatis-config.xml"));
Configuration configuration = parser.parse();
XMLConfigBuilder
继承自抽象类BaseBuilder
.
构造函数没什么好说的:
public XMLConfigBuilder(InputStream inputStream) {
this(inputStream, null, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
其中,XPathParser
会解析XML中的内容,这里我们就不详细跟进了,我们主要看与MyBatis
息息相关的各种属性是如何加载的。
public Configuration parse() {
//只能解析一次,否则异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//执行具体的解析,从 configuration节点下解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
下面主要跟进parseConfiguration
方法
private void parseConfiguration(XNode root) {
try {
//先读取properties
propertiesElement(root.evalNode("properties"));
//将 settings 转化为 Properties
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//解析typeAliase
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins
pluginElement(root.evalNode("plugins"));
//解析 objectFactory
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析 reflectorFactory
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
//解析环境信息
environmentsElement(root.evalNode("environments"));
//解析databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
解析Properties
这一步就是将配置文件中的<Properties>
解析出,然后放到Configuration
对象的属性键值对。解析过程分为两个部分:
先解析XML中的配置项,例如配置中的
test:123456
信息.<properties resource="db.properties">
<property name="test" value="123456"></property>
</properties>
然后在解析
resource
属性或者url
属性中的信息,注意它俩不能共存,也就是不能既配置resource
又配置url
解析XML中的属性配置项:
Properties defaults = context.getChildrenAsProperties();
//XNode 中的方法
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
//遍历子节点信息,读取出name和value属性,赋值给Properties对象
for (XNode child : getChildren()) {
//获取name属性
String name = child.getStringAttribute("name");
//获取value属性
String value = child.getStringAttribute("value");
//name 和value 都不能为 null
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
然后开始解析resource
或者url
信息
//获取resource 属性
String resource = context.getStringAttribute("resource");
//获取Java属性
String url = context.getStringAttribute("url");
//它俩不能共存,只能配置一个
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.");
}
然后直接调用Resources
工具类的方法,将资源转化为Properties
的key value
值.
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
最后在和Configuration.variables
合并.
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
//XPathParser 保存变量属性留在后边解析备用
parser.setVariables(defaults);
//所有的属性信息就解析完毕,存放到 Configuration 的 变量 variables (Properties类型) 中
configuration.setVariables(defaults);
将 settings 转化为 Properties
Properties settings = settingsAsProperties(root.evalNode("settings"));
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
//这里就能获取所有的属性键值对了,但是后续要判断键值对中的键是否是Configuration中的属性。
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
//如果没有该属性的setter方法,就抛出异常,因为 setting中的配置是和Configuration中一一对应的,这里不能配置错
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
赋值 VFS 虚拟文件系统
loadCustomVfs(settings);
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
//读取 vfsImpl 配置信息
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
赋值 Logger
loadCustomLogImpl(settings);
private void loadCustomLogImpl(Properties props) {
//读取 logImpl属性,获取类信息
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
解析 typeAlias
<typeAliases>
<typeAlias type="com.fanpan26.source.code.mybatis.UserMapper" alias="userMapper"/>
<package name="com.fanpan26.source.code.mybatis.model" />
</typeAliases>
Configuration
对象中有一个TypeAliasRegistry
对象,TypeAliasRegistry
中有一个HashMap<String,Class<?>>
类型的 typeAlias
集合,所有的注册信息都是存放到该HashMap
中,MyBatis
本身内置了基础类型的映射关系。
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
当配置文件中存在<typeAlias>
属性的时候,就将alias
注册上。
typeAliasesElement(root.evalNode("typeAliases"));
其中,它有两种注册方式,一种就是通过package
批量注册,毕竟一个类一个类的写太麻烦了。另外就是单个注册。
单个注册方式:
//读取属性中的 alias 值
String alias = child.getStringAttribute("alias");
//读取类型
String type = child.getStringAttribute("type");
try {
//反射获取类
Class<?> clazz = Resources.classForName(type);
//如果没有配置 alias,没关系,按照默认 名称注册
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
//否则直接注册
typeAliasRegistry.registerAlias(alias, clazz);
}
按照默认名称注册
public void registerAlias(Class<?> type) {
//反射获取类名
String alias = type.getSimpleName();
//这里是为了支持注解,如果存在Alias注解,那么使用Alias注解中的value值
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
//注册
registerAlias(alias, type);
}
按照package
注册,思路是这样的,遍历package
下所有的类进行注册。
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
//获取packages下的所有的类
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for (Class<?> type : typeSet) {
//排除接口,内部类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
注册plugins
<plugins>
<plugin interceptor="com.fanpan26.source.code.mybatis.plugin.MyPlugin"></plugin>
</plugins>
pluginElement(root.evalNode("plugins"));
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//获取 interceptor 属性
String interceptor = child.getStringAttribute("interceptor");
//获取属性信息
Properties properties = child.getChildrenAsProperties();
//这里利用反射将 Interceptor 实例化,然后调用 setProperties 方法
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
//最后将 Interceptor 实例加入到拦截链中,List<Interceptor> 对象
configuration.addInterceptor(interceptorInstance);
}
}
}
注册 ObjectFactory
其实大多数场景下,使用默认的 DefaultObjectFactory
即可。
<objectFactory type="com.fanpan26.source.code.mybatis.factory.MyObjectFactory">
<property name="a" value="b"/>
</objectFactory>
objectFactoryElement(root.evalNode("objectFactory"));
//注册 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);
//调用 setObjectFactory 方法
// protected ObjectFactory objectFactory = new DefaultObjectFactory();
configuration.setObjectFactory(factory);
}
}
注册 ObjectWrapperFactory
<objectWrapperFactory type="com.fanpan26.source.code.mybatis.factory.MyObjectWrapperFactory"/>
默认是 DefaultObjectWrapperFactory
,不过这个类好像没啥用
public class DefaultObjectWrapperFactory implements ObjectWrapperFactory {
public DefaultObjectWrapperFactory() {
}
public boolean hasWrapperFor(Object object) {
return false;
}
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
throw new ReflectionException("The DefaultObjectWrapperFactory should never be called to provide an ObjectWrapper.");
}
}
注册方法如下:
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
//获取类全名
String type = context.getStringAttribute("type");
//反射创建
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//设置属性
//protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
configuration.setObjectWrapperFactory(factory);
}
}
注册reflectorFactory
<reflectorFactory type="com.fanpan26.source.code.mybatis.factory.MyReflectorFactory"/>
reflectorFactory
用户创建类的 Reflector 对象。其中带有缓存功能。使用 ConcurrentHashMap<Class<?>,Reflector>
缓存。
reflectorFactoryElement(root.evalNode("reflectorFactory"));
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
//获取类型
String type = context.getStringAttribute("type");
//反射生成对象
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//设置
//protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
configuration.setReflectorFactory(factory);
}
}
注册 settings
就是将<settings>
节点中的所有设置的属性值都设置到Configuration
对象中。
settingsElement(settings);
private void settingsElement(Properties props) {
//自动映射等
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
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"), false));
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.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
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.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
注册 environments
environmentsElement(root.evalNode("environments"));
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//这里如果之前没有传递 environment 参数,则取 default 值
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
//获取id
String id = child.getStringAttribute("id");
//这里会判断id是否为空或者 evnironment是否为空,
if (isSpecifiedEnvironment(id)) {
//获取事务相关配置,这里和之前的代码差不多,解析type内容,然后反射创建对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//获取DataSource相关配置
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//获取DataSource
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//设置环境
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
这里以 PooledDataSource
举例,看看DataSource
如何初始化的。
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
//先获取DataSourceFactory类型,这里是 POOLED
String type = context.getStringAttribute("type");
//获取子属性,相关数据库配置, url username password 等
Properties props = context.getChildrenAsProperties();
//反射创建 DataSourceFactory,这里是 PooledDataSourceFactory 实例
//org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
//这里设置属性的时候,利用了反射,将各个属性值赋值给了 DataSource 对象
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
注册 DatabaseIdProvider
<databaseIdProvider type="com.fanpan26.source.code.mybatis.factory.MyDatabaseIdProvider">
<property name="dataBaseId" value="db1"/>
</databaseIdProvider>
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
//获取类全名
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
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) {
//获取dataBaseId
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
//将dataBaseId设置给environment
configuration.setDatabaseId(databaseId);
}
}
注册 TypeHandlers
<typeHandlers>
<typeHandler handler="com.fanpan26.source.code.mybatis.handler.MyTypeHandler"></typeHandler>
<package name="com.fanpan26.source.code.mybatis.handler"/>
</typeHandlers>
和 typeAlias
类似,都可以通过单独注册和package
批量注册
typeHandlerElement(root.evalNode("typeHandlers"));
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//获取package name
String typeHandlerPackage = child.getStringAttribute("name");
//批量注册
typeHandlerRegistry.register(typeHandlerPackage);
} else {
/*
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler = new UnknownTypeHandler(this);
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
*/
//获取java类型
String javaTypeName = child.getStringAttribute("javaType");
//获取jdbcType类型
String jdbcTypeName = child.getStringAttribute("jdbcType");
//获取handler
String handlerTypeName = child.getStringAttribute("handler");
//反射获取类型
Class<?> javaTypeClass = resolveClass(javaTypeName);
//反射获取JdbcType类型
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
//注册自定义映射处理器,后续源码分析会分析 TypeHandler的作用
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
注册 mappers
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
注册核心业务处理接口 Mapper
mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//扫描包注册
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
//内部执行了批量注册,将 package 下的接口都注册到mappers中
configuration.addMappers(mapperPackage);
} else {
//resource 注册,通常是 xml 文件
String resource = child.getStringAttribute("resource");
//或者通过url注册,同理url或者resource不能同时使用
String url = child.getStringAttribute("url");
//获取 class
String mapperClass = child.getStringAttribute("class");
//当只有resource的时候
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();
//当使用url的时候
} 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();
//最后解析class
} else if (resource == null && url == null && mapperClass != null) {
//注册class
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.");
}
}
}
}
}
mapper的解析比较复杂,尤其是解析XML文件,需要解析内部的各种属性元素等。
从xml文件解析Mapper流程
从xml文件解析Mapper
和解析Configuration
差不多,无非就是解析xml元素,然后找到对应属性赋值即可。
InputStream inputStream = Resources.getResourceAsStream(resource);
//读取配置文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//详细解析流程
mapperParser.parse();
下面看一下parse
方法
public void parse() {
//如果该文件已经加载过,不必重新加载
if (!configuration.isResourceLoaded(resource)) {
//解析 mapper 节点下的信息 select insert update delete
configurationElement(parser.evalNode("/mapper"));
//将资源加入到已加载资源列表中
configuration.addLoadedResource(resource);
//将mapper加入到configuration中
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
总结
MyBatis
的Configuration
对象的加载就告一段落,这就意味着MyBatis
准备就绪了。接下来就要解析一个重要的对象SqlSessionFactory
了,因为它负责创建SqlSession
,而SqlSession
则负责执行各种SQL和方法。
【小盘子看源码-MyBatis-1】MyBatis配置文件的加载流程的更多相关文章
- angular源码分析:angular的整个加载流程
在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...
- [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler
[源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler 目录 [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampl ...
- [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader
[源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 目录 [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 0x00 摘要 0x01 ...
- laravel框架源码分析(一)自动加载
一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...
- Spring源码分析(二十一)加载BeanFactory
摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.定制化BeanFactory 二.加载BeanDefinit ...
- easyui源码翻译1.32--EasyLoader(简单加载)
前言 扩展自$.fn.datebox.defaults,使用$.fn.datetimebox.defaults重写默认值对象.下载该插件翻译源码 源码 /** * jQuery EasyUI 1.3. ...
- requireJS 源码(二) data-main 的加载实现
(一)requireJs 的整体结构: requireJS 源码 前192行,是一些 变量的声明,工具函数的实现 以及 对 三个全局变量(requirejs,require,define)若被占用后的 ...
- 【 js 模块加载 】【源码学习】深入学习模块化加载(node.js 模块源码)
文章提纲: 第一部分:介绍模块规范及之间区别 第二部分:以 node.js 实现模块化规范 源码,深入学习. 一.模块规范 说到模块化加载,就不得先说一说模块规范.模块规范是用来约束每个模块,让其必须 ...
- 6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?
Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...
随机推荐
- FPGA的电源选择重要性分析
米尔Z-Turn Board 7Z010(20)板卡体验有感:IF"> FPGA的电源需求通常很复杂,因为FPGA有多达至少三种供电要求,为了实现可靠的系统性能,必须对这些要求排序. ...
- HTML 初始
HTML(Hyper Text Markup Language的缩写)中文译为“超文本标记语言”,主要是通过HTML标签对网页中的文本.图片.声音等内容进行描述. 一.HTML 骨架结构 每种语言都有 ...
- FreePascal - Typhon如何添加不能识别单元?
Typhon 32位 6.9 问题:想使用LSUtils单元,这个单元在Lazarus里面,直接引入就可以使用,而且单元头注释明显写明是CodeTyphon工程的一部分,那么正常在Typhon只要引入 ...
- 汽车行业如何个性化定制转型?看APS系统在这家企业的运用
传统汽车行业中往往采用的是按库存推动式生产,一旦市场产生变动就会造成大量的生产,给企业带来大批的资金压力,而另一方面采用按单生产的方式企业往往面临供应链,产能的诸多约束条件限制,稍有不慎就会带来产线停 ...
- 补充1:IDA的脚本IDC语言
1.IDA脚本的打开与使用: IDA脚本两种语言:IDC(IDC,本地脚本语言)和Python 2.IDC语言介绍 1.IDC变量:IDC是一种松散的语言,没有明确的类型.使用3中数据类型,整数(ID ...
- 还是把这道kmp的题po出来吧,省的以后自己也忘了
做了一个问题突然想到可以用Kmp解决,所以看了一下自己之前写的关于Kmp的博客用JAVA实现的KMP匹配子串,记录一下,省的又忘了. /* *题目描述: * 假定我们都知道非常高效的算法来检查一个单词 ...
- 项目Beta冲刺(团队)--4/7
课程名称:软件工程1916|W(福州大学) 作业要求:项目Beta冲刺 团队名称:葫芦娃队 作业目标:进行新一轮的项目冲刺,尽力完成并完善项目 团队博客 队员学号 队员昵称 博客地址 04160242 ...
- ReadIniTest_GetPrivateProfileString
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.R ...
- Async programming
Asynchrony, in computer programming, refers to the occurrence of events independent of the mainprogr ...
- 开源项目(5-1) yolo万能检测识别
(1)Windows10+YOLOV3+VisualStudio2017最新版本超详细过程 https://blog.csdn.net/KID_yuan/article/details/8838026 ...