基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Java动态代理:JDK与CGLIB深度解析

知识点图片

本文讲解Java动态代理,核心是对比JDK(基于接口)和CGLIB(基于继承)这两种主流实现方式的原理、代码与应用场景。

我们来详细、系统地讲解一下Java中的动态代理。

目录

  1. 什么是代理模式?
  2. 什么是动态代理? (与静态代理对比)
  3. 为什么需要动态代理? (应用场景)
  4. Java中实现动态代理的两种主流方式
    • JDK动态代理
    • CGLIB动态代理
  5. 代码实战
    • JDK动态代理示例
    • CGLIB动态代理示例
  6. JDK动态代理 vs. CGLIB动态代理 (对比总结)
  7. 总结

1. 什么是代理模式?

代理模式(Proxy Pattern)是一种设计模式。它的核心思想是:为其他对象提供一种代理以控制对这个对象的访问

打个比方,你不想亲自处理租房的繁琐事宜(找房、看房、谈价),于是你找了一个房产中介。这个中介就是你的“代理”。

  • 你 (Client):调用者。
  • 房产中介 (Proxy):代理对象。
  • 房东 (Real Subject):被代理的真实对象。

中介不仅能帮你完成租房的核心业务,还可以在此基础上增加一些额外的服务,比如验证房源真伪、提供法律咨询等。这些额外服务就是“增强”功能。

2. 什么是动态代理?

代理模式可以分为 静态代理动态代理

  • 静态代理

    • 特点:代理类是在编译时就创建好的(.java文件是程序员手写的)。
    • 实现:代理类和被代理类需要实现同一个接口。代理类持有一个被代理对象的引用,并在自己的方法中调用被代理对象的方法,同时可以添加额外逻辑。
    • 缺点:非常不灵活。如果接口中新增了一个方法,那么所有的代理类都需要手动修改。如果有很多个类需要被代理,就需要创建同样多的代理类,导致类爆炸。
  • 动态代理

    • 特点:代理类是在运行时动态生成的,我们不需要手动编写代理类的代码。
    • 优势:非常灵活。可以为一个或多个接口动态地生成一个代理类,解决了静态代理的缺点。

3. 为什么需要动态代理?(应用场景)

动态代理的核心价值在于 在不修改原始类代码的情况下,为其增加额外的功能。这正是面向切面编程(AOP)思想的体现。

常见的应用场景包括:

  1. 统一日志记录:在方法调用前后记录日志。
  2. 事务管理:在方法开始前开启事务,在方法结束后根据执行情况提交或回滚事务(Spring的@Transactional就是这么做的)。
  3. 性能监控:记录方法的执行时间。
  4. 权限控制:在方法执行前检查当前用户是否有权限。
  5. 缓存:在方法执行前检查缓存,如果命中则直接返回,否则执行方法并将结果放入缓存。

4. Java中实现动态代理的两种主流方式

Java生态中主要有两种实现动态代理的方式:JDK自带的动态代理和第三方的CGLIB库。

4.1 JDK动态代理

  • 核心java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。
  • 原理:利用反射机制。在运行时创建一个实现了指定接口列表的代理类。当调用代理对象的方法时,这个调用会被转发到 InvocationHandlerinvoke 方法中处理。
  • 硬性要求被代理的类必须实现至少一个接口。JDK代理是基于接口的。

4.2 CGLIB动态代理

  • 核心:CGLIB是一个强大的、高性能的代码生成库。它被广泛应用于AOP框架(如Spring AOP)中。
  • 原理:利用字节码技术(底层是ASM)。它在运行时动态地为目标类创建一个子类作为代理类,并重写父类中的非final方法,从而实现功能的增强。
  • 优势被代理的类可以不实现接口
  • 限制:无法代理被 final 修饰的类和方法,因为final类不能被继承,final方法不能被重写。

5. 代码实战

5.1 JDK动态代理示例

步骤:

  1. 定义一个接口。
  2. 创建一个实现该接口的真实对象类。
  3. 创建一个 InvocationHandler 实现类,用于编写代理逻辑。
  4. 使用 Proxy.newProxyInstance() 方法创建代理对象。

代码:

1. 定义接口 UserService

java
public interface UserService {
    void addUser(String username);
    void deleteUser(String username);
}

2. 实现类 UserServiceImpl

java
public 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. 创建 InvocationHandler - MyInvocationHandler

java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {
    // 持有被代理的真实对象
    private final Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 1. 在调用真实方法前的增强逻辑
        System.out.println("--- [前置增强] 记录日志:准备执行 " + method.getName() + " 方法 ---");

        // 2. 调用真实对象的方法 (通过反射)
        Object result = method.invoke(target, args);

        // 3. 在调用真实方法后的增强逻辑
        System.out.println("--- [后置增强] 记录日志:" + method.getName() + " 方法执行完毕 ---");

        return result;
    }
}

4. 创建代理对象并测试

java
import java.lang.reflect.Proxy;

public class JdkProxyDemo {
    public static void main(String[] args) {
        // 1. 创建被代理的真实对象
        UserService realUserService = new UserServiceImpl();

        // 2. 创建 InvocationHandler,并将真实对象传入
        InvocationHandler handler = new MyInvocationHandler(realUserService);

        // 3. 使用 Proxy.newProxyInstance() 创建代理对象
        // 参数1: 类加载器,用于加载动态生成的代理类
        // 参数2: 代理类需要实现的接口数组
        // 参数3: 调用处理器
        UserService proxyUserService = (UserService) Proxy.newProxyInstance(
                realUserService.getClass().getClassLoader(),
                realUserService.getClass().getInterfaces(),
                handler
        );

        // 4. 通过代理对象调用方法
        System.out.println("代理对象的类型:" + proxyUserService.getClass().getName());
        proxyUserService.addUser("Alice");
        System.out.println("--------------------");
        proxyUserService.deleteUser("Bob");
    }
}

输出:
```
代理对象的类型:com.sun.proxy.$Proxy0
--- [前置增强] 记录日志:准备执行 addUser 方法 ---
数据库操作:添加用户 Alice
--- [后置增强] 记录日志:addUser 方法执行完毕 ---

--- [前置增强] 记录日志:准备执行 deleteUser 方法 ---
数据库操作:删除用户 Bob
--- [后置增强] 记录日志:deleteUser 方法执行完毕 ---

plaintext

#### 5.2 CGLIB动态代理示例

首先,需要添加CGLIB的依赖。
Maven:
```xml
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

步骤:

  1. 创建一个普通的类(无需实现接口)。
  2. 创建一个 MethodInterceptor 实现类,用于编写代理逻辑。
  3. 使用 Enhancer 类来创建代理对象。

代码:

1. 创建一个普通类 ProductService

java
public class ProductService {
    public void addProduct(String productName) {
        System.out.println("数据库操作:添加产品 " + productName);
    }

    public final void deleteProduct(String productName) {
        System.out.println("这是一个 final 方法,无法被CGLIB代理!");
    }
}

2. 创建 MethodInterceptor - MyMethodInterceptor

java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 1. 前置增强
        System.out.println("--- [CGLIB前置增强] 开始事务 ---");

        // 2. 调用父类(真实对象)的方法
        // 注意:这里推荐使用 proxy.invokeSuper(obj, args) 而不是 method.invoke(target, args)
        // 因为 invokeSuper 性能更高,且能避免一些潜在问题。
        Object result = proxy.invokeSuper(obj, args);

        // 3. 后置增强
        System.out.println("--- [CGLIB后置增强] 提交事务 ---");

        return result;
    }
}

3. 创建代理对象并测试

java
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyDemo {
    public static void main(String[] args) {
        // 1. 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        
        // 2. 设置父类(被代理的类)
        enhancer.setSuperclass(ProductService.class);
        
        // 3. 设置回调(方法拦截器)
        enhancer.setCallback(new MyMethodInterceptor());
        
        // 4. 创建代理对象
        ProductService proxyProductService = (ProductService) enhancer.create();

        // 5. 通过代理对象调用方法
        System.out.println("代理对象的类型:" + proxyProductService.getClass().getName());
        proxyProductService.addProduct("Laptop");
        
        // 调用 final 方法,不会被代理
        System.out.println("--------------------");
        proxyProductService.deleteProduct("Mouse");
    }
}

输出:

plaintext
代理对象的类型:com.example.ProductService$$EnhancerByCGLIB$$...
--- [CGLIB前置增强] 开始事务 ---
数据库操作:添加产品 Laptop
--- [CGLIB后置增强] 提交事务 ---
--------------------
这是一个 final 方法,无法被CGLIB代理!

6. JDK动态代理 vs. CGLIB动态代理

特性 JDK 动态代理 CGLIB 动态代理
实现基础 Java 反射机制 ASM 字节码操作
代理前提 目标类必须实现接口 目标类不能是 final,否则无法创建子类
代理方式 在运行时动态创建接口的实现类 在运行时动态创建目标类的子类
方法限制 - 不能代理 finalprivate 方法,因为子类无法重写它们
性能 在 JDK 1.8 之后,性能与 CGLIB 相差无几,甚至在某些场景下更快。 传统上认为性能更高,但现在优势不明显。创建代理对象速度较慢。
依赖 JDK 自带,无需额外依赖。 需要引入第三方 cglib 库。

如何选择?

  • 如果目标类实现了接口,或者你推崇面向接口编程,优先使用 JDK 动态代理
  • 如果目标类没有实现接口,而且你又需要代理它,那么就使用 CGLIB
  • 像 Spring AOP 这样的框架会智能选择:如果目标类实现了接口,默认用 JDK 动态代理;如果没实现接口,就用 CGLIB。

7. 总结

动态代理是 Java 中一个非常强大和核心的技术,是理解许多主流框架(如 Spring、Hibernate)内部原理的关键。它允许我们在不侵入原有代码的情况下,对方法进行增强,极大地提高了代码的灵活性和可维护性,是实现 AOP 的基石。掌握 JDK 和 CGLIB 这两种代理方式,能够帮助你更深入地理解和应用 Java。

00:00
00:00