MyBatis 源码分析——动态代理
MyBatis框架是如何去执行SQL语句?相信不只是你们,笔者也想要知道是如何进行的。相信有上一章的引导大家都知道SqlSession接口的作用。当然默认情况下还是使用DefaultSqlSession类。关于SqlSession接口的用法有很多种。笔者还是比较喜欢用getMapper方法。对于getMapper方法的实现方式。笔者不能下一个定论。笔者只是想表示一下自己的理解而以——动态代理。
笔者把关于getMapper方法的实现方式理解为动态代理。事实上笔者还想说他可以是一个AOP思想的实现。那么具体是一个什么样子东西。相信笔者说了也不能代表什么。一切还是有大家自己去查看和理解。从源码上我们可以看到getMapper方法会去调用Configuration类的getMapper方法。好了。一切的开始都在这里了。
DefaultSqlSession类:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
对于Configuration类上一章里面就说明他里面存放了所有关于XML文件的配置信息。从参数上我们可以看到他要我们传入一个Class<T>类型。这已经可以看到后面一定要用到反射机制和动态生成相应的类实例。让我们进一步查看一下源码。
Configuration类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
当笔者点击进来发现他又调用MapperRegistry类的getMapper方法的时候,心里面有一种又恨又爱的冲动——这就是构架之美和复杂之恨。MapperRegistry类笔者把他理解存放动态代理工厂(MapperProxyFactory类)的库存。当然我们还是进去看一看源码吧。
MapperRegistry类:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
好了。笔者相信大家看到这一段代码的时候都明白——MapperRegistry类就是用来存放MapperProxyFactory类的。我们还是在看一下knownMappers成员是一个什么要样子的集合类型。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
knownMappers是一个字典类型。从Key的类型上我们可以判断出来是一个类一个动态代理工厂。笔者看到这里的时候都会去点击一个MapperProxyFactory类的源码。看看他里面又是一些什么东东。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
还好。代码不是很多,理解起来也不是很复杂。略看一下源码,笔者做了一个很大胆的猜测——一个类,一个动态代理工厂,多个方法代理。我们先把猜测放在这里,然后让我们回到上面部分吧。我们发现MapperRegistry类的getMapper方法里面最后会去调用MapperProxyFactory类的newInstance方法。这个时候我们又看到他实例化了一个MapperProxy类。MapperProxy类是什么。这个就关系到Proxy类的用法了。所以读者们自己去查看相关资料了。意思明显每执行一次XxxMapper(例如:笔者例子里面的IProductMapper接口)的方法都会创建一个MapperProxy类。方法执行之前都会先去调用相应MapperProxy类里面的invoke方法。如下
MapperProxy类:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
从源码的意思:从缓存中获得执行方法对应的MapperMethod类实例。如果MapperMethod类实例不存在的情况,创建加入缓存并返回相关的实例。最后调用MapperMethod类的execute方法。
到这里笔者小结一下,上面讲到笔者例子里面用到的getMapper方法。getMapper方法就是用来获得相关的数据操作类接口。而事实数据操作类邦定了动态代理。所以操据操作类执行方法的时候,会触动每个方法相应的MapperProxy类的invoke方法。所以invoke方法返回的结果就是操据操作类执行方法的结果。这样子我们就知道最后的任务交给了MapperMethod类实例。
MapperMethod类里面有俩个成员:SqlCommand类和MethodSignature类。从名字上我们大概的能想到一个可能跟SQL语句有关系,一个可能跟要执行的方法有关系。事实也是如此。笔者查看了SqlCommand类的源码。确切来讲这一部分的内容跟XxxMapper的XML配置文件里面的select节点、delete节点等有关。我们都会知道节点上有id属性值。那么MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。要找到MappedStatement类就必须通过id来获得。有一个细节要注意:代码用到的id = 当前接口类 + XML文件的节点的ID属性。如下
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
看到这里的时候,我们就可以回头去找一找在什么时候增加了MappedStatement类。上面之所以可以执行是建立在XML配置信息都被加载进来了。所以MappedStatement类也一定是在加载配置的时候就进行的。请读者们自行查看一下MapperBuilderAssistant类的addMappedStatement方法——加深理解。SqlCommand类的name成员和type成员我们还是关注一下。name成员就是节点的ID,type成员表示查寻还是更新或是删除。至于MethodSignature类呢。他用于说明方法的一些信息,主要有返回信息。
笔者上面讲了这多一点主要是为了查看execute方法源码容易一点。因为execute方法都要用到SqlCommand类和MethodSignature类。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
重点部分就是这里,我们会发现我们转了一圈,最后还是要回到SqlSession接口实例上。完美的一圈!笔者用红色标出来了。
看到了这里我们就清楚调头去看一下SqlSession接口实例吧。
MyBatis 源码分析——动态代理的更多相关文章
- MyBatis 源码分析——动态SQL语句
有几年开发经验的程序员应该都有暗骂过原生的SQL语句吧.因为他们不能一句就搞定一个业务,往往还要通过代码来拼接相关的SQL语句.相信大家会理解SQL里面的永真(1=1),永假(1=2)的意义吧.所以m ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析(2)—— Plugin原理
@(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...
- MyBatis 源码分析 - 插件机制
1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...
- MyBatis 源码分析 - 映射文件解析过程
1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...
- MyBatis 源码分析 - 配置文件解析过程
* 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...
- MyBatis 源码分析系列文章导读
1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...
- Mybatis源码分析
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- MyBatis源码分析(各组件关系+底层原理
MyBatis源码分析MyBatis流程图 下面将结合代码具体分析. MyBatis具体代码分析 SqlSessionFactoryBuilder根据XML文件流,或者Configuration类实例 ...
随机推荐
- Java——类谜题
1.令人混淆的构造器 代码如下格式: public class Confusing { private Confusing(Object o) { System.out.println("O ...
- PHPCMS v9 实现首页,列表页,内容页调用点击量方法
大家好,今天有点闲,看很多朋友经常问PHPCMS v9 首页,列表页,内容页调用点击怎么弄,打算抽时间把代码全部归纳出来,以便大家日后使用,如下: 1,首页调用点击量 {pc:content acti ...
- Appium的安装和使用
<!DOCTYPE html><html><head><title>Appium的安装和使用</title><meta http-eq ...
- js arguments.callee & caller的用法及区别
在函数内部,arguments.callee该属性是一个指针,指向拥有这个arguments对象的函数; 而函数对象的另一个属性:caller,这个属性保存着调用当前函数的函数的引用,如果是在全局作用 ...
- 301、302、200、206、304、404等HTTP状态引见(转载)
该文章来自网上转载,感谢他的辛勤付出! 如果向您的服务器发出了某项请求要求显示您网站上的某个网页,那么,您的服务器会返回 HTTP 状态代码以响应该请求. 一些常见的状态代码为: 200 - 服务器成 ...
- Delphi ADOQuery连接数据库的查询、插入、删除、修改
http://blog.csdn.net/chinazhd/article/details/45047777 //查询记录 procedure TForm1.Button1Click(Sender: ...
- STM32_IAP详解(有代码,有上位机)
Iap,全名为in applacation programming,即在应用编程,与之相对应的叫做isp,in system programming,在系统编程,两者的不同是isp需要依靠烧写器在单片 ...
- [转]配置 VIM 的 Go 语言开发环境
本文是针对像我这样的 VIM 小白而写的,所使用的 VIM-GO 插件虽然步骤简单但不够详细,特写此文以做记录和分享.欢迎各位大神纠正补充! 特别说明 本博文不是 Go 语言环境搭建教程,只是 VIM ...
- IOS之富文本编辑 分类: ios技术 2015-03-06 22:51 89人阅读 评论(0) 收藏
之前做项目时遇到一个问题: 使用UITextView显示一段电影的简介,由于字数比较多,所以字体设置的很小,行间距和段间距也很小,一大段文字挤在一起看起来很别扭,想要把行间距调大,结 ...
- [cocos2d-x] --- CCNode类详解
Email : awodefeng@163.com 1 CCNode是cocos2d-x中一个很重要的类,CCNode是场景.层.菜单.精灵等的父类.而我们在使用cocos2d-x时,接触最多的就是场 ...