Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singleton Bean instance by generic parameter

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 ?

like image 457
Basemasta Avatar asked Mar 19 '23 14:03

Basemasta


2 Answers

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.

like image 80
Sotirios Delimanolis Avatar answered Mar 23 '23 22:03

Sotirios Delimanolis


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

like image 43
Ricardo Veguilla Avatar answered Mar 23 '23 22:03

Ricardo Veguilla