《深入理解mybatis原理(十一)》 Mybatis插件原理之实现细节

    xiaoxiao2025-03-12  12

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。

    一、代理链的生成

    Mybatis 支持对 Executor StatementHandler PameterHandler ResultSetHandler 进行拦截,也就是说会对这 4 种对象进行代理。下面以 Executor 为例。 Mybatis 在创建 Executor 对象时会执行下面一行代码:

    executor =(Executor) interceptorChain.pluginAll(executor); InterceptorChain 里保存了所有的拦截器,它在 mybatis 初始化的时候创建。上面这句代码的含义是调用拦截器链里的每个拦截器依次对 executor 进行 plugin (插入?)代码如下:

    /** * 每一个拦截器对目标类都进行一次代理 * @paramtarget * @return 层层代理后的对象 */ public Object pluginAll(Object target) { for(Interceptor interceptor : interceptors) { target= interceptor.plugin(target); } returntarget; } 下面以一个简单的例子来看看这个 plugin 方法里到底发生了什么。

    @Intercepts({@Signature(type = Executor.class, method ="update", args = {MappedStatement.class, Object.class})}) public class ExamplePlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } } 每一个拦截器都必须实现上面的三个方法,其中: 1)  Object intercept(Invocation invocation) 是实现拦截逻辑的地方,内部要通过 invocation.proceed() 显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

    2) Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。

    3) setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

    注解里描述的是指定拦截方法的签名  [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

    二、Plugin.wrap方法

    从前面可以看出,每个拦截器的plugin方法是通过调用Plugin.wrap方法来实现的。代码如下:

    public staticObject 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) { //生成代理对象, Plugin对象为该代理对象的InvocationHandler (InvocationHandler属于java代理的一个重要概念,不熟悉的请参考相关概念) returnProxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target,interceptor,signatureMap)); } returntarget; } 这个 Plugin 类有三个属性:

    private Object target;//被代理的目标类

    private Interceptor interceptor;//对应的拦截器

    private Map<Class<?>, Set<Method>> signatureMap;//拦截器拦截的方法缓存

    我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对

    executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)。

    三、代理链上的拦截

    代理链生成后,对原始目标的方法调用都转移到代理者的invoke方法上来了。Plugin作为InvocationHandler的实现类,他的invoke方法是怎么样的呢? 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)) { //调用代理类所属拦截器的intercept方法, return interceptor.intercept(new Invocation(target, method, args)); } returnmethod.invoke(target, args); } catch(Exception e) { throwExceptionUtil.unwrapThrowable(e); } } invoke 里,如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法。我们看到传递给拦截器的是一个 Invocation 对象,这个对象是什么样子的,他的功能又是什么呢? public class Invocation { private Object target; private Methodmethod; private Object[]args; publicInvocation(Object target, Method method, Object[] args) { this.target =target; this.method =method; this.args =args; } ... public Objectproceed() throws InvocationTargetException, IllegalAccessException { returnmethod.invoke(target, args); } } 可以看到, Invocation 类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。 在每个拦截器的 intercept 方法内,最后一个语句一定是 returninvocation.proceed() (不这么做的话拦截器链就断了,你的 mybatis 基本上就不能正常工作了)。 invocation.proceed() 只是简单的调用了下 target 的对应方法,如果 target 还是个代理,就又回到了上面的 Plugin.invoke 方法了。这样就形成了拦截器的调用链推进。 public Object intercept(Invocation invocation) throws Throwable { //完成代理类本身的逻辑 ... //通过invocation.proceed()方法完成调用链的推进 return invocation.proceed(); }

    四、总结

    我们假设在 MyBatis 配置了一个插件,在运行时会发生什么? 1)  所有可能被拦截的处理类都会生成一个代理

    2) 处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法

    3) 执行插接中的拦截方法后,推进目标的执行

    如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

    因此,在编写插件时需注意以下几个原则:

    1) 不编写不必要的插件;

    2) 实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否者直接返回目标本省,这样可以减少目标被代理的次数。

    转载请注明原文地址: https://ju.6miu.com/read-1296972.html
    最新回复(0)