Spring框架中采用AOP模式开发的探究
在软件开发过程中,可通过预编译方式和运行期间动态代理实现程序功能的统一维护,这种技术我们称之为AOP,全程为Aspect Oriented Programming,意为面向切面编程。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
如果我们在同一个方法上自定义多个切面、切点,他们默认的执行顺序是什么呢?
场景:单个切面
示例代码:
- @Component
- @Aspect
- public class TestAspect {
- @Pointcut("@within(org.springframework.stereotype.Service)")
- public void pointCut() {
- }
- @Around(value = "pointCut()")
- public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
- System.out.println("我是环绕通知.....");
- return joinPoint.proceed();
- }
- @Before(value = "pointCut()")
- public void beforeAdvice(JoinPoint joinPoint) {
- System.out.println("我是前置通知.....");
- }
- @After(value = "pointCut()")
- public void afterAdvice(JoinPoint joinPoint) {
- System.out.println("我是后置通知.....");
- }
- @AfterReturning(value = "pointCut()")
- public void afterReturning(JoinPoint joinPoint) {
- System.out.println("我是返回后通知.....");
- }
- @AfterThrowing(value = "pointCut()")
- public void afterThrowing(JoinPoint joinPoint) {
- System.out.println("我是异常后通知.....");
- }
- }
输出结果:
- 我是环绕通知.....
- 我是前置通知.....
- 我是目标方法.....
- 我是后置通知.....
- 我是返回后通知.....
在单个切面中,切点的执行顺序为:@Around, @Before, @After, @AfterReturning, @AfterThrowing,在Spring官网中,对切点的执行顺序也做了说明。
场景:单个切面
示例代码:
- @Component
- @Aspect
- public class TestAspect_2 {
- @Pointcut("@within(org.springframework.stereotype.Service)")
- public void pointCut() {
- }
- @Around(value = "pointCut()")
- public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
- System.out.println("我是环绕通知2.....");
- return joinPoint.proceed();
- }
- @Before(value = "pointCut()")
- public void beforeAdvice(JoinPoint joinPoint) {
- System.out.println("我是前置通知2.....");
- }
- @After(value = "pointCut()")
- public void afterAdvice(JoinPoint joinPoint) {
- System.out.println("我是后置通知2.....");
- }
- @AfterReturning(value = "pointCut()")
- public void afterReturning(JoinPoint joinPoint) {
- System.out.println("我是返回后通知2.....");
- }
- @AfterThrowing(value = "pointCut()")
- public void afterThrowing(JoinPoint joinPoint) {
- System.out.println("我是异常后通知2.....");
- }
- }
输出结果:
- 我是环绕通知.....
- 我是前置通知.....
- 我是环绕通知2.....
- 我是前置通知2.....
- 我是目标方法.....
- 我是后置通知2.....
- 我是返回后通知2.....
- 我是后置通知.....
- 我是返回后通知.....
在多个切面中,切点的执行顺序仍然为:@Around, @Before, @After, @AfterReturning, @AfterThrowing。
那么我们如何调整自定义切面的执行顺序呢?Spring官网中又给出了如下说明:
我们可以使用@Order指定的切入点的执行顺序。我们在切面TestAspect_2加上@Order(1)
注解
示例代码:
- @Component
- @Aspect
- @Order(1)
- public class TestAspect_2 {
- .......
- }
输出结果:
- 我是环绕通知2.....
- 我是前置通知2.....
- 我是环绕通知.....
- 我是前置通知.....
- 我是目标方法.....
- 我是后置通知.....
- 我是返回后通知.....
- 我是后置通知2.....
- 我是返回后通知2.....
加上注解之后,TestAspect_2中的切点优先于TestAspect中的切点执行,@order中的值越小优先级越高
在Service层,我们经常开启事务来避免数据的一致性问题,而事务其实也是一个切面,那么事务的优先级和自定义的切面优先级有什么关系呢?在spring官网中事务部分我们可以看到如下配置说明:
可以发现事务采取的是最低优先级配置,即默认情况下,事务是切面中最晚执行,我们将通过代码进行验证,在ProxyTransactionManagementConfiguration中,可以看到事务的order默认值
由于事务默认采用最低优先级,因此当用户自定义切面且作用于事务方法时,当切面中未抛出异常,可能导致事务控制失效。
示例代码:
- @Component
- @Aspect // 切面未指定顺序
- public class TestAspect {
- @Pointcut("@within(org.springframework.stereotype.Service)")
- public void pointCut() {
- }
- @Around(value = "pointCut()")
- public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
- System.out.println("我是环绕通知.....");
- try {
- return joinPoint.proceed();
- } catch (Throwable throwable) {
- // 切面未抛出目标方法的异常
- throwable.printStackTrace();
- } finally {
- }
- return null;
- }
- }
- @Service
- public class TestServiceImpl implements TestService {
- @Autowired
- private TestAMapper testAMapper;
- @Autowired
- private TestBMapper testBMapper;
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void addData() {
- System.out.println("我是目标方法.....");
- User user = getUser();
- testAMapper.addUser(user);
- testBMapper.addUser(user);
- //插入数据之后,发生异常
- int a = 1/0;
- }
- private User getUser() {
- User user = new User();
- user.setUserName("张三");
- user.setAge(18);
- user.setAddress("上海市");
- return user;
- }
- }
输出结果:
数据结果:
由于自定义切面没有指定优先级(默认为2147483647,即最低级别),即使事务方法中发生异常,事务也没有回滚。
解决办法:给自定义事务指定一个高优先级(即给order设置个小值,比如:1)或者保证自定义切面会向上抛出目标方法产生的异常。
总结:AOP是面向对象编程的一个补充,通过Spring的动态代理,可在无侵入的情况下为原始类增加额外的功能,有效减少了代码冗余,并让原始类仅关注自身的功能实现,但不合理的使用就会造成问题发生,而这种发生的几率可能很难被发现,最终导致生产故障,所以在平时的业务开发中,需要要养成给自定义切面指定顺序的好习惯。
注:文章均原创,部分图片及文字来源于网络,若涉及版权,请联系作者,将第一时间备注出处,谢谢。