结构型模式之代理模式(静态代理)
由于某些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称之为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。
代理其实是实现简介访问的媒介,当然在代理类中还可以在执行代理操作之前,之后,之中,环绕等执行相关动作。Spring 中面向切面编程就是这个原理
代理模式是一种应用很广泛的结构型设计模式,而且变化形式非常多,常见的代理形式包括远程代理、保护代理、虚拟代理、缓冲代理、智能引用代理等,后面将学习这些不同的代理形式
当使用代理类的时候, 真实类中的信息对用户来说是透明的(不可见的)
主要就是用于对象的间接访问提供了一个方案,可以对对象的访问进行控制
定义
- Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。
- Proxy(代理主题角色):它包含了对真实主题的引用,从而可以在任何时候操作真实主题对象;在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题角色中,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
- RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
实例
第一个例子
- 需求: 我们知道mac笔记本是在美国生产的,那么如果中国供销商想要卖mac笔记本,那么必须从美国供销商那里先进货,然后中国的顾客才可以在中国供销商买mac。这里的中国供销商就相当于代理,美国供销商就相当于真实主题角色
- Mac笔记本抽象接口(相当于其中的抽象主题)
1 | /* |
- 美国供销商(相当于这里RealSubject)
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
29
30
31/*
* 中国的笔记本,实现了MacBook 表示在中国买笔记本
* 但是中国想要买到苹果笔记本,那么还是需要先从美国进货,因此中国只是一个中间的代理作用而已
* 当然代理的最大作用就是在代理之前、之后、之中执行相关的操作,这就是面向切面编程的原理
*/
public class ChinaMac implements MacBook {
private MacBook mcBook=new USAMac(); //创建USAMac的对象
/**
* 在购买之前执行的操作
*/
public void preBuy(){
System.out.println("购买之前执行的操作");
}
/**
* 在购买之后执行的操作
*/
public void afterBuy(){
System.out.println("购买之后执行的操作");
}
public void buy() {
this.preBuy(); //之前执行的操作
mcBook.buy(); //在美国买笔记本
System.out.println("在中国买笔记本");
this.afterBuy(); //之后执行的操作
}
}
- 我们可以看到我们在使用代理模式的时候可以在之前和之后进行操作
测试类
- 我们在使用的时候直接使用代理类即可,我们根本不知道在真实类的使用,完全是代理类为我们提供了
1 | public class Client { |
第二个例子
我们登录一个网站的服务器端的验证步骤:
- 读取用户名和密码
- 验证用户名和密码
- 记录到日志中
这里的验证密码和记录到日志中可以在代理类中实现,在用户执行操作之前需要读取用户名和密码,并且验证,在操作之后需要将用户的一些操作记录到日志中。其实这里的真实用户需要做的只是执行自己的操作,而验证和记录都是交给代理类实现的。
实现
- 用户接口(User)
1 | /* |
- 真实的用户类(实现了用户接口)
- 主要的做的就是执行自己的操作
1 | public class RealUser implements User { |
- 代理类(实现了User接口)
- 在执行操作之前验证密码和用户名是否正确
- 在执行操作之后记录到日志中
- 实际上这里就是面向切面编程
1 | public class ProxUser implements User { |
- 测试类
- 实际上执行了验证用户名和密码,记录日志的操作,但是对于客户端来说只能看到自己执行的操作
1 | public class Client { |
缺点
- 如果增加一个接口就需要增加一个代理类,如果是要增加很多,那么就要增加很多代理类,代码将会重复
解决方法
- 下面我们将会讲解到动态代理,仅仅需要一个代理类即可
结构型模式之动态代理模式
前面我们说的代理模式其实是属于静态代理模式,就是说在程序执行之前已经写好了代理类,但是缺点也是说过,必须为每个接口都实现一个代理类,如果有多个接口需要代理,那么代码肯定是要重复的,因此就需要动态代理了。
动态代理可以实现多个接口共用一个代理类,只需要改变初始化的参数即可,可以省去很多的重复的代码。
JDK的动态代理需要一个类一个接口,分别为Proxy和InvocationHandler
主要原理就是利用了反射的原理
InvocationHandler
- 这个是代理类必须实现的接口,其中有一个方法
public Object invoke(Object proxy,Method method,Object[] args)
Object proxy
:指被代理的对象。Method method
:要调用的方法Object[] args
:方法调用时所需要的参数
Proxy
- Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader loader
:类加载器Class<?>[] interfaces
:得到全部的接口InvocationHandler h
:得到InvocationHandler接口的子类实例
实例
- 肯德基的接口
1 | /* |
- 肯德基的实现类(RealSubject)
1 | /* |
- 苹果笔记本的接口
1 | /* |
- 美国供销商的类(RealSubject)
1 | /* |
- 动态代理的类(实现了InvocationHandler接口)
1 | import java.lang.reflect.InvocationHandler; |
- 测试类
1 | import java.lang.reflect.Proxy; |
总结
- 动态代理的好处
- 即使有多个接口,也仅仅只有一个动态代理类
笔者有话说
- 最近建了一个微信交流群,提供给大家一个交流的平台,扫描下方笔者的微信二维码,备注【交流】,我会把大家拉进群