El cableado automático de una lista utilizando el esquema util da NoSuchBeanDefinitionException

90

Tengo un bean que quiero inyectar con una lista con nombre usando el espacio de nombres Spring util, <util:list id="myList">pero Spring está buscando una colección de beans de tipo String. Mi prueba rota es:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ListInjectionTest {

    @Autowired @Qualifier("myList") private List<String> stringList;

    @Test public void testNotNull() {
        TestCase.assertNotNull("stringList not null", stringList);
    }
}

Mi contexto es:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:util="http://www.springframework.org/schema/util"
   xmlns="http://www.springframework.org/schema/beans"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">

   <util:list id="myList">
       <value>foo</value>
       <value>bar</value>
   </util:list>

</beans>

Pero consigo

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [java.lang.String] found for dependency [collection of java.lang.String]: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=myList)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:726)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:571)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:412)

Lo que me desconcierta bastante, ya que pensé que esta sería la forma en que se esperaba que funcionara.

Paul McKenzie
fuente

Respuestas:

171

Esto se debe a una parte bastante oscura del comportamiento de @ Autowired, especificado en 3.11.2. @Autowired :

También es posible proporcionar todos los beans de un tipo particular desde el ApplicationContextagregando la anotación a un campo o método que espera una matriz de ese tipo ...

Lo mismo se aplica a las colecciones mecanografiadas ...

En otras palabras, al decir @Autowired @Qualifier("myList") List<String>, en realidad estás pidiendo "dame la lista de todos los beans de tipo java.lang.Stringque tienen el calificador" myList ".

La solución se menciona en 3.11.3. Ajuste del cableado automático basado en anotaciones con calificadores :

Si tiene la intención de expresar la inyección basada en anotaciones por nombre, no la utilice principalmente @Autowired, incluso si es técnicamente capaz de hacer referencia a un nombre de bean a través de @Qualifier valores. En su lugar, prefiera la @Resource anotación JSR-250 que se define semánticamente para identificar un componente objetivo específico por su nombre único, siendo el tipo declarado irrelevante para el proceso de coincidencia.

Como consecuencia específica de esta diferencia semántica, los beans que se definen a sí mismos como una colección o un tipo de mapa no pueden inyectarse a través de ellos, @Autowiredya que la coincidencia de tipos no se les aplica correctamente. Úselo @Resourcepara dichos beans, refiriéndose al bean de colección / mapa específico por un nombre único.

Entonces usa esto en tu prueba, y funciona bien:

@Resource(name="myList") private List<String> stringList;
skaffman
fuente
9
Usted es una vida más segura y también lo es stackoverflow.com. :)
Rihards
5
Le daría diez votos si pudiera. Eres Da Man, skaffman.
duffymo
3
El hecho de que muchos se hayan confundido por este medio con la semántica es realmente confuso. Me pregunto cuál fue la razón detrás de un diseño tan confuso.
supertonsky
3
Vaya, considero a Spring como una de mis áreas más fuertes en la programación y nunca me he encontrado con este problema hasta hoy y me ahorraste un montón de tiempo. ¡Gracias!
Avi
2
¿Alguna sugerencia sobre cómo hacer que esto funcione con la inyección de constructor / método, diciendo que @Resource no admite el parámetro?
cjbooms
0

Otra cosa que podría estar sucediendo es que está conectando automáticamente una propiedad de un bean. En tal caso, no es necesario conectarlo automáticamente, solo cree el método setter y use la etiqueta de propiedad en el ejemplo de definición de bean (cuando se usa xml):

<bean id="cleaningUpOldFilesTasklet" class="com.example.mypackage.batch.tasklets.CleanUpOldFilesTasklet">
    <property name="directoriesToClean">
        <list>
            <value>asfs</value>
            <value>fvdvd</value>
            <value>sdfsfcc</value>
            <value>eeerer</value>
            <value>rerrer</value>
        </list>
    </property>
</bean>

Y la clase:

public class CleanUpOldFilesTasklet extends TransferingFilesTasklet implements Tasklet{

private long pastMillisForExpiration;
private final String dateFormat = "MM.dd";
Date currentDate = null;

List<String> directoriesToClean;

public void setDirectoriesToClean(List<String> directories){
    List<String> dirs = new ArrayList<>();
    for(String directory : directories){
        dirs.add(getSanitizedDir(directory));
    }
    this.directoriesToClean = dirs;
}

Mira, no hay @Autowiredanotaciones en clase.

juliangonzalez
fuente