Spring使用了哪些动态代理方式

53 阅读3分钟

Spring AOP 使用了两种主要的动态代理方式:

1. JDK 动态代理(基于接口)

  • 原理:JDK 动态代理是基于接口的代理方式。它要求目标类必须实现至少一个接口,Spring 会使用 java.lang.reflect.Proxy 类来创建一个代理对象,该代理对象实现目标接口,并通过 InvocationHandler 来拦截对目标对象的调用。

  • 使用场景:当目标类实现了接口时,Spring 会使用 JDK 动态代理。如果目标类没有实现接口,则不能使用 JDK 动态代理。

  • 优点:轻量级、性能较好,不需要生成额外的类,代理对象是基于接口的,符合面向接口编程的原则。

  • 实现方式

    • Spring 会在目标类实现的接口上创建一个代理类,并将切面(增强逻辑)织入代理类的方法中。
    • 通过反射机制,代理类会调用目标类的方法。
  • 示例代码

    public interface MyService {
        void sayHello();
    }
    
    public class MyServiceImpl implements MyService {
        public void sayHello() {
            System.out.println("Hello, world!");
        }
    }
    
    @Aspect
    @Component
    public class LoggingAspect {
        @Before("execution(* MyService.sayHello(..))")
        public void logBefore() {
            System.out.println("Method is about to be called");
        }
    }
    

    如果 MyService 接口被实现,Spring 会使用 JDK 动态代理来创建 MyServiceImpl 的代理对象。

2. CGLIB 代理(基于子类继承)

  • 原理:CGLIB 代理是基于继承的方式。它通过字节码生成技术,在运行时动态生成目标类的子类,并在子类中重写目标类的方法,从而在方法调用时插入增强逻辑。CGLIB 是使用字节码技术通过修改目标类的方法来实现代理的,因此它不依赖接口。

  • 使用场景:当目标类没有实现接口时,Spring 会使用 CGLIB 代理。它适用于目标类没有接口或者接口不可修改的情况。

  • 优点:可以代理没有接口的类,灵活性更高。它通过继承方式生成子类,能够增强类的所有方法。

  • 缺点

    • 不能代理 final 类和 final 方法,因为 CGLIB 通过继承生成子类,而 final 类和方法不能被继承或重写。
    • 相比 JDK 动态代理,CGLIB 生成的代理对象较为重。
  • 实现方式

    • Spring 使用 CGLIBEnhancer 类来创建目标类的子类,重写目标方法并在方法执行前后加入切面逻辑。
  • 示例代码

    public class MyService {
        public void sayHello() {
            System.out.println("Hello, world!");
        }
    }
    
    @Aspect
    @Component
    public class LoggingAspect {
        @Before("execution(* MyService.sayHello(..))")
        public void logBefore() {
            System.out.println("Method is about to be called");
        }
    }
    

    如果 MyService 类没有实现接口,Spring 会使用 CGLIB 来创建代理对象。

3. 选择代理方式

Spring 在创建代理对象时会根据目标对象的特性(是否实现接口)来选择使用 JDK 动态代理或 CGLIB 代理:

  • 如果目标对象实现了接口:Spring 默认使用 JDK 动态代理。
  • 如果目标对象没有实现接口:Spring 会使用 CGLIB 动态代理。

4. 代理方式的切换

  • 默认情况下:Spring 默认使用 JDK 动态代理,只有当目标类没有实现接口时,才会选择 CGLIB 代理。

  • 手动配置:如果需要强制使用 CGLIB 代理,可以通过配置 @EnableAspectJAutoProxy(proxyTargetClass=true) 来强制启用 CGLIB 代理,而不管目标类是否实现了接口。例如:

    @Configuration
    @EnableAspectJAutoProxy(proxyTargetClass=true)  // 强制使用CGLIB代理
    public class AppConfig {
        // 配置其他bean
    }
    

    这时,即使目标类实现了接口,Spring 也会选择使用 CGLIB 代理。

5. 总结

  • JDK 动态代理:通过代理接口生成代理对象,只能代理实现了接口的目标对象。
  • CGLIB 代理:通过继承目标类生成代理对象,可以代理没有实现接口的目标类,适用于没有接口或接口不可修改的情况。