7.3 插件的代理和反射设计
插件用的是责任链模式。首先什么是责任链模式,就是一个对象,在 MyBatis 中可能是四大对象中的一个,在多个角色中传递,处在传递链上的任何角色都有处理它的机会。这句话还是很抽象,打个比方,你在公司中是个重要人物,你需要请假 3 天。那么,请假流程是,首先你需要项目经理批准,然后部门经理批准,最后总裁批准才能完成。你的请假请求就是一个对象,它经过项目经理、部门经理、总裁多个角色审批处理,每个角色都可以对你的请假请求作出修改和批示。这就是责任链模式,它的作用是让每一个在责任链上的角色都有机会去拦截这个对象。在将来如果有新的角色也可以轻松拦截请求对象,进行处理。
MyBatis 的责任链是由 interceptorChain 去定义的,不知道读者是否记得 MyBatis 在创建执行器时用到过这样的代码。
executor = (Executor) interceptorChain.pluginAll(executor);
我们不妨看看 pluginAll() 方法是如何实现的,如代码清单 7-5 所示。
代码清单 7-5:interceptorChain 中的 pluginAll
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
我们知道 plugin 方法是生成代理对象的方法,当它取出插件的时候是从 Configuration 对象中去取出的。从第一个对象(四大对象中的一个)开始,将对象传递给了 plugin 方法,然后返回一个代理;如果存在第二个插件,那么我们就拿到第一个代理对象,传递给 plugin 方法再返回第一个代理对象的代理......依此类推,有多少个拦截器就生成多少个代理对象。这样每一个插件都可以拦截到真实的对象了。这就好比每一个插件都可以一层层处理被拦截的对象。其实读者只要认真阅读 MyBatis 的源码,就可以发现 MyBatis 的四大对象也是这样处理的。
如果要我们自己编写代理类,工作量会很大,为此 MyBatis 中提供了一个常用的工具类,用来生成代理对象,它便是 Plugin 类。Plugin 类实现了 InvocationHandler 接口,采用的是 JDK 的动态代理,我们先看看这个类的两个十分重要的方法,如代码清单 7-6 所示。
代码清单 7-6:MyBatis 提供生成代理对象的 Plugin 类
public class Plugin implements InvocationHandler { ...... public static Object wrap(Object target, Interceptor interceptor) { Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } ...... }
我们看到它是一个动态代理对象,其中 wrap 方法为我们生成这个对象的动态代理对象。
我们再看 invoke 方法,如果你使用这个类为插件生成代理对象,那么代理对象在调用方法的时候就会进入到 invoke 方法中。在 invoke 方法中,如果存在签名的拦截方法,插件的 intercept 方法就会被我们在这里调用,然后就返回结果。如果不存在签名方法,那么将直接反射调度我们要执行的方法。
我们创建一个 Invocation 对象,其构造方法的参数包括被代理的对象、方法及其参数。Invocation 对象进行初始化,它有一个 proceed() 方法,如代码清单 7-7 所示。
代码清单 7-7:反射调用被代理对象的 proceed() 方法
public Object proceed() throws InvocationTargetException, IllegalAccess Exception { return method.invoke(target, args); }
这个方法就是调度被代理对象的真实方法。现在假设有 n 个插件,我们知道第一个传递的参数是四大对象的本身,然后调用一次 wrap 方法产生第一个代理对象,而这里的反射就是反射四大对象本身的真实方法。如果有第二个插件,我们会将第一个代理对象传递给 wrap 方法,生成第二个代理对象,这里的反射就是指第一个代理对象的 invoke 方法,依此类推直至最后一个代理对象。如果每一个代理对象都调用这个 proceed 方法,那么最后四大对象本身的方法也会被调用,只是它会从最后一个代理对象的 invoke 方法运行到第一个代理对象的 invoke 方法,直至四大对象的真实方法。
在初始化的时候,我们一个个的加载插件实例,并用 setProperties() 方法进行初始化。我们可以使用 MyBatis 提供的 Plugin.wrap 方法去生成代理对象,再一层层地使用 Invocation 对象的 proceed() 方法来推动代理对象运行。所以在多个插件的环境下,调度 proceed() 方法时,MyBatis 总是从最后一个代理对象运行到第一个代理对象,最后是真实被拦截的对象方法被运行。大部分情况下,使用 MyBatis 的 Plugin 类生成代理对象足够我们使用,当然如果你觉得自己可以写规则,也可以不用这个类,我们必须慎之又慎使用这个方法,因为它将覆盖底层的方法。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论