在 Java 中,动态代理是一个很常用的功能,虽然说一般不需要自己直接去用,但是了解它们是怎么回事还是很有必要的。
这篇博客的主要内容便是 JDK 动态代理和 CGLIB 动态代理的简单使用和理解。
JDK 动态代理
JDK 动态代理依赖于 接口 来确定它需要代理的方法,使用时可以分为以下几个角色:
- TargetInterfaces - 需要代理的目标接口(们),JDK 动态代理将会为这些接口的方法调用创建代理
- TargetObject - 实现了目标接口的对象 InvocationHandler - 方法调用 处理器,JDK 动态代理在内部通过
- InvocationHandler 对象来处理目标方法的调用 java.lang.reflect.Proxy - 组装
- InvocationHandler 和 TargetObject, 创建代理对象,创建出来的代理对象是它的子类的实例
TargetInterfaces 和 TargetObject 相对来说很容易理解,就是一些接口和实现了这些接口的对象,比如说:
interface TargetInterfaceA {
void targetMethodA();
}
interface TargetInterfaceB {
void targetMethodB();
}
class TargetClass implements TargetInterfaceA, TargetInterfaceB {
@Override
public void targetMethodA() {
System.out.println("Target method A...");
}
@Override
public void targetMethodB() {
System.out.println("Target method B...");
}
}
上面的例子中,目标接口为 [TargetInterfaceA, TargetInterfaceB], 而目标对象就是 TargetClass 的实例.
现在,我们想要拦截 TargetClass 实现的接口的方法的调用,我们需要先通过 InvocationHandler 来定义代理逻辑:
class SimpleInvocationHandler implements InvocationHandler {
private Object target;
public SimpleInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("Before invocation method %s", method.getName()));
Object result = method.invoke(target, args);
System.out.println(String.format("After invocation method %s", method.getName()));
return result;
}
}
InvocationHandler 这个接口只定义了一个方法 invoke, 该方法的参数为:
- proxy - 代理对象实例,注意,不是 TargetObject, 是 Proxy 子类的实例,因此,我们需要在
InvocationHandler 实例内部持有 TargetObject - method - 要调用的方法
- args - 方法调用参数
有了 InvocationHandler 和 TargetClass 之后,我们就可以创建 TargetObject 并通过 Proxy 组装创建代理对象了,主要通过 newProxyInstance 方法完成:
TargetClass targetObject = new TargetClass();
Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), new SimpleInvocationHandler(targetObject););
方法 Proxy.newProxyInstance 的参数为:
- ClassLoader - 一个 ClassLoader, 简单的话直接用 targetObject 的 ClassLoader 就可以了
- Class<?>[] - 要代理的接口数组,同样,直接获取 targetObject 实现的所有接口
- InvocationHandler - 定义了方法调用处理逻辑的 InvocationHandler
可以看到,创建代理对象的时候需要我们先创建 TargetObject 才行,而且还需要手动将 TargetObject 传递给 InvocationHandler, 很麻烦……
完整代码和测试:
interface TargetInterfaceA {
void targetMethodA();
}
interface TargetInterfaceB {
void targetMethodB();
}
class TargetClass implements TargetInterfaceA, TargetInterfaceB {
@Override
public void targetMethodA() {
System.out.println("Target method A...");
}
@Override
public void targetMethodB() {
System.out.println("Target method B...");
}
}
class SimpleInvocationHandler implements InvocationHandler {
private Object target;
public SimpleInvocationHandler(Object target) {
this.target = target;
}
public static Object bind(Object targetObject) {
SimpleInvocationHandler handler = new SimpleInvocationHandler(targetObject);
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), handler);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(String.format("Before invocation method %s", method.getName()));
Object result = method.invoke(target, args);
System.out.println(String.format("After invocation method %s", method.getName()));
return result;
}
}
public class ProxyTest {
public static void main(String[] args) {
Object proxy = SimpleInvocationHandler.bind(new TargetClass());
((TargetInterfaceA) proxy).targetMethodA();
((TargetInterfaceB) proxy).targetMethodB();
}
}
输出为:
Before invocation method targetMethodA
Target method A...
After invocation method targetMethodA
Before invocation method targetMethodB
Target method B...
After invocation method targetMethodB
JDK代理类
在运行代码时可以将下面这行代码放在最前面查看 Proxy 动态生成的代理类是怎样的:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
前面的代码生成的代理类为:
final class $Proxy0 extends Proxy implements TargetInterfaceA, TargetInterfaceB {
private static Method m0;
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
// 目标接口中定义的方法
m4 = Class.forName("classload.TargetInterfaceB").getMethod("targetMethodB");
m3 = Class.forName("classload.TargetInterfaceA").getMethod("targetMethodA");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
// 通过 InvocationHandler 来调用目标方法
public final void targetMethodA() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
// 通过 InvocationHandler 来调用目标方法
public final void targetMethodB() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
}
通过阅读代理类的代码我们可以发现:
- 代理类继承了 Proxy 并实现了目标接口
- 代理类在静态初始化块通过反射获取了目标接口的方法
- 代理类实现的接口方法会通过 InvocationHandler 来调用目标方法
- InvocationHandler 传递的第一个参数为代理对象,不是 TargetObject1
另外,代理类还获取了 Object 的 hashCode、equals 和 toString 方法,它们的调用逻辑都是一样的,就是直接调用 InvocationHandler 对象对应的方法,比如:
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);
}
}
因此,我们也是可以代理目标对象的这些方法的。
CGLIB 动态代理
CGLIB 动态代理和 JDK 动态代理类似,只不过 CGLIB 动态代理是基于类的,不需要实现接口,简单使用的话只需要定义一个 MethodInterceptor 就可以了, 相当于 JDK 动态代理中的 InvocationHandler.
class SimpleMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println(String.format("Before invocation method %s", method.getName()));
Object result = proxy.invokeSuper(obj, args);
System.out.println(String.format("After invocation method %s", method.getName()));
return result;
}
}
有了 MethodInterceptor 后我们就可以创建代理对象了:
class ProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 设置需要被代理的类
enhancer.setSuperclass(TargetClass.class);
// 设置 MethodInterceptor
enhancer.setCallback(new SimpleMethodInterceptor());
// 创建代理对象
TargetClass proxyObject = (TargetClass) enhancer.create();
// 调用方法
proxyObject.targetMethodA();
proxyObject.targetMethodB();
}
}
class TargetClass {
public void targetMethodA() {
System.out.println("Target method A...");
}
public void targetMethodB() {
System.out.println("Target method B...");
}
}
执行输出为:
Before invocation method targetMethodA
Target method A...
After invocation method targetMethodA
Before invocation method targetMethodB
Target method B...
After invocation method targetMethodB
CGLIB代理类
对于 CGLIB 来说可以设置
DebuggingClassWriter.DEBUG_LOCATION_PROPERTY 属性的值来保存生成的代理类2:
public class TargetClass$$EnhancerByCGLIB$$eb42b691 extends TargetClass implements Factory {
private MethodInterceptor CGLIB$CALLBACK_0;
static void CGLIB$STATICHOOK1() {
// 要代理的目标方法
var10000 = ReflectUtils.findMethods(new String[]{
"targetMethodA", "()V", "targetMethodB", "()V"}, (var1 = Class.forName("TargetClass")).getDeclaredMethods());
CGLIB$targetMethodA$0$Method = var10000[0];
CGLIB$targetMethodA$0$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodA", "CGLIB$targetMethodA$0");
CGLIB$targetMethodB$1$Method = var10000[1];
CGLIB$targetMethodB$1$Proxy = MethodProxy.create(var1, var0, "()V", "targetMethodB", "CGLIB$targetMethodB$1");
}
// 目标方法的简单代理
final void CGLIB$targetMethodA$0() {
super.targetMethodA();
}
public final void targetMethodA() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
// 当 MethodInterceptor 不为空时通过 MethodInterceptor 调用目标方法
if (var10000 != null) {
var10000.intercept(this, CGLIB$targetMethodA$0$Method, CGLIB$emptyArgs, CGLIB$targetMethodA$0$Proxy);
} else {
super.targetMethodA();
}
}
// 目标方法的简单代理
final void CGLIB$targetMethodB$1() {
super.targetMethodB();
}
public final void targetMethodB() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
// 当 MethodInterceptor 不为空时通过 MethodInterceptor 调用目标方法
if (var10000 != null) {
var10000.intercept(this, CGLIB$targetMethodB$1$Method, CGLIB$emptyArgs, CGLIB$targetMethodB$1$Proxy);
} else {
super.targetMethodB();
}
}
final int CGLIB$hashCode$5() {
return super.hashCode();
}
// 对 Object 方法的代理
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$5$Method, CGLIB$emptyArgs, CGLIB$hashCode$5$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}
}
可以看到
- CGLIB 每个方法设置了两个代理,一个直接调用父类方法,另一个判断是否存在 MethodInterceptor 来进行调用
- 代理类继承了 TargetClass, 和 JDK 动态代理中继承 Proxy 的方式不一样
当我们设置了 MethodInterceptor 以后,CGLIB 便可以通过 MethodInterceptor 来调用目标方法,另外,调用
MethodInterceptor.intercept 方法时传递的第一个参数为代理类实例, 因此,需要执行被代理的方法时,应该通过 MethodProxy.invokeSuper 来完成,如果使用 Method.invoke 的话就会导致无限递归调用。
Spring @Configuration
在使用 Spring 的时候我们可以通过如下方式定义 Bean:
@Configuration
@ComponentScan(basePackageClasses = Company.class)
public class Config {
@Bean
public Address getAddress() {
return new Address("High Street", 1000);
}
@Bean
public Person getPerson() {
return new Person(getAddress());
}
}
当初对于这种方式的一种困惑就是,Spring 是怎么拦截对 getAddress 方法的调用的,因为在我的印象中 JDK 动态代理做不到这样的事情,现在才发现,Spring 会通过 CGLIB 为 Config 创建代理对象,拦截对 getAddress 方法的调用,保证 Bean 的单例性。
因为在 Java 中会根据先对象的 实际类型 查找方法,找不到才到 父类 中进行查找,而恰好的是,CGLIB 创建的代理对象是覆盖了父类的方法的,这样一来,在代理类中通过 MethodInterceptor 拦截方法的调用就可以避免重复创建 Bean 了。
这在 Spring 中对应的 MethodInterceptor 为ConfigurationClassEnhancer.BeanMethodInterceptor.
小结
这里汇总一下 JDK 动态代理和 CGLIB 动态代理的代理方式:
- JDK 动态代理通过创建继承了 Proxy 并实现了 TargetInterfaces 的代理类来完成代理,调用
TargetInterfaces 的方法时,代理类会将方法调用转交给 InvocationHandler 完成 - CGLIB 动态代理通过创建继承了 TargetClass 的代理类来完成代理,调用 TargetClass 的方法时,如果MethodInterceptor 不为空,那么就会将方法调用转交给 MethodInterceptor 完成
可以看到,两种实现动态代理的方式还是很接近的,只不过一个是通过接口,一个是通过子类。