【JavaSE】动态代理技术详解与案例实战

一、动态代理概述

动态代理是Java语言中一项强大的技术特性,它允许程序在运行时动态创建代理对象,而无需在编译期预先定义代理类。与静态代理相比,动态代理具有更高的灵活性和可维护性,是现代Java框架(如Spring)的核心基础之一。

1.1 静态代理 vs 动态代理

静态代理需要在编译期手动编写代理类,每个被代理类都需要对应一个代理类:

typescript 复制代码
// 静态代理示例:需要为每个类编写代理类
public class UserServiceStaticProxy implements UserService {
    private UserService userService;
    
    public UserServiceStaticProxy(UserService userService) {
        this.userService = userService;
    }
    
    @Override
    public void addUser(String name) {
        System.out.println("Before method");
        userService.addUser(name);
        System.out.println("After method");
    }
}

动态代理则在运行时动态生成代理类,大大减少了代码量:

scss 复制代码
// 动态代理:一个处理器可以代理多个类
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LoggingHandler(target)
);

1.2 Java中两种动态代理方式

Java平台主要提供两种动态代理实现机制:

  1. JDK动态代理 - 基于接口实现,Java原生支持
  2. CGLib动态代理 - 基于继承实现,需引入第三方库

这两种方式在实现原理、适用场景和性能特征上有着显著差异,下面我们将深入探讨这两种动态代理技术。

二、JDK动态代理详解

2.1 JDK动态代理的核心原理

JDK动态代理是基于接口 的代理方式。当目标类实现了至少一个接口时,可以使用JDK动态代理。其核心原理是通过反射机制在运行时创建一个实现了目标接口的代理类。 核心组件

  • java.lang.reflect.Proxy- 负责生成代理类
  • java.lang.reflect.InvocationHandler- 调用处理器,包含代理逻辑

2.2 JDK动态代理的实现机制

typescript 复制代码
// 1. 定义业务接口
public interface UserService {
    void addUser(String name);
    void deleteUser(String name);
}

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

// 3. 实现InvocationHandler
public class LoggingHandler implements InvocationHandler {
    private final Object target;
    
    public LoggingHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[JDK代理] 调用方法: " + method.getName());
        long start = System.currentTimeMillis();
        
        Object result = method.invoke(target, args);
        
        long end = System.currentTimeMillis();
        System.out.println("[JDK代理] 方法执行时间: " + (end - start) + "ms");
        return result;
    }
}

// 4. 使用代理
public class JdkProxyDemo {
    public static void main(String[] args) {
        UserService realService = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            realService.getClass().getClassLoader(),
            realService.getClass().getInterfaces(),
            new LoggingHandler(realService)
        );
        
        proxy.addUser("张三");
    }
}

2.3 JDK动态代理的创建流程

以下是JDK动态代理的创建过程:

flowchart TD A[目标对象] --> B[获取ClassLoader] B --> C[获取接口数组] C --> D[创建InvocationHandler] D --> E[调用Proxy.newProxyInstance] E --> F[生成代理类字节码] F --> G[加载代理类] G --> H[创建代理实例] H --> I[返回代理对象]

具体生成代理类的反编译代码大致如下:

scala 复制代码
// 反编译后的代理类示例
public final class $Proxy0 extends Proxy implements UserService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    
    public $Proxy0(InvocationHandler var1) throws {
        super(var1);
    }
    
    public final void addUser(String var1) throws {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    
    // 其他方法...
}

从反编译代码可以看出,JDK生成的代理类继承自Proxy类并实现了目标接口,这也就是为什么JDK动态代理只能基于接口而不能基于类。

三、CGLib动态代理详解

3.1 CGLib动态代理的核心原理

CGLib(Code Generation Library)是一个强大的、高性能的代码生成库,它通过继承目标类的方式来实现代理。当目标类没有实现任何接口时,可以使用CGLib动态代理。 CGLib通过在运行时创建目标类的子类,并重写其中的非final方法来实现代理功能。由于是基于继承,因此可以代理没有实现接口的普通Java类。

3.2 CGLib动态代理的实现机制

首先需要添加CGLib依赖:

xml 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

实现CGLib代理:

typescript 复制代码
// 1. 目标类(无需实现接口)
public class ProductService {
    public void addProduct(String name) {
        System.out.println("添加产品: " + name);
    }
    
    public String getProductInfo(String name) {
        return "产品信息: " + name;
    }
}

// 2. 实现MethodInterceptor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {
    private Object target;
    
    public Object getInstance(Object target) {
        this.target = target;
        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代理] 方法调用前: " + method.getName());
        long start = System.currentTimeMillis();
        
        // 使用invokeSuper调用父类方法
        Object result = proxy.invokeSuper(obj, args);
        
        long end = System.currentTimeMillis();
        System.out.println("[CGLib代理] 方法执行时间: " + (end - start) + "ms");
        return result;
    }
}

// 3. 使用代理
public class CglibTest {
    public static void main(String[] args) {
        ProductService target = new ProductService();
        CglibProxy proxyFactory = new CglibProxy();
        ProductService proxy = (ProductService) proxyFactory.getInstance(target);
        
        proxy.addProduct("笔记本电脑");
    }
}

3.3 CGLib的动态代理机制

CGLib采用FastClass机制为代理类和被代理类各生成一个类,为代理类或被代理类的方法分配索引,调用时直接根据索引来执行方法,避免了使用反射调用带来的性能开销。

四、JDK代理 vs CGLib代理全面对比

4.1 原理机制对比

特性 JDK动态代理 CGLib动态代理
实现基础 基于接口实现 基于继承实现
代理目标 只能代理接口 可以代理普通类
生成方式 反射机制生成代理类 字节码技术生成子类
方法调用 反射调用目标方法 通过FastClass机制直接调用

4.2 性能对比

根据实际测试数据,两种代理在性能上有明显差异:

场景 JDK动态代理 CGLib动态代理
创建速度 较快(反射生成) 较慢(字节码生成,约慢8倍)
执行速度 较慢(反射调用) 较快(直接调用,约快10倍)
内存占用 较小 较大(生成子类)

4.3 使用限制对比

限制方面 JDK动态代理 CGLib动态代理
接口要求 必须实现接口 无需接口
final限制 无限制 不能代理final类和方法
构造器要求 无特殊要求 需要默认构造器

五、Spring框架中的代理选择机制

Spring框架根据目标类的特性自动选择代理方式:

graph LR A[目标类] --> B{是否实现接口} B -->|是| C[JDK动态代理] B -->|否| D[CGLib动态代理] C --> E[生成代理对象] D --> E

在实际开发中,可以强制Spring使用CGLib代理:

xml 复制代码
<!-- 强制使用CGLib代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>

六、实际应用场景与最佳实践

6.1 选择建议

  1. 优先使用JDK动态代理的场景

    • 目标对象已经实现了接口
    • 需要频繁创建和销毁代理对象
    • 对启动性能要求较高
  2. 优先使用CGLib动态代理的场景

    • 目标对象没有实现接口
    • 单例对象,不需要频繁创建代理
    • 对运行时性能要求较高

6.2 性能优化建议

  1. 对于单例对象,使用CGLib代理更合适,因为执行性能更高
  2. 对于频繁创建销毁的对象,使用JDK动态代理,因为创建开销较小
  3. 缓存代理对象,避免重复创建带来的性能开销
  4. 合理使用条件判断,避免不必要的代理逻辑

6.3 综合示例:日志记录与性能监控

java 复制代码
// 综合代理工厂,根据条件选择代理方式
public class ProxyFactory {
    
    public static Object getProxy(Object target) {
        // 如果有接口,使用JDK代理;否则使用CGLib代理
        if (target.getClass().getInterfaces().length > 0) {
            return createJdkProxy(target);
        } else {
            return createCglibProxy(target);
        }
    }
    
    private static Object createJdkProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LoggingInvocationHandler(target)
        );
    }
    
    private static Object createCglibProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(new LoggingMethodInterceptor(target));
        return enhancer.create();
    }
}

// JDK代理处理器
class LoggingInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 统一的预处理逻辑
        long start = System.currentTimeMillis();
        System.out.println("开始执行: " + method.getName());
        
        try {
            Object result = method.invoke(target, args);
            return result;
        } finally {
            long end = System.currentTimeMillis();
            System.out.println("执行完成: " + method.getName() + ", 耗时: " + (end - start) + "ms");
        }
    }
}

// CGLib方法拦截器  
class LoggingMethodInterceptor implements MethodInterceptor {
    private final Object target;
    
    public LoggingMethodInterceptor(Object target) {
        this.target = target;
    }
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("开始执行: " + method.getName());
        
        try {
            // 使用invokeSuper调用父类方法
            Object result = proxy.invokeSuper(obj, args);
            return result;
        } finally {
            long end = System.currentTimeMillis();
            System.out.println("执行完成: " + method.getName() + ", 耗时: " + (end - start) + "ms");
        }
    }
}

七、总结

JDK动态代理和CGLib动态代理都是Java中强大的代理技术,各有其适用场景和优势:

  • JDK动态代理基于接口实现,是Java标准库的一部分,使用简单但功能有限
  • CGLib动态代理基于继承实现,功能更强大但需要第三方库支持

选择策略

  • 如果目标对象已经实现了接口,优先使用JDK动态代理
  • 如果目标对象没有接口或者是需要更高性能的单例,使用CGLib动态代理
  • 在Spring环境中,通常不需要手动选择,框架会自动选用合适的代理方式
相关推荐
XovH3 分钟前
Django 从 0 到 1 打造完整电商平台:收货地址管理
后端
Postkarte不想说话21 分钟前
Jupyter Lab安装
后端
fliter23 分钟前
在 Async Rust 中实现请求合并(Request Coalescing)
后端
王立志_LEO24 分钟前
Gunicorn 启动django服务
后端
fliter25 分钟前
一个让我调试一周的 Rust match 陷阱
后端
一只大袋鼠36 分钟前
SpringBoot 初学阶段知识点汇总(一)
spring boot·笔记·后端
Rust研习社38 分钟前
Rust 官方拟定 LLM 政策,防止 LLM 污染开源社区?
开发语言·后端·ai·rust·开源
无风听海1 小时前
ASP.NET Core Minimal API 深度解析
后端·asp.net
IT_陈寒1 小时前
Java的finally块竟然不是你想的那个finally!
前端·人工智能·后端