在mybatis插件机制原理剖析一文章中,我们简单的剖析了mybatis插件的实现的基本原理,但是还是不够完善,比如:如果有多个拦截器要怎么处理,能不能只要实现了MyInterceptor接口,就自动给包装成代理对象,一个拦截器能不能多个方法进行拦截等等....
现在我们就基于上篇文章继续优化改进:
-
首先,一个拦截器能多个方法进行拦截,将MySignature注解进行改造, 让它可以支持多个方法如下:
/**
* 支持多个方法的注解,注意 @Target({})
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface MySignature2 {
// class
Class<?> type();
String method();
Class<?>[] args();
}
-
第二步:若想在拦截器使用,在定义一个包装MySignature2的注解,我们定义成如下:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName MyIntercepts
* @Description:
* @author: 轩逸
* @date: 2020/9/22 19:54
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyIntercepts2 {
/**
* @Title: 支持多个方法签名
* @Description:
* @return
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 19:55
*/
MySignature2[] value();
}
-
第三步,改造我们的拦截器接口,使得自动实现该接口的包装成一个代理对象,如下
import com.xfyang.plugin.MyInvcation;
/**
* @ClassName MyInterceptor2
* @Description:
* @author: 轩逸
* @date: 2020/9/22 20:05
*/
public interface MyInterceptor2 {
/**
* @return
* @Title:
* @Description:拦截
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 9:53
*/
Object intercept(MyInvcation myInvcation) throws Exception;
/**
* @Title:
* @Description:默认把当前的拦截器包装成给代理对象返回
* @return
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 20:09
*/
default Object plugin(Object target) {
return MyPlugin2.wrap(target, this);
}
}
-
第四步,改造我们的MyPlugin,使其能够支持多个方法的拦截(也即一个拦截器可以多个方法进行拦截)
import com.xfyang.plugin.MyInvcation;
import org.apache.ibatis.reflection.ExceptionUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @ClassName MyPlugin2
* @Description:
* @author: 轩逸
* @date: 2020/9/22 10:21
*/
public class MyPlugin2 implements InvocationHandler {
private final Object target;
private final MyInterceptor2 myInterceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
public MyPlugin2(Object target, MyInterceptor2 myInterceptor, Map<Class<?>, Set<Method>>signatureMap) {
this.target = target;
this.myInterceptor = myInterceptor;
this.signatureMap = signatureMap;
}
/**
* @Title:
* @Description: 包装拦截器符合当前调用方法的代理对象
* @return
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 20:13
*/
public static Object wrap(Object target, MyInterceptor2 myInterceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(myInterceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new MyPlugin2(target, myInterceptor, signatureMap));
}
return target;
}
/**
* @Title:
* @Description:执行代理的拦截器方法,并执行目标的真实方法
* @return
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 20:14
*/
@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 myInterceptor.intercept(new MyInvcation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
/**
* @Title:
* @Description:获取当前拦截器的方法签名
* @return
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 20:15
*/
private static Map<Class<?>, Set<Method>> getSignatureMap(MyInterceptor2 interceptor) {
MyIntercepts2 myIntercepts2Annotation = interceptor.getClass().getAnnotation(MyIntercepts2.class);
if (myIntercepts2Annotation == null) {
throw new RuntimeException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
MySignature2[] sigs = myIntercepts2Annotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (MySignature2 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 RuntimeException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
/**
* @Title:
* @Description:获取符合当前执行方法的的接口
* @return
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 20:15
*/
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()]);
}
}
-
第五步,定义一个拦截器连MyInterceptorChain2,吧对的拦截器依次加入进行
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @ClassName MyInterceptorChain
* @Description:
* @author: 轩逸
* @date: 2020/9/22 20:30
*/
public class MyInterceptorChain2 {
// 拦截器集合
private final List<MyInterceptor2> interceptors = new ArrayList<>();
// 依次加入到链条中
public Object pluginAll(Object target) {
for (MyInterceptor2 interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
// 加入拦截器
public void addInterceptor(MyInterceptor2 interceptor) {
interceptors.add(interceptor);
}
// 提供给外面一个无法修改的拦截器集合
public List<MyInterceptor2> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
-
第六步,定义一个拦截器SQLTimeConsumeInterceptor2.
import com.xfyang.plugin.MyInvcation;
import com.xfyang.plugin.QueryService;
/**
* @ClassName SQLTimeConsumeInterceptor2
* @Description:
* @author: 轩逸
* @date: 2020/9/22 20:25
*/
@MyIntercepts2({
@MySignature2(type = QueryService.class, method = "queryByName", args = {String.class}),
@MySignature2(type = QueryService.class, method = "updateByName", args = {String.class}),
})
public class SQLTimeConsumeInterceptor2 implements MyInterceptor2 {
/**
* @param myInvcation
* @return
* @Title:
* @Description:拦截
* @version V1.0
* @author 轩逸
* @Date 2020-09-22 9:53
*/
@Override
public Object intercept(MyInvcation myInvcation) throws Exception {
long beginTime = System.currentTimeMillis();
Object object = myInvcation.process();
System.out.println("SQLTimeConsumeInterceptor2-->当前方法名:" + myInvcation.getMethod().getName() + ",SQL耗时:" + (System.currentTimeMillis() - beginTime));
return object;
}
}
-
最后一步,执行测试,首先验证一个拦截器针对一个接口中的2个方法是否OK?
import com.xfyang.plugin.QueryService;
import com.xfyang.plugin.QueryServiceImpl;
/**
* @ClassName MyPlugin2Test
* @Description:
* @author: 轩逸
* @date: 2020/9/22 20:25
*/
public class MyPlugin2Test {
public static void main(String[] args) {
//通过代理答应执行耗时:
QueryService queryService = new QueryServiceImpl();
//包装代理
QueryService proxyQueryService = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
System.out.println(proxyQueryService.queryByName("12345"));
//包装代理
QueryService proxyQueryService2 = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
System.out.println(proxyQueryService2.updateByName("12345"));
}
}
控制台打印接口如下,符合预期,一个拦截器针对一个接口类中的2个方法都生效了。 ![]()
其次,验证多个拦截器针对同一个接口中的方法进行层层代理拦截。
package com.xfyang.plugin2;
import com.xfyang.plugin.QueryService;
import com.xfyang.plugin.QueryServiceImpl;
/**
* @ClassName MyPlugin2Test
* @Description:
* @author: 轩逸
* @date: 2020/9/22 20:25
*/
public class MyPlugin2Test {
public static void main(String[] args) {
//testProxyQueryByOneInterceptor()
//创建目标对象
QueryService queryService = new QueryServiceImpl();
//添加到拦截器连中
MyInterceptorChain2 myInterceptorChain2 = new MyInterceptorChain2();
myInterceptorChain2.addInterceptor(new SQLTimeConsumeInterceptor2());
myInterceptorChain2.addInterceptor(new SQLTimeConsumeInterceptor3());
//依次创建拦截器的代理对象(层层代理)
QueryService proxyQueryService = (QueryService) myInterceptorChain2.pluginAll(queryService);
System.out.println(proxyQueryService.queryByName("xyfang"));
}
public void testProxyQueryByOneInterceptor() {
//通过代理答应执行耗时:
QueryService queryService = new QueryServiceImpl();
//包装代理
QueryService proxyQueryService = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
System.out.println(proxyQueryService.queryByName("12345"));
//包装代理
QueryService proxyQueryService2 = (QueryService) MyPlugin2.wrap(queryService, new SQLTimeConsumeInterceptor2());
System.out.println(proxyQueryService2.updateByName("12345"));
}
}
验证结果,符合预期:
![]()
在目前实现类中加入:
![]()
![]()
![]()
执行如下:
![]()
可能有人对结果,不是很理解:
SQLTimeConsumeInterceptor3-->开始执行
SQLTimeConsumeInterceptor2-->开始执行
执行方法: queryByName xyfang
SQLTimeConsumeInterceptor2-->当前方法名:queryByName,SQL耗时:1000
SQLTimeConsumeInterceptor3-->当前方法名:queryByName,SQL耗时:1000
查询名称:xyfang
那是因为:SQLTimeConsumeInterceptor3 是对SQLTimeConsumeInterceptor2的代理,SQLTimeConsumeInterceptor2在对QueryService进行代理,从而执行目标类QueryServiceImpl中的queryByName方法, 所以 SQLTimeConsumeInterceptor3-->开始执行是最开始执行的,返回的时候就SQLTimeConsumeInterceptor3-->当前方法名:queryByName,SQL耗时:1000最后返回打印了。
下一篇,我将画个时序图加深下印象,加深了解,就能理解为啥打印是这样的,请看下一篇。
有需要转载的同学,请注明来源、作者等,谢谢!