callcc.dev

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,也有第三方库可以使用。