代理模式:控制对象访问的守门员🔐,优雅实现功能增强与访问控制!
文章目录
前言:为什么需要代理?🤔
各位宝子们,今天我们来聊一个设计模式界的"守门员"——代理模式!😎 还在为如何控制对象访问而头疼吗?还在为如何在不修改原有代码的情况下增加功能而烦恼吗?代理模式来拯救你啦!
代理模式是设计模式家族中的"访问控制专家",它能帮我们优雅地控制对对象的访问,同时还能在不修改原有代码的情况下增加新功能。今天就带大家彻底搞懂这个"看似简单,实则强大"的设计模式!💯
一、代理模式:访问控制的专家 🛡️
1.1 什么是代理模式?
代理模式(Proxy Pattern)是一种结构型设计模式,它允许通过创建一个代理对象来控制对其他对象的访问。就像现实生活中的经纪人、律师一样,代理对象充当了客户与目标对象之间的中介,客户不直接与目标对象交互,而是通过代理对象间接交互!🤝
1.2 为什么需要代理模式?
想象一下这些场景:
- 需要控制对敏感对象的访问权限
- 需要在访问对象时执行额外的操作(如日志记录、性能监控)
- 需要延迟加载大型资源对象
- 需要在远程服务器上执行操作
- 需要为对象添加功能,但不想修改原有代码
这些场景有什么共同点?它们都涉及到对对象访问的控制和增强。代理模式就是为这些场景量身定制的!🚀
二、代理模式的结构:中间人的艺术 🎭
代理模式包含以下几个角色:
- 抽象主题(Subject):定义了代理对象和真实对象的共同接口,这样就可以在任何使用真实对象的地方使用代理对象
- 真实主题(Real Subject):定义了代理对象所代表的真实对象,是最终要引用的对象
- 代理(Proxy):保存一个引用使得代理可以访问实体,并提供一个与Subject接口相同的接口,这样代理就可以用来替代实体
// 抽象主题
public interface Subject {
void request();
}
// 真实主题
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实主题处理请求");
}
}
// 代理
public class Proxy implements Subject {
private RealSubject realSubject;
public Proxy() {
this.realSubject = new RealSubject();
}
@Override
public void request() {
// 前置处理
preRequest();
// 调用真实主题的方法
realSubject.request();
// 后置处理
postRequest();
}
private void preRequest() {
System.out.println("代理前置处理");
}
private void postRequest() {
System.out.println("代理后置处理");
}
}
// 客户端代码
Subject proxy = new Proxy();
proxy.request();
// 输出:
// 代理前置处理
// 真实主题处理请求
// 代理后置处理
看到了吗?通过代理对象,我们可以在调用真实对象的方法前后添加自己的处理逻辑,而客户端对此一无所知!这就是代理模式的魅力所在!🎩✨
三、代理模式的三种类型:静态代理、动态代理与CGLIB代理 🔄
3.1 静态代理:编译时确定的代理
静态代理是最基础的代理模式,代理类在编译时就已经确定。上面的例子就是一个典型的静态代理。
优点:
- 实现简单,容易理解
- 可以在不修改目标对象的前提下扩展目标对象的功能
缺点:
- 代理类和真实主题类都实现了相同的接口,会产生很多代理类
- 接口增加方法时,代理类和真实主题类都要维护
3.2 动态代理:运行时生成的代理
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("真实主题处理请求");
}
}
// 动态代理处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object target; // 真实主题
public DynamicProxyHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理
System.out.println("动态代理前置处理: " + method.getName());
// 调用真实主题的方法
Object result = method.invoke(target, args);
// 后置处理
System.out.println("动态代理后置处理: " + method.getName());
return result;
}
}
// 客户端代码
Subject realSubject = new RealSubject();
InvocationHandler handler = new DynamicProxyHandler(realSubject);
// 创建动态代理
Subject proxy = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler
);
proxy.request();
// 输出:
// 动态代理前置处理: request
// 真实主题处理请求
// 动态代理后置处理: request
优点:
- 可以代理多个类,只需要一个代理处理器
- 可以在运行时动态地创建代理,无需手动编写代理类
缺点:
- 只能代理实现了接口的类
- 反射调用方法比直接调用方法性能差
3.3 CGLIB代理:基于继承的代理
CGLIB(Code Generation Library)是一个强大的高性能代码生成库,可以在运行时扩展Java类并实现接口。CGLIB通过生成目标类的子类来实现代理。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 真实主题(不需要实现接口)
public class RealSubject {
public void request() {
System.out.println("真实主题处理请求");
}
}
// CGLIB代理拦截器
public class CglibProxyInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置处理
System.out.println("CGLIB代理前置处理: " + method.getName());
// 调用真实主题的方法
Object result = proxy.invokeSuper(obj, args);
// 后置处理
System.out.println("CGLIB代理后置处理: " + method.getName());
return result;
}
}
// 客户端代码
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new CglibProxyInterceptor());
// 创建CGLIB代理
RealSubject proxy = (RealSubject) enhancer.create();
proxy.request();
// 输出:
// CGLIB代理前置处理: request
// 真实主题处理请求
// CGLIB代理后置处理: request
优点:
- 可以代理没有实现接口的类
- 性能比JDK动态代理更好
缺点:
- 不能代理final类和final方法
- 需要引入第三方库
四、代理模式实战:实际应用案例 💼
4.1 图片延迟加载
想象一个图片查看器应用,加载高清图片可能需要很长时间。我们可以使用代理模式来实现图片的延迟加载,只有当真正需要显示图片时才加载图片数据。
// 图片接口
public interface Image {
void display();
}
// 真实图片
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("加载图片: " + filename);
}
@Override
public void display() {
System.out.println("显示图片: " + filename);
}
}
// 代理图片
public 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();
}
}
// 客户端代码
Image image = new ProxyImage("高清图片.jpg");
// 图片未加载
System.out.println("图片未加载");
// 图片加载并显示
image.display();
// 再次显示图片(不会重新加载)
image.display();
这个例子展示了代理模式如何实现延迟加载,只有在真正需要时才创建昂贵的对象,提高了系统性能!🚀
4.2 权限控制
代理模式可以用来实现权限控制,只有具有特定权限的用户才能访问某些资源。
// 文档接口
public interface Document {
void read();
void write();
}
// 真实文档
public class RealDocument implements Document {
private String name;
public RealDocument(String name) {
this.name = name;
}
@Override
public void read() {
System.out.println("读取文档: " + name);
}
@Override
public void write() {
System.out.println("写入文档: " + name);
}
}
// 权限控制代理
public class ProtectionProxy implements Document {
private RealDocument realDocument;
private String userRole;
public ProtectionProxy(String documentName, String userRole) {
this.realDocument = new RealDocument(documentName);
this.userRole = userRole;
}
@Override
public void read() {
// 所有用户都可以读取文档
realDocument.read();
}
@Override
public void write() {
// 只有管理员可以写入文档
if ("admin".equals(userRole)) {
realDocument.write();
} else {
System.out.println("权限不足,无法写入文档");
}
}
}
// 客户端代码
Document adminDocument = new ProtectionProxy("敏感文件.txt", "admin");
adminDocument.read(); // 可以读取
adminDocument.write(); // 可以写入
Document userDocument = new ProtectionProxy("敏感文件.txt", "user");
userDocument.read(); // 可以读取
userDocument.write(); // 权限不足,无法写入
这个例子展示了代理模式如何实现权限控制,保护敏感资源不被未授权的用户访问!🔒
4.3 远程代理
远程代理可以隐藏远程对象的复杂性,使客户端感觉像是在调用本地对象。
// 服务接口
public interface Service {
String performAction(String data);
}
// 远程服务实现
public class RemoteService implements Service {
@Override
public String performAction(String data) {
return "处理数据: " + data;
}
}
// 远程代理
public class RemoteProxy implements Service {
private Service remoteService;
public RemoteProxy() {
// 在实际应用中,这里会通过网络连接到远程服务
// 这里简化为直接创建远程服务对象
this.remoteService = new RemoteService();
}
@Override
public String performAction(String data) {
System.out.println("远程代理: 准备发送数据到远程服务");
// 在实际应用中,这里会通过网络调用远程服务
String result = remoteService.performAction(data);
System.out.println("远程代理: 接收到远程服务的响应");
return result;
}
}
// 客户端代码
Service service = new RemoteProxy();
String result = service.performAction("测试数据");
System.out.println("结果: " + result);
这个例子展示了远程代理如何隐藏远程调用的复杂性,使客户端感觉像是在调用本地对象!🌐
五、代理模式在Java标准库中的应用 📚
5.1 Java动态代理
Java的java.lang.reflect.Proxy
类提供了创建动态代理的功能,这是Java标准库中代理模式的直接应用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 创建动态代理的工厂方法
public static <T> T createProxy(T target, Class<?>... interfaces) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
interfaces,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法: " + method.getName());
return method.invoke(target, args);
}
}
);
}
5.2 Spring AOP
Spring的面向切面编程(AOP)就是基于代理模式实现的。Spring AOP使用JDK动态代理或CGLIB来创建目标对象的代理,从而实现方法拦截和横切关注点的模块化。
// Spring AOP配置示例
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public MyAspect myAspect() {
return new MyAspect();
}
}
// 切面定义
@Aspect
public class MyAspect {
@Before("execution(* com.example.service.*.*(..))") // 切点表达式
public void before(JoinPoint joinPoint) {
System.out.println("前置通知: " + joinPoint.getSignature().getName());
}
}
5.3 JDBC连接池
JDBC连接池(如Apache DBCP、HikariCP等)使用代理模式来包装数据库连接,以便在连接关闭时将其返回到池中,而不是真正关闭。
// 简化的JDBC连接池代理示例
public class ConnectionProxy implements Connection {
private Connection realConnection;
private ConnectionPool pool;
public ConnectionProxy(Connection realConnection, ConnectionPool pool) {
this.realConnection = realConnection;
this.pool = pool;
}
@Override
public void close() throws SQLException {
// 不真正关闭连接,而是将其返回到连接池
pool.releaseConnection(this);
}
// 其他Connection方法的代理实现
// ...
}
六、代理模式的优缺点与适用场景 ⚖️
6.1 优点
- 职责清晰:真实主题就是实现实际的业务逻辑,不用关心其他非本职责的事务
- 高扩展性:在不修改目标对象的前提下,可以通过代理对象扩展目标对象的功能
- 智能化:代理类可以在调用目标方法前后做一些额外工作,如权限控制、日志记录等
6.2 缺点
- 增加复杂度:引入代理模式会增加系统的复杂度
- 请求处理速度可能变慢:因为代理对象会对请求进行一些处理,可能会导致请求处理速度变慢
- 实现复杂:某些代理模式的实现(如动态代理)可能比较复杂
6.3 适用场景
- 远程代理:为远程对象提供本地代表
- 虚拟代理:延迟加载大型资源对象
- 保护代理:控制对敏感对象的访问
- 智能引用:在访问对象时执行额外操作
- 缓存代理:为开销大的运算结果提供临时存储
七、代理模式与其他模式的对比 🔄
7.1 代理模式 vs 装饰器模式
- 代理模式:关注的是控制对对象的访问,可能不会添加新功能
- 装饰器模式:关注的是动态地给对象添加新功能,不改变其接口
7.2 代理模式 vs 适配器模式
- 代理模式:提供相同的接口,控制对对象的访问
- 适配器模式:提供不同的接口,使不兼容的接口可以一起工作
7.3 代理模式 vs 外观模式
- 代理模式:代理与真实对象实现相同的接口,一般只代理一个对象
- 外观模式:为子系统提供一个简化的接口,通常涉及多个对象
八、代理模式的最佳实践 🌟
- 选择合适的代理类型:根据需求选择静态代理、动态代理或CGLIB代理
- 保持接口的一致性:代理对象应该与真实对象实现相同的接口
- 注意性能影响:代理可能会影响性能,特别是在频繁调用的场景下
- 避免过度代理:不要创建代理的代理的代理…
- 考虑线程安全:在多线程环境下,确保代理的线程安全性
总结:代理模式,访问控制的优雅之道 🎯
代理模式是一种非常实用的设计模式,它让我们可以在不修改原有代码的情况下,控制对对象的访问,同时还能增加新功能。它在Java生态系统中应用广泛,从JDK的动态代理到Spring的AOP,再到各种连接池的实现,都能看到代理模式的身影。
在实际开发中,当你需要控制对对象的访问,或者在不修改原有代码的情况下增加新功能时,代理模式是一个非常好的选择!记住,好的设计模式就像好的工具一样,用在对的地方才能发挥最大的作用!🌈
下次当你想要控制对对象的访问时,先问问自己:“我是应该直接访问对象呢,还是应该使用代理模式呢?” 如果你需要在访问对象时执行额外的操作,那么代理模式可能是更好的选择!💪
希望这篇文章对你理解代理模式有所帮助!如果有任何问题,欢迎在评论区留言讨论!👇