Spring Boot进阶:原理、实战与面试题分析
上QQ阅读APP看书,第一时间看更新

3.1.2 Spring AOP案例分析

在理解了AOP的相关概念以及Spring框架所提供的各种注解之后,在本节中,我们将通过代码示例来展示注解的使用方法。

现在,假设有一个代表用户账户的AccountService接口,如代码清单3-1所示。

代码清单3-1 AccountService接口代码

public interface AccountService {
    boolean doAccountTransaction(Account source, Account dest, int amount) throws MinimumAmountException;
}

可以看到,在该AccountService接口中定义了一个用于实现账户交易的doAccount-Transaction()方法。然后我们提供它的实现类,如代码清单3-2所示。

代码清单3-2 AccountService接口实现类代码

public class AccountServiceImpl implements AccountService {
    private static final Logger LOGGER = Logger.getLogger(AccountServiceImpl.class);

    @Override
    public boolean doAccountTransaction(Account source, Account dest, int amount) throws MinimumAmountException {
        LOGGER.info("执行交易");
        if (amount < 10) {
            throw new MinimumAmountException("交易金额过少");
        }
        return true;
    }
}

在doAccountTransaction()方法中,我们在执行交易之前记录了操作日志,这种实现方式看上去没有什么问题。如果针对交易操作,我们希望在该操作之前、之后、执行过程中以及抛出MinimumAmountException异常时都记录对应的日志,那么实现起来就没那么容易了。这个时候,可以通过AOP进行切入,并添加对应的日志记录。基于Spring AOP,其实现过程如代码清单3-3所示。

代码清单3-3 AccountServiceAspect代码

@Aspect
public class AccountServiceAspect {
    private static final Logger LOGGER = Logger.getLogger(AccountServiceAspect.class);

    @Pointcut("execution(* com.springboot.aop.service.AccountService.doAccountTransaction(..))")
    public void doAccountTransaction() {}

    @Before("doAccountTransaction()")
    public void beforeTransaction(JoinPoint joinPoint) {
        LOGGER.info("交易前");
    }

    @After("doAccountTransaction()")
    public void afterTransaction(JoinPoint joinPoint) {
        LOGGER.info("交易后");
    }

    @AfterReturning(pointcut = "doAccountTransaction() and args(source, dest, amount)", returning = "isTransactionSuccessful")
    public void afterTransactionReturns(JoinPoint joinPoint, Account source, Account dest, Double amount, boolean isTransactionSuccessful) {
        if (isTransactionSuccessful) {
            LOGGER.info("转账成功 ");
        }
    }

    @AfterThrowing(pointcut = "doAccountTransaction()", throwing = "minimumAmountException")
    public void exceptionFromTransaction(JoinPoint joinPoint, MinimumAmountException minimumAmountException) {
        LOGGER.info("抛出异常: " + minimumAmountException.getMessage());
    }

    @Around("doAccountTransaction()")
    public boolean aroundTransaction(ProceedingJoinPoint proceedingJoinPoint){
        LOGGER.info("调用方法前 ");
        boolean isTransactionSuccessful = false;
        try {
            isTransactionSuccessful = (Boolean)proceedingJoinPoint.proceed();
        } catch (Throwable e) {
        }
        LOGGER.info("调用方法后");
        return isTransactionSuccessful;
    }
}

上述AccountServiceAspect就是一个切面,代表了Spring AOP机制的典型使用方法,我们一一来展开讨论。

首先,我们看到这里使用@Pointcut注解定义了一个切点,并通过execution()指示器限定该切点匹配的包结构为com.springboot.aop.service,匹配的方法是AccountService类的doAccountTransaction()方法。也就是说,针对com.springboot.aop.service.AccountService类中doAccountTransaction()方法的任何一次调用,都会触发切面,也就会执行对应的通知逻辑。请注意,因为在Spring AOP中连接点只支持方法的调用,所以这里专门定义了一个doAccountTransaction()方法,并在该方法上使用了@Pointcut注解。

另外,在AccountServiceAspect中综合使用了Spring AOP所提供的@Before、@After、@Around、@AfterThrowing和@AfterReturning注解来设置五种不同类型的通知。

  • @Before:在方法调用之前调用通知。
  • @After:在方法完成之后调用通知,无论方法执行成功与否。
  • @Around:通知包裹了目标方法,在被通知的方法调用之前和调用之后执行自定义的行为。
  • @AfterThrowing:在方法抛出异常后进行通知,可以通过该注解指定目标异常信息。
  • @AfterReturning:在方法执行成功之后调用通知。

在使用这些通知注解时,同样需要注意它们的目标切点都是添加了@Pointcut注解的doAccountTransaction()方法。这些注解对应的实现方法都不复杂,这里不一一展开讨论。当执行AccountServiceImpl的doAccountTransaction()方法时,我们在控制台中能够看到如代码清单3-4所示的日志信息,这说明Spring AOP已经生效。

代码清单3-4 AOP输出日志代码示例

INFO  AccountServiceAspect:50 - 调用方法前
INFO  AccountServiceAspect:27 - 交易前
INFO  AccountServiceImpl:14 - 执行交易
INFO  AccountServiceAspect:32 - 交易后
INFO  AccountServiceAspect:58 - 调用方法后

本案例的详细实现代码可以在以下地址下载:https://github.com/tianminzheng/spring-boot-examples/tree/main/SpringAopExample