深入浅出jdk动态代理和cglib动态代理
代理模式
生活中我们可以见到各种各样的代理,比如租房子找中介,比如驾校招生代理。他们有一个共同点就是他们只算作中间人,但不是最后真正的执行者,房子中介租房的时候,房子主人并不是他,驾校招生代理在招人的时候,只是登记你的信息,具体去哪练车,还是要看驾校的,这就是代理通俗上描述的样子。在代理设计模式中,其实很类似。
某个接口定义了一些方法,有一个实现类实现了它,然后有个代理类也实现了这个接口,并持有实现类实例,代理类的接口实现本质上是调用实现类的逻辑,在调用前后可以增加一些逻辑,也可以控制是否调用实现类。
代码实现
接口类:
/**
* 接口类
*/
public interface Door {
void open();
}
实现类:
/**
* 实现类
*/
public class DoorImpl implements Door {
@Override
public void open() {
System.out.println("开门...");
}
}
代理类:
/**
* 代理类
*/
public class DoorProxy implements Door{
//持有接口Door的一个实例
private Door door;
public DoorProxy(Door door) {
this.door = door;
}
@Override
public void open() {
//这里可以在调用真实逻辑前添加一些逻辑
System.out.println("插钥匙...");
door.open();
//这里可以在调用真实逻辑后添加一些逻辑
System.out.println("拔钥匙...");
}
}
使用:
/**
* 使用者
*/
public class Main {
public static void main(String[] args) {
Door door = new DoorProxy(new DoorImpl());
door.open();
}
}
输出:
Connected to the target VM, address: '127.0.0.1:50052', transport: 'socket'
插钥匙...
开门...
拔钥匙...
Disconnected from the target VM, address: '127.0.0.1:50052', transport: 'socket'
Process finished with exit code 0
这样一个代理模式就演示完成了。但是这中实现有两个弊端
- 有多少个需要代理的对象,就要创建多少个Proxy类,如果需要代理的对象很多,创建这些代理类就是个不小的工作
- 假设实现类又实现了另外的接口,代理类中要想使用,必须再持有另一个实现类实例,这样代理类中持有了两个实例,其实是一样的,只是接口不同,冗余
于是JDK给出了动态代理,就是动态生成字节码。然后加载到内存中,一切都是自动的,只需要写调用真实逻辑前后我们需要增加的逻辑即可。
JDK动态代理
JDK动态代理可以动态生成字节码,调用真实逻辑的方法需要我们自己写。
public class Main {
public static void main(String[] args) {
//我们接口的实现实例
Door doorImpl = new DoorImpl();
Door door = (Door) Proxy.newProxyInstance(doorImpl.getClass().getClassLoader(), doorImpl.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用真实逻辑前的一些处理
System.out.println("插钥匙...");
//调用真实逻辑
Object re = method.invoke(doorImpl,args);
//调用真实逻辑后的一些处理
System.out.println("拔钥匙...");
return re;
}
});
door.open();
}
}
首先是new出我们的实现类,因为这个类里面是我们的实现逻辑,然后调用
java.lang.reflectProxy.newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
这个方法生成一个代理类,强制转换成为Door
的实例,就可以使用了。这里解释下这三个参数。ClassLoader loader
这个参数是接口实现类的类加载器,可以通过DoorImpl.getClass().getClassLoader()
获取到。Class<?>[] interfaces
这个参数是实现类所实现的接口,这里可以传入new Class[]{Door.class}
,表示当前实现类实现了Door
这个接口,但是大多数情况下,我们的实现类可能不只实现一个接口,为了更加通用,我们使用反射来获取实现类实现的所有接口。InvocationHandler h
这个参数是个接口,里面只有一个方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
这个方法里面定义了我们怎么控制调用真实逻辑,也就是说在调用真实逻辑前要做的事和调用真实逻辑后要做的事。其中方法的三个参数分别是代理类,实现类的方法,调用实现类方法的参数。上面的这个方法里面逻辑其中Object re = method.invoke(doorImpl,args);
是在调用真实逻辑,注意这里面method.invoke
的第一个参数是doorImpl
而不是proxy
。Object re = method.invoke(doorImpl,args);
前面和后面就是我们要添加的一些逻辑了。
JDK动态代理原理
上面我们运行发现和之前写的代理是同样的效果,那么JDK是怎么做的呢,最好的办法就是撸一遍源码。给出大致过程,不过多贴源码,感兴趣的自己去看吧,贴太多就本末倒置了。
java.lang.reflect.Proxy#newProxyInstance主要逻辑:
1. 克隆接口,检查权限
2. Class<?> cl = getProxyClass0(loader, intfs);这一行是生成代理类
3. return cons.newInstance(new Object[]{h});最后调用了生成类的构造方法,传入了h,这个h就是上面代码中的InvocationHandler实例
继续追踪上面的第2步中的生成细节:
java.lang.reflect.Proxy#getProxyClass0主要逻辑:
1. 检查实现类实现的类个数不能超过65535个
2. return proxyClassCache.get(loader, interfaces);
到这里就可以看出来和缓存有关,继续追:
java.lang.reflect.WeakCache#get主要逻辑:
1. 参数不能为null判断
2. 判断缓存中是否存在,存在返回,不存在则调用java.lang.reflect.Proxy.ProxyClassFactory#apply创建,源码中对应的是这一句:Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
然后继续追:
java.lang.reflect.Proxy.ProxyClassFactory#apply主要逻辑:
1. 还是一顿检查
2. 拼接class名:包路径$Proxy数字.class
3. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);到这里的时候就是生成字节码了
追到这我们已经看到生成字节码了,返回了一个byte数组,我们想办法拿到这个字节码,具体的生成细节就不去细致去看了,只要看这个类的字节码,就知道大概生成了啥。jdk8及其以前的版本在代码里添加环境变量
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
然后运行代码即可拿到生成的class文件。我这里拿到了生成的类,然后把它反编译出来:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.sun.proxy;
import com.company.Door;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Door {
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 boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void open() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.company.Door").getMethod("open");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
需要注意几个地方:
- 当前类是继承自
java.lang.reflect.Proxy
类,实现了Door
接口 - 当前类只有一个构造方法,就是参数为
InvocationHandler
构造方法,然后使用super(h)
初始化了父类,现在来看,这个InvocationHandler
是不是有点眼熟?在Proxy.newProxyInstance
中提到过。 Door
中的方法void open()
在本类中有实现,只有一行对于我们来说有效的语句:
super.h.invoke(this, m3, (Object[])null);
源码中可以看到m3
是哪个方法:
m3 = Class.forName("com.company.Door").getMethod("open");
在super.h.invoke(this, m3, (Object[])null);
这句中,父类的h
是我们传入的内部类,然后它的invoke方法就是调用我们自己定义的那一套逻辑。也就是:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//调用真实逻辑前的一些处理
System.out.println("插钥匙...");
//调用真实逻辑
Object re = method.invoke(doorImpl,args);
//调用真实逻辑后的一些处理
System.out.println("拔钥匙...");
return re;
}
此时便真想大白了。JDK帮我们动态生成了一个代理类,类中实现了我们的接口,然后最后调用的还是我们的逻辑。
Spring中的@Transactional问题
有这么一个问题:Spring中的某个service有个两个方法A()
和B()
,在B()
方法上面添加了注解@Transactional
注解,然后A()
方法中调用了this.B()
,为什么事务没有开启?我们知道加了@Transactional
方法所在的类会被Spring自动生成代理,如无特殊情况,默认是使用JDK动态代理(Spring Boot 2.0默认使用了cglib,Spring还是JDK动态代理),当前类被代理了,A()
方法中使用this.B()
调用本类中的B()
方法,根据上面我们知道,只有调用代理类的B()
方法才会走InvocationHandler
(Spring帮我们实现了此接口)那一套,当前是service,不是代理后的类,确切的说是根本就没调用到代理后的类中去,所以这中情况下不会自动开启和关闭事务,如果想让B()
方法生效怎么办:
- 把
A()
方法到别的类中,注入当前service,然后调用B()
方法 - 在当前service中注入自己,你没有看错,就是注入一个自己(这是Spring的循环依赖,Spring会帮我们处理好的),然后在
A()
方法中使用service.B()
调用B()
方法即可。
cglib动态代理
另一种动态代理是cglib动态代理,cglib底层采用ASM字节码技术,可以动态修改字节码。示例还是使用Door
接口和DoorImpl
作为我们的接口和实现类,其中某一种使用cglib手动生成字节码的代码可能是这样的:
/**
* 使用者
*/
public class Main {
public static void main(String[] args) {
//把动态生成的类输出到当前文件夹
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./");
//我们接口的实现实例
Door doorImpl = new DoorImpl();
//cglib生成类的朱磊
Enhancer enhancer = new Enhancer();
//设置类加载器
enhancer.setClassLoader(doorImpl.getClass().getClassLoader());
//设置实现类
enhancer.setSuperclass(doorImpl.getClass());
//设置在原来的基础上添加的逻辑
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//在之前的逻辑前添加逻辑
System.out.println("插钥匙...");
//执行真实逻辑 调用的是父类的方法
Object object = proxy.invokeSuper(obj,args);
//在之前的逻辑执行后添加逻辑
System.out.println("拔钥匙...");
return object;
}
});
//获取动态生成的类
Door door = (Door) enhancer.create();
door.open();
}
}
输出是这样的:
Connected to the target VM, address: '127.0.0.1:55007', transport: 'socket'
CGLIB debugging enabled, writing to './'
插钥匙...
开门...
拔钥匙...
Disconnected from the target VM, address: '127.0.0.1:55007', transport: 'socket'
Process finished with exit code 0
然后在我们当前文件夹下,生成了5个类,我们找到继承自DoorImpl
的那个类:
public class DoorImpl$$EnhancerByCGLIB$$850acfc8 extends DoorImpl implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$open$0$Method;
private static final MethodProxy CGLIB$open$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("com.company.DoorImpl$$EnhancerByCGLIB$$850acfc8");
Class var1;
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
CGLIB$open$0$Method = ReflectUtils.findMethods(new String[]{"open", "()V"}, (var1 = Class.forName("com.company.DoorImpl")).getDeclaredMethods())[0];
CGLIB$open$0$Proxy = MethodProxy.create(var1, var0, "()V", "open", "CGLIB$open$0");
}
final void CGLIB$open$0() {
super.open();
}
public final void open() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
var10000.intercept(this, CGLIB$open$0$Method, CGLIB$emptyArgs, CGLIB$open$0$Proxy);
} else {
super.open();
}
}
final boolean CGLIB$equals$1(Object var1) {
return super.equals(var1);
}
public final boolean equals(Object var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
return var2 == null ? false : (Boolean)var2;
} else {
return super.equals(var1);
}
}
final String CGLIB$toString$2() {
return super.toString();
}
public final String toString() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
}
final int CGLIB$hashCode$3() {
return super.hashCode();
}
public final int hashCode() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}
final Object CGLIB$clone$4() throws CloneNotSupportedException {
return super.clone();
}
protected final Object clone() throws CloneNotSupportedException {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
}
public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
String var10000 = var0.toString();
switch(var10000.hashCode()) {
case -1263249173:
if (var10000.equals("open()V")) {
return CGLIB$open$0$Proxy;
}
break;
case -508378822:
if (var10000.equals("clone()Ljava/lang/Object;")) {
return CGLIB$clone$4$Proxy;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return CGLIB$equals$1$Proxy;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return CGLIB$toString$2$Proxy;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return CGLIB$hashCode$3$Proxy;
}
}
return null;
}
public DoorImpl$$EnhancerByCGLIB$$850acfc8() {
CGLIB$BIND_CALLBACKS(this);
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
CGLIB$THREAD_CALLBACKS.set(var0);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
CGLIB$STATIC_CALLBACKS = var0;
}
private static final void CGLIB$BIND_CALLBACKS(Object var0) {
DoorImpl$$EnhancerByCGLIB$$850acfc8 var1 = (DoorImpl$$EnhancerByCGLIB$$850acfc8)var0;
if (!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if (var10000 == null) {
return;
}
}
var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}
}
public Object newInstance(Callback[] var1) {
CGLIB$SET_THREAD_CALLBACKS(var1);
DoorImpl$$EnhancerByCGLIB$$850acfc8 var10000 = new DoorImpl$$EnhancerByCGLIB$$850acfc8();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Callback var1) {
CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
DoorImpl$$EnhancerByCGLIB$$850acfc8 var10000 = new DoorImpl$$EnhancerByCGLIB$$850acfc8();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
CGLIB$SET_THREAD_CALLBACKS(var3);
DoorImpl$$EnhancerByCGLIB$$850acfc8 var10000 = new DoorImpl$$EnhancerByCGLIB$$850acfc8;
switch(var1.length) {
case 0:
var10000.<init>();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
default:
throw new IllegalArgumentException("Constructor not found");
}
}
public Callback getCallback(int var1) {
CGLIB$BIND_CALLBACKS(this);
MethodInterceptor var10000;
switch(var1) {
case 0:
var10000 = this.CGLIB$CALLBACK_0;
break;
default:
var10000 = null;
}
return var10000;
}
public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
}
public Callback[] getCallbacks() {
CGLIB$BIND_CALLBACKS(this);
return new Callback[]{this.CGLIB$CALLBACK_0};
}
public void setCallbacks(Callback[] var1) {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
}
static {
CGLIB$STATICHOOK1();
}
}
这个类继承自我们的实现类DoorImpl
,然后里面包含一个和父类方法签名一致的open
方法,这就是重写了父类方法。
我们找到open
方法然后看调用逻辑:
if (var10000 != null) {
var10000.intercept(this, CGLIB$open$0$Method, CGLIB$emptyArgs, CGLIB$open$0$Proxy);
} else {
super.open();
}
如果定义并设置了MethodInterceptor
的实现类,就调用MethodInterceptor.intercept
方法,如果没有呢,就直接调用父类open
方法。此人是我们再考虑Spring中的@Transactional
注解使用cglib动态生成字节码技术进行动态代理的话,依旧是会有上面提到的同一个service中A方法调用B方法问题,因为其本质还是调用了实现类中的open
方法,而不是代理类中open
方法。
cglib动态代理和jdk动态代理使用场景
- jdk动态代理要求必须实现接口,若某个需要代理的类未实现任何接口,就需要使用cglib
- cglib通过继承机制进行动态代理,如果某个需要代理的类是final修饰或者需要代理的方法是final修饰的,cglib无法进行动态代理