1
0
Files
spring-boot-demo/spring-aop/docs/05-broken-aspect-gallery.md
Ankur Mhatre 86246dc860 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
2026-09-08 16:47:48 +00:00

85 lines
3.3 KiB
Markdown

[&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.