代理模式
最近学到 Spring 的 AOP,发现以前没搞懂的“JDK 动态代理”终于过来找茬了,所以还是细细地疏理一下吧。
1. 代理模式
碰见一个新概念,第一要做的还是要“顾名思义”:模式二字自不必说,所谓“代理”,即当某个目标对象不能被直接使用时,给这个目标对象提供一个“代办人”(代天行道),以便代替目标对象发挥其本来的功能。当然,在这个过程中“代理对象”通常会对“目标对象”进行一些改造升级,而不是单纯的复制。
另一方面,从行为表现上来说,代理其实也可想像为“接口”和“实现类”的关系:接口就是目标对象,相应的实现类就是接口的代理。在 Java 中不能创建接口的实例,而只能创建实现这个接口的类的实例,所以“接口”这个概念虽然不是为代理模式而生的,但它天然就是代理的模式。类似的,其实也可想像为类继承中“基类”和“子类”的关系。
对于 Java 来说,代理模式在代码实现上可以分为静态代理和动态代理。
2. 静态代理
静态代理其实没啥好说的,实际就是通常写代码的方式,将代理对象直接在代码中显示构造出来:
// 目标对象/目标类/被代理类
interface Animal {
public void info();
}
// 需要编写实现类,即“代理类”
class Turtle implements Animal {
@Override
public void info() {
System.out.println("I am an animal, named turtle.");
}
}
public class StaticProxyTest {
public static void main(String[] args) {
// proxyAnimal是Turtle类的实例,但在Java中可自动向上转型为Animal接口
// 故而可以说proxyAnimal为Animal的代理
Animal proxyAnimal = new Turtle(); // 显示构造出代理对象proxyAnimal
// 调用代理对象的info方法
proxyAnimal.info();
}
}
3. 动态代理
3.1. JDK 代理
Java 标准库提供了一种动态代理的机制:可以在运行期动态创建某个接口的实例,也即为这个接口创建代理对象。
具体地,java.lang.reflect 包下提供了一个Proxy
类和一个InvocationHandler
接口,Java 利用它们生成 JDK 动态代理类或动态代理对象,如:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标对象/目标类/被代理类
interface Animal {
public void info();
}
public class DynamicProxyTest {
public static void main(String[] args) {
// 需要先定义一个InvocationHandler类型的handler,其处理(改造)目标对象的每个方法
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("I am an animal, named turtle.");
return null;
}
};
// 直接生成动态代理对象
Animal proxyAnimal = (Animal) Proxy.newProxyInstance(
Animal.class.getClassLoader(), // 接口实现类(即代理对象所属类)的类加载器
new Class[]{Animal.class}, // 代理类要实现的接口(们)
handler); // 改造目标对象方法的InvocationHandler实例
// 调用代理对象的info方法
proxyAnimal.info();
}
}
总结而言,创建 JDK 动态代理对象的过程如下:
定义一个
InvocationHandler
实例,它负责实现接口(即被代理的目标)的方法调用;InvocationHandler
本身也是一个接口,需要定义它的实现类,其中需要重写invoke()
方法。调用代理对象的所有方法时都会被替换成调用该invoke()
方法,其函数签名为:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数 | 解释 |
---|---|
proxy | 代表动态代理对象 |
method | 代表正在执行的方法 |
args | 代表调用目标方法时传入的实参 |
通过
Proxy.newProxyInstance()
创建 interface 实例;其函数签名为
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)
,它需要 3 个参数:
参数 | 解释 |
---|---|
loader | 定义这个代理类的类加载器(通常就是接口类的 ClassLoader) |
interfaces | 代理类将要实现的接口数组(至少需要传入一个接口进去) |
h | 用来处理接口方法调用的 InvocationHandler 实例 |
- 将
Proxy.newProxyInstance()
返回的 Object 强制转型为接口。
除直接创建动态代理对象外,Java 还可先创建动态代理类,而后通过动态代理类再创建动态代理对象,此时调用的是Proxy.getProxyClass()
方法,其完整函数签名为static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
,形参含义同上。不过,即使采用这种方式生成动态代理类之后,如果程序需要通过该代理类来创建对象,依然需要传入一个InvocationHandler
对象。
3.2. cglib 代理
cglib 代理不依赖于接口,而是基于基类/子类。暂略......
4. 动态代理的优势应用
仅看上面定义式的描述,实在很难看出动态代理的优势,而实际上动态代理在解耦方面具有重要应用。例如:
4.1. 解耦合
对于如下图 18.4 的软件系统,工程中出现大量重复代码段是相当糟糕的情况,大部分稍有经验的开发者都会将其中深色代码段定义成一个方法,然后让另外三段代码段直接调用该方法即可,如下图 18.5。
然而,采用这种方式来实现代码复用依然产生一个重要问题:代码段 1、代码段 2、代码段 3 和深色代码段分离开了,但代码段 1、代码段 2、代码段 3 又和一个特定方法耦合了!最理想的效果是:代码段 1、2、3 既可以执行深色代码部分,又无须在程序中以硬编码方式直接调用深色代码的方法,这时就可以通过动态代理来达到这种解耦效果。如下面的代码,一些重要的理念也包含在注释中:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 由于JDK动态代理只能为接口创建动态代理,所以先提供一个Dog接口
interface Dog {
void info();
void run();
}
// 由于接口不可能提供实现,如果直接使用Proxy为Dog接口创建动态代理对象,
// 那动态代理对象的所有方法的执行效果又将完全一样,故而实际情况通常是:
// 该Dog接口总会提供一个/多个实现类,比如这里的GunDog类
// 此类中定义两个方法来代表上述所谓的代码段1、代码段2,目标是希望这两段代码能够跟某个公共代码松耦合
class GunDog implements Dog {
public void info() {
System.out.println("我是一只猎狗");
}
public void run() {
System.out.println("我奔跑迅速");
}
}
// 定义通用方法,即代码段1、2的公共代码段
class DogUtil {
// 第一个拦截器方法
public void method1() {
System.out.println("--------模拟通用方法1---------");
}
// 第二个拦截器方法
public void method2() {
System.out.println("--------模拟通用方法2---------");
}
}
// 程序的关键
class MyInvocationHandler implements InvocationHandler {
// 需要被代理的目标对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
DogUtil du = new DogUtil();
du.method1();
// 通过反射以target作为主调来执行method方法
Object result = method.invoke(target, args); // 关键
du.method2();
return result;
}
}
// 提供一个代理工厂类,专门为指定的target生成动态代理实例
class MyProxyFactory {
public static Object getProxy(Object target) {
MyInvocationHandler handler = new MyInvocationHandler();
handler.setTarget(target);
// 创建并返回一个target的动态代码对象,它与target实现了相同的接口
// 故而动态代理对象可以当作target对象使用,即实现了对target对象的代理
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
}
}
public class DynamicProxyTest {
public static void main(String[] args) {
// 创建一个原始的GunDog对象,作为target
Dog target = new GunDog();
// 以指定的target来创建动态代理对象
Dog dog = (Dog) MyProxyFactory.getProxy(target);
// info()和run()两个代码段既实现了对method1()、method2()通用方法的插入
// 同时在这两个代码段的定义代码——GunDog类的两个方法——中,又没有以硬编码方式调用method1()、method2()方法
dog.info();
dog.run();
}
}
4.2. 已有方法的动态增强
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface TargetInterface {
public void save();
}
public class Target implements TargetInterface {
public void save() {
System.out.println("save running...");
}
}
// 增强代码
public class Advice {
public void before() {
System.out.println("前置增强......");
}
public void afterReturning() {
System.out.println("后置增强......");
}
}
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target(); //目标对象
Advice advice = new Advice(); // 增强对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标对象的类加载器
target.getClass().getInterfaces(), // 目标对象相同的接口字节码对象数组
new InvocationHandler() {
// 调用代理对象的任何方法,实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before();
method.invoke(target, args);
advice.afterReturning();
return null;
}
}
);
proxy.save();
}
}