AOP:动态代理的替身文学(JDK vs CGLIB)

94 阅读5分钟

4.3 AOP:动态代理的替身文学(JDK vs CGLIB)

引言:AOP的替身文学

在Spring的世界里,AOP(面向切面编程)就像是一场替身文学大戏。你的业务代码是主角,而AOP则是那个默默无闻的替身演员,负责处理那些“脏活累活”,比如日志记录、事务管理、权限校验等。而这场替身文学的主角,就是动态代理。Spring提供了两种替身演员:JDK动态代理CGLIB动态代理。今天我们就来揭秘这场替身文学的幕后故事。


1. JDK动态代理:接口的忠实替身

1.1 什么是JDK动态代理? JDK动态代理是基于接口的代理模式。它要求被代理的类必须实现至少一个接口。Spring会为这个接口生成一个代理类,所有的调用都会通过这个代理类来执行。

1.2 核心原理

  • InvocationHandler:这是JDK动态代理的核心接口。你需要实现它的invoke方法,在这里定义代理逻辑。
  • Proxy.newProxyInstance:这是生成代理对象的方法。

1.3 代码示例

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

// 1. 定义接口
interface UserService {
    void saveUser();
}

// 2. 实现接口
class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("保存用户信息");
    }
}

// 3. 实现InvocationHandler
class UserServiceProxy implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("前置通知:开始保存用户");
        Object result = method.invoke(target, args);
        System.out.println("后置通知:用户保存成功");
        return result;
    }
}

// 4. 测试JDK动态代理
public class JdkProxyDemo {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new UserServiceProxy(userService)
        );
        proxy.saveUser();
    }
}

1.4 运行结果

前置通知:开始保存用户
保存用户信息
后置通知:用户保存成功

1.5 优缺点

  • 优点:基于接口,符合Java的设计原则。
  • 缺点:只能代理实现了接口的类。

2. CGLIB动态代理:类的万能替身

2.1 什么是CGLIB动态代理? CGLIB(Code Generation Library)是一个强大的代码生成库,它可以在运行时动态生成类的子类,从而实现对类的代理。与JDK动态代理不同,CGLIB不需要接口,直接对类进行代理。

2.2 核心原理

  • MethodInterceptor:这是CGLIB的核心接口。你需要实现它的intercept方法,在这里定义代理逻辑。
  • Enhancer:这是生成代理对象的工具类。

2.3 代码示例

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 OrderService {
    public void createOrder() {
        System.out.println("创建订单");
    }
}

// 2. 实现MethodInterceptor
class OrderServiceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("前置通知:开始创建订单");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("后置通知:订单创建成功");
        return result;
    }
}

// 3. 测试CGLIB动态代理
public class CglibProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class);
        enhancer.setCallback(new OrderServiceInterceptor());

        OrderService proxy = (OrderService) enhancer.create();
        proxy.createOrder();
    }
}

2.4 运行结果

前置通知:开始创建订单
创建订单
后置通知:订单创建成功

2.5 优缺点

  • 优点:不需要接口,可以直接代理类。
  • 缺点:生成代理类的速度较慢,且无法代理final类或final方法。

3. JDK vs CGLIB:替身演员的终极对决

特性JDK动态代理CGLIB动态代理
代理方式基于接口基于类
性能较快较慢(生成子类)
限制只能代理实现了接口的类无法代理final类或final方法
适用场景适合代理接口适合代理类

4. Spring的选择:替身演员的幕后导演

Spring在默认情况下会根据目标类是否实现了接口来选择使用JDK动态代理还是CGLIB动态代理:

  • 如果目标类实现了接口,Spring会优先使用JDK动态代理。
  • 如果目标类没有实现接口,Spring会使用CGLIB动态代理。

你也可以通过配置强制使用CGLIB:

<aop:config proxy-target-class="true">
    <!-- AOP配置 -->
</aop:config>

5. 图文并茂:替身文学的全景图

为了更直观地理解JDK动态代理和CGLIB动态代理的工作原理,我们绘制一张动态代理的全景图。这张图将展示两种代理模式的核心流程和区别。

AOP动态代理全景图

graph TD
    A[客户端] --> B[JDK动态代理]
    A --> C[CGLIB动态代理]

    subgraph JDK动态代理
        B --> D[Proxy.newProxyInstance]
        D --> E[InvocationHandler]
        E --> F[目标对象]
        F --> G[接口方法调用]
    end

    subgraph CGLIB动态代理
        C --> H[Enhancer.create]
        H --> I[MethodInterceptor]
        I --> J[目标对象]
        J --> K[类方法调用]
    end

    style B fill:#f9f,stroke:#333,stroke-width:2px
    style C fill:#bbf,stroke:#333,stroke-width:2px
    style D fill:#f96,stroke:#333,stroke-width:2px
    style E fill:#f96,stroke:#333,stroke-width:2px
    style H fill:#6f9,stroke:#333,stroke-width:2px
    style I fill:#6f9,stroke:#333,stroke-width:2px

图例说明

  1. JDK动态代理流程

    • 客户端调用代理对象。
    • 代理对象通过Proxy.newProxyInstance生成。
    • 调用逻辑由InvocationHandler处理。
    • 最终调用目标对象的接口方法。
  2. CGLIB动态代理流程

    • 客户端调用代理对象。
    • 代理对象通过Enhancer.create生成。
    • 调用逻辑由MethodInterceptor处理。
    • 最终调用目标对象的类方法。
  3. 颜色区分

    • 紫色:JDK动态代理相关组件。
    • 蓝色:CGLIB动态代理相关组件。
    • 橙色:JDK动态代理的核心类。
    • 绿色:CGLIB动态代理的核心类。

6. 常见面试题

6.1 JDK动态代理和CGLIB动态代理的区别是什么?

  • JDK动态代理基于接口,CGLIB基于类。
  • JDK动态代理性能较好,CGLIB生成代理类较慢。
  • JDK动态代理只能代理实现了接口的类,CGLIB可以代理普通类,但不能代理final类或final方法。

6.2 Spring AOP默认使用哪种动态代理?

  • Spring AOP默认使用JDK动态代理,如果目标类没有实现接口,则使用CGLIB动态代理。

6.3 如何强制Spring使用CGLIB动态代理?

  • 在Spring配置中设置proxy-target-class="true"

6.4 CGLIB动态代理的原理是什么?

  • CGLIB通过生成目标类的子类来实现代理,重写父类的方法,并在子类中插入代理逻辑。

6.5 JDK动态代理的InvocationHandler接口的作用是什么?

  • InvocationHandler接口用于定义代理逻辑,所有的代理方法调用都会通过invoke方法来处理。

7. 总结:替身文学的终极奥义

  • JDK动态代理:适合代理接口,性能较好,但限制较多。
  • CGLIB动态代理:适合代理类,功能强大,但性能稍差。

无论选择哪种替身演员,AOP都能让你的代码更加优雅,业务逻辑更加清晰。下次面试官问你AOP的原理时,你可以自信地说:“AOP就是一场替身文学,而Spring是这场戏的幕后导演!”