Mybatis3源码笔记(三)Configuration
1. XMLConfigBuilder
上一篇大致介绍了SqlSession的生成。在DefaultSqlSessionFactory
的构造函数中就提到了Configuration
这个对象。现在我们来看看Configuration
的生成流程。
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
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.
}
}
}
代码比较简单,就是根据XMLConfigBuilder
根据配置文件XML来parse生成的。其实不用看代码,我们脑海中应该也有一个大致的Configuration的构成细节,肯定是根据mybatis-config.xml
具体生成对应的组成属性。一般的mybatis-config.xml
文件如下:
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value="" />
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver" />
<property name="url" value="jdbc:hsqldb:mem:stringlist" />
<property name="username" value="sa" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/apache/ibatis/submitted/stringlist/Mapper.xml" />
</mappers>
</configuration>
但是我们都知道XML的解析都有DTD文件来约束和验证,那我们常用的mybatis-config.xml
是DTD肯定也是有的。在哪呢?秘密就在XMLConfigBuilder构造函数中的XMLMapperEntityResolver
。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public class XMLMapperEntityResolver implements EntityResolver {
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
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-3-config.dtd
和mybatis-3-mapper.dtd
,同时为了兼容旧版本的ibatis,还用到了ibatis-3-config.dtd
和ibatis-3-mapper.dtd
。
之前说过mybatis-3-mapper.dtd
,那mybatis-3-mapper.dtd
看名字,我们也能猜到是专门用来解析mapper的xml文件的,一般的样例如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.submitted.stringlist.Mapper">
<select id="getUsersAndGroups" resultMap="results">
select * from users where id = #{id}
</select>
<resultMap type="org.apache.ibatis.submitted.stringlist.User" id="results">
<id column="id" property="id"/>
<collection property="groups" ofType="string">
<result column="group_id"/>
</collection>
<collection property="roles" ofType="string">
<result column="rol_id"/>
</collection>
</resultMap>
<select id="getUsersAndGroupsMap" resultMap="mapResults">
select * from users where id = #{id}
</select>
<resultMap type="map" id="mapResults">
<id column="id" property="id" />
<collection property="groups" ofType="string" javaType="list">
<result column="group_id" />
</collection>
<collection property="roles" ofType="string" javaType="list">
<result column="rol_id"/>
</collection>
</resultMap>
</mapper>
具体的dtd文件就不贴出来了,免的有凑字数的嫌疑。继续看代码:
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
//最终把XML文件生成document对象用来后面的解析工作
this.document = createDocument(new InputSource(inputStream));
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
//本地异常日志记录(ThreadLocal)单例模式来记录每一次的执行过程,用来详细定位异常信息。(为了防止内存泄露,在finally里有reset的操作)
ErrorContext.instance().resource("SQL Mapper Configuration");
//记录入参props
this.configuration.setVariables(props);
this.parsed = false;
//记录入参environment
this.environment = environment;
this.parser = parser;
}
public Configuration parse() {
//相同的XMLConfigBuilder对象只允许parse一次
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//开始解析configuration根节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
下面就到了重头戏Configuration的详细解析过程。其实前几项的解析相对来说比较简单,就是最后的mapper的解析比较复杂。
private void parseConfiguration(XNode root) {
try {
//解析properties节点(variables)
propertiesElement(root.evalNode("properties"));
//解析settings节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
//根据settings内容解析VFS(虚拟文件系统vfsImpl)
loadCustomVfs(settings);
//根据settings内容解析log日志具体实现类(logImpl)
loadCustomLogImpl(settings);
//解析typeAliases节点(TypeAliasRegistry里注册对应的别名)
typeAliasesElement(root.evalNode("typeAliases"));
//解析plugins节点(注册interceptorChain里记录对应的拦截器)
pluginElement(root.evalNode("plugins"));
//解析objectFactory节点(自定义objectFactory)
objectFactoryElement(root.evalNode("objectFactory"));
//解析objectWrapperFactory节点(自定义objectWrapperFactory)
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析reflectorFactory节点(自定义reflectorFactory)
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//设置其它的setting参数
settingsElement(settings);
//解析environments节点(environment)解析放到objectFactory and objectWrapperFactory解析之后,具体原因参见issue117
environmentsElement(root.evalNode("environments"));
//解析databaseIdProvider节点(databaseId)
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析typeHandlers节点,注册类型转换器(typeHandlerRegistry)
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mappers节点(重中之重)
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
下面逐一解析下各个节点
1. propertiesElement(root.evalNode("properties"))
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//解析所有子节点property
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
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.");
}
//根据url或者resource生成对应的property集合
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
//入参中的variables如果也存在的话,一并放入defaults
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//设置variables
configuration.setVariables(defaults);
}
}
<properties resource="org/apache/ibatis/builder/jdbc.properties">
<property name="prop1" value="aaaa"/>
<property name="jdbcTypeForNull" value="NULL" />
</properties>
<properties url="file:./src/test/java/org/apache/ibatis/builder/jdbc.properties">
<property name="prop1" value="bbbb"/>
</properties>
2. Properties settings = settingsAsProperties(root.evalNode("settings"))
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// 利用MetaClass来检查Configuration
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
//检查下对应的setting的key值在configuration里存不存在
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;
}
<settings>
<setting name="autoMappingBehavior" value="NONE"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="cacheEnabled" value="false"/>
<setting name="proxyFactory" value="CGLIB"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="false"/>
<setting name="useColumnLabel" value="false"/>
<setting name="useGeneratedKeys" value="true"/>
<setting name="defaultExecutorType" value="BATCH"/>
<setting name="defaultStatementTimeout" value="10"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="defaultResultSetType" value="SCROLL_INSENSITIVE"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="safeRowBoundsEnabled" value="true"/>
<setting name="localCacheScope" value="STATEMENT"/>
<setting name="jdbcTypeForNull" value="${jdbcTypeForNull}"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString,xxx"/>
<setting name="safeResultHandlerEnabled" value="false"/>
<setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.defaults.RawLanguageDriver"/>
<setting name="callSettersOnNulls" value="true"/>
<setting name="logPrefix" value="mybatis_"/>
<setting name="logImpl" value="SLF4J"/>
<setting name="vfsImpl" value="org.apache.ibatis.io.JBoss6VFS"/>
<setting name="configurationFactory" value="java.lang.String"/>
<setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
<setting name="shrinkWhitespacesInSql" value="true"/>
<setting name="defaultSqlProviderType" value="org.apache.ibatis.builder.XmlConfigBuilderTest$MySqlProvider"/>
</settings>
3. loadCustomVfs(settings)
根据setting解析Vfs(代码比较简单,就不注释了)
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
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);
}
}
}
}
4. loadCustomLogImpl(settings)
根据setting解析log实现
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
这个resolveClass
方法会经常用到,我们跟一下看看。
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
//根据typeAliases先去捞一波class,如果没有的话用Resources走classpath生成class
if (typeAliases.containsKey(key)) {
value = (Class<T>) typeAliases.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
public class TypeAliasRegistry {
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);
...
5. typeAliasesElement(root.evalNode("typeAliases"))
解析typeAliases节点(TypeAliasRegistry里注册对应的别名),正好跟上前的resolveClass联动起来。
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//注册package包下面所有的Class,key:getSimpleName(),value:Class(不包括接口,内部类,匿名类)
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//普通加载
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
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);
}
}
}
}
}
<typeAliases>
<typeAlias alias="BlogAuthor" type="org.apache.ibatis.domain.blog.Author"/>
<typeAlias type="org.apache.ibatis.domain.blog.Blog"/>
<typeAlias type="org.apache.ibatis.domain.blog.Post"/>
<package name="org.apache.ibatis.domain.jpetstore"/>
</typeAliases>
Mybatis3源码笔记(三)Configuration的更多相关文章
- Tomcat8源码笔记(三)Catalina加载过程
之前介绍过 Catalina加载过程是Bootstrap的load调用的 Tomcat8源码笔记(二)Bootstrap启动 按照Catalina的load过程,大致如下: 接下来一步步分析加载过程 ...
- Mybatis3源码笔记(六)SqlSession执行过程
前几篇大致分析了初始化的过程,今天打算走一个SqlSession具体执行过程. @Test void shouldSelectAllAuthors() { try (SqlSession sessio ...
- Mybatis3源码笔记(八)小窥MyBatis-plus
前言 Mybatis-Plus是一个 MyBatis增强工具包,简化 CRUD 操作,在 MyBatis 的基础上只做增强不做改变,为简化开发.提高效率而生,号称无侵入,现在开发中比较常用,包括我自己 ...
- Mybatis3源码笔记(一)环境搭建
1. 源码下载 地址:https://github.com/mybatis/mybatis-3.git. 国内访问有时确实有点慢,像我就直接先fork.然后从git上同步到国内的gitte上,然后在i ...
- Mybatis3源码笔记(四)Configuration(续)
1.pluginElement(root.evalNode("plugins")) 解析plugins节点(注册interceptorChain里记录对应的拦截器) private ...
- Mybatis3源码笔记(七)Plugin
1.Mybatis3的插件其实主要是用到了责任链和动态代理两种模式相结合而生成的.下面我们看一个例子,在执行所有update操作时,执行一个小小的测试输出. @Intercepts({@Signatu ...
- Mybatis3源码笔记(五)mapperElement
1.四种解析mapper方式 : package,resource,url,class. <mappers> <mapper resource="org/apache/ib ...
- Mybatis3源码笔记(二)SqlSession
1. 核心层次 2. SqlSession 先从顶层的SqlSession接口开始说起.SqlSession是MyBatis提供的面向用户的API,表示和数据库的会话对象,用于完成对数据库的一系列CR ...
- jQuery源码笔记——三
将类数组对象转化为数组对象 javascript中有许多类数组对象,比如HTMLCollection,NodeList,arguments.她们的特点是和数组一样有length属性,并且有0,1,2这 ...
随机推荐
- KnowRbao_uni-app
uni-app开发项目模板 主要的代码如下: pages.json 这里是添加页面的路径代码还可以设置标题: { "pages" : [ //pages数组中第一项表示应用启动页, ...
- Django Static与Media
关于Django中Static和Media的设置问题(尤其是css和js静态文件加载的问题),网上有很多回答,但是发现有相当一部分回答并不能解决问题.有的可能是Django版本问题,有的是把media ...
- Python3.x 基础练习题100例(01-10)
练习01: 题目: 有四个数字:1.2.3.4,能组成多少个互不相同且无重复数字的三位数?各是多少? 分析: 可填在百位.十位.个位的数字都是1.2.3.4.组成所有的排列后再去 掉不满足条件的排列. ...
- dubbo-zookeeper quick start
目录 dubbo快速开始 服务提供者(Service provider) 定义服务接口(Defining service interfaces) 在服务提供方实现接口(Implement interf ...
- 记录core中GRPC长连接导致负载均衡不均衡问题 二,解决长连接问题
题外话: 1.这几天收到蔚来的面试邀请,但是自己没做准备,并且远程面试,还在上班时间,再加上老东家对我还不错.没想着换工作,导致在自己工位上做算法题不想被人看见,然后非常紧张.估计over了.不过没事 ...
- 使用jsoup十分钟内掌握爬虫技术
对,就是十分钟,没有接触过爬虫的你,肯定一脸懵逼,感觉好高深的样子,一开始我也有点懵,但用了以后发现还是很简单的,java爬虫框架有很多,让我有种选择困难症,通过权衡比较还是感觉jsoup比较好用些, ...
- FreeBSD 宣布 2020 年第 4 季度状态报告
FreeBSD 宣布 2020 年第 4 季度状态报告● 继续努力从 FreeBSD 基本系统中移除 GPL 协议的软件,以实现 FreeBSD 项目基本目标.● Linux 二进制兼容层的 Linu ...
- python爬取考研专业信息
伴随着2021考研成绩的公布,2021考研国家线也即将到来.大家是否有过考研的想法了?如果想考研我们就需要了解很多的信息,但是百度的上有太多信息需要我们去一一的鉴别,是比较浪费时间的.所以我们可以学习 ...
- BZOJ_4034 [HAOI2015]树上操作 【树链剖分dfs序+线段树】
一 题目 [HAOI2015]树上操作 二 分析 树链剖分的题,这里主要用到了$dfs$序,这题比较简单的就是不用求$lca$. 1.和树链剖分一样,先用邻接链表建双向图. 2.跑两遍$dfs$,其实 ...
- 在Windows10搭建WebAssembly开发环境
最近研究WebAssembly技术,准备用WebAssembly编译C/C++代码供前端调用.网上看了很多文章,收获很大,现在就遇到的问题做一个记录. 官网关于windows开发环境搭建基本上几句话, ...