4

I would like to have a singleton bean instance by generic parameter based on a single @Component generic class.

(I am using Spring 4.)

My code :

I have an interface like this :

public interface Mapper<I, O> {
    ...
}

And multiple implementation of it which are Spring @Components (singletons). Something like this :

@Component
public class MapperA implements Mapper<ClazzAI, ClazzAO> {
    ...
}

and

@Component
public class MapperB implements Mapper<ClazzBI, ClazzBO> {
    ...
}

where ClazzAI, ClazzAO, ClazzBI and ClazzBO are basic Java classes.

I have another Spring @Component (singleton) which have a Mapper class as a generic parameter :

@Component
public class TransformerImpl<I, O, M extends Mapper<I, O>> {

    /** The Mapper */
    protected final M mapper;

    @Inject
    private TransformerImpl(final M mapper) {

        this.mapper= mapper;
    }

    ...
}

and I would like to use it like this :

@Inject
private TransformerImpl<ClazzAI, ClazzAO, MapperA> transformerA;

@Inject
private TransformerImpl<ClazzBI, ClazzBO, MapperB> transformerB;

The problem :

But Spring is not able to instantiate those 2 objects because it founds 2 implementations of Mapper : MapperA and MapperB even if I specify which implementation I want as a generic parameter.

Any idea how to make it without the need of instantiate all of those beans in a @Configuration class ?

Tiny
  • 27,221
  • 105
  • 339
  • 599
Basemasta
  • 381
  • 1
  • 13

2 Answers2

5

You're asking for a singleton but requiring two injection points

@Inject
private TransformerImpl<ClazzAI, ClazzAO, MapperA> transformerA;

@Inject
private TransformerImpl<ClazzBI, ClazzBO, MapperB> transformerB;

for differently constructed objects. That doesn't make much sense.

You now realize you need two beans. If you can't (don't want to) do it in a @Configuration class with @Bean factory methods, you'll need to declare (and scan) two separate @Component classes. (I made your parent constructor public here.)

@Component
class MapperATransformerImpl extends TransformerImpl<ClazzAI, ClazzAO, MapperA> {
    @Inject
    public MapperATransformerImpl(MapperA mapper) {
        super(mapper);
    }
}

@Component
class MapperBTransformerImpl extends TransformerImpl<ClazzBI, ClazzBO, MapperB> {
    @Inject
    public MapperBTransformerImpl(MapperB mapper) {
        super(mapper);
    }
}

When processing the injection target

@Inject
private TransformerImpl<ClazzAI, ClazzAO, MapperA> transformerA;

Spring will find the MapperATransformerImpl, which is of type TransformerImpl<ClazzAI, ClazzAO, MapperA> and inject that.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
2

Try with Spring 4. See Using generics as autowiring qualifiers

Edit

Like @SotiriosDelimanolis explained in his answer, Spring 4 can use type parameter information as qualifiers to select which bean definition matches a particular injection point, but in the end, it will only match against bean definition with concrete type definitions. In your case, the problem is that you need a TransformerImpl bean definition for each concrete type you want to inject.

As an alternative to defining all bean definition explicitly, check my answer to Spring autowiring issues on paramaterized class

Community
  • 1
  • 1
Ricardo Veguilla
  • 3,107
  • 1
  • 18
  • 17
  • Opps, I missed the part where you mentioned you are already using Spring 4. – Ricardo Veguilla Sep 03 '14 at 16:49
  • The construction of the `TransformerImpl` bean is independent of the `@Inject` injection points that need it. As such, Spring has no idea which `Mapper` bean to use here and will complain that 2 of them exist. – Sotirios Delimanolis Sep 03 '14 at 17:43
  • @SotiriosDelimanolis, After reading your answer I realized I answered a similar question before. I just edited my answer with the explanation. You are, of course, right, the change I proposed before wouldnt make a difference. – Ricardo Veguilla Sep 03 '14 at 17:48
  • I implemented a `BeanDefinitionRegistryPostProcessor` as you suggest. But it seems like it only allows Spring to **instantiate** the `TransformerImpl` beans (which is great by the way) but not telling Spring **how to inject them**. I am forced to use the `@Qualifier` annotation in order to tell Spring how to inject them based of the naming convention have I defined in the `BeanDefinitionRegistryPostProcessor`. Any idea how to solve this in a more proper way ? – Basemasta Sep 05 '14 at 08:12