23种设计模式一代理模式

前言

在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 优点

  1. 职责清晰

    • 真实对象专注于业务逻辑
    • 代理对象负责访问控制和功能增强
  2. 符合开闭原则

    • 在不修改原有代码基础上扩展功能
    • 新增功能只需修改代理类
  3. 安全性提升

    • 通过代理控制对真实对象的访问权限
    • 隐藏真实对象的复杂细节
  4. 提高性能

    • 实现延迟加载,按需创建资源
    • 缓存常用数据,减少重复计算
  5. 代码解耦

    • 将横切关注点(日志、事务等)从业务逻辑中分离
    • 提高代码的可维护性

6.2 缺点

  1. 增加系统复杂度

    • 引入了额外的代理类和层次结构
    • 调试和理解代码链路更加困难
  2. 性能开销

    • 动态代理使用反射机制,有一定性能损耗
    • 多层代理会增加方法调用的栈深度
  3. 开发成本

    • 静态代理需要为每个真实主题创建代理类
    • 动态代理需要理解反射和字节码操作
  4. 潜在问题

    • 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 核心要点总结

  1. 代理模式的核心:控制访问 + 功能增强
  2. 三种代理方式
    • 静态代理:编译时确定,适合固定场景
    • JDK动态代理:基于接口,使用反射机制
    • CGLIB动态代理:基于继承,使用字节码技术
  3. 实际应用广泛:AOP、RPC、权限控制、缓存、ORM等

7.2 学习建议

  1. 循序渐进

    • 先理解静态代理,再学习动态代理
    • 通过实际案例加深理解
  2. 实践为主

    • 手写各种代理实现
    • 阅读优秀框架(如Spring、MyBatis)的源码
  3. 扩展学习

    • 了解其他设计模式(装饰器模式、外观模式)
    • 深入学习Java反射和字节码技术

7.3 扩展思考

  1. 如何实现多层代理链?

    • 思考责任链模式与代理模式的结合
    • 学习Spring AOP的@Order注解实现
  2. 代理模式与装饰器模式的区别?

    • 代理:控制访问,关注对象的生命周期
    • 装饰器:增强功能,关注对象的行为扩展
  3. 现代框架中的代理应用?

    • 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 最佳实践

  1. 合理选择代理方式

    • 简单场景使用静态代理
    • 复杂场景使用动态代理
    • 高性能场景进行基准测试
  2. 注意代理边界

    • 避免过度使用代理
    • 清晰定义代理的职责范围
  3. 文档和注释

    • 详细说明代理的增强逻辑
    • 标注性能敏感的代理操作

结语

代理模式是Java开发中非常重要且实用的设计模式。从静态代理到动态代理,从JDK代理到CGLIB代理,每种方式都有其适用场景。掌握代理模式不仅能让你写出更优雅的代码,还能帮助你更好地理解Spring等优秀框架的设计思想。

相关推荐
苏渡苇11 小时前
优雅应对异常,从“try-catch堆砌”到“设计驱动”
java·后端·设计模式·学习方法·责任链模式
短剑重铸之日12 小时前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
feasibility.13 小时前
AI 编程助手进阶指南:从 Claude Code 到 OpenCode 的工程化经验总结
人工智能·经验分享·设计模式·自动化·agi·skills·opencode
BD_Marathon13 小时前
七大设计原则介绍
设计模式
YigAin15 小时前
Unity23种设计模式之 享元模式
设计模式·享元模式
范纹杉想快点毕业1 天前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
茂桑1 天前
DDD领域驱动设计-基础设施层
设计模式·架构
小温冲冲2 天前
通俗且全面精讲工厂设计模式
设计模式
进击的小头2 天前
设计模式与C语言高级特性的结合
c语言·设计模式