mybatis 插件
插件的使用
1、在配置文件配置plugins
<plugins>
<plugin interceptor="com.test.plugin.MyBatisInterceptor"></plugin>
...
</plugins>
2、拦截器开发
实现Interceptor接口,在对应的拦截器类上配置注解,指定拦截方法
@Intercepts(
@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})
)
public class MyBatisInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//TODO 拦截方法前处理
//执行被代理方法
Object obj = invocation.proceed();
//TODO 拦截方法后处理
return obj;
}
}
Intercepts注解可以配置多个Signature注解,有以下三个配置项:
type:指定拦截类型
method:指定拦截方法
args: 拦截方法参数。
这三个参数就能完全确定一个方法。比如下面的配置。
如上面的拦截器就是要拦截Executor.query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler);方法。即该方法会被代理,走我们上面定义的拦截器的intercept方法。
mybatis官方文档也提到一般建议在下列方法进行插件拦截
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
这四个地方包含了sql执行的整个过程。
源码分析
配置解析
XMLConfigBuilder.pluginElement()会解析配置的plugins信息。将配置的插件信息存放到InterceptorChain类的interceptors中。
InterceptorChain 类有两个主要方法。addInterceptor解析配置时候使用。pluginAll方法在Configuration对象创建对应的sql处理类时候会调用改方法。
public class InterceptorChain {
//保存所有的拦截器配置
private final List<Interceptor> interceptors;
//创建executor或handler时候调用
public Object pluginAll(Object target) {
//验证所有的intercepter和当前要创建的对象是否匹配
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//解析plugins配置时候调用
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
}
代理创建
在Configuration创建处理对象时候会调用pluginAll方法。下面四处会调用pluginAll方法。
再看plugin方法。interceptor的plugin在interceptor接口里有默认实现。这也是jdk8的新特性。可以在接口里指定默认实现
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
下面来看Plugin类,这个类实现了InvocationHandler接口。不用说这里实现就是动态代理了。
public class Plugin implements InvocationHandler {
public static Object wrap(Object target, Interceptor interceptor) {
//解析当前interceptor所有配置的Intercepts注解,拦截的所有方法
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//如果拦截器配置的拦截方法存在与当前target,那么当前对象需要被代理,否则不需要
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//动态代理方法被调用时返回
@Override
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));
}
//不是配置的拦截器方法,走原方法。
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
//解析拦截器上配置的所有Signature
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//获取Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
//获取所有的Signature注解
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
try {
//解析Signature对应的method
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;
}
//查看当前要创建的handler或executor与拦截器相匹配的方法
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
}
}
代理链
一个方法可以配置多个拦截器。这是怎么实现的呢。
在pluginAll创建代理对象的时候我们看到是把所有的interceptor拿出来,然后循环。如果有多个这一步
target = interceptor.plugin(target); 赋值target = 新创建的target。这里就会创建多个target是个链表
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
然后在调用时候intercept方法会有一个Invocation对象。一般会调用其proceed方法,里面实现只是调用
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
这里的target已经是动态代理被代理的target了。如果target还是个代理对象则会继续下去。知道最后一个真实对象方法被调用。
总结
mybatis在执行语句时候会通过Configuration对象创建来创建对应的exexutor或handler。创建完后会调用InterceptorChain.pluginAll方法来判断该方法是否配置有对应的拦截器,判断依据是配置的@Intercepts注解里签名方法在当前target对象存在。如果有就需要为该对象创建一个代理对象(handler是Plugin对象),否则返回原对象。代理对象在执行被拦截方法首先调用Plugin.invoke方法,会触发对应拦截器的intercept方法。intercept方法就是我们定义拦截器要实现的方法。