Java动态代理
Java动态代理是一种在运行时对目标类进行拓展的技术。目前,Java动态代理有两种实现方式:JDK和CGLIB(Code Generation Library),下面分别从两个章节对它们进行介绍。
1 JDK
JDK动态代理是官方原生方案,Java 1.3引入的特性。Proxy.newProxyInstance()
即用来在运行时生成代理类;newProxyInstance()方法第一个参数用于指定类加载器;第二个参数用于指定代理类所需要实现的接口列表,如果这里指定的不是接口,那么会抛出IllegalArgumentException
异常;第三个参数用于指定InvocationHandler
接口的实现类实例,InvocationHandler接口中只有一个invoke()
方法,事实上,Proxy.newProxyInstance()所生成的代理类就是通过委托invoke()方法来进行拓展处理的,换句话说,invoke()方法才是真正的拓展逻辑所在。
public class Proxy implements java.io.Serializable {
protected InvocationHandler h;
private Proxy() {
}
protected Proxy(InvocationHandler h) {
this.h = h;
}
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
}
}
1.1 代理目标
CustomInterface
接口中定义了一个doPrint()
方法,用于打印给定内容。
public interface CustomInterface {
public boolean doPrint(String content);
}
代码语言:javascript复制public class CustomInterfaceImpl implements CustomInterface {
@Override
public boolean doPrint(String content) {
System.out.println(content);
return true;
}
}
1.2 拓展逻辑
在运行时对doPrint()
方法进行拓展:执行前后分别打印一条日志。
1.3 实现InvocationHandler接口,封装拓展逻辑
既然InvocationHandler
接口中的invoke()
方法承载了拓展逻辑,那就直接定义一个实现类CustomInvocationHandler
,将打印日志的拓展逻辑封装在invoke()方法中。另外,该实现类CustomInvocationHandler还需要持有代理目标CustomInterface实例,这样才可以执行CustomInterface中的doPrint()方法。
public class CustomInvocationHandler implements InvocationHandler {
private Object target;
public CustomInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, @NotNull Method method, Object[] args) throws Throwable {
System.out.println("*** start log ***");
Object result = method.invoke(this.target, args);
System.out.println("*** end log ***");
return result;
}
}
1.4 生成运行时代理类
代码语言:javascript复制public class DynamicProxyApp {
public static void main(String[] args) {
CustomInvocationHandler customInvocationHandler = new CustomInvocationHandler(new CustomInterfaceImpl());
CustomInterface customInterfaceProxy = (CustomInterface) Proxy.newProxyInstance(
CustomInterface.class.getClassLoader(),
new Class<?>[]{CustomInterface.class},
customInvocationHandler
);
customInterfaceProxy.doPrint("jdk dynamic proxy");
}
}
运行结果:
*** start log ***
jdk dynamic proxy
*** end log ***
1.5 时序图
1.6 源码解读
JDK所生成的动态代理类,默认贮存在内存中,如果想将代理类持久化到磁盘中,并形成.class
文件(直接使用IDEA打开即可),那么可以采用以下方法来实现。
// JDK 8
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// JDK 11
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Proxy0即JDK所生成的代理类,从下面源码可以发现它不仅实现了CustomInterface接口,还继承了Proxy类;同时,Proxy0代理类由final关键字修饰,这意味着它是不可继承的;doPrint()方法中的逻辑很简单,就是直接委托InvocationHandler接口中invoke()方法来处理。
代码语言:javascript复制public final class $Proxy0 extends Proxy implements CustomInterface {
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 boolean doPrint(String var1) throws {
try {
return (Boolean)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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.example.optimus_prime.proxy.CustomInterface").getMethod("doPrint", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
2 CGLIB
CGLIB是一款高性能的代码生成库,它被广泛应用于基于代理的AOP
框架中。作为JDK动态代理的互补,它为那些没有实现接口的目标类提供了代理方案(CGLIB同样支持为已实现接口的目标类进行拓展)。本质上,CGLIB通过生成子类、覆盖代理目标中的方法来实现拓展。
2.1 代理目标
CustomInterfaceImpl
类并没有实现CustomInterface
接口,其同样定义了一个doPrint()
方法。
public class CustomInterfaceImpl {
public boolean doPrint(String content) {
System.out.println(content);
return true;
}
}
2.2 拓展逻辑
在运行时对doPrint()
方法进行拓展:执行前后分别打印一条日志。
2.3 实现MethodInterceptor接口,封装拓展逻辑
代码语言:javascript复制public class CustomMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(
Object proxy,
Method method,
Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("*** start log ***");
Object result= methodProxy.invokeSuper(proxy, args);
System.out.println("*** end log ***");
return result;
}
}
2.4 生成运行时代理类
代码语言:javascript复制public class CglibProxyApp {
public static void main(String[] args) throws IOException {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(CustomInterfaceImpl.class);
enhancer.setCallback(new CustomMethodInterceptor());
CustomInterfaceImpl customInterfaceProxy = (CustomInterfaceImpl) enhancer.create();
customInterfaceProxy.doPrint("cglib dynamic proxy");
}
}
运行结果:
*** start log ***
cglib dynamic proxy
*** end log ***
2.5 时序图
2.6 源码解读
CGLIB所生成的动态代理类,默认贮存在内存中,如果想将代理类持久化到磁盘中,并形成.class
文件(直接使用IDEA打开即可),那么可以采用以下方法来实现。
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\optimus_prime");
CustomInterfaceImpl
777d4723即CGLIB所生成的代理类,从下面源码可以发现它继承CustomInterfaceImpl类并覆盖了父类中doPrint()方法,doPrint()方法中的逻辑很直观,如果提供了MethodInterceptor回调接口,则执行MethodInterceptor回调接口中intercept()方法,否则直接执行目标类(父类)中的doPrint()方法;另外,该代理类还暴露了CGLIB
代码语言:javascript复制public class CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723
extends CustomInterfaceImpl
implements Factory {
private static final Method CGLIB$doPrint$0$Method;
private static final MethodProxy CGLIB$doPrint$0$Proxy;
private MethodInterceptor CGLIB$CALLBACK_0;
final boolean CGLIB$doPrint$0(String var1) {
return super.doPrint(var1);
}
@Override
public final boolean doPrint(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 != null) {
Object var2 = var10000.intercept(
this,
CGLIB$doPrint$0$Method,
new Object[]{var1},
CGLIB$doPrint$0$Proxy);
return var2 == null ? false : (Boolean)var2;
} else {
return super.doPrint(var1);
}
}
}
既然intercept()方法封装了日志打印的拓展逻辑,那么intercept()方法中methodProxy.invokeSuper(proxy, args)
的作用肯定就是执行CustomInterfaceImpl中的doPrint()方法了。为了更高效地执行目标类中的doPrint()方法,CGLIB还是花了一定心思的,CGLIB引入了FastClass机制,FastClass机制就是为方法构建索引,调用方法时根据方法签名来计算索引,通过索引来直接调用对应的方法。
abstract public class FastClass {
/**
* type主要指定所调用方法的归属类,比如本文所涉及的:
* (1) CustomInterfaceImpl
* (2) CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723
*/
private Class type;
protected FastClass(Class type) {
this.type = type;
}
/**
* @param sig 方法签名:包含方法名称、方法返回类型和参数类型
* @return 方法索引
*/
abstract public int getIndex(Signature sig);
/**
* 根据方法索引调用对应的方法
* @param index the method index
* @param obj the object the underlying method is invoked from
* @param args the arguments used for the method call
*/
abstract public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException;
}
public class MethodProxy {
// CustomInterfaceImpl类中doPrint()方法签名信息:方法名称、方法返回类型和参数类型
private Signature sig1;
// CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723代理类中CGLIB$doPrint$0()方法签名信息:方法名称、方法返回类型和参数类型
private Signature sig2;
private CreateInfo createInfo;
private volatile FastClassInfo fastClassInfo;
private final Object initLock = new Object();
private void init() {
if (fastClassInfo == null) {
synchronized (initLock) {
if (fastClassInfo == null) {
CreateInfo ci = createInfo;
FastClassInfo fci = new MethodProxy.FastClassInfo();
fci.f1 = helper(ci, ci.c1);
fci.f2 = helper(ci, ci.c2);
fci.i1 = fci.f1.getIndex(sig1);
fci.i2 = fci.f2.getIndex(sig2);
fastClassInfo = fci;
createInfo = null;
}
}
}
}
private static class FastClassInfo {
// CustomInterfaceImpl$$FastClassByCGLIB$$e8bf017e
FastClass f1;
// CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9
FastClass f2;
// CustomInterfaceImpl类中doPrint()方法的索引
int i1;
// CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723代理类中CGLIB$doPrint$0()方法的索引
int i2;
}
/**
* 执行CustomInterfaceImpl$$FastClassByCGLIB$$e8bf017e类invoke()方法
* @param obj 该参数不可以是所生成的代理类的实例,否则将无限递归执行
* @param args args 参数,即"cglib dynamic proxy"
*/
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
} catch (IllegalArgumentException e) {
if (fastClassInfo.i1 < 0) {
throw new IllegalArgumentException("Protected method: " sig1);
}
throw e;
}
}
/**
* 执行CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9类invoke()方法
* @param obj 即所生成的代理类的实例
* @param args 参数,即"cglib dynamic proxy"
*/
public Object invokeSuper(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
return fci.f2.invoke(fci.i2, obj, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
从上面MethodProxy源码中,我们已经知道了invokeSuper内部逻辑:首先,init()
方法根据方法签名计算出方法索引fci.i2
;然后根据方法索引调用fci.f2
中的invoke()方法。而fci.f2
成员变量是FastClass类型的且FastClass是一个抽象类,那我们可以猜测CGLIB肯定还生成了FastClass的子类。没错,如下所示:
public class CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9
extends FastClass {
public CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723$$FastClassByCGLIB$$2f30e4f9(Class var1) {
super(var1);
}
@Override
public int getIndex(Signature var1) {
String var10000 = var1.toString();
switch(var10000.hashCode()) {
case 247754584:
if (var10000.equals("CGLIB$doPrint$0(Ljava/lang/String;)Z")) {
return 10;
}
break;
case 983113313:
if (var10000.equals("doPrint(Ljava/lang/String;)Z")) {
return 1;
}
break;
}
return -1;
}
@Override
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723 var10000 = (CustomInterfaceImpl$$EnhancerByCGLIB$$777d4723)var2;
int var10001 = var1;
try {
switch(var10001) {
case 1:
return new Boolean(var10000.doPrint((String)var3[0]));
case 10:
return new Boolean(var10000.CGLIB$doPrint$0((String)var3[0]));
}
} catch (Throwable var4) {
throw new InvocationTargetException(var4);
}
throw new IllegalArgumentException("Cannot find matching method/constructor");
}
}
在IDEA DEBUG模式下,进入MethodProxy类一探究竟:fci.i2
索引值的确为10,那一切也就真相大白了!!!
case 10:
return new Boolean(var10000.CGLIB$doPrint$0((String)var3[0]));
3 总结
JDK动态代理所代理的目标类必须实现接口,否则抛出异常;另外,如果目标类的方法非接口中已定义的,那么代理类是不会对该方法进行代理的(该方法压根儿不会出现在代理类中)。而CGLIB动态代理则没有目标类必须实现接口的限制,但由于其基于继承机制,那么目标类就不能由final
关键字修饰,类似地,该目标类中相关方法也不能由private
或final
关键字修饰。
4 参考文档
- https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/Proxy.html
- https://dzone.com/articles/cglib-missing-manual
- https://objectcomputing.com/resources/publications/sett/november-2005-create-proxies-dynamically-using-cglib-library
- https://github.com/cglib/cglib