Reputation: 1869
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
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 @Service
on the Square
type
UPDATED
Concerning package-scan this com.example.bridge
and this com.example.bridge.**
are actually the same.
Upvotes: 1