🌺The Begin🌺点点关注,收藏不迷路🌺

前言

在软件开发中,我们经常遇到这样的需求:需要在访问某个对象之前或之后增加一些额外的逻辑,比如权限校验、日志记录、性能监控、事务管理等。如果直接修改原有对象的代码,不仅违反了开闭原则,还会导致代码臃肿、难以维护。**代理模式(Proxy Pattern)**正是为解决这类问题而生——它为另一个对象提供一个替身或占位符,以控制对这个对象的访问。本文将带你从原理到实战,从静态代理到动态代理,彻底掌握这一经典设计模式。

1. 什么是代理模式

代理模式是一种结构型设计模式。它通过引入一个代理对象来代替真实对象,代理对象与真实对象实现相同的接口,客户端通过代理对象间接访问真实对象,从而在访问前后添加额外的处理逻辑。

1.1 模式的核心角色

调用

«interface»

Subject

+request()

RealSubject

+request()

Proxy

-realSubject: RealSubject

+request()

Client

  • 抽象主题(Subject):定义真实对象和代理对象的共同接口
  • 真实主题(RealSubject):真正的业务逻辑执行者
  • 代理(Proxy):持有真实对象的引用,控制对其的访问

1.2 代理模式的执行流程

真实对象 代理对象 客户端 真实对象 代理对象 客户端 调用 request() 前置处理(日志/鉴权/缓存) 调用真正的 request() 返回结果 后置处理(清理/日志) 返回结果

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 动态代理原理

目标接口

InvocationHandler

Proxy.newProxyInstance

运行时生成代理类

代理对象实现目标接口

方法调用转发给 InvocationHandler

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)通过字节码技术生成目标类的子类,拦截所有方法调用。

目标类

CGLIB Enhancer

运行时生成子类

子类继承目标类

重写所有非 final 方法

方法调用转发给 MethodInterceptor

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 动态代理

CGLIB 代理

类是 final?

❌ 无法代理!考虑重构或修改设计

✓ 注意:只能代理 public 方法

✓ 注意:不能代理 final/static 方法

✓ 性能敏感考虑缓存代理实例

最佳实践总结

实践建议说明
优先使用接口JDK 代理更标准,性能可接受
避免过度代理代理嵌套过深会影响性能
注意 equals/hashCode代理对象和真实对象比较可能出问题
异常处理代理中的异常要正确向上抛出
内存泄漏注意代理对象是否持有大对象引用

结语

代理模式是 Java 生态中最核心的设计模式之一。从静态代理的简单直接,到 JDK 动态代理的接口要求,再到 CGLIB 的无侵入代理,每一种实现都有其适用场景。而 Spring AOP、MyBatis Mapper、RPC 框架等底层都大量使用了代理模式,理解它不仅能写出更优雅的代码,更能深入理解这些框架的工作原理。

记忆口诀:

  • 代理模式三角色:抽象、真实、代理类
  • 静态手动写代码,动态运行时来生成
  • JDK 需要实现接口,CGLIB 子类来继承
  • 前置后置加环绕,控制访问更从容
  • Spring AOP 最经典,事务日志全搞定

你在项目中用过代理模式解决过哪些问题?欢迎评论区分享交流!

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

更多推荐