1

I've got TopicGenerator interface:

public interface TopicGenerator {
    File create(MultiValueMap params);
    boolean accept(MultiValueMap params);
}

And 3 implementations:

@RequiredArgsConstructor
public class JavaTopicGenerator implements TopicGenerator {
//implementation ommited for readability

@RequiredArgsConstructor
public class PhpTopicGenerator implements TopicGenerator {
//implementation ommited for readability

@RequiredArgsConstructor
public class CppTopicGenerator implements TopicGenerator {
//implementation ommited for readability

Now what I try to do is use them depending on my params thats why I created special TopicFacade.

@RequiredArgsConstructor
public class TopicFacade {

    @NonNull
    private final TopicService topicService;

    @NonNull
    private final List<TopicGenerator> topicGenerators;

    public void generate(MultiValueMap<String, String> params, HttpServletResponse response) {
        for (TopicGenerator topicGenerator : topicGenerators) {
            if (topicGenerator.accept(params)) {
                File tempFile = topicService.generate(params);
                //do something else.
            }
        }
    }
}

Where on my TopicServiceImpl I've got:

@Service
@RequiredArgsConstructor
public class TopicServiceImpl implements TopicService {

    @NonNull
    private final List<TopicGenerator> reportGenerators;

        public File generate(MultiValueMap params) {
        for (TopicGenerator topicGenerator : topicGenerators) {
            if (topicGenerator.create(params)) {
                return topicGenerator.export(params);
            }
        }

I get an error like: Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'topicServiceImpl': Unsatisfied dependency expressed through field 'topicGenerators'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.List<com.topic.service.TopicGenerator>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

(Earlier when I was using field injection instead of constructor I was able to add @Service before one of the 3 implementation and code was working was working on that single implementation, but its not what I'm looking for)

degath
  • 1,530
  • 4
  • 31
  • 60
  • 1
    Where are `CppTopicGenerator` etc actually declared as *beans*? I just see classes. You need to annotate them with a stereotype, or use a `@Configuration` class. – Michael Nov 14 '18 at 13:57
  • @Michael I tried to annotate them with a stereotype (before those 3 classes I tried Service annotation, or Component annotation), had an error Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type TopicFacade available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} – degath Nov 14 '18 at 14:07
  • @degath you have to annotate *every* class with a stereotype if you want to use dependency injection. So every `TopicGenerator`, `Service` and `Facade` needs a stereotype annotation – Lino Nov 14 '18 at 14:07
  • @Lino by `every` you mean those 3? Right? Check my last comment then. I did that and had an error. I tried Service and Component annotations. – degath Nov 14 '18 at 14:10
  • Ohhhh. Give me a second to check it. – degath Nov 14 '18 at 14:10
  • @degath with every I mean every class you want to use. `No qualifying bean of type TopicFacade` means that you have to annotate `TopicFacade` also with e.g. `@Service` – Lino Nov 14 '18 at 14:11
  • I did it as you mentioned and now I have an `Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'topicServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?` – degath Nov 14 '18 at 14:13
  • 1
    `Is there an unresolvable circular reference` means that you have a class `A` which uses class `B` and `B` uses `A`. You'd have to think about your application structure if thats the case – Lino Nov 14 '18 at 14:16
  • I'll think about application structure now. Seems like I know everything for now. Really, thanks. :) – degath Nov 14 '18 at 14:24
  • @Lino After fixing circular error I have right now `Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.topic.service.TopicGenerator' available: expected single matching bean but found 3: javaTopicGenerator,phpTopicGenerator ,cppTopicGenerator` – degath Nov 14 '18 at 14:35

2 Answers2

3

Can define a bean of type List<TopicGenerator> like below :

@Configuration
public class AppConfig {

    @Autowired
    private TopicGenerator cppTopicGenerator;

    @Autowired
    private TopicGenerator phpTopicGenerator;

    @Autowired
    private TopicGenerator javaTopicGenerator;

    @Bean
    public List<TopicGenerator> topicGeneratorList()
    {
        return Arrays.asList(cppTopicGenerator, phpTopicGenerator, javaTopicGenerator);
    }
}

With this your original code should work fine.

Beans can be referred in camelCase of their respective Class Name. For e.g. cppTopicGenerator will refer to the bean of class CppTopicGenerator.java. Although it's good practice to use @Qualifier to be more clear.

bittu
  • 786
  • 7
  • 14
0

Use qualifier annotation. https://www.tutorialspoint.com/spring/spring_qualifier_annotation.htm

https://stackoverflow.com/a/40844528/4587961

Then your Facade can have all these implementations. And you have a method which will return specific implementation depending on your condition.

Then initialize your list after you autowire all dependencies. https://stackoverflow.com/a/8519295/4587961

@Service // Try and play around with annotations.
public class TopicFacade {

    @Autowired
    @Qualifier("Service1")
    private final TopicService topicService1;

    //... The same stuff for other services.

    @Autowired
    @Qualifier("ServiceN")
    private final TopicService topicServiceN;

    //Initialize this in the post construct and put all your services there.
    @NonNull
    private final List<TopicGenerator> topicGenerators = new ArrayList();

    @PostConstruct
    public void initTopicGenerators() throws Exception {
        topicGenerators.add(topicService1);
        //And others.
        topicGenerators.add(topicServiceN);
    }

    public void generate(MultiValueMap<String, String> params, HttpServletResponse response) {
        for (ReportGenerator reportGenerator : reportGenerators) {
            if (reportGenerator.accept(params)) {
                File tempFile = topicService.generate(params);
                //do something else.
            }
        }
    }
}
Yan Khonski
  • 12,225
  • 15
  • 76
  • 114
  • Can you give an example of this: `//Initialize this in the post construct and put all your services there.` Because I actually don't really know how to do that. – degath Nov 14 '18 at 15:05
  • https://www.mkyong.com/spring/spring-postconstruct-and-predestroy-example/ – Yan Khonski Nov 14 '18 at 15:16
  • I know how to use post construct to do things like run some method, but don't know how to `put all my services there` and `initialize` topicGenerators – degath Nov 14 '18 at 15:18
  • Thanks, gonna check that asap. – degath Nov 14 '18 at 15:20
  • Also move the list with services into `TopicFacade` class. You do not need them in `TopicServiceImpl` – Yan Khonski Nov 14 '18 at 15:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183652/discussion-between-yan-khonski-and-degath). – Yan Khonski Nov 14 '18 at 18:16