29

Given this code:

public interface Service {}

@Component
@Qualifier("NotWanted")
public class NotWantedService implements Service {}

@Component
@Qualifier("Wanted")
public class WantedService implements Service {}

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(NotWantedService.class);
ctx.register(WantedService.class);
ctx.refresh()

How do I now do:

ctx.getBean(Service.class)

in a way that will only get the one with @Qualifier("Wanted") and not the one with @Qualifier("NotWanted")? I'm specifically asking if it's possible to do it using getBean, not injecting to a class, then using that one as a kind of proxy.

Rozart
  • 1,668
  • 14
  • 27
levant pied
  • 3,886
  • 5
  • 37
  • 56
  • Why not getting bean by name since you're using name constant, i.e. `ctx.getBean("Wanted")`? – aux Mar 26 '17 at 00:16
  • @aux Say you have 50 places where you do `ctx.getBean("service1")` and you now want to change it to `ctx.getBean("service2")`. That's 50 changes. Changing the qualifier is change to 2 bean definitions (`service1` and `service2`) only. There are other cases, too - say I want to get multiple `Service` instances that are `Wanted`. They cannot all have the same bean name. – levant pied Mar 28 '17 at 18:50
  • OK, i see. Then what about introducing your own "registry" bean that holds references to your beans and used for lookup by different parameters, like `Repositories` in spring-data-rest? Or a wrapper bean? – aux Mar 28 '17 at 19:23
  • 1
    @aux See last sentence in the question. I wanted to know if I wanted to do away with the indirection. In other words, I have a working solution, trying to see if there's a better one (read: less code). – levant pied Mar 29 '17 at 20:00

5 Answers5

32

You can use

BeanFactoryAnnotationUtils.qualifiedBeanOfType(ctx.getBeanFactory(), Service.class, "Wanted")

It's important to use ctx.getBeanFactory(), not ctx itself, because the 'qualifiedBeanOfType' method can resolve qualifiers only for ConfigurableListenableBeanFactory.

  • How can I get `ctx.getBeanFactory()` when I use `application.run()` to get the `ctx`? – UnixAgain May 18 '20 at 11:57
  • @UnixAgain did you try ``new AnnotationConfigApplicationContext()`` instance of ``ctx``? – TuGordoBello May 18 '20 at 18:51
  • 1
    @UnixAgain Use applicationContext.getAutowireCapableBeanFactory() – Abhishek Chatterjee May 20 '20 at 09:01
  • @AbhishekChatterjee thanks . I finally find the reason out. My class of bean is implemented as a singleton, so the instance is the same one. More details of my solution is posted under this question. – UnixAgain May 21 '20 at 06:02
  • @TuGordoBello thanks . I finally find the reason out. My class of bean is implemented as a singleton, so the instance is the same one. More details of my solution is posted under this question. – UnixAgain May 21 '20 at 06:02
10

It's not the @Qualifier annotation's purpose to use it when getting beans via ApplicationContext. But since you need such or similar functionality for some reasons, I suggest a workaround.

Create @Wanted and @NotWanted annotation:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
        ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Wanted {
}

and

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
            ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotWanted {
}

Annotate your bean classes with these new annotations:

@Component
@NotWanted
public class NotWantedService implements Service {}

and

@Component
@Wanted
public class WantedService implements Service {}

Then you should add 2 methods somewhere where you have access to the ApplicationContext :

ApplicationContext applicationContext;

private <T> Collection<T>  getBeansByTypeAndAnnotation(Class<T> clazz, Class<? extends Annotation> annotationType){
    Map<String, T> typedBeans = applicationContext.getBeansOfType(clazz);
    Map<String, Object> annotatedBeans = applicationContext.getBeansWithAnnotation(annotationType);
    typedBeans.keySet().retainAll(annotatedBeans.keySet());
    return typedBeans.values();
}

private <T> Optional<T> getBeanByTypeAndAnnotation(Class<T> clazz, Class<? extends Annotation> annotationType) {
    Collection<T> beans = getBeansByTypeAndAnnotation(clazz, annotationType);
    return beans.stream().findFirst();
}

And now you can use them to get beans or one bean by annotation and type like this:

Collection<Service> services = getBeansByTypeAndAnnotation(Service.class, Wanted.class);

or

Service service = getBeanByTypeAndAnnotation(Service.class, Wanted.class);

Possibly it's not the best way to deal with the problem. But since we are unable to get beans from ApplicationContext by qualifier and type 'out of box', that's one of the ways to do this.

Rozart
  • 1,668
  • 14
  • 27
  • 1
    Thanks @Rozart - I tried that already. Unfortunately, that does not take `@Qualifier` into consideration. It takes the name of the bean, specified with `@Bean(name="abc")`, which is not the same. – levant pied Jan 14 '16 at 20:14
  • That's the same as the `@Bean(name="abc")` though, it doesn't have the same semantics. You cannot have two beans named the same, but you can have more than one qualified the same. That is, I want 10 different services qualified with "Wanted", but not at all called "Wanted". – levant pied Jan 14 '16 at 20:17
  • I've updated the answer. Maybe that will help you. :) – Rozart Jan 14 '16 at 22:11
  • Thanks Rozart. Can you clarify this: "It's not the @Qualifier annotation's purpose to use it when getting beans via ApplicationContext."? It might not be supported at the moment, but why would that be logically inconsistent? – levant pied Jan 19 '16 at 14:33
  • That's exacly what I meant by 'It's not the @Qualifier's purpose' - it's not supported currently. Logically IMO it is consistent, but not yet supported. I hope my answer helped you. ;) – Rozart Jan 19 '16 at 16:04
  • Thanks Rozart - it did not answer the question, though it has some useful information that might come in handy. – levant pied Jan 19 '16 at 16:18
  • @levantpied You should file a feature request with Spring. – Adam Gent Mar 17 '17 at 12:50
4

In my case, I have two qualified bean of one class, e.g

@Configuration
public class AConfig {
    @Bean(name = "a")
    public Hello hello1() {
        return new Hello();
    }

    @Bean(name = "b")
    public Hello hello2() {
        return new Hello();
    }
}

Then I can get a specific bean by

ApplicationContext context = SpringApplication.run(AutoConfiguration.class); 
var aHello = context.getBean("a", Hello.class); 
var bHello = context.getBean("b", Hello.class);

This is the simplest way of doing this. Or you can do the same thing as below:

var aHello = context.getBeansOfType(Hello.class).getBean("a");
var bHello = context.getBeansOfType(Hello.class).getBean("b");

Or you can also do as @Dmitry Ovchinnikov says:

BeanFactoryAnnotationUtils.qualifiedBeanOfType(ctx.getBeanFactory(), Service.class, "Wanted")

In this case, ctx.getBeanFactory() can be replaced by context.getAutowireCapableBeanFactory().

UnixAgain
  • 437
  • 5
  • 13
  • The `context.get()` method gives me a `BeanDefinitionOverrideException`. – bart-kosmala Jul 27 '20 at 15:43
  • @bart-kosmala Sorry it's `context.getBean()` – UnixAgain Aug 16 '20 at 08:58
  • @UnixAgain, you provide bean names, not bean qualifier. You specify qualifiers using the org.springframework.beans.factory.annotation.Qualifier annotation. – Eduard Wirch Dec 13 '21 at 11:48
  • Your solutions seem interesting, although I can't find out what AutoConfiguration.class is supposed to be (tried a few dependencies although I'm unable to import that class) – FARS Feb 27 '22 at 13:35
3

If you want to get bean from context not injecting, better way to define bean name in @Component annotation and get it by name from context. In most cases @Qualifier is used for injections.

eg04lt3r
  • 2,467
  • 14
  • 19
2

The closest canonical way to do this in Spring is with the utility class BeanFactoryAnnotationUtils ... but this sadly only works with @Qualifier annotation value argument directly (hence why the argument is a string).

What @Rozart is recommending is the best approach and really something like that should be in BeanFactoryAnnotationUtils. I only included the above answer in the case someone lands here and does want to use @Qualifier directly (and all the bean aliasing that comes along with it).

I recommend filing a feature request in Spring (I would but I think they might be tired of me bugging them :-) ).

Community
  • 1
  • 1
Adam Gent
  • 47,843
  • 23
  • 153
  • 203