Search code examples
javaspring-bootdependency-injectioninversion-of-control

Is there any way to know in Spring what bean to use while being from the parent class?


I have this specific problem in which I can't use a @Qualifier cause I need the bean in the parent class. My idea is to remove the baseComponent propertie and make an abstract method in BaseController like getComponent() and return the desired bean for BaseComponent ... but perhaps there is a cleaner way to do this through configuration.

@RestController
public abstract class BaseController {

    @Autowired
    private BaseComponent baseComponent;

    @GetMapping("/something")
    public void doSomething() {
        baseComponent.printSomething();
    }

}

@RestController
@RequestMapping(value = "/foo")
public class FooController extends BaseController {

}

@RestController
@RequestMapping(value = "/bar")
public class BarController extends BaseController {

}

public interface BaseComponent {
    void printSomething();
}

@Component
public class FooComponent implements BaseComponent {

    @Override
    public void printSomething() {
        System.out.println("foo!");
    }

}

@Component
public class BarComponent implements BaseComponent{

    @Override
    public void printSomething() {
        System.out.println("bar!");
    }

}

Solution

  • This is one of the reasons I don't like autowiring directly to a private field. I would do this by injecting BaseComponent through the constructor of BaseController:

    public abstract class BaseController {
    
       private final BaseComponent baseComponent;
    
       protected BaseController(BaseComponent baseComponent){
           this.baseComponent = baseComponent;
       }
    
       @GetMapping("/something")
       public ResponseEntity<String> getSomething(){
           return new ResponseEntity<String>(baseComponent.getSomething(), HttpStatus.OK);
       }
    }
    
    @RestController
    @RequestMapping("/foo")
    public class FooController extends BaseController{
    
       @Autowired
       public FooController(@Qualifier("fooComponent") BaseComponent baseComponent) {
          super(baseComponent);
       }
    }
    
    @RestController
    @RequestMapping("/bar")
    public class BarController extends BaseController{
    
       @Autowired
       public BarController(@Qualifier("barComponent") BaseComponent baseComponent){
           super(baseComponent);
       }
    }
    
    @Component
    public class BarComponent implements BaseComponent {
    
       @Override
       public String getSomething() {
           return "bar";
       }
    }
    
    @Component
    public class FooComponent implements BaseComponent {
    
       @Override
       public String getSomething() {
         return "foo";
       }
    }
    

    Requests to /something/bar will return bar and requests to something/foo will return foo.

    Note that the abstract BaseComponent is not actually declared as any kind of Spring component nor does it have any dependencies automatically injected. Instead, the subclasses are the components and the dependencies are wired into their constructors and passed through super to BaseComponent. The subclass constructors provide a place for the @Qualifier annotation to specify which BaseComponent you want.

    In theory, I don't like declaring two classes that are identical other than the annotations. In practice, though, I have found that sometimes it is simplest just to declare classes to hold the Spring annotations. That's better than the old days of XML configuration.