Ali
Ali

Reputation: 1869

Spring boot component scan does not recognize qualifier annotation for different beans

I have been noticing something weird with the example of using the Bridge design pattern with Spring boot. To overcome the issues of having two beans of the same type in the classpath, I am using Qualifier annotation. However, for some reason, it does not work without using a wildcard for component scan.

Color.java

package com.example.bridge;

public interface Color {

  String fill();
}

Blue.java

package com.example.bridge;

import org.springframework.stereotype.Service;

@Service("Blue")
public class Blue implements Color {
  @Override
  public String fill() {
    return "Color is Blue";
  }
}

Red.java

package com.example.bridge;

import org.springframework.stereotype.Service;

@Service("Red")
public class Red implements Color {
  @Override
  public String fill() {
    return "Color is Red";
  }
}

Shape.java

package com.example.bridge;

public abstract class Shape {
  protected Color color;

  public Shape(Color color){
    this.color = color;
  }

  abstract public String draw();
}

Square.java

package com.example.bridge;

import org.springframework.stereotype.Service;

@Service
public class Square extends Shape {

  public Square(Color color) {
    super(color);
  }

  @Override
  public String draw() {
    return "Square drawn. " + color.fill();
  }
}

Triangle.java

package com.example.bridge;

@Service
public class Triangle extends Shape {

  public Triangle(Color color) {
    super(color);
  }

  @Override
  public String draw() {
    return "Triangle drawn. " + color.fill();
  }
}

BridgeApplication.java

package com.example.bridge;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.example.bridge")
public class BridgeApplication {
  public static void main(String[] args) {
    SpringApplication.run(BridgeApplication.class, args);
  }
}

Controller:

package com.example.bridge;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BridgeController {

  @Autowired
  @Qualifier("Red")
  private Color red;

  @GetMapping("/red")
  @ResponseStatus(HttpStatus.OK)
  public String redSquare() {
    Shape square = new Square(red);
    return square.draw();
  }

}

This project fails to start with the following exception:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-04-14 20:52:52.839 ERROR 9689 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.example.bridge.Square required a single bean, but 2 were found:
    - Blue: defined in file [IdeaProjects/test-bridge-design/target/classes/com/example/bridge/Blue.class]
    - Red: defined in file [IdeaProjects/test-bridge-design/target/classes/com/example/bridge/Red.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Ok, now let's change the base package for the component scan to use "com.example.*". The same issue.

Now, if I change the base package to be "com.example.bridge.*" it works and the application can be started. Technically, I don't need to set the wildcard for the base package and it should pick up all the beans recursively. Also, I don't understand what the difference between "com.example.bridge.*" and "com.example.*" is.

Upvotes: 0

Views: 1036

Answers (1)

CodeScale
CodeScale

Reputation: 3304

You got this error because when spring loads the application context it finds that Square is a spring bean and so tries to inject a Color. As it found 2impl it generates an error.

The injection of color is only working on controller because you qualified the injection point -> @Autowired @Qualifier("Red") private Color red;

As you init the Square type manually inside the controller Shape square = new Square(red); you don't need the @Serviceon the Square type

UPDATED

Concerning package-scan this com.example.bridge and this com.example.bridge.** are actually the same.

Upvotes: 1

Related Questions