skip to content

What problem does the TargetSource interface solve, and how do singleton, prototype, and pooled TargetSources differ?

level: seniorimportance: should knowfreq 35%

answer

  1. proxy holds TargetSource, not the target
  2. getTarget/releaseTarget/isStatic per call
  3. singleton static, prototype/pooled/threadlocal non-static
  4. CommonsPool2TargetSource = commons-pool2, setMaxSize
  5. HotSwappableTargetSource.swap() atomic

basics

~20 s

TargetSource decides where the real target object comes from for each proxied call. By default it's a fixed singleton, but you can make it return a fresh prototype instance per call, borrow one from a pool, or keep a per-thread instance — all behind the same proxy.

solid answer

~40 s

A Spring AOP proxy never holds the target directly; it holds a `TargetSource`, and on every intercepted call the chain ends by asking `targetSource.getTarget()`, invoking the method, then `releaseTarget(obj)`. This indirection lets the *lifecycle* of the target vary independently of the proxy. `SingletonTargetSource` returns the same instance forever and reports `isStatic()==true` so the proxy can cache it. `PrototypeTargetSource` (non-static) pulls a brand-new bean from the `BeanFactory` per invocation. `CommonsPool2TargetSource` borrows an instance from a commons-pool2 object pool and returns it in `releaseTarget`, capping concurrent instances. `ThreadLocalTargetSource` gives each thread its own target. `HotSwappableTargetSource.swap()` atomically replaces the backing object behind a live proxy, and `LazyInitTargetSource` defers creating the target until first use. This is how Spring implements pooling/prototype scoping transparently.

code

java · 18 lines
java
// Hot-swap the backing object behind a live proxy
HotSwappableTargetSource ts = new HotSwappableTargetSource(new GreeterImpl("v1"));
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
pf.addInterface(Greeter.class);
Greeter proxy = (Greeter) pf.getProxy();

proxy.hello("Ada");            // routed to v1
ts.swap(new GreeterImpl("v2")); // atomic replace, callers unchanged
proxy.hello("Ada");            // now routed to v2

// Custom TargetSource: fresh instance every call (non-static)
TargetSource perCall = new TargetSource() {
    public Class<?> getTargetClass() { return GreeterImpl.class; }
    public boolean isStatic() { return false; }
    public Object getTarget() { return new GreeterImpl("fresh"); }
    public void releaseTarget(Object t) { /* cleanup */ }
};

go deeper

for a junior

Know the default is a single fixed target (SingletonTargetSource).

for a middle

Explain getTarget/releaseTarget per call and name prototype vs pooled sources.

for a senior

Reason about isStatic() caching, pooling reuse hazards, and hot-swap semantics.

for a principal

Judge when target-lifecycle indirection is worth its complexity vs. simpler singleton beans; consider thread-local leaks and pool sizing under load.

**The core abstraction.** `org.springframework.aop.TargetSource` decouples the proxy from *how* and *when* the target instance is obtained. Its contract: - `Object getTarget() throws Exception` — return the object the current call should hit. - `void releaseTarget(Object target)` — hook to return/clean up the instance after the call. - `Class<?> getTargetClass()` — the target type. - `boolean isStatic()` — `true` means `getTarget()` always returns the same instance, so the AOP framework may cache it and skip release; `false` means it may vary per call. **Why it exists.** A proxy is a stable reference other beans wire to. But sometimes you don't want one fixed backing object: you want a fresh instance per call (prototype), a bounded set of reusable instances (pool), a per-thread instance, a lazily created one, or the ability to hot-swap the target at runtime. `TargetSource` is the seam that makes all of these transparent to callers — they keep calling the same proxy. **Invocation flow.** In `ReflectiveMethodInvocation.proceed()`, after all interceptors have run, the framework calls `AopUtils.invokeJoinpointUsingReflection` on the object obtained from the `TargetSource` for that invocation (`JdkDynamicAopProxy`/`CglibAopProxy` call `targetSource.getTarget()` at the start of `invoke`, and `releaseTarget` in a `finally`). So the target is resolved *per proxied call* unless the source is static. **Concrete implementations:** - `SingletonTargetSource` — wraps one instance; `isStatic()==true`. This is the default when you do `new ProxyFactory(target)`. - `EmptyTargetSource` — no target (used for introduction-only proxies); `getTarget()` returns null. - `PrototypeTargetSource` (extends `AbstractPrototypeBasedTargetSource`) — `isStatic()==false`; each `getTarget()` calls `beanFactory.getBean(targetBeanName)` for a **new** prototype-scoped bean. Requires a `BeanFactory` and a prototype bean definition. - `CommonsPool2TargetSource` — pooled; backs onto Apache commons-pool2. `getTarget()` borrows, `releaseTarget()` returns to the pool; `setMaxSize(n)` bounds concurrency (a caller blocks/fails when the pool is exhausted, per config). Exposes JMX-friendly stats (active/idle count). Used to reuse expensive stateful targets. - `ThreadLocalTargetSource` — one target instance per thread, created lazily; useful for thread-bound state; must be destroyed to avoid leaks (implements `ThreadLocalTargetSourceStats`/`DisposableBean`). - `HotSwappableTargetSource` — holds a single current target with `swap(Object newTarget)` that atomically replaces it (synchronized), so live proxies immediately route to the new object. - `LazyInitTargetSource` — delays obtaining the target until the first `getTarget()`. **How you wire it.** Programmatically: `proxyFactory.setTargetSource(new HotSwappableTargetSource(obj))`. Declaratively, the classic XML `TargetSourceCreator`s and `<aop:scoped-proxy>`/pooling used `CommonsPool2TargetSource`. On a live proxy: `((Advised) proxy).setTargetSource(newSource)`. **Gotchas.** (1) A non-static (`isStatic()==false`) source means the target is resolved every call — don't cache the result of `getTarget()` yourself. (2) Pooling requires the target to be safe to reuse; stateful leftovers between borrows cause bugs — reset in the target or via advice. (3) `ThreadLocalTargetSource` leaks if threads are pooled (app servers) and you never destroy it — instances stick to worker threads. (4) Prototype/pooled targets defeat the usual singleton assumptions; wiring collaborators into them needs care. (5) `releaseTarget` is only meaningful for non-static sources; for `SingletonTargetSource` it's a no-op. **When to use.** Pooling for expensive-to-create but reusable stateful objects; prototype for per-call fresh state behind a shared reference; hot-swap for zero-downtime target replacement (config reload); thread-local for thread-affine state. For plain stateless singleton beans, the default `SingletonTargetSource` is correct and cheapest.

  • What does isStatic() returning true let the AOP framework do?
    It signals the target never changes, so the framework can cache the resolved target and skip calling getTarget()/releaseTarget() on every invocation — an optimization. Non-static sources must be consulted per call.
  • Why can ThreadLocalTargetSource leak memory in a servlet container?
    It stores a target instance per thread; app-server worker threads are pooled and long-lived, so instances stay bound to threads indefinitely unless the TargetSource is destroyed, effectively leaking one target (and its references) per worker thread.

saying these in an interview costs you the question

  • Thinking the proxy stores the target object directly
  • Believing getTarget() is called once and cached even for prototype/pooled sources
  • Assuming pooled targets are safe to reuse without resetting state

context