首先我们先来通过一个例子来看看如何使用插件。 1、首先建立一个MyPlugin实现接口Interceptor,然后重写其中的三个方法(注意,这里必须要实现Interceptor接口,否则无法被拦截)。
package com.lonelyWolf.mybatis.plugin;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;
@Intercepts({@Signature(type = Executor.class,method = “query”,args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class MyPlugin implements Interceptor { /**
- 这个方法会直接覆盖原有方法
- @param invocation
- @return
- @throws Throwable
*/ @Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println(“成功拦截了Executor的query方法,在这里我可以做点什么”);
return invocation.proceed();//调用原方法 }
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);//把被拦截对象生成一个代理对象 }
@Override
public void setProperties(Properties properties) {//可以自定义一些属性 System.out.println(“自定义属性:userName->” + properties.getProperty(“userName”));
}
}
@Intercepts是声明当前类是一个拦截器,后面的@Signature是标识需要拦截的方法签名,通过以下三个参数来确定
-
type:被拦截的类名。
-
method:被拦截的方法名
-
args:标注方法的参数类型
2、我们还需要在mybatis-config中配置好插件。
这里如果配置了property属性,那么我们可以在setProperties获取到。
完成以上两步,我们就完成了一个插件的配置了,接下来我们运行一下:
可以看到,setProperties方法在加载配置文件阶段就会被执行了。
MyBatis插件实现原理
接下来让我们分析一下从插件的加载到初始化到运行整个过程的实现原理。
插件的加载
既然插件需要在配置文件中进行配置,那么肯定就需要进行解析,我们看看插件式如何被解析的。我们进入XMLConfigBuilder类看看
解析出来之后会将插件存入InterceptorChain对象的list属性
看到InterceptorChain我们是不是可以联想到,MyBatis的插件就是通过责任链模式实现的。
插件如何进行拦截
既然插件类已经被加载到配置文件了,那么接下来就有一个问题了,插件类何时会被拦截我们需要拦截的对象呢?
其实插件的拦截是和对象有关的,不同的对象进行拦截的时间也会不一致,接下来我们就逐一分析一下。
拦截Executor对象
我们知道,SqlSession对象是通过openSession()方法返回的,而Executor又是属于SqlSession内部对象,所以让我们跟随openSession方法去看一下Executor对象的初始化过程。
可以看到,当初始化完成Executor之后,会调用interceptorChain的pluginAll方法,pluginAll方法本身非常简单,就是把我们存到list中的插件进行循环,并调用Interceptor对象的plugin方法:
再次点击进去:
到这里我们是不是发现很熟悉,没错,这就是我们上面示例中重写的方法,而plugin方法是接口中的一个默认方法。
这个方法是关键,我们进去看看:
可以看到这个方法的逻辑也很简单,但是需要注意的是MyBatis插件是通过JDK动态代理来实现的,而JDK动态代理的条件就是被代理对象必须要有接口,这一点和Spring中不太一样,Spring中是如果有接口就采用JDK动态代理,没有接口就是用CGLIB动态代理。
正因为MyBatis的插件只使用了JDK动态代理,所以我们上面才强调了一定要实现Interceptor接口。 而代理之后汇之星Plugin的invoke方法,我们最后再来看看invoke方法:
而最终执行的intercept方法,就是我们上面示例中重写的方法。
其他对象插件解析
接下来我们再看看StatementHandler,StatementHandler是在Executor中的doQuery方法创建的,其实这个原理就是一样的了,找到初始化StatementHandler对象的方法:
进去之后里面执行的也是pluginAll方法:
其他两个对象就不在举例了,其实搜一下全局就很明显了: PS:
四个对象初始化的时候都会调用pluginAll来进行判定是否有被代理。
插件执行流程
下面就是实现了插件之后的执行时序图:
假如一个对象被代理很多次
一个对象是否可以被多个代理对象进行代理?也就是说同一个对象的同一个方法是否可以被多个拦截器进行拦截?
答案是肯定的,因为被代理对象是被加入到list,所以我们配置在最前面的拦截器最先被代理,但是执行的时候却是最外层的先执行。 具体点: 假如依次定义了三个插件:插件A,插件B和插件C。 那么List中就会按顺序存储:插件A,插件B和插件C,而解析的时候是遍历list,所以解析的时候也是按照:插件A,插件B和插件C的顺序,但是执行的时候就要反过来了,执行的时候是按照:插件C,插件B和插件A的顺序进行执行。
PageHelper插件的使用
上面我们了解了在MyBatis中的插件是如何定义以及MyBatis中是如何处理插件的,接下来我们就以经典分页插件PageHelper为例来进一步加深理解。
首先我们看看PageHelper的用法:
package com.lonelyWolf.mybatis;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lonelyWolf.mybatis.mapper.UserMapper;
import com.lonelyWolf.mybatis.model.LwUser;
import org.apache.ibatis.executor.result.DefaultResultHandler;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
andler;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;