目录
- MyBatis详细的执行流程
- 1. 创建加载核心配置文件的inputStream流
- 2 创建一个sqlSessionFactory对象
- 2.1 创建sqlSessionFactory代码用例
- 2.2 build(inputStream)方法
- 2.3 build(InputStream inputStream, String environment, Properties properties)
- 2.4 XMLConfigBuilder构造方法。
- 2.5 new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())
- 2.6 返回2.4的XMLConfigBuilder构造方法
- 2.7 返回2.3新建完一个XMLConfigBuilder对象之后
- 2.8 parseConfiguration方法
- 2.9 build(Configuration config)
MyBatis详细的执行流程
1. 创建加载核心配置文件的inputStream流
1.1 Recourse.getResourceAsStream(String resource)方法
调用Recourse类下的getResourceAsStream(String resource)方法,传递过来的resource值为在resources目录下的mybatis-config.xml文件的文件名。
getResourceAsStream(String resource)的源码
public static InputStream getResourceAsStream(String resource) throws IOException {
return getResourceAsStream(null, resource);
}
解释:在这个getResourceAsStream(String resource)方法很简单,直接返回一个InputStream 对象,这个InputStream 对象由getResourceAsStream(null, resource)方法中获得。
1.2. getResourceAsStream(null, resource)的方法
让我们进入getResourceAsStream的重载方法看看吧。
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
解释:在这个方法一进来我们就看到了,新建了一个InputStream 对象 in。Okay!我们可以猜想这个 in 对象 可能就是要返回的给我们上一级的。让我们来看看它是怎么生成的!
在in 的生成中 调用了classLoaderWrapper.getResourceAsStream(resource, loader) 的方法。
老千层饼了,我们可以得知,in是调用了classLoaderWrapper对象的getResourceAsStream(resource, loader)方法生成的。
ps: classLoaderWrapper在Resources类中是这样创建的:
private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();它是一个私有静态的对象哦!![有点饿汉模式的味道(设计模式中单例模式的一种)]。
1.3. getResourceAsStream(resource, loader)方法
既然都走到这里了那么我们就去探探getResourceAsStream(resource, loader)方法的深浅吧!
ps:到这里,我们传进来的
resource是一个"mybatis-config.xml"这样的字符串,
loader是null。
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
return getResourceAsStream(resource, getClassLoaders(classLoader));
}
解释:现在已经从Resource类跳到了ClassLoaderWrapper类了哦! 在这个getResourceAsStream(String resource, ClassLoader classLoader)方法里,它调用了一个重载的方法getResourceAsStream(resource, getClassLoaders(classLoader),把我们传进来的classLoader也就是值为null的ClassLoader 对象,传给了getClassLoaders方法,所以我们要去这个getClassLoaders方法里看看嘿嘿,好好追根朔源
1.4. getClassLoaders(classLoader)方法
我们可以从这个getClassLoaders方法里看到,这个方法返回了一个ClassLoader[]的数组,数组里面保存这个5个ClassLoader类对象。知道了这个我们就返回看这里 getResourceAsStream(resource, getClassLoaders(classLoader))
,
ps: 这个数组里的第一个classLoader对象是我们传进去的空对象,在这此调用中数组不会都是null值。
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
// 使用当前线程的ClassLoader
// 使用当前类的ClassLoader
// 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader
};
}
1.5. 回到1.3的getResourceAsStream(resource, getClassLoaders(classLoader))
方法
在这个方法里的源码我们可以看到,当遍历对象不为null的时候,将返回一个不为空的InputStream流。
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
for (ClassLoader cl : classLoader) {
if (null != cl) {
// try to find the resource as passed
InputStream returnValue = cl.getResourceAsStream(resource);
// now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
if (null == returnValue) {
returnValue = cl.getResourceAsStream("/" + resource);
}
if (null != returnValue) {
return returnValue;
}
}
}
return null;
}
InputStream returnValue = cl.getResourceAsStream(resource),是将文件名传进去,如何去对应的加载器寻找资源,找得到就返回加载资源的输入流,找不到就返回一个空值。
1.6. 返回一个输入流
然后就层层返回,直到我们一开始的地方,这样我们的inputStream就新建好啦。
InputStream inputStream = Resources.getResourceAsStream(resource);
2 创建一个sqlSessionFactory对象
2.1 创建sqlSessionFactory代码用例
接下来进行的是,一个调用SqlSessionFactoryBuilder().build(inputStream)方法生成一个SqlSessionFactory的过程。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.2 build(inputStream)方法
新建了一个SqlSessionFactoryBuilder实例,调用的是系统隐式提供的无参构造方法。利用该对象并调用了带字节流的build方法。从其源码可以看出,又跳到了重载方法build(InputStream inputStream, String environment, Properties properties)
里去了。
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
2.3 build(InputStream inputStream, String environment, Properties properties)
从这个新的build方法一进来,我们就调用了XMLConfigBuilder类的构造方法新建了一个XMLConfigBuilder对象。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//inputStream为我们传进去的输入流,
//environment, properties都是空对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
注意:inputStream为我们传进去的输入流,environment, properties都是空对象
2.4 XMLConfigBuilder构造方法。
- 具体的XMLConfigBuilder构造方法。进来之后又调用了自己的另一个构造方法
private XMLConfigBuilder(XPathParser parser, String environment, Properties props)
.从源码中我们可以看到,我们新建了一个XPathParser对象,在这个对象里又新建了一个new XMLMapperEntityResolver() ,它是MyBatis DTD文件的脱机实体解析程序类,里面定义了一些dtd文件名。接下来我们要把一个加载核心配置文件的输入流、一个值为true的布尔值、一个为空对象的props,和一个装着一些属性是常量值为dtd文件名的类
拿去构造一个XPathParser对象,
ps:在这个构造方法中它把自己的一个parsed属性设置为false。下面会用到。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
解释:就是给之前新建的XMLConfigBuilder里面的属性赋值的过程。例如说:XMLConfigBuilder建新的XPathParser对象,再在XPathParser对象给他的document属性new一个新的document对象以及给XPathParser其他属性的赋值过程。
2.5 new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())
进来之后我们发现构造方法中有两个东西。
- 一个是名为
commonConstructor
的方法, - 另一个方法是
createDocument
,用于给XPathParser里的document属性赋值的方法。
源码
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
2.5.1 commonConstructor(validation, variables, entityResolver)
参数validation:true,variables:null,entityResolver是刚刚创建的MyBatis DTD文件的脱机实体解析程序类
commonConstructor源码
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
从上面的源码可以看出这个commonConstructor()存在的目的是给PathParse对象里的属性赋值。
让我们看看PathParse类里有什么对象。
public class XPathParser {
private final Document document;
private boolean validation;
private EntityResolver entityResolver;
private Properties variables;
private XPath xpath;
}
从中我们可以得知,我们还有一个document对象没有赋值,所以也就会再使用一个createDocument(new InputSource(inputStream))方法;
2.5.2 createDocument(new InputSource(inputStream));
ps:new InputSource(inputStream),只是新建了一个InputSource对象,并把这个对象里面的其中一个字节流属性赋值为inputStream。
InputSource对象的具体属性在下面。然后调用这个新建的InputSource对象
使用了createDocument方法。
我们可以从源码中看到,这个方法用于给XPathParser对象里的属性赋值,具体属性请看下方。
createDocument方法代码
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//用于创建一个DocumentBuilderFactory 对象。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//给factory对象设置它的属性值。
//指定由此工厂生成的解析器是否将在分析文档时验证文档。
factory.setValidating(validation);
//指定此工厂生成的解析器是否将提供对XML命名空间的支持。
factory.setNamespaceAware(false);
//指定此工厂生成的分析器是否将忽略注释。
factory.setIgnoringComments(true);
/*指定此工厂创建的解析器在解析XML文档时
是否必须消除元素内容中的空白(有时松散地称为“可忽略的空白”)
(请参见XML Rec 2.10)。
*/
factory.setIgnoringElementContentWhitespace(false);
//指定此工厂生成的解析器是否将CDATA节点转换为文本节点,
//并将其附加到相邻的(如果有)文本节点上。
factory.setCoalescing(false);
//指定此工厂生成的解析器是否将展开实体引用节点。
factory.setExpandEntityReferences(true);
//通过factory创建一个DocumentBuilder 对象
DocumentBuilder builder = factory.newDocumentBuilder();
//entityResolver 是之前传进来的2.5中的new值。
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 {
}
});
//调用DocumentBuilder类的对象builder里的parse方法
//作用:将给定输入源的内容解析为XML文档,
// 并返回一个新的DOM{@linkdocument}对象。
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
这样我们就成功给XPathParse对象里的属性都赋值上了,其中builder.parse(inputSource);这个方法还可以往下探,但是真的太多了,现在看不太明白,留着以后有空再补上。
InputSource对象里的属性
public class InputSource{
private String publicId;
private String systemId;
private InputStream byteStream;//把inputStream赋值给它
private String encoding;
private Reader characterStream;
}
2.6 返回2.4的XMLConfigBuilder构造方法
建完XPathParse对象之后返回2.4的XMLConfigBuilder构造方法
XMLConfigBuilder构造方法
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
我们从中可以得到,它有把建好的对象都传给了一个新的构造方法也就是下面这个。
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对象中有一个parsed属性值为false。
2.7 返回2.3新建完一个XMLConfigBuilder对象之后
- 2.3中的部分代码
//inputStream为我们传进去的输入流,
//environment, properties都是空对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
从中我们可以知道:
现在创建了一个新的XMLConfigBuilder对象后,接着我们调用这个对象的parse方法(),也就是这一段return build(parser.parse());
。这个parse()方法将返回一个Configuration对象。通过这个方法给Configuration对象里的属性赋值。然后返回这个Configuration对象。用于build()方法
。下面是parse()方法
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parsed默认值是false,在创建XMLConfigBuilder对象时设置的,在2.6中有提及到; 所以我们这里就会执行parseConfiguration(parser.evalNode("/configuration"));
2.8 parseConfiguration方法
parser.evalNode("/configuration")返回了一个根节点类,evalNode里又以之前赋值的document对象,和“/configuration”字符串作为参数最后返回一个XNode类
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
在这个类中会设置核心配置文件对照的类属性节点。
2.9 build(Configuration config)
到这个时候,我们就带着这个赋好值的Configuration对象到build方法里。,直接新建一个DefaultSqlSessionFactory对象传入config对象,赋值给DefaultSqlSessionFactory对象里的Configuration属性并且返回DefaultSqlSessionFactory对象。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
//new DefaultSqlSessionFactory(config);具体内容
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
}
到此处,我们就拿到了一个sqlSessionFactory对象。第二次修改,还是只整出了一个sqlSessionFactory对象。