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

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:

  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.