Claudio D'Angelo opened SPR-12180 and commented

I have a controller class with a field List<String> autowired:

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

In my configuration I've wrote:

<util:list id="publicViews" value-type="java.lang.String">
    <value >gestione</value>
    <value >inserimento</value>
    <value >dettaglio</value>
</util:list>
<context:component-scan
    base-package="it.lispa.sire.tributi.aper_pdt.au.cicloquad.controllers" />

When the application start spring throw an error:

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying 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=publicViews)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:997)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:825)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:779)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:490)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287)
....

In the DefaultListableBeanFactory the search for autowire candidate get the collection type and use this type to search the candidate:

else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
            Class<?> elementType = descriptor.getCollectionType();
            if (elementType == null) {
                if (descriptor.isRequired()) {
                    throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]");
                }
                return null;
            }
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor);

I think that the system must search candidate for the type variable and not for elementType. In my issue the elementType is String not java.util.List.


Affects: 3.2.11

Issue Links: - #17020 Can't inject a bean of a collection type ("is duplicated by") - #15894 Make spring support like CDI @Produces (for Array/Map/Collection inject) ("is duplicated by") - #12570 Allow for normal bean wiring semantics for types assignable to Map - #13096 Support @Autowired-like self injection - #19532 Self reference fallback in 4.3 is not meant to apply to collection elements - #19164 NoSuchBeanDefinitionException message shows internal array class names - #18904 Doc: Constructor injection of arrays and collections - #18162 Cannot inject List even using @Named

0 votes, 8 watchers

Comment From: spring-projects-issues

Juergen Hoeller commented

I'm afraid this is by design: Regular autowiring treats collections and map specifically - as collections of target beans, in the case of maps with the target bean names as keys. Injecting a target bean which is a collection or map itself doesn't work with @Autowired for that reason. As an immediate alternative, consider using @Resource for a named target bean which may be a collection or map as well, as you'd expect it. Using @Value with an expression that points to the target bean would work too.

Note that injecting a target bean which holds a nested collection or map - e.g. defined as an inner bean for a list property of an outer bean - works just fine with @Autowired as well. The problematic case is just where the top-level target bean is a collection or map itself.

That said, we can consider a specific flag on @Autowired or a separate annotation which keeps using by-type and qualifier resolution but works for collection or map target beans as well. With the upcoming revision of JSR-330 (@Inject) next year, we might be able to align there as well.

Hope that helps for the time being,

Juergen

Comment From: spring-projects-issues

Christopher Smith commented

I understand that the autowiring does treat collection types that way, but I don't see why it normatively should. The behavior that would surprise me least for this type of injection would be:

  • If one or more qualifying beans of the element type are available but no collection of that element, create a collection and insert all the found beans.
  • If no qualifying beans of the element type are available but a matching collection of that element is, inject the collection.
  • If both "loose" qualifying beans and a collection are found, and the collection is not annotated @Primary, throw for duplicate.
  • If no loose beans or collection are found, throw for no bean unless an exception applies (e.g., #13771).

It would also potentially be useful to be able to fold collections; my use case involves a list of String header names, and each client component might want to add multiple to the list. Using independent String beans is a bit clumsy in a case like that, and it would be clearly understandable for the consuming interface to say that it would merge all the candidates.

Comment From: spring-projects-issues

Christopher Smith commented

I have another use case where this limitation is causing problems. I have a number of AWS keypairs that are injected as collection into an @ConfigurationProperties class. I need to process these into AWSCredentials objects, of which there are multiple, and as I can't easily return multiple beans from a JavaConfig @Bean method, I'm returning a Map<String,AWSCredentials> which is properly resolvable by name, but which can't be injected, and whose values can't be injected.

Comment From: spring-projects-issues

Christopher Smith commented

I am really looking forward to the API conveniences in 4.3. Are the new semantics of collection injection documented yet?

Comment From: spring-projects-issues

Juergen Hoeller commented

Not yet. In summary, Map injection tries the multiple-beans behavior first and then falls back to the resolution of an assignable Map bean. Along the same lines, it also detects Collection and array beans and falls back to a self reference if not resolvable otherwise.

I would not recommend arrangements that play with those semantics in too subtle a fashion. The main goal is to keep existing contexts working while also allowing straightforward arrangements that rely on the new variants. In case of ambiguities, please use qualifiers...

Feel free to play with it in the latest 4.3.0.BUILD-SNAPSHOT :-)

Juergen

Comment From: dk2k

Workaround. Try this: @Autowired() @Qualifier("publicViews") private Object publicViews; and then cast to List

Comment From: sbrannen

If you have shown interest in this issue, you may also be interested in the following which is currently scheduled for inclusion in Spring Framework 6.1.

  • 30022