Spring框架中采用AOP模式开发的探究

本文探讨了Spring框架中AOP模式的使用,包括切面的执行顺序。在单个切面中,执行顺序为@Around, @Before, @After, @AfterReturning, @AfterThrowing。通过@Order注解可以调整切面执行顺序,数值越小优先级越高。默认情况下,事务切面具有最低优先级,可能导致自定义切面未抛出异常时事务控制失效。因此,为确保事务正确性,建议给自定义切面指定优先级。" 79559989,7481181,Java实现m3u8视频碎片合并,"['Java开发', '视频处理', '文件操作']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring框架中采用AOP模式开发的探究

在软件开发过程中,可通过预编译方式和运行期间动态代理实现程序功能的统一维护,这种技术我们称之为AOP,全程为Aspect Oriented Programming,意为面向切面编程。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
 

如果我们在同一个方法上自定义多个切面、切点,他们默认的执行顺序是什么呢?

场景:单个切面

示例代码:

  1. @Component
  1. @Aspect
  1. public class TestAspect {
  1.  
  1.     @Pointcut("@within(org.springframework.stereotype.Service)")
  1.     public void pointCut() {
  1.  
  1.     }
  1.  
  1.     @Around(value = "pointCut()")
  1.     public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  1.         System.out.println("我是环绕通知.....");
  1.         return joinPoint.proceed();
  1.     }
  1.  
  1.     @Before(value = "pointCut()")
  1.     public void beforeAdvice(JoinPoint joinPoint) {
  1.         System.out.println("我是前置通知.....");
  1.     }
  1.  
  1.     @After(value = "pointCut()")
  1.     public void afterAdvice(JoinPoint joinPoint) {
  1.         System.out.println("我是后置通知.....");
  1.     }
  1.  
  1.     @AfterReturning(value = "pointCut()")
  1.     public void afterReturning(JoinPoint joinPoint) {
  1.         System.out.println("我是返回后通知.....");
  1.     }
  1.  
  1.     @AfterThrowing(value = "pointCut()")
  1.     public void afterThrowing(JoinPoint joinPoint) {
  1.         System.out.println("我是异常后通知.....");
  1.     }
  1. }

输出结果:

  1. 我是环绕通知.....
  1. 我是前置通知.....
  1. 我是目标方法.....
  1. 我是后置通知.....
  1. 我是返回后通知.....

在单个切面中,切点的执行顺序为:@Around, @Before, @After, @AfterReturning, @AfterThrowing,在Spring官网中,对切点的执行顺序也做了说明。

 

场景:单个切面

示例代码:

  1. @Component
  1. @Aspect
  1. public class TestAspect_2 {
  1.  
  1.     @Pointcut("@within(org.springframework.stereotype.Service)")
  1.     public void pointCut() {
  1.  
  1.     }
  1.     
  1.     @Around(value = "pointCut()")
  1.     public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  1.         System.out.println("我是环绕通知2.....");
  1.         return joinPoint.proceed();
  1.     }
  1.  
  1.     @Before(value = "pointCut()")
  1.     public void beforeAdvice(JoinPoint joinPoint) {
  1.         System.out.println("我是前置通知2.....");
  1.     }
  1.  
  1.     @After(value = "pointCut()")
  1.     public void afterAdvice(JoinPoint joinPoint) {
  1.         System.out.println("我是后置通知2.....");
  1.     }
  1.  
  1.     @AfterReturning(value = "pointCut()")
  1.     public void afterReturning(JoinPoint joinPoint) {
  1.         System.out.println("我是返回后通知2.....");
  1.     }
  1.  
  1.     @AfterThrowing(value = "pointCut()")
  1.     public void afterThrowing(JoinPoint joinPoint) {
  1.         System.out.println("我是异常后通知2.....");
  1.     }
  1. }

输出结果:

  1. 我是环绕通知.....
  1. 我是前置通知.....
  1. 我是环绕通知2.....
  1. 我是前置通知2.....
  1. 我是目标方法.....
  1. 我是后置通知2.....
  1. 我是返回后通知2.....
  1. 我是后置通知.....
  1. 我是返回后通知.....

在多个切面中,切点的执行顺序仍然为:@Around, @Before, @After, @AfterReturning, @AfterThrowing。
 

那么我们如何调整自定义切面的执行顺序呢?Spring官网中又给出了如下说明:

 

我们可以使用@Order指定的切入点的执行顺序。我们在切面TestAspect_2加上@Order(1)

注解

示例代码:

  1. @Component
  1. @Aspect
  1. @Order(1)
  1. public class TestAspect_2 {
  1. .......
  1. }

输出结果:

  1. 我是环绕通知2.....
  1. 我是前置通知2.....
  1. 我是环绕通知.....
  1. 我是前置通知.....
  1. 我是目标方法.....
  1. 我是后置通知.....
  1. 我是返回后通知.....
  1. 我是后置通知2.....
  1. 我是返回后通知2.....

加上注解之后,TestAspect_2中的切点优先于TestAspect中的切点执行,@order中的值越小优先级越高
 

在Service层,我们经常开启事务来避免数据的一致性问题,而事务其实也是一个切面,那么事务的优先级和自定义的切面优先级有什么关系呢?在spring官网中事务部分我们可以看到如下配置说明:

 

可以发现事务采取的是最低优先级配置,即默认情况下,事务是切面中最晚执行,我们将通过代码进行验证,在ProxyTransactionManagementConfiguration中,可以看到事务的order默认值

 

由于事务默认采用最低优先级,因此当用户自定义切面且作用于事务方法时,当切面中未抛出异常,可能导致事务控制失效。

示例代码:

  1. @Component
  1. @Aspect   //  切面未指定顺序
  1. public class TestAspect {
  1.  
  1.     @Pointcut("@within(org.springframework.stereotype.Service)")
  1.     public void pointCut() {
  1.  
  1.     }
  1.  
  1.     @Around(value = "pointCut()")
  1.     public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
  1.         System.out.println("我是环绕通知.....");
  1.         try {
  1.             return joinPoint.proceed();
  1.         } catch (Throwable throwable) {
  1.             // 切面未抛出目标方法的异常
  1.             throwable.printStackTrace();
  1.         } finally {
  1.         }
  1.         return null;
  1.     }
  1. }
     
  1. @Service
  1. public class TestServiceImpl implements TestService {
  1.     @Autowired
  1.     private TestAMapper testAMapper;
  1.     @Autowired
  1.     private TestBMapper testBMapper;
  1.  
  1.     @Override
  1.     @Transactional(rollbackFor = Exception.class)
  1.     public void addData() {
  1.         System.out.println("我是目标方法.....");
  1.         User user = getUser();
  1.         testAMapper.addUser(user);
  1.         testBMapper.addUser(user);
  1.         //插入数据之后,发生异常
  1.         int a = 1/0;
  1.     }
  1.  
  1.     private User getUser() {
  1.         User user = new User();
  1.         user.setUserName("张三");
  1.         user.setAge(18);
  1.         user.setAddress("上海市");
  1.         return user;
  1.     }
  1. }
     

输出结果:

 

数据结果:

 

由于自定义切面没有指定优先级(默认为2147483647,即最低级别),即使事务方法中发生异常,事务也没有回滚。
 

解决办法:给自定义事务指定一个高优先级(即给order设置个小值,比如:1)或者保证自定义切面会向上抛出目标方法产生的异常。
 

总结:AOP是面向对象编程的一个补充,通过Spring的动态代理,可在无侵入的情况下为原始类增加额外的功能,有效减少了代码冗余,并让原始类仅关注自身的功能实现,但不合理的使用就会造成问题发生,而这种发生的几率可能很难被发现,最终导致生产故障,所以在平时的业务开发中,需要要养成给自定义切面指定顺序的好习惯。

注:文章均原创,部分图片及文字来源于网络,若涉及版权,请联系作者,将第一时间备注出处,谢谢。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值