mybatis 插件

插件的使用

1、在配置文件配置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 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方法。
image

再看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> 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 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, Set> 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> signatureMap = new HashMap<>(); for (Signature sig : sigs) { Set 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, Set> 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方法就是我们定义拦截器要实现的方法。

posted @ 2023-08-14 17:03  朋羽  阅读(131)  评论(0)    收藏  举报