Le câblage automatique d'une liste à l'aide du schéma util donne NoSuchBeanDefinitionException

90

J'ai un bean que je veux injecter avec une liste nommée à l'aide de l'espace de noms Spring util <util:list id="myList">mais Spring recherche plutôt une collection de beans de type String. Mon test cassé est:

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

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

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

Mon contexte est:

<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>

Mais je reçois

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)

Ce qui me laisse plutôt perplexe car je pensais que ce serait la façon dont cela devrait fonctionner.

Paul McKenzie
la source

Réponses:

171

Cela est dû à une partie plutôt obscure du comportement de @ Autowired, spécifié dans 3.11.2. @Autowired :

Il est également possible de fournir tous les beans d'un type particulier à partir de ApplicationContexten ajoutant l'annotation à un champ ou à une méthode qui attend un tableau de ce type ...

Il en va de même pour les collections typées ...

En d'autres termes, en disant @Autowired @Qualifier("myList") List<String>, vous demandez en fait "donnez-moi la liste de tous les beans de type java.lang.Stringqui ont le qualificatif" myList ".

La solution est mentionnée en 3.11.3. Affiner le câblage automatique basé sur les annotations avec des qualificatifs :

Si vous avez l'intention d'exprimer une injection basée sur des annotations par nom, ne l'utilisez pas principalement @Autowired- même si elle est techniquement capable de faire référence à un nom de bean via des @Qualifier valeurs. Au lieu de cela, préférez l' @Resource annotation JSR-250 qui est définie sémantiquement pour identifier un composant cible spécifique par son nom unique, le type déclaré n'étant pas pertinent pour le processus de correspondance.

Conséquence spécifique de cette différence sémantique, les beans qui sont eux-mêmes définis comme un type de collection ou de carte ne peuvent pas être injectés via @Autowiredcar la correspondance de type ne leur est pas correctement applicable. À utiliser @Resourcepour de tels beans, en faisant référence au bean collection / map spécifique par un nom unique.

Alors utilisez ceci dans votre test, et cela fonctionne bien:

@Resource(name="myList") private List<String> stringList;
skaffman
la source
9
Vous êtes une vie plus sûre, tout comme stackoverflow.com! :)
Rihards
5
Je donnerais dix votes si je pouvais. Vous êtes Da Man, skaffman.
duffymo
3
Le fait que beaucoup aient été confus par cela signifie que la sémantique est vraiment déroutante. Je me demande quelle était la raison derrière un design aussi déroutant.
supertonsky
3
Wow, je considère le printemps comme l'un de mes domaines les plus forts en programmation et je n'ai jamais rencontré ce problème jusqu'à aujourd'hui et vous m'avez fait gagner beaucoup de temps. Merci!
Avi
2
Une suggestion sur la façon de faire fonctionner cela avec l'injection de constructeur / méthode, en disant que @Resource ne prend pas en charge le paramètre?
cjbooms
0

Une autre chose qui pourrait se produire est que vous utilisez automatiquement une propriété d'un bean. Dans ce cas, vous n'avez pas besoin de le câbler automatiquement, mais créez simplement la méthode setter et utilisez la balise property dans l'exemple de définition du bean (lors de l'utilisation de 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>

Et la classe:

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;
}

Voir, aucune @Autowiredannotation dans la classe.

juliangonzalez
la source