简体   繁体   中英

Spring - autowiring generic implementation of generic interface

I've got small question. It's probably trivial, but I've never faced it before.

I have generic interface and a generic implementation of it. I wanted to autowire it, but an error occured. Here are the details:

Interface

@Service
public interface Serializing<T extends Serializable> {
    String serialize(T toBeSerialized);

    T deserialize(String toBeDeserialized, Class<T> resultType);
}

Implementation

@Service
public class JsonSerializer<T extends Serializable> implements Serializing<T> {
   /** code **/
}

Autowire attempt

private NoteDAO noteDAO;

@Qualifier("jsonSerializer")
private Serializing<UserComment> serializer;

@Autowired
public NoteController(NoteDAO noteDAO, Serializing<UserComment> serializer) {
    this.noteDAO = noteDAO;
    this.serializer = serializer;
}

Error

Parameter 1 of constructor in somepackagepath.NoteController required a bean of type 'anotherpackagepath.Serializing' that could not be found.

I want to keep it as simple as possible. I've check the web, but I've found only about defining my exact beans in configuration. I prefer to avoid it if possible.

In your specific case , Spring doesn't allow to use a generic type to be wired as dependency such as :

@Autowired
public NoteController(NoteDAO noteDAO, Serializing<UserComment> serializer) {
    this.noteDAO = noteDAO;
    this.serializer = serializer;
}

for a very simple reason : consistency.
This dependency that you made a Spring bean with @Service :

@Service
public class JsonSerializer<T extends Serializable> implements Serializing<T> {
   /** code **/
}

can be wired into other beans.

Imagine that the beans that depend on the Serializing instance don't use the same generic : Serializing<UserComment> in Foo and Serializing<UserQuestion> in Bar such as :

public class Foo{

    @Autowired
    public Foo(NoteDAO noteDAO, Serializing<UserComment> serializer) {
        this.noteDAO = noteDAO;
        this.serializer = serializer;
    }

}
public class Bar{

    @Autowired
    public Bar(NoteDAO noteDAO, Serializing<UserQuestion> serializer) {
        this.noteDAO = noteDAO;
        this.serializer = serializer;
    }

}

Here the Serializing object is the same but each bean declares a distinct generic on.
So it would break the type safety of the generic type.


In fact, erasure of the generics is not the real problem there as Spring (from Spring 4) owns a Resolver able to resolve the type :

behind the scenes, the new ResolvableType class provides the logic of actually working with generic types. You can use it yourself to easily navigate and resolve type information. Most methods on ResolvableType will themselves return a ResolvableType

And before Spring 4 you also had other workaround to accept generic types in the bean dependencies.
The real problem is that you annotated a generic class with @Service to make it a bean while it is an instance of this generic class that has to be configured as a bean.

So to achieve what you want to do, declare the JsonSerializer beans that you want to instantiate in a @Configuration class :

@Configuration
public class SerializingBeans {

    @Bean
    public JsonSerializer<UserComment> userCommentSerializer() {
        return new JsonSerializer<UserComment>();
    }

    @Bean
    public JsonSerializer<UserAnswer> userAnswerSerializer() {
        return new JsonSerializer<UserAnswer>();
    }
}

You can now wire the dependency as a generic type :

@Service
public class NoteController {

    private Serializing<UserComment> userCommentSerializer;

    @Autowired
    public NoteController(Serializing<UserComment> serializer) {
        this.userCommentSerializer = serializer;

    }
}

It's related to java's type erasure : https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

In java, you can'nt know the generic type at runtime. The only workarround is to use anonymous classes or inherited classes with non-generic type : Inject anonymous classes with spring

You have to tell Spring about all the beans you want it to autowire. No exceptions.

If you cannot spell it out beforehand, then I'd say it's not a candidate for injection.

Somethings you need to have full control and call new . Maybe your case is one of those. There's nothing wrong with that; it just means that Spring DI isn't appropriate for that use case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM