超全MyBatis * 详解(绝对干货)

作者:六胖 时间:2023-11-14 02:28:19 

前言

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

  • Mybatis Mapper 接口没有实现类,怎么实现的 *

  • JDK * 为什么不能对类进行代理(充话费送的问题)

  • 抽象类可不可以进行 JDK * (附加问题)

超全MyBatis * 详解(绝对干货)

答不上来的铁汁,证明 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);
}
}

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

  • 创建被代理接口的实现类

  • 创建 * 类,说一下三个参数

    • 类加载器

    • 被代理类所实现的接口数组

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

  • 被代理实现类调用方法


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 功能实现了,大致运行流程也清楚了,下面要针对原理实现展开分析

* 原理分析

从原理的角度上解析一下,上面 * 测试程序是如何执行的

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

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

超全MyBatis * 详解(绝对干货)

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

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

超全MyBatis * 详解(绝对干货)

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

超全MyBatis * 详解(绝对干货)

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

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

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

超全MyBatis * 详解(绝对干货)

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

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

mybatis version:3.4.x

Mybatis 源码实现

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

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

超全MyBatis * 详解(绝对干货)

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 * 详解(绝对干货)

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

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

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

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

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

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

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

无实现类完成 *

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

超全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 字符串,定义个字符串变量,是否能成功返回

超全MyBatis * 详解(绝对干货)

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

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

超全MyBatis * 详解(绝对干货)

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

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

Mybatis 是这么说的

超全MyBatis * 详解(绝对干货)

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

超全MyBatis * 详解(绝对干货)

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

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

  • 根据 .xml 上关联的 namespace, 通过 Class#forName 反射的方式返回 Class 对象(不止 .xml namespace 一种方式)

  • 将得到的 Class 对象(实际就是接口对象)传递给 Mybatis 代理工厂生成代理对象,也就是刚才 mapperInterface 属性

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

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

超全MyBatis * 详解(绝对干货)

超全MyBatis * 详解(绝对干货)

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

超全MyBatis * 详解(绝对干货)

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

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

  3. 添加到对应的映射器容器中,等待生成 * 对象

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

超全MyBatis * 详解(绝对干货)

至此,文初提的 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 是不能对类进行代理的

超全MyBatis * 详解(绝对干货)

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

超全MyBatis * 详解(绝对干货)

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

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

结言

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

Q:JDK * 能否对类代理?

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

Q:抽象类是否可以 JDK * ?

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

Q:Mybatis Mapper 接口没有实现类,怎么实现的 * ?

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

来源:https://juejin.cn/post/6924117553782456328

标签:MyBatis, ,
0
投稿

猜你喜欢

  • java排查一个线上死循环cpu暴涨的过程分析

    2022-07-20 01:27:08
  • Android 消息机制问题总结

    2023-08-06 03:10:49
  • Android 去掉状态栏的方法汇总

    2022-05-25 01:31:18
  • Java 实现判定顺序表中是否包含某个元素(思路详解)

    2023-09-11 01:03:04
  • C#飞行棋小程序设计分析

    2023-06-05 05:27:24
  • Android使用fragment实现左侧导航

    2023-10-30 10:02:09
  • 如何基于SpringBoot部署外部Tomcat过程解析

    2021-10-26 07:14:16
  • 简单了解Java中的可重入锁

    2023-12-18 12:29:19
  • Java使用JSON传递字符串注意事项解析

    2021-11-05 13:35:37
  • 详解Spring中的Environment外部化配置管理

    2023-11-23 05:24:24
  • SpringBoot打成war包在tomcat或wildfly下运行的方法

    2023-11-23 08:20:56
  • spring框架集成flyway项目的详细过程

    2023-09-14 11:47:14
  • IntelliJ IDEA 安装教程2019.09.23(最新版)

    2023-08-24 23:01:44
  • C# 如何在MVC3中取消备用控制器的选择

    2023-02-16 06:48:18
  • Android实现清除单个域名的cookie

    2021-10-09 01:57:52
  • java 实现比较版本号功能

    2022-08-12 20:35:11
  • Java Swing中JList选择事件监听器ListSelectionListener用法示例

    2021-06-21 22:52:01
  • C#串口通讯概念及简单的实现方法

    2021-06-25 13:49:24
  • mybatisPlus条件构造器常用方法小结

    2023-12-16 07:04:09
  • C#下载歌词文件的同步和异步方法

    2023-04-11 22:46:49
  • asp之家 软件编程 m.aspxhome.com