0%

Java中的动态代理

在实际的项目开发中,会大量的用到代理模式。这一设计模式又与面向切面编程(AOP)紧密相关。
Java中可以通过静态代理或动态代理两种方式实现代理模式。其中静态代理容易理解,但由于需要编写大量代理类及代理方法代码,非常不利于维护;而动态代理的代理类在运行时生成,也不用编写大量重复性代码,相比静态代理有很大的优势。

动态代理涉及一个重要的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
1.抽象出公共的接口类,用于代理。
2.实现公共接口类,作为被代理的对象类。
3.实现```InvocationHandler```接口,并与公共接口类绑定。
4.通过```Proxy```生成动态代理的对象。
5.通过调用代理对象的接口方法,操控被代理对象。

下面通过一个例子来看看动态代理的具体实现:
首先我们定义一个```ICar```接口,抽象出汽车的两个行为——加速```speedUp```与减速```slowDown```:
```java
public interface ICar {
void speedUp();
void slowDown();
}

接下来实现一个具体的汽车类

1
2
3
4
5
6
7
8
9
10
11
12
13
```java
public class Benz implements ICar {

@Override
public void speedUp() {
System.out.println("Benz speedUp");
}

@Override
public void slowDown() {
System.out.println("Benz slowDown");
}
}

假设有这样的需求:在加速前,减速后进行纪录当前时间,记录车的时速等操作,
这些都与汽车加速/减速本身的机械动作(业务)无关,因此直接在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在这样情况下使用代理模式就可以避免侵入业务逻辑。对于动态代理的模式,在这时我们先实现```InvocationHandler```接口类:
```java
public class CarHandler implements InvocationHandler {

private final ICar target;

public CarHandler(ICar car) {
target = car;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("speedUp")) {
System.out.println("CarHandler before car speedUp");
}

Object result = method.invoke(target, args);

if (method.getName().equals("slowDown")) {
System.out.println("CarHandler after car slowDown");
}
return result;
}
}

这个类主要做了两件事情:
1.实现

1
2
3
这个方法中有三个参数:
proxy是实际的代理对象,一般情况下不会使用。特别是在调用method的invoke方法时,不要传这个参数,否则代理对象执行方法还会再执行进来,直接报stackOverFlow错误……
method代表通过代理对象调用的接口函数,例如```ICar```中的```speedUp```或```slowDown

args是调用method时传进的参数
2.另外一件事是通过构造方法,将被代理对象传进来。这样就可以将其传给method对象的invoke方法,来执行真实对象的相应方法了

由于我们需要在加速前和减速后做有关处理,因此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
同时```InvocationHandler```的```invoke```方法返回值应与实际调用方法返回值一致,因此将```method.invoke```的结果返回给上层。

至此准备工作就完成了,下面来看如何使用这个动态代理:
```java
public class TestMain {

public static void main(String[] args) {
ICar benzCar = new Benz(); // 首先生成被代理对象
CarHandler carHandler = new CarHandler(benzCar); // 创建InvocationHandler对象,将被代理对象传入绑定

ICar target = (ICar) Proxy.newProxyInstance(
benzCar.getClass().getClassLoader(),
benzCar.getClass().getInterfaces(),
carHandler); // 通过Proxy类的newProxyInstance静态方法,生成代理对象

target.speedUp(); // 通过代理对象进行操作
System.out.println();
target.slowDown();
}
}

1
2
1.被代理对象的ClassLoader
2.被代理对象所实现的接口。在本例中即```ICar

3.

1
2
通过这个静态方法返回代理对象,然后再通过代理对象调用各公共接口,触发```invoke```中编写的代理逻辑。
运行程序后输出:

CarHandler before car speedUp
Benz speedUp

Benz slowDown
CarHandler after car slowDown

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
通过这个例子可以看到使用代理模式,不需要实现每个代理方法(设想ICar有100个接口的情况)。同时代理对象在运行时动态生成,提高了灵活性。

这可以通过一个扩展例子体现:
新需求要求所有接口调用时都要记录日志,便于后期追踪问题。在静态代理模式下,我们不得不为每个接口方法添加log函数,有大量重复性工作。在动态代理的模式下,可以通过定义```InvocationHandler```的公共类来解决。

首先定义一个基础的```CommonHandler```类,实现代理的通用逻辑:
```java
public abstract class CommonHandler implements InvocationHandler {

private final Object target;

public CommonHandler(Object obj) {
target = obj;
}

public Object getTarget() {
return target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("CommonHandler print log before method invoke");
return null;
}
}

可以看到在

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
另外添加了```getTarget```方法,供外部访问```target```对像,同时```target```对象修改为```Object```类型。
接下来对```CarHandler```进行修改:
```java
public class CarHandler extends CommonHandler {

public CarHandler(ICar car) {
super(car);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
super.invoke(proxy, method, args);

if (method.getName().equals("speedUp")) {
System.out.println("CarHandler before car speedUp");
}

Object result = method.invoke(getTarget(), args);

if (method.getName().equals("slowDown")) {
System.out.println("CarHandler after car slowDown");
}
return result;
}
}

1
2

再次运行程序,输出如下:

CommonHandler print log before method invoke
CarHandler before car speedUp
Benz speedUp

CommonHandler print log before method invoke
Benz slowDown
CarHandler after car slowDown
`

在每个被代理的方法前都添加了日志。如果其他类型的代理对象需要这一逻辑,直接继承CommonHandler即可,不需要再实现同样的逻辑了。