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.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.