skip to content

What is a RepositoryProxyPostProcessor, when in the lifecycle is it invoked, and give real examples of what it contributes.

level: seniorimportance: should knowfreq 34%

answer

  1. postProcess(ProxyFactory, RepositoryInformation)
  2. Runs at proxy creation, once per repository
  3. Adds AOP advice before getProxy()
  4. Transaction interception + exception translation
  5. CrudMethodMetadata (lock/entity-graph)

basics

~20 s

It is a callback that lets Spring Data add AOP advice to a repository proxy before the proxy is created. During getRepository(), each registered post-processor's postProcess(ProxyFactory, RepositoryInformation) runs. Examples: adding transaction interception and JPA exception translation.

solid answer

~40 s

`RepositoryProxyPostProcessor` is a single-method SPI: `postProcess(ProxyFactory factory, RepositoryInformation information)`. Post-processors are registered on the `RepositoryFactorySupport` (via `addRepositoryProxyPostProcessor`), and inside `getRepository(...)` — after the `ProxyFactory` is set up with the target and interfaces but *before* `getProxy()` — the factory invokes every post-processor in order, giving each a chance to add `Advisor`/`Advice` to the proxy or inspect repository metadata. This is the extension point through which cross-cutting concerns are layered onto repositories: `TransactionalRepositoryProxyPostProcessor` (adds transaction interception so CRUD methods run in transactions), `PersistenceExceptionTranslationRepositoryProxyPostProcessor` (translates JPA/persistence exceptions to Spring's DataAccessException hierarchy), `CrudMethodMetadataPostProcessor` (exposes per-call CRUD method metadata like LockModeType), and `ExposeInvocationInterceptor` support. It runs once per repository at proxy-creation time, not per method call.

code

java · 26 lines
java
// A custom post-processor that times every repository method call by
// weaving an interceptor into each repository proxy at creation time.
public class TimingRepositoryProxyPostProcessor implements RepositoryProxyPostProcessor {

    @Override
    public void postProcess(ProxyFactory factory, RepositoryInformation information) {
        // Runs ONCE per repository, before factory.getProxy().
        // 'information' tells us the interface/domain type if we want to filter.
        factory.addAdvice((MethodInterceptor) invocation -> {
            long start = System.nanoTime();
            try {
                return invocation.proceed();
            } finally {
                long micros = (System.nanoTime() - start) / 1_000;
                // record metric: information.getRepositoryInterface() + method + micros
            }
        });
    }
}

// Register it on the factory (e.g. via a RepositoryFactoryCustomizer bean):
@Bean
RepositoryFactoryCustomizer timingCustomizer() {
    return factory -> factory.addRepositoryProxyPostProcessor(
            new TimingRepositoryProxyPostProcessor());
}

go deeper

for a junior

Usually unaware of this SPI.

for a middle

May know 'something adds transactions' but not name the mechanism.

for a senior

Names the SPI, its creation-time lifecycle, and concrete built-ins (transaction, exception translation).

for a principal

Reasons about advisor ordering, custom weaving for cross-cutting concerns, and interaction with the transaction interceptor.

**The interface.** `RepositoryProxyPostProcessor` (package `org.springframework.data.repository.core.support`) is a functional SPI: ```java void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation); ``` - `ProxyFactory` — the Spring AOP builder for *this* repository's proxy; you can `addAdvice(...)`/`addAdvisor(...)` to layer in interceptors. - `RepositoryInformation` — metadata about the repository being built: the interface type, domain type, id type, which methods are base vs. query vs. custom. Lets a post-processor decide *whether* and *how* to add advice. **Lifecycle — exactly when it runs.** Post-processors are collected on the `RepositoryFactorySupport`. The factory itself calls `addRepositoryProxyPostProcessor(...)` for its built-ins, and `RepositoryFactoryBeanSupport` adds framework ones (e.g. the exception-translation processor) before the factory builds anything. Then, inside `RepositoryFactorySupport.getRepository(...)`: 1. metadata/`RepositoryInformation` computed, 2. target (base impl + fragments) created, 3. a `ProxyFactory` configured with the target and interfaces, 4. **each registered `RepositoryProxyPostProcessor.postProcess(factory, information)` invoked in registration order,** 5. the core `QueryExecutorMethodInterceptor` added, 6. `factory.getProxy()` produces the JDK proxy. So it is a **creation-time, once-per-repository** hook — *not* invoked on every method call (the advice it *adds* is what runs per call). **Real examples (what actually gets contributed).** - **`TransactionalRepositoryProxyPostProcessor`** (spring-data-commons, used by JPA): adds a transaction interceptor / `TransactionalProxy` marker so base CRUD methods (`save`, `delete`, …) execute within a transaction using the configured `PlatformTransactionManager`. This is *why* `save()` is transactional even though you never annotated it, and why explicit method-level `@Transactional` on repository interfaces is honored. - **`PersistenceExceptionTranslationRepositoryProxyPostProcessor`**: adds a `PersistenceExceptionTranslationInterceptor` so store-specific exceptions (JPA `PersistenceException`, `ConstraintViolationException`, etc.) are translated into Spring's consistent `DataAccessException` hierarchy. - **`CrudMethodMetadataPostProcessor`** (JPA): captures per-invocation CRUD method metadata (e.g. `@Lock` lock mode, `@QueryHints`, `@EntityGraph` on CRUD methods) and exposes it to `SimpleJpaRepository` via a thread-bound proxy. - **`ExposeInvocationInterceptor.INSTANCE`** support: makes the current `MethodInvocation` available (needed by some advisors). **Terms.** - *SPI (Service Provider Interface)*: an interface the framework calls into so behavior can be plugged in. - *Advisor/Advice*: Spring AOP interceptors added to the proxy. - *DataAccessException*: Spring's technology-neutral, unchecked data-access exception hierarchy. **Gotchas / when-to-use.** - You rarely implement this yourself; it's mostly framework-internal. But it *is* the documented extension point if you need to weave custom advice into *every* repository proxy (e.g. metrics/tracing around all repository calls) — you could register a custom `RepositoryProxyPostProcessor` via a `RepositoryFactoryCustomizer` / by extending the factory. - Because it runs at *creation* time, it cannot see call-time arguments; it only shapes the advisor chain. Decisions that depend on the actual method go into the `Advice` it installs, keyed off `RepositoryInformation`. - Ordering matters: advisors added earlier sit further from the target. Adding a post-processor in the wrong order (e.g. relative to the transaction advice) can change whether your interceptor runs inside or outside the transaction. - It is invoked once per repository interface, so N repositories ⇒ N invocations, each with its own `ProxyFactory`.

  • Is a RepositoryProxyPostProcessor called on every repository method invocation?
    No. It runs once per repository at proxy-creation time to shape the advisor chain. The advice it installs (e.g. transaction or timing interceptor) is what executes per method call.
  • Which post-processor makes repository CRUD methods transactional without any @Transactional you wrote?
    TransactionalRepositoryProxyPostProcessor, which adds a transaction interceptor so base methods like save/delete run within a transaction managed by the PlatformTransactionManager.
  • How would you add tracing/metrics to every repository call cleanly?
    Register a custom RepositoryProxyPostProcessor (e.g. via RepositoryFactoryCustomizer) that adds a MethodInterceptor to each ProxyFactory, being mindful of ordering relative to the transaction advice.

saying these in an interview costs you the question

  • Saying it runs on every method call
  • Thinking it can see method arguments (it only sees creation-time metadata)
  • Confusing it with Spring's BeanPostProcessor (different SPI, different lifecycle)

context