A fiação automática de uma lista usando o esquema util dá NoSuchBeanDefinitionException

90

Eu tenho um bean que quero injetar com uma lista nomeada usando o namespace do utilitário <util:list id="myList">Spring, mas o Spring está procurando uma coleção de beans do tipo String. Meu teste quebrado é:

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

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

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

Meu contexto é:

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

Mas eu 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)

O que me intriga um pouco, pois imaginei que seria assim que deveria funcionar.

Paul McKenzie
fonte

Respostas:

171

Isso se deve a uma parte bastante obscura do comportamento de @Autowired, especificada em 3.11.2. @Autowired :

Também é possível fornecer todos os beans de um tipo específico do ApplicationContextadicionando a anotação a um campo ou método que espera um array desse tipo ...

O mesmo se aplica a coleções digitadas ...

Em outras palavras, ao dizer @Autowired @Qualifier("myList") List<String>, você está realmente pedindo "dê-me a lista de todos os beans do tipo java.lang.Stringque têm o qualificador" myList ".

A solução é mencionada em 3.11.3. Ajuste automático baseado em anotação com qualificadores :

Se você pretende expressar a injeção orientada por anotação por nome, não use principalmente @Autowired- mesmo que seja tecnicamente capaz de se referir a um nome de bean por meio de @Qualifier valores. Em vez disso, prefira a @Resource anotação JSR-250, que é semanticamente definida para identificar um componente de destino específico por seu nome exclusivo, com o tipo declarado sendo irrelevante para o processo de correspondência.

Como uma consequência específica dessa diferença semântica, os beans que são eles próprios definidos como uma coleção ou tipo de mapa não podem ser injetados via @Autowireduma vez que a correspondência de tipo não é apropriadamente aplicável a eles. Use @Resourcepara esses beans, referindo-se ao bean de coleção / mapa específico por nome exclusivo.

Portanto, use isso em seu teste e funcionará bem:

@Resource(name="myList") private List<String> stringList;
skaffman
fonte
9
Você tem uma vida mais segura e stackoverflow.com também! :)
Rihards
5
Eu daria dez votos se pudesse. Você é Da Man, skaffman.
duffymo
3
O fato de muitos terem sido confundidos com isso significa que a semântica é realmente confusa. Eu me pergunto qual foi a razão por trás de um design tão confuso.
supertonsky
3
Uau, considero o Spring uma das minhas áreas mais fortes em programação e nunca encontrei esse problema até hoje e você me economizou muito tempo. Obrigado!
Avi de
2
Alguma sugestão de como fazer isso funcionar com injeção de construtor / método, dizendo que @Resource não suporta parâmetro?
cjbooms
0

Outra coisa que pode estar acontecendo é que você está autowiring uma propriedade de um bean. Nesse caso, você não precisa fazer o autowire, apenas crie o método setter e use a tag de propriedade no exemplo de definição de bean (ao usar 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>

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

Veja, nenhuma @Autowiredanotação na aula.

Juliangonzalez
fonte