MyBatis 是如何完成从 XML 里的标签语句到 Mapper 对象的?答案是动态代理。为了完成代理工作,XML 里必须要有关键的信息,第一是 namespace,它描述的是对应的的哪个 DAO 接口;第二是语句的 ID,对应的 DAO 接口里的方法。这里我们不去看 MyBatis 的源码,我用模拟的代码去描述 MyBatis 的工作本质,也就是 JDK 动态代理。
DAO 接口
我们先来声明 DAO 接口。
interface FakeMapper { Integer selectCount(); }
实例类
class FakeMapperImpl { Integer count = 120; public Integer selectCount() { return count++; } }
这是执行者,但是可以看到,我们并没有为它实现 FakeMapper 接口。接下来就是奇妙的代理对象了。
代理类
要实现动态代理,首先我们的代理对象必须实现
InvocationHandler
接口,里面的invoke
方法即是代理对象的代理逻辑。我这里简化了代理过程,MyBatis 当然不是这么做的。class FakeProxy implements InvocationHandler { Object instance; Class<?> clazz; public FakeProxy(Object target) { instance = target; clazz = instance.getClass(); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Method m = clazz.getMethod(methodName, method.getParameterTypes()); Object result; result = m.invoke(instance, args); return result; } }
我们根据传入的方法签名(method 和 args),来做自己的执行逻辑。
创建代理对象
有了这些我们就可以创建代理对象了。
Class<?> clazz = FakeMapperImpl.class; ClassLoader classLoader = clazz.getClassLoader(); Class<?>[] interfaces = new Class[]{FakeMapper.class}; Object proxyObject = Proxy.newProxyInstance(classLoader, interfaces, new FakeProxy(new FakeMapperImpl()));
Proxy#newProxyInstance
方法指定了需要实现的接口,以及实现了InvocationHandler
的代理对象。测试
运行一下测试代码
System.out.println(proxyObject instanceof FakeMapper); // true Integer result = ((FakeMapper) proxyObject).selectCount(); System.out.println(result); // 120
什么奇妙魔法?
我们可以把 JDK 的动态代理过程视为一个代码生成即可。从
interfaces
里收集所有的方法签名,然后拼凑出一个代理对象。追踪 newProxyInstance 的调用栈,我们会发现,最后是用到代理生成器 ProxyGenerator,利用 generateProxyClass 方法即可生成代理对象的字节码。/* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);
然后用生成的字节码,在给定的 ClassLoader 里定义一个类。
我们来看看生成的字节码反编译之后的样子。
public final class MyFakeImpl extends Proxy implements FakeMapper { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public MyFakeImpl(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final Integer selectCount() throws { try { return (Integer)super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("FakeMapper").getMethod("selectCount"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
C#的等价物
C#当然也能这么玩,但是 C#的官方自带的实现方法有点门槛,相当于我们要手动去实现 generateProxyClass 方法,需要对 MSIL 有一定的了解,参考 System.Reflection.Emit 里提供的方法。变通的快速入门做法是我们先写一个实现接口的类,然后编译得到 MSIL,再反推来做代理类的具体实现。Of course,也有第三方库可以使用。