¿Cómo Autowire Bean de tipo genérico <T> en Spring?

81

Tengo un bean Item<T>que debe estar conectado automáticamente en una @Configurationclase.

@Configuration
public class AppConfig {

    @Bean
    public Item<String> stringItem() {
        return new StringItem();
    }

    @Bean
    public Item<Integer> integerItem() {
        return new IntegerItem();
    }

}

Pero cuando lo intento @Autowire Item<String>, obtengo la siguiente excepción.

"No qualifying bean of type [Item] is defined: expected single matching bean but found 2: stringItem, integerItem"

¿Cómo debo Autowire tipo genérico Item<T>en Spring?

usuario3374518
fuente

Respuestas:

144

La solución simple es actualizar a Spring 4.0, ya que considerará automáticamente los genéricos como una forma de @Qualifier, como se muestra a continuación:

@Autowired
private Item<String> strItem; // Injects the stringItem bean

@Autowired
private Item<Integer> intItem; // Injects the integerItem bean

De hecho, incluso puede conectar automáticamente los genéricos anidados al inyectar en una lista, como se muestra a continuación:

// Inject all Item beans as long as they have an <Integer> generic
// Item<String> beans will not appear in this list
@Autowired
private List<Item<Integer>> intItems;

¿Cómo funciona esto?

La nueva ResolvableTypeclase proporciona la lógica de trabajar realmente con tipos genéricos. Puede usarlo usted mismo para navegar y resolver fácilmente la información de tipo. La mayoría de los métodos en ResolvableTypeellos mismos devolverán un ResolvableType, por ejemplo:

// Assuming 'field' refers to 'intItems' above
ResolvableType t1 = ResolvableType.forField(field); // List<Item<Integer>> 
ResolvableType t2 = t1.getGeneric(); // Item<Integer>
ResolvableType t3 = t2.getGeneric(); // Integer
Class<?> c = t3.resolve(); // Integer.class

// or more succinctly
Class<?> c = ResolvableType.forField(field).resolveGeneric(0, 0);

Consulte los ejemplos y tutoriales en los siguientes enlaces.

Espero que esto te ayude.

Shishir Kumar
fuente
10
Esto es increíble. Pensé que Type Erasure hacía que este tipo de cosas no fuera posible.
John Strickler
5
¿Cómo puedo obtener bean por resolvabletype de beanfactory?
ceram1
@JohnStrickler También pensé que esto no se puede hacer de manera segura. Todavía no he comprobado la implementación de ResolvableType, pero ¿significa que ahora podemos obtener el tipo genérico de forma segura?
xi.lin
2
En realidad, en algunos casos puede obtener un tipo genérico en tiempo de ejecución en Java. Una condición, debe tener una subclase de clase genérica. Ejemplo: public class Parent<T> {}subclase de clase principal public class Child extends Parent<Integer> { }y ahora: Child c = new Child(); System.out.println(((ParameterizedType)c.getClass().getGenericSuperclass()).getActualTypeArguments()[0]);se imprimiráclass java.lang.Integer
Michał Przybylak
1
¿Funcionará esto con la configuración automática de beans de escaneo de paquetes? No lo creo.
Preslav Rachev
12

Si no desea actualizar a Spring 4, debe realizar el autowire por nombre como se muestra a continuación:

@Autowired
@Qualifier("stringItem")
private Item<String> strItem; // Injects the stringItem bean

@Autowired
@Qualifier("integerItem")
private Item<Integer> intItem; // Injects the integerItem bean
Rishav Basu
fuente
¿Quiere trabajar con concreto StringItemy IntegerItemclases directamente?
user3374518
2

A continuación se muestra una solución que hice para responder a esta pregunta:


        List<String> listItem= new ArrayList<>();

        ResolvableType resolvableType = ResolvableType.forClassWithGenerics(List.class, String.class);
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setTargetType(resolvableType);
        beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        beanDefinition.setAutowireCandidate(true);

        DefaultListableBeanFactory bf = (DefaultListableBeanFactory) configurableWebApplicationContext.getBeanFactory();

        bf.registerBeanDefinition("your bean name", beanDefinition);
        bf.registerSingleton("your bean name", listItem);
howard791006
fuente
Por cierto en Spring 5+
howard791006
1

La estrategia Spring Autowired se define en su archivo de configuración (application.xml).

si no lo ha definido, el valor predeterminado es por Tipo, la inyección de resorte usa el mecanismo de reflexión JDK.

entonces List? String? y List? Item ?, el tipo es el mismo List.class, por lo que Spring confundió cómo inyectar.

y como la respuesta de las personas anteriores, debe apuntar a @Qualifier para decirle a Spring qué bean debe inyectarse.

Me gusta el archivo de configuración de primavera para definir el frijol en lugar de la Anotación.

<bean>
 <property name="stringItem">
        <list>
              <....>
        </list>
 </property>

zg_spring
fuente
0

Spring 4.0 es la respuesta con el uso de la anotación @Qualifier. Espero que esto ayude

shikjohari
fuente
0

Creo que no tiene nada que ver con los genéricos ... Si está inyectando dos beans diferentes del mismo tipo, debe proporcionar un calificador para ayudar a Spring a identificarlos;

... En otro lugar

@Configuration
@Bean
public Item stringItem() {
    return new StringItem();
}

@Bean
public Item integerItem() {
    return new IntegerItem();
}

Si tiene declaraciones no genéricas como estas, debe agregar un calificador para ayudar a Spring a identificarlas ...

@Autowired
**@Qualifier("stringItem")**
private Item item1;

@Autowired
**@Qualifier("integerItem")**
private Item item2;

Por supuesto, en las versiones 4 y superiores, Spring considera los tipos genéricos a través de los resolutores, lo cual es muy bueno ...

Ramkumar S
fuente