I would like to find a way to elect programmatically a Candidate between different implementation present on the bean factory, at Spring Container Level.
With Profiles I can achieve that simply like this
I Have the following Profiles "CH" and "IT" and I want that if a Bean Profiled with IT is there, then will fallback to default.
Given those classes:
The Default Implementation
@Component
public class DefaultMapper {
public void doSomething() {
System.out.println("Map Default");
}
}
The Swiss Implementation
@Component
@Profile("CH")
public class SwissMapper extends DefaultMapper {
@Override
public void doSomething() {
System.out.println("do something swiss");
}
}
The Italian Implementation:
@Component
@Profile("IT")
public class ItalianMapper extends DefaultMapper {
@Override
public void doSomething() {
System.out.println("do pasta");
}
}
Now if I run it with @ActiveProfiles("IT")
it throws a NoUniqueBeanDefinitionException, which is fair enough, since the Default is not profiled, and it will be registered without any further attention by the container.
Some Consideration:
if I add @Profile("default")
to DefaultMapper
this will work until I have another default bean has a subclass profiled ONLY with CH.
Default Implementation of another Bean:
@Component
@Profile("default")
public class DefaultParser {
public void doSomething() {
System.out.println("Parse Default");
}
}
Then the Swiss implementation (No Italian is available, or better said the default fits for Italian):
@Component
@Profile("CH")
public class SwissParser extends DefaultParser {
@Override
public void doSomething() {
System.out.println("Ässe Ässe");
}
}
Now if I run it with @ActiveProfiles("IT")
it throws a No BeanDefinitionException, which is fair enough, but not that fair, since the "default" is not chained as ActiveProfiles and for IT i have no implementation. the CH profile here will work since has a profiled implementation for each default class.
Said That I would like a better way to cover this scenario without:
@Profile({"default","IT", "<all other profiles which have NOT a specialized implementation"}
, or @Profile("!CH")
where I exclude those profiles which have a specialization. @Primary
I think is not a solution, because on the DefaultMapper
I have to Specialization which will be marked as @Primary
and the container doesn't like it at allThen I would like to solve this where I can decide for ALL the injected Classes, when the resolution of the Beans is ambiguous then I would like to programmatically decide (in container) which implementation I would like to pick. I think an Annotation likewise to @Profile, i.E. @Candidate("CH") will suit compared to a value set in application.properties in a i.E. nationality.cantidate=CH. Something like:
public class MyExtension extends UnknowSpringType {
@Override
Object resolveAmbigousDependency(final Context ctx, final Resolution resolution) {
final List<Class> clazzes = resolution.getResolvedClasses();
for (final Class clazz : clazzes) {
if (clazz.isAnnotationPresent(Candidate.class) && clazz.getAnnotation(Candidate.class).getVaue().equals(candidateApplicationPropertyValue)) {
return clazz;
}
return getDefaultImplementation(clazzes);
}
}
I've done in the past Something similar with CDI SPI extension which is elegant, and also I could cover this in another way with @Specializes
from CDI, but I cannot find the same easy way on Spring (I don't have so much experience on it), BeanFactory? BeanFactoryPostProcessor?
Help?
You can mark all your specific Parsers as @Primary this will make sure Spring autowires this one instead of the default one which is not marked as @Primary. When no specific Parsers exist for the chosen profile it will fall back to the non @Primary default component.
@Component
public class DefaultParser {
public void doSomething() {
System.out.println("Parse Default");
}
}
@Component
@Primary
@Profile("CH")
public class SwissParser extends DefaultParser {
@Override
public void doSomething() {
System.out.println("Ässe Ässe");
}