Skip to content

More Bean Ambiguity Strategies, Custom Scopes, and BeanPostProcessor

Additional Bean Ambiguity Resolution Strategies

Section titled “Additional Bean Ambiguity Resolution Strategies”

Beyond @Qualifier and @Primary, a few other approaches resolve NoUniqueBeanDefinitionException:

@Autowired
PaymentService paypalPaymentService;

Spring matches the field name with the bean name.

@Resource(name = "paypalPaymentService")
PaymentService paymentService;

Injecting All Implementations as a Collection

Section titled “Injecting All Implementations as a Collection”
@Autowired
List<PaymentService> paymentServices;

or

@Autowired
Map<String, PaymentService> paymentServices;

This approach is commonly used to implement the Strategy pattern — the caller picks the right implementation from the collection at runtime instead of Spring picking one at injection time.

public class CustomScope implements Scope {
private Map<String, Object> beans = new HashMap<>();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return beans.computeIfAbsent(name, k -> objectFactory.getObject());
}
@Override
public Object remove(String name) {
return beans.remove(name);
}
}
@Bean
public static CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("custom", new CustomScope());
return configurer;
}
@Scope("custom")
@Component
class CustomScopedBean {}

Real-World Singleton vs Prototype Use Cases

Section titled “Real-World Singleton vs Prototype Use Cases”
Singleton Prototype
UserService ReportBuilder
OrderService File Processing Task
PaymentService Data Transformation Object
Repository Temporary Workflow Object

Singletons are ideal for stateless shared services; prototype beans suit stateful or temporary objects.

BeanPostProcessor allows custom processing before and after bean initialization.

flowchart LR
A[Instantiate Bean] --> B[Dependency Injection]
B --> C[postProcessBeforeInitialization]
C --> D[Initialization]
D --> E[postProcessAfterInitialization]
E --> F[Ready for Use]
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName);
Object postProcessAfterInitialization(Object bean, String beanName);
}
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("Before Init: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("After Init: " + beanName);
return bean;
}
}
  • Logging
  • Proxy creation
  • Annotation processing
  • Performance monitoring
  • Security

Spring internally uses BeanPostProcessor to implement:

  • @Autowired
  • @Transactional
  • @Async
  • @Cacheable
  • @Resource and bean-name matching are lighter-weight alternatives to @Qualifier; injecting a List/Map of all implementations is the idiomatic way to wire up a Strategy pattern.
  • A custom bean scope requires implementing Scope, registering it via CustomScopeConfigurer, then referencing it by name in @Scope("custom") — rarely needed, but explains how the built-in scopes work under the hood.
  • BeanPostProcessor is the extension point Spring itself uses to implement @Autowired, @Transactional, @Async, and @Cacheable — it’s the mechanism, not just an app-level customization hook.