前言
在Java开发中,代理模式是一个非常实用且常见的设计模式。它不仅能够帮我们控制对象的访问,还能在不修改原有代码的基础上增强功能。今天我们就来深入剖析Java中的代理模式,从静态代理到动态代理,再到实际应用场景,带你全面掌握这个重要的设计模式。
一、代理模式概述
1.1 什么是代理模式
代理模式(Proxy Pattern)是结构型设计模式的一种,它为其他对象提供一种代理以控制对这个对象的访问。简单来说,就是不直接访问目标对象,而是通过一个代理对象来间接访问。
核心思想:在不改变目标对象的前提下,通过代理对象来扩展目标对象的功能。
1.2 代理模式的核心作用
- 控制访问:保护目标对象,控制对目标对象的访问权限
- 功能增强:在调用目标对象前后添加额外的逻辑(如日志、事务管理)
- 延迟加载:对于资源消耗大的对象,实现延迟初始化
1.3 常见应用场景
- 远程代理:为不同地址空间的对象提供本地代表
- 虚拟代理:根据需要创建开销很大的对象
- 保护代理:控制不同用户对对象的访问权限
- 智能引用代理:访问对象时执行额外操作(如引用计数)
二、代理模式的核心结构
2.1 代理模式的主要角色
代理模式主要包含以下几个角色:
抽象主题(Subject):声明真实对象和代理对象的公共接口
真实主题(RealSubject):定义代理对象所代表的真实对象
代理(Proxy):实现抽象主题接口,持有对真实主题的引用
客户端:通过代理对象访问真实主题
2.2 类图关系
┌────────────────┐
│ Subject │ (抽象主题)
│ │
└──────┬─────────┘
│ 实现
│
┌──────┴─────────┐ ┌─────────────────┐
│ RealSubject │ │ Proxy │ (代理类)
│ (真实主题) │ │ │
└────────────────┘ └────────┬────────┘
│ 持有引用
│
┌────────┴────────┐
│ RealSubject │
└─────────────────┘
三、静态代理实现
静态代理是在程序运行前就已经确定代理类和真实主题的关系。代理类和真实主题都实现同一个接口。
3.1 代码实现
3.1.1 定义接口
java
/**
* 抽象主题接口 - 定义图片加载的标准方法
*/
public interface Image {
void display();
}
3.1.2 真实主题类
java
/**
* 真实主题 - 实现图片加载的实际逻辑
*/
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName); // 构造时立即加载
}
/**
* 实际的图片加载方法
* @param fileName 文件名
*/
private void loadFromDisk(String fileName) {
System.out.println("正在从磁盘加载图片: " + fileName);
try {
// 模拟图片加载耗时
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("图片加载完成: " + fileName);
}
@Override
public void display() {
System.out.println("显示图片: " + fileName);
}
}
3.1.3 代理类
java
/**
* 代理类 - 控制对真实主题的访问
*/
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) {
System.out.println("代理类: 首次调用,创建真实对象");
realImage = new RealImage(fileName);
} else {
System.out.println("代理类: 使用已加载的对象");
}
// 执行真实对象的方法
realImage.display();
}
}
3.1.4 测试代码
java
/**
* 静态代理测试类
*/
public class StaticProxyDemo {
public static void main(String[] args) {
System.out.println("=== 静态代理测试 ===\n");
// 使用代理对象
Image image = new ProxyImage("test_image.jpg");
// 第一次调用 - 会创建真实对象并加载图片
System.out.println("第一次调用display():");
image.display();
// 第二次调用 - 使用已加载的对象
System.out.println("\n第二次调用display():");
image.display();
}
}
3.1.5 运行结果
=== 静态代理测试 ===
第一次调用display():
代理类: 首次调用,创建真实对象
正在从磁盘加载图片: test_image.jpg
图片加载完成: test_image.jpg
显示图片: test_image.jpg
第二次调用display():
代理类: 使用已加载的对象
显示图片: test_image.jpg
3.2 静态代理的优缺点
优点:
- 职责清晰:真实对象专注于业务逻辑,代理对象负责控制访问
- 高内聚:各个类职责明确
缺点:
- 代码冗余:每个真实主题都需要一个对应的代理类
- 不够灵活:接口一旦修改,所有实现类都需要修改
四、动态代理实现
动态代理是在程序运行时动态生成代理类,解决了静态代理的灵活性不足问题。Java提供了两种动态代理实现方式:JDK动态代理和CGLIB动态代理。
4.1 JDK动态代理
JDK动态代理使用Java反射机制实现,只能代理实现了接口的类。
4.1.1 代码实现
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理 - 接口定义
*/
interface UserService {
void addUser(String username);
void deleteUser(String username);
}
/**
* 真实主题 - 实际业务逻辑
*/
class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("执行业务逻辑: 添加用户 " + username);
}
@Override
public void deleteUser(String username) {
System.out.println("执行业务逻辑: 删除用户 " + username);
}
}
/**
* JDK动态代理处理器
*/
class JDKDynamicProxy implements InvocationHandler {
private Object target; // 真实对象
public JDKDynamicProxy(Object target) {
this.target = target;
}
/**
* 创建代理对象
*/
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
this // 调用处理器
);
}
/**
* 处理代理对象的方法调用
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("=== 前置通知 ===");
System.out.println("方法名: " + method.getName());
System.out.println("参数: " + (args != null ? Arrays.toString(args) : "无"));
long startTime = System.currentTimeMillis();
try {
// 调用真实对象的方法
Object result = method.invoke(target, args);
return result;
} catch (Exception e) {
System.out.println("方法执行异常: " + e.getMessage());
throw e;
} finally {
// 后置增强
long endTime = System.currentTimeMillis();
System.out.println("方法执行时间: " + (endTime - startTime) + "ms");
System.out.println("=== 后置通知 ===\n");
}
}
}
/**
* JDK动态代理测试类
*/
public class JDKDynamicProxyDemo {
public static void main(String[] args) {
// 创建真实对象
UserService userService = new UserServiceImpl();
// 创建代理对象
JDKDynamicProxy proxy = new JDKDynamicProxy(userService);
UserService proxyUserService = (UserService) proxy.getProxy();
// 使用代理对象调用方法
System.out.println("=== JDK动态代理测试 ===\n");
proxyUserService.addUser("张三");
proxyUserService.deleteUser("李四");
}
}
4.1.2 运行结果
=== JDK动态代理测试 ===
=== 前置通知 ===
方法名: addUser
参数: [张三]
执行业务逻辑: 添加用户 张三
方法执行时间: 1ms
=== 后置通知 ===
=== 前置通知 ===
方法名: deleteUser
参数: [李四]
执行业务逻辑: 删除用户 李四
方法执行时间: 0ms
=== 后置通知 ===
4.2 CGLIB动态代理
CGLIB通过字节码技术生成子类来代理目标对象,可以代理没有实现接口的类。
4.2.1 添加Maven依赖
xml
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
4.2.2 代码实现
java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* CGLIB动态代理 - 真实主题(不需要实现接口)
*/
class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单: " + orderId);
}
public void cancelOrder(String orderId) {
System.out.println("取消订单: " + orderId);
}
}
/**
* CGLIB代理拦截器
*/
class CGLIBProxyInterceptor implements MethodInterceptor {
private Object target; // 真实对象
public CGLIBProxyInterceptor(Object target) {
this.target = target;
}
/**
* 创建代理对象
*/
public Object getProxy() {
// 创建增强器
Enhancer enhancer = new Enhancer();
// 设置父类(被代理的类)
enhancer.setSuperclass(target.getClass());
// 设置回调
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
/**
* 拦截方法调用
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("=== CGLIB 前置通知 ===");
System.out.println("方法名: " + method.getName());
long startTime = System.currentTimeMillis();
try {
// 调用父类方法(即真实对象的方法)
Object result = proxy.invokeSuper(obj, args);
return result;
} finally {
// 后置增强
long endTime = System.currentTimeMillis();
System.out.println("方法执行时间: " + (endTime - startTime) + "ms");
System.out.println("=== CGLIB 后置通知 ===\n");
}
}
}
/**
* CGLIB动态代理测试类
*/
public class CGLIBDynamicProxyDemo {
public static void main(String[] args) {
// 创建真实对象
OrderService orderService = new OrderService();
// 创建代理对象
CGLIBProxyInterceptor interceptor = new CGLIBProxyInterceptor(orderService);
OrderService proxyOrderService = (OrderService) interceptor.getProxy();
// 使用代理对象调用方法
System.out.println("=== CGLIB动态代理测试 ===\n");
proxyOrderService.createOrder("ORDER_001");
proxyOrderService.cancelOrder("ORDER_002");
}
}
4.2.3 运行结果
=== CGLIB动态代理测试 ===
=== CGLIB 前置通知 ===
方法名: createOrder
创建订单: ORDER_001
方法执行时间: 2ms
=== CGLIB 后置通知 ===
=== CGLIB 前置通知 ===
方法名: cancelOrder
取消订单: ORDER_002
方法执行时间: 1ms
=== CGLIB 后置通知 ===
4.3 JDK动态代理 vs CGLIB动态代理
| 特性 | JDK动态代理 | CGLIB动态代理 |
|---|---|---|
| 实现原理 | Java反射机制 | 字节码生成(继承目标类) |
| 代理对象 | 实现了接口的类 | 任意类(不能是final类) |
| 限制条件 | 目标对象必须实现接口 | 目标类不能是final,方法不能是final |
| 性能 | 创建代理快,调用稍慢 | 创建代理慢,调用快 |
| Spring支持 | 默认选择 | 当目标类无接口时使用 |
| 方法拦截 | 只能拦截public方法 | 可拦截public、protected、package方法 |
选择建议:
- 如果目标对象实现了接口,优先使用JDK动态代理
- 如果目标对象没有实现接口,使用CGLIB动态代理
- 对于性能要求高的场景,建议进行基准测试后选择
五、代理模式的实际应用场景
5.1 Spring AOP(面向切面编程)
Spring AOP是代理模式最典型的应用,通过动态代理实现横切关注点(如日志、事务、权限控制)的分离。
java
// AOP示例:声明式事务管理
@Transactional
public void updateUser(User user) {
// AOP代理会自动处理事务的开启、提交、回滚
userDao.update(user);
}
5.2 远程服务调用(RPC)
远程代理为不同地址空间的对象提供本地代表。
java
// 远程代理示例
public interface RemoteService {
String executeRemoteCall(String request);
}
// 客户端通过代理调用远程服务
public class RemoteServiceProxy implements RemoteService {
@Override
public String executeRemoteCall(String request) {
// 实际的远程通信逻辑
return HttpUtils.post("http://remote.service/api", request);
}
}
5.3 权限控制
保护代理用于控制不同用户对对象的访问权限。
java
// 权限控制代理示例
public class SecureDocumentProxy implements Document {
private Document document;
private User currentUser;
@Override
public void read() {
if (currentUser.hasPermission("READ")) {
document.read();
} else {
throw new SecurityException("无权访问");
}
}
@Override
public void write(String content) {
if (currentUser.hasPermission("WRITE")) {
document.write(content);
} else {
throw new SecurityException("无权写入");
}
}
}
5.4 缓存代理
虚拟代理的一种,用于延迟加载和缓存结果。
java
public class CacheProxy implements DataService {
private DataService realService;
private Map<String, Object> cache = new ConcurrentHashMap<>();
@Override
public String getData(String key) {
// 先从缓存获取
if (cache.containsKey(key)) {
return (String) cache.get(key);
}
// 缓存未命中,调用真实服务
String result = realService.getData(key);
cache.put(key, result);
return result;
}
}
5.5 MyBatis Mapper代理
MyBatis使用动态代理技术,让开发者只需定义接口而不需要编写实现类。
java
// Mapper接口
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(Long id);
}
// MyBatis会在运行时生成代理实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectById(1L); // 实际调用的是代理对象
六、代理模式的优缺点分析
6.1 优点
-
职责清晰
- 真实对象专注于业务逻辑
- 代理对象负责访问控制和功能增强
-
符合开闭原则
- 在不修改原有代码基础上扩展功能
- 新增功能只需修改代理类
-
安全性提升
- 通过代理控制对真实对象的访问权限
- 隐藏真实对象的复杂细节
-
提高性能
- 实现延迟加载,按需创建资源
- 缓存常用数据,减少重复计算
-
代码解耦
- 将横切关注点(日志、事务等)从业务逻辑中分离
- 提高代码的可维护性
6.2 缺点
-
增加系统复杂度
- 引入了额外的代理类和层次结构
- 调试和理解代码链路更加困难
-
性能开销
- 动态代理使用反射机制,有一定性能损耗
- 多层代理会增加方法调用的栈深度
-
开发成本
- 静态代理需要为每个真实主题创建代理类
- 动态代理需要理解反射和字节码操作
-
潜在问题
- CGLIB代理不能代理final类和final方法
- JDK动态代理只能代理接口
- 循环代理可能导致栈溢出
6.3 性能优化建议
java
// 1. 使用缓存减少代理对象的创建
public class ProxyCache {
private static final Map<Class<?>, Object> proxyCache = new ConcurrentHashMap<>();
public static <T> T getProxy(Class<T> interfaceClass, InvocationHandler handler) {
return (T) proxyCache.computeIfAbsent(interfaceClass,
key -> Proxy.newProxyInstance(key.getClassLoader(),
new Class<?>[]{key}, handler));
}
}
// 2. 减少不必要的反射调用
public class OptimizedProxy implements InvocationHandler {
private Method targetMethod; // 缓存方法对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.equals(targetMethod)) {
// 直接使用缓存的方法对象,避免重复查找
return method.invoke(target, args);
}
// ...其他逻辑
}
}
七、总结与思考
7.1 核心要点总结
- 代理模式的核心:控制访问 + 功能增强
- 三种代理方式 :
- 静态代理:编译时确定,适合固定场景
- JDK动态代理:基于接口,使用反射机制
- CGLIB动态代理:基于继承,使用字节码技术
- 实际应用广泛:AOP、RPC、权限控制、缓存、ORM等
7.2 学习建议
-
循序渐进
- 先理解静态代理,再学习动态代理
- 通过实际案例加深理解
-
实践为主
- 手写各种代理实现
- 阅读优秀框架(如Spring、MyBatis)的源码
-
扩展学习
- 了解其他设计模式(装饰器模式、外观模式)
- 深入学习Java反射和字节码技术
7.3 扩展思考
-
如何实现多层代理链?
- 思考责任链模式与代理模式的结合
- 学习Spring AOP的@Order注解实现
-
代理模式与装饰器模式的区别?
- 代理:控制访问,关注对象的生命周期
- 装饰器:增强功能,关注对象的行为扩展
-
现代框架中的代理应用?
- Spring Boot自动装配机制
- Dubbo服务治理中的代理应用
- Netty中的ChannelHandler代理
java
// 多层代理链示例
public class MultiProxyDemo {
public static void main(String[] args) {
// 创建真实对象
UserService target = new UserServiceImpl();
// 第一层代理:日志
InvocationHandler logHandler = new LogHandler(target);
UserService proxy1 = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
logHandler
);
// 第二层代理:事务
InvocationHandler transactionHandler = new TransactionHandler(proxy1);
UserService proxy2 = (UserService) Proxy.newProxyInstance(
proxy1.getClass().getClassLoader(),
proxy1.getClass().getInterfaces(),
transactionHandler
);
// 调用会依次经过:事务代理 -> 日志代理 -> 真实对象
proxy2.addUser("测试用户");
}
}
7.4 最佳实践
-
合理选择代理方式
- 简单场景使用静态代理
- 复杂场景使用动态代理
- 高性能场景进行基准测试
-
注意代理边界
- 避免过度使用代理
- 清晰定义代理的职责范围
-
文档和注释
- 详细说明代理的增强逻辑
- 标注性能敏感的代理操作
结语
代理模式是Java开发中非常重要且实用的设计模式。从静态代理到动态代理,从JDK代理到CGLIB代理,每种方式都有其适用场景。掌握代理模式不仅能让你写出更优雅的代码,还能帮助你更好地理解Spring等优秀框架的设计思想。