Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Autowired does not work for target bean of type Collection [SPR-12180] #16794

Closed
spring-projects-issues opened this issue Sep 11, 2014 · 7 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Sep 11, 2014

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:

0 votes, 8 watchers

@spring-projects-issues
Copy link
Collaborator Author

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

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Nov 6, 2014

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., Allow autowiring of empty collection in @Bean method parameters using java.util.Optional [SPR-9132] #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.

@spring-projects-issues
Copy link
Collaborator Author

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.

@spring-projects-issues
Copy link
Collaborator Author

Christopher Smith commented

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

@spring-projects-issues
Copy link
Collaborator Author

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

@dk2k
Copy link

dk2k commented Oct 4, 2019

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

@sbrannen
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants