【注解与反射】7 - 3 动态代理
§7-3 动态代理
7-3.1 动态代理的思想
动态代理可无侵入式地为对象添加额外的功能。调用者借助代理在调用原对象功能的同时,可以在不修改原对象源码的情况下调用额外的功能。调用过程为:
调用者 -> 代理 -> 对象
代理类含有对象中需要被代理的方法,这些方法的签名一致,这样外部调用者可以通过代理中的代理方法调用对象方法,同时还能实现额外的功能。为了使得代理能够 “清楚” 有哪些方法需要被代理,这些方法应当由一个统一的接口来定义。这样,代理只需实现该接口并重写该方法即可。
Java 提供了用于创建代理类的 API,java.lang.reflect.Proxy
。该类中的静态方法 newProxyInstance(...)
可用于创建代理类对象。
Proxy
的静态方法:
静态方法 | 描述 |
---|---|
static InvocationHandler(Object proxy) |
返回代理类对象的调用处理器 |
static boolean isProxyClass(Class<?> cl) |
测试给定类是否为代理类 |
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) |
为指定的接口返回将方法调用分配给指定调用处理器的代理类对象 |
但在开始使用动态代理前,我们需要确定以下内容:
- 什么类需要被代理;
- 类中哪些方法需要被代理,使用接口定义该方法,原始类和代理类都需要实现接口并重写上述方法。
7-3.2 准备工作
实现动态代理的前提是明确被代理对象和代理方法。这里以 BigStar
类为被代理类,其中 String sing(String)
和 void dance()
方法需要被代理。被代理的方法由接口 Star
定义,且由 BigStar
实现并重写。
Star
接口声明:
public interface Star {
String sing(String songName);
void dance();
}
BigStar
类声明:
public class BigStar implements Star {
private String name;
public BigStar() {}
public BigStar(String name) {
this.name = name;
}
public String toString() {
return "BigStar{name = " + name + "}";
}
@Override
public String sing(String songName) {
System.out.println(this.name + "唱了" + songName + "。");
return "谢谢大家。";
}
// 省略 Getters & Setters 及 toString() 方法
}
这些准备工作做好之后,接下来就是定义一个用于创建指定代理类的工具类。这里该工具类声明为 StarProxy
。
7-3.3 声明代理工具类
在本例中,代理工具类有且仅有一个静态公共方法 newProxy
,该方法内部调用 Proxy.newProxyInstance(...)
方法以返回一个代理类对象。该方法的参数较多,参数的说明如下说明。
newProxyInstance()
方法形参说明:
参数 | 类型 | 说明 | 解释 |
---|---|---|---|
ClassLoader loader |
类加载器 | 定义代理的类的加载器 | 谁定义代理类,填入谁的加载器。本例为 StarProxy.class.getClassLoader() |
Class<?>[] interfaces |
接口 Class 对象数组 |
代理类所实现的接口的数组 | 一个存放了定义需要被代理的方法的接口的 Class 数组。接口可以有多个。本例中仅有 Star |
InvocationHandler h |
调用处理器 | 真正执行代理方法的接口 | 这是一个函数式接口,仅有一个抽象方法 Object invoke(Object proxy, Method method, Object[] args) 。被代理的方法由此方法执行,通过反射获取代理方法并传入形参。 |
该方法返回一个 Object
类型的代理对象,可使用强制类型转换将对象转换成指定类型。
本例中,我们需要定义一个代理工具类 StarProxy
创建代理对象,该类有且仅有一个方法 Star
。声明如下:
public class StarProxy {
/**
* 创建一个代理对象。
*
* @param bigStar 被代理的对象
* @return 代理对象
*/
public static Star newProxy(BigStar bigStar) {
// 调用 Proxy 的静态方法创建代理对象并返回,使用强转转换类型
return (Star) Proxy.newProxyInstance(
StarProxy.class.getClassLoader(), // 定义代理的类,这里由 StarProxy 定义代理,填入它的加载器
new Class<?>[]{Star.class}, // 所有定义了希望被代理的方法的接口,可以有多个
new InvocationHandler() { // 使用匿名内部类重写接口的方法,可改写为 Lambda 表达式
@Override
public Object invoke
(Object proxy, // 代理
Method method, // 代理方法,调用 method.invoke() 以调用原对象的方法
Object[] args) // 方法可能有的形参
throws Throwable {
// 非侵入式地为原对象添加功能,由代理实现
if ("sing".equals(method.getName())) {
System.out.println("代理正在准备场地、售票、收钱。");
} else if ("dance".equals(method.getName())) {
System.out.println("代理正在准备场地。");
}
// 由原对象调用原方法,并传入希望的方法形参(如有的话),并返回方法返回值。
return method.invoke(bigStar, args);
}
}
);
}
}
这样,一个代理就定义完毕。
7-3.4 测试代理
在测试类中获取上述声明的代理,并依次调用被代理的方法:
public class DynamicProxyDemo {
public static void main(String[] args) {
Star star = StarProxy.newProxy(new BigStar("鸡哥"));
System.out.println(star.sing("只因你太美"));
star.dance();
}
}
得到结果:
代理正在准备场地、售票、收钱。
鸡哥唱了只因你太美。
谢谢大家。
代理正在准备场地。
鸡哥正在跳舞。
可以看到,被代理的方法在原基础上添加了额外的功能,而这并不涉及对原方法的侵入式修改。
通过代理调用方法时,会最终调用创建代理时传入的 IncocationHandler
的 invoke()
方法。