0

I have an @Service class which contains a List of other @Service classes. That List basically contains all the services which implements UniformEventsResearchApi.

Being a rookie with Spring, I'm not sure how I can get Spring to allow me to follow the Open-Closed Principle and thus having that list automagically injected with all of the concrete implementations.

Here is an incomplete UML class diagram:

uml class diagram

And here is some "code":

@Service
public class EventsResearchService {

    // todo: this should be Injected automatically
    private List<UniformEventsResearchApi> eventsResearchApis = Arrays.asList(new EventbriteApi());

// Already tried, but without success:
//
//    @Autowired
//    private List<UniformEventsResearchApi> eventsResearchApis2;
//
//    @Autowired
//    @Qualifier("EventsResearchApi")
//    public void setXList(List<UniformEventsResearchApi> apis) {
//        this.eventsResearchApis2 = apis;
//    }

}


@Service
@Qualifier("EventsResearchApi")
public interface UniformEventsResearchApi { /* ... */ }


public abstract class EventsResearchApi implements UniformEventsResearchApi { /* ... */ }


/** Any class like this one which extends EventsResearchApi should be automatically injected in the List */
public class EventbriteApi extends EventsResearchApi { /* ... */ }
payne
  • 4,691
  • 8
  • 37
  • 85
  • One major question: Have you actually tried the commented code, like @Autowiring the `private List eventsResearchApis2` field? I would suppose so and I tried to edit your question in this direction. Anyway, if that doesn't work, you have probably something wrong in your Spring setup. Which version of the Spring framework do you use? – Petr Bodnár Nov 14 '19 at 22:05
  • @PetrBodnár Yes, I tried the commented code. I get a red underline with the error saying no beans could be found. The answer provided below did work. I think it's my idea of getting an interface to be marked as a Bean that wasn't legit: in any case, the services themselves should be marked as beans anyways. – payne Nov 14 '19 at 22:14
  • 1
    OK, thanks for the explanation, that makes sense now. BTW although being asked a little bit differently, this is de-facto duplicate of https://stackoverflow.com/questions/7083308/is-there-a-way-in-spring-to-autowire-all-dependencies-of-a-given-type (and there are possibly more)... – Petr Bodnár Nov 14 '19 at 22:19
  • @PetrBodnár I looked for an answer for a while before deciding to write this. That other one is indeed quite similar, aside from being framed with XML. I see the answer mentions `@Inject` : I'll look into that. Thanks! – payne Nov 14 '19 at 22:30

1 Answers1

4

This is an easy task for spring actually:

You can auto wire a list of beans just like a regular bean In this case spring will indeed find all the beans that implement the interface and inject:

public interface SomeInterface {
}

@Component
public class SomeImpl1 implements SomeInterface {}

@Component
public class SomeImpl2 implements SomeInterface {}

@Service
public сlass SampleBean {

     @Autowired
     private List<SomeInterface> beans;
}

One note, there should be at least one implementation of beans available, Otherwise Spring won’t let you such an injection. If you know that such a situation is possible you can inject Optional<List<SomeInterface>>

With field injection it looks rather ugly, but you can use constructor injection (which is better anyway) or consider using “java configuration”:

@Service
public class SampleBean {
   private final List<SomeInterface> beans;
   // I haven’t checked this with compiler, should work
    @Autowired // optional, if you have a single constructor, you can omit it
    public SampleBean(Optional<List<SomeInterface>> beans) {
       this.beans = beans.orElse(Collections.emptyList());   
    }
}
payne
  • 4,691
  • 8
  • 37
  • 85
Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • This worked! My idea of annotating the interface was the wrong approach. Thanks. May I ask why the constructor-injection approach is better? – payne Nov 14 '19 at 22:16
  • Great, I’m glad to hear it worked for you. As for construtor injection vs field injection: In two words: Because it doesn’t break encapsulation and allows easy Unit Testing (by supplying dependencies) but its a subject for another question / googling frankly I won’t be able to provide a full answer in a comment – Mark Bramnik Nov 14 '19 at 22:21
  • Yes, this topic of constructor vs. field injection is very opinion based actually. Both are correct, depending on what you expect from them. For me, for example, the constructor injection given in the example looks ugly... :)) – Petr Bodnár Nov 14 '19 at 22:30