Mastering Spring AOP Pointcut Expressions: Precise execution and @annotation Matching Techniques
This article explains why many Spring AOP pointcuts fail, clarifies the execution and @annotation expressions, details wildcard usage, presents dozens of concrete pointcut examples, compares their granularity and performance, and offers best‑practice tips to avoid common pitfalls in real‑world Java projects.
1. Execution Pointcut Fundamentals
The core of AOP consists of two parts: pointcut (where to apply) and advice (what to do) . If the pointcut does not match, the advice is ineffective.
1.1 What is a Pointcut?
A pointcut is a set of matching rules that tell Spring which classes and methods should be intercepted.
1.2 Spring AOP Interception Limits
Spring AOP relies on dynamic proxies (JDK/CGLIB). Regardless of the pointcut, the following cannot be intercepted:
private methods (no proxy)
static methods (not part of an instance)
final methods (cannot be overridden)
inner class methods
self‑calls (this.xxx()) (bypass the proxy)
1.3 Two Core Pointcut Types
execution : coarse‑grained, batch interception based on package, class, method name, parameters, and return type.
@annotation : fine‑grained, precise interception based on a custom method annotation.
2. Common Wildcards
The main source of confusion is mixing * and ... The table below summarizes their meanings and pitfalls:
* (single‑level wildcard): matches a single package level, a single class name, or a single parameter. It cannot span multiple levels.
.. (multi‑level wildcard): matches any number of package levels or any number of parameters (including zero).
+ (sub‑class wildcard): matches the specified class and all its subclasses/implementations; can only be placed after a class name.
3. execution Pointcut Details
3.1 Standard Syntax
execution([modifier] [return type] [package path].[class].[method]([parameter list]) [throws exception])Modifiers and throws are optional; return type, package, class, method name, and parameters are required.
3.2 Deep Dive
Modifier : if omitted, all modifiers match. It is recommended to specify public to avoid unintentionally matching private methods.
Return type : common values are void, String, Integer, custom types, or * for any return type. Beginners often forget to specify it, causing syntax errors.
Package path : com.demo.controller matches only the exact package (no sub‑packages). com.demo..controller matches any sub‑package under com.demo that ends with controller (most common). com..* matches all packages under com at any depth.
Class name : *Controller matches all classes ending with Controller. UserController matches a single class. BaseService+ matches BaseService and all its subclasses.
Method name : * matches any method. add* matches methods starting with add. *List matches methods ending with List.
Parameter list : () – no parameters. (String) – exactly one String parameter. (*) – exactly one parameter of any type (does **not** match multiple parameters). (..) – any number of parameters of any type (the universal wildcard). (String,..) – first parameter is String, followed by any others. (..,Integer) – last parameter is Integer.
3.3 Classic Expressions
@Pointcut("execution(public * com.demo..controller.*.*(..))")– intercept all public methods of any controller. @Pointcut("execution(* com.demo..service.*Service.*(..))") – intercept all service methods.
@Pointcut("execution(* com.demo.service.UserService.getUserById(..))")– intercept a single method.
@Pointcut("execution(* com.demo..*.*(add*,update*,delete*)(..))")– intercept add/update/delete methods, exclude queries. @Pointcut("execution(* com.demo..*.*(String))") – intercept methods with a single String argument. @Pointcut("execution(void com.demo..*.*(..))") – intercept only void methods.
@Pointcut("execution(* com.demo..controller.*.*(..)) && @annotation(org.springframework.web.bind.annotation.PostMapping)")– intercept POST controller methods.
@Pointcut("execution(* com.demo..service.*.*(..)) && !execution(* com.demo.service.TestService.*(..))")– intercept all service methods except those in TestService. @Pointcut("execution(public * com.demo..*.*(..))") – intercept all public methods (performance‑heavy).
3.4 execution Pitfalls
Pitfall 1 : Mixing * and .. incorrectly, e.g., com.demo.*.controller only matches a single sub‑package; use com.demo..controller for any depth.
Pitfall 2 : Using (*) expecting multiple parameters – it matches only one. Use (..) for any number.
Pitfall 3 : Trying to intercept private or static methods – proxies cannot handle them, so the pointcut will never fire.
Pitfall 4 : Over‑broad pointcuts like execution(* *.*(..)) cause slow startup and performance loss due to scanning the whole classpath.
4. @annotation Pointcut
4.1 When to Use
executionis suitable for blanket interception, but many business scenarios require selective interception (operation logs, data masking, rate limiting, special permissions). In those cases, a custom annotation combined with @annotation provides precise control.
4.2 Code Example – Custom Runtime Annotation
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogRecord {
// operation description
String value() default "";
// operation type
String operateType() default "普通操作";
// whether to record request details
boolean saveDetail() default true;
}4.3 Aspect Definition
@Aspect
@Component
public class LogRecordAspect {
// match all methods annotated with @LogRecord
@Pointcut("@annotation(com.demo.annotation.LogRecord)")
public void logPointCut() {}
@Around("logPointCut() && @annotation(logRecord)")
public Object around(ProceedingJoinPoint pjp, LogRecord logRecord) throws Throwable {
String desc = logRecord.value();
String type = logRecord.operateType();
System.out.println("【操作日志切面】操作描述:" + desc + ",操作类型:" + type);
return pjp.proceed();
}
}4.4 Applying the Annotation
@RestController
@RequestMapping("/user")
public class UserController {
// annotation present – aspect runs
@LogRecord(value = "新增用户", operateType = "新增")
@PostMapping("/add")
public String addUser() { return "新增成功"; }
// no annotation – aspect does not run
@GetMapping("/list")
public String listUser() { return "用户列表"; }
}4.5 Advanced @annotation Techniques
Limit annotation to controllers only:
@Pointcut("execution(* com.demo..controller.*.*(..)) && @annotation(com.demo.annotation.LogRecord)")Intercept all methods of a class annotated with @LogRecord using @within: @Pointcut("@within(com.demo.annotation.LogRecord)") Exclude specific methods:
@Pointcut("@annotation(com.demo.annotation.LogRecord) && !execution(* com.demo..*.*test*(..))")4.6 @annotation Pitfalls
Annotation must have RUNTIME retention; otherwise it is invisible at runtime.
Can only be applied to method annotations; class‑level annotations require @within.
To read annotation attributes, bind the annotation object in the advice parameters.
Full class‑name path is required in the pointcut; short names are not allowed.
Self‑invocation ( this.xxx()) bypasses the proxy, causing the annotation‑based aspect to fail.
5. Pointcut Logical Operators
&& – logical AND (both conditions must match).
|| – logical OR (any condition matches).
! – logical NOT (exclude matches).
5.1 Composite Examples
Intercept all controllers and any service method annotated with @LogRecord:
@Pointcut("execution(* com.demo..controller.*.*(..)) || @annotation(com.demo.annotation.LogRecord)")Intercept all controllers except test controllers:
@Pointcut("execution(* com.demo..controller.*.*(..)) && !execution(* com.demo..controller.TestController.*(..))")Intercept only POST/PUT modifying controller methods:
@Pointcut("execution(* com.demo..controller.*.*(..)) && (@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping))")6. execution vs @annotation Comparison
Key differences:
Interception granularity : execution – coarse, batch; @annotation – fine, method‑level.
Code intrusiveness : execution – zero intrusiveness; @annotation – requires adding the annotation.
Typical scenarios : execution – global logging, timing, exception handling; @annotation – operation logs, rate limiting, data masking, special permissions.
Performance impact : execution scans a wide range (higher overhead); @annotation intercepts only marked methods (lowest overhead).
Flexibility : execution – low; @annotation – very high, allowing per‑method control.
Conclusion
Understanding the precise semantics of execution and @annotation pointcut expressions, mastering wildcards, and combining logical operators enables developers to write clean, efficient, and maintainable AOP configurations, avoiding common pitfalls such as accidental interception of private/static methods, over‑broad pointcuts, and self‑invocation failures.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
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.
