文章目录
一、什么是代理模式
为某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
代理模式的主要优点是:
- 功能增强:可以在不修改真实主题的情况下,增加额外的功能。
- 控制访问:可以控制对真实主题的访问,例如,在访问真实主题之前检查权限。
- 延迟加载:可以延迟加载真实主题,直到真正需要时才加载。
代理模式的主要类型有:
- 静态代理:静态代理类在程序运行前就已经存在,一般这种代理类是由程序员来创建的。
- 动态代理:动态代理类的源码是在程序运行时生成的,需要用到 JDK 的一些反射类库。
使用代理模式时,需要注意以下几点:
- 代理模式使得代码更加灵活,但也增加了复杂性。因此,在不需要额外功能或控制访问的情况下,应避免使用代理模式。
- 在使用动态代理时,需要注意反射操作可能带来的性能问题。
总的来说,代理模式是一种非常有用的设计模式,它可以在不修改原始对象的情况下,为其添加额外的功能或控制其访问。但是,它也可能增加代码的复杂性,因此需要谨慎使用。
二、代理模式的结构
1、介绍
- Subject(抽象角色):通过接口或抽象类声明真实角色实现的业务方法。这是代理模式的基础,它定义了代理和真实对象都应该遵循的公共接口或抽象类。
- Proxy(代理角色):实现抽象角色,是真实角色的代理。它持有真实角色的实例,并在调用真实角色的业务逻辑方法前后添加一些额外的操作,如日志记录、权限校验、事务管理等。代理角色通过调用真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- RealSubject(真实角色):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。这是实际执行具体业务逻辑的对象。
2、代码实现样例
(1)静态代理
- 定义一个接口及其实现类:
java
// 抽象角色(接口)
public interface Subject {
void request();
}
// 真实角色
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Called RealSubject request()");
}
}
// 代理角色
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
// 在调用真实对象之前,可以添加一些操作
System.out.println("Before calling RealSubject request()");
realSubject.request();
// 在调用真实对象之后,可以添加一些操作
System.out.println("After calling RealSubject request()");
}
}
- 静态代理使用
java
public class StaticProxyDemo {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
ProxySubject proxySubject = new ProxySubject(realSubject);
proxySubject.request(); // 输出代理前后的额外信息和真实角色的信息
}
}
(2)动态代理
动态代理通常使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。以下是使用动态代理的示例:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 抽象角色(接口)
public interface Subject {
void request();
}
// 真实角色
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("Called RealSubject request()");
}
}
// 调用处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object subject;
public DynamicProxyHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用方法之前可以添加一些操作
System.out.println("Before calling method: " + method.getName());
Object result = method.invoke(subject, args); // 调用真实对象的方法
// 在调用方法之后可以添加一些操作
System.out.println("After calling method: " + method.getName());
return result;
}
}
// 使用动态代理
public class DynamicProxyDemo {
public static void main(String[] args) {
RealSubject realSubject = new RealSubject();
InvocationHandler handler = new DynamicProxyHandler(realSubject);
// 创建代理对象
Subject proxySubject = (Subject) Proxy.newProxyInstance(
RealSubject.class.getClassLoader(),
realSubject.getClass().getInterfaces(),
handler
);
// 调用代理对象的方法
proxySubject.request(); // 输出代理前后的额外信息和真实角色的信息
}
}
在这个动态代理的示例中,DynamicProxyHandler类实现了InvocationHandler接口,并重写了invoke方法。这个invoke方法会在每次调用代理对象的方法时被调用。在invoke方法中,你可以添加在调用真实对象方法前后的额外逻辑。
Proxy.newProxyInstance方法用于动态创建代理对象。它接受三个参数:类加载器、代理对象需要实现的接口数组、以及一个InvocationHandler对象。返回的是实现了指定接口的代理对象。
动态代理比静态代理更加灵活,因为它可以在运行时动态地创建代理对象,而无需为每个真实角色编写特定的代理类。但是,动态代理也有其局限性,比如它只能代理实现了接口的类。如果需要代理没有实现接口的类,则需要使用其他方法,如CGLIB等库。
三、代理模式的应用场景
代理模式的应用场景十分丰富,以下是一些具体的例子:
- 远程代理:
当你使用网络应用或在线服务时,客户端通常不会直接访问服务器上的对象,而是通过代理服务器进行中转。代理服务器隐藏了实际的网络通信细节,使得客户端能够更方便、安全地访问远程资源。 - 虚拟代理:
在图片加载应用中,对于大量或大尺寸的图片,如果一次性加载所有图片可能会导致内存不足或页面加载缓慢。这时,可以使用虚拟代理,先加载低分辨率或缩略图,当用户需要查看高清图片时,再加载真实的高清图片。
在视频流媒体服务中,为了节省带宽和提高用户体验,通常不会一次性加载整个视频文件。而是使用虚拟代理,先加载视频的元数据或初始片段,然后按需加载后续的视频内容。 - 安全代理:
在企业应用中,为了保护敏感数据,如用户密码或财务数据,可以使用安全代理来限制对这些数据的直接访问。代理会检查用户的权限,并只在用户拥有足够权限时才允许访问。
在一些金融交易系统中,安全代理可以用来监控和记录所有的交易操作,以防止未经授权的访问和恶意操作。 - 缓存代理:
在Web应用中,为了提高页面加载速度,通常会对一些频繁访问的数据进行缓存。这时,可以使用缓存代理来拦截对目标对象的请求,并检查是否已经有缓存的结果。如果有,则直接返回缓存的结果,避免重复计算或访问数据库。
在分布式系统中,缓存代理还可以用来减少跨网络的通信次数,提高系统的响应速度。 - 事务代理:
在数据库操作中,为了保证数据的完整性和一致性,通常会使用事务来包裹一系列的数据库操作。事务代理可以确保这些操作要么全部成功,要么全部失败回滚,从而避免数据的不一致状态。 - 文件访问代理:
在文件系统中,为了控制对文件的访问权限,可以使用文件访问代理。代理会检查用户的权限,并根据权限来决定是否允许读取、写入或删除文件。
这些例子只是代理模式应用场景的冰山一角,实际上,代理模式的应用非常广泛,几乎可以在任何需要间接访问或控制对象访问的场景中使用。