静态代理与动态代理:深入理解Java代理模式

代理模式概述

在软件开发中,代理模式是一种常见的设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在不修改目标对象代码的前提下,增加额外的功能或控制访问。

静态代理:编译时确定的代理

什么是静态代理?

静态代理是指在编译时期就已经确定代理类与被代理类的关系。代理类和目标类实现相同的接口,代理类内部持有目标对象的引用,并在调用目标方法前后添加额外的逻辑。

静态代理的实现

让我们通过一个简单的例子来理解静态代理:

java 复制代码
// 1. 定义接口
interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

// 2. 实现目标类
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("添加用户: " + username);
    }
    
    @Override
    public void deleteUser(String username) {
        System.out.println("删除用户: " + username);
    }
}

// 3. 创建代理类
class UserServiceProxy implements UserService {
    private UserService target; // 目标对象
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void addUser(String username) {
        System.out.println("【代理】开始添加用户...");
        long start = System.currentTimeMillis();
        
        // 调用目标方法
        target.addUser(username);
        
        long end = System.currentTimeMillis();
        System.out.println("【代理】添加用户完成,耗时: " + (end - start) + "ms");
    }
    
    @Override
    public void deleteUser(String username) {
        System.out.println("【代理】开始删除用户...");
        long start = System.currentTimeMillis();
        
        // 调用目标方法
        target.deleteUser(username);
        
        long end = System.currentTimeMillis();
        System.out.println("【代理】删除用户完成,耗时: " + (end - start) + "ms");
    }
}

// 4. 客户端使用
public class StaticProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        UserService target = new UserServiceImpl();
        
        // 创建代理对象,并传入目标对象
        UserService proxy = new UserServiceProxy(target);
        
        // 通过代理对象调用方法
        proxy.addUser("张三");
        proxy.deleteUser("李四");
    }
}

静态代理的优缺点

优点:

  • 简单直观,易于理解和实现
  • 在不修改目标对象的前提下扩展功能
  • 符合开闭原则

缺点:

  • 代理类和目标类必须实现相同的接口,如果接口有多个方法,代理类需要重写所有方法
  • 每个代理类只能代理一个接口,如果有多个接口需要代理,需要创建多个代理类
  • 代理类在编译时就已经确定,不够灵活

动态代理:运行时生成的代理

什么是动态代理?

动态代理是指在程序运行时动态生成代理类。Java提供了两种主要的动态代理机制:

  1. JDK动态代理:基于接口的代理
  2. CGLIB动态代理:基于类继承的代理

JDK动态代理

JDK动态代理利用Java的反射机制,在运行时创建代理类。它要求目标类必须实现接口。

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1. 定义接口和目标类(与静态代理相同)
interface OrderService {
    void createOrder(String orderId);
    void cancelOrder(String orderId);
}

class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder(String orderId) {
        System.out.println("创建订单: " + orderId);
    }
    
    @Override
    public void cancelOrder(String orderId) {
        System.out.println("取消订单: " + orderId);
    }
}

// 2. 创建InvocationHandler实现类
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 {
        String methodName = method.getName();
        System.out.println("【动态代理】开始执行方法: " + methodName);
        long start = System.currentTimeMillis();
        
        // 调用目标方法
        Object result = method.invoke(target, args);
        
        long end = System.currentTimeMillis();
        System.out.println("【动态代理】方法执行完成,耗时: " + (end - start) + "ms");
        
        return result;
    }
}

// 3. 动态代理工厂类
class ProxyFactory {
    public static Object createProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(), // 类加载器
            target.getClass().getInterfaces(),  // 目标对象实现的接口
            new LoggingHandler(target)          // InvocationHandler
        );
    }
}

// 4. 客户端使用
public class DynamicProxyDemo {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        
        // 创建动态代理
        OrderService proxy = (OrderService) ProxyFactory.createProxy(target);
        
        // 通过代理对象调用方法
        proxy.createOrder("ORD001");
        proxy.cancelOrder("ORD002");
        
        // 可以查看代理类的信息
        System.out.println("代理类: " + proxy.getClass().getName());
        System.out.println("代理类的父类: " + proxy.getClass().getSuperclass().getName());
        System.out.println("代理类实现的接口: ");
        for (Class<?> clazz : proxy.getClass().getInterfaces()) {
            System.out.println("  - " + clazz.getName());
        }
    }
}

CGLIB动态代理

CGLIB(Code Generation Library)是一个强大的、高性能的代码生成库,它可以在运行时扩展Java类和实现Java接口。与JDK动态代理不同,CGLIB可以代理没有实现接口的类。

java 复制代码
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 1. 目标类(不需要实现接口)
class ProductService {
    public void addProduct(String productName) {
        System.out.println("添加产品: " + productName);
    }
    
    public void updateProduct(String productName) {
        System.out.println("更新产品: " + productName);
    }
}

// 2. 创建MethodInterceptor实现类
class CglibProxyInterceptor 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代理】方法执行完成,耗时: " + (end - start) + "ms");
        
        return result;
    }
}

// 3. CGLIB代理工厂
class CglibProxyFactory {
    public static Object createProxy(Class<?> targetClass) {
        // 创建增强器
        Enhancer enhancer = new Enhancer();
        
        // 设置父类(目标类)
        enhancer.setSuperclass(targetClass);
        
        // 设置回调
        enhancer.setCallback(new CglibProxyInterceptor());
        
        // 创建代理对象
        return enhancer.create();
    }
}

// 4. 客户端使用
public class CglibProxyDemo {
    public static void main(String[] args) {
        // 创建CGLIB代理
        ProductService proxy = (ProductService) CglibProxyFactory.createProxy(ProductService.class);
        
        // 通过代理对象调用方法
        proxy.addProduct("笔记本电脑");
        proxy.updateProduct("智能手机");
        
        // 可以查看代理类的信息
        System.out.println("代理类: " + proxy.getClass().getName());
        System.out.println("代理类的父类: " + proxy.getClass().getSuperclass().getName());
    }
}

动态代理的优缺点

优点:

  • 高度灵活,一个代理类可以代理多个接口
  • 减少代码量,不需要为每个目标类创建代理类
  • 可以在运行时决定代理哪个对象

缺点:

  • JDK动态代理只能代理实现了接口的类
  • CGLIB不能代理final类或final方法
  • 动态代理的性能略低于静态代理(但影响通常很小)

静态代理 vs 动态代理:核心对比

特性 静态代理 动态代理
实现时机 编译时 运行时
实现方式 手动编写代理类 通过反射或字节码生成
灵活性 低,一个代理类只能代理一个接口 高,一个代理类可以代理多个接口
代码量 多,需要为每个目标类编写代理类 少,通过通用代码处理多个类
性能 略高(直接调用) 略低(反射调用)
适用场景 接口方法少,代理逻辑简单 接口方法多,代理逻辑复杂

实际应用场景

静态代理适用场景

  • AOP(面向切面编程)的简单实现
  • 权限控制
  • 本地远程代理

动态代理适用场景

  1. Spring AOP:Spring框架使用JDK动态代理和CGLIB实现AOP
  2. MyBatis:Mapper接口的代理实现
  3. RPC框架:远程方法调用的客户端代理
  4. 事务管理:声明式事务的实现
  5. 日志记录:方法调用的日志记录

Spring框架中的代理示例

java 复制代码
// Spring中常用的注解驱动代理
@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    @Override
    @LogExecutionTime // 自定义注解,通过AOP实现方法执行时间日志
    public User findUserById(Long id) {
        // 业务逻辑
        return userRepository.findById(id);
    }
    
    @Override
    @PreAuthorize("hasRole('ADMIN')") // Spring Security权限控制
    public void deleteUser(Long id) {
        // 业务逻辑
        userRepository.deleteById(id);
    }
}

总结

代理模式是Java中非常重要且实用的设计模式。静态代理简单直观,适合简单的代理需求;动态代理灵活强大,适合复杂的系统架构。理解两者的原理和适用场景,能够帮助我们在实际开发中做出更合适的技术选择。

无论是静态代理还是动态代理,它们都体现了面向对象设计的一个重要原则:对扩展开放,对修改关闭。通过代理模式,我们可以在不修改原有代码的基础上,为系统添加新的功能,这大大提高了代码的可维护性和可扩展性。

相关推荐
Marktowin5 小时前
Mybatis-Plus更新操作时的一个坑
java·后端
赵文宇5 小时前
CNCF Dragonfly 毕业啦!基于P2P的镜像和文件分发系统快速入门,在线体验
后端
程序员爱钓鱼5 小时前
Node.js 编程实战:即时聊天应用 —— WebSocket 实现实时通信
前端·后端·node.js
Libby博仙6 小时前
Spring Boot 条件化注解深度解析
java·spring boot·后端
源代码•宸6 小时前
Golang原理剖析(Map 源码梳理)
经验分享·后端·算法·leetcode·golang·map
小周在成长7 小时前
动态SQL与MyBatis动态SQL最佳实践
后端
瓦尔登湖懒羊羊7 小时前
TCP的自我介绍
后端
小周在成长7 小时前
MyBatis 动态SQL学习
后端
子非鱼9217 小时前
SpringBoot快速上手
java·spring boot·后端