Seal^_^ 2025-12-05 09:02 采纳率: 72.6%
浏览 1

Spring AOP如何通过CGLIB和JDK动态代理实现方法拦截? 在什么情况下会选择使用CGLIB代理?

Spring AOP如何通过CGLIB和JDK动态代理实现方法拦截? 在什么情况下会选择使用CGLIB代理?

  • 写回答

2条回答 默认 最新

  • 檀越@新空间 2025-12-05 09:35
    关注

    上午好☀️☀️☀️️
    本答案参考通义千问

    Spring AOP(面向切面编程)通过 JDK动态代理CGLIB 实现对方法的拦截,其核心机制是基于 代理模式。下面我将详细解释这两种实现方式的工作原理,并说明在什么情况下会选择使用 CGLIB代理


    一、Spring AOP 的两种代理方式

    1. JDK 动态代理

    • 原理:JDK 动态代理是基于接口的代理方式。它通过 java.lang.reflect.Proxy 类创建代理对象。
    • 适用场景:被代理的类必须实现一个或多个接口。
    • 工作方式
      • Spring 会为每个需要增强的接口生成一个代理对象。
      • 当调用目标方法时,实际调用的是代理对象,而代理对象会调用 InvocationHandler 中的 invoke() 方法。
      • invoke() 方法中,可以插入前置、后置、异常处理等逻辑。

    示例代码:

    public interface UserService {
        void sayHello();
    }
    
    public class UserServiceImpl implements UserService {
        @Override
        public void sayHello() {
            System.out.println("Hello");
        }
    }
    
    public class MyInvocationHandler implements InvocationHandler {
        private Object target;
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 前置增强
            System.out.println("Before method: " + method.getName());
    
            Object result = method.invoke(target, args);
    
            // 后置增强
            System.out.println("After method: " + method.getName());
    
            return result;
        }
    }
    

    使用方式

    UserService userService = (UserService) Proxy.newProxyInstance(
        UserService.class.getClassLoader(),
        new Class[]{UserService.class},
        new MyInvocationHandler(new UserServiceImpl())
    );
    userService.sayHello();
    

    2. CGLIB(Code Generation Library)

    • 原理:CGLIB 是一个基于字节码操作的库,通过继承的方式生成目标类的子类,从而实现代理。
    • 适用场景:被代理的类没有实现接口时,Spring 会自动选择 CGLIB。
    • 工作方式
      • Spring 会使用 CGLIB 生成一个目标类的子类。
      • 在子类中重写目标方法,插入切面逻辑(如日志、事务管理等)。
      • 调用时实际上是调用这个子类的对象。

    示例代码(无需接口):

    public class UserService {
        public void sayHello() {
            System.out.println("Hello");
        }
    }
    

    Spring 会自动使用 CGLIB 对该类进行代理,不需要接口。


    二、Spring AOP 如何实现方法拦截?

    Spring AOP 使用 动态代理技术 来实现方法拦截,具体流程如下:

    1. 确定代理类型:根据目标类是否实现接口,决定使用 JDK 动态代理还是 CGLIB。
    2. 生成代理对象:Spring 根据配置和目标类生成对应的代理对象。
    3. 执行方法调用:当调用代理对象的方法时,实际上调用的是代理对象内部的逻辑。
    4. 插入切面逻辑:在方法调用前后插入切面逻辑(如日志、权限检查、事务控制等)。

    三、Spring AOP 选择 CGLIB 代理的条件

    在以下情况下,Spring AOP 会使用 CGLIB 代理:

    1. 目标类没有实现任何接口

      • 如果被代理的类没有实现接口,那么只能使用 CGLIB 代理。
    2. 使用 @Aspect 注解定义切面

      • 如果你使用了 @Aspect 注解,Spring 会默认使用 CGLIB 代理,因为 @Aspect 需要对类进行增强,而不是接口。
    3. 使用 @EnableAspectJAutoProxy 配置

      • 即使目标类实现了接口,如果启用了 @EnableAspectJAutoProxy,Spring 也会优先使用 CGLIB。
    4. 使用 @Around 等注解

      • 如果你的切面中使用了 @Around 注解,Spring 通常会使用 CGLIB 来确保能够拦截所有方法。

    四、Spring AOP 选择 JDK 动态代理的条件

    在以下情况下,Spring AOP 会使用 JDK 动态代理:

    1. 目标类实现了至少一个接口

      • Spring 会优先使用 JDK 动态代理,因为它更轻量、性能更好。
    2. 显式配置使用 JDK 动态代理

      • 可以通过设置 proxy-target-class="false" 或者在配置类中设置 @EnableAspectJAutoProxy(proxyTargetClass = false) 强制使用 JDK 动态代理。

    五、总结:Spring AOP 如何实现方法拦截?

    Spring AOP 通过以下方式实现方法拦截:

    1. 根据目标类是否实现接口,选择 JDK 动态代理或 CGLIB 代理。
    2. 生成代理对象,代理对象封装了原始对象和切面逻辑。
    3. 调用方法时,实际调用的是代理对象,从而触发切面逻辑。
    4. 在方法执行前后插入增强逻辑(如日志、事务等)。

    六、何时选择 CGLIB 代理?

    在以下情况下应选择 CGLIB 代理:

    1. 目标类没有实现任何接口。
    2. 使用 @Aspect 注解定义切面。
    3. 需要对非接口方法进行增强(如私有方法、final 方法等)。
    4. 希望避免接口依赖,直接对类进行增强。

    七、代码示例:如何强制使用 CGLIB 代理?

    配置类方式:

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class AppConfig {
    }
    

    XML 配置方式:

    <aop:config>
        <aop:aspectj-autoproxy proxy-target-class="true"/>
    </aop:config>
    

    八、CGLIB vs JDK 动态代理的区别(对比表)

    | 特性 | JDK 动态代理 | CGLIB | |---------------------|-------------------------------|----------------------------------| | 是否依赖接口 | ✅ 必须实现接口 | ❌ 不需要接口 | | 性能 | 更快 | 略慢(字节码操作) | | 支持方法 | 接口方法 | 所有方法(包括私有、final) | | 使用场景 | 有接口的类 | 没有接口的类或需要增强非接口方法 | | 代理方式 | 基于接口的代理 | 基于继承的代理 |


    如果你有具体的项目结构或代码片段,我可以进一步帮你分析应该使用哪种代理方式。

    评论

报告相同问题?

问题事件

  • 创建了问题 12月5日