skip to content

Can a named @Pointcut declare parameters, and how are they used to bind values into advice?

level: seniorimportance: nice to knowfreq 25%

answer

  1. params on @Pointcut = pointcut params
  2. args()/this()/target()/@annotation() bind by name
  3. advice repeats the param name
  4. needs -parameters for name matching
  5. forward params through composition

basics

~20 s

Yes. The @Pointcut method's parameters become the pointcut's parameters. Binding designators like args(), this(), target() and @annotation() tie a matched value to a named parameter, and advice with the same parameter names receives that value.

solid answer

~30 s

A named pointcut isn't limited to pure matching — it can also *bind* context. You give the @Pointcut method parameters, then use binding forms of designators (args(name), this(name), target(name), @annotation(name), @args(name)) that reference those parameter names. When advice references the pointcut and declares matching parameter names, Spring passes the bound values in. Example: @Pointcut("execution(* *..*Service.*(..)) && args(id,..)") void serviceCallWithId(Long id) {}, then @Before("serviceCallWithId(id)") public void log(Long id). Parameter binding by name is why the pointcut method's parameter list matters. When the composite only needs to test membership (not extract a value), no parameters are needed.

code

java · 20 lines
java
@Aspect
@Component
public class RetryAspect {

    // Bind the @Retry annotation instance to a pointcut parameter
    @Pointcut("@annotation(retry)")
    void retryable(Retry retry) {}

    @Around("retryable(retry)")   // pass the bound name
    public Object around(ProceedingJoinPoint pjp, Retry retry) throws Throwable {
        int attempts = 0;
        while (true) {
            try {
                return pjp.proceed();
            } catch (Exception e) {
                if (++attempts >= retry.maxAttempts()) throw e;
            }
        }
    }
}

go deeper

for a junior

Usually just knows no-parameter named pointcuts.

for a middle

Aware args()/@annotation() can bind, can copy a working example.

for a senior

Explains name-based binding, -parameters requirement, and threading params through composites.

for a principal

Weighs binding vs plain selection, and the operational cost of relying on parameter-name retention across the build.

**Two jobs of a pointcut.** Matching (does this join point qualify?) and, optionally, *binding* — extracting values (arguments, the target object, an annotation instance) and handing them to advice as typed parameters. Named pointcuts support both. **Declaring parameters.** Add parameters to the `@Pointcut` method signature; these become the pointcut's formal parameters. Inside the expression, *binding designators* reference them by name: - `args(name, ..)` — binds a method argument to `name` (`..` allows other args). - `this(name)` — binds the proxy/this object. - `target(name)` — binds the target object being advised. - `@annotation(name)` — binds the annotation instance present on the method. - `@args(name)`, `@within`, `@target` — annotation-on-argument/type variants. ```java @Pointcut("execution(* com.app..*Service.*(..)) && args(orderId,..)") void serviceCallWithOrderId(Long orderId) {} ``` **Consuming the binding in advice.** The advice references the pointcut *passing the parameter names*, and declares matching-named, matching-typed parameters: ```java @Before("serviceCallWithOrderId(orderId)") public void audit(Long orderId) { log.info("service call for order {}", orderId); } ``` Spring matches by **name**, not position, so the pointcut parameter name and the advice parameter name must agree. (This name-matching relies on parameter-name info; modern Spring reads it via the `-parameters` compiler flag or debug info.) **@annotation binding — a very common use.** Bind the annotation instance to read its attributes: ```java @Pointcut("@annotation(retry)") void retryable(Retry retry) {} @Around("retryable(retry)") public Object aroundRetry(ProceedingJoinPoint pjp, Retry retry) throws Throwable { int max = retry.maxAttempts(); // ... } ``` **Threading parameters through composition.** If you compose a binding pointcut into another, the outer pointcut must also declare and forward the parameter: ```java @Pointcut("serviceCallWithOrderId(orderId) && inTxLayer()") void txServiceCallWithOrderId(Long orderId) {} ``` **Gotchas.** - Name mismatch between pointcut param and advice param → binding fails / IllegalArgumentException at startup. - Missing parameter names at runtime (no `-parameters`, no debug symbols) can break name-based binding; enable `-parameters`. - `args(id,..)` matches by runtime argument *type* to the declared parameter type; a wrong type won't match. - A `ProceedingJoinPoint`/`JoinPoint` parameter in advice is separate from bound parameters and doesn't need to appear in the pointcut. - Pure membership pointcuts should have **no** parameters — adding unused ones just invites confusion. **When to use.** Use binding when advice needs the actual value — an id, the target bean, or an annotation's attributes (retry counts, cache keys, permission names). Use plain no-parameter named pointcuts when you only need to select where advice runs.

  • How does Spring match a bound pointcut parameter to the advice parameter — by position or by name?
    By name. The name used in the pointcut binding (e.g. args(orderId)) must match the advice method's parameter name (Long orderId). This requires parameter-name info at runtime — compile with -parameters (or debug symbols) so names are retained.
  • You want to read a custom annotation's attributes inside @Around advice — which designator and shape do you use?
    Bind with @annotation(x) in a @Pointcut declaring that annotation type as a parameter — @Pointcut("@annotation(retry)") void retryable(Retry retry){} — then reference retryable(retry) from the advice and declare Retry retry, reading retry.maxAttempts() etc.

saying these in an interview costs you the question

  • Thinking named pointcuts can only match, never bind values
  • Assuming binding is positional rather than by parameter name
  • Forgetting that composition must forward the bound parameter

context