1 代理模式介绍
代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对其它对象的访问,常用于无法直接访问某个对象或访问某个对象不方便的情况。
代理模式分为静态代理和动态代理。
📌 场景
远程代理:代理对象作为客户端与远程对象进行交互,例如远程方法调用(RPC)。
虚拟代理:代理对象作为原始对象的代表,延迟原始对象的创建,例如图片加载等。
安全代理:代理对象控制对原始对象的访问,例如权限控制。
智能代理:代理对象提供额外的功能,例如缓存、日志记录、性能优化等。
如Spring AOP(面向切面编程)中的动态代理,Hibernate 中的延迟加载(虚拟代理),Java RMI(远程方法调用)中的远程代理等。
📌 优点
代理模式可以控制对真实对象的访问。代理对象可以在访问真实对象之前或之后,执行一些额外的操作,比如权限控制、缓存等;
代理模式可以隐藏真实对象的实现细节,使客户端只需与代理对象交互,从而降低了客户端与真实对象之间的耦合度。
📌 缺点
代理模式会增加系统的复杂度。由于引入了代理对象,系统的结构会变得更加复杂,这可能会增加系统的维护成本;
代理模式可能会影响系统的性能。在访问真实对象之前或之后,代理对象可能需要执行一些额外的操作,这可能会降低系统的性能;
代理模式需要额外的代码。引入代理对象需要编写额外的代码,这可能会增加开发成本。
2 静态代理
2.1 静态代理角色
动作:一般使用接口或者抽象类来实现;
真实角色:被代理的角色;
代理角色:代理真实角色(真实角色后 , 一般会做一些附属的操作);
客户:使用代理角色来进行一些操作。
2.2 静态代理实现
以房产中介帮房东代理租房为例:
📌 1.定义租赁操作
/**
* 租赁操作
*/
public interface Rent {
/**
* 租房
*/
void rentHouse();
}
📌 2.定义房东
/**
* 房东
*/
public class Landlord implements Rent{
@Override
public void rentHouse() {
System.out.println("房东出租房子");
}
}
📌 3.定义中介
/**
* 中介
*/
public class Intermediary implements Rent{
/**
* 房东
*/
private Landlord landlord;
public Intermediary() {
}
public Intermediary(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rentHouse() {
// 看房
seeHouse();
// 签合同
contract();
// 租房
landlord.rentHouse();
// 收取费用
toll();
}
/**
* 看房
*/
public void seeHouse() {
System.out.println("中介带你看房");
}
/**
* 签合同
*/
public void contract() {
System.out.println("签租赁合同");
}
/**
* 收取费用
*/
public void toll() {
System.out.println("收中介费");
}
}
📌 4.使用
// 中介给房东代理
Intermediary inter = new Intermediary(new Landlord());
// 租房。不用面对房东,直接找中介租房即可
inter.rentHouse();
租客直接接触的是中介,见不到房东,但是租客依旧通过代理租到了房东的房子。
不过静态代理中,类变多了,多了代理类,工作量变大了,开发效率降低。此时可以使用动态代理来处理这个问题。
3 动态代理
3.1 动态代理技术
动态代理的角色和静态代理的一样;
动态代理的代理类是动态生成的,静态代理的代理类是提前写好的;
动态代理分为两类 : 一类是基于接口 , 一类是基于类
基于接口的动态代理:JDK 动态代理;
基于类的动态代理:CGLIB 动态代理;
现在用的比较多的是 Javassist 来生成动态代理。
这里使用 JDK 的原生代码来实现,其余的道理都是一样。
📌 Proxy-代理类
Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler。
📌 newProxyInstance 方法
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
参数:
loader - 类加载器来定义代理类。
interfaces - 代理类实现的接口列表。
h - 调度方法调用的调用处理函数。
返回值:
具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例。
异常:
IllegalArgumentException:非法参数异常。
📌 InvocationHandler-调用处理程序
InvocationHandler 是由代理实例的调用处理程序实现的接口(代理类实现该接口)。每个代理实例都有一个关联的调用处理程序。
invoke 方法:
Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable
处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
参数:
proxy - 调用该方法的代理实例。
method - 所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
args - 包含的方法调用传递代理实例的参数值的对象的阵列,或 null 如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer 或 java.lang.Boolean。
3.2 动态代理实现
还是以房产中介帮房东代理租房为例:
📌 1.定义租赁操作
/**
* 租赁操作
*/
public interface Rent {
/**
* 租房
*/
void rentHouse();
}
📌 2.定义房东
/**
* 房东
*/
public class Landlord implements Rent{
@Override
public void rentHouse() {
System.out.println("房东出租房子");
}
}
📌 3.定义中介
/**
* 中介
*/
public class Intermediary implements InvocationHandler {
/**
* 租赁操作
*/
private Rent rent;
/**
* 代理租赁
*
* @param rent 需要租赁的对象
*/
public void setRent(Rent rent) {
this.rent = rent;
}
/**
* 生成代理对象
*
* @return 代理对象
*/
public Object getProxy() {
// 重点是第二个参数,获取要代理的抽象角色,之前都是一个角色,现在可以代理一类角色
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
}
/**
* 处理代理实例上的方法调用并返回结果
*
* @param proxy 代理类
* @param method 代理类的调用处理程序的方法对象
* @param args 包含的方法调用传递代理实例的参数值的对象的阵列
* @return 代理对象
* @throws Throwable 错误
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 看房
seeHouse();
// 签合同
contract();
// 动态代理租房业务:本质利用反射实现
Object result = method.invoke(rent, args);
// 收取费用
toll();
return result;
}
/**
* 看房
*/
public void seeHouse() {
System.out.println("中介带你看房");
}
/**
* 签合同
*/
public void contract() {
System.out.println("签租赁合同");
}
/**
* 收取费用
*/
public void toll() {
System.out.println("收中介费");
}
}
📌 4.加房产商
/**
* 房产商
*/
public class RealEstate implements Rent{
@Override
public void rentHouse() {
System.out.println("房产商出租房子");
}
}
📌 5.租客租房
// 房东
Landlord landlord = new Landlord();
// 中介
Intermediary intermediary = new Intermediary();
// 中介给房东提供代理服务
intermediary.setRent(landlord);
Rent proxy1 = (Rent) intermediary.getProxy();
// 代理类执行租房操作
proxy1.rentHouse();
// 房产商
RealEstate realEstate = new RealEstate();
// 中介给房产商提供代理服务
intermediary.setRent(realEstate);
Rent proxy2 = (Rent) intermediary.getProxy();
// 代理类执行租房操作
proxy2.rentHouse();
控制台输出:
一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
📌 注意事项
和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。
和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。