# 代理模式
# 定义代理模式
- 代理模式:为另一个对象提供一个替身,以让别人通过替身来控制对原本对象的访问;
- 一般来讲,被代理的对象会是远程的对象,创建开销比较大的对象,或者需要安全控制的对象;
- Subject 为 RealSubject 和 Proxy 提供了接口;
- 通过对统一接口的实现,Proxy 在 RealSubject 出现的地方能够替代它;
- Proxy 代理并其他对象对 RealSubject 的访问;
- Proxy 持有对 RealSubject 的引用,有时还会负责对他的创造和销毁;
- 所有客户想要与 RealSubject 交互必须通过 Proxy;
# 虚拟代理
- 虚拟代理作为创建开销比较大的对象的替身;
- 通常在使用虚拟代理时,我们会在真正需要 “被代理对象” 时才创建被代理对象;
- 在此之前,我们就用虚拟代理对象,来代替真正想要的对象;
- 等真正需要的对象被创建好后,客户的请求还会通过代理被委托到目标对象上;
# 加载 CD 封面
- 假如我想在一个音乐 APP 中加载 CD 封面;
- 因为网络请求会有一个等待时间,在真正的封面被加载来之前,我们需要一个虚拟代理,来先替代一下;
- 假设我们用 Swing 的 Icon 组件去显示封面;
- 再封面加载好之前,先用虚拟代理用 Icon 组件显示 “请稍等,封面加载中。。。”
# 保护代理
- 保护代理,是一种根据访问权限,决定客户是否被允许访问对象的代理;
- 🌰 例如:比如有一个对象,通过保护代理,雇员只可以访问其中是几个方法,而经理就可以访问全部的方法;
- 🌰 保护代理就像是一个明星的经纪人,它会拦截所有外面打进来的电话。然后做一个筛选,只把有用的电话转接给明星;
# Java 动态代理
- 我们可以利用 Java 的动态代理去实现保护代理;
- 动态代理,利用 Java 的反射技术(Java Reflection),在运行时创建一个动态代理类,实现一个或多个接口;
- 注意:动态代理的是接口,不是类,更不是抽象类;
- 于此同时,我们还要提供一个实现类 InvocationHandler 接口的实现类。客户对于 Proxy 上任何方法的调用都会被传入此类;
- 再通过 InvocationHandler 实现类来控制对 RealSubject 的对应方法的访问;
- 利用 Java 的 Proxy 类,调用
Proxy.newProxyInstance()
方法就可以创建动态代理; Proxy.newProxyInstance()
方法有三个参数:- 代理要实现的接口的 “类加载器”;
- 代理要实现的接口;
- InvocationHandler 接口实现类;
# 现实案例
- 现在我们有一个相亲 App;
- 里面每个人都可以给其他用户打分(喜欢 or 不喜欢);
- 每个用户的个人信息,自己都可以改。但是自己的分数不可以改;
- 其他用户不可以改别人的个人信息。但是可以给别人打分;
下面 👇 是用户类的接口 PersonBean:
- 根据需求我们知道,用户不能给自己评分,也不能改变别人的个人信息;用户只能给别人打分,并修改自己的信息;
- 可以使用保护代理来实现:
- 首先创建两个 InvocationHandler:
- OwnerInvocationHandler 用来处理用户对自己 PersonBean 对象的请求;
- NonOwnerInvocationHandler 处理用户对别人 PersonBean 对象的请求;
- 然后我们根据 PersonBean 接口创建一个动态代理;
- 首先创建两个 InvocationHandler:
首先创建 InvocationHandler:
- 当代理的方法被调用时,代理就会把这个调用转发给 InvocationHandler;
- InvocationHandler 接口中只有一个
invoke()
方法。不管代理被调用的是什么方法,最后 Handler 处理器被调用的一定是invoke
方法; - 之后
invoke
方法内部会去判断如何处理这个请求,可能会转发给 RealSubject 对象,也可能做其他的处理;
// InvocationHandler 是 java.lang.reflect 包的一部分
import java.lang.reflect.*;
public class NonOwnerInvocationHandler implements InvocationHandler {
//把RealSubject要实例进来
PersonBean person;
// 实例化时,保存对 RealSubject 的引用
public NonOwnerInvocationHandler(PersonBean person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 用户可以访问其他用户的信息
// 如果方法名是 get 开头,就允许调用
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
// 用户也可以给其他用户评分
} else if (method.getName().equals("setHotOrNotRating")) {
return method.invoke(person, args);
// 用户不可以改别人的信息,返回报错
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
}catch (InvocationTargetException e){
e.printStackTrace();
}
//调用其他方法直接不理
return null;
}
}
创建 Proxy 类,并实例化 Proxy 对象:
- 现在我们要根据 PersonBean 接口,去创建一个动态代理;
- 它会把客户对它的方法调用,转发给 👆 上面的两个 InvocationHandler;
- 需要分别创建两个方法用来获取 Proxy 类。一个针对 NonOwner,另一个针对 Owner;
- 使用
Proxy.newProxyInstance
来创建动态代理,分别接受三个参数:- 目标接口的类加载器;
- 目标接口;
- 调用处理器 InvocationHandler;
import java.lang.reflect.Proxy;
// 这个类是客户的类;
// 客户在里面通过 getOwnerProxy 和 getNonOwnerProxy 方法获取动态代理;
public class MatchMakingTestDrive {
public PersonBean getOwnerProxy(PersonBean person) {
// Proxy 代理是对 PersonBean 接口的实现;
// 所以这里可以进行强制转型;
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}
public PersonBean getNonOwnerProxy(PersonBean person) {
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}
}
在测试类里面去检测功能是否实现把: