JDK动态代理的理解与分析

40次阅读

共计 4374 个字符,预计需要花费 11 分钟才能阅读完成。

前言
java 的设计模式中有一项设计模式叫做代理模式,所谓代理模式,就是通过代理方来操作目标对象,而不是自己直接调用。代理又分为静态代理和动态代理,静态代理就是针对每个被代理对象写一个代理类,操作不够优雅;动态代理,可以根据接口动态的生成代理类,这动态生成的类不需要自己书写,jdk 帮你完成了。无论是动态代理还是静态代理,最终都会产生一个代理类(class 文件),里面都含有对被代理对象的封装,只是诞生的途径不一样。下面我在代码层面详细介绍一下这两种代理的实现和原理。
本文来自于我的博客网站 http://51think.net,欢迎来访。
一、静态代理
1、创建手机接口,拥有打电话的行为
public interface MobilePhone {
// 打电话给 jack
void callJack();
}

2、创建实现类安卓手机,实现此接口
public class AndroidMobilePhone implements MobilePhone{
private String name;
private String age;

public AndroidMobilePhone(String name, String age) {
this.name = name;
this.age = age;
}
// 打电话给 jack
@Override
public void callJack(){
System.out.println(” hey boy! name=”+name+”,age=”+age);
}
}
3、创建静态代理类,实现此接口
public class AndroidMobileStaticProxyPhone implements MobilePhone{
private MobilePhone amp;

public AndroidMobileStaticProxyPhone(MobilePhone amp) {
this.amp = amp;
}
// 打电话给 jack
@Override
public void callJack(){
System.out.println(“– 静态代理前置 –“);
amp.callJack();
System.out.println(“– 静态代理后置 –“);
}
}
从静态代理类 AndroidMobileStaticProxyPhone 中,我们可以发现,他持有了 MobilePhone 类型的对象,一旦将被代理对象传入,它就可以操作被代理对象了。
4、创建 main 方法调用
如果我们不使用代理,调用是这样的:
MobilePhone mp=new AndroidMobilePhone(“ 杰克 ”,”23″);
mp..callJack();
如果使用静态代理,调用变成如下方式:
MobilePhone mp=new AndroidMobilePhone(“ 杰克 ”,”23″);
MobilePhone staticProxy=new AndroidMobileStaticProxyPhone(mp);
staticProxy.callJack();
从上述代码中,我们可以看出,静态代理其实就是通过一个包装类来调用目标对象而已。
二、动态代理
1、仍然沿用 MobilePhone 接口类 2、创建 java.lang.reflect.InvocationHandler 接口的实现类 MobilePhoneHandler
public class MobilePhoneHandler<T> implements InvocationHandler {
private T target;

public MobilePhoneHandler(T target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理
System.out.println(“– 动态代理前置处理 –“);
Object obj=method.invoke(target,args);
// 后置处理
System.out.println(“– 动态代理后置处理 –“);
return obj;
}
}
关于 InvocationHandler,源码注释如下:
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
即,每个代理实例都需要关联一个 invocation handler,当一个方法被代理实例调用时,这个方法会被编码并发送到 invocation handler 中进行处理。这里所说的 invocation handler 即本文中刚刚创建的 MobilePhoneHandler<T> 类。MobilePhoneHandler 类所实现的 invoke 方法包装了对被代理对象的反射调用,后文中的动态代理类正是调用此 invoke 方法来调用被代理对象的方法。
3、创建 main 方法调用
MobilePhone mp=new AndroidMobilePhone(“ 杰克 ”,”23″);
InvocationHandler handler=new MobilePhoneHandler<MobilePhone>(mp);
MobilePhone mpProxy=(MobilePhone)Proxy.newProxyInstance(MobilePhone.class.getClassLoader(),new Class<?>[]{MobilePhone.class},handler );
mpProxy.callJack();
输出如下:
– 动态代理前置处理 –
hey boy! name= 杰克,age=23
– 动态代理后置处理 –
在输出内容的前置处理和后置处理中,我们可以加一些横向的处理逻辑,这样就变成了 spring 的 AOP。
关注 Proxy.newProxyInstance 这个方法调用,同样是来自于 java.lang.reflect 包里的类。看一下源码注释:
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler. This method is equivalent to:
* <pre>
* Proxy.getProxyClass(loader, interfaces).
* getConstructor(new Class[] {InvocationHandler.class}).
* newInstance(new Object[] {handler});
* </pre>
*
注释中表明,这个 newProxyInstance 方法返回了一个特定接口代理类的实例,这个代理实例将方法调用分配给特定的 invocation handler。这个 Proxy.newProxyInstance 方法等同于如下调用:
Proxy.getProxyClass(loader, interfaces). getConstructor(new Class[] {InvocationHandler.class}).newInstance(new Object[] {handler});
我们用 debug 方式跟踪一下代码,newProxyInstance 方法最终会执行到 Proxy 的内部类 ProxyClassFactory 的 apply 方法:
long num = nextUniqueNumber.getAndIncrement(); 这一行使用 cas 生成一个自增长的序号。关注 ProxyGenerator.generateProxyClass 方法:此方法动态生成一个 class 文件,这个 class 文件就是我们所说的动态代理类!我们用代码的方式将这个 class 文件写出来:
byte[] classFile = ProxyGenerator.generateProxyClass(“$Proxy0”, AndroidMobilePhone.class.getInterfaces());
String path = “E:\\projectspace\\Test\\bin\\com\\proxy\\MobileProxy.class”;
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println(“ 代理类 class 文件写入成功 ”);
} catch (Exception e) {
System.out.println(“ 写文件错误 ”);
}

到目录中找到此 class 文件:

反编先看一下反编译的类名和实现关系:

从此图中可以看出,动态代理类最终还是实现了我们的 MobilePhone 接口,即动态代理类也是 MobilePhone 接口的一个实现类,它也实现了 callJack 方法。如下:

红框标注 this.h.invoke(this, m3, null); 中的 h 正是我们上文中创建的 MobilePhoneHandler 类的对象。这样即可完成对被代理对象的调用。类的调用关系如下:

总结
我们再看一下之前 main 方法中的这一行:
MobilePhone mpProxy=(MobilePhone)Proxy.newProxyInstance(MobilePhone.class.getClassLoader(),new Class<?>[]{MobilePhone.class},handler );
现在可以得知 Proxy.newProxyInstance 返回的是动态生成的代理类 $Proxy0 的对象,也可以称作是 MobilePhone 接口的一个实现类的对象。当调用 mpProxy.callJack() 时,其实是调用 $Proxy0.callJack(), 然后对照刚刚的类调用关系图,即可调用到被代理对象 AndroidMobilePhone 实例的 callJack 方法,从而实现了动态代理。
当我们具象的查看某一个动态代理 class 反编译文件时,比如 $Proxy0,它内部就是采用静态代理的方式进行包装。其动态是体现在,能够在给定的接口和 invocationHandler 情况下,动态生成代理类,如 $Proxy0,$Proxy1,$Proxy2 等等,不必手动创建,使用起来更灵活。

正文完
 0