Mybatis的mapper接口实现原理

news2024/11/15 9:37:01

目录

  • 1 概述
  • 2 动态代理和反射对象
  • 3 源码分析
  • 4 总结


1 概述

在这里插入图片描述

为啥mybatis的mapper只有接口没有实现类,但它却能工作?

说起mybatis,大伙应该都用过,有些人甚至底层源码都看过了。在mybatis中,mapper接口是没有实现类的,取而代之的是一个xml文件。也就是说我们调用mapper接口,其实是使用了mapper.xml中定义sql完成数据操作。

大家有没想过,为什么mapper没有实现类,它是如何和xml关联起来的?

一个简单的例子

ok,别急,现在我们已经抛出问题,现在我们从demo开始,再结合我们所拥有的知识点出发,一一剖析整个过程。

先来搞个简单的查询:

UserMapper一个接口:

User findById(Long id);

userMapper.xml的sql语句

<mapper namespace="com.oldlu.UserMapper">
 
<select id="findById" resultType="com.oldlu.User">
    select *  from user where id = #{id}
</select>
 
</mapper>

我们知道,接口是不直接被初始化的,但是可以被实现,所以new对象的时候是初始化实现类,然后接口再引用该对象。那么调用接口的方法实际上就是调用被引用对象的方法,也就是实现类的方法。

那么,UserMapper.findById被调用时候,不禁有这两个疑问?

- 被引用的对象是谁呢?
- 接口被调用时候发生了什么?

我们先来回答第二个问题,既然找不到实现类,UserMapper有没可能被代理起来呢,findById方法调用时候,我们找到代理对象来执行就行了。

代理有两种方式:

  • 静态代理
  • 动态代理

设计模式之代理模式的懂静态代理和动态代理:https://blog.csdn.net/ZGL_cyy/article/details/132910893

而静态代理基本是不可能的了,静态代理需要对UserMapper所有的方法进行重写。那么只能是动态代理,动态代理接口的所有方法,每次接口被调用,就会进入动态代理对象的invoke方法,然后加载xml中的sql完成操作数据库,再返回结果。

再然后说到动态代理,常见的方式有以下2种方式:

  • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

所以,动态代理代理还是对象类,那么我们只有接口,不能new,哪来的对象呢?别忘了,我们还有反射机制,我们是不是可以通过反射给接口生成对象,还记得Class.forName吗。

综合上面的猜想:

  1. 第一步:通过反射机制给接口生成对象
  2. 第二步:动态代理反射对象,这样接口被调用,就会触发动态代理

2 动态代理和反射对象

动态代理有几种实现方式,这里我们就先讲JDK动态代理,使用步骤如下:

  • 新建一个接口
  • 创建代理类,实现java.lang.reflect.InvocationHandler接口
  • 接口测试

接口我们就用UserMapper,我们来写个代理对象。

public class Test {
 
        // 接口
        static interface Subject {
            void sayHi();
 
            void sayHello();
 
        }
 
        // 默认实现类(我们可以反射生成)
        static class SubjectImpl implements Subject {
            @Override
            public void sayHi() {
                System.out.println("hi");
            }
 
            @Override
            public void sayHello() {
                System.out.println("hello");
            }
        }
 
 
        // jkd动态代理
        static class ProxyInvocationHandler implements InvocationHandler {
 
            private Subject target;
 
            public ProxyInvocationHandler(Subject target) {
                this.target = target;
            }
 
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.print("say:");
                return method.invoke(target, args);
            }
        }
 
        public static void main(String[] args) {
            Subject subject = new SubjectImpl();
            Subject subjectProxy = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), new ProxyInvocationHandler(subject));
            subjectProxy.sayHi();
            subjectProxy.sayHello();
        }
    }

ok,一个简单的动态代理例子送给你们,上面代码中关键生成动态代理对象的关键代码是:

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
  • loader: 用哪个类加载器去加载代理对象
  • interfaces:动态代理类需要实现的接口
  • h:动态代理方法在执行时,会调用h里面的invoke方法去执行

3 源码分析

好啦,上面该做的准备已经都准备好了,我们对mybatis的这个mapper接口大概都有些思路了,下面我们去正式验证一下,那么肯定就要去看源码了。我们只是去验证上面的mapper接口问题,所以不需要去看全部的代码,当然如果你看整个流程下来的话,会更加清晰。

论证猜想,我们可以采用结果导向的方式去看源码,从获取mapper那里开始看,也就是

SqlSession sqlSession = sqlSessionFactory.openSession();
 
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

主要从sqlSession.getMapper(UserMapper.class);这里开始,先看整个UserMapper是不是被动态代理的。ok,我们进入代码中:

  • org.apache.ibatis.session.Configuration#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}

继续走到Configuration方法里,Configuration是mybatis所有配置相关的地方,mybatis-cfg.xml、UserMapper.xml等文件都会被预先加载到Configuration里。

  • org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

这时候,我们发现Configuration里面出现了一个mapperRegistry,翻译过来可以理解为mapper的注册器,其实在加载UserMapper.xml的时候,我们就需要在mapperRegistry里面进行注册,所有,我们可以从这里面进行获取。继续走~

  • org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 
        //获取mapper代理工厂
        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);
        }
    }

ok,这里分为了两步:

  • knownMappers.get(type);
  • 获取已知的加载过的mapper中获取出mapper代理工厂
  • mapperProxyFactory.newInstance(sqlSession);
  • 代理工厂生成动态代理返回

我们一步步分析,别急,knownMappers其实是个map,根据userMapper.class获取MapperProxyFactory:

Map<Class<?>, MapperProxyFactory<?>> knownMappers

所以knownMappers必然是源码前面的步骤中set进去的。我们先找找,到底是哪里set进去的。找呀找,找到这里:

  • org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
        String namespace = builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class<?> boundType = null;
            try {
                //反射生成namespace的对象
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException e) {
                // ignore, bound type is not required
            }
 
            if (boundType != null) {
                if (!configuration.hasMapper(boundType)) {
                    // Spring may not know the real resource name so we set a flag
                    // to prevent loading again this resource from the mapper interface
                    // look at MapperAnnotationBuilder#loadXmlResource
                    configuration.addLoadedResource("namespace:" + namespace);// namespace:com.lfq.UserMapper
                    configuration.addMapper(boundType);
                }
            }
        }
    }

我们看这个boundType,是通过Resources.classForName(namespace);生成的class,Resources.classForName底层其实就是调用Class.forName生成的反射对象,而参数是namespace,namespacne不正是com.lfq.UserMapper嘛:

<mapper namespace="com.lfq.UserMapper">

完美!!Class.forName(com.lfq.UserMapper)生成反射对象。论证了我们第一点猜想。生成的boundType在被configuration.addMapper(boundType);所以就有了:

  • org.apache.ibatis.session.Configuration#addMapper
public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);
    }
  • org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
/**
 * TODO 将mapper接口包装成mapper代理
 * 原创:公众号:java思维导图
 */
                knownMappers.put(type, new MapperProxyFactory<>(type));
//解析接口上的注解或者加载mapper配置文件生成mappedStatement(com/lfq/UserMapper.java)
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 开始解析
                parser.parse();
// 加载完成标记
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

上面的代码,就给我论证了这个MapperProxyFactory是哪里来的,MapperProxyFactory里面其实就一个参数mapperInterface,就是反射生成的这个对象。ok,第一个猜想已经论证完毕,接着我们看刚才说到的第二点:动态代理。

回到mapperProxyFactory.newInstance(sqlSession); 这个MapperProxyFactory就是我们刚刚new出来的,我们打开newInstance方法看看:

  • org.apache.ibatis.binding.MapperProxyFactory#newInstance(MapperProxy)
public T newInstance(SqlSession sqlSession) {
        //MapperProxy为InvocationHandler的实现类
        final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
        //真实生成代理
        return newInstance(mapperProxy);
    }
 
    protected T newInstance(MapperProxy<T> mapperProxy) {
        //采用JDK自带的Proxy代理模式生成
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

终于在里面看到Proxy.newProxyInstance了,好激动呀。又论证了第二点的动态代理猜想 上面代码中,首先把sqlSession, mapperInterface, methodCache三个参数封装到MapperProxy中,而MapperProxy是实现了InvocationHandler接口的方法,因此动态代理被调用的时候,会进入到MapperProxy的invoke方法中。

sqlSession是必须的,因为操作数据库需要用到sqlsession。具体invoke里面的内容,我们不做多分析啦,刚兴趣的同学自己去看下源码哈。可以猜想:找到对应的sql,然后执行sql操作,哈哈哈。

4 总结

  1. 通过 @MapperScan(“com.xxx.dao”) 并将它们的beanClass设置为 MapperFactoryBean
  2. MapperFactoryBean 实现了 spring 的 FactoryBean 接口,俗称工厂Bean
  3. 当我们通过 @Autowired 注入Dao接口时,返回的对象就是 MapperFactoryBean 这个工厂Bean中的 getObject() 方法对象
  4. getObject 是通过JDK动态代理,返回了一个Dao接口的代理对象 MapperProxy

好啦,今天的内容就到这里啦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1013117.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Git(7)——使用Beyond Compare快速解决冲突

一、简介 根据前六章的学习&#xff0c;我们应该很清楚地感知到不同分支合并代码时产生的冲突是最让我们头疼的问题&#xff0c;因为他需要我们手动去解决冲突的文件&#xff0c;有没有一种方法可以快速地解决冲突呢&#xff1f;本篇文章将介绍如何使用Byond Compare去快速解决…

Deepin配置ibus

文章目录 Deepin配置ibus删除fcitx安装ibus配置ibus Deepin配置ibus Linux下小小输入法好多地方不兼容, 需要安装ibus输入框架 Deepin版本: Deepin20.9 删除fcitx sudo apt purge fcitx-bin fcitx-data fcitx-frontend-gtk2 fcitx-frontend-gtk3 sudo apt purge fcitx-modu…

商城系统开发,如何确保用户数据的安全性?

确保用户数据的安全性是商城系统开发中至关重要的一项任务。随着数字化时代的到来&#xff0c;用户的个人信息和交易数据已成为黑客和不法分子的重要目标&#xff0c;因此保护用户数据的安全性对于商城系统的成功运营至关重要。在开发商城系统时&#xff0c;以下几个方面是确保…

虚拟线上发布会带来颠覆性新体验,3D虚拟场景直播迸发品牌新动能

虚拟线上发布会是近年来在数字化营销领域备受关注的形式&#xff0c;而随着虚拟现实技术的不断进步&#xff0c;3D虚拟场景直播更成为了品牌宣传、推广的新选择。可以说&#xff0c;虚拟线上发布会正在以其颠覆性的新体验&#xff0c;为品牌带来全新的活力。 1.突破时空限制&am…

步态识别常见模块解读及代码实现:基于OpenGait框架

步态识别常见模块解读及代码实现&#xff1a;基于OpenGait框架 最近在看步态识别相关论文&#xff0c;但是因为记忆力下降的原因&#xff0c;老是忘记一些内容。因此记录下来方便以后查阅&#xff0c;仅供自己学习参考&#xff0c;没有背景知识和论文介绍。 目录 步态识别常见…

小米OPPO三星一加红魔全机型解锁BL详细教程合集-ROOT刷机必要操作

解锁BL一个熟悉又陌生的词汇&#xff0c;只要你刷机root过&#xff0c;你肯定都解锁BL成功过。我们简单的描述下BL是什么&#xff1f;BL全名bootloader&#xff0c;目前市面上全部机型&#xff0c;基本出厂全部BL处于锁定的状态锁定的BL机型&#xff0c;不支持刷入非官方固件或…

荣誉丨“Qspace|轻空间”荣获“盐城市零碳空间工程技术研究中心”称号

近日&#xff0c;盐城市科学技术局公布了《2023年度盐城市工程技术研究中心认定名单》&#xff0c;轻空间&#xff08;江苏&#xff09;膜科技有限公司荣誉入选。 工程技术研究中心是指主要依托城市综合实力和创新能力较强的企业、高校或科研院所,具有较完备的工程技术综合配套…

【生命的分支:揭秘二叉树的神奇编码】

1.树概念及结构 2.二叉树概念及结构 3.二叉树顺序结构及实现 4.二叉树链式结构及实现 内容回顾&#xff1a; 1、顺序表&#xff1a;数组 缺点&#xff1a; 中间或头部插入删除数据需要挪动数据&#xff0c;效率低。空间不够&#xff0c;需要扩容&#xff0c;扩容有消耗&…

[Qt]事件

文章摘于 爱编程的大丙 文章目录 1. 事件处理器1.1 事件1.2 事件处理器函数1.2.1 鼠标事件1.2.2 键盘事件1.2.3 窗口重绘事件1.2.4 窗口关闭事件1.2.5 重置窗口大小事件 1.3 重写事件处理器函数1.3.1 头文件1.3.2 源文件1.3.3 效果 1.4 自定义按钮1.4.1 添加子类1.4.2 使用自定…

ECS-7000S集中空调节能控制和管理系统 制冷机房集群控制系统解决方案

ECS-7000S集中空调节能控制和管理系统 自动调整冷水主机运行台数 ECS-7000S制冷机房集群控制系统 公司是一家从事智能电网用户端的智能电力监控与电气安全系统的研发,生产和销售于一体的高新技术企业&#xff0c;自主研发了风机节能控制器,新风空调节能控制器,电梯节能控制器…

报团取暖!

大家好&#xff0c;我是技术UP主小傅哥。 3600人的加入&#xff0c;600多天的运营&#xff0c;其实小傅哥还悄悄的运营了一个免费的帮助大家找工作的星球&#xff0c;现在已经有非常多的伙伴加入&#xff0c;并分享许多公司的实习、校招、内推岗位&#xff0c;也有很多伙伴在线…

Python实战 | 如何使用 Python 调用 API

**本文目录 ** 一、前言 二、调用浙江数据开放平台API获取数据 &#xff08;一&#xff09;API获取数据的流程 &#xff08;二&#xff09;HTTP请求 &#xff08;三&#xff09;API的参数 &#xff08;四&#xff09;使用request库获取API数据 三、调用百度通用翻译API **四、*…

管理方法论:6. 正视团队冲突——化解危机,长治久安

概念 团队冲突指的是两个或两个以上的团队在目标、利益、认识等方面互不相容或互相排斥&#xff0c;从而产生心理或行为上的矛盾&#xff0c;导致抵触、争执或攻击事件。 参考&#xff1a; https://baike.baidu.com/item/%E5%9B%A2%E9%98%9F%E5%86%B2%E7%AA%81/6747073 htt…

PAL/NTSC/1080I和interlaced scan(隔行扫描)

目录 1.PAL/NTSC和1080I 2.PAL/NTSC/1080I的timing 2.1 NTSC的垂直同步 2.2 PAL的垂直同步​编辑 2.3 1080i50FPS的vic20的时序 3.interlaced video timing实现说明 1.PAL/NTSC和1080I NTSC 和PAL 是两种不同视讯标准, 两种都是CRT时代遗留下的产物, 也都使用Interlace技术…

深度学习-卷积神经网络-卷积图像去噪边缘提取-图像去噪 [北邮鲁鹏]

目录标题 参考学习链接图像噪声噪声分类椒盐噪声脉冲噪声对椒盐噪声&脉冲噪声去噪使用高斯卷积核中值滤波器 高斯噪声减少高斯噪声 参考学习链接 计算机视觉与深度学习-04-图像去噪&卷积-北邮鲁鹏老师课程笔记 图像噪声 噪声点&#xff0c;其实在视觉上看上去让人感…

JAVA面向对象(OOP)总结----宏观的程序设计

类&#xff1a;使用关键字class&#xff0c;抽象的概念集合。例如人类&#xff0c;具有共性的产物。 对象&#xff1a;具有自己独立属性&#xff0c;具有个性的个体。 类中可以定义的内容&#xff1a; 成员变量&#xff08;属性&#xff09;&#xff1b;成员方法&#xff08;…

内网穿透:实现远程访问和测试内部网络的关键技术

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 引言 内网穿透是一项重…

基于ssm的蛋糕预定网站

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

BLCD无刷电机三相电流采样说明与总结

最近在在搞无刷电机电流采集&#xff0c;查了跟多资料&#xff0c;做个小记录&#xff0c;只记录低端和高端的三电阻采集&#xff0c;因为双电阻和但电阻比较麻烦&#xff0c;脑子不够用。 一、高端电流采样 高端采样&#xff1a;如上图&#xff08;红色波形&#xff09;&…

Allure的下载和部署

介绍 Allure是一个功能强大的测试报告和测试管理框架&#xff0c;旨在提供清晰、易于理解的测试结果。它支持多种编程语言和测试框架&#xff0c;并提供了丰富的图形化报告&#xff0c;包括图表、图像和日志&#xff0c;以帮助团队更好地分析和诊断测试问题。 下载 这里提供…