最近在阅读 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 源码的大门。