1
0

Three new article modules: configuration binding, profiles and config data, Spring AOP

configuration-properties/  @ConfigurationProperties vs @Value on Spring Boot 4.1.1.
  The relaxed-binding matrix is generated by binding each spelling rather than
  transcribed, and re-checked against real processes -- the in-process probe was
  wrong twice before it was right. Records the three findings that came out of it:
  @Value does get relaxed resolution inside Spring Boot (Boot attaches
  ConfigurationPropertySources), the configuration processor silently stops
  generating metadata on JDK 23+ when declared as a plain dependency, and @Valid is
  not what makes nested constraints run.

profiles-and-config/       Precedence, profiles, spring.config.import and config trees.
  /precedence reports every source holding a property in rank order with file and
  line, which turns "my profile file had no effect" into a two-line answer. Also
  pins the counterintuitive one: an imported file outranks the file that imported it.

spring-aop/                Designators, proxy types, and aspects that do not fire.
  One advice per supported designator so the reference table is generated from real
  matches; all fourteen unsupported designators fed to the parser. Two corrections to
  the reference documentation: unsupported designators throw
  UnsupportedPointcutPrimitiveException (extends RuntimeException, not
  IllegalArgumentException), and spring-boot-starter-aop was renamed to
  spring-boot-starter-aspectj in Boot 4.

19 contract tests across the three modules, 15 captured transcripts, all regenerated
by scripts/run-all.sh. Verified on Spring Boot 4.1.1, Spring Framework 7.0.9,
JDK 25.0.4.1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gip4srpzMwjgoba6uEfbr5
This commit is contained in:
2026-09-08 16:36:17 +00:00
parent 958b401f0f
commit 86246dc860
107 changed files with 5075 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
[Index](../README.md) &middot; [Designators &rarr;](02-designators.md)
# 1. What Spring AOP actually is
Spring AOP is a **proxy** mechanism that borrows AspectJ's **pointcut language**. Both halves
of that sentence explain a failure mode.
## It is proxies
Spring does not modify your bytecode. When a bean matches a pointcut, the container puts a
proxy in the bean registry in its place. Callers get the proxy; the proxy runs advice and then
forwards to the real object.
Everything that follows from this:
- Only **beans** can be advised. An object created with `new` has no proxy.
- Only calls **through the proxy** are intercepted. A call the object makes to itself is not.
- Only **overridable** methods can be advised: not private, not final, not static.
- Join points are **method executions**. Field access, constructor calls and exception handlers
are not available, no matter what the pointcut language allows you to write.
## It borrows the pointcut language
`aspectjweaver` on the classpath supplies the pointcut *parser*. Spring uses it to decide which
methods match and then does its own weaving with proxies. AspectJ's own weaver is not involved.
This is why the designator list is a subset: the language can express `call()` and `cflow()`,
and a proxy cannot implement them. See [chapter 2](02-designators.md).
## The Spring Boot 4 starter rename
In Spring Boot 3 the dependency was `spring-boot-starter-aop`. In Spring Boot 4 it is
**`spring-boot-starter-aspectj`**.
```
spring-boot-starter-aop last published 4.0.0-M2 (last GA: 3.5.16)
spring-boot-starter-aspectj first published 4.0.0-M3
```
The contents are unchanged: `spring-boot-starter`, `spring-aop`, `aspectjweaver`. But the old
artifact is no longer in the Boot BOM, so copying a dependency block out of any pre-4 tutorial
fails resolution with a missing-version error rather than a helpful message.
Without the starter, `@Aspect` classes are ordinary beans, no pointcut is ever parsed, and
every aspect in the application silently matches nothing.
## When to use something else
If you need to advise field access, constructors, or calls between objects you do not own, you
need real AspectJ weaving (compile-time or load-time), not Spring AOP. If you need to advise
one internal call, you need to refactor — see [chapter 5](05-broken-aspect-gallery.md).

View File

@@ -0,0 +1,86 @@
[&larr; What Spring AOP is](01-what-spring-aop-is.md) &middot; [Index](../README.md) &middot; [Proxy types &rarr;](03-proxy-types.md)
# 2. The designator reference
Generated by [`DesignatorAspect`](../src/main/java/com/ankurm/aop/aspect/DesignatorAspect.java)
and [`PointcutParserEndpoint`](../src/main/java/com/ankurm/aop/web/PointcutParserEndpoint.java).
Transcript: [`01-designators.txt`](output/01-designators.txt).
## Supported
| Designator | Matches on | Evaluated | Cost |
|---|---|---|---|
| `execution(...)` | method signature | statically | cheap |
| `within(Type)` | the declaring type | statically | cheap |
| `this(Type)` | the **proxy**'s type | at runtime | per call |
| `target(Type)` | the **target object**'s type | at runtime | per call |
| `args(Types)` | argument runtime types; can bind | at runtime | per call |
| `@target(Ann)` | annotation on the executing object's class | at runtime | per call |
| `@args(Ann)` | annotation on argument runtime types | at runtime | per call |
| `@within(Ann)` | annotation on the declaring type | statically | cheap |
| `@annotation(Ann)` | annotation on the method | statically | cheap |
| `bean(name)` | Spring bean name, wildcards allowed | — | cheap |
`bean(...)` is Spring's own; it does not exist in AspectJ.
The runtime group cannot be decided from the signature alone, so Spring must check on every
candidate invocation. Prefer the static equivalent where one exists: `@within` instead of
`@target`, `within` instead of `this`, when the distinction does not matter.
## What each one actually matched here
```
execution (full signature) -> DefaultOrderService.place(..)
execution (wildcards) -> DefaultOrderService.cancel(..)
within -> InventoryService.reserve(..)
this(OrderService) -> place, cancel, interfaceless
target(DefaultOrderService) -> place, cancel, interfaceless
args (bound: SKU-1/2) -> DefaultOrderService.place(..)
@target(Audited) -> DefaultOrderService.cancel(..)
@args(Trackable) -> DefaultOrderService.interfaceless(..)
@within(Audited) -> DefaultOrderService.place(..)
@annotation(Marker) -> DefaultOrderService.place(..)
bean(inventoryService) -> InventoryService.reserve(..)
bean(*OrderService) -> place, cancel, interfaceless
```
`InventoryService.finalCheck` was called and appears nowhere: it is `final`, so no proxy could
override it. That is [failure 4](05-broken-aspect-gallery.md).
## Not supported
`call`, `get`, `set`, `preinitialization`, `staticinitialization`, `initialization`, `handler`,
`adviceexecution`, `withincode`, `cflow`, `cflowbelow`, `if`, `@this`, `@withincode`.
All fourteen were fed to the parser. All fourteen were rejected, with:
```
org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException
Pointcut expression 'call(* ...place(..))' contains unsupported pointcut primitive 'call'
```
**The reference documentation says these throw `IllegalArgumentException`. They do not.**
`UnsupportedPointcutPrimitiveException extends RuntimeException` directly — checked with
`javap`, pinned by `AopContractTests.unsupportedDesignatorExceptionType`. A
`catch (IllegalArgumentException)` will not catch it.
## When the failure happens
`setExpression(...)` only stores the string. The expression is not parsed or validated until
something asks it to match. An unsupported designator therefore fails at the first candidate
invocation, not at startup — so a rarely-exercised aspect can ship broken.
## Combining and naming
`&&`, `||` and `!` compose designators. Name the result rather than repeating it:
```java
@Pointcut("within(com.ankurm.aop.service..*)")
public void inServiceLayer() {}
@Before("this(OrderService) && inServiceLayer()")
public void advice(JoinPoint jp) { }
```
A named pointcut is referenced by its method name and is the single biggest readability win
available here.

View File

@@ -0,0 +1,78 @@
[&larr; Designators](02-designators.md) &middot; [Index](../README.md) &middot; [Advice types &rarr;](04-advice-types.md)
# 3. JDK dynamic proxies and CGLIB
Transcript: [`02-proxy-types.txt`](output/02-proxy-types.txt).
| | JDK dynamic proxy | CGLIB |
|---|---|---|
| Built by | `java.lang.reflect.Proxy` | subclassing the target |
| Requires | at least one interface | a non-final class |
| Proxy is an instance of | the interfaces only | the target class |
| Can advise | public interface methods | public, protected, package-private |
| Cannot advise | anything not on the interface | `final`, `private`, `static` |
| Class name | `$Proxy62` | `Foo$$SpringCGLIB$$0` |
## Spring Boot chooses CGLIB
The Spring Framework's own default is interface-based when an interface exists. **Spring Boot
sets `spring.aop.proxy-target-class=true`**, so you get CGLIB either way unless you change it.
Measured, on a bean that *does* implement an interface:
```
defaultOrderService CGLIB subclass class: DefaultOrderService$$SpringCGLIB$$0
interfaces : (none)
proxy is an instance of DefaultOrderService : True
```
And with `--spring.aop.proxy-target-class=false`:
```
defaultOrderService JDK dynamic proxy class: $Proxy62
interfaces : OrderService
proxy is an instance of DefaultOrderService : False
```
## What changes when you switch
Same aspects, same beans:
```
CGLIB: this(OrderService) -> place, cancel, interfaceless
JDK: this(OrderService) -> place, cancel
```
`interfaceless(..)` is not on `OrderService`, so under a JDK proxy it is invisible to advice —
and casting the bean to `DefaultOrderService` throws `ClassCastException`.
This is why `this()` and `target()` exist as separate designators. `this()` tests the proxy;
`target()` tests the object behind it. Under CGLIB they nearly always agree, which is exactly
why the distinction only ever bites *after* somebody changes the proxy type.
## CGLIB constraints worth knowing
- A **final class** cannot be proxied at all — this one fails loudly.
- A **final method** is silently not advised. The bean is still a proxy; the method just is not
overridden. This is the quiet one.
- **Private** methods are never advised by either strategy.
- The target's constructor is **not** called twice: Spring creates the proxy instance through
Objenesis. On a JVM that forbids constructor bypassing you may see double invocation and a
debug log line about it.
- On the module path, classes in `java.lang` cannot be proxied without
`--add-opens=java.base/java.lang=ALL-UNNAMED`.
## `@Proxyable`, new in Spring 7.0
Per-bean control, rather than one global switch:
```java
@Proxyable(ProxyType.INTERFACES)
@Bean
MyService myService() { ... }
```
Verified against the class file rather than the docs:
`org.springframework.context.annotation.Proxyable`, targets `TYPE` and `METHOD`, with
`ProxyType value()` (`DEFAULT`, `INTERFACES`, `TARGET_CLASS`) and `Class<?>[] interfaces()`.
Note the package — it is in `spring-context`, not `spring-aop`.

View File

@@ -0,0 +1,56 @@
[&larr; Proxy types](03-proxy-types.md) &middot; [Index](../README.md) &middot; [Broken aspect gallery &rarr;](05-broken-aspect-gallery.md)
# 4. Advice types and ordering
## The five
| Annotation | Runs | Can it stop the call? | Can it change the result? |
|---|---|---|---|
| `@Before` | before | only by throwing | no |
| `@AfterReturning` | after a normal return | no | no (can read it) |
| `@AfterThrowing` | after an exception | no | no (can read it) |
| `@After` | after either | no | no |
| `@Around` | wraps | yes | yes |
`@Around` is the only one that receives a `ProceedingJoinPoint` and therefore the only one that
can decide whether the target runs at all, retry it, cache around it, or replace its result.
```java
@Around("@annotation(Timed)")
public Object time(ProceedingJoinPoint pjp) throws Throwable {
long start = System.nanoTime();
try {
return pjp.proceed();
} finally {
record(pjp.getSignature(), System.nanoTime() - start);
}
}
```
Two rules for `@Around`: it must return `Object` (or a compatible type) and it must actually
call `proceed()`. Forgetting `proceed()` silently turns every advised method into a method that
returns `null` and never runs — a failure that looks like the target method being broken.
## Ordering
Within one aspect, advice order for the same join point is **not** guaranteed and is not the
source order. If two pieces of advice in one aspect must be ordered, split them into two
aspects.
Between aspects, `@Order` or `Ordered` decides. Lower value = higher precedence = outermost.
Advice nests: the outermost aspect's `@Before` runs first and its `@After` runs last.
```
@Order(1) Tx : before ... [ @Order(2) Logging : before ... target ... after ] ... after
```
This matters when combining transactions with anything that must be inside or outside them.
`@Transactional` is ordered at `Ordered.LOWEST_PRECEDENCE` by default, so almost everything
else wraps *outside* it — meaning your logging advice sees the method return before the
transaction commits, and therefore before a commit failure has happened.
## `ExposeInvocationInterceptor`
You will see this at the head of every advisor chain in
[`02-proxy-types.txt`](output/02-proxy-types.txt). Spring adds it automatically so that
`AopContext.currentProxy()` and argument binding work. It is not something you configured.

View File

@@ -0,0 +1,84 @@
[&larr; Advice types](04-advice-types.md) &middot; [Index](../README.md) &middot; [Diagnosing &rarr;](06-diagnosing-a-silent-aspect.md)
# 5. Six aspects that do not fire
Sources: [`BrokenAspects`](../src/main/java/com/ankurm/aop/broken/BrokenAspects.java),
[`SelfInvokingService`](../src/main/java/com/ankurm/aop/broken/SelfInvokingService.java),
[`NewedUpService`](../src/main/java/com/ankurm/aop/broken/NewedUpService.java).
Transcript: [`03-broken-gallery.txt`](output/03-broken-gallery.txt).
None of these warn. None fail at startup. All of them look correct in review.
## 1. `@Aspect` without `@Component`
`@Aspect` is an AspectJ annotation. It tells Spring how to interpret a bean it already has; it
does not create one. Without a stereotype or an `@Bean` method, the class is never instantiated
and the pointcut is never registered.
The most common cause, and the most invisible — from the container's point of view nothing was
ever requested, so there is nothing to warn about.
**Fix:** add `@Component`.
## 2. A pointcut that matches nothing
```java
@Before("execution(* com.ankurm.aop.services.*.*(..))") // "services", plural
```
A package name matching no type is not an error; it is an empty match set. At runtime this is
indistinguishable from an aspect that was never registered.
**Fix:** assert on it. `AspectJExpressionPointcut#matches(Method, Class)` in a unit test is two
lines and catches every typo permanently.
## 3. A private method
Neither proxy strategy can override a private method, so neither can intercept it. The
annotation is legal and inert.
**Fix:** make it at least package-private *and* call it from outside the object — visibility
alone is not enough if the call is internal, which brings you to number 5.
## 4. A final method
CGLIB proxies by subclassing. A final method is inherited rather than overridden, so calls go
straight to the original. A final *class* fails loudly; a final *method* is silent.
Note `beanIsProxied: true` in the transcript. The bean is proxied. This one method is not.
**Fix:** remove `final`, or proxy by interface.
## 5. Self-invocation
The expensive one, because the code looks right and the annotation is visible.
```json
"5-self-invocation": {
"beanIsProxied": true,
"innerAdvisedWhenCalledFromOuter": false,
"innerAdvisedWhenCalledDirectly": true
}
```
Same method, same advice. Called through the proxy it is advised; reached by `this.inner()`
from another method of the same object it is not, because the proxy is not in that call path.
This is the same mechanism that makes `@Transactional` and `@Cacheable` silently do nothing on
internal calls. Learning it once here saves learning it three times.
**Fixes, best first:**
1. Move the method to another bean. This is almost always the right answer, and the resulting
design is usually better anyway.
2. Inject the bean into itself and call through that reference.
3. `AopContext.currentProxy()` with `exposeProxy = true`. Works; couples your code to Spring
AOP and makes the class aware it is proxied.
## 6. An object created with `new`
Spring AOP advises beans. An instance built by a factory, a helper or a test has no proxy and
never will.
**Fix:** get it from the container. If it genuinely must be constructed by hand and still
advised, that is what AspectJ load-time weaving is for.

View File

@@ -0,0 +1,75 @@
[&larr; Broken aspect gallery](05-broken-aspect-gallery.md) &middot; [Index](../README.md)
# 6. Diagnosing a silent aspect
Endpoint: [`AopDiagnosticsEndpoint`](../src/main/java/com/ankurm/aop/web/AopDiagnosticsEndpoint.java).
Almost every non-firing aspect is one of three things, and all three are visible in one place.
## The three questions, in order
**1. Is the bean proxied at all?**
```java
AopUtils.isAopProxy(bean)
```
`false` means no pointcut matched this bean, or it is not a bean. Stop here and check the
pointcut and the registration.
**2. Which kind of proxy?**
```java
AopUtils.isJdkDynamicProxy(bean) // interfaces only
AopUtils.isCglibProxy(bean) // subclass
```
If it is a JDK proxy and your method is not on an interface, that is your answer.
**3. Is your advice in the advisor list?**
```java
if (bean instanceof Advised advised) {
Arrays.stream(advised.getAdvisors())
.map(a -> a.getAdvice().getClass().getSimpleName())
.forEach(System.out::println);
}
```
Proxied, right kind, and your advice missing means the pointcut matched the *bean* but not the
*method*.
## Output
```
defaultOrderService CGLIB subclass target=DefaultOrderService
class : DefaultOrderService$$SpringCGLIB$$0
interfaces : (none)
advisors : 11
```
## Testing a pointcut without an application
The fastest check of all, and it belongs in a test rather than in a diagnostic endpoint:
```java
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.service.*.*(..))");
assertThat(pointcut.matches(Foo.class.getMethod("bar"), Foo.class)).isTrue();
```
Remember that `setExpression` does not parse; the parse happens on first `matches`. So this is
also how you find out that a designator is unsupported before production does.
## Logging
```
logging.level.org.springframework.aop=DEBUG
```
reports proxy creation per bean. Verbose, but it answers question 1 for every bean at once.
## Delete the endpoint before shipping
It reports internal wiring. If you want it permanently, put it behind the management port and
authentication.

View File

@@ -0,0 +1,11 @@
== versions ==
openjdk version "25.0.4.1" 2026-08-18 LTS
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)
spring-boot-starter-parent: 4.1.1
aspectjweaver: 1.9.25.1
== the Boot 4 starter rename ==
spring-boot-starter-aop last published: 4.0.0-M2 (last GA 3.5.16)
spring-boot-starter-aspectj first published: 4.0.0-M3

View File

@@ -0,0 +1,59 @@
== which designator matched which join point ==
Five methods are called once each: OrderService.place, OrderService.cancel,
InventoryService.reserve, InventoryService.finalCheck and
DefaultOrderService.interfaceless.
orderService proxy kind : CGLIB subclass
proxy is an instance of DefaultOrderService : True
args (bound: SKU-1/2) -> DefaultOrderService.place(..)
bean(*OrderService) -> DefaultOrderService.place(..), DefaultOrderService.cancel(..), DefaultOrderService.interfaceless(..)
@within(Audited) -> DefaultOrderService.place(..)
execution (full signature) -> DefaultOrderService.place(..)
@annotation(Marker) -> DefaultOrderService.place(..)
target(DefaultOrderService) -> DefaultOrderService.place(..), DefaultOrderService.cancel(..), DefaultOrderService.interfaceless(..)
this(OrderService) -> DefaultOrderService.place(..), DefaultOrderService.cancel(..), DefaultOrderService.interfaceless(..)
execution (wildcards) -> DefaultOrderService.cancel(..)
@target(Audited) -> DefaultOrderService.cancel(..)
bean(inventoryService) -> InventoryService.reserve(..)
within -> InventoryService.reserve(..)
@args(Trackable) -> DefaultOrderService.interfaceless(..)
== what the parser accepts and refuses ==
supported (all parsed and evaluated):
execution(* com.ankurm.aop.service.OrderService.place(..)) OK
within(com.ankurm.aop.service..*) OK
this(com.ankurm.aop.service.OrderService) OK
target(com.ankurm.aop.service.DefaultOrderService) OK
args(String, int) OK
@target(com.ankurm.aop.service.Audited) OK
@args(com.ankurm.aop.service.Trackable) OK
@within(com.ankurm.aop.service.Audited) OK
@annotation(com.ankurm.aop.service.Marker) OK
bean(defaultOrderService) OK
unsupported in Spring AOP:
call(* com.ankurm.aop.service.OrderService.place(. rejected
get(* com.ankurm.aop.service.*.*) rejected
set(* com.ankurm.aop.service.*.*) rejected
initialization(com.ankurm.aop.service.*.new(..)) rejected
staticinitialization(com.ankurm.aop.service.*) rejected
preinitialization(com.ankurm.aop.service.*.new(..) rejected
handler(java.lang.Exception) rejected
adviceexecution() rejected
withincode(* com.ankurm.aop.service.*.*(..)) rejected
cflow(execution(* com.ankurm.aop.service.*.*(..))) rejected
cflowbelow(execution(* com.ankurm.aop.service.*.*( rejected
if() rejected
@this(com.ankurm.aop.service.Audited) rejected
@withincode(com.ankurm.aop.service.Marker) rejected
the exception, in full:
org.aspectj.weaver.tools.UnsupportedPointcutPrimitiveException
Pointcut expression 'call(* com.ankurm.aop.service.OrderService.place(..))' contains unsupported pointcut primitive 'call'
The reference documentation says these produce an IllegalArgumentException. They do
not: UnsupportedPointcutPrimitiveException extends RuntimeException directly, so a
catch of IllegalArgumentException will not catch it.

View File

@@ -0,0 +1,53 @@
== Spring Boot default: spring.aop.proxy-target-class=true ==
$ java -jar target/spring-aop-demo-1.0.0.jar
defaultOrderService CGLIB subclass target=DefaultOrderService
class : DefaultOrderService$$SpringCGLIB$$0
interfaces : (none)
advisors : 11
inventoryService CGLIB subclass target=InventoryService
class : InventoryService$$SpringCGLIB$$0
interfaces : (none)
advisors : 6
selfInvokingService CGLIB subclass target=SelfInvokingService
class : SelfInvokingService$$SpringCGLIB$$0
interfaces : (none)
advisors : 2
proxy is an instance of DefaultOrderService : True
this(OrderService) -> DefaultOrderService.place(..), DefaultOrderService.cancel(..), DefaultOrderService.interfaceless(..)
target(DefaultOrderService) -> DefaultOrderService.place(..), DefaultOrderService.cancel(..), DefaultOrderService.interfaceless(..)
bean(*OrderService) -> DefaultOrderService.place(..), DefaultOrderService.cancel(..), DefaultOrderService.interfaceless(..)
== framework default restored: spring.aop.proxy-target-class=false ==
$ java -jar target/spring-aop-demo-1.0.0.jar --spring.aop.proxy-target-class=false
defaultOrderService JDK dynamic proxy target=DefaultOrderService
class : $Proxy62
interfaces : OrderService
advisors : 11
inventoryService CGLIB subclass target=InventoryService
class : InventoryService$$SpringCGLIB$$0
interfaces : (none)
advisors : 6
selfInvokingService CGLIB subclass target=SelfInvokingService
class : SelfInvokingService$$SpringCGLIB$$0
interfaces : (none)
advisors : 2
proxy is an instance of DefaultOrderService : False
this(OrderService) -> OrderService.place(..), OrderService.cancel(..)
target(DefaultOrderService) -> OrderService.place(..), OrderService.cancel(..)
bean(*OrderService) -> OrderService.place(..), OrderService.cancel(..)
Same aspects, same beans, different proxy strategy:
- With CGLIB the proxy is a SUBCLASS of DefaultOrderService, so it is an instance of
the implementation class and methods that are not on the interface are advised.
- With a JDK proxy the proxy implements OrderService only. It is NOT an instance of
DefaultOrderService, casting to that class throws ClassCastException, and any
method absent from the interface is invisible to advice.
This is why this() and target() differ. this() tests the proxy; target() tests the
object behind it. Under CGLIB they usually agree, which is exactly why the
distinction only bites after somebody switches the proxy type.

View File

@@ -0,0 +1,37 @@
== aspects that do not fire ==
{
"5-self-invocation": {
"beanIsProxied": true,
"result": "outer -> inner",
"innerAdvisedWhenCalledFromOuter": false,
"innerAdvisedWhenCalledDirectly": true
},
"6-created-with-new": {
"isProxy": false,
"adviceFired": false
},
"4-final-method": {
"beanIsProxied": true,
"proxyKind": "CGLIB subclass",
"adviceFired": false
},
"1-aspect-without-component-fired": false,
"2-pointcut-typo-fired": false,
"3-private-method-fired": false,
"note": "every value above should be false except the two that prove the method is advisable when reached through the proxy"
}
Reading it:
1 @Aspect without @Component - the class is never instantiated, so the pointcut
is never registered. No warning is produced.
2 pointcut typo - 'com.ankurm.aop.services' (plural) parses fine and
matches nothing. An empty match set is not an error.
3 private method - cannot be overridden, so cannot be intercepted.
4 final method - CGLIB subclasses; a final method is inherited, not
overridden. Note beanIsProxied is still true.
5 self-invocation - innerAdvisedWhenCalledFromOuter is false and
innerAdvisedWhenCalledDirectly is true. Same method,
same advice: only the call path differs.
6 created with new - no container, no proxy, no advice.