这是一篇Spring boot aop的简单应用。

应用场景

我在我的应用中,如果尝试获取一个不存在的资源,例如GET /user/123,当不存在123这个用户时,我会返回404错误给前端。

以前的做法是在service层获取资源,资源为null时抛出NotFoundException异常。久而久之代码里就会充斥着大量的重复判断的代码,例如

ArticleDO articleDO = articleMapper.getArticle(articleId);
if (articleDO == null) {
	throw Exceptions.notFoundException;
}

这明显不美观,于是我通过aop的方式用一行注解@NotFound实现了上面的效果,只要SQL查询结果为null,即抛出异常。

/**
 * 获取文章详细
 * @param id 文章id
 * @return 文章详细
 * @throws BaseNotFoundException not found
 */
@NotFound
@Select("SELECT * FROM db_article WHERE article_id=#{articleId}")
ArticleDO getArticle(Integer id) throws BaseNotFoundException;

引入包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义切点

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NotFound {

    Class<?> notFoundExceptionClz() default BaseNotFoundException.class;

}
@Aspect
@Component
public class NotFoundAop {

    @Pointcut("@annotation(nf)")
    public void notFound(NotFound nf) {
    }

}

以上是通过注解@annotation的方式定义了一个切点,除了使用注解,还可以使用execution的方式指定包、类、方法等。
spring AspectJ的Execution表达式

AOP的5种通知

  1. before(前置通知): 在方法开始执行前执行
  2. after(后置通知): 在方法执行后执行
  3. afterReturning(返回后通知): 在方法返回后执行
  4. afterThrowing(异常通知): 在抛出异常时执行
  5. around(环绕通知): 在方法执行前和执行后都会执行

执行顺序

around > before > around > after > afterReturning

代码实现

因为要对返回值进行判断,所以用了@AfterReturning通知,returning定义的是方法返回值。

@Aspect
@Component
public class NotFoundAop {

    @Pointcut("@annotation(nf)")
    public void notFound(NotFound nf) {
    }

    @AfterReturning(returning = "ret", pointcut = "notFound(nf)", argNames = "joinPoint,ret,nf")
    public void doNotFound(JoinPoint joinPoint, Object ret, NotFound nf) throws Exception {
        if (ret != null) {
            return;
        }
        if (BaseNotFoundException.class.isAssignableFrom(nf.notFoundExceptionClz())) {
            throw (BaseNotFoundException) nf.notFoundExceptionClz().getDeclaredConstructor().newInstance();
        } else {
            throw Exceptions.notFoundException;
        }
    }

}

我在@NotFound注解中加了notFoundExceptionClz参数,可以选填抛出的异常,不填则默认抛出Exceptions.notFoundException

这只是一个简单的不能再简单的demo,AOP的功能很强大,能做到的事情也非常的多。如果项目中出现了大量重复不美观的代码,就要考虑是否可以靠一些方式简化了。