You need to enforce that every bean whose name ends in 'Repository' is proxied for metrics and never lazy. How would you implement this with a post-processor, and what pitfalls must you avoid?
answer
- BFPP = metadata, BPP = proxy
- postProcessAfterInitialization returns the proxy
- never getBean in a BFPP
- static @Bean for both processors
- order BPPs so proxies compose, prefer AOP first
basics
~20 sUse a BeanFactoryPostProcessor to iterate bean definitions and, for names ending in 'Repository', set lazy-init false and mark them for proxying. Do metadata changes only in the BFPP; do the actual proxy wrapping in a BeanPostProcessor. Never call getBean in the BFPP.
solid answer
~40 sSplit the concern across the two hooks. In a BeanFactoryPostProcessor I iterate the definitions (getBeanDefinitionNames), and for each whose name ends in 'Repository' I mutate metadata safely: setLazyInit(false), and set an attribute/flag the proxying stage can read. I must NOT create instances here, because instantiating a bean before BeanPostProcessors are registered would skip proxying entirely. The actual metrics proxy is applied by a BeanPostProcessor's postProcessAfterInitialization, which returns a proxy wrapping the initialized instance — that's the correct, ordering-safe place to introduce proxies (AOP itself works this way). Pitfalls: registering the BFPP non-static on a @Config class (created-too-early warning); calling getBean/forcing eager creation in the BFPP; matching FactoryBeans by the wrong name; and ordering — ensure my BPP is ordered so it composes with, rather than fights, existing AOP proxies.
code
java · 35 lines// 1) BFPP: metadata only (never lazy + mark for proxying)
public class RepositoryPolicyBfpp implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) {
for (String name : bf.getBeanNamesForType(Object.class, false, false)) {
if (name.endsWith("Repository") && bf.containsBeanDefinition(name)) {
BeanDefinition bd = bf.getBeanDefinition(name);
bd.setLazyInit(false);
bd.setAttribute("metrics.proxy", Boolean.TRUE);
}
}
}
}
// 2) BPP: wrap the fully-initialized instance in a proxy
public class MetricsProxyBpp implements BeanPostProcessor, BeanFactoryAware {
private ConfigurableListableBeanFactory bf;
@Override public void setBeanFactory(BeanFactory f) {
this.bf = (ConfigurableListableBeanFactory) f;
}
@Override public Object postProcessAfterInitialization(Object bean, String name) {
if (bf.containsBeanDefinition(name)
&& Boolean.TRUE.equals(bf.getBeanDefinition(name).getAttribute("metrics.proxy"))) {
return MetricsProxyFactory.wrap(bean);
}
return bean;
}
}
// 3) Register BOTH as static @Bean methods
@Configuration
class PolicyConfig {
@Bean static RepositoryPolicyBfpp repoPolicy() { return new RepositoryPolicyBfpp(); }
@Bean static MetricsProxyBpp metricsProxy() { return new MetricsProxyBpp(); }
}go deeper
Likely only recognizes that a post-processor is involved; not expected to design this.
Should separate metadata (BFPP) from proxying (BPP) and avoid getBean in the BFPP.
Designs both hooks correctly, uses postProcessAfterInitialization, and flags static-@Bean and eager-instantiation pitfalls.
Reasons about BPP ordering/proxy composition, FactoryBean/infrastructure edge cases, identity/proxy-type constraints, and when plain AOP is the better tool.
## Why two hooks, not one The requirement has two halves: 1. **Change metadata** (never lazy) — a definition-level concern -> `BeanFactoryPostProcessor`. 2. **Wrap instances in a proxy** (metrics) — an instance-level concern -> `BeanPostProcessor`. Trying to do the proxying inside the BFPP is the trap: the BFPP runs **before** `BeanPostProcessor`s are registered and before beans are created. If you `getBean(...)` there to wrap it, that bean is instantiated prematurely, **bypasses** all proxy-creating BPPs (so it won't get transactional/AOP/metrics advice from the normal path), and can poison every bean it depends on. Rule: **BFPP touches definitions only; BPP touches instances.** ## The BFPP part (metadata) ```java public class RepositoryPolicyBfpp implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory bf) { for (String name : bf.getBeanDefinitionNames()) { if (name.endsWith("Repository")) { BeanDefinition bd = bf.getBeanDefinition(name); bd.setLazyInit(false); // never lazy bd.setAttribute("metrics.proxy", true); // marker read later by the BPP } } } } ``` Notes: - Match carefully. A `FactoryBean` named `fooRepository` produces a bean named `fooRepository` but the definition's bean class is the FactoryBean; if you need the *product* type, be aware of the `&`-prefix convention and `getType`/`isTypeMatch`. Prefer matching by type (`bf.getBeanNamesForType(...)`) over string suffix when possible. - Setting an **attribute** on the definition is a clean, side-effect-free way to pass intent to the later stage. ## The BPP part (proxying) ```java public class MetricsProxyBpp implements BeanPostProcessor, BeanFactoryAware, Ordered { private ConfigurableListableBeanFactory bf; public void setBeanFactory(BeanFactory f) { this.bf = (ConfigurableListableBeanFactory) f; } @Override public Object postProcessAfterInitialization(Object bean, String name) { if (bf.containsBeanDefinition(name) && Boolean.TRUE.equals(bf.getBeanDefinition(name).getAttribute("metrics.proxy"))) { return MetricsProxyFactory.wrap(bean); // return a proxy of the fully-initialized bean } return bean; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } } ``` Why `postProcessAfterInitialization`: at that point the bean is constructed, injected, and its init callbacks have run; returning a proxy here is exactly how Spring AOP (`AbstractAutoProxyCreator`) introduces proxies, so you compose correctly with the framework. ## Pitfalls to call out (this is what a principal-level answer must surface) 1. **Non-static @Bean registration.** Both post-processors, if declared via `@Bean` in a `@Configuration` class, must be `static` — otherwise the config class is instantiated too early and left un-enhanced ('created too early' warning), breaking @Value/inter-@Bean semantics on it. 2. **No eager instantiation in the BFPP.** Calling `getBean`, or resolving certain values that trigger creation, defeats proxying and can cascade. 3. **Ordering of BPPs.** If multiple proxying BPPs run (yours + AOP + transaction), the order determines proxy nesting. Implement `Ordered`/`PriorityOrdered` deliberately so your metrics proxy wraps (or is wrapped by) the right layer; getting it wrong can hide `@Transactional` advice or double-proxy. 4. **FactoryBean and infrastructure beans.** Suffix matching can accidentally target `FactoryBean`s or Spring-internal beans; prefer type-based selection and skip infrastructure roles (`BeanDefinition.ROLE_INFRASTRUCTURE`). 5. **Equality/identity.** Returning a proxy changes the object identity; make sure nothing relies on `==` against the raw bean, and that the proxy still satisfies the injected type (JDK dynamic proxy needs an interface; CGLIB proxies a class but not final classes/methods). 6. **Idempotency / self-injection.** Ensure you don't re-wrap an already-proxied bean, and remember that a bean calling its own methods bypasses the proxy. ## When simpler tools suffice Before reaching for hand-written post-processors, consider **Spring AOP** with a pointcut (`@Aspect` + `execution(* *..*Repository.*(..))`) for the metrics cross-cut, and `@Lazy(false)`/default eager singletons for the laziness requirement. Custom post-processors are justified when you need programmatic, classpath-driven policy that annotations can't express — the essence of this question is knowing the **division of labor** (definitions vs instances) and the **timing hazards**.
- Why not just create the proxy inside the BeanFactoryPostProcessor?The BFPP runs before BeanPostProcessors are registered and before beans are instantiated. Creating/wrapping instances there forces premature instantiation that bypasses all proxy-creating post-processors, breaking transactional/AOP/metrics advice for that bean and its dependencies.
- How would you make sure your metrics proxy composes correctly with @Transactional's proxy rather than hiding it?Control BPP ordering via Ordered/PriorityOrdered so the layers nest deliberately, keep proxies additive (wrap, don't replace), and match the AOP auto-proxy creator's ordering. Often it's cleaner to express the metrics concern as an AOP advisor so Spring composes it into a single proxy chain.
- Is there a simpler idiomatic alternative to hand-written post-processors here?Yes — an @Aspect with a pointcut like execution(* *..*Repository.*(..)) for the metrics cross-cut, and normal eager singletons (default) for the non-lazy requirement. Custom processors are only warranted when policy must be programmatic/classpath-driven.
saying these in an interview costs you the question
- Proposing to build the proxy inside the BFPP via getBean.
- Doing everything in a single BFPP with no BeanPostProcessor.
- Ignoring BPP ordering and its effect on proxy nesting / hidden @Transactional.
- Forgetting the static @Bean requirement for the processors.
- Assuming string-suffix matching is safe against FactoryBeans/infrastructure beans.