代理模式(Proxy Pattern)
代理模式是程序设计中的一种设计模式, 属于结构型模式.
为其他对象提供一种代理以控制对这个对象的访问, 换句话说, 就是实现代理的类代表他所代理的另一个类的功能.
组成:
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
优点:
- 职责清晰
- 真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
- 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
- 高扩展性
- 可以在目标对象实现的基础上,增强额外的功能操作,扩展目标对象的功能.
遵守开闭原则, 不修改别人已经写好的代码或者方法, 通过代理的方式来扩展该方法.
代理模式分为静态代理、动态代理。
- 静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
- 动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。
1. 静态代理
很多人的朋友圈中会有微商卖鞋, 他们从厂家拿货,然后在朋友圈中宣传,然后卖给熟人。
首先需要一个接口, 通用的接口是代理模式实现的基础.
接口命名为 SellShoes
, 功能为卖鞋, 参数是数量, 返回总价
public interface SellShoes {
int sell(int number);
}
接着还需要一个实现类, 一个名为 AdidasFactory
的类实现了这个接口, 表示微商从源头厂家拿货, 他实现了SellShoes
接口
public class AdidasFactory implements SellShoes {
//一双鞋150
@Override
public int sell(int number) {
System.out.println("[代理]从[阿迪]批发了"+number+"双鞋, 花了"+150*number+"元");
return 150*number;
}
}
下面编写代理类 AdidasStaticProxy
public class AdidasStaticProxy implements SellShoes {
private AdidasFactory adidasFactory;
public AdidasStaticProxy(AdidasFactory adidasFactory) {
this.adidasFactory = adidasFactory;
}
@Override
public int sell(int number) {
int sell = adidasFactory.sell(number);
System.out.println("[买家]从[代理]购买了"+number+"双鞋, 花了"+8*sell+"元");
return 8*sell;
}
}
AdidasStaticProxy
就是代理对象, 它有一个sell方法, 但是调用的是AdidasFactory
的sell方法, 并对返回的值进行处理.
现在我们编写测试代码
新建一个Customer
类代表顾客
public class Customer {
public static void main(String[] args) {
AdidasFactory adidasFactory = new AdidasFactory();
AdidasStaticProxy adidasStaticProxy = new AdidasStaticProxy(adidasFactory);
adidasStaticProxy.sell(5);
}
}
测试结果:
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
静态代理的优点
- 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
- 公共的业务由代理来完成,实现了业务的分工
- 公共业务发生扩展时变得更加集中和方便
静态代理的缺点
- 需要代理的类如果很多, 则需要为每一个类编写代理类, 工作量大, 开发效率低
2. 动态代理
上一节代码中 AdidasStaticProxy
类是代理,我们需要手动编写代码让 AdidasStaticProxy
实现 SellShoes
接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 SellShoes
接口的代理,而不需要去定义 AdidasStaticProxy
这个类。
假设微商小王拿到了Nike和Adidas两个厂家的代理. 我们进行代码的模拟。
直接借用上一节的接口SellShoes
另外编写一个工厂NikeFactory
实现上述接口
public class NikeFactory implements SellShoes {
//一双鞋100
@Override
public int sell(int number) {
System.out.println("[代理]从[耐克]批发了"+number+"双鞋, 花了"+100*number+"元");
return 100*number;
}
}
接下来需要编写小王, 这个动态代理人DynamicProxy
public class DynamicProxy implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("微商小王");
String name = target.getClass().getName();
log(name);
return method.invoke(target, args);
}
private void log(String message){
System.out.println("贩卖["+message+"]厂的鞋");
}
}
然后, 顾客可以买鞋了Customer
public class Customer {
public static void main(String[] args) {
NikeFactory nikeFactory = new NikeFactory();
AdidasFactory adidasFactory = new AdidasFactory();
DynamicProxy dynamicProxy = new DynamicProxy();
dynamicProxy.setTarget(adidasFactory);
SellShoes proxy = (SellShoes) Proxy.newProxyInstance(adidasFactory.getClass().getClassLoader(), adidasFactory.getClass().getInterfaces(), dynamicProxy);
proxy.sell(10);
dynamicProxy.setTarget(nikeFactory);
SellShoes proxy1 = (SellShoes) Proxy.newProxyInstance(nikeFactory.getClass().getClassLoader(), nikeFactory.getClass().getInterfaces(),dynamicProxy);
proxy1.sell(50);
}
}
输出结果:
我们并没有像静态代理那样为 SellShoes
接口实现一个代理类,但最终它仍然实现了相同的功能, 并且不同厂家都可以通过小王这一个代理来实现
小王不仅仅可以卖鞋子, 甚至可以卖衣服, 先编写一个卖衣服的接口SellClothes
public interface SellClothes {
int sell(int number);
}
创建品牌工厂来实现接口UNIQLOFactory
public class UNIQLOFactory implements SellClothes{
@Override
public int sell(int number) {
System.out.println("[代理]从[优衣库]批发了"+number+"件衣服, 花了"+50*number+"元");
return 50*number;
}
}
再次测试
public class Customer {
public static void main(String[] args) {
NikeFactory nikeFactory = new NikeFactory();
AdidasFactory adidasFactory = new AdidasFactory();
UNIQLOFactory uniqloFactory = new UNIQLOFactory();
DynamicProxy dynamicProxy = new DynamicProxy();
dynamicProxy.setTarget(adidasFactory);
SellShoes proxy = (SellShoes) Proxy.newProxyInstance(adidasFactory.getClass().getClassLoader(), adidasFactory.getClass().getInterfaces(), dynamicProxy);
proxy.sell(10);
dynamicProxy.setTarget(nikeFactory);
SellShoes proxy1 = (SellShoes) Proxy.newProxyInstance(nikeFactory.getClass().getClassLoader(), nikeFactory.getClass().getInterfaces(),dynamicProxy);
proxy1.sell(50);
dynamicProxy.setTarget(uniqloFactory);
SellClothes proxy2 = (SellClothes) Proxy.newProxyInstance(uniqloFactory.getClass().getClassLoader(), uniqloFactory.getClass().getInterfaces(),dynamicProxy);
proxy2.sell(50);
}
}
测试结果
可见, 我们仅仅通过小王这一个代理就可以代理不同品牌(同一接口的不同实现类), 甚至贩卖不同类型的物品(不同接口)
3. Proxy 和 InvocationHandler
3.1 Proxy
Proxy类就是用来创建一个真实对象的真实代理对象,提供了很多方法,最常用的就是newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法的作用就是创建一个代理类对象,总共有三个参数,含义如下:
- loader:一般指的是真实对象的ClassLoader对象
- interfaces:一般指的是真实对象所实现的接口.
- h: 指的就是实现了InvocationHandler接口的代理类调用处理类,表示的是当动态代理对象调用方法时会关联到哪个实现了InvocationHandler接口的对象上,并进行转发到invoke()方法中
3.2 InvocationHandler
InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。
- proxy 代理对象
- method 代理对象调用的方法
- args 调用的方法中的参数
因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
4. 实际场景应用
校验用户权限,每一个菜单请求,都要判断一下请求的用户是否有该菜单权限。菜单多了,代码冗余,且容易遗漏。
通过动态代理就可以实现为:每一个用户,每一个菜单的请求,都经过代理(proxy),由他判断是否有权限,调用者只需要调用,实现自己的逻辑,不关心权限问题。