对于单纯的Mybatis应用来说,读取配置文件,开启会话是整个应用启动的第一步
第一步:读取配置文件、创建SqlSessionFactory对象
// 使用mybatis提供的资源工具类,可以加载指定的资源文件并生成一个read
Reader resourceAsReader = Resources.getResourceAsReader("mybatis.xml");
// SqlSessionFactoryBuilder通过上面生成的read构建出一个sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
重点看一下sqlSessionFactory是如何构建出来的。跟进build 方法
public SqlSessionFactory build(Reader reader) {
// 调用自己的其他重载方法,完成sqlSessionFactory对象的创建
return build(reader, null, null);
}
再跟
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
/*
创建xml配置解析器,XMLConfigBuilder 和 XMLMapperBuilder都继承了BaseBuilder
在实例化XMLConfigBuilder的过程中完成了XMLConfigBuilder内部属性的实例化
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
其中parser 的实力 是: new XPathParser(reader, true, props, new XMLMapperEntityResolver())
具体分析可以进入到方法中去查看
*/
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 在这个方法中完成xml 文档的解析
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.
}
}
}
先来解析一个这个方法的参数,第一个就是上面传递过来的资源类,第二个就是环境设置,配置启用哪一个环境,如:dev 或者 prod等,关于第三个参数properties,我猜测是可以让我们传递一些我们自己的额外的配置的
根据代码顺序,先来看一下XMLConfigBuilder的创建过程,跟进方法 org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.Reader, java.lang.String, java.util.Properties)
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
// new XPathParser(reader, true, props, new XMLMapperEntityResolver()) 完成了配置文件到docment 的解析过程
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
跟着来看XPathParser 的初始化过程中做了什么事,跟进到org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.Reader, boolean, java.util.Properties, org.xml.sax.EntityResolver)
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
// 完成一些属性的实例化
commonConstructor(validation, variables, entityResolver);
// 解析最开始通过Resources生成的reader,并创建一个document
this.document = createDocument(new InputSource(reader));
}
其中commonConstructor
方法中完成一些属性的初始化,而在下面的createDocument
方法中,通过jdk的原生文档构建器构建出一个文档对象,以下是createDocument
方法的内容
/**
* 通过reader创建一个document
* 通过jdk 原生的文档构建工厂DocumentBuilderFactory 以传入的reader 重建 一个文件构建器,并将传递过来的输入流进行解析生成相应的文档对象
* @param inputSource
* @return
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
到这里XPathParser
的的创建过程就完了,在这个过程中完成了XPathParser
对象中的一些属性的初始化工作,并根据最开始传入的配置文件资源类,创建了一个文档对象并赋值给了XPathParser
的document
属性。回过头来继续看XMLConfigBuilder
的创建过程。
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
通过以上代码,可以看出调用了重载的构造方法org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties)
来看一下这个重载的方法中做了什么
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 这里初始化了当前类从父类继承的 configuration、typeAliasRegistry、typeHandlerRegistry 这三个属性
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
首先调用了父类的构造方法,并初始化了一个org.apache.ibatis.session.Configuration
类对象。
先来看一下Configuration
对象的创建过程,这里调用的是它的默认的构造方法,在这个方法里完成了一些默认的别名的注册,一下是具体代码
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
类的默认构造方法中,主要都是通过typeAliasRegistry
来完成别名的注册的。那么接下来,就先来看一下这个typeAliasRegistry
是个什么东西。以下是typeAliasRegistry
在 Configuration
类中初始化的代码
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
接下来看一下new TypeAliasRegistry();
这一段代码中发生了什么
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);
}
可以看到,在TypeAliasRegistry
初始化的过程中就已经注册了很多的别名,至于注册过程这里就不展开细说了,后面有时间的话再说,但是有一点可以先说一下,那就是这里最终注册的别名最终都会存在一个map中,其中key是别名,value是对应类的Class对象
除了TypeAliasRegistry
注册器,其实还有一个注册器TypeHandlerRegistry
也很重要,不过他们的初始化过程都差不多,这里就不重复说明了,后面都会都单独的文档进行说明的
好了,回过来再看父类的构造方法,跟进去org.apache.ibatis.builder.BaseBuilder#BaseBuilder
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
很简单,就是对一些属性进行了一次初始化。继续回过头来看XMLConfigBuilder
的创建
// 设置一些额外的配置
this.configuration.setVariables(props);
// 表示当前配置文件还没有被解析
this.parsed = false;
// 设置当前环境
this.environment = environment;
// 将上面创建的XPathParser对象赋值给parser 属性
this.parser = parser;
到这里 XMLConfigBuilder
的实例算是创建完成了,通过上面的分析过程可以知晓,在XMLConfigBuilder
初始化的过程中根据配置文件生成了一个document
对象,但是并没有对这个对象进行解析。所以,接下的任务就是对这个document
对象进行解析了
回到 org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)
方法中去,我们可以看到,在完成XMLConfigBuilder
的创建后,紧接着执行的就是build(parser.parse())
这个方法了,那么毫无疑问,对于document
的解析应该是在这个方法中完成的
先来分析parser.parse()
这个方法,由于parser
是XMLConfigBuilder
类的实例,那么parse()
方法肯定是属于XMLConfigBuilder
的,接下来就跟进到org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
方法中继续分析
/**
* 这里开始解析docment,只解析一次,解析过之后将parsed设置为ture, 下一次判断如果解析过了,直接抛出错误
* @return
*/
public Configuration parse() {
// 由该类的构造函数可以知道,这个时候parsed的值为false
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
/*
configuration是mybatis配置文件的根标签
parser.evalNode()方法的作用是返回节点的org.apache.ibatis.parsing.XNode表示,XNode里面主要把关键的节点属性和占位符变量结构化出来
*/
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
结合上面的注释进行分析,可以知道,在这个方法中真正做事的是下面这句代码,而这句代码也是正式解析mybatis配置文件的开始
parseConfiguration(parser.evalNode("/configuration"));
先来看parser.evalNode("/configuration")
,由上面的分析可以知道,parser
正是之前创建的XPathParser
。点到evalNode()方法中
/**
* 根据传入的表达式解析本类的document属性
* @param expression
* @return
*/
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
可以看到最终的结果是由xpath
根据传入表达式对 XPathParser
的自身属性document
进行解析,并返回一个Node
对象,然后由Xnode
进行包装后返回给最终的调用者。
上面所说的 xpath
以及 document
都是在 XPathParser
的实例化过程中被实例化的,其中xpath
是在XPathParser
的构造方法中调用的commonConstructor
方法中初始化的,而document
就是构造方法中初始化的
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
// 完成一些属性的实例化
commonConstructor(validation, variables, entityResolver);
// 解析最开始通过Resources生成的reader,并创建一个document
this.document = createDocument(new InputSource(reader));
}
现在知道了parser.evalNode("/configuration")
返回的事一个Xnode
对象,那么就先来看一下XNode
的数据结构,以下是XNode
的相关属性。这些都属性将在唯一的构造方法中完成初始化
private final Node node;
private final String name;
private final String body;
private final Properties attributes;
private final Properties variables;
private final XPathParser xpathParser
}
回过头来看parseConfiguration(parser.evalNode("/configuration"))
中的 parseConfiguration()
方法
/**
* 这里是真正解析xml每个标签的地方
* @param root
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
// 解析settings 标签,将解析到的配置存放到一个properties中
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 从setting 标签解析出来的结果中设置日志的实现
loadCustomLogImpl(settings);
// 解析typeAliases 标签,并将别名以及对应的类型注册到父类定义的org.apache.ibatis.builder.BaseBuilder.typeAliasRegistry
typeAliasesElement(root.evalNode("typeAliases"));
// 解析plugins 标签
pluginElement(root.evalNode("plugins"));
// 解析objectFactory 标签,加载对象工厂
// 什么是对象工厂?MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
// 默认的对象工厂DefaultObjectFactory做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
objectFactoryElement(root.evalNode("objectFactory"));
//解析对象包装器工厂标签,并创建对象包装器工厂objectWrapperFactoryElement
// 对象包装器工厂主要用来包装返回result对象,比如说可以用来设置某些敏感字段脱敏或者加密等。默认对象包装器工厂是DefaultObjectWrapperFactory,也就是不使用包装器工厂
// 提到了对象包装器工厂,就要先提一下对象包装器ObjectWrapper,它的默认实现是BeanWrapper
// 要实现自定义的对象包装器工厂,只要实现ObjectWrapperFactory中的两个接口hasWrapperFor和getWrapperFor
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
/*
因为加载配置文件中的各种插件类等等,为了提供更好的灵活性,mybatis支持用户自定义反射工厂,不过总体来说,用的不多,要实现反射工厂,
只要实现ReflectorFactory接口即可。默认的反射工厂是DefaultReflectorFactory。一般来说,使用默认的反射工厂就可以了。
*/
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 这里将上面解析过的setting标签的内容设置到了configuration中,若未中setting标签中解析到内容,则采用默认值
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 加载环境配置
environmentsElement(root.evalNode("environments"));
/*
加载数据库厂商标识
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/*
加载类型处理器
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
mybatis提供了两种方式注册类型处理器,package自动检索方式和显示定义方式。使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。、
为了简化使用,mybatis在初始化TypeHandlerRegistry期间,自动注册了大部分的常用的类型处理器比如字符串、数字、日期等。对于非标准的类型,用户可以自定义类型处理器来处理。
要实现一个自定义类型处理器,只要实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个实用类 org.apache.ibatis.type.BaseTypeHandler,
并将它映射到一个 JDBC 类型即可。
*/
typeHandlerElement(root.evalNode("typeHandlers"));
/*
加载mapper文件
*/
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
初步看一下,可以得出一个结论,在这个方法中将会对mybatis配置文件中的每一个标签进行解析。那么下一篇文档就来一步一步份分析这个解析过程