在 Spring 中,AOP(面向切面编程)可以通过动态代理实现。Spring AOP 支持两种主要的动态代理模式:JDK ProxyCGLIB Proxy。这两种方式各有特点,适用于不同的场景。

1. JDK 动态代理

JDK Proxy 是基于 Java 原生的反射机制实现的,使用 java.lang.reflect.Proxy 类生成代理对象。JDK Proxy 只能对实现了接口的类进行代理,因此只适用于代理那些有接口的目标类。

特点:
  • 基于接口:JDK Proxy 只能代理实现了接口的类。如果目标类没有实现接口,无法使用 JDK Proxy。
  • 性能较好:由于 JDK Proxy 是基于 Java 原生的反射机制,生成的代理对象性能通常较为优秀。
  • 代理方式:JDK Proxy 通过接口实现代理,所以目标类和代理类之间通过接口进行调用。
使用示例:

假设有一个接口 UserService 和其实现类 UserServiceImpl

public interface UserService {
    void createUser();
    void deleteUser();
}

public class UserServiceImpl implements UserService {
    @Override
    public void createUser() {
        System.out.println("Creating user");
    }

    @Override
    public void deleteUser() {
        System.out.println("Deleting user");
    }
}

配置 AOP:

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
    @Bean
    public UserService userService() {
        return new UserServiceImpl();
    }

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }
}

定义切面(Aspect):

@Aspect
public class MyAspect {

    @Before("execution(* com.example.UserService.createUser(..))")
    public void beforeCreateUser() {
        System.out.println("Before createUser method");
    }

    @After("execution(* com.example.UserService.deleteUser(..))")
    public void afterDeleteUser() {
        System.out.println("After deleteUser method");
    }
}

在这个例子中,UserServiceImpl 必须实现 UserService 接口,Spring 使用 JDK Proxy 为 UserService 接口创建代理对象。

2. CGLIB 动态代理

CGLIB Proxy 是基于字节码生成的动态代理工具,能够创建目标类的子类。与 JDK Proxy 不同,CGLIB Proxy 不要求目标类必须实现接口,可以代理没有实现接口的普通类。

特点:
  • 基于继承:CGLIB Proxy 是通过继承目标类创建子类,并在子类中织入增强逻辑。
  • 适用于无接口类:CGLIB Proxy 可以代理没有实现接口的类,而 JDK Proxy 不能。
  • 性能稍差:由于使用字节码生成和继承机制,CGLIB Proxy 的性能通常稍逊色于 JDK Proxy。
使用示例:

假设我们有一个没有接口的目标类 OrderService

public class OrderService {
    public void createOrder() {
        System.out.println("Creating order");
    }

    public void cancelOrder() {
        System.out.println("Canceling order");
    }
}

配置 AOP:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB 代理
public class AopConfig {
    @Bean
    public OrderService orderService() {
        return new OrderService();
    }

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }
}

定义切面(Aspect):

@Aspect
public class MyAspect {

    @Before("execution(* com.example.OrderService.createOrder(..))")
    public void beforeCreateOrder() {
        System.out.println("Before createOrder method");
    }

    @After("execution(* com.example.OrderService.cancelOrder(..))")
    public void afterCancelOrder() {
        System.out.println("After cancelOrder method");
    }
}

在这个例子中,OrderService 类没有实现接口,Spring 会自动使用 CGLIB 代理类来为 OrderService 类创建代理对象。

3. JDK Proxy 与 CGLIB Proxy 区别

特性JDK ProxyCGLIB Proxy
代理对象的类型代理接口代理类的子类
要求目标对象必须实现接口不需要接口,目标类可以没有接口
代理方式动态代理接口动态生成子类,重写方法
性能更优相对稍慢
适用场景适用于代理接口适用于没有实现接口的类
Spring 配置默认(只要有接口就使用 JDK Proxy)@EnableAspectJAutoProxy(proxyTargetClass = true)proxyTargetClass = true 配置

4. 选择使用 JDK Proxy 还是 CGLIB Proxy

  • 如果目标类实现了接口,JDK Proxy 是首选,因为它性能更好,且不需要生成子类。
  • 如果目标类没有实现接口,并且需要进行代理,CGLIB Proxy 是唯一选择。
  • 如果希望强制使用 CGLIB,即使目标类有接口,可以在 @EnableAspectJAutoProxy 注解中设置 proxyTargetClass = true,或者在 XML 配置中设置 <aop:proxy proxy-target-class="true"/>

5. 总结

  • JDK Proxy:通过接口实现代理,适用于目标类实现接口的情况。默认情况下,Spring 会使用 JDK Proxy。
  • CGLIB Proxy:通过继承目标类实现代理,适用于没有实现接口的目标类。CGLIB Proxy 性能稍逊,但在没有接口的情况下是必需的。

在 Spring 中,代理的选择是由是否实现接口以及 @EnableAspectJAutoProxy 中的配置决定的。默认情况下,Spring 会选择 JDK Proxy,如果需要使用 CGLIB Proxy,则可以通过配置来显式选择。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐