1.写在前面
(1).InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
(2).SqlSession sqlSession = sqlSessionFactory.openSession();
(3).EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
今天分析的源码只有上面简简单单的三部分,我会把这四行代码的源码分析总结先写在下面,方便需要直接看结论的同学一眼能查找到,后续具体的源码分析我们在后面慢慢分析。
总结:
1.获取SqlSessionFactory对象:首先通过类加载器获取配置文件的输入流,然后通过Xpath解析我
们的配置文件,将所有的配置文件内容(mybatis全局配置以及Mapper映射文件)都解析封装到
configuration对象中,我们的每一条SQL语句会被封装为一个MappedStatement,然后以SQL的Id
key,放入我们的mappedStatements中,同时为我们每一个接口的执行信息都会封装成一个MapperProxyFactory对象,然后以接口的Class为key,
MapperProxyFactory为value封装入knownMappers中,最终我们通过调用DefaultSqlSessionFactory
的有参构造将我们的configuration注入其中并返回DefaultSqlSessionFactory
第一步总结:解析所有配置为configuration对象,注入到DefaultSqlSessionFactory中创建返回。
2.获取SqlSession对象:我们调用DefaulSqlSessionFactory的OpenSessionFromDataSource方法,
先获取一些环境事务的配置,然后根据我们的默认Excutor类型创建对应的Excutor对象,并根据二级缓存和
插件的配置对我们的Excutor进行包装,最终创建DefaultSqlSession对象进行返回
注意:该步尽管流程很简单,但是这一步会创建我们的Excutor对象并且和插件的执行有关!!!
第二步总结:调用DefaulSqlSessionFactory的OpenSessionFromDataSource方法,读取事务信息,
根据默认执行器配置、缓存情况、插件情况创建和包装执行器,最终创建DefaultSqlSession进行返回。
3.获取Mapper:实际上调用的是configuration中的MapperRegistry的getMapper方法,MapperRegistry
中封装了我们在第一步获取的knownMappers(key = Mapper.class,value=MapperProxyFactory),我们
通过MapperProxyFactory创建MapperProxy(InvocationHandler的实现类),最终通过Proxy.newInstance()
动态代理技术返回Mapper接口的代理对象。
注意:MapperProxy不是返回的代理对象!!!
MapperProxy不是返回的代理对象!!!
MapperProxy不是返回的代理对象!!!
返回的是通过Proxy.newInstance()创建的对象,MapperProxy只是InvocationHandler的实现类
包含代理方法的执行内容。
第三步总结:根据configuration中已经封装好的接口的class和MapperProxyFactory映射信息获取对应的
MapperProxyFactory,然后通过动态代理技术获取Mapper最终的代理对象。
整体总结:
我们通过Xpath解析所有的XML文件配置信息映射为configuration对象,创建出DefaultSqlSessionFactory,然后通过DefaultSqlSessionFactory调用openSessionFromDataSource方法,获取事务信息,并根据默认的执行器配置、缓存配置、插件配置创建包装好我们SqlSession的Excutor对象,然后将configuration、执行器以及事务信息封装在在DefaultSqlSession中,最终通过DefaultSqlSession中configuration对象中封装的Mapper接口的类型和MapperProxyFactory的映射关系,通过动态代理技术创建出我们Mapper接口的代理类。
单纯只看上面三步可能会发现用到的只是各种封装里面的部分信息,其实上述源码只是mybatis执行过程的一小步,后续还会分析mybatis增删改查的源码以及插件执行的源码,有兴趣的同学可以继续查看,在那里你的一些疑惑会得到解答。
(分析流程有点长,慎入!!!)
2.获取SqlSessionFactory对象
好的我们先来看第一行代码
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
我们点进源码跟进方法
调用类加载器包装类想获取流,继续跟进
方法的参数有一个getClassLoaders,其中内置了很多类加载器
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{classLoader, this.defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
this.getClass().getClassLoader(), this.systemClassLoader};
}
我们继续跟进方法
实际上第一行代码就是通过类加载器去获取流并将其返回,这个大家了解一下就行。重点在第二行。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
我们跟进build()方法,发现最终执行的是下面这个方法
这个方法中我们创建了一个XMLConfigBuilder对象,并且把对象命名为了parser解析器之类的意思,我们看一看这个XMLConfigBuilder到底是何方神圣。
我们跟进构造方法后很容易看出它封装了一个XPath解析器,然后我们回到刚才的方法,我们调用了parser.parse()方法,我们跟进去看看
我们看到先用了Xpath解析了我们mybatis配置文件中的根标签configuration返回为XNode对象(经过Xpath解析后可以进行处理的对象)调用parseConfiguration解析该XNode对象。
我们可以看到我们程序在一个一个解析我们在配置文件中所有进行的配置项,然后我们重点跟进解析我们mappers的方法,我把源码拷贝在下面
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator i$ = parent.getChildren().iterator();
while(true) {
while(i$.hasNext()) {
XNode child = (XNode)i$.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
(第一处分析点)
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
(第二处分析点)
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
(第三处分析点)
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
为了方便分析,我在源码处加了三处分析点的提示
第一处分析点:首先判断我们配置mapper映射文件的方式(这里以mapper)配置为例,进入了该if判断
第二处分析点:我们分别获取我们配置的resource,url,class内容,假设我们设置了resource将会进
入下面第一个if
第三处分析点:我们首先获取了mapper配置文件的输入流(原理和获取mybatis全局配置文件输入流一致)
然后又通过XPath解析我们的mapper映射文件,调用parse方法
我们跟进mapperParser.parse();的parse方法
我们看到这是不是感觉似曾相识呢,我们又开始解析mapper配置文件的根标签mapper了跟进方法
mybatis又在逐个解析我们的配置,我们直接执行完该方法,我们发现我们的this对象的configuration属性中多了一些东西
多了一个mappedStatements对象,这个对象是一个Map,key是我们查询语句的ID,value是什么呢,我们点开看看(mapperRegistry同理)
我们发现value中封装了我们写的SQL语句!!!
所以实际上我们在mapper中书写的的增删改查四个标签,会被封装为mappedStatement对象,而所有的mappedStatement对象会被放在mappedStatements这个map中,key是我们SQL语句的ID,value就是mappedStatement,而这个mappedStatements会被放入configuration中。
解析完mapper后我们所有的解析操作就结束了,我们回到下面的代码
我们调用build方法,发现我们实际创建的是DefaultSqlSession,并把我们我们整个解析得到的configuration对象封装进行进行返回。
3.获取SqlSession对象
接下来我们来看如何获取的DefaultSqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
因为我们前一步获取的sqlSessionFactory是DefaultSqlSessionFactory,所以我们直接进入到DefaultSqlSessionFactory的openSession方法
发现它调用的是本来的openSessionFromDataSource从数据源获取Session的方法,并且传入了一个默认的执行器类型(这个在mybatis配置文件的settings中进行配置,默认是simple类型)。我们继续跟进方法来到openSessionFromDataSource()。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
前面几行代码就是从configuration中读取环境信息创建事务。我们直接看到核心
Executor executor = this.configuration.newExecutor(tx, execType);
我们跟进该方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
(手工断点哈哈哈哈)
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
我们看到在手工断点前面我们根据默认的Executor 类型创建了对应的执行器,然后(重点来了)我们会看全局配置中有没有进行cacheEnabled=true的配置,如果配置了,那么会用CachingExecutor去包装当前的Executor (实际的包装操作只有在查询时会去查二级缓存而已,其他没有别的变化)。
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);核心!!!
我们会遍历所有的拦截器链去调用其plugin方法对我们的executor进行包装(这是mybatis能够使用插件的核心操作,也就是说插件的注入是在获取SqlSession时完成的)
经过上述操作,返回我们的执行器对象。
最终传入configuration、excutor、tx 创建DefaultSqlSession进行返回。
4.获取Mapper
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
最终的操作就是获取Mapper啦(温馨提示:如果有小伙伴看过尚硅谷的mybatis视频,尚硅谷视频这步的分析是有一定错误的,小伙伴可以结合本文进行分析纠错)
由于上一步我们获取的SqlSession对象是DefaultSqlSessio对象,所以我们直接来到DefaultSqlSession对象的getMapper()方法。
我们调用的是configuration中的getMapper方法,并且把接口类型传入了(注意接口类型传入了!!)我们继续跟进方法
我们调用的是MapperRegistry的getMapper()方法,这个MapperRegistry有没有感觉很眼熟!!!!
大家看回到第一步的图,这个mapperRegistry封装了我们的knownMappers,我们回忆一下knownMappers是什么(可以看文章开头的第一步流程:我们以接口的Class为key,MapperProxyFactory为value封装入knownMappers中,所以这个mapperRegistry实际上封装了和接口有关的MapperProxyFactory对象),我们继续跟进方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
我们在knownMappers获取出了我们接口相关的MapperProxyFactory对象,然后调用了return mapperProxyFactory.newInstance(sqlSession);获取mapper进行返回,我们进入newInstance方法
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
我们创建了一个mapperProxy,看下图我们发现mapperProxy是InvocationHandler的实现类,说明这块使用了动态代理技术帮助我们生成原来接口类型的代理对象
最终我们通过动态代理创建MapperProxy进行返回。