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
3.3 KiB
← Advice types · Index · Diagnosing →
5. Six aspects that do not fire
Sources: BrokenAspects,
SelfInvokingService,
NewedUpService.
Transcript: 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
@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.
"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:
- Move the method to another bean. This is almost always the right answer, and the resulting design is usually better anyway.
- Inject the bean into itself and call through that reference.
AopContext.currentProxy()withexposeProxy = 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.