背景
最近在读mybatis源码,发现在使用插件的时候,mybatis拦截器用了动态代理模式,一开始不是很理解,就准备重新温习一下代理模式
基本概念
- 动态代理:Proxy+InvocationHandler
- Jdk的动态代理由Proxy这个类来生成,它有三个参数:
- ClassLoader loader,:指定当前目标对象使用类加载器,获取加载器的方法是固定的
- Class<?>[] interfaces,:目标对象实现的接口的类型,使用泛型方式确认类型
- InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
代码实现
//创建一个汽车接口
public interface CarService {
void start();
}
//汽车实现类
public class CarServiceImpl implements CarService {
@Override
public void start(){
System.out.println("汽车启动");
}
}
//代理工厂
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkBlogProxyFactory {
private Object target;
public JdkBlogProxyFactory(Object target) {
this.target = target;
}
public Object newInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new handler());
}
private class handler implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开锁");
//执行被代理对象的method方法
Object o = method.invoke(target,args);
System.out.println("加速");
return o;
}
}
}
public class DynamicProxyTest {
public static void main(String[] args) throws IOException {
CarService carService = new CarServiceImpl() ;
CarService proxy = (CarService) new JdkBlogProxyFactory(carService).newInstance();
proxy.start();
//打印代理对象$Proxy0,为什么jdk动态代理只能代理接口,因为代理对象继承proxy,只能实现被代理对象了
String proxyName = "com.example.demo.proxy.$Proxy0";
Class[] interfaces = new Class[]{CarService.class};
int accessFlags = Modifier.PUBLIC;
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// 将字节数组写出到磁盘
File file = new File("E:$Proxy0.class");
OutputStream outputStream = new FileOutputStream(file);
outputStream.write(proxyClassFile);
}
}
动态代理就是把被代理对象通过Proxy生成代理对象,代理对象执行方法就会执行事件处理器InvocationHandler的invoke方法,那么这个流程是怎么实现的呢?
底层实现原理
动态代理产生的动态对象我们将它通过输出流输出到本地,然后通过idea反编译看一下到底里面是什么
public class $Proxy0 extends Proxy implements CarService {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void start() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
我这里就展示主要的部分,到时候你们可以自己生成看完整版的,从这里我们可以看到,我们真正的代理对象是$Proxy0类,它继承了Proxy,实现了CarService,从这里我们可以看出来为什么JDK动态代理只能代理接口类,因为Java不支持双继承。程序在执行start方法的时候,就执行了InvocationHandler里面的invoke方法,到这里我们的底层流程就理顺了。
Mybatis动态代理说明
当我们熟悉了上面的动态代理实现,再来看mybatis的动态代理实现插件的功能就简单的多了,这里简单的说明一下。我们从InterceptorChain这个拦截器链说起
//这里会将我们注册的插件都遍历一边来生成代理对象,这里分别代理了四种对象,分别是ParameterHandler、ResultSetHandler、StatementHandler、Executor,所以我们就可以在这四个地方分别执行我们的插件功能。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//我们继承mybatis拦截器接口,然后再plugin方法里执行Plugin的wrap方法
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//生成代理对象
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;
}
//然后代理对象执行方法的时候就可以执行Plugin类的invoke方法,invoke方法里会执行Interceptor的interceptor方法
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);
}
}
//然后就到了我们自己定义的插件,这里我输出了一下sql日志
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
BoundSql boundSql = ms.getBoundSql(parameter);
logger.error("sql========>[{}]",boundSql.getSql());
return invocation.proceed();
}
总结
我这里简单的描述了一下mybatis插件的流程,如果有不清楚的可以去看看它的源码,通过框架来回顾我们的知识点是一种很好的方式,不容易忘记