Mybatis框架(8)---Mybatis插件原理(代理+责任链)

Mybatis 插件原理

在实际开发过程中, 我们经常使用的 Mybaits 插件就是分页插件了,通过分页插件我们可以在不用写count 语句和 limit的情况下就可以获取分页后的数据, 给我们开发带来很大

的便利。除了分页,插件使用场景主要还有更新数据库的通用字段,分库分表,加解密等的处理。

这篇博客主要讲 Mybatis 插件原理, 下一篇博客会设计一个 Mybatis 插件实现的功能就是每当新增数据的时候不用数据库自增 ID 而是通过该插件生成雪花 ID, 作为每条数据的主键。

一、JDK 动态代理 + 责任链设计模式

Mybatis 的插件其实就是个拦截器功能。它利用JDK动态代理和责任链设计模式的综合运用。采用责任链模式,通过动态代理组织多个拦截器, 通过这些拦截器你可以做一些

你想做的事。所以在讲 Mybatis 拦截器之前我们先说说 JDK 动态代理 + 责任链设计模式。有关 JDK 动态代理的原理,可以参考我之前写的一篇博客:【java 设计模式】--- 代理模式

1、JDK 动态代理案例

public class MyProxy {
    /**
     * 一个接口
     */
    public interface HelloService{
        void sayHello();
    }
    /**
     * 目标类实现接口
     */
    static class HelloServiceImpl implements HelloService{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">sayHello</span><span class="hljs-params">()</span> {
        System.out.println(<span class="hljs-string">"sayHello......"</span>);
    }
}
<span class="hljs-comment">/**
 * 自定义代理类需要实现InvocationHandler接口
 */</span>
<span class="hljs-keyword">static</span>  <span class="hljs-keyword">class</span> <span class="hljs-title class_">HWInvocationHandler</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">InvocationHandler</span> {
    <span class="hljs-comment">/**
     * 目标对象
     */</span>
    <span class="hljs-keyword">private</span> Object target;

    <span class="hljs-keyword">public</span> <span class="hljs-title function_">HWInvocationHandler</span><span class="hljs-params">(Object target)</span>{
        <span class="hljs-built_in">this</span>.target = target;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable {
        System.out.println(<span class="hljs-string">"------插入前置通知代码-------------"</span>);
        <span class="hljs-comment">//执行相应的目标方法</span>
        <span class="hljs-type">Object</span> <span class="hljs-variable">rs</span> <span class="hljs-operator">=</span> method.invoke(target,args);
        System.out.println(<span class="hljs-string">"------插入后置处理代码-------------"</span>);
        <span class="hljs-keyword">return</span> rs;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">wrap</span><span class="hljs-params">(Object target)</span> {
        <span class="hljs-keyword">return</span> Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),<span class="hljs-keyword">new</span> <span class="hljs-title class_">HWInvocationHandler</span>(target));
    }
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span>  {
    <span class="hljs-type">HelloService</span> <span class="hljs-variable">proxyService</span> <span class="hljs-operator">=</span> (HelloService) HWInvocationHandler.wrap(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HelloServiceImpl</span>());
    proxyService.sayHello();
}

}

运行结果

------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------

2、优化

上面代理的功能是实现了, 但是有个很明显的缺陷,就是HWInvocationHandler是动态代理类,也可以理解成是个工具类,我们不可能会把业务代码写到写到到invoke方法里,

不符合面向对象的思想,可以抽象一下处理。可以设计一个Interceptor接口,需要做什么拦截处理实现接口就行了。

public interface Interceptor {
    /**
     * 具体拦截处理
     */
    void intercept();
}

intercept() 方法就可以处理各种前期准备了

public class LogInterceptor implements Interceptor {
    @Override
    public void intercept() {
        System.out.println("------ 插入前置通知代码 -------------");
    }
}

public class TransactionInterceptor implements Interceptor {
@Override
public void intercept() {
System.out.println("------ 插入后置处理代码 -------------");
}
}

代理对象也做一下修改

public class HWInvocationHandler implements InvocationHandler {
<span class="hljs-keyword">private</span> Object target;

<span class="hljs-keyword">private</span> List&lt;Interceptor&gt; interceptorList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();

<span class="hljs-keyword">public</span> <span class="hljs-title function_">TargetProxy</span><span class="hljs-params">(Object target,List&lt;Interceptor&gt; interceptorList)</span> {
    <span class="hljs-built_in">this</span>.target = target;
    <span class="hljs-built_in">this</span>.interceptorList = interceptorList;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable {
   <span class="hljs-comment">//处理多个拦截器</span>
    <span class="hljs-keyword">for</span> (Interceptor interceptor : interceptorList) {
        interceptor.intercept();
    }
    <span class="hljs-keyword">return</span> method.invoke(target, args);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">wrap</span><span class="hljs-params">(Object target,List&lt;Interceptor&gt; interceptorList)</span> {
    <span class="hljs-type">HWInvocationHandler</span> <span class="hljs-variable">targetProxy</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HWInvocationHandler</span>(target, interceptorList);
    <span class="hljs-keyword">return</span> Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                  target.getClass().getInterfaces(),targetProxy);
}

}

现在可以根据需要动态的添加拦截器了,在每次执行业务代码 sayHello() 之前都会拦截,看起来高级一点,来测试一下

public class Test {
    public static void main(String[] args) {
        List<Interceptor> interceptorList = new ArrayList<>();
        interceptorList.add(new LogInterceptor());
        interceptorList.add(new TransactionInterceptor());
    <span class="hljs-type">HelloService</span> <span class="hljs-variable">target</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HelloServiceImpl</span>();
    <span class="hljs-type">Target</span> <span class="hljs-variable">targetProxy</span> <span class="hljs-operator">=</span> (Target) TargetProxy.wrap(target,interceptorList);
    targetProxy.sayHello();
}

}

运行结果

------ 插入前置通知代码 -------------
------ 插入后置处理代码 -------------
sayHello......

3、再优化

上面的动态代理确实可以把代理类中的业务逻辑抽离出来,但是我们注意到,只有前置代理,无法做到前后代理,所以还需要在优化下。所以需要做更一步的抽象,

把拦截对象信息进行封装,作为拦截器拦截方法的参数,把拦截目标对象真正的执行方法放到 Interceptor 中完成,这样就可以实现前后拦截,并且还能对拦截

对象的参数等做修改。设计一个Invocation 对象

public class Invocation {
<span class="hljs-comment">/**
 * 目标对象
 */</span>
<span class="hljs-keyword">private</span> Object target;
<span class="hljs-comment">/**
 * 执行的方法
 */</span>
<span class="hljs-keyword">private</span> Method method;
<span class="hljs-comment">/**
 * 方法的参数
 */</span>
<span class="hljs-keyword">private</span> Object[] args;

<span class="hljs-comment">//省略getset</span>
<span class="hljs-keyword">public</span> <span class="hljs-title function_">Invocation</span><span class="hljs-params">(Object target, Method method, Object[] args)</span> {
    <span class="hljs-built_in">this</span>.target = target;
    <span class="hljs-built_in">this</span>.method = method;
    <span class="hljs-built_in">this</span>.args = args;
}

<span class="hljs-comment">/**
 * 执行目标对象的方法
 */</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">process</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception{
   <span class="hljs-keyword">return</span> method.invoke(target,args);
}

}

Interceptor 拦截接口做修改

public interface Interceptor {
    /**
     * 具体拦截处理
     */
    Object intercept(Invocation invocation) throws Exception;
}

Interceptor 实现类

public class TransactionInterceptor implements Interceptor {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">intercept</span><span class="hljs-params">(Invocation invocation)</span> <span class="hljs-keyword">throws</span> Exception{
    System.out.println(<span class="hljs-string">"------插入前置通知代码-------------"</span>);
    <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> invocation.process();
    System.out.println(<span class="hljs-string">"------插入后置处理代码-------------"</span>);
    <span class="hljs-keyword">return</span> result;
}

}

Invocation 类就是被代理对象的封装,也就是要拦截的真正对象。HWInvocationHandler 修改如下:

public class HWInvocationHandler implements InvocationHandler {
<span class="hljs-keyword">private</span> Object target;

<span class="hljs-keyword">private</span> Interceptor interceptor;

<span class="hljs-keyword">public</span> <span class="hljs-title function_">TargetProxy</span><span class="hljs-params">(Object target,Interceptor interceptor)</span> {
    <span class="hljs-built_in">this</span>.target = target;
    <span class="hljs-built_in">this</span>.interceptor = interceptor;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable {
    <span class="hljs-type">Invocation</span> <span class="hljs-variable">invocation</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Invocation</span>(target,method,args);
    <span class="hljs-keyword">return</span> interceptor.intercept(invocation);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">wrap</span><span class="hljs-params">(Object target,Interceptor interceptor)</span> {
    <span class="hljs-type">HWInvocationHandler</span> <span class="hljs-variable">targetProxy</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HWInvocationHandler</span>(target, interceptor);
    <span class="hljs-keyword">return</span> Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),targetProxy);
}

}

测试类

public class Test {
    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        Interceptor transactionInterceptor = new TransactionInterceptor();
        HelloService targetProxy = (Target) TargetProxy.wrap(target,transactionInterceptor);
        targetProxy.sayHello();
    }
}

运行结果

------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------

4、再再优化

上面这样就能实现前后拦截,并且拦截器能获取拦截对象信息。但是测试代码的这样调用看着很别扭,对应目标类来说,只需要了解对他插入了什么拦截就好。

再修改一下,在拦截器增加一个插入目标类的方法。

public interface Interceptor {
    /**
     * 具体拦截处理
     */
    Object intercept(Invocation invocation) throws Exception;
<span class="hljs-comment">/**
 *  插入目标类
 */</span>
Object <span class="hljs-title function_">plugin</span><span class="hljs-params">(Object target)</span>;

}

public class TransactionInterceptor implements Interceptor {

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">intercept</span><span class="hljs-params">(Invocation invocation)</span> <span class="hljs-keyword">throws</span> Exception{
    System.out.println(<span class="hljs-string">"------插入前置通知代码-------------"</span>);
    <span class="hljs-type">Object</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> invocation.process();
    System.out.println(<span class="hljs-string">"------插入后置处理代码-------------"</span>);
    <span class="hljs-keyword">return</span> result;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">plugin</span><span class="hljs-params">(Object target)</span> {
    <span class="hljs-keyword">return</span> TargetProxy.wrap(target,<span class="hljs-built_in">this</span>);
}

}

这样目标类仅仅需要在执行前,插入需要的拦截器就好了,测试代码:

public class Test {
    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        Interceptor transactionInterceptor = new TransactionInterceptor();
        // 把事务拦截器插入到目标类中
        target = (HelloService) transactionInterceptor.plugin(target);
        target.sayHello();
    }
}

运行结果

------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------

5、多个拦截器如何处理

到这里就差不多完成了,那我们再来思考如果要添加多个拦截器呢,怎么搞?

public class Test {
    public static void main(String[] args) {
        HelloService target = new HelloServiceImpl();
        Interceptor transactionInterceptor = new TransactionInterceptor();
        target = (HelloService) transactionInterceptor.plugin(target);
        LogInterceptor logInterceptor = new LogInterceptor();
        target = (HelloService)logInterceptor.plugin(target);
        target.sayHello();
    }
}

运行结果

------ 插入前置通知代码 -------------
------ 插入前置通知代码 -------------
sayHello......
------ 插入后置处理代码 -------------
------ 插入后置处理代码 -------------

6、责任链设计模式

其实上面已经实现的没问题了,只是还差那么一点点,添加多个拦截器的时候不太美观,让我们再次利用面向对象思想封装一下。我们设计一个InterceptorChain 拦截器链类

public class InterceptorChain {
<span class="hljs-keyword">private</span> List&lt;Interceptor&gt; interceptorList = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>&lt;&gt;();

<span class="hljs-comment">/**
 * 插入所有拦截器
 */</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">pluginAll</span><span class="hljs-params">(Object target)</span> {
    <span class="hljs-keyword">for</span> (Interceptor interceptor : interceptorList) {
        target = interceptor.plugin(target);
    }
    <span class="hljs-keyword">return</span> target;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">addInterceptor</span><span class="hljs-params">(Interceptor interceptor)</span> {
    interceptorList.add(interceptor);
}
<span class="hljs-comment">/**
 * 返回一个不可修改集合,只能通过addInterceptor方法添加
 * 这样控制权就在自己手里
 */</span>
<span class="hljs-keyword">public</span> List&lt;Interceptor&gt; <span class="hljs-title function_">getInterceptorList</span><span class="hljs-params">()</span> {
    <span class="hljs-keyword">return</span> Collections.unmodifiableList(interceptorList);
}

}

其实就是通过 pluginAll() 方法包一层把所有的拦截器插入到目标类去而已。测试代码:

public class Test {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
    <span class="hljs-type">HelloService</span> <span class="hljs-variable">target</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HelloServiceImpl</span>();
    <span class="hljs-type">Interceptor</span> <span class="hljs-variable">transactionInterceptor</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TransactionInterceptor</span>();
    <span class="hljs-type">LogInterceptor</span> <span class="hljs-variable">logInterceptor</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">LogInterceptor</span>();
    <span class="hljs-type">InterceptorChain</span> <span class="hljs-variable">interceptorChain</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InterceptorChain</span>();
    interceptorChain.addInterceptor(transactionInterceptor);
    interceptorChain.addInterceptor(logInterceptor);
    target = (Target) interceptorChain.pluginAll(target);
    target.sayHello();
}

}

这里展示的是JDK动态代理+责任链设计模式,那么 Mybatis 拦截器就是基于该组合进行开发。


二、Mybatis Plugin 插件概念

1、原理

Mybatis 的拦截器实现机制跟上面最后优化后的代码非常的相似。它也有个代理类Plugin( 就是上面的 HWInvocationHandler)这个类同样也会实现了InvocationHandler接口,

当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,, 就会执行 Plugin 的invoke方法,Plugin 在 invoke 方法中根据

@Intercepts的配置信息 (方法名, 参数等) 动态判断是否需要拦截该方法. 再然后使用需要拦截的方法 Method 封装成 Invocation, 并调用 Interceptor 的proceed方法。

这样我们就达到了拦截目标方法的结果。例如 Executor 的执行大概是这样的流程:

拦截器代理类对象->拦截器->目标方法
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke。

2、如何自定义拦截器?

1) Interceptor 接口

首先 Mybatis 官方早就想到我们开发会有这样的需求,所以开放了一个org.apacheibatis.plugin.Interceptor这样一个接口。这个接口就是和上面 Interceptor 性质是一样的

public interface Interceptor {
  // 当 plugin 函数返回代理,就可以对其中的方法进行拦截来调用 intercept 方法
  Object intercept(Invocation invocation) throws Throwable;
  //plugin 方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。
  Object plugin(Object target);
 // 在 Mybatis 配置文件中指定一些属性
  void setProperties(Properties properties);
}

2)自定义拦截器

这里的ExamplePlugin和上面的LogInterceptor和TransactionInterceptor性质是一样的

@Intercepts({@Signature( type= Executor.class,  method = "update", args ={MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}

3)、全局 xml 配置

最后如果你使用的是Mybatis.xml也就是 Mybatis 本身单独的配置,你可以需要在这里配置相应的拦截器名字等。

如果你使用的是 spring 管理的 Mybatis,那么你需要在Spring配置文件里面配置注册相应的拦截器。

这样一个自定义 mybatis 插件流程大致就是这样了。

3、Mybatis 四大接口

竟然 Mybatis 是对四大接口进行拦截的,那我们要先要知道 Mybatis 的四大接口对象 Executor, StatementHandle, ResultSetHandler, ParameterHandler

1.Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) MyBatis的执行器,用于执行增删改查操作;
2.ParameterHandler (getParameterObject, setParameters) 处理SQL的参数对象;
3.ResultSetHandler (handleResultSets, handleOutputParameters) 处理SQL的返回结果集;
4.StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理

上图 Mybatis 框架的整个执行过程。


三、Mybatis Plugin 插件源码

经过上面的分析,再去看 Mybastis Plugin 源码的时候就很轻松了。

这几个也就对应上面的几个,只不过添加了注解,来判断是否拦截指定方法。

1、拦截器链 InterceptorChain

public class InterceptorChain {

private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

public Object pluginAll(Object target) {
// 循环调用每个 Interceptor.plugin 方法
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}

public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}

这个就和我们上面实现的是一样的。定义了拦截器链

2、Configuration

通过初始化配置文件把所有的拦截器添加到拦截器链中。

public class Configuration {
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">final</span> <span class="hljs-type">InterceptorChain</span> <span class="hljs-variable">interceptorChain</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">InterceptorChain</span>();
<span class="hljs-comment">//创建参数处理器</span>

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 创建 ParameterHandler
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 插件在这里插入
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}

// 创建结果集处理器
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql)
{
// 创建 DefaultResultSetHandler
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 插件在这里插入
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

// 创建语句处理器
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建路由选择语句处理器
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 插件在这里插入
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}

// 产生执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
// 这句再做一下保护, 囧, 防止粗心大意的人将 defaultExecutorType 设成 null?
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 然后就是简单的 3 个分支,产生 3 种执行器 BatchExecutor/ReuseExecutor/SimpleExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果要求缓存,生成另一种 CachingExecutor(默认就是有缓存), 装饰者模式, 所以默认都是返回 CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 此处调用插件, 通过插件可以改变 Executor 行为
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}

从代码可以看出 Mybatis 在实例化 Executor、ParameterHandler、ResultSetHandler、StatementHandler 四大接口对象的时候调用interceptorChain.pluginAll() 方法插入

进去的。其实就是循环执行拦截器链所有的拦截器的 plugin()方法,mybatis 官方推荐的 plugin 方法是 Plugin.wrap() 方法, 这个类就是我们上面的 TargetProxy 类。

3、Plugin

这里的 Plugin 就是我们上面的自定义代理类 TargetProxy 类

public class Plugin implements InvocationHandler {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">wrap</span><span class="hljs-params">(Object target, Interceptor interceptor)</span> {
<span class="hljs-comment">//从拦截器的注解中获取拦截的类名和方法信息</span>
Map&lt;Class&lt;?&gt;, Set&lt;Method&gt;&gt; signatureMap = getSignatureMap(interceptor);
<span class="hljs-comment">//取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)</span>
Class&lt;?&gt; type = target.getClass();
<span class="hljs-comment">//取得接口</span>
Class&lt;?&gt;[] interfaces = getAllInterfaces(type, signatureMap);
<span class="hljs-comment">//产生代理,是Interceptor注解的接口的实现类才会产生代理</span>
<span class="hljs-keyword">if</span> (interfaces.length &gt; <span class="hljs-number">0</span>) {
  <span class="hljs-keyword">return</span> Proxy.newProxyInstance(
      type.getClassLoader(),
      interfaces,
      <span class="hljs-keyword">new</span> <span class="hljs-title class_">Plugin</span>(target, interceptor, signatureMap));
}
<span class="hljs-keyword">return</span> target;

}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 获取需要拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 是 Interceptor 实现类注解的方法才会拦截处理
if (methods != null && methods.contains(method)) {
// 调用 Interceptor.intercept,也即插入了我们自己的逻辑
return interceptor.intercept(new Invocation(target, method, args));
}
// 最后还是执行原来逻辑
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}

<span class="hljs-comment">//取得签名Map,就是获取Interceptor实现类上面的注解,要拦截的是那个类(Executor,ParameterHandler,   ResultSetHandler,StatementHandler)的那个方法</span>

private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
// 取 Intercepts 注解,例子可参见 ExamplePlugin.java
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
// 必须得有 Intercepts 注解,没有报错
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor" + interceptor.getClass().getName());
}
//value 是数组型,Signature 的数组
Signature[] sigs = interceptsAnnotation.value();
// 每个 class 里有多个 Method 需要被拦截, 所以这么定义
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on" + sig.type() + "named" + sig.method() + ". Cause:" + e, e);
}
}
return signatureMap;
}

<span class="hljs-comment">//取得接口</span>

private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
// 拦截其他的无效
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}

4、Interceptor 接口

public interface Interceptor {

// 拦截
Object intercept(Invocation invocation) throws Throwable;
// 插入
Object plugin(Object target);
// 设置属性 (扩展)
void setProperties(Properties properties);

}

思路 这么下来思路就很清晰了,我们通过实现 Interceptor 类实现自定义拦截器,然后把它放入 InterceptorChain(拦截器链) 中,然后通过 JDK 动态代理来实现依次拦截处理。


致谢

非常感谢一篇博客,它讲的循序渐进,让我不仅仅对 JDK 动态代理 + 责任链模式有更好的理解,而且在代码设计上也有很大的启发,确实受益很大。非常感谢!

Mybatis Plugin 插件(拦截器)原理分析


 我相信,无论今后的道路多么坎坷,只要抓住今天,迟早会在奋斗中尝到人生的甘甜。抓住人生中的一分一秒,胜过虚度中的一月一年!(6)