You need to audit every write to a specific field across domain objects, including objects created with `new`. How do you architect this given Spring AOP's limitations?
answer
- Two walls: field set() + non-bean new()
- Full AspectJ weaving, not proxy
- CTW (ajc) vs LTW (spring-instrument + aop.xml)
- @Configurable injects new'd objects
- Consider JPA listener/Envers instead
basics
~10 sSpring AOP can't do it — it only advises bean method executions, not field writes or non-Spring objects. Use full AspectJ with a set() pointcut, woven at compile-time (ajc) or load-time (spring-instrument agent).
solid answer
~40 sThis requirement hits two Spring AOP walls at once: (1) it needs a **field `set()` join point**, which proxies can't intercept, and (2) the objects are created with `new`, so they're **not Spring beans** and never get a proxy. The answer is **full AspectJ weaving**, which rewrites bytecode instead of wrapping objects. Options: **compile-time weaving** with the AspectJ compiler (`ajc` / AspectJ Maven/Gradle plugin) for the cleanest runtime, or **load-time weaving** via the `spring-instrument` Java agent enabled with `@EnableLoadTimeWeaving` and an `aop.xml`. Write an `@Aspect` with `@Before("set(* com.example.domain..*.status)")`. Spring can still manage the aspect bean and inject collaborators via `@Configurable`/`aspectOf`. Trade-offs: extra build/agent complexity, wider interception surface (performance/audit scope), and team familiarity — so scope the pointcut tightly and document the weaving setup.
code
java · 16 lines// Full AspectJ aspect (woven at compile-time via ajc or load-time via spring-instrument).
// Cannot be done with proxy-based Spring AOP: field write + non-Spring objects.
@Aspect
public class StatusAuditAspect {
@Before("set(* com.example.domain..*.status) && args(newValue)")
public void onStatusWrite(JoinPoint jp, Object newValue) {
AuditLog.record(jp.getSignature().toShortString(), newValue);
}
}
// Enable load-time weaving in a Spring config (needs -javaagent:spring-instrument.jar):
@Configuration
@EnableLoadTimeWeaving
class WeavingConfig { }
// plus META-INF/aop.xml declaring the aspect and weave scope.go deeper
Just recognize Spring AOP can't intercept fields or non-beans; AspectJ is needed.
Name AspectJ weaving as the mechanism and know CTW vs LTW exist.
Detail the weaving setup (spring-instrument, aop.xml, @EnableLoadTimeWeaving) and @Configurable for new'd objects.
Weigh trade-offs (perf, build/deploy complexity, team cognition) and propose simpler alternatives like JPA listeners when appropriate.
## Why Spring AOP is disqualified Two independent limitations apply: 1. **Field-write join point.** Auditing 'every write to field `status`' is a `set()` join point. Proxy-based Spring AOP intercepts only **method executions** — there is no proxy hook for field access. Even a setter method (`setStatus`) wouldn't help if code assigns the field directly, and it never helps for non-bean objects. 2. **Objects created with `new`.** Domain objects, JPA entities, and value objects are typically instantiated directly, not by the Spring container, so they never receive a proxy. Spring AOP can only advise **Spring-managed beans**. This is the canonical **granularity gap** that pushes teams from Spring AOP to AspectJ. ## The solution: full AspectJ weaving AspectJ modifies **bytecode**, so it can advise fields, constructors, statics, `final`, and objects created with `new` — regardless of Spring management. Two weaving models: ### Compile-time weaving (CTW) - The AspectJ compiler **`ajc`** (via the AspectJ Maven plugin or the Freefair AspectJ Gradle plugin) weaves aspects into `.class` files at build time. - Pros: no runtime agent, best startup/runtime performance, deterministic. - Cons: your build must run `ajc`; weaving third-party jars needs binary weaving config. ### Load-time weaving (LTW) - A **Java agent** instruments classes as they're loaded. Spring ships **`spring-instrument`** (`-javaagent:spring-instrument.jar`) and you enable it with **`@EnableLoadTimeWeaving`** (or `<context:load-time-weaver/>`) plus a **`META-INF/aop.xml`** listing aspects and weave scope. - Pros: no special compiler; works on classes from the classpath. - Cons: agent on the JVM command line, slower class loading, more moving parts. ## Sketch ```java @Aspect public class StatusAuditAspect { @Before("set(* com.example.domain..*.status) && args(newValue)") public void onStatusWrite(JoinPoint jp, Object newValue) { AuditLog.record(jp.getSignature(), newValue); } } ``` `aop.xml` (LTW): ```xml <aspectj> <weaver options="-Xset:weaveJavaxPackages=false"> <include within="com.example.domain..*"/> </weaver> <aspects><aspect name="com.example.aspect.StatusAuditAspect"/></aspects> </aspectj> ``` ## Integrating with Spring - You can still let Spring manage the aspect and inject dependencies: AspectJ aspects are singletons obtained via `Aspects.aspectOf(...)`, and Spring can autowire them (`factory-method="aspectOf"` or `@Configurable`). - **`@Configurable`** additionally lets Spring dependency-inject objects created with `new` (using AspectJ's `AnnotationBeanConfigurerAspect`) — useful when domain objects need injected collaborators. - For transactions specifically, `@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)` switches to the AspectJ-woven transaction aspect (also fixes self-invocation). ## Architectural trade-offs to weigh (principal lens) - **Interception surface & performance:** field-write weaving can match a *lot* of join points — scope the pointcut to the exact type/field, and consider CTW for hot paths. - **Build/deploy complexity:** CTW couples you to `ajc`; LTW couples you to an agent flag in every environment (local, CI, container, prod). Document it and bake it into the container image / run config. - **Team cognition:** AspectJ is powerful but opaque; debugging woven code and 'why did this run' is harder. Prefer it only where the requirement genuinely exceeds proxy AOP. - **Alternatives first:** if you can funnel all writes through a single mutator method on a Spring bean, plain Spring AOP suffices — reserve AspectJ for true field/constructor/non-bean needs. - **Auditing note:** for entity state changes specifically, a JPA/Hibernate listener (`@PrePersist`/`@PreUpdate`, Envers, or `Interceptor`) may be a simpler, better-scoped tool than field weaving — choose the mechanism that matches the domain. ## Bottom line Field-level, `new`-object auditing is exactly what proxy AOP **cannot** do; full AspectJ (CTW preferred, LTW when a compiler step isn't viable) is the mechanism, applied with a tight pointcut and clear operational docs — or, for entities, a JPA-level listener instead.
- Could you avoid AspectJ entirely for this?Sometimes. If every write goes through a single setter on a Spring bean, plain Spring AOP on that method works. For entity state changes, a JPA callback (@PreUpdate), Hibernate Interceptor, or Envers is often simpler and better-scoped than field weaving. Reserve AspectJ for genuine field/constructor/non-bean interception.
- What's the operational cost difference between CTW and LTW?CTW pays the cost once at build (needs ajc in the build), giving fast, deterministic runtime. LTW needs a -javaagent flag present in every runtime environment and slows class loading, but requires no special compiler. In containers, CTW keeps the runtime image simpler.
saying these in an interview costs you the question
- Proposing a CGLIB proxy or @EnableAspectJAutoProxy to intercept field writes
- Assuming @Aspect + Spring alone can advise objects created with new
- Ignoring that field-write weaving can explode the interception surface and hurt performance