摘要
根据代理类生成的时机,代理类在运行时生成,为动态代理;
本文介绍:
- 如何通过JDK实现的动态代理,并详细介绍使用方式(Proxy, InvocationHandler);
- 实现原理ProxyGenerator。
一、代理模式回顾
代理模式二、基于JDK的动态代理
首先,已有主题接口Subject,以及真正主题对象类 RealSubject:
Subject
/**
* Subject主题接口
*/
public interface Subject {
void doTask();
}
RealSubject
:
/**
* 具体类,真正具有接口实现逻辑的类
*/
public class RealSubject implements Subject {
@Override
public void doTask() {
try {
Thread.sleep(5000L);
System.out.println("task completed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这就像在实际编码中,已经有了一个基于面向接口编程,优良设计的组件,组件实际提供功能的是类RealSubject。
现在要求在计算组件核心业务的耗时,我们不能侵入地修改核心业务提供类RealSubject的源码,代理模式就发挥作用了。
静态代理已经介绍过,这里使用基于JDK的动态代理来完成这个需求。
1) 首先创建调用处理器
public class SubjectHandler<T extends Subject> implements InvocationHandler {
private T subject;
public SubjectHandler(T subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在代理真实对象前我们可以添加一些自己的操作
long start = System.currentTimeMillis();
System.out.println("task begins");
// 调用真实主题对象方法
Object invoke = method.invoke(subject, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("task ends, duration: \t" + (-start + System.currentTimeMillis()) / 1000 + "s");
return invoke;
}
}
2) 创建动态代理
public static void main(String[] args) {
// 我们要代理的真实对象
Subject realSubject = new RealSubject();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new SubjectHandler<>(realSubject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), handler);
// 调用代理类方法
subject.doTask();
}
三、JDK的动态代理详细
明面上的jdk动态代理功能主要由两个类提供:
- InvocationHandler
- Proxy
1)InvocationHandler
jdk对java.lang.reflect.InvocationHandler
的说明:
每一个动态代理实例都关联一个调用处理器(InvocationHandler
),当调用代理实例的方法时,实际调用被封装,转发给给其关联的调用处理器的invoke
方法;
在实现上,调用处理器的invoke方法是通过反射的方式调用真实主题对象的方法,因此调用处理器必须持有真实主题对象的引用,这样,动态代理类间接将请求委托给真实主题对象。从而符合代理模式,真实请求通过代理类委托给真实主题对象处理的逻辑。
invoke(Object proxy, Method method, Object[] args)
参数说明:
- proxy是动态代理实例;
- method是
Method
实例,对应动态代理类实现的接口的方法; - args是调用参数。
2)Proxy
java.lang.reflect.Proxy
提供创建动态代理类以及动态代理类实例的静态方法,同时也是其创建出来的所有动态代理类的父类。
动态代理类实例,有两种创建方式:
-
直接调用其
newProxyInstance(ClassLoader, interfaces, invocationHandler)
方法,获得代理类实例; -
调用其
getProxyClass(ClassLoader, interface)
方法获取动态代理类对象,然后获取动态代理类的带参构造构造器,最后通过反射构造动态代理类实例:Class<?> proxyClass = Proxy.getProxyClass(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces()); Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); Subject subject = (Subject) constructor.newInstance(handler);
其中:
- ClassLoader是加载动态代理类所需的类加载器;
- interfaces是动态代理类要实现的接口列表;
- invocationHandler是动态代理类关联的调用处理器。
动态代理类是在运行时实现指定接口列表的类,每一个动态代理类都关联一个调用处理器,通过调用处理器,间接将请求委托给真实主题对象处理。
3)动态代理类
在调用处理器的invoke方法中,第一个参数Object proxy
是动态代理类实例,通过这个对象,可以一窥动态代理类。
Class<?> proxyClass = proxy.getClass();
System.out.println(proxyClass);
System.out.println(proxyClass.getSuperclass());
System.out.println(Arrays.toString(proxyClass.getInterfaces()));
输出:
class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy
[interface design.pattern.proxy.Subject]
四、JDK的动态代理原理
jdk动态代理的核心,就在于动态代理类的创建;
抽丝剥茧,发现在Proxy类的内部类 ProxyClassFactory
中的apply方法,使用:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags);
创建动态代理类的字节码。
那么,可以使用ProxyGenerator手动创建动态代理类的字节码,然后通过字节码反编译,探究动态代理类:
public static void main(String[] args) {
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
new Class[]{Subject.class}, Modifier.PUBLIC);
String path = DynamicClassGenTest.class.getResource("").getPath() + "$Proxy0.class";
File file = new File(path);
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类class文件写入成功");
} catch (Exception e) {
e.printStackTrace();
}
}
反编译字节码文件$Proxy0.class
伪代码:
public class $Proxy0 extends Proxy implements Subject {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// equals, toString, hashCode实现
public final void doTask() throws {
try {
super.h.invoke(this, m3, (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"));
// toString method
m3 = Class.forName("design.pattern.proxy.Subject").getMethod("doTask");
// hashCode method
}
// catch block
}
}
- 首先,动态代理类实现要代理的接口Subject,继承类Proxy。
- 在static代码块中,使用反射,接口Subject的doTask()方法,并使用反射Method引用之;
- Subject#doTask()方法的实现,将
this
(反映到调用处理器中就是实际动态代理对象),反射类Methods实例method(Subject的doTask()方法),调用参数作为参数,调用父类Proxy的InvocationHandler的invoke方法。
缺陷
根据动态代理类的创建原理,不难看出,动态代理类必须实现要代理的接口;因此jdk动态代理只能对接口进行代理,不能代理类。
另外,jdk动态代理基于JAVA反射,这决定了其性能不会太高。
总结
使用JDK动态代理时,通过类Proxy创建动态代理类,每一个动态代理类关联一个调用处理器,调用处理器将请求委托给真正具备处理能力的实际对象。
根据jdk动态代理类的原理,jdk动态代理只能对接口进行代理,不能代理类。
网友评论