代理模式
大约 9 分钟
代理模式
什么是代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在客户端和目标对象之间起到中介的作用,并且可以添加额外的功能。
代理模式的核心思想是:
- 为其他对象提供一种代理以控制对这个对象的访问
- 在客户端和目标对象之间起到中介的作用
- 可以在不改变目标对象的情况下增加额外的功能
为什么需要代理模式
在实际开发中,我们经常需要在访问对象时添加一些额外的控制逻辑,比如:
- 延迟加载(懒加载)
- 访问控制(权限检查)
- 日志记录
- 远程代理
- 智能引用
代理模式通过创建一个代理对象来控制对真实对象的访问,可以在不修改真实对象的情况下添加这些额外的功能。
代理模式的结构
代理模式包含以下几个角色:
- 抽象主题(Subject):声明了真实主题和代理主题的共同接口
- 真实主题(RealSubject):定义了代理主题所代表的真实对象
- 代理主题(Proxy):保存一个引用使得代理可以访问实体,并提供一个与Subject的接口相同的接口
代理模式的实现
基本实现
// 抽象主题接口
interface Subject {
void request();
}
// 真实主题
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实主题处理请求");
}
}
// 代理主题
class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.request();
postRequest();
}
private void preRequest() {
System.out.println("代理前置处理");
}
private void postRequest() {
System.out.println("代理后置处理");
}
}
// 使用示例
public class ProxyDemo {
public static void main(String[] args) {
Subject subject = new Proxy();
subject.request();
}
}
实际应用示例
// 图片加载示例
// 图片接口
interface Image {
void display();
}
// 真实图片类
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("从磁盘加载图片: " + filename);
// 模拟加载时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void display() {
System.out.println("显示图片: " + 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 ImageProxyDemo {
public static void main(String[] args) {
Image image1 = new ProxyImage("photo1.jpg");
Image image2 = new ProxyImage("photo2.jpg");
// 第一次显示图片,会加载
System.out.println("第一次显示图片1:");
image1.display();
System.out.println();
// 第二次显示图片,不会重新加载
System.out.println("第二次显示图片1:");
image1.display();
System.out.println();
// 显示图片2
System.out.println("显示图片2:");
image2.display();
}
}
代理模式的应用场景
- 远程代理:为一个位于不同的地址空间的对象提供一个本地的代表对象
- 虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象
- 保护代理:控制对原始对象的访问,保护代理用于对象应该有不同的访问权限的时候
- 智能引用:取代了简单的指针,它在访问对象时执行一些附加操作
- 防火墙代理:保护目标不让恶意用户接近
- 同步代理:在多线程应用中为主题提供安全的访问
代理模式的优缺点
优点
- 职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务
- 代理对象可以在客户端和目标对象之间起到中介的作用:保护目标对象
- 高扩展性:代理模式符合开闭原则,可以在不修改目标对象的情况下增加新的功能
- 智能化:代理可以在运行时动态地添加或删除功能
缺点
- 增加系统复杂度:由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢
- 实现代理模式需要额外的工作:有些代理模式的实现非常复杂
代理模式的类型
1. 静态代理
// 静态代理示例
// 用户服务接口
interface UserService {
void addUser(String username);
void deleteUser(String username);
String getUser(String username);
}
// 真实用户服务
class RealUserService implements UserService {
@Override
public void addUser(String username) {
System.out.println("添加用户: " + username);
}
@Override
public void deleteUser(String username) {
System.out.println("删除用户: " + username);
}
@Override
public String getUser(String username) {
System.out.println("获取用户: " + username);
return "用户信息: " + username;
}
}
// 用户服务代理
class UserServiceProxy implements UserService {
private UserService userService;
private String currentUser;
public UserServiceProxy(UserService userService, String currentUser) {
this.userService = userService;
this.currentUser = currentUser;
}
@Override
public void addUser(String username) {
if (hasPermission("add")) {
userService.addUser(username);
} else {
System.out.println("权限不足,无法添加用户");
}
}
@Override
public void deleteUser(String username) {
if (hasPermission("delete")) {
userService.deleteUser(username);
} else {
System.out.println("权限不足,无法删除用户");
}
}
@Override
public String getUser(String username) {
if (hasPermission("read")) {
return userService.getUser(username);
} else {
System.out.println("权限不足,无法获取用户信息");
return null;
}
}
private boolean hasPermission(String operation) {
// 简化的权限检查
System.out.println("检查用户 " + currentUser + " 的 " + operation + " 权限");
return true; // 简化实现
}
}
// 使用示例
public class StaticProxyDemo {
public static void main(String[] args) {
UserService realService = new RealUserService();
UserService proxyService = new UserServiceProxy(realService, "admin");
proxyService.addUser("张三");
proxyService.deleteUser("李四");
String user = proxyService.getUser("王五");
System.out.println(user);
}
}
2. 动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 动态代理示例
// 日志处理类
class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始执行方法: " + method.getName());
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("方法执行完成: " + method.getName() +
", 耗时: " + (endTime - startTime) + "ms");
return result;
}
}
// 计算器接口
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);
}
// 计算器实现
class CalculatorImpl implements Calculator {
@Override
public int add(int a, int b) {
// 模拟计算时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return a + b;
}
@Override
public int subtract(int a, int b) {
return a - b;
}
@Override
public int multiply(int a, int b) {
return a * b;
}
@Override
public double divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return (double) a / b;
}
}
// 动态代理工厂
class ProxyFactory {
public static <T> T createProxy(T target, Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{interfaceClass},
new LoggingHandler(target)
);
}
}
// 使用示例
public class DynamicProxyDemo {
public static void main(String[] args) {
Calculator calculator = new CalculatorImpl();
Calculator proxyCalculator = ProxyFactory.createProxy(calculator, Calculator.class);
System.out.println("10 + 5 = " + proxyCalculator.add(10, 5));
System.out.println("10 - 5 = " + proxyCalculator.subtract(10, 5));
System.out.println("10 * 5 = " + proxyCalculator.multiply(10, 5));
System.out.println("10 / 5 = " + proxyCalculator.divide(10, 5));
}
}
代理模式与其他模式的比较
与适配器模式的区别
- 代理模式:不改变接口,但控制对对象的访问
- 适配器模式:改变接口以匹配客户端需求
与装饰器模式的区别
- 代理模式:控制对对象的访问
- 装饰器模式:动态地给对象添加职责
与外观模式的区别
- 代理模式:为一个对象提供代理
- 外观模式:为一组对象提供统一接口
实际项目中的应用
// 数据库连接代理示例
// 数据库连接接口
interface DatabaseConnection {
void connect();
void executeQuery(String sql);
void close();
boolean isConnected();
}
// 真实数据库连接
class RealDatabaseConnection implements DatabaseConnection {
private String url;
private boolean connected = false;
public RealDatabaseConnection(String url) {
this.url = url;
}
@Override
public void connect() {
System.out.println("连接到数据库: " + url);
// 模拟连接过程
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
connected = true;
System.out.println("数据库连接成功");
}
@Override
public void executeQuery(String sql) {
if (!connected) {
throw new IllegalStateException("数据库未连接");
}
System.out.println("执行SQL查询: " + sql);
// 模拟查询过程
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("查询执行完成");
}
@Override
public void close() {
if (connected) {
System.out.println("关闭数据库连接");
connected = false;
}
}
@Override
public boolean isConnected() {
return connected;
}
}
// 数据库连接代理
class DatabaseConnectionProxy implements DatabaseConnection {
private RealDatabaseConnection realConnection;
private String url;
private long lastAccessTime;
private static final long TIMEOUT = 30000; // 30秒超时
public DatabaseConnectionProxy(String url) {
this.url = url;
}
@Override
public void connect() {
if (realConnection == null) {
realConnection = new RealDatabaseConnection(url);
}
realConnection.connect();
lastAccessTime = System.currentTimeMillis();
}
@Override
public void executeQuery(String sql) {
checkConnection();
logQuery(sql);
realConnection.executeQuery(sql);
lastAccessTime = System.currentTimeMillis();
}
@Override
public void close() {
if (realConnection != null) {
realConnection.close();
}
}
@Override
public boolean isConnected() {
if (realConnection == null) {
return false;
}
// 检查是否超时
if (System.currentTimeMillis() - lastAccessTime > TIMEOUT) {
System.out.println("连接超时,自动断开");
realConnection.close();
return false;
}
return realConnection.isConnected();
}
private void checkConnection() {
if (realConnection == null || !realConnection.isConnected()) {
connect();
}
// 检查超时
if (System.currentTimeMillis() - lastAccessTime > TIMEOUT) {
System.out.println("连接超时,重新连接");
realConnection.close();
connect();
}
}
private void logQuery(String sql) {
System.out.println("[" + new java.util.Date() + "] 执行查询: " + sql);
}
}
// 使用示例
public class DatabaseProxyDemo {
public static void main(String[] args) throws InterruptedException {
DatabaseConnection connection = new DatabaseConnectionProxy("jdbc:mysql://localhost:3306/test");
// 执行查询
connection.executeQuery("SELECT * FROM users");
// 等待一段时间
System.out.println("等待1秒...");
Thread.sleep(1000);
// 再次执行查询
connection.executeQuery("SELECT * FROM orders");
// 检查连接状态
System.out.println("连接状态: " + connection.isConnected());
// 关闭连接
connection.close();
}
}
// Spring AOP代理示例
// 模拟Spring AOP的实现
interface BusinessService {
void doBusiness();
String getResult();
}
class BusinessServiceImpl implements BusinessService {
@Override
public void doBusiness() {
System.out.println("执行业务逻辑");
// 模拟业务处理时间
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String getResult() {
return "业务处理结果";
}
}
// AOP切面接口
interface Aspect {
void before();
void after();
void afterReturning(Object result);
void afterThrowing(Exception ex);
}
// 日志切面
class LoggingAspect implements Aspect {
@Override
public void before() {
System.out.println("[日志] 方法开始执行");
}
@Override
public void after() {
System.out.println("[日志] 方法执行完成");
}
@Override
public void afterReturning(Object result) {
System.out.println("[日志] 方法返回: " + result);
}
@Override
public void afterThrowing(Exception ex) {
System.out.println("[日志] 方法抛出异常: " + ex.getMessage());
}
}
// 性能监控切面
class PerformanceAspect implements Aspect {
private long startTime;
@Override
public void before() {
startTime = System.currentTimeMillis();
System.out.println("[性能] 开始计时");
}
@Override
public void after() {
long endTime = System.currentTimeMillis();
System.out.println("[性能] 执行耗时: " + (endTime - startTime) + "ms");
}
@Override
public void afterReturning(Object result) {
// 空实现
}
@Override
public void afterThrowing(Exception ex) {
// 空实现
}
}
// AOP代理
class AopProxy implements BusinessService {
private BusinessService target;
private List<Aspect> aspects;
public AopProxy(BusinessService target, List<Aspect> aspects) {
this.target = target;
this.aspects = aspects;
}
@Override
public void doBusiness() {
// 执行前置通知
for (Aspect aspect : aspects) {
aspect.before();
}
try {
target.doBusiness();
// 执行后置通知
for (Aspect aspect : aspects) {
aspect.after();
}
} catch (Exception e) {
// 执行异常通知
for (Aspect aspect : aspects) {
aspect.afterThrowing(e);
}
}
}
@Override
public String getResult() {
Object result = null;
Exception exception = null;
// 执行前置通知
for (Aspect aspect : aspects) {
aspect.before();
}
try {
result = target.getResult();
// 执行后置通知
for (Aspect aspect : aspects) {
aspect.after();
}
// 执行返回通知
for (Aspect aspect : aspects) {
aspect.afterReturning(result);
}
} catch (Exception e) {
exception = e;
// 执行异常通知
for (Aspect aspect : aspects) {
aspect.afterThrowing(e);
}
}
if (exception != null) {
throw new RuntimeException(exception);
}
return (String) result;
}
}
// 使用示例
public class AopProxyDemo {
public static void main(String[] args) {
BusinessService realService = new BusinessServiceImpl();
// 创建切面
List<Aspect> aspects = new ArrayList<>();
aspects.add(new LoggingAspect());
aspects.add(new PerformanceAspect());
// 创建代理
BusinessService proxyService = new AopProxy(realService, aspects);
// 通过代理执行业务
proxyService.doBusiness();
System.out.println();
String result = proxyService.getResult();
System.out.println("获取结果: " + result);
}
}
总结
代理模式是一种非常实用的结构型设计模式,它通过创建代理对象来控制对真实对象的访问,可以在不修改真实对象的情况下添加额外的功能。在实际开发中,当我们需要在访问对象时添加控制逻辑时,代理模式是一个很好的选择。
使用代理模式的关键点:
- 识别出需要添加控制逻辑的对象
- 定义抽象主题接口
- 创建真实主题实现具体业务逻辑
- 创建代理主题来控制对真实主题的访问
- 客户端通过代理主题与真实主题交互
代理模式的优点是可以添加额外的控制逻辑,符合开闭原则,但也需要注意可能会增加系统复杂度和请求处理时间。在现代Java开发中,代理模式广泛应用于Spring AOP、数据库连接池、远程方法调用、图像加载等场景。