众所周知,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对象是通过XMLConfigBuilderparse()方法得到的,示例代码如下:

    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工具类的方法,将资源转化为Propertieskey 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();
}

总结

MyBatisConfiguration对象的加载就告一段落,这就意味着MyBatis准备就绪了。接下来就要解析一个重要的对象SqlSessionFactory了,因为它负责创建SqlSession,而SqlSession则负责执行各种SQL和方法。

【小盘子看源码-MyBatis-1】MyBatis配置文件的加载流程的更多相关文章

  1. angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...

  2. [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler

    [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler 目录 [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampl ...

  3. [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader

    [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 目录 [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 0x00 摘要 0x01 ...

  4. laravel框架源码分析(一)自动加载

    一.前言 使用php已有好几年,laravel的使用也是有好长时间,但是一直对于框架源码的理解不深,原因很多,归根到底还是php基础不扎实,所以源码看起来也比较吃力.最近有时间,所以开启第5.6遍的框 ...

  5. Spring源码分析(二十一)加载BeanFactory

    摘要: 本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.定制化BeanFactory 二.加载BeanDefinit ...

  6. easyui源码翻译1.32--EasyLoader(简单加载)

    前言 扩展自$.fn.datebox.defaults,使用$.fn.datetimebox.defaults重写默认值对象.下载该插件翻译源码 源码 /** * jQuery EasyUI 1.3. ...

  7. requireJS 源码(二) data-main 的加载实现

    (一)requireJs 的整体结构: requireJS 源码 前192行,是一些 变量的声明,工具函数的实现 以及 对 三个全局变量(requirejs,require,define)若被占用后的 ...

  8. 【 js 模块加载 】【源码学习】深入学习模块化加载(node.js 模块源码)

    文章提纲: 第一部分:介绍模块规范及之间区别 第二部分:以 node.js 实现模块化规范 源码,深入学习. 一.模块规范 说到模块化加载,就不得先说一说模块规范.模块规范是用来约束每个模块,让其必须 ...

  9. 6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?

    Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Senti ...

随机推荐

  1. Java学习之:Spring的扩展配置

    1.在配置文件applicationContext.xml中,引入相关的配置文件方式: 2.使用Jndi数据源的方式改造配置文件applicationContext.xml: 3.注释配置文件appl ...

  2. elasticsearch基础查询

    Es基础数据类型 string 字符串类型,es中最常用的类型,官方文档 比较重要的参数: index分析 analyzed(默认) not_analyzed no store存储 true 独立存储 ...

  3. linux-ifconfig 查看没有IP

    ifconfig 查看没有IP,如图: 解决方法: 1.切换路径到 2.进入编辑ifcfg-ens33文件(文件名可能不同)模式 3.ONBOOT改为yes 4.点击ESC,输入:wq进行保存 5.输 ...

  4. svn进行上传项目

    当svn的服务器搭建成功后,就可以进行上传项目了. 右键,选择客户端的repo-browser, 输入地址 然后就可以浏览所有项目: 然后在版本仓库上,右键,add folder, 添加对应的文件夹即 ...

  5. Linux基础:时间同步工具Chrony

    在Linux下,默认情况下,系统时间和硬件时间,并不会自动同步.在Linux运行过程中,系统时间和硬件时间以异步的方式运行,互不干扰.硬件时间的运行,是靠Bios电池来维持,而系统时间,是用CPU t ...

  6. Prometheus学习笔记(4)什么是pushgateway???

    目录 一.pushgateway介绍 二.pushgateway的安装运行和配置 三.自定义脚本发送pushgateway 四.使用pushgateway的优缺点 一.pushgateway介绍 pu ...

  7. 定时任务工具Linux crontab命令详解

    crontab:定时任务的守护进程,精确到分,设计秒的我们一般写脚本  -->相当于闹钟        日志文件:  ll /var/log/cron*        编辑文件: vim /et ...

  8. BLE——低功耗蓝牙(Bluetooth Low Energy)

    1.简介 以下蓝牙协议特指低功耗蓝牙协议. 蓝牙协议是由SIG制定并维护的通信协议,蓝牙协议栈是蓝牙协议的具体实现. 各厂商都根据蓝牙协议实现了自己的一套函数库——蓝牙协议栈,所以不同厂商的蓝牙协议栈 ...

  9. 28、Python网络编程

    一.基于TCP协议的socket套接字编程 1.套接字工作流程 先从服务器端说起.服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客 ...

  10. PHP memcache 环形队列

    <?php   /**  * PHP memcache 环形队列类  * 因业务需要只保留的队列中的Pop和Push,修改过期时间为0即永久  */ class MQueue {     pub ...