【Spring AOP(2)篇】原理及两种实现方式(cglib&jdk动态代理)

SpringAOP基于代理模式实现面向切面编程,通过JDK动态代理或CGLIB创建目标对象的代理。当目标对象实现接口时,默认使用JDK动态代理,否则使用CGLIB。JDK动态代理轻量级且仅适用于有接口的情况,CGLIB性能更高但不能代理final类和方法。文章通过实例解释了两种代理方式的工作原理和适用场景。
摘要由CSDN通过智能技术生成

简介: Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的一个重要特性,用于解耦和切割业务逻辑,是实现面向切面编程的重要手段之一。本文将介绍 Spring AOP 的原理及Spring AOP 的两种实现方式

Spring AOP实现原理

Spring AOP 是 Spring 框架提供的一种 AOP 实现方式,其核心原理是基于代理模式。它通过将目标对象(也就是被代理对象)包装成一个代理对象,从而实现对目标对象方法的动态拦截和处理,实现面向切面编程的目的。下面举个栗子。

假设你去医院看病,医生开了一些检查单让你去做,然后又给你开了一些药物和处方让你去取药。这里的医生就是一个代理对象,而你就是目标对象。医生不仅可以给你开药单,还可以对你的病情进行相应的诊断和建议,甚至可以对你的药物使用情况进行跟踪和评估。

在这个例子中,代理对象(医生)可以在你去做检查之前或取药之后,添加一些额外的逻辑处理。例如,医生可以检查你是否真的需要这些检查,或者是否有更好的药物替代方案。
这就类似于 Spring AOP 的代理对象在目标对象执行方法之前或之后,增加拦截和增强逻辑。

与此同时,代理对象还可以为目标对象屏蔽一些底层细节和对系统的影响,例如,医生可以提供一些健康咨询和建议,帮助你更好地理解自己的身体状况,而无需了解医学专业知识(也就是目标对象的内部实现)。这就类似于 Spring AOP 的代理对象可以为目标对象提供更好的封装和抽象,提高了系统的稳定性和安全性。

通过这个例子,我们可以更好地理解 Spring AOP 的代理原理,并深入理解代理对象和目标对象的区别和联系。代理对象相当于是目标对象的一个代理人,实现了对目标对象方法的拦截和处理,并承担了更多的职责和责任。同时,代理对象也可以起到更好的封装和抽象作用,提高了系统的可维护性和可扩展性。

Spring AOP 两种实现方式

默认使用 JDK 动态代理(如果目标对象实现了至少一个接口),如果目标对象没有实现接口,则使用 CGLIB 来代理对象。
可以通过配置参数 proxyTargetClass 来强制使用 CGLIB 代理,或者设置为 false 来强制使用 JDK 动态代理。如果不设置此参数,则根据上述规则来选择代理方式。

1.CGLIB

首先,定义好类,并且在类中添加需要增强的方法和切面逻辑。然后,定义一个切面类,调用 Enhancer.create 方法动态生成代理对象,并将 MethodInterceptor 对象传递给代理对象。最终,在 Spring 容器中进行配置和使用即可。
上代码

定义类和方法
定义一个类 UserService,其中有一个需要增强的 addUser 方法:

public class UserService {
    public void addUser(User user) {
        System.out.println("Add user: " + user.getName() + ", " + user.getAge());
    }
}
定义切面类

定义一个切面类 LogAspect,实现 MethodInterceptor 接口,并在 intercept 方法中编写增强逻辑:

@Component
public class LogAspect implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method: " + method.getName());
        return result;
    }
}
配置 Spring AOP

在 Spring 配置文件中,定义 bean,并初始化切面类,通过 Enhancer.create 方法动态生成代理对象,并注入到容器中:

@Configuration
public class AppConfig {
    @Autowired
    private LogAspect logAspect;
    
    @Bean
    public UserService userService() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(logAspect);
        return (UserService) enhancer.create();
    }
    
    @Bean
    public LogAspect logAspect() {
        return new LogAspect();
    }
}

最后,在测试类中通过 Spring 容器获取 UserService 的代理对象,并调用其方法:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testAddUser() {
        User user = new User();
        user.setName("Tom");
        user.setAge(20);
        userService.addUser(user);
    }
}

注解说明:

 - @Component:表示 LogAspect 是一个 Spring bean;
 - @Bean:表示该方法返回的对象将作为一个 bean 注入到 Spring 容器中;
 - Enhancer:CGLIB 提供的类,用于动态生成代理类;
 - setSuperclass:设置需要代理的父类;
 - setCallback:设置 MethodInterceptor 接口的实现类;
 - MethodInterceptor:CGLIB 提供的接口,用于拦截方法调用。

2.JDK 动态代理

首先,定义好接口,并且在实现类中添加需要增强的方法和切面逻辑。然后,定义一个切面类,实现 InvocationHandler 接口,并且在 invoke 方法中编写增强逻辑。最后,通过 Proxy 类动态生成代理对象,并将 InvocationHandler 对象传递给代理对象。最终,在 Spring 容器中进行配置和使用即可。

上代码

定义接口和实现类

定义一个接口 UserService 和一个实现类 UserServiceImpl,其中实现类中有一个需要增强的 addUser 方法:

public interface UserService {
    void addUser(User user);
}

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(User user) {
        System.out.println("Add user: " + user.getName() + ", " + user.getAge());
    }
}
定义切面类

定义一个切面类 LogAspect,并在其 before 方法中实现增强逻辑:

@Component
@Aspect
public class LogAspect {
    @Before("execution(* com.example.demo.service.UserService.addUser(..)) && args(user)")
    public void before(User user) {
        System.out.println("Before adding user: " + user.getName() + ", " + user.getAge());
    }
}
配置 Spring AOP

在 Spring 配置文件中,配置开启 Spring AOP 的注解支持,并扫描切面类:

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.example.demo")
public class AppConfig {

}

最后,在测试类中通过 Spring 容器获取 UserService 的代理对象,并调用其方法:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testAddUser() {
        User user = new User();
        user.setName("Tom");
        user.setAge(20);
        userService.addUser(user);
    }
}

注解说明:

  • @Aspect:表示 LogAspect 是一个切面类;
  • @Before:表示 before 类型的通知,在目标方法执行前执行增强逻辑;
  • @EnableAspectJAutoProxy:表示开启 Spring AOP 的自动代理功能,会自动使用 JDK 动态代理;同时,由于没有 proxyTargetClass 参数(默认为 false ),所以此处使用 JDK 动态代理代理类。

3.CGLIB与JDK动态代理的优缺点

JDK动态代理

JDK动态代理是指在运行时动态生成代理类,实现面向切面编程,并且只能代理实现了接口的类。在 Spring AOP 中,JDK 动态代理基于 InvocationHandler 进行 AOP 操作。
优点

  • 轻量级:JDK动态代理只需要通过反射调用方法,比CGLib更加轻量级;
  • 可扩展性:由于只针对接口生成代理类,因此支持类的动态加载,以及对新接口方法的支持,更具有扩展性;
  • 安全性:原始类和代理类之间只有接口的关系,不会动态生成类的子类,不存在类的继承关系,更加安全。

缺点

  • 只能基于接口生成代理对象,无法对类进行代理;
  • 对目标类的要求较高,目标类必须实现接口。
CGLib

CGLib 全称是Code Generation Library,是二者中性能较高的一种代理方式,它通过动态生成目标类的子类,并在子类中拦截代理类的方法调用。
优点

  • 性能高: CGLib 采用了底层字节码技术进行代理,性能比 JDK 动态代理更高;
  • 无需实现接口: 目标类不需要实现接口,也能被代理,更加灵活。

缺点

  • 不能代理 final 方法和 final 类;
  • 由于采用继承方式实现,可能会覆盖掉默认的构造函数,需要手动实现。

综上所述,JDK动态代理和CGLib各有其优点和缺点。如果目标类已经实现了接口,建议采用 JDK 动态代理;如果目标类没有实现接口,采用 CGLib 会更合适。 当然,如果没有特殊需求,采用默认的 JDK 动态代理也可以实现较好的 AOP 功能。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王笃定前行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值