谈谈 MyBatis 的插件化设计

2.什么是插件

插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来 扩展或改变框架原有的功能。 Mybatis 中也提供了插件的功能,虽然叫插件,但是实际上是通过 拦截器( Interceptor)实现的。在 MyBatis 的插件模块中涉及 责任链模式和 JDK 动态代理的知识, 下文会分析责任链模式和 JDK 动态代理在 MyBatis 插件模块中的实践。

3.责任链模式介绍

责任链模式主要用来处理 ”客户端发出一个请求,有多个对象都有机会来处理这一个请求,但是客户端不知道究竟谁会来处理他的请求“ 这样的情况。也就是需要让请求者和接收者解耦,这样就可以动态地切换和组合接收者了。 注意,在责任链模式中,请求不一定会被处理,因为可能没有合适的处理者,请求在责任链中从头传递到尾,每个处理对象都判断不属于自己处理,最后请求就没有对象来处理。 责任链模式的结构图如下:

谈谈 MyBatis 的插件化设计

当有请求进入时,经过 HandlerOne 的 handlerRequest 方法,再把请求传递给 HandlerTwo,处理完再把请求传递给 HandlerThree,以此类推,形成一个链条。链条上每一个对象所承担的责任各不相同,这就是责任链模式。 

4.MyBatis 中的拦截器 

(1)拦截器的作用点

MyBatis允许用户使用自定义拦截器对 SQL语句执行过程中的某一点进行拦截。 默认情况 下, MyBatis 允许拦截器拦截 Executor 的方法、 ParameterHandler 的方法、 ResultSetHandler 的 方法 以及 StatementHandler 的方法。这里说明一下,阅读过 MyBatis 源码的同学应该都知道,这里的 Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler 都是 MyBatis 操作 SQL 过程中的必备的核心层,而我们的拦截器也就是在这些接口的实现类的方法执行中起作用的。 具体可拦截的方法如下 : 

Executor:

update()、query()、flushStatements()、commit()、rollback() 、 getTransaction()、 close()、 isClosed()方法 。

ParameterHandler :

 getParameterObject()、 setParameters()方法 。 

ResultSetHandler:

handleResultSets()、 handleOutputParameters()方法 。

StatementHandler: 

prepare()、 parameterize()、 batch()、 update()、 query()方法。

(2)拦截器定义

public interface Interceptor { 

    //执行拦截器方法 
    Object intercept(Invocation invocation) throws Throwable; 

    // 创建interceptor对象,一般用mybatis提供的Plugin.wrap(...)方法 
    // 该方法内部会判断,当前对象是否匹配当前interceptor,匹配则创建代理对象,不匹配则返回当前对象。 
    Object plugin(Object target); 

    // mybatis加载初始化时,会根据xml或注解的配置,将拦截器中的配置值取出,调用这个方法做初始化操作
    void setProperties(Properties properties); 

}复制代码

我们自定义的拦截器需要实现这个接口,并在 MyBatis 中做好配置,即可生效。  

(3)拦截器链

public class InterceptorChain { 

    // 所有已经配置的拦截器 
    private final List<Interceptor> interceptors = new ArrayList<>(); 

    public Object pluginAll(Object target) { 
        for (Interceptor interceptor : interceptors) { 
            target = interceptor.plugin(target); 
        } 
        return target; 
    } 

    // mybatis加载初始化时,会根据xml或注解的配置,创建拦截器,调用这个方法存入字段 interceptors 中 
    public void addInterceptor(Interceptor interceptor) { 
        interceptors.add(interceptor);
    }

    // 获取所有的拦截器 
    public List<Interceptor> getInterceptors() { 
        return Collections.unmodifiableList(interceptors); 
    } 

} 复制代码

MyBatis 在启动加载配置文件时将已配置的所有拦截器对象(包括自定义的拦截器对象)都放在 InterceptorChain 类对象的字段 interceptors 中供后续操作使用。 在 InterceptorChain 的 pluginAll 方法中,循环 interceptors,调用 interceptor.plugin(target) 方法,这个 plugin 方法内部会判断这个 target 对象是否匹配当前当前的 interceptor 对象,如果匹配则用 JDK 动态代理创建 target 对象的代理对象,否则返回未被代理的 target 对象。这里是循环 interceptors,所以每个 interceptor 都会去执行一次 plugin 方法,也就是上个 interceptor.plugin(target) 调用的返回值是下一个 interceptor.plugin(target) 的入参。最后结果就是 target 可能会被代理多次。如下图: 

谈谈 MyBatis 的插件化设计

结果看到了,最后的 target 对象可能是被多次代理过的对象,也就是图中的 interceptorProxy1 对象。当 interceptorProxy1 执行 interceptor 方法时,内部会调用到 interceptorProxy2 对象的 interceptor 方法, interceptorProxy2 的 interceptor 方法内部继续往下调直到最终的 target 对象调用。 说到这,可以看出 target 对象先是匹配所有适合自己的 interceptor 对象,然后在所有匹配的 interceptor 对象中流转。这就可以理解成责任链模式的设计。

5.总结

关于 MyBatis 中拦截器的作用点,拦截器定义,以及拦截器的责任链模式在这里做了一定程度的讲解,下篇文章会补充拦截器的代理对象是如何创建的,也就是 interceptor.plugin(target); 代理的内部逻辑实现。

原文 

https://juejin.im/post/5cb6147f6fb9a0685255369b

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 谈谈 MyBatis 的插件化设计

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址