今天我们来讲下关于MyBatis的插件功能。为什么会有这个功能呢? 我们结合我们可能接触过的知识点类比可能能更好的理解。
在我看来,MyBatis的插件功能可以和Spring的AOP进行比较,主要功能都是为了扩展,当然功能没有AOP那么强大,也不需要。它们的相同点是可以在指定位置来进行业务功能的增强实现,不同点是AOP更加强大丰富。
比较完了,我们直接来进入实例去了解:
1. MyBatis插件实例
这里我还是以一个例子来抛砖引玉,我们进入到Test中的一个测试方法:
@Test
void mapPluginShouldInterceptGet() {
Map map = new HashMap();
map = (Map) new AlwaysMapPlugin().plugin(map);
assertEquals("Always", map.get("Anything"));
}
@Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
public static class AlwaysMapPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
return "Always";
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
这里有很多我们目前还没接触过的,我们一个个来分析。
1.1 接口Interceptor解析
public interface Interceptor {
/**
* 执行插件内容
*/
Object intercept(Invocation invocation) throws Throwable;
/**
* 封装
*/
Object plugin(Object target);
/**
* 参数赋值
*/
void setProperties(Properties properties);
}
大致熟悉有这样三个方法之后,我们来继续看他使用的注解有 @Intercepts、 @Signature具体功能我们根据之前的test方法和这个类来进行猜想一下,之后再来做分析,下面我们继续来看我们这唯一调用了一个MyBatis方法Plugin.wrap:
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
我们重点来看getSignatureMap方法:
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
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[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
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;
}
这里就可以看出我们之前 @Intercepts、 @Signature的作用,主要功能就是获取注解的信息,解析出来我们需要代理插件对应的方法,代码逻辑也比较清晰.下面一步是获取对应的接口,我们来看
getAllInterfaces:
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<?>[interfaces.size()]);
}
这里的代码逻辑都比较简单,获取我们class类对应的所有方法,包括他的父类,然后是否匹配我们注解对应的方法,有就添加。最后如果得到的接口不为空,则进行包装代理,代理方式是java的代理,这个我们不做展开,只要知道我们的Plugin实现了InvocationHandler接口:
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
@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)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
整体的流程就是这样,结合进MyBatis,无非是代理的方法更换。不过重点要记住的是,MyBatis只支持在某几个点来进行代理,我们来看下官网:
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
2. 今日总结
我们今天来了解了关于MyBatis插件的源码,相对也比较简单。到这里,我们整体的MyBatis内容源码就讲的差不多了,收货还是有的,所以我要继续坚持下去~~~~