面试官问:为什么mybatis的mapper没有实现类?底层是咋实现的?

Java动态代理
代理模式在GoF设计模式尤为突出,Spring AOP 就是代理模式的一个例子,而且它使用的也是JDK的动态代理实现。MyBatis同样在Mapper接口执行时也是使用这个,当你第一次使用Mybatis的Mapper接口时肯定和我一样非常惊讶,为什么主要定义接口,不需要实现,就可以使用了呢?

说说JDK动态代理,主要是三点

A:target-interface // 定义接口

B:target-proxy implements InvocationHandler // 代理实现动态代理接口

C:Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),

new Class[]{target-interface.class},

new target-proxy()); // 生成代理对象

只要完成以上三步,生成的代理对象调用接口的任何方法都会去调用 invoke 方法。其中 invoke 方法就是代理对象实现 InvocationHandler 的抽象方法。

Mybatis DAO接口为什么不需要实现类的原理
Mybatis 提供了 Mapper接口的代理对象,在执行 Mapper接口方法时,实际执行的是Mybatis的代理对象,代理对象在 invoke 方法内获取 Mapper接口类全名+方法全名 作为statement的ID,然后通过ID去Statement匹配注册的SQL,然后使用 SqlSession 执行这个 SQL。

所以,这也解释了为什么Mybatis映射文件需要 namespace 和 id , 前者是类全名,后者是方法名。

主要的类

SqlSessionFactoryBean

SqlSessionFactory

XMLConfigBuilder

Configuration

MapperRegistry

MapperProxyFactory

MapperProxy

MapperMethod

SqlCommand

MethodSignature

从源码出发
1)Spring 配置文件配置 SqlSessionFactoryBean

2)SqlSessionFactoryBean 调用 buildSqlSessionFactory() 创建 SqlSessionFactory 时会调用

3)XMLConfigBuilder.parse() ,目的就是构建 Configuration

4)Configuration 主要存储 Mybatis 所有的配置信息,当然也会有Mapper代理对象

5)XMLConfigBuilder.parseConfiguration 里面就注册了 Configuration 的部分配置,含 Mapper

6)执行重点方法 XMLConfigBuilder 的 mapperElement

private void mapperElement(XNode parent) throws Exception {
-- 省略部分代码
if (resource == null && url == null && mapperClass != null) {
// mapperInterface 就是目标对象,Mybatis 将为他生成代理
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url,
resource or class, but not more than one.");
}
-- 省略部分代码
}
7)继续跟踪 configuration.addMapper(mapperInterface);调用的是 MapperRegistry 的方法

8)看看 MapperRegistry 的 addMapper 方法,很明显一个缓存式代码

public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
-- 省略部分代码
try {
// 终于看到代理的影子
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 这里主要是把我们写的 SQL 语句加载到 Configuration 中,因为执行SQL时需要
parser.parse();
}
-- 省略部分代码
}
9)MapperProxyFactory 就非常简单了,通过参数构建出代理对象,就如其名。

// 使用JDK动态代理对象 Proxy 创建代理对象 mapperProxy
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy);
}

到这里就结束了Mapper接口是如何加载的,那么下面就正式进入到 Mapper 接口是如何被Mybatis代理的,从上面可以看出重点就是 MapperProxy 类,没错,想要被 Proxy调用,这个类必须实现 InvocationHandler接口,并且实现 invoke 方法,这样他才能在执行接口方法的时候会执行 invoke。那么既然有了 MapperProxy 对象,只要在这个对象的invoke方法里调用执行 SQL 语句就达到目的了,因此也就不需要接口实现类了。如下继续走第10步

10)MapperProxy<T> implements InvocationHandler 同时实现 invoke 方法

@Override
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);
}
}
// 如果是接口,先缓存,后执行 SQL
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

11)MapperMethod 有两个重点,其一就是初始化,其二就是执行SQL,为什么说初始化很重要呢?因为初始化的时候需要通过 接口名全称+方法全称 去 Configuration 找我们之前加载的SQL,这也就是为什么接口方法定义和SQL的ID必须保持一致的原因;其二执行SQL。

12)MapperMethod 初始化时实例 command 和 method

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 找到方法执行类型以及SQL注册的ID
this.command = new SqlCommand(config, mapperInterface, method);
// 方法的返回值映射
this.method = new MethodSignature(config, mapperInterface, method);
}
13)MapperMethod 调用 execute 执行 SQL,代码很简单如下

// command 对象包含了执行类型和执行SQL的ID,都是初始化时构建好
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}

-- 省略部分代码

case SELECT:
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());
}

到这里就获取到运行结果 Result 了,可以直接返回,对于如何执行 SQL的,那就要跟踪 SqlSession 的实现类了,比如 DefaultSqlSession,里面会涉及到SQL参数的注入,结果集如何映射到返回实例,一级缓存和二级缓存以及分页处理等。

欢迎工作一到五年的Java工程师朋友们加入JavaQQ群:219571750,群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容