JDK动态代理、Cglib动态代理及Spring AOP

JDK动态代理、Cglib动态代理及Spring AOP

引言

讲解JDK动态代理以及CGLib动态代理。

1. JDK动态代理

1.1 创建代理类及返回代理对象

jdk动态代理通过 proxy 类的静态方法 newProxyInstance 来创建代理类 并且 返回代理类 对象

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

其中

loader:类加载器,指定类加载器是为了精确地定位类。

interfaces:接口的Class类数组。使用JDK的反射实现动态代理必须要有接口,因为生成的代理需要实现这些接口,这样生成的代理类对象才能转化为代理目标的接口对象,进而根据接口中的方法调用处理器中的invoke方法。

h:InvocationHandler类型,它是每个代理类对应的处理器

1.2 InvocationHandler

InvocationHandler是每个代理类对应的处理器,它定义了一个invoke方法,其签名如下:

Object invoke(Object proxy, Method method, Object[] args)

其中

Object:方法调用的返回值,该返回值可以作为被代理的方法调用的返回值。

proxy:代理类对象。

method:目标类中被代理的方法。

args:目标类中被代理的方法的运行参数。

1.3 JDK动态代理的不足

在JDK中使用动态代理,必须有类的接口。因为生成的代理需要实现这个接口,这样我们生成的代理类对象才能转化为代理目标的接口对象,然后根据接口中的方法,调用处理器中invoke方法

2. Cglib动态代理

2.1 概述

为了弥补JDK动态代理的不足,第三方组织封装了一套工具包,即Cglib的工具包。这套包不基于接口,而是基于父子继承,通过重写的形式扩展方法,并且这个子类工具是自动生成的。

CGLIB(Code Generation Library)是一个强大的字节码生成库,常用于在运行时动态生成某个类的子类,实现方法拦截等功能。与JDK动态代理不同,CGLIB是通过继承目标类来创建代理对象的,因此它不要求目标类实现接口,适用于没有实现接口的类。

CGLIB动态代理的基本原理是:通过字节码技术,在运行时生成目标类的子类,并重写其中的方法(即拦截方法的调用)。

目标类不需要实现接口:CGLIB可以对没有实现接口的类进行代理,而JDK动态代理只能代理实现了接口的类。

通过继承的方式创建代理类:CGLIB通过继承目标类并重写其中的方法来实现动态代理,因此代理类是目标类的子类。

性能比JDK动态代理高:虽然JDK代理适用于接口,但CGLIB通过字节码修改直接在目标类基础上生成代理对象,相对更高效。

2.2 CGLIB代理的使用

CGLIB代理是通过继承目标类并在其方法上添加拦截逻辑来实现的。CGLIB的核心类是 Enhancer,它用于创建代理对象。

步骤

添加CGLIB依赖:在项目中需要添加CGLIB的依赖,如果使用Maven管理项目,可以在pom.xml中添加如下依赖:

<dependency>

<groupId>cglib</groupId>

<artifactId>cglib</artifactId>

<version>3.3.0</version>

</dependency>

创建目标类:CGLIB代理是通过继承目标类来实现的,因此目标类不需要实现接口。

public class Hello {

public void sayHello(String name) {

System.out.println("Hello,World " + name);

}

}

创建 MethodInterceptor 实现类:MethodInterceptor 接口用于拦截目标类的方法调用。

import org.springframework.cglib.proxy.MethodInterceptor;

import org.springframework.cglib.proxy.MethodProxy;

public class HelloInterceptor implements MethodInterceptor {

@Override

public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {

System.out.println("Before method call: " + method.getName());

Object result = proxy.invokeSuper(obj, args); // 调用父类方法(目标类方法)

System.out.println("After method call: " + method.getName());

return result;

}

}

创建代理对象:使用 Enhancer 创建目标类的代理对象。

import org.springframework.cglib.proxy.Enhancer;

public class CglibProxyTest {

public static void main(String[] args) {

// 创建目标对象

Hello hello = new Hello();

// 创建CGLIB代理

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(Hello.class); // 设置目标类

enhancer.setCallback(new HelloInterceptor()); // 设置拦截器

// 创建代理对象

Hello proxy = (Hello) enhancer.create();

// 调用代理对象的方法

proxy.sayHello("World");

}

}

输出结果

Before method call: sayHello

Hello, World

After method call: sayHello

2.3 关键类和方法

Enhancer:CGLIB的核心类,通过它可以生成代理对象。

setSuperclass(Class):设置代理类的父类。

setCallback(MethodInterceptor):设置拦截器,它会在调用目标方法时拦截并执行自定义逻辑。

create():创建代理对象。

MethodInterceptor:这是CGLIB的拦截器接口,intercept()方法会在代理对象的方法被调用时执行。在 intercept() 中,通常会调用 MethodProxy.invokeSuper() 来调用目标类的实际方法。

MethodProxy:CGLIB提供的工具类,invokeSuper() 方法用于执行父类(目标类)的方法。

2.4 与JDK动态代理的对比

代理方式不同:JDK动态代理是基于接口的,而CGLIB基于类继承。

目标类要求:JDK动态代理要求目标类实现接口,而CGLIB不需要目标类实现接口,可以代理没有接口的类。

性能:CGLIB由于使用继承的方式创建代理对象,相较于JDK动态代理可能稍微高效一些,但由于是通过字节码生成,所以性能开销也不能忽视。

限制:CGLIB不能代理 final 类和 final 方法,因为CGLIB是通过继承和覆盖父类的方法来实现的,final 修饰符会阻止方法的重写。

2.5 应用场景

没有接口的类:当目标类没有实现接口时,JDK动态代理无法使用,这时CGLIB是一个好的选择。

性能要求较高:CGLIB代理创建代理类的方式比JDK代理更直接,通常性能较好,适合对性能有要求的场景。

3. 总结

JDK 动态代理CGLIB 动态代理的底层实现都与字节码相关,但具体实现方式不同:


3.1 JDK 动态代理的底层实现

JDK 动态代理的核心是 基于接口的反射机制,依赖 java.lang.reflect.ProxyInvocationHandler 实现,其底层会 在运行时动态生成字节码,但不需要直接操作字节码库(如 ASM)。具体流程如下:

  1. 动态生成代理类
    当调用 Proxy.newProxyInstance() 时,JVM 会在内存中动态生成一个代理类(如 $Proxy0),该类会实现目标接口。

  2. 方法调用转发
    代理类中的方法会通过 InvocationHandler.invoke() 将调用转发到目标对象。

  3. 字节码生成方式
    JDK 动态代理的字节码生成由 JVM 内部实现(通过 sun.misc.ProxyGenerator 类生成字节码),开发者无需直接操作字节码。


3.2 CGLIB 动态代理的底层实现

CGLIB 的底层基于 字节码操作库(如 ASM),直接生成目标类的子类,并通过继承重写方法的方式实现代理。具体流程如下:

  1. 动态生成子类
    CGLIB 使用 ASM 库直接操作字节码,生成目标类的子类(如 TargetClass$$EnhancerByCGLIB$$...)。

  2. 方法拦截
    子类重写父类方法,并通过 MethodInterceptor 拦截方法调用。

  3. 字节码生成方式
    CGLIB 显式使用 ASM 库 生成和修改字节码,需要依赖 cglibasm 的 JAR 包。


3.3 对比总结

特性

JDK 动态代理

CGLIB 动态代理

底层技术

JVM 内部生成字节码(无需显式操作)

使用 ASM 库显式操作字节码

代理方式

基于接口(实现相同接口)

基于继承(生成目标类的子类)

性能

生成代理较快,方法调用稍慢(反射开销)

生成代理较慢,方法调用较快(直接调用子类方法)

依赖

JDK 原生支持

需要引入 cglibasm 依赖

限制

只能代理接口方法

无法代理 final 类或方法


3.4 示例代码

JDK 动态代理生成字节码

// 目标接口
public interface UserService {
    void save();
}

// 代理生成代码
UserService proxy = (UserService) Proxy.newProxyInstance(
    ClassLoader.getSystemClassLoader(),
    new Class[]{UserService.class},
    (InvocationHandler) (p, method, args) -> {
        System.out.println("Before method");
        return method.invoke(target, args);
    }
);

// 生成的代理类字节码(示例)
public final class $Proxy0 extends Proxy implements UserService {
    public final void save() {
        super.h.invoke(this, m3, null);
    }
}

CGLIB 动态代理生成字节码

// 目标类(无需实现接口)
public class UserService {
    public void save() {
        System.out.println("Save user");
    }
}

// 使用 CGLIB 生成代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("Before method");
    return proxy.invokeSuper(obj, args);
});
UserService proxy = (UserService) enhancer.create();

// 生成的子类字节码(示例)
public class UserService$$EnhancerByCGLIB$$1234 extends UserService {
    public void save() {
        MethodInterceptor interceptor = ...;
        interceptor.intercept(this, method, args, proxy);
    }
}

LICENSED UNDER CC BY-NC-SA 4.0