How to implement OR logic for spring qualifiers? - java

I have following configuration:
#Qualifier1
#Qualifier2
#Bean
public MyBean bean1(){...}
#Qualifier2
#Qualifier3
#Bean
public MyBean bean2(){...}
#Qualifier1
#Qualifier2
#Qualifier3
#Bean
public MyBean bean3(){...}
#Qualifier3
#Bean
public MyBean bean4(){...}
#Qualifier1
#Bean
public MyBean bean5(){...}
And it is the injection place:
#Qualifier2
#Qualifier3
#Autowired:
private List<MyBean> beans;
By default spring uses AND logic for each #Qualifier
So bean2 and bean3 will be injected.
But I want to have OR logic for that stuff so I expect beans bean1 bean2 bean3 and bean4 to be injected
How can I achieve it?
P.S.
#Qualifier annotation is not repeatable so I have to create meta annotation for each annotation:
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Qualifier1 {
}

What if you used marker interfaces instead of qualifiers? For example:
public class MyBean1 extends MyBean implements Marker1 {}
public class MyBean2 extends MyBean implements Marker2 {}
public class MyBean12 extends MyBean implements Marker1, Marker2 {}
Then using this:
#Bean
public MyBean1 myBean1() {
//...
}
#Bean
public MyBean2 myBean2() {
//...
}
#Bean
public MyBean12 myBean12() {
//...
}
and this:
#Autowired private List<Marker1> myBeans;
You would get a list of myBean1 and myBean12 beans.
And for this:
#Autowired private List<Marker2> myBeans;
You would get a list of myBean2 and myBean12 beans.
Will this work?
UPDATE I
Custom FactoryBean
I implemented TagsFactoryBean class and #Tags annotation which you can use to solve your task (I hope :)).
First, mark your beans with #Tags annotation:
#Tags({"greeting", "2letters"})
#Bean
public Supplier<String> hi() {
return () -> "hi";
}
#Tags({"parting", "2letters"})
#Bean
public Supplier<String> by() {
return () -> "by";
}
#Tags("greeting")
#Bean
public Supplier<String> hello() {
return () -> "hello";
}
#Tags("parting")
#Bean
public Supplier<String> goodbye() {
return () -> "goodbye";
}
#Tags("other")
#Bean
public Supplier<String> other() {
return () -> "other";
}
Then prepare TagsFactoryBean:
#Bean
public TagsFactoryBean words() {
return TagsFactoryBean.<Supplier>builder()
.tags("greeting", "other")
.type(Supplier.class)
.generics(String.class)
.build();
}
Here tags is an array of desired tags whose beans should be selected, type is a selected beans type, and generics is an array of generic types of the beans. The last parameter is optional and should be used only if your beans are generic.
Then you can use it with #Qualifier annotation (otherwise Spring injects all beans of Supplier<String> type):
#Autowired
#Qualifier("words")
private Map<String, Supplier<String>> beans;
The Map beans will contain three beans: hi, hello and other (their name are keys of the Map and their instances are its values).
More usage examples you can find in tests.
UPDATE II
Custom AutowireCandidateResolver
Thanks to #bhosleviraj recommendation, I implemented TaggedAutowireCandidateResolver that simplifies the process of autowiring the desired beans. Just mark your beans and the autowired collection with the same tags and you will get them injected into the collection:
#Autowired
#Tags({"greeting", "other"})
private Map<String, Supplier<String>> greetingOrOther;
#Configuration
static class Beans {
#Tags({"greeting", "2symbols", "even"})
#Bean
public Supplier<String> hi() {
return () -> "hi";
}
#Tags({"parting", "2symbols", "even"})
#Bean
public Supplier<String> by() {
return () -> "by";
}
#Tags({"greeting", "5symbols", "odd"})
#Bean
public Supplier<String> hello() {
return () -> "hello";
}
#Tags({"parting", "7symbols", "odd"})
#Bean
public Supplier<String> goodbye() {
return () -> "goodbye";
}
#Tags({"other", "5symbols", "odd"})
#Bean
public Supplier<String> other() {
return () -> "other";
}
}
You can use not only the Map for injecting beans but also other Collections.
To make it work you have to register a CustomAutowireConfigurer bean in your application and provide it with TaggedAutowireCandidateResolver:
#Configuration
public class AutowireConfig {
#Bean
public CustomAutowireConfigurer autowireConfigurer(DefaultListableBeanFactory beanFactory) {
CustomAutowireConfigurer configurer = new CustomAutowireConfigurer();
beanFactory.setAutowireCandidateResolver(new TaggedAutowireCandidateResolver());
configurer.postProcessBeanFactory(beanFactory);
return configurer;
}
}
More usage examples see in this Test.

Answer requires deep understanding of how autowiring resolution is implemented in Spring, so we can extend it.
I couldn't come up with any solution yet, but I can give you some pointers.
Possible candidate to extend is QualifierAnnotationAutowireCandidateResolver , override method that resolves to a qualified bean. And pass the custom autowire resolver to the bean factory.
You can clone source code and correct version branch from here:
https://github.com/spring-projects/spring-framework
There is a CustomAutowireConfigurerTests in spring-beans module, that might help you understand few things.

I guess you can't do it by using annotation.
What I'd use is the org.springframework.context.ApplicationContextAware Maybe you need to write some extra code but in this way you can solve your issue.
I'd implement a class like this:
#Component
public class SpringContextAware implements ApplicationContextAware {
public static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
}
public static synchronized ApplicationContext getCtx() {
return ctx;
}
}
Then in all beans where you need the OR logic you want you can do something like this:
#Autowired
private SpringContextAware ctxAware;
#PostConstruct
public void init() {
//Here you can do your OR logic
ctxAware.getCtx().getBean("qualifier1") or ctxAware.getCtx().getBean("qualifier2")
}
Will this solve your issue?
Angelo

Related

Why do we use a qualifier when we can have a name for the bean?

Why do we use qualifiers with #Bean when we can have different names for different beans of the same type (class)?
#Bean
#Qualifier("fooConfig")
public Baz method1() {
}
Isn't the following code more clean?
#Bean("fooConfig")
public Baz method1() {
}
If I create two beans of the same type with different names (using #Bean annotation), then can we inject them specifically using the #Qualifier annotation(can be added on field/constructor parameter/setter) in another bean?
#Bean("fooConfig")
public Baz method1(){
}
#Bean("barConfig")
public Baz method2(){
}
// constructor parameter of a different bean
final #Qualifier("fooConfig") Baz myConfig
If the above is true, then where do we use #Qualifier (with #Bean or #Component) instead of giving the bean a name as shown below?
#Bean
#Qualifier("fooConfig")
public Baz method1(){
}
#Bean
#Qualifier("barConfig")
public Baz method2(){
}
// constructor parameter of a different bean
final #Qualifier("fooConfig") Baz myConfig
Beans have names. They don't have qualifiers. #Qualifier is annotation, with which you tell Spring the name of Bean to be injected.
No.
Default Qualifier is the only implementation of the interface(example is below, 4th question) or the only method with a particular return type. You don't need to specify the #Qualifier in that case. Spring is smart enough to find itself.
For example:
#Configuration
public class MyConfiguration {
#Bean
public MyCustomComponent myComponent() {
return new MyCustomComponent();
}
}
If you will try to inject myComponent somewhere, Spring is smart enough to find the bean above. Becaude there is only one Bean with return type MyCustomComponent. But if there was a couple of methods, that would return MyCustomComponent, then you would have to tell Spring which one to inject with #Qualifier annotation.
SIDENOTE: #Bean annotation by default Spring uses the method name as a bean name. You can also assign other name like #Bean("otherComponent").
You have one Interface, and a couple of Classes implementing it. You inject bean of your interface. How can Spring know which Class should be used?
This is you interface:
public interface TestRepository{}
This is your implementation 1:
#Repository
public class Test1Repository implements TestRepository{}
Your implementation 2:
#Repository
public class Test2Repository implements TestRepository{}
Now you are injecting it like:
private final TestRepository testRepository;
public TestServiceImpl(TestRepository testRepository) {
this.testRepository= testRepository;
}
QUESTION! How is Spring supposed to know which class to inject? Test1 or Test2? That's why you tell it with #Qualifier which class.
private final TestRepository testRepository;
public TestServiceImpl(#Qualifier("test1Repository") TestRepository testRepository) {
this.testRepository= testRepository;
}
I Prefer different method to not using #Qualifier
Create common Interface
public interface CommonFooBar{
public String commonFoo();
public String commonBar();
}
Extends to each service
public interface FooService extends CommonFooBar {
}
public interface BarService extends CommonFooBar {
}
Then using it to your class
#Autowired
FooService fooService;
or
#Autowired
BarService barService;
so, we can defined the single responsibility to each interface and This kind of segregation is more readable to every junior.
I quite like a different way of working. Surely if you provide a unique name for your bean, then that is all you need?
Given the example below, its easy to see that Spring will name the beans based on the method name used to create the beans. In other words, if you give your beans sensible names, then the code should become self-explanatory. This also works when injecting beans into other classes.
The end result of this is:
Spring will name your beans based on the method used to create them.
If you import a bean, Spring will try to match on the bean name.
If you try to import a bean that does not match the name, Spring will attempt to match the class.
If your injected field name does not match the bean name and there are more than one instance of your bean, Spring will throw an exception on startup as it won't know which one to inject.
Lets not over-complicate Spring.
#Bean
mqConnectionFactory() {
ConnectionFactory connectionFactory = new MQXAConnectionFactory();
return connectionFactory;
}
#Bean
public ConnectionFactory pooledConnectionFactory(ConnectionFactory mqconnectionFactory) {
JmsPoolConnectionFactory connectionFactory = new JmsPoolConnectionFactory();
connectionFactory.setConnectionFactory(mqConnectionFactory);
return connectionFactory;
}
#Bean
public ConnectionFactory cachingConnectionFactory(ConnectionFactory mqConnectionFactory) {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setTargetConnectionFactory(mqConnectionFactory);
return connectionFactory;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory cachingConnectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(cachingConnectionFactory);
return jmsTemplate;
}
#Bean
public DefaultMessageListenerContainer messageListenerContainer(ConnectionFactory pooledConnectionFactory) {
DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
container.setConnectionFactory(pooledConnectionFactory);
...
return container;
}

Make a member of a bean available for autowiring

#Component
class MultiProvider {
public Foo getFoo();
public Bar getBar();
}
#Component
class FooConsumer {
FooConsumer(Foo f);
}
Can I have MultiProvider.getFoo() autowired into the FooConsumer constructor..
without making Foo a bean itself (for example, because Spring should not destroy it, since that is MultiProviders responsibility)
and without introducing a dependency from FooConsumer to MultiProvider (or any other class)?
You can achieve this simply by annotating getFoo() method in MultiProvider by #Bean
#Component
class MultiProvider {
#Bean(destroyMethodName="cleanup") // HERE IS THE TRICK
public Foo getFoo();
public Bar getBar();
}
#Component
class FooConsumer {
FooConsumer(Foo f);
}
if the problem comes from the point that spring can not properly destroy it you can include the logic inside cleanup method declared while annotating by #Bean
public class Foo {
public void cleanup() {
// destruction logic
}
}
Note that #component and #configurable are more or less the same with
some subtle differences but in your case you can use #component if you don't
want to change it. More Info
Spring can only autowire declared beans, a possible workaround can be something like the following:
#Component
class FooConsumer {
private final Foo foo;
FooConsumer(MultiProvider multiProvider) {
// MultiProvider will be autowired by spring - constructor injection
this.foo = multiProvider.getFoo();
}
}
You can include them in your Configuration.
#Configuration
class MyConfig {
#Bean
public MultiProvider getMP() {
return new MultiProvider() ;
}
#Bean
public Foo getFoo() {
return getMP(). getFoo();
}
}
Not sure if that violates your 'not a Bean itself' rule.

Using ApplicationContext inside #Configuration class

Regarding the below code:
When ServicesConfig1 is loaded, can I be sure that ApplicationContext was already loaded and never will be NULL?
When Bean1 is loaded how can I be sure that Bean2 was already loaded? let's say that I can't move Bean2 declaration to ServicesConfig1
I tested it and ApplicationContext wasn't NULL, but I just want to be sure that it not loaded in random order, so my test now passed by next time will fail.
f
#Configuration
public class ServicesConfig1
{
#Inject
private ApplicationContext ctx;
#Bean
public Bean1 bean1()
{
Bean2 bean2 = ctx.getBean(Bean2.class);
....
}
}
#Configuration
public class ServicesConfig2
{
#Bean
public Bean2 bean2()
{
return new Bean2();
}
}
You do not need to inject ApplicationContext here at all.
Think of ApplicationContext as a more container specific bean, which allows more fine-grained control over beans lifecycle and which you do not need in most cases.
For your concrete case you can use simple dependency injection.
#Configuration
public class ServicesConfig1{
#Bean
public Bean1 bean1(Bean2 bean2){
....
}
}
#Configuration
public class ServicesConfig2 {
#Bean
public Bean2 bean2() {
return new Bean2();
}
}
By passing bean2 as an argument to bean1 you signaling to the DI container that for constructing it you have a dependency on bean2, so the container will look if bean2 is already been instantiated and if not it will create it and then inject it for you.
You can use #DependsOn
#Configuration
public class ServicesConfig1
{
#Inject
private ApplicationContext ctx;
#Bean
#DependsOn(value = "Bean2")
public Bean1 bean1()
{
Bean2 bean2 = ctx.getBean(Bean2.class);
....
}
}
#Configuration
public class ServicesConfig2
{
#Bean
public Bean2 bean2()
{
return new Bean2();
}
}
Even better solution would be to take Bean2 as argument for bean1 function. this way spring will initialize and inject it into your function.
#Bean
public Bean1 bean1(Bean2 bean2)
{
...
}

Spring: Autowire bean that does not have qualifier

Is it possible to autowire a bean that does NOT have the given qualifier in spring? The use case would be to have a list of all beans, but exclude one:
#Autowired
#NotQualifier("excludedBean") // <-- can we do something like this?
List<SomeBean> someBeanList;
public class Bean1 implements SomeBean {}
public class Bean2 implements SomeBean {}
#Qualifier("excludedBean")
public class Bean3 implements SomeBean {}
In the example above someList should contain an instance of Bean1 and Bean2 but not Bean3.
(Remark: I'm aware that the opposite would work, i.e. add some qualifier to Bean1 and Bean2 and then autowire with that qualifier.)
EDIT: Some further clarifications:
All beans are in the spring context (also the one being excluded).
Configuration needs to be annotation-based, not xml-based. Therefore, e.g. turning off autowired-candidate does not work.
Autowire capability of the bean must remain in general. In other words, I want to exclude the bean from the injection point List<SomeBean> someBeanList;, but I want to autowire it somewhere else.
You can introduce you own annotation with meta annotations #Conditional and #Qualifier
#Target({ElementType.FIELD, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
#Conditional(MyCondition.class)
public #interface ExcludeBean {
and then introduce class where you can do your conditional logic
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !metadata.equals(ExcludeBean.class);
}
}
In your Configuration class
#Bean
#ExcludeBean
public BeanA beanA() {
return new BeanA();
}
You can also exclude bean from being candidate for autowiring by setting autowire-candidate on particular bean or by specifying default-autowire-candidates="list of candidates here"
main point it's bean for exclude is in context but not injected into some cases with exclude condition .
you can do exclude bean with qualifier with custom annotaion and BeanPostProcessor. (I did as example for simple case , for collection of bean type , but you can extend it)
annotaion for exclude :
#Component
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ExcludeBeanByQualifierForCollectionAutowired {
String qualifierToExcludeValue();
Class<?> aClass();
}
bean post processor with injection
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
#Component
public class ExcludeAutowiredBeanPostProcessor implements BeanPostProcessor {
#Autowired
private ConfigurableListableBeanFactory configurableBeanFactory;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
ExcludeBeanByQualifierForCollectionAutowired myAutowiredExcludeAnnotation = field.getAnnotation(ExcludeBeanByQualifierForCollectionAutowired.class);
if (myAutowiredExcludeAnnotation != null) {
Collection<Object> beanForInjection = new ArrayList<>();
String[] beanNamesOfType = configurableBeanFactory.getBeanNamesForType(myAutowiredExcludeAnnotation.aClass());
for (String injectedCandidateBeanName : beanNamesOfType) {
Object beanCandidate = configurableBeanFactory.getBean(injectedCandidateBeanName);
Qualifier qualifierForBeanCandidate = beanCandidate.getClass().getDeclaredAnnotation(Qualifier.class);
if (qualifierForBeanCandidate == null || !qualifierForBeanCandidate.value().equals(myAutowiredExcludeAnnotation.qualifierToExcludeValue())) {
beanForInjection.add(beanCandidate);
}
}
try {
field.set(bean, beanForInjection);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
and example:
public class ParentBean {}
public class Bean1Included extends ParentBean {}
public class Bean2Included extends ParentBean {}
public class Bean3Included extends ParentBean {}
#Qualifier("excludedBean")
public class BeanExcluded extends ParentBean {}
configuration
#Configuration
public class BeanConfiguration {
#Bean
public Bean1Included getBean1(){
return new Bean1Included();
}
#Bean
public Bean2Included getBean2(){
return new Bean2Included();
}
#Bean
public Bean3Included getBean3(){
return new Bean3Included();
}
#Bean
public BeanExcluded getExcludedBean(){
return new BeanExcluded();
}
#Bean
public ExcludeAutowiredBeanPostProcessor excludeAutowiredBeanPostProcessor(){
return new ExcludeAutowiredBeanPostProcessor();
}
}
and result:
#ExtendWith(SpringExtension.class) // assumes Junit 5
#ContextConfiguration(classes = BeanConfiguration.class)
public class ExcludeConditionTest {
#Autowired
private ApplicationContext context;
#Autowired
private BeanExcluded beanExcluded;
#ExcludeBeanByQualifierForCollectionAutowired(qualifierToExcludeValue = "excludedBean" , aClass = ParentBean.class)
private List<ParentBean> beensWithoutExclude;
#Test
void should_not_inject_excluded_bean() {
assertThat(context.getBeansOfType(ParentBean.class).values())
.hasOnlyElementsOfTypes(Bean1Included.class,
Bean2Included.class,
Bean3Included.class,
BeanExcluded.class);
assertThat(beansWithoutExclude)
.hasOnlyElementsOfTypes(Bean1Included.class,
Bean2Included.class,
Bean3Included.class)
.doesNotHaveAnyElementsOfTypes(BeanExcluded.class);
assertThat(beanExcluded).isNotNull();
}
}
There might be two cases :
case 1 : Bean3 in not in spring context;
case 2 : Bean3 is in spring context but not injected in some cases with #Autowired ,
if you need to exclude bean with Qualifier from context at all ,use
Condition. This bean is not registered in application conxtet if matches returns false. as result :
#Autowired List someBeanList; -- here injected all beans instanceof SomeBean and registered in application context.
from spring api
Condition A single condition that must be matched in order for a
component to be registered. Conditions are checked immediately before
the bean-definition is due to be registered and are free to veto
registration based on any criteria that can be determined at that
point.
autowired with qualifier :
2.1 if you want to exclude bean with the some qualifier from autowired
value in some bean/beans and in xml configuration you can use
autowire-candidate
2.2 also you can get
all autowired values by Setter Injection and filter only
beans that you need.
//no Autowired. Autowired in method
private List<ParentBean> someBeen = new ArrayList<>();
#Autowired
public void setSomeBeen(List<ParentBean> beens){
// if you use java 8 use stream api
for (ParentBean bean:beens) {
Qualifier qualifier = bean.getClass().getAnnotation(Qualifier.class);
if(qualifier == null ||!qualifier.value().equals("excludedBean")){
someBeen.add(bean);
}
}
}
2.3 you can use custome AutowiredAnnotationBeanPostProcessor :) and customise #Autowired for you requirements if you need something realy custom.
from spring api AutowiredAnnotationBeanPostProcessor :
Note: A default AutowiredAnnotationBeanPostProcessor will be
registered by the "context:annotation-config" and
"context:component-scan" XML tags. Remove or turn off the default
annotation configuration there if you intend to specify a custom
AutowiredAnnotationBeanPostProcessor bean definition.
Another way to do this, is creating a custom component with #Qualifier
#Component
#Qualifier
public #interface MyComponent {
public boolean isMock() default false;
}
#Autowired
#MyComponent(true)
List<SomeBean> mockList; // will inject just component with "isMock = true"
#Autowired
#MyComponent(false)
List<SomeBean> notMockList; // will inject just component with "isMock = false"
#MyComponent
public class Bean1 implements SomeBean {}
#MyComponent
public class Bean2 implements SomeBean {}
#MyComponent(isMock = true)
public class Bean3 implements SomeBean {}
Obs: this code is not tested, just giving an idea

Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.
#Service
public class MyService {
public void test(){...}
}
For example for windows's platform I need MyServiceWin extending MyService, for linux platform I need MyServiceLnx extending MyService.
For now I know only one horrible solution:
#Service
public class MyService {
private MyService impl;
#PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
Please consider that I'm using annotation only and not XML config.
1. Implement a custom Condition
public class LinuxCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Same for Windows.
2. Use #Conditional in your Configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
#Bean
#Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
3. Use #Autowired as usual
#Service
public class SomeOtherServiceUsingMyService {
#Autowired
private MyService impl;
// ...
}
Let's create beautiful config.
Imagine that we have Animal interface and we have Dog and Cat implementation. We want to write write:
#Autowired
Animal animal;
but which implementation should we return?
So what is solution? There are many ways to solve problem. I will write how to use #Qualifier and Custom Conditions together.
So First off all let's create our custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public #interface AnimalType {
String value() default "";
}
and config:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AnimalFactoryConfig {
#Bean(name = "AnimalBean")
#AnimalType("Dog")
#Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
#Bean(name = "AnimalBean")
#AnimalType("Cat")
#Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
Note our bean name is AnimalBean. why do we need this bean? because when we inject Animal interface we will write just #Qualifier("AnimalBean")
Also we crated custom annotation to pass the value to our custom Condition.
Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)
public class AnimalCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
and finally injection:
#Qualifier("AnimalBean")
#Autowired
Animal animal;
You can move the bean injection into the configuration, as:
#Configuration
public class AppConfig {
#Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternatively, you may use profiles windows and linux, then annotate your service implementations with the #Profile annotation, like #Profile("linux") or #Profile("windows"), and provide one of this profiles for your application.
Autowire all your implementations into a factory with #Qualifier annotations, then return the service class you need from the factory.
public class MyService {
private void doStuff();
}
My Windows Service:
#Service("myWindowsService")
public class MyWindowsService implements MyService {
#Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
My Mac Service:
#Service("myMacService")
public class MyMacService implements MyService {
#Override
private void doStuff() {
//Mac specific stuff happens here
}
}
My factory:
#Component
public class MyFactory {
#Autowired
#Qualifier("myWindowsService")
private MyService windowsService;
#Autowired
#Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
Then your factory can tap into the Application context and pull instances into it's own map. When you add a new service class, just add another entry to the enum, and that's all you have to do.
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
Now you can just pass the class type you want into the factory, and it will provide you back the instance you need. Very helpful especially if you want to the make the services generic.
Simply make the #Service annotated classes conditional:
That's all. No need for other explicit #Bean methods.
public enum Implementation {
FOO, BAR
}
#Configuration
public class FooCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.FOO == implementation;
}
}
#Configuration
public class BarCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.BAR == implementation;
}
}
Here happens the magic.
The condition is right where it belongs: At the implementating classes.
#Conditional(FooCondition.class)
#Service
class MyServiceFooImpl implements MyService {
// ...
}
#Conditional(BarCondition.class)
#Service
class MyServiceBarImpl implements MyService {
// ...
}
You can then use Dependency Injection as usual, e.g. via Lombok's #RequiredArgsConstructor or #Autowired.
#Service
#RequiredArgsConstructor
public class MyApp {
private final MyService myService;
// ...
}
Put this in your application.yml:
implementation: FOO
👍 Only the implementations annotated with the FooCondition will be instantiated. No phantom instantiations. 👍
Just adding my 2 cents to this question. Note that one doesn't have to implement so many java classes as the other answers are showing. One can simply use the #ConditionalOnProperty. Example:
#Service
#ConditionalOnProperty(
value="property.my.service",
havingValue = "foo",
matchIfMissing = true)
class MyServiceFooImpl implements MyService {
// ...
}
#ConditionalOnProperty(
value="property.my.service",
havingValue = "bar")
class MyServiceBarImpl implements MyService {
// ...
}
Put this in your application.yml:
property.my.service: foo
MyService.java:
public interface MyService {
String message();
}
MyServiceConfig.java:
#Configuration
public class MyServiceConfig {
#Value("${service-type}")
MyServiceTypes myServiceType;
#Bean
public MyService getMyService() {
if (myServiceType == MyServiceTypes.One) {
return new MyServiceImp1();
} else {
return new MyServiceImp2();
}
}
}
application.properties:
service-type=one
MyServiceTypes.java
public enum MyServiceTypes {
One,
Two
}
Use in any Bean/Component/Service/etc. like:
#Autowired
MyService myService;
...
String message = myService.message()

Categories

Resources