一文吃透代理模式:原理、实战与源码剖析
·
一文吃透代理模式:原理、实战与源码剖析
|
🌺The Begin🌺点点关注,收藏不迷路🌺
|
前言
在软件开发中,我们经常遇到这样的需求:需要在访问某个对象之前或之后增加一些额外的逻辑,比如权限校验、日志记录、性能监控、事务管理等。如果直接修改原有对象的代码,不仅违反了开闭原则,还会导致代码臃肿、难以维护。**代理模式(Proxy Pattern)**正是为解决这类问题而生——它为另一个对象提供一个替身或占位符,以控制对这个对象的访问。本文将带你从原理到实战,从静态代理到动态代理,彻底掌握这一经典设计模式。
1. 什么是代理模式
代理模式是一种结构型设计模式。它通过引入一个代理对象来代替真实对象,代理对象与真实对象实现相同的接口,客户端通过代理对象间接访问真实对象,从而在访问前后添加额外的处理逻辑。
1.1 模式的核心角色
- 抽象主题(Subject):定义真实对象和代理对象的共同接口
- 真实主题(RealSubject):真正的业务逻辑执行者
- 代理(Proxy):持有真实对象的引用,控制对其的访问
1.2 代理模式的执行流程
2. 代理模式的分类
| 分类 | 特点 | 实现方式 | 适用场景 |
|---|---|---|---|
| 静态代理 | 代理类在编译期就已确定 | 手动编写代理类 | 代理逻辑简单、类数量少 |
| 动态代理(JDK) | 运行时动态生成代理类 | java.lang.reflect.Proxy | 目标类有接口 |
| 动态代理(CGLIB) | 通过字节码技术生成子类 | CGLIB 库 | 目标类无接口 |
| Spring AOP 代理 | 结合 IoC 容器自动代理 | Spring 自动选择 JDK/CGLIB | 企业级切面编程 |
3. 典型应用场景
| 场景分类 | 具体案例 |
|---|---|
| 权限控制 | 访问前校验用户是否有权限 |
| 延迟加载 | Hibernate/MyBatis 的懒加载代理 |
| 日志记录 | 方法调用前后记录日志 |
| 性能监控 | 统计方法执行时间 |
| 缓存代理 | 为耗时操作增加缓存 |
| 事务管理 | Spring 声明式事务代理 |
| 远程调用 | RPC 框架中的本地代理(如 Feign、Dubbo) |
| 连接池管理 | 代理数据库连接,归还时自动回收 |
4. 代码实战:静态代理
4.1 场景描述
假设有一个用户服务,需要在调用方法前后增加日志记录。
4.2 静态代理实现
// 1. 抽象主题接口
public interface UserService {
void addUser(String username);
void deleteUser(Long id);
User findUser(Long id);
}
// 2. 真实主题(业务逻辑)
public class UserServiceImpl implements UserService {
@Override
public void addUser(String username) {
System.out.println("【真实对象】添加用户:" + username);
// 模拟数据库操作
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
@Override
public void deleteUser(Long id) {
System.out.println("【真实对象】删除用户:" + id);
}
@Override
public User findUser(Long id) {
System.out.println("【真实对象】查询用户:" + id);
return new User(id, "user_" + id);
}
}
// 3. 静态代理类(日志代理)
public class UserServiceLogProxy implements UserService {
private final UserService target; // 持有真实对象的引用
public UserServiceLogProxy(UserService target) {
this.target = target;
}
@Override
public void addUser(String username) {
long start = System.currentTimeMillis();
System.out.println("【代理】前置日志:准备添加用户 " + username);
target.addUser(username); // 调用真实对象
long end = System.currentTimeMillis();
System.out.println("【代理】后置日志:添加用户完成,耗时 " + (end - start) + "ms");
}
@Override
public void deleteUser(Long id) {
System.out.println("【代理】前置日志:准备删除用户 " + id);
target.deleteUser(id);
System.out.println("【代理】后置日志:删除用户完成");
}
@Override
public User findUser(Long id) {
System.out.println("【代理】前置日志:准备查询用户 " + id);
User user = target.findUser(id);
System.out.println("【代理】后置日志:查询用户完成,返回 " + user);
return user;
}
}
// 4. 客户端使用
public class Client {
public static void main(String[] args) {
// 创建真实对象
UserService realService = new UserServiceImpl();
// 创建代理对象,包装真实对象
UserService proxy = new UserServiceLogProxy(realService);
// 通过代理访问
proxy.addUser("张三");
proxy.findUser(100L);
}
}
输出结果:
【代理】前置日志:准备添加用户 张三
【真实对象】添加用户:张三
【代理】后置日志:添加用户完成,耗时 102ms
【代理】前置日志:准备查询用户 100
【真实对象】查询用户:100
【代理】后置日志:查询用户完成,返回 User{id=100, name='user_100'}
静态代理的痛点:每个需要增强的方法都要手动编写代理逻辑,如果接口有几十个方法,代理类会非常冗长。
5. 代码实战:JDK 动态代理
5.1 JDK 动态代理原理
5.2 JDK 动态代理实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 通用的日志增强处理器
public class LogInvocationHandler implements InvocationHandler {
private final Object target; // 真实对象
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
long start = System.currentTimeMillis();
String methodName = method.getName();
System.out.println("【动态代理】开始执行 " + methodName + ",参数:" + Arrays.toString(args));
// 调用真实对象的方法
Object result = method.invoke(target, args);
// 后置增强
long end = System.currentTimeMillis();
System.out.println("【动态代理】执行完成 " + methodName + ",耗时 " + (end - start) + "ms");
return result;
}
// 工厂方法:创建代理对象
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
}
}
// 客户端使用
public class DynamicProxyClient {
public static void main(String[] args) {
// 保存生成的代理类(用于学习)
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 创建真实对象
UserService realService = new UserServiceImpl();
// 动态生成代理对象
UserService proxy = LogInvocationHandler.createProxy(realService);
// 通过代理访问
proxy.addUser("李四");
proxy.deleteUser(200L);
// 打印代理对象的类名
System.out.println("代理类类型:" + proxy.getClass().getName());
// 输出:com.sun.proxy.$Proxy0
}
}
5.3 JDK 动态代理的限制
// ❌ JDK 动态代理要求目标类必须实现接口
public class NoInterfaceClass { // 无接口
public void doSomething() {
System.out.println("do something");
}
}
// 使用 JDK 代理会报错
NoInterfaceClass obj = new NoInterfaceClass();
// ClassCastException: 无法创建代理,因为 NoInterfaceClass 没有实现任何接口
// Proxy.newProxyInstance(obj.getClass().getClassLoader(),
// obj.getClass().getInterfaces(), // 为空数组
// handler);
6. 代码实战:CGLIB 动态代理
6.1 CGLIB 原理
CGLIB(Code Generation Library)通过字节码技术生成目标类的子类,拦截所有方法调用。
6.2 CGLIB 动态代理实现
<!-- Maven 依赖 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
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 EmailService {
public void sendEmail(String to, String content) {
System.out.println("【真实对象】发送邮件到 " + to + ",内容:" + content);
}
public final void finalMethod() {
System.out.println("【final 方法】无法被代理");
}
}
// CGLIB 方法拦截器
public class CglibLogInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String methodName = method.getName();
// 前置增强
System.out.println("【CGLIB代理】前置:准备执行 " + methodName);
long start = System.currentTimeMillis();
// 调用父类(目标类)的方法
Object result = proxy.invokeSuper(obj, args);
// 后置增强
long end = System.currentTimeMillis();
System.out.println("【CGLIB代理】后置:" + methodName + " 执行完成,耗时 " + (end - start) + "ms");
return result;
}
// 创建代理对象
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类
enhancer.setCallback(new CglibLogInterceptor()); // 设置拦截器
return (T) enhancer.create(); // 创建代理对象
}
}
// 客户端使用
public class CglibClient {
public static void main(String[] args) {
// 设置保存 CGLIB 生成的代理类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");
EmailService realService = new EmailService();
EmailService proxy = CglibLogInterceptor.createProxy(realService);
proxy.sendEmail("user@example.com", "Hello Proxy!");
// proxy.finalMethod(); // final 方法不会被代理
}
}
输出结果:
【CGLIB代理】前置:准备执行 sendEmail
【真实对象】发送邮件到 user@example.com,内容:Hello Proxy!
【CGLIB代理】后置:sendEmail 执行完成,耗时 0ms
6.3 JDK 代理 vs CGLIB 代理
| 对比维度 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 目标类要求 | 必须实现接口 | 无要求(不能是 final 类) |
| 方法要求 | 无限制 | 不能代理 final/static 方法 |
| 实现原理 | 反射 + 动态生成接口实现类 | 字节码生成子类 |
| 性能(创建) | 较快 | 较慢(生成字节码) |
| 性能(调用) | 较慢(反射开销) | 较快(直接调用) |
| 依赖 | JDK 内置 | 需要额外依赖 |
| Spring 默认 | 有接口时优先使用 | 无接口时使用 |
7. 框架源码中的代理模式
7.1 Spring AOP 代理实现
// Spring AOP 核心代理创建逻辑(简化版)
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 判断是否使用 JDK 代理还是 CGLIB
if (config.isOptimize() || config.isProxyTargetClass()
|| hasNoUserSuppliedProxyInterfaces(config)) {
return new CglibAopProxy(config); // CGLIB 代理
} else {
return new JdkDynamicAopProxy(config); // JDK 代理
}
}
}
// Spring 声明式事务的代理
@Service
public class OrderService {
@Transactional // 实际会被代理增强
public void createOrder(Order order) {
// 业务逻辑
// 事务代理会在方法前后添加:begin → 执行业务 → commit/rollback
}
}
7.2 MyBatis Mapper 代理
// MyBatis 通过 JDK 动态代理生成 Mapper 接口的实现类
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是 Object 的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 否则,转换为 SQL 执行
return mapperMethod.execute(sqlSession, args);
}
}
// 使用示例:开发者只定义接口,MyBatis 动态生成实现
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
}
// 实际使用时,MyBatis 返回的是代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 返回代理对象
User user = mapper.selectById(1L); // 方法调用被代理转发
7.3 RPC 框架中的代理(Feign)
// OpenFeign 通过动态代理将接口调用转为 HTTP 请求
@FeignClient(name = "user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getUser(@PathVariable("id") Long id);
}
// 实际调用时,代理会完成:
// getUser(1L) → HTTP GET http://user-service/user/1 → 解析响应 → 返回 User 对象
8. 代理模式 vs 装饰器模式 vs 适配器模式
| 对比维度 | 代理模式 | 装饰器模式 | 适配器模式 |
|---|---|---|---|
| 目的 | 控制访问权限 | 动态增加功能 | 接口转换适配 |
| 关注点 | 访问控制 | 功能增强 | 兼容性 |
| 关系 | 代理与真实对象相同接口 | 装饰器与被装饰者相同接口 | 适配器与目标接口不同 |
| 创建时机 | 编译时/运行时 | 运行时 | 通常编译时 |
| 典型示例 | 延迟加载、权限控制 | Java I/O 流包装 | 电源适配器 |
9. 代理模式的应用场景详解
9.1 延迟加载(Lazy Loading)
// Hibernate 懒加载代理
@Entity
public class User {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders; // 返回的是代理对象,使用时才查询数据库
}
// 代理对象在访问 orders 时才触发 SQL 查询
User user = session.get(User.class, 1L);
// 此时 orders 是代理对象,未查询数据库
List<Order> orders = user.getOrders(); // 触发懒加载,执行 SELECT 查询
9.2 缓存代理
public class CacheProxy<T> implements InvocationHandler {
private final T target;
private final Map<String, Object> cache = new ConcurrentHashMap<>();
public CacheProxy(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只对查询方法进行缓存
if (method.getName().startsWith("find") || method.getName().startsWith("get")) {
String key = method.getName() + Arrays.toString(args);
Object cached = cache.get(key);
if (cached != null) {
System.out.println("【缓存代理】命中缓存:" + key);
return cached;
}
Object result = method.invoke(target, args);
cache.put(key, result);
System.out.println("【缓存代理】存入缓存:" + key);
return result;
}
// 修改方法直接调用,并清理相关缓存
return method.invoke(target, args);
}
}
9.3 权限控制代理
public class SecurityProxy implements InvocationHandler {
private final Object target;
private final String currentUser;
public SecurityProxy(Object target, String currentUser) {
this.target = target;
this.currentUser = currentUser;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 检查权限注解
if (method.isAnnotationPresent(RoleRequired.class)) {
RoleRequired roleRequired = method.getAnnotation(RoleRequired.class);
String requiredRole = roleRequired.value();
if (!hasRole(currentUser, requiredRole)) {
throw new SecurityException("用户 " + currentUser + " 没有 " + requiredRole + " 权限");
}
}
return method.invoke(target, args);
}
private boolean hasRole(String user, String role) {
// 权限校验逻辑
return "admin".equals(user);
}
}
10. 最佳实践与注意事项
最佳实践总结
| 实践建议 | 说明 |
|---|---|
| 优先使用接口 | JDK 代理更标准,性能可接受 |
| 避免过度代理 | 代理嵌套过深会影响性能 |
| 注意 equals/hashCode | 代理对象和真实对象比较可能出问题 |
| 异常处理 | 代理中的异常要正确向上抛出 |
| 内存泄漏 | 注意代理对象是否持有大对象引用 |
结语
代理模式是 Java 生态中最核心的设计模式之一。从静态代理的简单直接,到 JDK 动态代理的接口要求,再到 CGLIB 的无侵入代理,每一种实现都有其适用场景。而 Spring AOP、MyBatis Mapper、RPC 框架等底层都大量使用了代理模式,理解它不仅能写出更优雅的代码,更能深入理解这些框架的工作原理。
记忆口诀:
- 代理模式三角色:抽象、真实、代理类
- 静态手动写代码,动态运行时来生成
- JDK 需要实现接口,CGLIB 子类来继承
- 前置后置加环绕,控制访问更从容
- Spring AOP 最经典,事务日志全搞定
你在项目中用过代理模式解决过哪些问题?欢迎评论区分享交流!

|
🌺The End🌺点点关注,收藏不迷路🌺
|
更多推荐




所有评论(0)