目录
- 一 解析配置文件入口
- 二 解析properties文件
- 三 解析settings文件
- 四 解析typeAliases文件
- 五 解析 Plugin文件
- 六 解析 Environments 文件
- 七 解析Mapper 文件
官网: mybatis – MyBatis 3 | 简介
参考书籍:《通用源码阅读指导书:MyBatis源码详解》 易哥
参考文章:
- Mybatis源码解析
上篇文件我们介绍了如何获取到Mybatis-conf.xml文件
的过程,现在我们来看看获取获取SqlSessionFactory
一 解析配置文件入口
// 第一阶段:MyBatis的初始化阶段
String resource = "mybatis-config.xml";
// 得到配置文件的输入流
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 得到SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- 我们看到代码中Build方法,就是程序的入口,我们来看一看吧
// 第一步调用这个方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 第二部调用重载方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 创建文件解析器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 调用解析器的parse方法
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
- **看到这个结构,我们应该感到熟悉,啊,这不就是建造者模式吗?没错,SqlSessionFactory中用了设计模式中的建造者模式 **
- 我们可以看到重载方法中调用了XMLConfigBuilder()方法,来解析文件,所以我们来瞅瞅
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 调用父类的方法
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
- 可以看到XMLConfigBuilder继承BaseBuilder,我们先来看看这个方法吧,这个方法有多个实现类,每一个子类多是以XMLXXXXXBuilder命名的,也就是其子类都对应解析一种xml文件或xml文件中一种元素。
BaseBuilder中只有三个成员变量,而typeAliasRegistry和typeHandlerRegistry都是直接从Configuration的成员变量获得的,接着我们看看Configuration这个类
Configuration类位于mybatis包的org.apache.ibatis.session目录下,其属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将XML配置中的内容解析赋值到Configuration对象中。
public abstract class BaseBuilder {
// 配置文件,对应其属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将XML配置中的内容解析赋值到Configuration对象中。
protected final Configuration configuration;
// 类型注册
protected final TypeAliasRegistry typeAliasRegistry;
// 类型处理器
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
}
- 由于XML配置项有很多,所以Configuration类的属性也很多。先来看下Configuration对于的XML配置文件示例:
<?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>
<!-- 属性配置,读取properties中的配置文件 -->
<properties resource="db.propertis">
<property name="XXX" value="XXX"/>
</properties>
<!-- 类型别名 -->
<typeAliases>
<!-- 在用到User类型的时候,可以直接使用别名,不需要输入User类的全部路径 -->
<typeAlias type="com.luck.codehelp.entity.User" alias="user"/>
</typeAliases>
<!-- 类型处理器 -->
<typeHandlers>
<!-- 类型处理器的作用是完成JDBC类型和java类型的转换,mybatis默认已经由了很多类型处理器,正常无需自定义-->
</typeHandlers>
<!-- 对象工厂 -->
<!-- mybatis创建结果对象的新实例时,会通过对象工厂来完成,mybatis有默认的对象工厂,正常无需配置 -->
<objectFactory type=""></objectFactory>
<!-- 插件 -->
<plugins>
<!-- 可以自定义拦截器通过plugin标签加入 -->
<plugin interceptor="com.lucky.interceptor.MyPlugin"></plugin>
</plugins>
<!-- 全局配置参数 -->
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" /><!-- 是否自动生成主键 -->
<setting name="defaultExecutorType" value="REUSE" />
<setting name="lazyLoadingEnabled" value="true"/><!-- 延迟加载标识 -->
<setting name="aggressiveLazyLoading" value="true"/><!--有延迟加载属性的对象是否延迟加载 -->
<setting name="multipleResultSetsEnabled" value="true"/><!-- 是否允许单个语句返回多个结果集 -->
<setting name="useColumnLabel" value="true"/><!-- 使用列标签而不是列名 -->
<setting name="autoMappingBehavior" value="PARTIAL"/><!-- 指定mybatis如何自动映射列到字段属性;NONE:自动映射;PARTIAL:只会映射结果没有嵌套的结果;FULL:可以映射任何复杂的结果 -->
<setting name="defaultExecutorType" value="SIMPLE"/><!-- 默认执行器类型 -->
<setting name="defaultFetchSize" value=""/>
<setting name="defaultStatementTimeout" value="5"/><!-- 驱动等待数据库相应的超时时间 ,单位是秒-->
<setting name="safeRowBoundsEnabled" value="false"/><!-- 是否允许使用嵌套语句RowBounds -->
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/><!-- 下划线列名是否自动映射到驼峰属性:如user_id映射到userId -->
<setting name="localCacheScope" value="SESSION"/><!-- 本地缓存(session是会话级别) -->
<setting name="jdbcTypeForNull" value="OTHER"/><!-- 数据为空值时,没有特定的JDBC类型的参数的JDBC类型 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- 指定触发延迟加载的对象的方法 -->
<setting name="callSettersOnNulls" value="false"/><!--如果setter方法或map的put方法,如果检索到的值为null时,数据是否有用 -->
<setting name="logPrefix" value="XXXX"/><!-- mybatis日志文件前缀字符串 -->
<setting name="logImpl" value="SLF4J"/><!-- mybatis日志的实现类 -->
<setting name="proxyFactory" value="CGLIB"/><!-- mybatis代理工具 -->
</settings>
<!-- 环境配置集合 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 事务管理器 -->
<dataSource type="POOLED"><!-- 数据库连接池 -->
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- mapper文件映射配置 -->
<mappers>
<mapper resource="com/luck/codehelp/mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 我们来看看Configuration这个类的成员变量
// <environment>节点的信息
protected Environment environment;
// 以下为<settings>节点中的配置信息
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
// 以上为<settings>节点中的配置信息
// <properties>节点信息
protected Properties variables = new Properties();
// 反射工厂
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// 对象工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
// 对象包装工厂
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 是否启用懒加载,该配置来自<settings>节点
protected boolean lazyLoadingEnabled = false;
// 代理工厂
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
// 数据库编号
protected String databaseId;
// 配置工厂,用来创建用于加载反序列化的未读属性的配置。
protected Class<?> configurationFactory;
// 映射注册表
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
// 拦截器链(用来支持插件的插入)
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 类型处理器注册表,内置许多,可以通过<typeHandlers>节点补充
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 类型别名注册表,内置许多,可以通过<typeAliases>节点补充
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
// 语言驱动注册表
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// 映射的数据库操作语句
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
// 缓存
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
// 结果映射,即所有的<resultMap>节点
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
// 参数映射,即所有的<parameterMap>节点
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
// 主键生成器映射
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
// 载入的资源,例如映射文件资源
protected final Set<String> loadedResources = new HashSet<>();
// SQL语句片段,即所有的<sql>节点
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
// 暂存未处理完成的一些节点
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
// 用来存储跨namespace的缓存共享设置
protected final Map<String, String> cacheRefMap = new HashMap<>();
- 我们可以通过代码看到代码调用了的Configuration无参构造代码, typeAliasRegistry 注册了很多别名,我们看看TypeAliasRegistry
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
- 我们来看看别名注册表,我们可以发现其中已经有注册的文件,好了完成了这些,我们就可以得到一个 Configuration 对象,并且把我们Mybatis-conf.xml文件解析到其中
// 存储中心其实TypeAliasRegistry里面有一个HashMap,并且在TypeAliasRegistry的构造器中注册很多别名到这个hashMap中。
private final Map<String, Class<?>> typeAliases = new HashMap<>();
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);
}
- 开始调用parser.parse()方法,进行文件的解析
public Configuration parse() {
// 是否加载过
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析配置文件 configuration为开始节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
- 注意一个 xpath 表达式 - /configuration。这个表达式代表的是 MyBatis 的标签,这里选中这个标签,并传递给parseConfiguration方法。
private void parseConfiguration(XNode root) {
try {
// 解析 properties 配置
propertiesElement(root.evalNode("properties"));
// 解析 settings 配置,并将其转换为 Properties 对象
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载 vfs
loadCustomVfs(settings);
// 解析 typeAliases 配置
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 plugins 配置
pluginElement(root.evalNode("plugins"));
// 解析 objectFactory 配置
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 objectWrapperFactory 配置
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 reflectorFactory 配置
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 中的信息设置到 Configuration 对象中
settingsElement(settings);
// 解析 environments 配置
environmentsElement(root.evalNode("environments"));
// 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
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 resource="db.properties" >
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
// 解析properties文件
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 解析成Properties文件
Properties defaults = context.getChildrenAsProperties();
// 解析resource
String resource = context.getStringAttribute("resource");
// 解析url
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.");
}
// // 从文件系统中加载并解析属性文件
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 通过 url 加载并解析属性文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 将属性值设置到 configuration 中
configuration.setVariables(defaults);
}
}
- root.evalNode()方法,解析指定标签文件
/**
* 进行XML节点的解析
* @param expression 解析的语句
* @param root 解析根
* @param returnType 返回值类型
* @return 解析结果
*/
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 对指定节点root运行解析语法expression,获得returnType类型的解析结果
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
- 解析 properties 节点的子节点,并将解析结果设置到 Properties 对象中。
- 从文件系统或通过网络读取属性配置,这取决于 properties 节点的 resource 和 url 是否为空。
- 将解析出的属性对象设置到 XPathParser 和 Configuration 对象中。
需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。
三 解析settings文件
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
</settings>
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
- 我们可以看到调用settingsAsProperties的方法,看他是如何解析settings文件的
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 解析settings文件
Properties props = context.getChildrenAsProperties();
// 创建 Configuration 类的“元信息”对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
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).");
}
}
return props;
}
- **getChildrenAsProperties()方法我们在上面也见到过,这里就不多讲,我们来看看settingsElement(settings),为configuration配置我们的设置 **
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")));
}
四 解析typeAliases文件
在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。
- 第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名
<typeAliases>
<package name="com.mybatis.model"/>
</typeAliases>
- 第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:
<typeAliases>
<typeAlias alias="employe" type="com.mybatis.model.Employe" />
<typeAlias type="com.mybatis.model.User" />
</typeAliases>
- 下面我们来看一下两种不同的别名配置是怎样解析的
typeAliasesElement(root.evalNode("typeAliases"));
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 从指定的包中解析别名和类型的映射
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
// configuration文件中注册他
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
}
// 从 typeAlias 节点中解析别名和类型的映射
else {
// 获取 alias 和 type 属性值,alias 不是必填项,可为空
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
// 加载 type 对应的类型
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);
}
}
}
}
}
- ** 通过手动的方式注册别名到类型的映射**
public void registerAlias(Class<?> type) {
// 别名
String alias = type.getSimpleName();
// 从注解中取出别名
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// 重载方法
registerAlias(alias, type);
}
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// 将别名转成小写
String key = alias.toLowerCase(Locale.ENGLISH);
/*
* 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
* 不一致则抛出异常,不允许一个别名对应两种类型
*/
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
// 缓存到typeAliases
typeAliases.put(key, value);
}
- 从指定的包中解析并注册别名
public void registerAliases(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
/*
* 查找包下的父类为 Object.class 的类。
* 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。
*/
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取查找结果
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for (Class<?> type : typeSet) {
// 忽略匿名类,接口,内部类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
// 为类型注册别名
registerAlias(type);
}
}
}
- 我们看看查找指定包下的所有类
private Set<Class<? extends T>> matches = new HashSet();
public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) {
//将包名转换成文件路径
String path = this.getPackagePath(packageName);
try {
//通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如com/mybatis/model/Employe.class
List<String> children = VFS.getInstance().list(path);
Iterator i$ = children.iterator();
while(i$.hasNext()) {
String child = (String)i$.next();
//以.class结尾的文件就加入到Set集合中
if (child.endsWith(".class")) {
this.addIfMatching(test, child);
}
}
} catch (IOException var7) {
log.error("Could not read package: " + packageName, var7);
}
return this;
}
protected String getPackagePath(String packageName) {
//将包名转换成文件路径
return packageName == null ? null : packageName.replace('.', '/');
}
protected void addIfMatching(ResolverUtil.Test test, String fqn) {
try {
//将路径名转成全限定的类名,通过类加载器加载类名,比如com.mybatis.model.Employe.class
String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.');
ClassLoader loader = this.getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
//加入到matches集合中
this.matches.add(type);
}
} catch (Throwable var6) {
log.warn("Could not examine class '" + fqn + "'" + " due to a " + var6.getClass().getName() + " with message: " + var6.getMessage());
}
}
主要有以下几步:
- 通过 VFS(虚拟文件系统)获取指定包下的所有文件的路径名,比如 com/mybatis/model/Employe.class
- 筛选以.class结尾的文件名
- 将路径名转成全限定的类名,通过类加载器加载类名
- 对类型进行匹配,若符合匹配规则,则将其放入内部集合中
五 解析 Plugin文件
插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。比喻分页插件,在SQL执行之前动态拼接语句,我们后面会单独来讲插件机制,先来了解插件的配置。如下:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
pluginElement(root.evalNode("plugins"));
/**
* 解析<plugins>节点
* @param parent <plugins>节点
* @throws Exception
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) { // <plugins>节点存在
for (XNode child : parent.getChildren()) { // 依次<plugins>节点下的取出每个<plugin>节点
// 读取拦截器类名
String interceptor = child.getStringAttribute("interceptor");
// 读取拦截器属性
Properties properties = child.getChildrenAsProperties();
// 实例化拦截器类
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 设置拦截器的属性
interceptorInstance.setProperties(properties);
// 将当前拦截器加入到拦截器链中
configuration.addInterceptor(interceptorInstance);
}
}
}
- 根据别名解析Class,然后创建实例
protected <T> Class<? extends T> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
}
- Intercepto类:这是插件接口,所有插件需要实现该接口
public interface Interceptor {
/**
* 该方法内是拦截器拦截到目标方法时的操作
* @param invocation 拦截到的目标方法的信息
* @return 经过拦截器处理后的返回结果
* @throws Throwable
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* 用返回值替代入参对象。
* 通常情况下,可以调用Plugin的warp方法来完成,因为warp方法能判断目标对象是否需要拦截,并根据判断结果返回相应的对象来替换目标对象
* @param target MyBatis传入的支持拦截的几个类(ParameterHandler、ResultSetHandler、StatementHandler、Executor)的实例
* @return 如果当前拦截器要拦截该实例,则返回该实例的代理;如果不需要拦截该实例,则直接返回该实例本身
*/
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 设置拦截器的属性
* @param properties 要给拦截器设置的属性
*/
default void setProperties(Properties properties) {
// NOP
}
}
- InterceptorChain:拦截链
ublic class InterceptorChain {
// 拦截器链
private final List<Interceptor> interceptors = new ArrayList<>();
// target是支持拦截的几个类的实例。该方法依次向所有拦截器插入这几个类的实例
// 如果某个插件真的需要发挥作用,则返回一个代理对象即可。如果不需要发挥作用,则返回原对象即可
/**
* 向所有的拦截器链提供目标对象,由拦截器链给出替换目标对象的对象
* @param target 目标对象,是MyBatis中支持拦截的几个类(ParameterHandler、ResultSetHandler、StatementHandler、Executor)的实例
* @return 用来替换目标对象的对象
*/
public Object pluginAll(Object target) {
// 依次交给每个拦截器完成目标对象的替换工作
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
/**
* 向拦截器链增加一个拦截器
* @param interceptor 要增加的拦截器
*/
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
/**
* 获取拦截器列表
* @return 拦截器列表
*/
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
/**
* 根据拦截器的配置来生成一个对象用来替换被代理对象
* @param target 被代理对象
* @param interceptor 拦截器
* @return 用来替换被代理对象的对象
*/
public static Object wrap(Object target, Interceptor interceptor) {
// 得到拦截器interceptor要拦截的类型与方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 被代理对象的类型
Class<?> type = target.getClass();
// 逐级寻找被代理对象类型的父类,将父类中需要被拦截的全部找出
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 只要父类中有一个需要拦截,说明被代理对象是需要拦截的
if (interfaces.length > 0) {
// 创建并返回一个代理对象,是Plugin类的实例
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
// 直接返回原有被代理对象,这意味着被代理对象的方法不需要被拦截
return target;
}
六 解析 Environments 文件
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
environmentsElement(root.evalNode("environments"));
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
// 获取Id属性
String id = child.getStringAttribute("id");
//检测当前 environment 节点的 id 与其父节点 environments 的属性 default 内容是否一致
if (isSpecifiedEnvironment(id)) {
// 将environment中的transactionManager标签转换为TransactionFactory对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 将environment中的dataSource标签转换为DataSourceFactory对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 数据源
DataSource dataSource = dsFactory.getDataSource();
// 创建 DataSource 对象
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// configuration设置
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
- transactionManagerElement()
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
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()
/**
* 解析配置信息,获取数据源工厂
* 被解析的配置信息示例如下:
* <dataSource type="POOLED">
* <property name="driver" value="{dataSource.driver}"/>
* <property name="url" value="{dataSource.url}"/>
* <property name="username" value="${dataSource.username}"/>
* <property name="password" value="${dataSource.password}"/>
* </dataSource>
*
* @param context 被解析的节点
* @return 数据源工厂
* @throws Exception
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 通过这里的类型判断数据源类型,例如POOLED、UNPOOLED、JNDI
String type = context.getStringAttribute("type");
// 获取dataSource节点下配置的property
Properties props = context.getChildrenAsProperties();
// 根据dataSource的type值获取相应的DataSourceFactory对象
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
// 设置DataSourceFactory对象的属性
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
七 解析Mapper 文件
/**
* 解析mappers节点,例如:
* <mappers>
* <mapper resource="com/github/yeecode/mybatisDemo/UserDao.xml"/>
* <package name="com.github.yeecode.mybatisDemo" />
* </mappers>
* @param parent mappers节点
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 处理mappers的子节点,即mapper节点或者package节点
if ("package".equals(child.getName())) { // package节点
// 取出包的路径
String mapperPackage = child.getStringAttribute("name");
// 全部加入Mappers中
configuration.addMappers(mapperPackage);
} else {
// resource、url、class这三个属性只有一个生效
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获取文件的输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用XMLMapperBuilder解析Mapper文件
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解析Mapper文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 配置的不是Mapper文件,而是Mapper接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
下一文章我会详细介绍一下Mapper的解析,其他不重要文件的解析,其实更上面有些解析类似,或者很简单就不一一解释了
当这些文件解析完毕,就返回一个Configuration对象,最终我们拿到了SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}