杰瑞科技汇

Java动态代理与CGLIB有何区别?

  1. 什么是动态代理? (铺垫,理解为什么需要它)
  2. JDK 动态代理简介 (作为对比,理解其局限性)
  3. CGLIB 动态代理详解 (核心内容)
    • 是什么?工作原理?
    • 如何使用?
    • 与 JDK 动态代理的核心区别
  4. CGLIB 的高级特性
    • MethodInterceptor (核心拦截器)
    • Enhancer (核心类)
    • 回调过滤器 (CallbackFilter)
  5. CGLIB 的注意事项
    • 性能
    • 构造器问题
    • final 方法/类
  6. 如何选择?

什么是动态代理?

在 Java 中,代理是一种设计模式,它提供了一个与真实对象(被代理对象)具有相同接口的代理对象,客户端可以通过这个代理对象来访问真实对象,代理对象可以在调用真实对象的方法前后,执行一些额外的操作,

  • 日志记录
  • 权限控制
  • 事务管理
  • 性能监控

动态代理与静态代理(我们自己手动编写代理类)不同,它是在程序运行时,通过反射机制动态地创建代理类及其对象,这大大提高了代码的灵活性和可维护性。


JDK 动态代理简介

JDK 自带了一个动态代理实现,它是最基础、最常用的动态代理方式。

核心特点:

  • 基于接口:JDK 动态代理要求被代理的对象必须实现一个或多个接口,代理类会实现这些相同的接口。
  • 核心类java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler
  • 工作方式:我们创建一个 InvocationHandler 的实现类,然后通过 Proxy.newProxyInstance() 方法,传入被代理对象的类加载器、接口列表和我们实现的 InvocationHandler,来动态生成一个代理对象。

局限性: 如果我们的目标类没有实现任何接口,JDK 动态代理就无能为力了,对于第三方库中的类(如 HashMap)或者我们自己写的普通 POJO 类,就无法使用 JDK 动态代理。

这就引出了我们的主角:CGLIB


CGLIB 动态代理详解

1 是什么?工作原理?

CGLIB (Code Generation Library) 是一个强大的、高性能的代码生成库,它可以在运行时动态地生成一个子类,并覆盖其非 final 的方法来实现代理。

核心特点:

  • 基于类:CGLIB 不要求目标类实现任何接口,它是通过继承来创建代理对象的。
  • 工作原理:它通过生成一个目标类的子类,然后将这个子类作为代理类,在这个子类中,它会重写父类(目标类)的非 final 和非 private 方法,当调用代理对象的方法时,实际上会调用到 CGLIB 提供的回调方法(MethodInterceptor),我们可以在回调方法中插入自己的逻辑,并决定是否以及如何调用父类(目标类)的原方法。

类比:你可以把 CGLIB 想象成一个“魔法工厂”,你给它一个“图纸”(目标类),它能瞬间给你造出一个“升级版”(代理子类),这个升级版和原版长得一模一样,但你可以给它额外添加一些“技能”(增强逻辑)。

2 如何使用?

你需要添加 CGLIB 的依赖,如果你使用的是 Spring Boot,它通常会自动包含,如果需要手动添加,Maven 配置如下:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version> <!-- 使用较新版本 -->
</dependency>

使用步骤:

  1. 创建一个目标类(可以是一个普通类,不需要实现接口)。
  2. 创建一个 MethodInterceptor 接口的实现类,这是拦截逻辑的核心。
  3. 使用 Enhancer 类来配置和生成代理对象。

代码示例:

目标类

// RealObject.java - 没有实现任何接口
public class RealObject {
    public void doSomething() {
        System.out.println("RealObject: 正在执行核心业务逻辑...");
    }
    public String doSomethingElse(String input) {
        System.out.println("RealObject: 正在执行另一个业务逻辑,输入是: " + input);
        return "处理结果: " + input;
    }
}

拦截器

// MyMethodInterceptor.java
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
    /**
     * @param obj         代理对象(也就是生成的子类的实例)
     * @param method      被调用的父类(目标类)的方法
     * @param args        方法调用时传入的参数
     * @param methodProxy  用于调用父类方法的代理对象
     * @return            方法调用的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("【CGLIB 拦截器】方法调用前: " + method.getName());
        // 调用父类的原始方法(即目标类的方法)
        // 这里有两种方式:
        // 1. method.invoke(target, args); // 如果有目标对象实例
        // 2. methodProxy.invokeSuper(obj, args); // 推荐方式,性能更高
        Object result = methodProxy.invokeSuper(obj, args);
        System.out.println("【CGLIB 拦截器】方法调用后: " + method.getName() + ", 返回值: " + result);
        return result;
    }
}

创建并使用代理

// CglibDemo.java
import net.sf.cglib.proxy.Enhancer;
public class CglibDemo {
    public static void main(String[] args) {
        // 1. 创建 Enhancer 对象,相当于 JDK 中的 Proxy
        Enhancer enhancer = new Enhancer();
        // 2. 设置父类(即要代理的类)
        enhancer.setSuperclass(RealObject.class);
        // 3. 设置回调对象,即我们的 MethodInterceptor 实现
        enhancer.setCallback(new MyMethodInterceptor());
        // 4. 创建代理对象
        RealObject proxyObject = (RealObject) enhancer.create();
        // 5. 通过代理对象调用方法
        System.out.println("--- 调用 doSomething() ---");
        proxyObject.doSomething();
        System.out.println("\n--- 调用 doSomethingElse() ---");
        String result = proxyObject.doSomethingElse("Hello CGLIB");
        System.out.println("从主方法获取的最终结果: " + result);
    }
}

输出结果:

--- 调用 doSomething() ---
【CGLIB 拦截器】方法调用前: doSomething
RealObject: 正在执行核心业务逻辑...
【CGLIB 拦截器】方法调用后: doSomething, 返回值: null
--- 调用 doSomethingElse() ---
【CGLIB 拦截器】方法调用前: doSomethingElse
RealObject: 正在执行另一个业务逻辑,输入是: Hello CGLIB
【CGLIB 拦截器】方法调用后: doSomethingElse, 返回值: 处理结果: Hello CGLIB
从主方法获取的最终结果: 处理结果: Hello CGLIB

3 与 JDK 动态代理的核心区别

特性 JDK 动态代理 CGLIB 动态代理
实现机制 基于接口,使用 java.lang.reflect.Proxy API 基于类继承,使用 net.sf.cglib.proxy.Enhancer
代理对象类型 实现目标类接口的类 目标类的子类
目标类要求 必须实现至少一个接口 可以没有接口,对普通类有效
性能 第一次调用较慢,后续调用与原生方法接近 整体性能通常优于 JDK 代理,invokeSuperinvoke
局限性 无法代理没有接口的类 无法代理 final 类和 final 方法
依赖 JDK 内置,无需额外库 需要引入 cglib 第三方库

CGLIB 的高级特性

1 MethodInterceptor (核心拦截器)

这是最常用的回调类型,提供了对方法调用的完全控制,如上例所示,intercept 方法是核心,它可以在方法执行前后插入任意逻辑,并可以决定是否执行原始方法。

2 Enhancer (核心类)

Enhancer 是 CGLIB 中最核心的类,用于生成类,它的主要配置项包括:

  • setSuperclass(Class<?> superclass): 设置要生成的类的父类(即被代理的类)。
  • setCallback(Callback callback): 设置一个单一的回调对象,作用于所有方法。
  • setCallbacks(Callback[] callbacks): 设置一个回调数组,结合 CallbackFilter 使用,可以为不同方法指定不同的回调。
  • setCallbackFilter(CallbackFilter filter): 设置回调过滤器,决定方法调用时使用哪个回调。
  • create(): 执行生成操作并返回代理实例。

3 回调过滤器 (CallbackFilter)

当一个类中,我们想对不同方法采用不同的增强逻辑时,CallbackFilter 就派上用场了。

示例: 我们想对 doSomething() 方法进行日志增强,对 doSomethingElse() 方法进行性能监控增强。

// MyCallbackFilter.java
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
import java.lang.reflect.Method;
public class MyCallbackFilter implements CallbackFilter {
    // 定义两种回调
    // NoOp.INSTANCE 表示不进行任何拦截,直接调用原方法
    // new MyLoggingInterceptor() 是我们的日志拦截器
    // new MyPerformanceInterceptor() 是我们的性能拦截器
    @Override
    public int accept(Method method) {
        if (method.getName().equals("doSomething")) {
            return 0; // 返回第一个回调
        } else if (method.getName().equals("doSomethingElse")) {
            return 1; // 返回第二个回调
        }
        // 默认不拦截
        return 2;
    }
}
// CglibFilterDemo.java
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
public class CglibFilterDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealObject.class);
        // 创建回调数组
        Callback[] callbacks = new Callback[]{
                new MyLoggingInterceptor(),      // 索引 0
                new MyPerformanceInterceptor(), // 索引 1
                NoOp.INSTANCE                   // 索引 2 (无操作)
        };
        enhancer.setCallbacks(callbacks);
        enhancer.setCallbackFilter(new MyCallbackFilter());
        RealObject proxy = (RealObject) enhancer.create();
        proxy.doSomething();
        proxy.doSomethingElse("test");
    }
}

CGLIB 的注意事项

1 性能

CGLIB 的性能通常优于 JDK 动态代理,尤其是在方法被多次调用时,这是因为 CGLIB 生成的字节码直接继承自目标类,而 JDK 代理需要通过反射 InvocationHandler 来分派调用,生成 CGLIB 代理类本身的开销比 JDK 代理要大。

2 构造器问题

CGLIB 通过生成子类来创建代理,因此它会调用目标类的无参构造器,如果目标类没有无参构造器,CGLIB 将会抛出异常。

public class NoArgConstructorRequired {
    // 如果没有这个无参构造器,CGLIB会失败
    public NoArgConstructorRequired() {
    }
    public void test() {
        System.out.println("test");
    }
}

3 final 方法/类

CGLIB 的核心是继承,根据 Java 的规则,final 类和 final 方法是不能被继承和重写的。

  • 无法代理 finalEnhancer.setSuperclass() 会直接抛出异常。
  • 无法代理 final 方法:CGLIB 会忽略 final 方法的拦截,直接调用原始方法,增强逻辑不会生效。

如何选择?

场景 推荐方案 原因
目标类已经实现了接口 优先使用 JDK 动态代理 JDK 是标准库,无额外依赖,更符合面向接口的设计思想。
目标类没有实现接口 必须使用 CGLIB CGLIB 是唯一的选择(或者自己用字节码操作工具如 ASM 手动实现)。
需要代理 final 类或方法 CGLIB 不可行 需要考虑其他方案,如 AOP 框架(如 Spring)的组合代理或字节码增强。
对性能有极致要求 CGLIB CGLIB 的方法调用性能通常略优,但差别不大,对于绝大多数应用,JDK 代理已经足够快。

在现代框架(如 Spring)中,代理策略通常是自动选择的:

  • 如果目标类实现了接口,默认使用 JDK 动态代理。
  • 如果目标类没有实现接口,则自动降级使用 CGLIB。
  • 你也可以通过配置强制 Spring 使用 CGLIB 代理(为了使用 @Transactional 注解时能代理类本身的方法)。

希望这份详细的解释能帮助你完全理解 CGLIB 动态代理!

分享:
扫描分享到社交APP
上一篇
下一篇