AspectJ中的JoinPoint与ProceedingJoinPoint

2023/05/13

1. 概述

在这个简短的教程中,我们介绍AspectJ中JoinPoint和ProceedingJoinPoint接口之间的区别。

2. JoinPoint

JoinPoint是一个AspectJ的接口,它提供对给定连接点可用状态的反射访问,例如方法参数、返回值或抛出的异常。 它还提供有关方法本身的所有静态信息(如方法名,返回值类型等)。

我们可以将它与@Before、@After、@AfterThrowing和@AfterReturning通知一起使用。 这些切入点将分别在方法执行前、执行后、返回值后、抛出异常之后或仅在方法返回值之后执行。

为了更好地理解,让我们看一个基本的例子。 首先,我们需要声明一个切入点,我们将定义为ArticleService类中getArticleList()方法的每次执行:


@Service
public class ArticleService {

    public List<String> getArticleList() {
        return Arrays.asList("Article 1", "Article 2");
    }

    public List<String> getArticleList(String startsWithFilter) {
        if (StringUtils.isBlank(startsWithFilter))
            throw new IllegalArgumentException("startsWithFilter can't be blank");
        return getArticleList().stream().filter(a -> a.startsWith(startsWithFilter)).collect(toList());
    }
}

@Aspect
@Component
public class JoinPointBeforeAspect {
    private static final Logger log = Logger.getLogger(JoinPointBeforeAspect.class.getName());

    @Pointcut("execution(* cn.tuyucheng.taketoday.joinpoint.ArticleService.getArticleList(..))")
    public void articleListPointcut() {
    }
}

接下来,我们可以定义通知,在我们的示例中,我们将使用@Before通知:

public class JoinPointBeforeAspect {

    @Before("articleListPointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        log.info(format("Method %s executed with %s arguments",
                joinPoint.getStaticPart().getSignature(),
                Arrays.toString(joinPoint.getArgs())
        ));
    }
}

在上面的例子中,我们使用@Before通知来记录方法执行及其参数。一个类似的用例是记录我们代码中发生的异常:


@Aspect
@Component
public class JoinPointAfterThrowingAspect {
    private static final java.util.logging.Logger log = Logger.getLogger(JoinPointAfterThrowingAspect.class.getName());

    @Pointcut("execution(* cn.tuyucheng.taketoday.joinpoint.ArticleService.getArticleList(..))")
    public void articleListPointcut() {
    }

    @AfterThrowing(pointcut = "articleListPointcut()", throwing = "e")
    public void logExceptions(JoinPoint jp, Exception e) {
        log.severe(e.getMessage());
    }
}

通过使用@AfterThrowing通知,我们确保仅在发生异常时才进行日志记录。

3. ProceedingJoinPoint

ProceedingJoinPoint是JoinPoint的扩展,它公开了额外的proceed()方法。 当proceed()方法被调用时,代码执行会跳转到下一个通知或目标方法,它使我们能够控制代码流并决定是否继续进行进一步的调用

它只能与@Around通知一起使用,围绕整个方法调用:


@Aspect
@Component
public class JoinPointAroundCacheAspect {

    public final static Map<Object, Object> CACHE = new HashMap<>();

    @Pointcut("execution(* cn.tuyucheng.taketoday.joinpoint.ArticleService.getArticleList(..))")
    public void articleListPointcut() {
    }

    @Around("articleListPointcut()")
    public Object aroundAdviceCache(ProceedingJoinPoint pjp) throws Throwable {
        Object articles = CACHE.get(pjp.getArgs());
        if (articles == null) {
            articles = pjp.proceed(pjp.getArgs());
            CACHE.put(pjp.getArgs(), articles);
        }
        return articles;
    }
}

在上面的示例中,我们举例演示了@Around通知最常用的用法之一。 只有当CACHE中没有返回结果时,才会调用实际的方法,这正是Spring中@Cache注解的实现原理

我们还可以使用ProceedingJoinPoint和@Around通知实现重试操作,以防出现任何异常:


@Aspect
@Component
public class JoinPointAroundExceptionAspect {
    private static final java.util.logging.Logger log = Logger.getLogger(JoinPointAroundExceptionAspect.class.getName());

    @Pointcut("execution(* cn.tuyucheng.taketoday.joinpoint.ArticleService.getArticleList(..))")
    public void articleListPointcut() {
    }

    @Around("articleListPointcut()")
    public Object aroundAdviceException(ProceedingJoinPoint pjp) throws Throwable {
        try {
            return pjp.proceed(pjp.getArgs());
        } catch (Throwable e) {
            log.severe(e.getMessage());
            log.info("Retrying operation");
            return pjp.proceed(pjp.getArgs());
        }
    }
}

例如,此解决方案可用于在网络中断的情况下重试HTTP调用。

4. 总结

在本文中,我们介绍了AspectJ中的JoinPoint和ProceedingJoinPoint之间的区别。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章