1

Let's say we have 2 implementations of an interface:

@Component
public class Toyota implements Car {}

@Component
public class Bmw implements Car {}

What is the advantage of using a @Qualifier

@Autowired
@Qualifier("toyota")
private Car car;

over using the specific implementation type when autowiring?

@Autowired
private Toyota car;

The downside of @Qualifier I see here is losing "type-safety" by relying on a string that could get out of sync with the bean (class) name.

How to avoid this fragility? What is the advantage of @Qualifier?

Hawk
  • 2,042
  • 8
  • 16
  • 1
    As always when writing to the interface: Abstraction from the implementation. By specifying `@Qualifier("toyota")`, you indicate a specific need, but still don't specify how that should be implemented. With Spring Boot's auto-configuration features, there can be many implementing classes with that qualifier, and auto-configuration can then select one to be loaded. You can't do that if you hard link to a class. – Andreas Jun 01 '21 at 22:40
  • Don't use field injection. The Spring team recommend constructor injection. – Michael Jun 01 '21 at 22:41
  • @Michael I agree, field injection was only used for brevity in the example – Hawk Jun 02 '21 at 00:01
  • @Andreas can you please provide a code example of auto-configuration selecting one of the implementations with the same Qualifier? – Hawk Jun 02 '21 at 00:06
  • @Hawk Sorry, no, not with `@Qualifier`, but there are lots of examples in the Spring source code using the [`@ConditionalOnXxx`](https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/condition/package-summary.html) annotations, since that is a core part of auto-configuration. E.g. the `@ConditionalOnProperty` can be used to allow the `application.properties` file to choose an implementation at runtime (install/config time). – Andreas Jun 02 '21 at 00:21
  • 1
    I have actually never seen a "real" case that would convince me that `@Qualifier` is a good thing, all of the examples that I have seen miss-use Spring injection to begin with. I guess I have not seen one _yet_. I like your question – Eugene Jun 02 '21 at 01:20
  • I have greped `@Qualifier` in the `spring-boot` project itself and while there are some usages, like [here](https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java#L420), the majority of the use cases are in tests. – Eugene Jun 02 '21 at 01:26

3 Answers3

2

There are other ways to create beans besides @Component. Consider:

@Configuration
public class CarConfig {
    @Bean
    public Toyota first() {
        return new Toyota(1);
    }

    @Bean
    public Toyota second() {
        return new Toyota(2);
    }
}

In this case, your 2nd example where you disambiguate by choosing a more specific type is not possible. Using a named qualifier is the only way to select between these two.

You also may not want the client class to know about the specific implementations of your interface. It might encourage the client class to use methods of Toyota's API that aren't part of Car's interface. Relying on those methods will increase coupling and may make it harder to swap out the implementation for a different one later.

Michael
  • 41,989
  • 11
  • 82
  • 128
  • 1
    And how to avoid the fragility of a string literal, whose value is still the implementation class name? Since it is the class name, the coupling is still in the source code; if the implementation class name changes compilation does not reveal that, only `BeanInstantiationException` reveals it at runtime. – Hawk Jun 02 '21 at 00:11
  • @Hawk There is always going to be some fragility with runtime DI. Your setup is not immune to runtime issues either - what if Toyota is not a bean, e.g. if it's not annotated with `@Component`? It will fail at runtime too. The way I sometimes add confidence is by having a `@SpringBootTest` unit test which asserts nothing, it just spins up the app and makes sure the components wire together properly. Basically a smoke test. These days I am leaning away from Spring in favour of frameworks that do the DI work at compile-time, like Micronaut. – Michael Jun 02 '21 at 09:09
0

How about the following approach:

public final class CarTypes {
    private CarTypes() {}
    public static final String TOYOTA = “toyota”;
    public static final String BMW = “bmw”;
}
public interface Car {...}

public class Toyota implements Car {...}
public class BMW implements Car {...}


@AllArgsConstructor
public class CarFactory { // or whatever that uses the car 
   private final Car car;
}

And the spring configuration:

static import CarTypes.*;
@Configuration
public class MyConfig {

   @Bean(name = TOYOTA)
   public Car toyotaWhateverMethod() { return new Toyota();}
    @Bean(name = BMW) 
    public Car bmwWhateverMethod() {return new BMW();}
    @Bean
    public CarFactory toyotaCarFactory(@Qualifier(TOYOTA) Car car) {
        return new CarFactory(car); 
    } 
          
}

Note that the classes themselves do not carry any annotation at all, and in the configuration you always rely on the final fields of a statically imported class CarTypes

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
0

It allows you to program to an interface rather than the implementation. Refer this for why it is good to program to an interface.

So if you program to an interface , you should auto-wire to an interface rather than an implementation, and @Qualifier is used to specify which bean to be injected if you have more than one bean implement the same interface.

I see here is losing "type-safety" by relying on a string that could get out of sync with the bean (class) name. How to avoid this fragility?

You can simply configure the bean name in @Component explicitly rather than relies on the default which the bean name is derived from the class name. Set a constant for the bean name and make sure the all codes that need to access it should access via this constant :

@Component(Toyota.BEAN_NAME)
public class Toyota implements Car {
  public static final String BEAN_NAME = "MyToyota";
}
@Autowired
@Qualifier(Toyota.BEAN_NAME)
private Car car;
Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • Interesting, but keeping the constant in the implementation is IMHO not a good idea - you still couple to the implementation. – Hawk Jun 03 '21 at 13:56
  • well no one prevent you from declaring that constant in the interface if you want to be 100% decouple from the implementaion. i just showed you the idea – Ken Chan Jun 03 '21 at 14:15