javaspringlombokfinalspring-annotations

Why @Qualifier annotation does not work with final member variable


I noticed that the @Qualifier annotation does not work with final member variable. In the code below,

Can anyone please explain why?

// 2 Vehicle beans are annotated by @Qualifier;
@Configuration
public class Config 
{
  @Bean
  @Qualifier("vehicle1")
  Vehicle vehicle1() {
    var veh = new Vehicle();
    veh.setName("Benz");
    return veh;
  }

  @Bean
  @Qualifier("vehicle2")
  Vehicle vehicle2() {
    var veh = new Vehicle();
    veh.setName("BMW");
    return veh;
  }
}

// Person has a final member variable Vehicle
@Component
@Data
public class Person 
{
  private String name="Mike";

  @Qualifier("vehicle2")
  private final Vehicle vehicle;
}

Springboot complain about NoUniqueBeanDefinitionException:

  • No qualifying bean of type 'project1.Vehicle' available: expected single matching bean but found 2: vehicle1,vehicle2

Solution

  • The problem is closely related to lombok, which is quite common.

    What I would suggest for such problem is to look at the processed class.

    If you are using IntelliJ, go to the project build directory to open the class file.

    1. if the Vechicle member variable of Person is declared as final, an exception is thrown;

    The processed class is:

    @Component
    public class Person {
        private String name = "Mike";
        @Qualifier("vehicle2")
        private final Vehicle vehicle;
    
        public Person(final Vehicle vehicle) {
            this.vehicle = vehicle;
        }
        ...
    }
    
    

    We see that injection is through the constructor in this case, and @Qualifier is neglected. Hence we will see NoUniqueBeanDefinitionException as we does not specify which bean to use.

    2. otherwise, no exception is thrown (but the vehicle is null).

    This time the processed class is:

    @Component
    public class Person {
        private String name = "Mike";
        @Qualifier("vehicle2")
        private Vehicle vehicle;
    
        public Person() {
        }
        ...
    }
    

    vehicle does not appear in the constructor this time and there is no @Autowired for `vehicle, so no injection is preformed and it is null.

    How to make it work?

    To begin, it is not common to use @Data with Spring bean, @Data is usually used by DTO. And in favour of constructor injection, I will modify Person like below

    @Component
    public class Person {
        private String name = "Mike";
        private final Vehicle vehicle;
        public Person(@Qualifier("vehicle2") Vehicle vehicle) {
            this.vehicle = vehicle;
        }
        // ... getter setter omitted
    }