简体   繁体   中英

Resolve Bean Injection Programmatically

Hallo 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: - Having to define on the Default Classes such @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 soultion, because on the DefaultMapper i have to Specialization which will be marked as @Primary and the container doesn't like it at all ;)

Then I would like to solve this where i can decide for ALL the injected Classes, when the resolution of the Beans is ambigous then i would like to programmatically decide (in container) which implementation i would like to pick. I think an Annotation likewise to @Profile , iE @Candidate("CH") will suit compared to a value set in application.properties in a iE 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 wich 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.

See: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Primary.html

@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");
}

You can activate multiple profiles, CH and IT and your implementation will fail.

It is a internationalization problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM