23.0 第23章 动态代理 23.1 静态代理

第23章 动态代理

本章,我们来探讨Java中另外一个动态特性:动态代理。动态代理是一种强大的功能,它可以在运行时动态创建一个类,实现一个或多个接口,可以在不修改原有类的基础上动态为通过该类获取的对象添加方法、修改行为,这么描述比较抽象,下文会具体介绍。这些特性使得它广泛应用于各种系统程序、框架和库中,比如Spring、Hibernate、MyBatis、Guice等。

动态代理是实现面向切面的编程AOP(Aspect Oriented Programming)的基础。切面的例子有日志、性能监控、权限检查、数据库事务等,它们在程序的很多地方都会用到,代码都差不多,但与某个具体的业务逻辑关系也不太密切,如果在每个用到的地方都写,代码会很冗余,也难以维护,AOP将这些切面与主体逻辑相分离,代码简单优雅得多。

和注解类似,在大部分的应用编程中,我们不需要自己实现动态代理,而只需要按照框架和库的文档说明进行使用就可以了。不过,理解动态代理有助于我们更为深刻地理解这些框架和库,也能更好地应用它们,在自己的业务需要时,也能自己实现。

要理解动态代理,我们首先要了解静态代理,了解了静态代理后,我们再来看动态代理。动态代理有两种实现方式:一种是Java SDK提供的;另外一种是第三方库(如cglib)提供的。我们会分别介绍这两种方式,包括其用法和基本实现原理,理解了基本概念和原理后,我们来看一个简单的应用,实现一个极简的AOP框架。

23.1 静态代理

我们首先介绍代理。代理是一个比较通用的词,作为一个软件设计模式,它在《设计模式》一书中被提出,基本概念和日常生活中的概念是类似的。代理背后一般至少有一个实际对象,代理的外部功能和实际对象一般是一样的,用户与代理打交道,不直接接触实际对象。虽然外部功能和实际对象一样,但代理有它存在的价值,比如:
1)节省成本比较高的实际对象的创建开销,按需延迟加载,创建代理时并不真正创建实际对象,而只是保存实际对象的地址,在需要时再加载或创建。
2)执行权限检查,代理检查权限后,再调用实际对象。
3)屏蔽网络差异和复杂性,代理在本地,而实际对象在其他服务器上,调用本地代理时,本地代理请求其他服务器。

代理模式的代码结构也比较简单,我们看个简单的例子,如代码清单代码23-1所示。

代码清单23-1 静态代理示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class SimpleStaticProxyDemo {
static interface IService {
public void sayHello();
}
static class RealService implements IService {
@Overridepublic
void sayHello() {
System.out.println("hello");
}
}
static class TraceProxy implements IService {
private IService realService;
public TraceProxy(IService realService) {
this.realService = realService;
}
@Overridepublic
void sayHello() {
System.out.println("entering sayHello");
this.realService.sayHello();
System.out.println("leaving sayHello");
}
}
public static void main(String[] args) {
IService realService = new RealService();
IService proxyService = new TraceProxy(realService);
proxyService.sayHello();
}
}

代理和实际对象一般有相同的接口,在这个例子中,共同的接口是IService,实际对象是RealService,代理是TraceProxy。TraceProxy内部有一个IService的成员变量,指向实际对象,在构造方法中被初始化,对于方法sayHello的调用,它转发给了实际对象,在调用前后输出了一些跟踪调试信息,程序输出为:

1
2
3
entering sayHello
hello
leaving sayHello

我们在第12章介绍过两种设计模式:适配器和装饰器,它们与代理模式有点类似,它们的背后都有一个别的实际对象,都是通过组合的方式指向该对象,不同之处在于,适配器是提供了一个不一样的新接口,装饰器是对原接口起到了“装饰”作用,可能是增加了新接口、修改了原有的行为等,代理一般不改变接口。不过,我们并不想强调它们的差别,可以将它们看作代理的变体,统一看待。

在上面的例子中,我们想达到的目的是在实际对象的方法调用前后加一些调试语句。为了在不修改原类的情况下达到这个目的,我们在代码中创建了一个代理类TraceProxy,它的代码是在写程序时固定的,所以称为静态代理。

输出跟踪调试信息是一个通用需求,可以想象,如果每个类都需要,而又不希望修改类定义,我们需要为每个类创建代理,实现所有接口,这个工作就太烦琐了,如果再有其他的切面需求,整个工作可能又要重来一遍。这时,就需要动态代理了,主要有两种方式实现动态代理:Java SDK和第三方库cglib,我们先来介绍JavaSDK。