Reputation: 1216
I have an abstract configuration class in a Spring 4 based project. The class looks something like this:
public abstract class C<B> {
@Bean
public B b() {
return a();
}
protected abstract B a();
}
Of course, this is not the actual class, but let's work with this theoretical class, that involves the most important parts of the real problematic class.
This class C
is extended by multiple classes. For example:
@Configuration
public class E extends C<X> {
@Override
protected X a() { return new X(); }
}
@Configuration
public class F extends C<Y> {
@Override
protected Y a() { return new Y(); }
}
The goal is to let every extending class instantiate and set up the bean the way they are intended to be and to expose the instance as a Spring bean to be injected into other beans. In other words, all of the fields should have a bean instance injected in the following bean:
@Component
public class Bean {
private final X x;
private final Y y;
public Bean(X x, Y y) {
this.x = x;
this.y = y;
}
}
The problem is that the bean created by b()
will always have a name of b
, which means that even if I have more than 1 extension of C
, only one of the beans will be available. In the above example, it would mean that either x
, or y
field is null (or would throw an exception).
How can I give unique names to these beans, so that all of the extensions of C
can expose its own bean instance without overlap?
Upvotes: 5
Views: 1110
Reputation: 111
Seems there is no way to override naming strategy of @Bean
beans.
But you can create your custom annotation, for example @UniqueNamedBean
, and register your custom BeanFactoryPostProcessor that scans methods of existing bean definition classes and creates additional bean definitions.
This is kotlin, not pure java, but hope it helps.
@Component
class UniqueNamedBeanPostProcessor : BeanFactoryPostProcessor {
override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) {
for (name in beanFactory.beanDefinitionNames) {
val def = beanFactory.getBeanDefinition(name) as? AnnotatedBeanDefinition ?: continue
val className = def.beanClassName
if (className == null || def.factoryMethodName != null) continue
val metadata = def.metadata
for (method in metadata.getAnnotatedMethods(UniqueNamedBean::class.java.name)) {
val beanDef = GenericBeanDefinition()
beanDef.factoryBeanName = name
beanDef.factoryMethodName = method.methodName
method.getAnnotationAttributes(Scope::class.qualifiedName!!)?.let {
beanDef.scope = it["value"]!! as String
}
beanDef.autowireMode = AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR
(beanFactory as DefaultListableBeanFactory).registerBeanDefinition(
className + "_" + method.methodName, beanDef
)
}
}
}
}
Upvotes: 1
Reputation: 434
Indeed, without specifying the bean name it will fail to start with the following message:
The bean 'b', defined in class path resource
Here's a way, which is counter intuitive because if the subclass extender doesn't call super.b()
, then some important logic would never be executed.
But for the sake of answering your specific question:
C superclass
public abstract class C<B> {
protected B b() {
return a();
}
protected abstract B a();
}
Extending subclasses
@Configuration
public class E extends C<X> {
@Bean("X)// Bean name qualifier
@Override
protected X b() {
return super.b();
}
@Override
protected X a() {
return new X();
}
}
@Configuration
public class F extends C<Y> {
@Bean("Y") // Bean name qualifier
@Override
protected Y b() {
return super.b();
}
@Override
protected Y a() {
return new Y();
}
}
Spring component
@Component
public class Bean {
private final X x;
private final Y y;
public Bean(X x, Y y) {
this.x = x;
this.y = y;
}
}
Hope this helps
Upvotes: 1