skip to content

Method-Execution-Only Join Points

Spring AOP intercepts only method executions on Spring beans: no field access, no constructors, no call sites. That granularity gap is the standard justification for moving to AspectJ weaving.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

What kinds of join points can proxy-based Spring AOP advise, and what can't it?

level: juniorimportance: must knowfreq 62%

answer

  1. Only method execution join points
  2. No field / constructor / static / private
  3. Proxy wraps, can't hook new()
  4. Self-invocation bypasses proxy
  5. Need more → AspectJ weaving

basics

~10 s

Spring AOP only intercepts method executions on Spring beans. It cannot advise field reads/writes, constructors, static methods, or private methods — because it works by wrapping the bean in a proxy object.

solid answer

~40 s

Proxy-based Spring AOP supports exactly one join point type: the execution of a public (or protected) instance method on a Spring-managed bean. Because interception happens in a proxy that wraps the target, there is no hook for field access (get/set), object construction (constructors), static methods, or private/final methods — the proxy can only stand in front of overridable, externally-invoked methods. AspectJ's pointcut language technically has `execution`, `call`, `get`, `set`, `initialization`, etc., but Spring implements only the equivalent of method `execution`. If you need those other join points you must switch to full AspectJ (compile-time or load-time weaving). This is why the Spring reference calls its AOP 'method interception'.

code

java · 19 lines
java
@Aspect
@Component
public class AuditAspect {

    // WORKS: advises the *execution* of public methods on Spring beans.
    @Before("execution(* com.example.service.*.*(..))")
    public void beforeServiceCall(JoinPoint jp) {
        System.out.println("calling " + jp.getSignature());
    }

    // DOES NOT WORK in Spring AOP: field 'set' join point.
    // Spring rejects unsupported pointcut primitives at startup.
    // @Before("set(* com.example..*)")   // UnsupportedPointcutPrimitiveException
    // public void beforeFieldWrite() { }

    // DOES NOT WORK in Spring AOP: constructor 'initialization' join point.
    // @After("initialization(com.example..*.new(..))")  // not supported
    // public void afterConstruct() { }
}

go deeper

for a junior

Know the one-liner: Spring AOP only intercepts method executions on Spring beans; not fields, constructors, or statics.

for a middle

Explain WHY via proxy mechanics and list what's excluded (private/final/static/self-invocation).

for a senior

Contrast with AspectJ's full pointcut designators and know Spring rejects unsupported primitives at startup.

for a principal

Frame the trade-off: proxy simplicity vs. AspectJ weaving's reach, and its impact on transaction/cache annotation design.

## Background: what a 'join point' is In Aspect-Oriented Programming (AOP), a **join point** is a point in the running program where cross-cutting behavior (an **aspect**) could be inserted — e.g. 'when a method runs', 'when a field is read', 'when an object is constructed'. A **pointcut** is a predicate that selects a subset of join points; an **advice** is the code (like `@Before`, `@Around`) that runs at matched join points. ## Spring AOP supports only method-execution join points Spring AOP is **proxy-based**. When a bean has an applicable aspect, the container does not give you the raw object — it gives you a **proxy** that wraps it (a JDK dynamic proxy if the bean implements an interface, or a CGLIB subclass proxy otherwise). Calls go proxy → advice chain → real target method. Because interception lives in that proxy wrapper, the **only** thing it can intercept is a **method execution** on the target bean. Concretely, Spring AOP **cannot** advise: - **Field access** — reading or writing a field (`this.count`, `obj.name = x`). There is no proxy hook for field get/set. - **Constructors** — object creation join points (`new Foo()`). The proxy exists only after construction; it can't wrap `new`. - **Static methods** — no instance, nothing to proxy. - **`private` methods** — not visible to a subclass/interface proxy, so never intercepted. - **`final` methods / `final` classes** — CGLIB can't override `final`, so they slip through. - **Self-invocation** — when a bean calls its own method via `this.other()`, the call bypasses the proxy entirely, so no advice runs (a very common gotcha). ## Why: proxy mechanics A JDK proxy implements the same interfaces and delegates each interface method to an `InvocationHandler`. A CGLIB proxy subclasses the target and overrides its methods. In **both** cases the interception is 'run some code around this overridable, externally-dispatched method call'. Fields, constructors, statics, and private/final members are simply not overridable dispatch points, so there is nothing to wrap. ## The granularity gap → AspectJ The full **AspectJ** language defines many join-point kinds via pointcut designators: `execution`, `call` (the call *site*, in the caller), `get`/`set` (field access), `initialization`/`preinitialization` (constructors), `staticinitialization`, `handler` (exception handlers), etc. Spring's `@Aspect` support reuses AspectJ's **annotations and pointcut expression syntax** but implements **only** the runtime equivalent of `execution` — Spring will actually reject a Spring-AOP pointcut that uses unsupported designators like `call`, `get`, `set`, or `initialization` with an `IllegalArgumentException`/`UnsupportedPointcutPrimitiveException`. When this method-only granularity isn't enough — e.g. you must trace every field write, intercept constructors, or weave into non-Spring objects like JPA entities or domain objects created with `new` — you move to **full AspectJ weaving**: - **Compile-time weaving (CTW)** via the AspectJ compiler (`ajc`). - **Load-time weaving (LTW)** via a Java agent (`spring-instrument` / `-javaagent`), enabled with `@EnableLoadTimeWeaving` or `<context:load-time-weaver/>`. AspectJ modifies bytecode rather than using proxies, so it can reach fields, constructors, `final`, `private`, and self-invocation — at the cost of a build/agent step and more complexity. ## When to use which - Stick with **Spring AOP** for the 95% case: transactions (`@Transactional`), security, caching (`@Cacheable`), logging around service/repository **public method calls between beans**. Simple, no extra build step. - Reach for **AspectJ** only when you genuinely need non-method join points, must advise objects Spring doesn't create, or must catch self-invocation. ## Key gotcha to remember `@Transactional` and `@Cacheable` are Spring AOP under the hood, so all the same limitations apply: annotate `public` methods, and don't rely on **self-invocation** — an internal `this.method()` call won't trigger the aspect.

  • Why can't Spring AOP intercept a private method?
    The proxy either implements the target's interfaces (JDK) or subclasses it (CGLIB). Private methods aren't part of an interface and can't be overridden by a subclass, so the proxy never sees the call — it dispatches straight on the target instance.
  • If I need to advise constructors or field writes, what do I use?
    Full AspectJ weaving — compile-time weaving with ajc, or load-time weaving via the spring-instrument Java agent and @EnableLoadTimeWeaving. AspectJ rewrites bytecode instead of using proxies, so it can hit constructors, fields, statics, and self-invocation.

saying these in an interview costs you the question

  • Claiming Spring AOP can intercept field access or constructors out of the box
  • Saying private methods can be advised if the pointcut matches them
  • Thinking @EnableAspectJAutoProxy gives full AspectJ join-point coverage (it only enables the proxy-based method interception)

context

open as a page

Why does an internal self-invoked call to a @Transactional method sometimes run without a transaction?

level: middleimportance: must knowfreq 70%

basics

~10 s

Because @Transactional is applied by the proxy. When a bean calls its own method with this.method(), the call goes straight to the target and skips the proxy, so the transactional advice never runs.

open as a page

Why do final classes, final/private methods, and static methods escape Spring AOP advice?

level: middleimportance: should knowfreq 40%

basics

~20 s

Spring AOP advises via a proxy that either implements the bean's interfaces (JDK) or subclasses it (CGLIB). Final classes/methods can't be subclassed/overridden, private methods aren't overridable/on interfaces, and static methods have no instance — so none get intercepted.

open as a page

AspectJ distinguishes execution() from call() and offers get()/set()/initialization(). Which of these does Spring AOP actually implement, and why?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Spring AOP implements only method execution. It does not support call, get/set (fields), initialization (constructors), staticinitialization, or handler, because a runtime proxy can only intercept a method being executed on the wrapped bean.

open as a page

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?

level: principalimportance: should knowfreq 30%

basics

~10 s

Spring 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).

open as a page