14

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 {
}
gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • are you sure the description is correct? `So bean2 and bean3 will be injected` vs `so I expect beans bean1 bean2 bean3 and bean4 to be injected` – Eugene Jul 19 '19 at 11:49
  • @Eugene could you elaborate? AR is **So bean2 and bean3 will be injected** But I WANT another behaviour – gstackoverflow Jul 19 '19 at 11:52
  • @Eugene I want to inject bean if it has at least one matching qualifier – gstackoverflow Jul 19 '19 at 11:58
  • are you saying that if a certain based based on `QualifierX` has already been loaded into the context, you don't want _another_ bean with the same `QualifierX` to be loaded into the context? – Eugene Jul 19 '19 at 12:00
  • @Eugene I want all beans – gstackoverflow Jul 19 '19 at 12:01
  • @gstackoverflow Is it possible to add multiple `@Qualifier` to a `@Bean` method? – Madhu Bhat Jul 26 '19 at 06:43
  • @Madhu Bhat, good question beacause Qualifier is not repeatable but you can create META annotation and it is possible at this case. Modificating the question – gstackoverflow Jul 29 '19 at 07:19
  • How abount `@Profile` + SPEL [https://stackoverflow.com/questions/33449014/get-spring-profile-name-with-spring-el](https://stackoverflow.com/questions/33449014/get-spring-profile-name-with-spring-el) `@Qualifier` is used to resolve ambiguity, not to "abuse" with complex logics – Valijon Jul 29 '19 at 17:08

3 Answers3

10

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.

Cepr0
  • 28,144
  • 8
  • 75
  • 101
2

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.

1

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

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65