设计模式(二)代理模式

假设我们需要转账,转账有支付宝和微信支付两种方式,两种方式的转账前都需要检查一下账户信息,转账后显示余额。我们可以提供一个代理人,专门帮我们做检查账户信息和显示余额这种切面工作,而转账类(无论是支付宝还是微信支付)只负责转账本身。这就是代理模式。

静态代理

首先提供一个转账接口

1
2
3
4
public interface Transfer {
// 转账
void transfer(int amount);
}

支付宝类和微信支付类分别有他们自己的转账实现

支付宝

1
2
3
4
5
6
7
public class AliPayTransfer implements Transfer {
@Override
public void transfer(int amount) {
System.out.println("使用支付宝转出了 " + amount + " 元");
}

}

微信支付

1
2
3
4
5
6
public class WechatPayTransfer implements Transfer {
@Override
public void transfer(int amount) {
System.out.println("使用微信支付转出了 " + amount + " 元");
}
}

我们还需要一个支付代理人,帮我们做转账前的检查账户和转账后的显示余额:

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
public class TransferProxy implements Transfer {

private Transfer transfer;

// 支付宝转账还是微信转账,是通过构造方法传递进来给代理人的
public TransferProxy(Transfer transfer) {
this.transfer = transfer;
}

@Override
public void transfer(int amount) {
before();
this.transfer.transfer(amount);
after();
}

private void before(){
System.out.println("检查账户");
}

private void after(){
System.out.println("显示余额");
}

}

现在,可以开始转账了:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {

// 支付宝转账 100 元
Transfer aliTransfer = new AliPayTransfer();
Transfer transferProxy = new TransferProxy(aliTransfer);
transferProxy.transfer(100);

// 微信转账 100 元
Transfer wechatTransfer = new WechatPayTransfer();
Transfer transferProxy2 = new TransferProxy(wechatTransfer);
transferProxy2.transfer(100);

}

这就是静态代理,无论我们选用何种方式转账,都交给代理帮我们负责售前、转账、售后服务。但是,假设未来我们增加了现金交易,需要在 TransferProxy 中加入现金交易的构造方法,再之后,加入网银、云闪付等等,无疑我们的代理类会越来越沉重。于是,有没有一种方法,可以让代理类在运行时动态地知道即将进行的是何种方式的转账,这样就不用在代理类编写很多转账类了。动态代理就是这样来的。


动态代理

首先,还是一个接口

1
2
3
4
public interface Transfer {
// 转账
void transfer(int amount);
}

支付宝转账

1
2
3
4
5
6
7
public class AliPayTransfer implements Transfer {
@Override
public void transfer(int amount) {
System.out.println("使用支付宝转出了 " + amount + " 元");
}

}

微信支付转账

1
2
3
4
5
6
public class WechatPayTransfer implements Transfer {
@Override
public void transfer(int amount) {
System.out.println("使用微信支付转出了 " + amount + " 元");
}
}

动态代理人

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
import java.lang.reflect.Proxy;

public class TransferDynamicProxy {

public Object getTransferWay(Transfer transfer){
return Proxy.newProxyInstance(transfer.getClass().getClassLoader(),
new Class[]{Transfer.class},
(proxy, method, args) -> {
before();
Object invoke = method.invoke(transfer, args);
after();
return invoke;
});

}

private void before(){
System.out.println("检查账户");
}

private void after(){
System.out.println("显示余额");
}

}

开始转账

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {

// 使用支付宝转账
Transfer transferDynamicProxy = (Transfer) new TransferDynamicProxy().getTransferWay(new AliPayTransfer());
transferDynamicProxy.transfer(100);

// 使用微信支付
Transfer transferDynamicProxy2 = (Transfer) new TransferDynamicProxy().getTransferWay(new WechatPayTransfer());
transferDynamicProxy2.transfer(100);
}

我们只需要给代理人 Transferproxy 类传入不同的转账类实例(微信还是支付宝),动态代理类就会对应地去生成具体代理类,然后通过具体代理类进行相关转账前、中、后操作。这就是动态代理。


Proxy 类

在上面 getTransferWay 中,我们接收一个真实的转账类(支付宝转账类),并动态地生成对应的代理类(支付宝转账代理类)。这个工作由 java.lang.reflect.Proxy 来实现。

在 Proxy 类中,提供了 static 方法 newProxyInstance :

1
2
3
4
5
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException

该方法通过反射生成具体的代理类,接收三个参数:

  1. 类加载器
  2. 代理类实现的所有接口
  3. 要处理的事情(InvocationHandler)

InvocationHandler 本质上是一个函数式接口,表示要处理的事情

1
2
3
public interface InvocationHandler {  
public void invoke(Object o, Method m);
}

面向切面编程(AOP)

在 Spring AOP 中,正是通过动态代理来实现切面功能的,例如日志记录,事务等。


MyBatis 为什么通过一个接口就能访问数据库?