Backend Development 14 min read

Understanding Spring AOP Annotations, Pointcuts, and Execution Order

This article explains how Spring AOP annotations such as @Before, @After, @Around and @AfterReturning are defined, demonstrates their usage with concrete Java code, analyzes why @Around may be invoked twice, and provides the correct implementation and execution flow of advice methods.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Spring AOP Annotations, Pointcuts, and Execution Order

Spring AOP provides several advice annotations like @Before , @After , @Around , @AfterReturning , and @AfterThrowing . By defining pointcuts and an aspect class, developers can intercept controller methods for logging, caching, or other cross‑cutting concerns.

@Aspect
@Component
public class LogAspect {
    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
    ThreadLocal
startTime = new ThreadLocal<>();

    // execution of any public method in com.lmx.blog.controller package
    @Pointcut("execution(public * com.lmx.blog.controller.*.*(..))")
    @Order(2)
    public void pointCut() {};

    // custom annotation pointcut
    @Pointcut("@annotation(com.lmx.blog.annotation.RedisCache)")
    @Order(1)
    public void annotationPoint() {};

    @Before(value = "annotationPoint() || pointCut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("方法执行前执行......before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("<=====================================================");
        logger.info("请求来源: =>" + request.getRemoteAddr());
        logger.info("请求URL:" + request.getRequestURL().toString());
        logger.info("请求方式:" + request.getMethod());
        logger.info("响应方法:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("请求参数:" + Arrays.toString(joinPoint.getArgs()));
        logger.info("------------------------------------------------------");
        startTime.set(System.currentTimeMillis());
    }

    @Around("pointCut() && args(arg)")
    public Response around(ProceedingJoinPoint pjp, String arg) throws Throwable {
        System.out.println("name:" + arg);
        System.out.println("方法环绕start...around");
        String result = null;
        try {
            result = pjp.proceed().toString() + "aop String";
            System.out.println(result);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("方法环绕end...around");
        return (Response) pjp.proceed();
    }

    @After("within(com.lmx.blog.controller.*Controller)")
    public void after() {
        System.out.println("方法之后执行...after.");
    }

    @AfterReturning(pointcut = "pointCut()", returning = "rst")
    public void afterRunning(Response rst) {
        if (startTime.get() == null) {
            startTime.set(System.currentTimeMillis());
        }
        System.out.println("方法执行完执行...afterRunning");
        logger.info("耗时(毫秒):" + (System.currentTimeMillis() - startTime.get()));
        logger.info("返回数据:{}", rst);
        logger.info("==========================================>");
    }

    @AfterThrowing("within(com.lmx.blog.controller.*Controller)")
    public void afterThrowing() {
        System.out.println("异常出现之后...afterThrowing");
    }
}

The article first shows a simple controller method without parameters. Because the @Around pointcut requires a method argument, this method only triggers @Before and @After , not @Around . The console output confirms the missing around advice.

@RequestMapping("/achieve")
public Response achieve() {
    System.out.println("方法执行-----------");
    return Response.ok(articleDetailService.getPrimaryKeyById(1L));
}

When a method with a String argument is added, the @Around advice matches, but the original implementation calls ProceedingJoinPoint.proceed() twice, causing the whole advice chain to execute two times. The log demonstrates duplicated before/after messages.

@RedisCache(type = Response.class)
@RequestMapping("/sendEmail")
public Response sendEmailToAuthor(String content) {
    System.out.println("测试执行次数");
    return Response.ok(true);
}

To fix the double execution, the @Around method is rewritten to store the result of a single proceed() call in a local variable and return it, eliminating the second invocation.

@Around("pointCut() && args(arg)")
public Response around(ProceedingJoinPoint pjp, String arg) throws Throwable {
    System.out.println("name:" + arg);
    System.out.println("方法环绕start...around");
    String result = null;
    Object object = pjp.proceed();
    try {
        result = object.toString() + "aop String";
        System.out.println(result);
    } catch (Throwable e) {
        e.printStackTrace();
    }
    System.out.println("方法环绕end...around");
    return (Response) object;
}

Finally, the article summarizes the execution order of advice. If a method matches only the basic pointcut, the order is @Before → @After → @AfterReturning (or @AfterThrowing) . If it also matches an @Around pointcut, the order becomes @Around → @Before → @Around (proceed) → @After → @AfterReturning (or @AfterThrowing) . Understanding this flow helps avoid common pitfalls such as duplicate executions.

Overall, the guide provides a practical walkthrough of defining pointcuts, writing various advice types, diagnosing why an @Around advice may run twice, and correcting the implementation for proper AOP behavior in Spring backend applications.

JavaAOPSpringloggingAnnotationsAspectJAspect-Oriented Programming
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.