插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来 扩展或改变框架原有的功能。 Mybatis 中也提供了插件的功能,虽然叫插件,但是实际上是通过 拦截器( Interceptor)实现的。在 MyBatis 的插件模块中涉及 责任链模式和 JDK 动态代理的知识, 下文会分析责任链模式和 JDK 动态代理在 MyBatis 插件模块中的实践。
责任链模式主要用来处理 ”客户端发出一个请求,有多个对象都有机会来处理这一个请求,但是客户端不知道究竟谁会来处理他的请求“ 这样的情况。也就是需要让请求者和接收者解耦,这样就可以动态地切换和组合接收者了。 注意,在责任链模式中,请求不一定会被处理,因为可能没有合适的处理者,请求在责任链中从头传递到尾,每个处理对象都判断不属于自己处理,最后请求就没有对象来处理。 责任链模式的结构图如下:
当有请求进入时,经过 HandlerOne 的 handlerRequest 方法,再把请求传递给 HandlerTwo,处理完再把请求传递给 HandlerThree,以此类推,形成一个链条。链条上每一个对象所承担的责任各不相同,这就是责任链模式。
(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 可能会被代理多次。如下图:
结果看到了,最后的 target 对象可能是被多次代理过的对象,也就是图中的 interceptorProxy1 对象。当 interceptorProxy1 执行 interceptor 方法时,内部会调用到 interceptorProxy2 对象的 interceptor 方法, interceptorProxy2 的 interceptor 方法内部继续往下调直到最终的 target 对象调用。 说到这,可以看出 target 对象先是匹配所有适合自己的 interceptor 对象,然后在所有匹配的 interceptor 对象中流转。这就可以理解成责任链模式的设计。
关于 MyBatis 中拦截器的作用点,拦截器定义,以及拦截器的责任链模式在这里做了一定程度的讲解,下篇文章会补充拦截器的代理对象是如何创建的,也就是 interceptor.plugin(target); 代理的内部逻辑实现。