超全MyBatis动态代理详解!

系统运维2025-11-05 13:57:533

本文转载自微信公众号「源码兴趣圈」,超全作者龙台  。态代转载本文请联系源码兴趣圈公众号。理详 

前言

假如有人问你这么几个问题,超全看能不能答上来

Mybatis Mapper 接口没有实现类,态代怎么实现的理详动态代理 JDK 动态代理为什么不能对类进行代理(充话费送的问题) 抽象类可不可以进行 JDK 动态代理(附加问题)

答不上来的铁汁,证明 Proxy、超全Mybatis 源码还没看到位。态代不过没有关系,理详继续往下看就明白了

动态代理实战

众所周知哈,超全Mybatis 底层封装使用的态代 JDK 动态代理。说 Mybatis 动态代理之前,理详先来看一下平常我们写的超全动态代理 Demo,抛砖引玉

一般来说定义 JDK 动态代理分为三个步骤,态代如下所示

定义代理接口 定义代理接口实现类 定义动态代理调用处理器

三步代码如下所示,理详玩过动态代理的小伙伴看过就能明白

public interface Subject { // 定义代理接口     String sayHello(); } public class SubjectImpl implements Subject {  // 定义代理接口实现类     @Override     public String sayHello() {         System.out.println(" Hello World");         return "success";     } } public class ProxyInvocationHandler implements InvocationHandler {  // 定义动态代理调用处理器     private Object target;     public ProxyInvocationHandler(Object target) {         this.target = target;     }     @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println(" 🧱 🧱 🧱 进入代理调用处理器 ");         return method.invoke(target, args);     } } 

1.写个测试程序,运行一下看看效果,同样是分三步

2.创建被代理接口的实现类

创建动态代理类,说一下三个参数 类加载器 被代理类所实现的接口数组

3.用处理器(调用被代理类方法,每次都经过它)

被代理实现类调用方法

public class ProxyTest {     public static void main(String[] args) {         Subject subject = new SubjectImpl();         Subject proxy = (Subject) Proxy                 .newProxyInstance(                         subject.getClass().getClassLoader(),                         subject.getClass().getInterfaces(),                         new ProxyInvocationHandler(subject));         proxy.sayHello();         /**          * 打印输出如下          * 调用处理器:🧱 🧱 🧱 进入代理调用处理器          * 被代理实现类:Hello World          */     } } 

Demo 功能实现了,大致运行流程也清楚了,下面要针对原理实现展开分析

动态代理原理分析

从原理的角度上解析一下,上面动态代理测试程序是如何执行的b2b信息网

第一步简单明了,创建了 Subject 接口的实现类,也是我们常规的实现

第二步是创建被代理对象的动态代理对象。这里有朋友就问了,怎么证明这是个动态代理对象?如图所示

JDK 动态代理对象名称是有规则的,凡是经过 Proxy 类生成的动态代理对象,前缀必然是 $Proxy,后面的数字也是名称组成部分

如果有小伙伴想要一探究竟,关注 Proxy 内部类 ProxyClassFactory,这里会有想要的答案

回归正题,继续看一下 ProxyInvocationHandler,内部保持了被代理接口实现类的引用,invoke 方法内部使用反射调用被代理接口实现类方法

可以看出生成的动态代理类,继承了 Proxy 类,然后对 Subject 接口进行了实现,而实现方法 sayHello 中实际调用的是源码库 ProxyInvocationHandler 的 invoke 方法

一不小心发现了 JDK 动态代理不能对类进行代理的原因 ^ ^

也就是说,当我们调用 Subject#sayHello 时,方法调用链是这样的

但是,Demo 里有被代理接口的实现类,Mybatis Mapper 没有,这要怎么玩

不知道不要紧,知道了估计也看不到这了,一起看下 mybatis 源码是怎么玩的

mybatis version:3.4.x

Mybatis 源码实现

不知道大家考没考虑过这么一个问题,Mybatis Mapper 为什么不需要实现类?

假如说,我们项目使用的三层设计,Controller 控制请求接收,Service 负责业务处理,Mapper 负责数据库交互

Mapper 层也就是我们常说的数据库映射层,负责对数据库的操作,比如对数据的查询或者新增、删除等

大胆设想下,项目没有使用 Mybatis,需要在 Mapper 实现层写数据库交互,会写一些什么内容?

会写一些常规的服务器租用 JDBC 操作,比如:

// 装载Mysql驱动 Class.forName(driveName); // 获取连接 con = DriverManager.getConnection(url, user, pass); // 创建Statement Statement state = con.createStatement(); // 构建SQL语句 String stuQuerySqlStr = "SELECT * FROM STUDENT"; // 执行SQL返回结果 ResultSet result = state.executeQuery(stuQuerySqlStr); ... 

如果项目中所有 Mapper 实现层都要这么玩,那岂不是很想打人...

所以 Mybatis 结合项目痛点,应运而生,怎么做的呢

将所有和 JDBC 交互的操作,底层采用 JDK 动态代理封装,使用者只需要自定义 Mapper 和 .xml 文件

SQL 语句定义在 .xml 文件或者 Mapper 中,项目启动时通过解析器解析 SQL 语句组装为 Java 中的对象

解析器分为多种,因为 Mybatis 中不仅有静态语句,同时也包含动态 SQL 语句

这也就是为什么 Mapper 接口不需要实现类,因为都已经被 Mybatis 通过动态代理封装了,如果每个 Mapper 都来一个实现类,臃肿且无用。经过这一顿操作,展示给我们的就是项目里用到的 Mybatis 框架

上面铺垫这么久,终于要到主角了,为什么 Mybatis Mapper 接口没有实现类也可以实现动态代理

想要严格按照先后顺序介绍 Mybatis 动态代理流程,而不超前引用未介绍过的术语,这几乎是不可能的,笔者尽量说的通俗易懂

无实现类完成动态代理

核心点来了,拿起小本本坐板正了

我们先来看下普通动态代理有没有可能不用实现类,仅靠接口完成

public interface Subject {     String sayHello(); } public class ProxyInvocationHandler implements InvocationHandler {     @Override     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         System.out.println(" 🧱 🧱 🧱 进入代理调用处理器 ");         return "success";     } } 

根据代码可以看到,我们并没有实现接口 Subject,继续看一下怎么实现动态代理

public class ProxyTest {     public static void main(String[] args) {         Subject proxy = (Subject) Proxy                 .newProxyInstance(                         subject.getClass().getClassLoader(),                         new Class[]{Subject.class},                         new ProxyInvocationHandler());         proxy.sayHello();         /**          * 打印输出如下          * 调用处理器:🧱 🧱 🧱 进入代理调用处理器          */     } } 

可以看到,对比文初的 Demo,这里对 Proxy.newProxyInstance 方法的参数作出了变化

之前是通过实现类获取所实现接口的 Class 数组,而这里是把接口本身放到 Class 数组中,殊归同途

有实现类接口和无实现类接口产生的动态代理类有什么区别

有实现类接口是对 InvocationHandler#invoke 方法调用,invoke 方法通过反射调用被代理对象(SubjectImpl)方法(sayHello) 无实现类接口则是仅对 InvocationHandler#invoke 产生调用。所以有实现类接口返回的是被代理对象接口返回值,而无实现类接口返回的仅是 invoke 方法返回值

InvocationHandler#invoke 方法返回值是 success 字符串,定义个字符串变量,是否能成功返回

现在第一个问题答案已经浮现,Mapper 没有实现类,所有调用 JDBC 等操作都是在 Mybatis InvocationHandler 实现的

问题既然已经得到了解决,给人一种感觉,好像没那么难,但是你不好奇,Mybatis 底层怎么做的么?

先抛出一个问题,然后带着问题去看源码,可能让你记忆 Double 倍深刻

咱们 Demo 里的接口是固定的,Mybatis Mapper 可是不固定的,怎么搞?

Mybatis 是这么说的

看看 Mybatis 底层它怎么实现的动态接口代理,小伙伴只需要关注标记处的代码即可

和我们的 Demo 代码很像,核心点在于 mapperInterface 它是怎么赋值的

先来说一下 Mybatis 代理工厂中具体生成动态代理类具体逻辑

根据 .xml 上关联的 namespace, 通过 Class#forName 反射的方式返回 Class 对象(不止 .xml namespace 一种方式) 将得到的 Class 对象(实际就是接口对象)传递给 Mybatis 代理工厂生成代理对象,也就是刚才 mapperInterface 属性

谜底揭晓,Mybatis 使用接口全限定名通过 Class#forName 生成 Class 对象,这个 Class 对象类型就是接口

为了方便大家理解,通过 Mybatis 源码提供的测试类举例。假设已有接口 AutoConstructorMapper 以及对应的 .xml 如下

执行第一步,根据 .xml namespace 得到 Class 对象

首先第一步获取 .xml 上 mapper 标签 namespace 属性,得到 mapper 接口全限定信息

根据 mapper 全限定信息获取 Class 对象

添加到对应的映射器容器中,等待生成动态代理对象

如果此时调用生成动态代理对象,代理工厂 newInstance 方法如下:

至此,文初提的 Proxy、Mybatis 动态代理相关问题已全部答疑

抽象类能否 JDK 动态代理

说代码前结论先行,不能!

public abstract class AbstractProxy {     abstract void sayHello(); } AbstractProxy proxyInterface = (AbstractProxy) Proxy         .newProxyInstance(                 ProxyTest.class.getClassLoader(),                 new Class[]{AbstractProxy.class},                 new ProxyInvocationHandler()); proxyInterface.sayHello(); 

毫无疑问,报错是必然的,JDK 是不能对类进行代理的

带着小疑惑我们看一下 Proxy 源码报错位置,JDK 动态代理在生成代理类的过程代码中,会有是否接口验证

抽象类终归是类,加个 abstract 也成不了接口(就像我,虽然胖了 60 斤,但依然是帅哥)

下次面试官如果有问这问题的,斩钉截铁一点,就是不能

结言

结合 Mybatis 使用 JDK 动态代理相关的问题,展开了文章的讲述,这里总结如下

Q:JDK 动态代理能否对类代理?

因为 JDK 动态代理生成的代理类,会继承 Proxy 类,由于 Java 无法多继承,所以无法对类进行代理

Q:抽象类是否可以 JDK 动态代理?

不可以,抽象类本质上也是类,Proxy 生成代理类过程中,会校验传入 Class 是否接口

Q:Mybatis Mapper 接口没有实现类,怎么实现的动态代理?

Mybatis 会通过 Class#forname 得到 Mapper 接口 Class 对象,生成对应的动态代理对象,核心业务处理都会在 InvocationHandler#invoke 进行处理

希望读过的小伙伴都能有所收获,如果对于文章内容有所疑惑,可以通过留言或者添加作者好友的方式沟通,祝好!

本文地址:http://www.bzve.cn/news/588f64498767.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

热门文章

全站热门

拯救者R720i7(性能出众,高效散热,打造极致游戏体验)

安装了几个软件,不想要了,可是不知道如何卸载,该怎么卸载呢?下面我将详细为大家分享卸载ubuntu系统下的软件方法。卸载指定的软件1、我们打开菜单中的“软件中心”。这个菜单在桌面的左侧窗口上,像一个A字的那个图标就是了。点它。2、在打开后的“软件中心”窗口中的有上侧。我们在搜索框中输入我们想要卸载的软件名字,比如,我要卸载“搜狗”,那么我就在里面输入“sogou”。ubuntu 中的搜狗就是这个名字。3、在结果中,假如显示了你需要卸载的软件,那么我们找到那一行,然后点中该软件所在的那一行,点击后面的“卸载”按钮。这样我们就能够完成指定软件的卸载啦!卸载多余不需要的软件1、打开软件中心后,我们点击“已安装”那个选项。2、然后,我们在已安装列表中展开需要卸载的软件类型,比如我先要卸载“影音”类的,那么我就展开“影音”。3、在展开后的该类型的软件列表中。找到要卸载的那个软件,然后点中它,点击后面的“卸载”按钮。我这里卸载的是“茄子大头贴”4、在验证窗口中输入你计算机的管理密码,然后点击“授权”按钮。命令卸载1、命令卸载就一个哦是:sudo apt-get remove 软件的名字然后按enter键执行。假如需要验证密码,那么输入密码按回车键确定,假如需要输入Y/N,那么就输入Y后,按回车键继续卸载。我这里卸载“Deluge”这个下载软件。

电脑打印结婚请柬教程(轻松DIY,高效方便,一键打印定制结婚请柬)

Ubuntu 14.04中加入了开启本地菜单的选项,允许用户将全局菜单移动到各个窗口中。前面为大家介绍了如何禁用Ubuntu 13.10全局菜单,而在14.04中只需更改设置即可完成。打开系统设置中外观选项,在外观选项窗口切换到行为标签,选择显示窗口菜单方式,选择“在窗口标题栏”后关闭该窗口,现在你可以看到菜单已由全局菜单移动到窗口标题栏中。

解决电视访问电脑密码错误问题的有效方法(技巧和步骤帮助您解决电视访问电脑时的密码错误)

Node.js 4.0 发布的主要目标是为io.js 用户提供一个简单的升级途径,所以这次并没有太多重要的 API 变更。下面的内容让我们来看看如何轻松的在 ubuntu server 上安装、配置 Node.js。一、基础系统安装Node 在 Linux,Macintosh,Solaris 这几个系统上都可以完美的运行,linux 的发行版本当中使用 Ubuntu 相当适合。这也是我们为什么要尝试在ubuntu 15.04 上安装 Node.js,当然了在 14.04 上也可以使用相同的步骤安装。1.系统资源Node.js 所需的基本的系统资源取决于你的架构需要。本教程我们会在一台 1GB 内存、 1GHz 处理器和 10GB 磁盘空间的服务器上进行,最小安装即可,不需要安装 Web 服务器或数据库服务器。2.系统更新在我们安装 Node.js 之前,推荐你将系统更新到最新的补丁和升级包,所以请登录到系统中使用超级用户运行如下命令:复制代码代码如下:# apt-get update    3.安装依赖Node.js 仅需要你的服务器上有一些基本系统和软件功能,比如 make、gcc和wget 之类的。假如你还没有安装它们,运行如下命令安装:复制代码代码如下:# apt-get install python gcc make g++ wget二、下载最新版的Node JS v4.0.0复制其中的最新的源代码的链接,然后用wget 下载,命令如下:复制代码代码如下:# wget https://nodejs.org/download/rc/v4.0.0-rc.1/node-v4.0.0-rc.1.tar.gz下载完成后使用命令tar 解压缩: 复制代码代码如下:# tar -zxvf node-v4.0.0-rc.1.tar.gz三、安装 Node JS v4.0.0现在可以开始使用下载好的源代码编译 Node.js。在开始编译前,你需要在 ubuntu server 上切换到源代码解压缩后的目录,运行configure 脚本来配置源代码:复制代码代码如下:root@ubuntu-15:~/node-v4.0.0-rc.1# ./configure现在运行命令 make install 编译安装 Node.js:复制代码代码如下:root@ubuntu-15:~/node-v4.0.0-rc.1# make installmake 命令会花费几分钟完成编译,安静的等待一会。四、验证 Node.js 安装一旦编译任务完成,我们就可以开始验证安装工作是否 OK。我们运行下列命令来确认 Node.js 的版本。复制代码代码如下:root@ubuntu-15:~# node -v v4.0.0-pre在命令行下不带参数的运行node 就会进入 REPL(Read-Eval-Print-Loop,读-执行-输出-循环)模式,它有一个简化版的emacs 行编辑器,通过它你可以交互式的运行JS和查看运行结果。五、编写测试程序我们也可以写一个很简单的终端程序来测试安装是否成功,并且工作正常。要做这个,我们将会创建一个“test.js” 文件,包含以下代码,操作如下:现在为了运行上面的程序,在命令行运行下面的命令: 复制代码代码如下:root@ubuntu-15:~# node test.js 在一个成功安装了 Node JS 的环境下运行上面的程序就会在屏幕上得到上图所示的输出,这个程序加载类util” 到变量 “util” 中,接着用对象 “util” 运行终端任务,console.log 这个命令作用类似 C++ 里的cout就是这些了。假如你刚刚开始使用Node.js 开发应用程序,希望本文能够通过在 ubuntu 上安装、运行Node.js 让你了解一下Node.js 的大概。

这段时间使用公司服务器时候发现Linux上竟然没有中文输入法,经过一番折腾后,终于把这个问题解决,将过程记录如下: 1.首先安装ibus框架,打开终端,输入如下命令 sudo add-apt-repository ppa:shawn-p-huang/ppa sudo apt-get update sudo apt-get install ibus-gtk ibus-qt4 ibus-pinyin ibus-pinyin-db-open-phrase 2.然后进入SystemSettings-->LanguageSupport,假如它提示要自动更新,我们就自动更新,然后在最下面将(keyboard input method system)选为ibus,貌似因为环境下ibus默认是不启动的 3.这个时候我们发现任务栏还是没有输入法,不要紧我们接着进入屏幕左上角的DashHome,在搜索中输入ibus,点击回车,这个时候上方的任务栏已经有小键盘图标了,右键点击它的Preference功能设置,接着点击Input Method ,在下拉框中可以找到你的输入法,这里我使用的是Google 拼音 至此,已解决中文输入法的问题。

Webalizer是一款免费的应用程序,可用于分析网站服务器日志。这样一来,你就能更清楚地了解你的网站或服务器收到的流量大小。它是一种使用广泛的开源工具,提供了非常详细的报告。这个工具的使用和安装很简单,cPanel之类的许多高级托管控制面板使用该工具,为用户提供流量方面的详细信息。功能特性这款工具的功能很强大,足以解析不同格式的访问日志。它还可以从压缩文件中获取信息,不需要先解压缩文件。你从命令行和图形化用户界面都可以使用该工具,以你觉得方便的方式来查看报告。它支持多种语言,开发团队正在努力添加支持另外许多语言的功能。它能够解析任何大小或复杂程度的日志文件。它同时支持IPV4和IPV6,还有原生的地理位置服务和DNS服务器软件。在Ubuntu上安装和配置WebalizerWebalizer需要Apache网站服务器软件安装在Linux系统上,因为它要读取和解析Apache错误日志,从而分析流量。假如Apache之前没有安装在你的系统上,你试图看一看webalizer是如何工作的,那么启动终端,运行下面这个命令,即可将Apache安装在你的Ubuntu上:复制代码代码如下:sudo /etc/init.d/apache2 start现在启动你的浏览器,装入http://localhost,核实http在正常运行,它应该会显示诸如此类的页面:Apache默认页面注意:默认情况下,Apache的文档根目录是/var/www/html/,所以你需要把脚本放在这个位置,那样Apache网站服务器就能提供这些脚本。由于Apache已安装在我们的Ubuntu系统上并运行起来,现在运行下面这个命令,安装webalizer。恭喜你,webalizer已安装完毕。现在我们需要配置它。配置webalizer你可能也注意到,在安装过程中,webalizer目录已经创建在/var/www/路径上,我们需要把它移到/var/www/html,那样Apache才能顺利该目录。在终端上运行下面这个命令来完成这项任务。复制代码代码如下:sudo gedit /etc/webalizer/webalizer.conf务必要确保Apache访问日志文件路径在该文件中正确无误(下列屏幕截图中高亮显示的部分)。假如路径这一项出错,就纠正路径,并保存文件。好了,我们离成功只有一步之遥了。测试Webalizer配置运行下面这个命令,核实webalizer已成功安装和配置。测试webalizer启动浏览器,装入http://localhost/webalizer/ URL。它应该会装入webalizer页面,并且附有系统当前HTTP活动的报告。可以执行webalizer –h得到所有命令行参数:复制代码代码如下:#!/bin/shrun=/usr/sbin/webalizer$run -F clf -p -n -t www.test.com-o /var/www/html/log /var/log/httpd/access_log说明:-F clf 指明我们的web日志格式为标准的一般日志文件格式(Common Logfile Format)-p 指定使用递增模式,这就是说每作一次分析后,webalizer会生产一个历史文件,这样下一次分析时就可以不分析已经处理过的部分。这样我们就可以在短时间内转换我们的日志文件,而不用担心访问量太大时日志文件无限增大了。-n “ “ 指定服务器主机名为空,这样输出结果会美观一些。-o “www.test.com” 指定输出结果标题./var/log/httpd/access_log:指定日志文件然后在/etc/crontab中加入:01 1 * * * root /etc/rc.d/webalizer即每天凌晨1点执行该脚本。然后运行/etc/rc.d/init.d/crond reload重载入crond服务。结束语Webalizer是在微软Windows、Linux和Mac OS上广泛使用的一种工具,可用于分析系统上的Web活动。它是相当简单的工具,可以解析网站服务器日志,即便它受到数百万次的访问。这个工具的重要性对系统和网站管理员来说毋容置疑。

热门文章

友情链接

滇ICP备2023006006号-39