无涯

无所谓无 无所谓有

设计模式-代理模式Java语言实现

前言

关于代理模式的介绍,可以看我的另外一篇文章设计模式-代理模式

这篇文章主要以代码演示Java语言的实现。

介绍

代理又可以分为静态代理和动态代理两种,下面大概说下两者的主要区别

  1. 静态代理,被代理类必须基于接口实现,必须编写代理类,代理类与被代理类实现相同的接口。
  2. 动态代理
    1. JDK,被代理类必须基于接口实现,不用编写代理实现,需要编写InvocationHandler实现
    2. CgLib,可以代理类、接口,需要编写MethodInterceptor实现

静态代理

电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如提供按摩椅,娃娃机(这个每次去电影院都会尝试下,基本上是夹不起来,有木有大神可以传授下诀窍),卖爆米花、饮料(贵的要死,反正吃不起)等。我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?然后在影片开始结束时播放一些广告。

下面我们通过代码来模拟下电影院这一系列的赚钱操作。

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Cnima,代表电影播放的能力。

1
2
3
4
5
6
7
8
9
public interface Cinema {

/**
* 播放电影
*
* @param filmName name
*/
void play(String filmName);
}
  • 接下来我们要创建一个真正的实现这个 Cinema 接口的类,和一个实现该接口的代理类。

真正的实现类

1
2
3
4
5
6
7
public class DefaultCinema implements Cinema {

@Override
public void play(String filmName) {
System.out.println("现在正在播放电影:" + filmName);
}
}
  • 代理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CinemaProxy implements Cinema {

private final Cinema cinema;

public CinemaProxy() {
this.cinema = new DefaultCinema();
}

@Override
public void play(String filmName) {
CinemaProxyTask.playStartAd();
cinema.play(filmName);
CinemaProxyTask.playEndAd();
}
}
  • 代理任务类
1
2
3
4
5
6
7
8
9
10
public class CinemaProxyTask {

public static void playStartAd() {
System.out.println("正在播放片头广告");
}

public static void playEndAd() {
System.out.println("正在播放片尾广告");
}
}
  • 测试类
1
2
3
4
5
6
7
8
9
10
11
12
public class MainTest {

public static void main(String[] args) {
String filmName = "中国医生";
staticProxy(filmName);
}

public static void staticProxy(String filmName) {
Cinema proxyCinema = new CinemaProxy();
proxyCinema.play(filmName);
}
}

image-20220102164025624

  • 运行结果
1
2
3
正在播放片头广告
现在正在播放电影:中国医生
正在播放片尾广告

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。这个就是是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 CinemaProxy 这个类。

优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

缺点

  • 代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护。

与装饰者模式的比较

  • 装饰静态代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。

JDK 动态代理

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由 Java 反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为 Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的 Proxy 类和InvocationHandler 接口提供了生成动态代理类的能力。

还是刚才的Cinema接口,我们编写个动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class JdkCinemaProxy implements InvocationHandler {

private final Object object;

public JdkCinemaProxy(Object object) {
this.object = object;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
if (method.getName().equals("play")) {
CinemaProxyTask.playStartAd();
invoke = method.invoke(object, args);
CinemaProxyTask.playEndAd();
}
return invoke;
}
}

这个invoke方法有很多参数

  • proxy,这个是生成的代理类的实例
  • method,表示代理的方法
  • args,表示传入方法的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public class MainTest {

public static void main(String[] args) {
String filmName = "中国医生";
dynamicJdkProxy(filmName);
}

public static void dynamicJdkProxy(String filmName) {
// 保存动态生成的代理类
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
DefaultCinema defaultCinema = new DefaultCinema();
JdkCinemaProxy jdkCinemaProxy = new JdkCinemaProxy(defaultCinema);
// 动态生成代理类,返回的Object 就是生成的代理类实例
Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{Cinema.class}, jdkCinemaProxy);
Cinema cinema = (Cinema) o;
cinema.play(filmName);
}
}
  • newProxyInstance,方法需要你传入,类加载器、需要代理的接口,以及处理的InvocationHandler
  • 运行时会生成代理类,并实时加载进JVM。

我们来看下动态生成的代理类。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public final class $Proxy0 extends Proxy implements Cinema {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void play(String var1) throws {
try {
super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (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"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("top.oneyoung.design.proxy.Cinema").getMethod("play", Class.forName("java.lang.String"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

  • 我们可以看到,该类有四个成员变量分别对应Object的三个公共方法以及一个Cinema接口的play方法。
  • 构造方法需要传入IvocationHandler,并继续传入父类的构造方法。这里传入的IvocationHandler的其实就是我们刚才编写的JdkCinemaProxy
  • 代理类的play方法,super.h.invoke(this, m3, new Object[]{var1});
    • super.h就是我们编写的JdkCinemaProxy
    • 然后调用invoke方法,完成了代理。

至此整个流程就清晰了。这就是 jdk 的动态代理。

可以看到,JDK的动态代理其实就是自动帮我们生成了代理类,避免我们手动编写。

CgLib动态代理

// TODO