0

I am working on a batch written with Spring Batch and I have a reader/writer components which can easily be reused for multiple instantations of the same process. The reader and writer are both generic and rely on an interface for their functionality, but the generic type can not be autowired for some wierd reason.Here is my setup.

I have an interface, which describes the step to be executed like this, and I have 11 implementations of this interface:

import java.util.List;

public interface Process<E> {

    List<E> determineChanges(List<Integer> approvalIds);

    void applyChanges(List<E> changeCto);
}

Next I have defined the reader and writer implementations as follows:

import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@StepScope
public class GenericApprovalReader<T extends Process<U>, U> implements ItemReader<List<U>> {

    private final SomeOtherBeanStepScopedBean otherBean;

    private final T processor;

    @Autowired
    public GenericApprovalReader(SomeOtherBeanStepScopedBean otherBean,
                                 T processor) {
        this.SomeOtherBean = otherBean;
        this.processor= processor;
    }

    @Override
    public List<U> read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        List<Integer> items = this.someOtherBean.read();

        if(items != null) {
            return processor.determineChanges(items );
        } else {
            return null;
        }
    }
}

And similarly I have a writer:

import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@StepScope
// Note that second type parameter is necessary only due to necessity to parametrize the ItermWriter<List<U>> with the
// same type as is used for parametrization of Approval<U>
public class GenericApprovalWriter<T extends Process<U>, U> implements ItemWriter<List<U>> {

    private final T processor;

    @Autowired
    public GenericApprovalWriter(T processor) {
        this.approvalProcessor = processor;
    }

    @Override
    public void write(List<? extends List<U>> items) throws Exception {
        for(List<U> item: items) {
            this.processor.applyChanges(item);
        }
    }
}

Now when I try to wire up the readers and writers in my Step in some @Configuration class as follows:

    @Bean(name="firstProcessingStep")
    Step firstProcessingStep(GenericApprovalReader<FirstProcessor, Item> reader,
                                   GenericApprovalWriter<FirstProcessor, Item> writer) {
        return stepBuilderFactory.get("firstProcessingStep")
                .<List<Item>,List<Item>>chunk(CHUNK_SIZE)
                .reader(reader)
                .writer(writer)
                .allowStartIfComplete(false)
                .build();
    }

I get the following exception:

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type '?' available: expected single matching bean but found 11

Why am I getting this error? Why is Spring not able to construct the proper generic instances of the writer and reader, despite the concrete types being specified?

According to this other post about Autowiring of generic beans in Spring this behavior should be possible after Spring 4.0, which I am using.

TheCoolDrop
  • 796
  • 6
  • 18

1 Answers1

-1

expected single matching bean but found occurs when the bean is auto-wired that matches two or more loaded beans in the spring boot application context. When the bean is auto-wired in the spring boot, two or more beans are found in the context. As a result, the bean could not be auto-wired. 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 expected single matching bean but found n.

the class should be annotated with @Primary. The annotated @Primary class is considered to be the default class in the auto-wire. The @Autowired annotation loads the @Primary bean and adds an interface or abstract variable to it.

Change your code like this.

@Component
@Primary    
@StepScope
public class GenericApprovalReader<T extends Process<U>, U> implements ItemReader<List<U>> {
//content

OR

@Component
@Primary    
@StepScope
 GenericApprovalWriter<T extends Process<U>, U> implements ItemWriter<List<U>> {

//content