@AspectJ in Full: Pointcut Expressions and Five Advice Types

bee2026-10-0842 min read0 views
How to write execution pointcuts, the order of five advice types, argument binding, and how multiple aspects queue up — every trap in aspect development, covered.
1 / 107
Section
0. The 30-second version
2 / 107

Plain words first. Your business code keeps growing the same pile of chores that have nothing to do with business: logging, timing, permission checks, alerts. Left alone, that code has to be hand-copied into every method — dozens of methods, dozens of copies, and one rule change means a project-wide hunt. An aspect does exactly one thing: pull those repeated chores out of the methods, write them once, and let the container slip them around the call at runtime. The business method does not change a single character and never learns it was wrapped.

3 / 107

To use this you must separate five terms. A join point is the moment of one real method invocation; a pointcut is an expression that decides "which join points are mine"; advice is the extra code you actually want to run; an aspect is the class packaging pointcut + advice; weaving is the act of attaching that advice onto the call path. Their relationship in one sentence: an aspect = where to intercept (pointcut) + what to do once intercepted (advice), and weaving connects them to the call.

4 / 107
类比|Analogy

putting a case on a phone explains advice and aspects. The phone (your business object) is never opened or modified; slide a case (the aspect) on and it gains shock protection, a kickstand and a card slot — features the phone maker never shipped, yet present on every unit of that model. @Before is the lens ring on top, @Around is the full bezel wrapping the whole device. You can peel the case off anytime and the phone is untouched. Two costs to remember: buttons feel mushy through the case (performance), and a bare phone you new yourself cannot take a case at all.

5 / 107
类比|Analogy

a parcel sorting centre explains pointcut evaluation. When a parcel (a method call) arrives, the machine reads three screens to pick its chute: first city and compound (which package, which class), then street number and recipient (method name, return type), finally parcel size (the argument list). Every screen must agree — matching only the city ships nothing. The sinister part of a wrong pointcut: a mis-sort never errors, the parcel just quietly never enters a chute, which is the headline symptom in Section 7's table.

6 / 107
Diagram
Figure · Chapter map: the pointcut expression tree
Figure · Chapter map: the pointcut expression tree
7 / 107

After this article you should be able to answer three questions:

8 / 107
  • What does each symbol in execution( com.example.service...*(..)) control, and what breaks if you drop one dot?
  • Which advice enters first and leaves last among the five, and why can only @Around rewrite the return value or swallow an exception?
  • What are the four usual causes of a silently dead aspect, and what is the first thing to look at so you know it is not your imagination?
9 / 107
Section
1. Turning aspects on: what one annotation does
10 / 107

Before writing an aspect you must flip the switch. In plain Spring it is usually this line:

11 / 107
Code
Codejava
@Configuration@EnableAspectJAutoProxypublic class AopConfig {}
Notes
  • @EnableAspectJAutoProxy registers a component named AnnotationAwareAspectJAutoProxyCreator
  • That component is a BeanPostProcessor (more precisely, also a SmartInstantiationAwareBeanPostProcessor). Its job: after a bean finishes initialization, decide whether it is a target of any aspect, and if so build a proxy for it
  • The AutoProxy in its name says it: you never create the proxy; it happens behind the scenes — which is why, once aspects are active, what you get at runtime is usually a proxy

Tip: under Spring Boot you normally need zero configuration. When AopAutoConfiguration detects AspectJ support on the classpath it auto-enables @AspectJ auto-proxying (properties spring.aop.auto=true and spring.aop.proxy-target-class default to true). You just annotate classes with @Aspect and @Component.

12 / 107
Section
2. Pointcut expressions in full
13 / 107

Pointcuts are the part that needs the most practice. The core is execution, whose full syntax is best remembered as a skeleton:

14 / 107
text
execution( modifier?  returnType  declaringType?.methodName(params)  throws? )
15 / 107

Only "return type, method name, parameters" are required; the rest can be omitted or replaced with * and ... These five examples cover the vast majority of everyday cases:

16 / 107
Code
Codejava
// Example 1: any method of any class in the service package, any return type, any args@Pointcut("execution(* com.example.service.*.*(..))")// Example 2: only public methods of UserService@Pointcut("execution(public * com.example.service.UserService.*(..))")// Example 3: void return type, method name starts with save@Pointcut("execution(void com.example.service.*.save*(..))")// Example 4: com.example and all subpackages, class name ends with Service@Pointcut("execution(* com.example..*Service.*(..))")// Example 5: exactly findById with a Long parameter@Pointcut("execution(* com.example.service.UserService.findById(Long))")
Notes
  • Example 1: matches "any return type", the first . matches "any class in that package", and (..) means "any number of any args"
  • Example 2: adds the public modifier; a hard-coded class name targets just that one class
  • Example 3: save* is a prefix wildcard, so save, saveOrder and saveAll all match
  • Example 4: the double dots in com.example.. mean "this package and all subpackages" — the key to "why doesn't my pointcut reach subpackages?"
  • Example 5: writing a concrete type Long requires an exact signature match, whereas (..) matches any args
17 / 107

Beyond execution there are designators for "lazier" scenarios; they differ in the dimension they match:

18 / 107
Table
DesignatorMatches onExampleWhen to use
withinType / packagewithin(com.example.service..*)Coarse interception of a whole package or class
thisProxy typethis(com.example.UserService)Test which type the generated proxy is
targetTarget typetarget(com.example.UserService)Test the original target's type
argsMethod argumentsargs(Long, ..)Match by arg type/count and bind args
@annotationMethod annotation@annotation(com.example.anno.Log)Intercept only methods carrying an annotation
@withinType annotation@within(com.example.anno.Loggable)Intercept all methods of annotated classes
19 / 107
Note

this and target are the easiest to confuse. this tests whether the proxy is of a type, while target tests whether the original target object is. They diverge with introductions; in everyday business code they are mostly equivalent, but interviews love this distinction.

20 / 107
Section
3. Combining pointcuts: && || !
21 / 107

Several pointcuts can be joined with logical operators, reading almost like prose:

22 / 107
Code
Codejava
// Methods in the service package AND carrying @Log@Pointcut("execution(* com.example.service..*(..)) && @annotation(com.example.anno.Log)")// Exclude internals: not the methods under com.example.service.internal@Pointcut("execution(* com.example.service..*(..)) && !within(com.example.service.internal..*)")// Either condition@Pointcut("@annotation(com.example.anno.Log) || @within(com.example.anno.Loggable)")
Notes
  • && is intersection, || is union, ! is complement — note these are not Java's short-circuit operators but the pointcut combination syntax
  • && binds tighter than ||; when mixing them, always add parentheses, or you will get surprising results
  • !within(...) is the most useful exclusion trick: circle a package first, then subtract what you do not need, instead of enumerating class names

Trap: the difference between .. and is the most common slip. com.example.service. matches one level only, while com.example.service..* matches all levels. If a pointcut stubbornly misses classes in a subpackage, first check whether you dropped a dot.

23 / 107
Section
4. Five advice types: what each may and may not do
24 / 107

Advice is the code inserted at the join points a pointcut matches. Spring AOP offers five, and the crux is their "permissions":

25 / 107
Table
AdviceChange argsChange returnSwallow exceptionTypical use
@BeforeNoNoNoArgument validation, call logging
@AfterReturningNoNo (read only)NoLog success, post-call statistics
@AfterThrowingNoNoNo (catch but not swallow)Error logging, alerting
@AfterNoNoNoResource cleanup (both paths)
@AroundYesYesYesTransactions, caching, timing, retries
26 / 107

In one line: only @Around is the all-rounder. It receives a ProceedingJoinPoint, so it can change arguments, decide whether to invoke the target, rewrite the return value, or even swallow exceptions. The other four can only "observe" and "record".

27 / 107

That sentence is what this picture argues: the left side may look at everything and touch nothing, the right side can touch everything and therefore owns the consequences. The selection rule is never "which one is more advanced" but "does this job need to change the outcome":

28 / 107
Diagram
Figure · @Around versus the other four
Figure · @Around versus the other four
29 / 107
Section
5. Binding parameters: wiring context into advice
30 / 107

How does an advice method get "information about the current call"? Through parameter binding. Here is a complete aspect class:

31 / 107
Code
Codejava
@Aspect@Componentpublic class LogAspect {    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);    @Pointcut("execution(* com.example.service..*(..))")    public void serviceMethods() {}    @Before("serviceMethods()")    public void before(JoinPoint jp) {        log.info("calling {}.{}",                jp.getSignature().getDeclaringTypeName(), jp.getSignature().getName());    }    @AfterReturning(pointcut = "serviceMethods()", returning = "result")    public void afterReturning(JoinPoint jp, Object result) {        log.info("{} returned: {}", jp.getSignature().getName(), result);    }    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")    public void afterThrowing(JoinPoint jp, Throwable ex) {        log.error("{} threw: {}", jp.getSignature().getName(), ex.getMessage());    }    @After("serviceMethods()")    public void after(JoinPoint jp) {        log.info("{} finished", jp.getSignature().getName());    }    @Around("serviceMethods()")    public Object around(ProceedingJoinPoint pjp) throws Throwable {        long start = System.nanoTime();        try {            return pjp.proceed();     // must be called, or the target never runs        } finally {            log.info("{} took {}ms", pjp.getSignature().getName(),                     (System.nanoTime() - start) / 1_000_000);        }    }}
Notes
  • JoinPoint is the common entry for all non-around advice; it exposes the signature, args and target
  • @AfterReturning(returning = "result") binds the return value to the parameter named result — the names must match exactly
  • @AfterThrowing(throwing = "ex") binds the thrown exception to the parameter ex
  • @Around uses ProceedingJoinPoint, whose proceed() is the gate that "lets the call through" to the target

Key point: the names in returning / throwing are parameter names. A typo will not fail compilation but will fail binding at startup or runtime. Keep the names aligned and read carefully.

32 / 107

Here is how that name-matching actually happens, in six frames. Watch frame ④: the string in the annotation and the parameter names are compared one by one — no expression language is involved at all, it is literally names:

33 / 107
Animation
Animation · How advice receives args, results and exceptions
Animation · How advice receives args, results and exceptions
34 / 107

Because no expression language is involved here, plenty of beginners assume returning = "result" and the #{} inside @Cacheable(key = "#user.id") are the same syntax, and then write horrors like returning = "#result", which fail in silence. That other thing is SpEL, a separate language: it has its own root object (#root / #this), collection selection and projection, ternary and safe-navigation operators, and completely different failure messages. The last mode shows the three classic miswrites side by side:

35 / 107
Kernel lab
TeaVMPointcut binding is not SpEL: two languages, two jobsidle
Start with literal to separate #root from #this, then collection for selection and projection, and finish with fail to compare three real error messages
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
36 / 107
Section
6. Execution order: the full advice chain for one call
37 / 107

With all advice attached to one method, what is the order? Run it once and the output is clear:

38 / 107
Diagram
Figure 1 · Advice execution order
Figure 1 · Advice execution order
39 / 107
Code
Codetext
@Around before@Before==== target method runs ====@AfterReturning@After@Around after / return
Notes
  • The first half of @Around enters first and its second half leaves last, like a pair of parentheses wrapping the other advice
  • On a successful return it is @AfterReturning; the moment an exception is thrown, @AfterReturning is replaced by @AfterThrowing
  • @After runs on both paths, making it ideal for cleanup
40 / 107

When several aspects match the same method, order is decided by @Order:

41 / 107
Code
Codejava
@Aspect@Order(1)          // smaller number = more "outer"@Componentpublic class AuthAspect { /* authorization, should run first */ }@Aspect@Order(2)@Componentpublic class LogAspect { /* logging, runs after authorization */ }
Notes
  • Smaller @Order = more outer: AuthAspect's @Before runs first and its @After runs last
  • Without @Order, the order is undefined — never rely on source order
  • To make "a failed auth short-circuit and skip subsequent logging", put the auth aspect on the outer ring
42 / 107
Section
7. A checklist for aspects that do not fire
43 / 107

"My aspect is written but never runs" is the number one aspect problem. Walk this table line by line and you will locate it eighty percent of the time:

44 / 107
Table
SymptomCauseFix
Works when called by another bean, not via internal self-callSelf-invocation bypasses the proxyExtract to another bean, or inject a self-proxy
Advice never fires on private / final methodsSuch methods cannot be proxiedMake them public and non-final; or use AspectJ
Starts cleanly but nothing is interceptedWrong pointcut (package level, signature)Turn on debug logging and check matching
The target is an object you newedNon-container beans get no proxyHand it to the container (@Component / @Bean)
ClassCastException when casting to the implJDK proxy received as an implementation classInject by interface, or enable proxyTargetClass
45 / 107

The first tool is always logging: set logging.level.org.springframework.aop=DEBUG and the container prints which aspects and proxy type each bean received, with a "does not match" reason when a pointcut misses.

46 / 107
Section
8. Trap: forgetting to call proceed() in @Around
47 / 107
Trap

if an @Around advice never calls pjp.proceed(), the target method never executes — yet nothing errors, it just "does not happen". Such a bug is brutal to trace, because everything before and after looks normal and only the business logic vanished. Treat proceed() as mandatory inside @Around, and wrap it in try/finally so post logic always runs and exceptions always propagate.

48 / 107
Section
9. Try it: how the advice order actually executes
49 / 107

The demo below visualizes the advice chain's execution order. Switch to the "exception path" and run it again to see how @AfterThrowing replaces @AfterReturning and how @After runs on both paths:

50 / 107
Kernel lab
TeaVMAdvice chain execution orderidle
Switch to the exception path and watch @AfterThrowing versus @After
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
51 / 107

That one shows how the proxy forwards; the ordering table in Section 6 deserves a demo that is about ordering itself. advices hangs all five advice types on one method — run normal, then error, and the difference between the two paths shows up immediately:

52 / 107
Kernel lab
TeaVMFive advice types across the success and error pathsidle
Run normal first and copy the log order; then error, and watch @AfterReturning get replaced by @AfterThrowing
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
53 / 107
Animation
Animation · How a pointcut matches
Animation · How a pointcut matches
54 / 107
Section
10. Decision: @Around or @Before for logging
55 / 107
Decision
Decisionyou need "call logging" on a set of Service methods, recording method name, arguments, return value and duration. Use `@Before` or `@Around`?
56 / 107
Section
11. Hands on: run pointcuts, weaving and ordering yourself
57 / 107

Everything above was prose; this section is all clickable. The four demos follow "see the symptom, then the mechanism, then the order", and each lets you switch parameters.

58 / 107

The first answers the parcel sorting analogy from Section 0: the same package text means different things inside execution and within. Pick "execution parts" to watch segment-by-segment matching; pick "Why nothing matched" for the five causes of silent misses — the one beginners stall on hardest, because it raises no error at all:

59 / 107
Kernel lab
TeaVMPointcut evaluation live: how a parcel clears three screensidle
Start with execution parts, then switch to 'Why nothing matched' and compare with the table in Section 13
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
60 / 107

Animation is still too quick for "check it slot by slot". Spread the same evaluation over a stepping bench: a candidate method and one expression on the left, "which slot is being judged right now, and with what verdict" on the right. Press next and watch step ⑤ — the whole expression has already short-circuited here; the last two slots were never even evaluated:

61 / 107
Stepper
StepperStepping an evaluation: why the method in the impl sub-package is never advised1 / 6
Press next in order; the decisive lines are 4 and 5, then re-run with the two-dot form at line 8
Code under debug
1String c = "com.example.service.impl.UserServiceImpl.save(User)"; // (1) the candidate method
2@Pointcut("execution(* com.example.service.*.*(..))") // (2) the expression you wrote
3slot 1 return type * -> any return type counts as a hit // (3)
4slot 2 declaring type com.example.service.* // (4)
5 one star eats direct children only; impl is a grandchild -> false // (5)
6slot 3 method name * -> never reached at all // (6)
7slot 4 argument list (..) -> never reached either // (7)
8@Pointcut("execution(* com.example.service..*.*(..))") // (8) fix: one dot becomes two
Variables now
candidateUserServiceImpl#save(User)
declaring typecom.example.service.impl.UserServiceImpl
pointcutexecution(* com.example.service.*.*(..))
Call stack
1matchOrNot
2evaluate execution
1Look closely at who is being tested: notice the extra impl segment in the package. Nine out of ten 'my aspect does nothing' cases hide here — you think this class sits next to UserService, but it lives one level deeper.
62 / 107

The second settles "does the annotation go on the method or the class". @annotation and @within differ by one word yet behave oppositely, and this branch also names an invisible killer: Java does not inherit method annotations onto the implementing class, so annotating only the interface method usually matches nothing:

63 / 107
Kernel lab
TeaVM@annotation or @within: one word apartidle
Focus on step 6, 'do inherited annotations count' — it explains the failing variant in Section 15
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
64 / 107

The third is the before-versus-after weaving contrast: the very same userService.save("Alice") call has one stack frame and a body mixed with logging, try/catch and timing when no aspect exists; once woven, three cross-cutting layers wrap it while the business code is unchanged by a single line. Switch to "What it costs" to see the price of that case (slower startup, and four cases nothing can advise):

65 / 107
Kernel lab
TeaVMBefore versus after weaving: two shapes of one callidle
Click in order: 'No aspect' then 'Aspect woven' then 'What it costs'
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
66 / 107

The fourth resolves the question left open at the end of Section 6: when several aspects hit one method, who sits outside. "Nested timeline" lists the full chain step by step (Around1-before → Around2-before → Before → target → AfterReturning → After → Around2-after → Around1-after); "Reversed order" shows why forgetting @Order turns into "fine on my laptop, red in CI":

67 / 107
Kernel lab
TeaVMAspect nesting order: how the onion peelsidle
Watch '@Order applies' first, then the nested timeline, then the reversed-order trap
Scenario
Click “Run demo” to execute the AOT-compiled Java kernel right in your browser, step by step.
68 / 107
Animation
Animation · How the five advices move through one call
Animation · How the five advices move through one call
69 / 107

Four demos done; what remains is turning "read an expression" into "say instantly who it catches". The table in Section 2 sorts designators by dimension; this round trains something different — expression in, coverage out — and since no two entries on either side repeat, position tricks will not save you:

70 / 107
Match
MatchOne expression ↔ who it actually catchesMatched 0/7 · Missed 0
Left: pointcut fragments people really write. Right: the coverage each one produces. A wrong pair explains exactly who was missed
Pick a card on the left first
71 / 107

From here, type the commands yourself. This console talks to the same container running in your browser, and every line of output is computed by the kernel — boot, then beans to see who got a shell, and lab pointcut nomatch is the full version of the stepping bench above:

72 / 107
Console
73 / 107
Section
12. Sandbox: assemble an execution expression yourself
74 / 107

On the left, change the four slots one at a time — return type, package depth, method name, argument list; on the right you immediately get which methods this matches and why the rest are missed. Choosing the "dropped a dot" and "() mistaken for (..)" cells once by hand beats memorising ten rules:

75 / 107
Sandbox
SandboxFour-slot execution builder: who matched, who was missed
Result
execution(* com.example.service..*.*(..))
Match: save(User) / findById(Long) / deleteAll() — impl sub-package included
Coverage: every proxyable public method of those classes
The most common and widest everyday form: the whole service tree in one shot.
76 / 107
Note

the one-star cell is where most accidents happen. Rule of thumb: **a single between dots eats exactly one segment*, while .. eats "this package and every descendant"; and the .. inside the argument parentheses means something else entirely — "any number of arguments of any type". One symbol, two meanings depending on position, is the hardest part of execution to memorise, and the origin of row three in Section 13's table.

77 / 107
Section
13. Common errors quick reference
78 / 107

Every "error text" below can be pasted verbatim into a search box — do not paraphrase it:

79 / 107
Table
Error text (fragment)Real cause30-second rescueRead deeper in
No exception at all, advice simply never runs (symptom: not one log line)A pointcut evaluating to false is a legitimate outcome; Spring concludes "you should not be intercepted" and quietly does nothingTemporarily widen to @Around("execution( (..))"): if that fires, your expression is the culprit; then set logging.level.org.springframework.aop=TRACE and count candidate advisorsThis article, Section 11 pointcut demo · #14 AOP internals
Invalid binding type: long. Method: public void ...afterThrowing(long) / ConflictingAdviceReqArgExceptionThe name in returning = "result" / throwing = "ex" does not equal the parameter name, or two parameters of the same type are bound in one adviceCopy-paste the parameter variable name into the annotation instead of typing it; a Throwable binding must be the last parameterSection 5 · #15 AOP practice
IllegalArgumentException: error at anonymous pointcut expression around this / Can't parse advice pointcut designatorSyntax error in the pointcut string: unbalanced parentheses, a dangling &&, or an annotation written as a simple name instead of its fully qualified nameCheck three things: brackets balanced, @annotation(...) holds a fully qualified class name, and parentheses added wherever && and `` mixSection 3
Cannot find class [com.example.anno.Log] for annotation [@Log]The annotation class referenced by the pointcut is not compiled, lives in another module, or its package is misspelledCopy the annotation's fully qualified name from the IDE and paste it into the pointcut string — never type it by handSection 12 sandbox · #2 Maven
ClassCastException: class com.sun.proxy.$Proxy42 cannot be cast to class com.example.service.UserServiceImplThe target has an interface so a JDK proxy was built, but the injection point is declared as the implementation classInject by interface type; or force CGLIB: spring.aop.proxy-target-class=true under Boot (already the default), @EnableAspectJAutoProxy(proxyTargetClass = true) in plain Spring#12 Dynamic proxy · #14 AOP internals
Advice on a private / final / static method never fires, again without an errorProxies cannot see private methods, CGLIB cannot override final ones, static calls do not belong to an instanceMake it a public non-final instance method; if you truly need private-method interception, switch to AspectJ compile-time weavingSection 7 · #14 AOP internals section 7
No qualifying bean of type 'com.example.aspect.LogAspect' available@Aspect is only a descriptor — it does not make the class a bean; without @Component (or a @Bean registration) the creator never finds itAdd @Component and confirm component scanning covers it; when findCandidateAdvisors() returns an empty list, every aspect goes silent at once#14 AOP internals section 6 checklist
Advices vanish when a method is called internally via this.method()this is the raw object, not the proxy, so the call never enters the advice chainExtract to another bean; or enable exposeProxy = true and use AopContext.currentProxy(); or inject your own proxy#12 Dynamic proxy · #15 AOP practice
Classes in sub-packages never match (com.example.service..(..) is useless)A single * matches one level only, so impl sits outside itChange the package slot to com.example.service...(..) — two dotsSection 3 trap · Section 12 sandbox, first cell
After writing () instead of (..), every method with arguments stops matching() demands exactly zero arguments; (..) is what means "any number of any type"Memorise: (..) any / () must be empty / (*) exactly one / (Long) exactly one LongSection 2 example 5 · Section 12 sandbox
80 / 107
Trap

mixing up and .. is the shared root of the two most frequent rows above. eats exactly one segment (one package name, one type name, one argument), while .. in the type slot eats "any depth of sub-package" and in the argument slot eats "any number of any arguments". So com.example..Service.(..) reads: under com.example at any depth, every class whose name ends with Service, all methods, any arguments. For long expressions, say each segment out loud instead of trusting your gut.

81 / 107

Row one of that table fails in silence, but a malformed pointcut has the opposite fate: it explodes in your face, with a stack whose upper frames belong to Spring while the actual culprit sits inside AspectJ's parser. Practise on it:

82 / 107
Triage
Error triageIllegalArgumentException: a valid WildcardType is required
A malformed pointcut blows up while the aspect bean is created

You only edited the package segment of one execution expression, and now the application refuses to start. The message names neither your class nor the offending character.

APPLICATION FAILED TO START
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'timingAspect' defined in file [TimingAspect.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.aspect.TimingAspect]: Constructor threw exception; nested exception is java.lang.IllegalArgumentException: error The near term's relationship is malformed; a valid WildcardType is required
at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvice(ReflectiveAspectJAdvisorFactory.java:273)
at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.<init>(InstantiationModelAwarePointcutAdvisorImpl.java:103)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:302)
at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:181)
at org.aspectj.weaver.patterns.PerCflowPointcut.resolve(PerCflowPointcut.java:83)
Caused by: java.lang.IllegalArgumentException: error The near term's relationship is malformed; a valid WildcardType is required
Click the frame you blame — guessing is allowed
No pressure: guess the exception first, then which line actually made the call.
83 / 107
Section
14. Quick quizzes
84 / 107
Quiz
Check yourselfYou wrote `@Before("execution(* com.example.service.*.*(..))")`, but the advice on `com.example.service.impl.UserServiceImpl#save()` never fires and startup reports nothing. Most likely cause?
Pick one — you get feedback right away
85 / 107
Quiz
Check yourselfAn `@Around` advice body contains only `log.info("enter")` and some timing, and forgets `pjp.proceed()`. What happens at runtime?
Pick one — you get feedback right away
86 / 107
Section
15. Exercises
87 / 107
Section
Tier one · Follow along
88 / 107

Goal: in three minutes, produce a complete aspect whose logs you can actually see, confirming AOP works in your project. Drop these files into a fresh Spring Boot project (the build needs spring-boot-starter-aop, or at least spring-boot-starter plus org.aspectj:aspectjweaver).

89 / 107
java
package com.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ConfigurableApplicationContext;import org.springframework.stereotype.Service;@SpringBootApplicationpublic class AspectDemoApplication {    public static void main(String[] args) {        try (ConfigurableApplicationContext ctx =                     SpringApplication.run(AspectDemoApplication.class, args)) {            UserService svc = ctx.getBean(UserService.class);            svc.save("Alice");            System.out.println("what we actually got: " + svc.getClass().getName());        }    }}@Serviceclass UserService {    public void save(String name) {        System.out.println("==== target method runs: saving " + name + " ====");    }}
90 / 107
java
package com.example.aspect;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;@Aspect@Component                       // drop this line and the aspect is never discoveredpublic class TraceAspect {    private static final Logger log = LoggerFactory.getLogger(TraceAspect.class);    @Pointcut("execution(* com.example..*Service.*(..))")    public void serviceMethods() {}    @Before("serviceMethods()")    public void before(JoinPoint jp) {        log.info("[BEFORE] {}", jp.getSignature().toShortString());    }    @Around("serviceMethods()")    public Object around(ProceedingJoinPoint pjp) throws Throwable {        long start = System.nanoTime();        log.info("[AROUND-IN]  {} args={}", pjp.getSignature().getName(), java.util.Arrays.toString(pjp.getArgs()));        try {            return pjp.proceed();          // mandatory: without it the target never runs        } finally {            log.info("[AROUND-OUT] {} took={}ms",                    pjp.getSignature().getName(), (System.nanoTime() - start) / 1_000_000);        }    }    @AfterReturning(pointcut = "serviceMethods()", returning = "result")    public void afterReturning(JoinPoint jp, Object result) {        log.info("[RETURNED] {} -> {}", jp.getSignature().getName(), result);    }    @After("serviceMethods()")    public void after(JoinPoint jp) {        log.info("[AFTER] {}", jp.getSignature().getName());    }}
91 / 107

Expected output (the ==== line comes from System.out, the rest from slf4j, whose prefix carries the timestamp and logger name):

92 / 107
Code
Codetext
[AROUND-IN]  save args=[Alice][BEFORE] UserServiceBean.save(..)==== target method runs: saving Alice ====[RETURNED] save -> null[AFTER] save[AROUND-OUT] save took=1mswhat we actually got: com.example.UserService$$SpringCGLIB$$0
Notes
  • [AROUND-IN] precedes [BEFORE]: the front half of @Around is the outermost parenthesis
  • save -> null: the target is void, so the value bound by @AfterReturning is naturally null
  • The last line shows $$SpringCGLIB$$: what you fetched from the container is already the proxy, not the object you constructed
93 / 107
Section
Tier two · Variations
94 / 107

Change exactly one thing each time and read the conclusion:

95 / 107
  1. Turn the pointcut into com.example..Service.(..) (drop one dot). You will observe: UserService still matches while it sits directly in com.example, but the moment it moves into com.example.service.impl every advice dies, silently. Compare with the one-star cell of Section 12 and "a single eats one level" becomes obvious.
  2. Delete return pjp.proceed(); from the @Around and keep only the logging. You will observe: the ==== target method runs ==== line disappears entirely and the result becomes null, while the program throws nothing at all. That is the live version of the second quiz in Section 14.
  3. Add a second MetricAspect on UserService#save with @Order(2) (same body, prefix [METRIC]) and mark TraceAspect @Order(1). You will observe the sequence AROUND-IN(Trace) → AROUND-IN(Metric) → BEFORE → target → AFTER → AROUND-OUT(Metric) → AROUND-OUT(Trace); swap the two numbers and both entry and exit flip as a block. Now delete both @Order annotations and restart a few times — the order begins to drift, which is precisely how ghost bugs are bred.
  4. Make save private. You will observe: all advice vanishes again, still with no error. See the proxy boundaries in #14 AOP internals.
96 / 107
Section
Tier three · Build one
97 / 107

Build a slow-call attribution aspect: not just timing, but tiered by threshold and aggregated.

98 / 107
  • Define @SlowWatch(value = "place order", warnMs = 200, errorMs = 1000) with @Target(METHOD) and @Retention(RUNTIME)
  • Write @Around("@annotation(slowWatch)"): INFO on the normal path, WARN past warnMs, ERROR past errorMs, always including method name, the annotation's value, elapsed time and argument count
  • Accumulate per-method call counts and total time in a ConcurrentHashMap<String, LongAdder>, and print a "Top 3 slowest methods" summary (via @Scheduled or on shutdown)
  • Never swallow: catch, record a failure count, then rethrow the original
99 / 107

Acceptance checklist: ① drive one method to 50ms / 300ms / 1200ms and see INFO / WARN / ERROR respectively; ② on an exception the caller still receives the original throwable (verify with @AfterThrowing or a try/catch that the stack was not replaced); ③ annotate a private method with @SlowWatch and explain in your own words why nothing happens without guessing; ④ remove @Component from the aspect and confirm business output is byte-for-byte identical — proof it really is only a case slipped over the phone.

100 / 107
Section
16. Self-check
101 / 107
Self-check

without looking, name the five slots of execution( com.example.service...*(..)) from left to right, and say which two may be omitted.

102 / 107
Self-check

what do (..), (), (*) and (Long) each match, and which one is most often confused with the first?

103 / 107
Self-check

which element does @annotation(com.example.anno.Log) inspect versus @within(com.example.anno.Log)? What happens if the annotation only sits on the interface method?

104 / 107
Self-check

in one successful call, what is the execution order of the five advice types, and which one gets substituted when an exception is thrown?

105 / 107
Self-check

what happens when two aspects carry no @Order at all, and why is that called a bug that only reproduces on another machine?

106 / 107

Mantra: **the pointcut says where to grab, the advice says what to do once grabbed, `@Around` must call proceed, `..` eats sub-packages while `*` eats one level.**

107 / 107
Summary

this section walked through the spots that trip up aspect development — the execution skeleton is "modifier returnType class?.method(params) throws?", where .. and * decide matching depth; within / this / target / args / @annotation / @within each match a different dimension; among the five advice types only @Around can change args, change the return value, and swallow exceptions; in ordering, @Around acts like parentheses around the other four, and multiple aspects queue by @Order (smaller is more outer). Two iron rules to finish: @Around must call proceed(), and when an aspect does not fire, check the four usual suspects — self-invocation, private/final methods, a wrong pointcut, and non-container objects.