Mybatis 生成的 com.sun.proxy.$Proxy8 是啥
2024-08-01 09:37 阅读(335)

最近在阅读 Mybatis 源码的过程中,发现 Mybatis 生成了一个叫 com.sun.proxy.$Proxy8 的类,这是个啥,有啥作用,长啥样子?夺命连环问,让人大汗淋漓!


示例代码

try (SqlSession session = sqlSessionFactory.openSession()) {
		UserMapper mapper = session.getMapper(UserMapper.class);
    User user = mapper.selectById(1);
}       

使用阿里内部流出的 Java 源码神器 XCodeMap,跑一下,生成一个带上下文数据的序列图,快速浏览一下代码结构。

可以看到 selectById 方法,最终实际调用了 com.sun.proxy.$Proxy8 这个类的 invoke 方法。

这个类根本找不到源码。猜测应该是动态代理生成的。

那这个类具体长啥样呢?

查看类的字节码

在运行参数里面加上 -Dxcodemap.dump.class=com.sun.proxy.$Proxy8,再运行一次,xcodemap 会打印出这个类的字节码,存放在项目目录的 .xcodemap/cache 下面。

我们这里先睹为快(用Idea打开)。

public final class $Proxy8 extends Proxy implements UserMapper {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy8(InvocationHandler var1) throws  {
        super(var1);
    }

    public final User selectById(int var1) throws  {
        try {
            return (User)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
}

可以看到,这个类实现了 UserMapper 接口,并实现了 selectById 方法。换句话说,com.sun.proxy.$Proxy8 就是 UserMapper 的实现类。所有对 UserMapper 接口方法的调用,最终都是由这个类完成的。这个类通过动态代理机制生成,负责拦截接口方法的调用,并通过预先定义的逻辑来执行实际的操作。这样,开发者只需定义接口和相应的 SQL 语句,而不需要关心具体的实现类是如何生成和工作的,从而大大简化了开发过程,提高了开发效率。

那这个类是怎么生成的呢?

追踪类的生成过程

xcodemap 有一个很强大的功能,那就是可以追踪任意 Java 对象的来龙去脉。

找到 $Proxy8,右键 “show object”,左边的序列图会用红色标记出相关节点。


顺着这些节点,我们可以很快找到 newInstance 的源码

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

可以看到,这是调用了JDK 的动态代理方法,即使用了 Proxy.newProxyInstance 方法。其中,MapperProxy 类实现了 InvocationHandler 接口,负责处理代理实例的方法调用。

Java 动态代理的基本原理可以分为三个步骤来描述:


首先,创建一个实现了 InvocationHandler 接口的处理器类,这个处理器类包含一个 invoke 方法,用于处理代理实例的方法调用

然后,通过调用 Proxy 类的静态方法 newProxyInstance 来创建代理实例。这个方法需要三个参数:类加载器、一个或多个接口以及 InvocationHandler 的实例

当我们通过代理实例调用方法时,实际调用的是处理器类的 invoke 方法,从而实现了对目标对象方法的动态代理


我们在使用 Mybatis 时,只需要定义接口和 XML 格式的 SQL,并不需要编写具体的实现类。这是因为 Mybatis 通过动态代理机制来自动生成这些实现类,从而简化了开发者的工作。

总结

我们在读 MyBatis 源码时,往往在第一步就卡住了,因为动态代理的缘故,找不到调用路径。

但在 XCodeMap 的帮助下,通过查看代理类的字节码,追踪代理类的生成过程,只需几分钟,就能打开 MyBatis 源码的大门。