设计模式之禅——代理模式

引:如果你不想干就找个代理帮你干好了,就像游戏不想自己升级就找个代理帮你升级好了,这就是代理模式

定义

为其他对象提供一种代理以控制对这个对象的访问。——结构类。

它的通用类图如下:

proxy

我们可以看到类图中的三个角色:

  • Subject抽象主题角色:它可以是抽象类也可以是接口,是一个最普通的业务类型定义。
  • RealSubject具体主题角色:也叫做被委托角色、被代理角色。
  • Proxy代理主题角色:也叫做委托类、代理类。它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

下面是它的通用实现代码:

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
// 抽象主题类
public interface Subject {
// 定义一个方法
public void request();
}

// 真实主题类
public class RealSubject implements Subject {
// 实现方法
@Override
public void request() {
// 业务逻辑处理
}
}

// 代理类
public class Proxy implements Subject {
// 要代理哪个实现类
private Subject subject = null;
// 默认被代理者
public Proxy() {
this.subject = new Proxy();
}
// 通过构造函数传递被代理者
public Proxy(Subject _subject) {
this.subject = _subject;
}
// 实现接口中定义的方法
@Override
public void request() {
this.before();
this.subject.request();
this.after();
}
// 预处理
private void before() {
// do something
}
// 善后处理
private void after() {
// do something();
}
}

应用

优点

  1. 职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务。
  2. 高扩展性,无论具体角色怎么变化。

使用场景

典型的Spring AOP

扩展

普通代理

要求只能访问代理角色,而不能访问真实角色,真实角色的创建是在代理的内部创建的。在该模式下,调用者只知代理而不用知道真实的角色是谁,屏蔽了真实角色的变更对高层模块的影响。

ps:在实际项目中,一般是通过约定来禁止new一个真实的角色。

强制代理

要求必须通过真实角色找到一个代理角色,否则不能直接访问,即要求真实角色有一个赋予自己一个代理的方法。

代理增强

一个类可以实现多个接口,代理类也可以实现多个主题接口,实现不同的任务。而且代理的目的是在目标对象方法的基础上做增强,这种增强的本质通常是对目标对象的方法精心拦截和过滤。代理类可以为真是角色预处理消息、过滤消息、消息转发、事后处理消息等功能、

动态代理

动态代理是在实现阶段不用关心代理谁,而是在运行阶段才指定代理哪一个对象,即面向切面编程,也叫AOP。

下面是它的通用类图:

dynamicProxy

两条独立发展的线路。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者没有必然的相互耦合的关系。通知Advice从另一个切面切入,最终在高层模块Client进行耦合,完成逻辑的封装任务。下面是他的通用实现代码:

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
65
66
67
68
// 抽象主题
public interface Subject {
// 业务操作
public void doSomething(String str);
}

// 真实主题
public class RealSubject implements Subject {
// 业务操作
public void doSomething(String str) {
System.out.println("do Something!---->" + str);
}
}

// 动态代理的Handler
public class MyInvocationHandler implements InvocationHandler {
// 被代理的对象
private Object target = null;
// 通过一个构造函数传递一个对象
public MyInvocationHandler(Object _obj) {
this.target = _obj;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.target,args);
}
}

// 动态代理类
public class DynamicProxy<T> {
public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) {
// Spring AOP知识。
if (true) {
// 执行一个前置通知
(new BeforeAdvice()).exec();
}
// 执行目标,并返回结果
return (T) Proxy.newProxyInstance(loader,interfaces,h);
}
}

// 通知接口及实现
public interface IAdvice {
// 通知只有一个方法,执行即可
public void exec();
}
public class BeforeAdvice implements IAdvice {
public void exec() {
System.out.println("我是前置通知,我被执行了!");
}
}

// 动态代理的场景类
public class Client {
public static void main(String[] args) {
// 定义一个主题
Subject subject = new RealSubject();
// 定义一个Handler
InvocationHandler invocationHandler = new MyInvocationHandler(subject);
// 定义主题的代理
Subject proxy = DynamicProxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),invocationHandler);
// 代理的行为
proxy.doSomething("Finish");
}
}

// 输出
我是前置通知,我被执行了!
do Something!---->Finish

subject.getInterfaces()是查找该类的所有接口,然后实现接口的所有方法,然后又InvocationHandler实现该类的所有的方法,由其invoke方法接管所有方法的实现。其动态调用过程如下:

Client -> dynamicProxy -> MyInvocationHandler -> RealSubject

从上面可以看出要实现动态代理的首要条件时:被代理类要实现一个接口。

最佳实践

代理模式应用得非常广泛,大到一个系统框架、企业平台,小到代码片段、事务处理,都会用到代理模式。特别是在Sping AOP里面,如果调试看到$Proxy()这个东西,它应该就是一个动态代理了。

在学习AOP框架时,弄清楚几个名词就成:切面(Aspect)、切入点(JoinPoint)、通知(Advice)、织入(weave)就足够了。

参考

  1. 《设计模式之禅》