skip to content

How do you bind the target object (and arguments) into advice using target()/this()/args(), and what are the semantics of the bound reference?

level: seniorimportance: should knowfreq 30%

answer

  1. variable name = binding form
  2. advice param type = instanceof test
  3. this→proxy (re-enters advice), target→raw (bypasses)
  4. argNames / -parameters for name mapping
  5. combine with execution() to narrow

basics

~20 s

Instead of a type, name a variable in the PCD (e.g. target(svc)) and declare a matching parameter in the advice method; Spring passes the object in. this() binds the proxy, target() binds the real object, args() binds arguments. The parameter's declared type supplies the instanceof check.

solid answer

~40 s

this(), target() and args() have two forms: a **type form** (used only for matching) and a **binding form** where you write a lowercase variable name that must correspond to a parameter of the advice method. Spring uses the advice parameter's declared type as the type to test, and injects the matched object/argument. target(svc) binds the raw target bean; this(proxy) binds the AOP proxy (calls through it still go through advice); args(id) binds a method argument by position/type. You often combine binding with argNames or rely on parameter-name discovery. Semantics matter: a target()-bound reference lets you call the real object directly, bypassing the proxy (so no further advice, no self-invocation interception); a this()-bound reference is the proxy, so re-invoking it re-enters the advice chain. Binding is common for auditing, multi-tenant, and validation aspects.

code

java · 15 lines
java
public interface Auditable { Long ownerId(); }

@Aspect @Component
class AuditAspect {

    // Bind the target (typed as the marker interface) AND the first Long arg.
    @Around("execution(* com.example..*(..)) && target(entity) && args(actorId,..)")
    public Object audit(ProceedingJoinPoint pjp,
                        Auditable entity,
                        Long actorId) throws Throwable {
        log.info("actor={} touches owner={}", actorId, entity.ownerId());
        // Calling entity.xxx() here hits the RAW target — advice will NOT re-fire.
        return pjp.proceed();
    }
}

go deeper

for a junior

Know that naming a variable instead of a type in the PCD passes the object into the advice.

for a middle

Wire up target()/args() binding correctly, matching variable names to advice parameters.

for a senior

Explain proxy-vs-raw semantics of the bound reference and combine binding PCDs with static ones for performance.

for a principal

Design marker-interface + binding conventions and reason about re-entrancy/recursion when acting on bound proxies.

## Two forms of this()/target()/args() Each of these PCDs can be written **either** with a type (pure matching) **or** with a **variable name** (binding + matching): ``` target(com.example.OrderService) // match only target(svc) // bind the target as 'svc' ``` When you use a variable name, that name **must match a parameter of the advice method**, and the **parameter's declared type is used both as the instanceof test and as the type of the injected object.** ```java @Before("target(svc)") public void audit(OrderService svc) { /* svc is the real target bean */ } ``` Here the pointcut says 'match join points whose target is an OrderService, and bind it as svc'. Spring resolves `svc` → the advice parameter typed `OrderService`. ## Which object each binds - `this(x)` → binds the **proxy** (the object the caller invoked through). - `target(x)` → binds the **target** (the real underlying bean). - `args(x, ..)` → binds **method arguments** by position and type. `args(id, ..)` binds the first argument (assignable to the parameter type) as `id`. ## Parameter name resolution Spring needs to map pointcut variable names to advice parameters. It uses (a) the `argNames` attribute, (b) compiled `-parameters` debug info / parameter-name discovery, or (c) for a single binding it can infer. Best practice: compile with `-parameters` (Spring Boot default) or set `argNames`. ```java @Before(value = "target(svc) && args(orderId)", argNames = "svc,orderId") public void audit(OrderService svc, Long orderId) { ... } ``` ## Semantics of the bound reference (the subtle part) - A **target()-bound** reference is the **raw bean**. If you call methods on it, you go **directly to the object, bypassing the proxy** — so no advice fires on those calls (this is the same reason self-invocation isn't intercepted). Use it to read state, not to re-trigger cross-cutting behaviour. - A **this()-bound** reference is the **proxy**. Calling methods on it goes **through the advice chain** again. Useful if you deliberately want interception, but beware infinite recursion in advice. ## Combining with static PCDs Binding PCDs are dynamic; pair them with a static PCD to narrow the candidate set cheaply: ```java @Around("execution(* com.example..*.*(..)) && target(auditable)") public Object around(ProceedingJoinPoint pjp, Auditable auditable) throws Throwable { ... } ``` ## Gotchas - The **variable name in the PCD and the advice parameter name must agree** (or be mapped via argNames), otherwise you get an IllegalArgumentException about pointcut parameters at startup. - **No wildcards** in the type-binding: `target(svc)` derives the type from the parameter; you cannot write `target(com.example..*)`. - With **JDK proxies**, binding via `this(x)` where x is typed as the concrete impl class will not match (same proxy-type rule as matching) — type the parameter as the interface. - args() binds by **runtime argument type** and is dynamic; ordering with `..` lets you bind specific positions (`args(.., lastArg)`). ## When to use - **Auditing / multi-tenancy**: `target(t)` to inspect the entity/tenant owner. - **Validation**: `args(cmd)` to grab the command object. - **Capability marker**: `target(secured)` where `Secured` is a marker interface, to advise any secured bean and access it.

  • If you call a method on a target()-bound reference from inside the advice, will the advice fire again?
    No. target() binds the raw underlying object, so calls on it bypass the proxy and its advice chain — the same mechanism as self-invocation not being intercepted. Bind with this() (the proxy) if you deliberately want interception, but guard against recursion.

context