代理模式是一种结构型设计模式,允许通过一个代理对象来控制对另一个对象的访问。代理对象通常会执行一些额外的操作,例如权限检查、延迟初始化、日志记录等。它在客户端和目标对象之间充当中介,能够为客户端提供更加灵活和安全的访问方式。
在代理模式中,代理对象与真实对象实现相同的接口或继承自同一个父类,这样代理对象就可以代替真实对象进行操作。
一、代理模式的类型
代理模式根据其功能和行为的不同,可以分为几种不同的类型:
-
静态代理(Static Proxy)
静态代理在编译时就已经确定了代理对象和真实对象的关系。在这种模式下,代理类和真实类会实现相同的接口,客户端可以通过代理类来访问真实类。静态代理的主要缺点是代理类需要与真实类一一对应,无法动态创建。
-
动态代理(Dynamic Proxy)
动态代理是在程序运行时,通过反射机制动态生成代理类,代理对象和真实对象之间的关系是在运行时确定的。Java中的
java.lang.reflect.Proxy
类和CGLib等技术提供了动态代理的支持。动态代理的最大优点是可以在运行时动态生成代理对象,且不需要手动创建多个代理类。 -
远程代理(Remote Proxy)
远程代理通常用于网络编程场景,它代表一个远程对象,在客户端与服务器之间充当代理,处理网络通信等细节。客户端通过代理对象访问远程服务器,代理会将请求转发到远程对象并返回结果。
-
虚拟代理(Virtual Proxy)
虚拟代理通常用于延迟加载的场景,它代理一个资源消耗较大的对象,只有在真正需要时才会创建真实对象。比如,图像加载、数据库查询等操作,可以在用户需要时再初始化资源,减少内存和计算的开销。
-
保护代理(Protection Proxy)
保护代理主要用于权限控制的场景,它控制对目标对象的访问,提供安全检查。客户端通过代理对象访问目标对象,代理在访问前进行身份验证、权限控制等安全操作,确保目标对象只能被授权的用户访问。
-
缓存代理(Cache Proxy)
缓存代理负责在内存中缓存真实对象的数据,以提高性能。它可以避免频繁的重复计算或从外部系统(如数据库)获取数据。当代理对象被请求时,如果缓存中有数据,它就直接返回缓存的数据;否则,它会请求真实对象并将结果缓存。
三、代理模式的结构
代理模式通常由以下几部分组成:
-
Subject(主题接口):定义代理对象和真实对象都要实现的接口,通常是目标对象和代理对象都要遵循的合同。
-
RealSubject(真实主题):目标对象,代理对象实际控制和访问的对象。真实对象包含真正的业务逻辑。
-
Proxy(代理类):代理对象,它通过实现与真实对象相同的接口来转发客户端的请求。在代理中,可能会添加一些附加的操作(如权限检查、日志记录、延迟加载等),然后转发给真实对象。
三、代理模式的应用实例
1. 静态代理
我们用购买火车票作为例子,代理类可以在调用真实对象的方法之前添加一些附加操作。
-
卖票接口
javapublic interface SellTickets { void sell(); }
-
火车站
javapublic class TrainStation implements SellTickets{ @Override public void sell() { System.out.println("火车站出票"); } }
-
代售点
javapublic class ProxyPoint implements SellTickets{ TrainStation trainStation = new TrainStation(); @Override public void sell() { System.out.println("代售点收取服务费"); trainStation.sell(); System.out.println("代售点将票给顾客"); } }
-
测试类
javapublic class Client { public static void main(String[] args) { ProxyPoint proxyPoint = new ProxyPoint(); proxyPoint.sell(); } }
-
输出结果
2. 动态代理
JDK动态代理
Java的动态代理通过反射机制,在运行时生成代理对象。java.lang.reflect.Proxy
类允许我们为接口创建动态代理类。
我们接着上述例子进行,创建卖票接口和火车站类。
-
代理工厂类
javapublic class ProxyFactory { private TrainStation trainStation = new TrainStation(); public SellTickets getProxyObject(){ /** * ClassLoader loader, 类加载器,直接通过对象获取 * Class<?>[] interfaces, 代理类实现接口的字节码对象,直接通过对象获取 * InvocationHandler h), 代理对象的调用处理程序 */ SellTickets proxyInstance = (SellTickets) Proxy.newProxyInstance( trainStation.getClass().getClassLoader(), trainStation.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //proxy:表示代理对象,即动态生成的代理类实例,是通过 Proxy.newProxyInstance() 方法创建的。 //method:表示被调用的方法,是一个 Method 对象,包含了目标方法的信息(方法名、参数等)。 //args:是一个 Object[] 数组,包含了调用该方法时传入的参数。 System.out.println("收取服务费"); Object object = method.invoke(trainStation, args); System.out.println("售票处出票"); return object; } } ); return proxyInstance; } }
-
测试类
javapublic class Client { public static void main(String[] args) { ProxyFactory proxyFactory = new ProxyFactory(); SellTickets sellTickets = proxyFactory.getProxyObject(); sellTickets.sell(); } }
-
运行结果
3. 虚拟代理
虚拟代理用于延迟加载,在访问时才会创建目标对象。比如在图像加载的场景中,只有当用户需要查看图片时,图像才会被加载。
java
interface Image {
void display();
}
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImage();
}
private void loadImage() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // 延迟加载
}
realImage.display();
}
}
// 客户端代码
public class ProxyImageExample {
public static void main(String[] args) {
Image image = new ProxyImage("image.jpg");
image.display(); // 第一次调用,加载图像并显示
image.display(); // 第二次调用,直接显示
}
}
第一次调用display()
时,代理对象才会创建真实对象,并加载图像,第二次调用则直接显示图像。
四、代理模式的优缺点
优点:
-
增加了对象的附加功能:通过代理对象,我们可以在不修改目标对象的情况下,为其添加额外的操作(如日志、安全检查、缓存等)。
-
控制访问:代理模式可以用于控制访问,尤其是在远程代理和保护代理中,可以对目标对象的访问进行限制和权限控制。
-
延迟加载:虚拟代理可以延迟加载目标对象,减少系统开销,尤其在某些资源消耗大的操作中表现突出。
-
简化客户端访问:客户端只需要通过代理访问目标对象,而不需要关心目标对象的复杂性或其创建过程。
缺点:
-
增加系统的复杂性:代理模式引入了代理对象,可能会使系统的结构变得更加复杂,特别是静态代理模式中需要为每个真实对象创建一个对应的代理类。
-
性能开销:代理模式通常会引入一些额外的处理(如方法拦截、权限验证等),可能会导致性能开销,尤其是在频繁使用代理的情况下。
-
不适用于所有场景:代理模式虽然灵活,但并不适用于每个场景。对于某些简单的系统来说,代理模式可能显得过于复杂,使用不当可能带来不必要的额外开销。