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

Collection autowiring does not resolve field-level type variable against containing class [SPR-15160] #19726

Closed
spring-issuemaster opened this issue Jan 18, 2017 · 2 comments

Comments

@spring-issuemaster
Copy link
Collaborator

commented Jan 18, 2017

thomas weidner opened SPR-15160 and commented

Spring fails to autowire fields correctly, if the field contains a generic type variable, which is later bound. It does not matter if the type variable is bound by deriving a concrete (not generic) type or just defining a bean of the generic type with fixed type parameters.

Spring either finds not all beans or too many beans, whereas the second case is especially troublesome as it breaks static type checking.

Consider two beans of types Foo and Bar both implementing an interface Base. Moreover, we have a type BeanCollector<C> which autowires a single field of type List<C>, a field of type C and prints both values after construction. Additionally, a BaseBeanCollector<C behaves the same way, but the type variable has a type bound on Base.We define the following beans:

	@Bean
	public BeanCollector<Foo> fooBeanCollector() {
		return new BeanCollector<>("foo");
	}

	@Bean
	public BeanCollector<Bar> barBeanCollector() {
		return new BeanCollector<>("bar");
	}

	@Bean
	public BaseBeanCollector<Foo> fooBaseBeanCollector() {
		return new BaseBeanCollector<Foo>("foo base");
	}

	@Bean
	public BaseBeanCollector<Bar> barBaseBeanCollector() {
		return new BaseBeanCollector<>("bar base");
	}


	@Bean
	public BaseBeanCollector<Foo> fooBaseBeanCollectorAnonClass() {
		return new BaseBeanCollector<Foo>("foo base anonymous class") {};
	}

	@Bean
	public BaseBeanCollector<Bar> barBaseBeanCollectorAnonClass() {
		return new BaseBeanCollector<Bar>("bar base anonymous class") {};
	}


	@Bean
	public BeanCollector<Foo> fooBeanCollectorAnonClass() {
		return new BeanCollector<Foo>("foo anonymous class") {};
	}

	@Bean
	public BeanCollector<Bar> barBeanCollectorAnonClass() {
		return new BeanCollector<Bar>("bar anonymous class") {};
	}

	@Bean
	public FooBeanCollector fooBeanCollectorExtraClass() {
		return new FooBeanCollector();
	}

	@Bean
	public FooBaseBeanCollector fooBaseBeanCollectorExtraClass() {
		return new FooBaseBeanCollector();
	}

Here, Foo(Base)BeanCollector are classes which extend (Base)BeanCollector<Foo>.

The really remarkable output of this code is:

Beans for foo base: [com.example.Bar@7fb4f2a9, com.example.Foo@4dc27487]
Beans for foo: [] and org.springframework.jmx.export.annotation.AnnotationMBeanExporter@13df2a8c
Beans for bar base: [com.example.Bar@7fb4f2a9, com.example.Foo@4dc27487]
Beans for foo base anonymous class: [com.example.Foo@4dc27487]
Beans for bar base anonymous class: [com.example.Bar@7fb4f2a9]
Beans for foo anonymous class: [] and com.example.Foo@4dc27487
Beans for bar anonymous class: [] and com.example.Bar@7fb4f2a9
Beans for foo extra class: [] and com.example.Foo@4dc27487
Beans for foo base extra class: [com.example.Foo@4dc27487]
Beans for bar: [] and org.springframework.jmx.export.annotation.AnnotationMBeanExporter@13df2a8c

The following table shows the autowiring behaviour:
| | collector instance without extra Type | collector instance as anonymous type | collector instance as class type |
| no type bound | empty bean list, arbitrary single bean | empty bean list | empty bean list |
| type bound | too many beans in list | OK | OK |

Either, spring should behave as expected, or report an error that autowiring using type variables is not supported. The current behaviour can trigger all kind of subtle bugs.


Affects: 4.3.5

Reference URL: https://github.com/thomas001/spring-generic-autowire-bug

Issue Links:

  • #16146 Introspect factory method return type for type variable resolution at injection points

1 votes, 3 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 24, 2017

Juergen Hoeller commented

There are a few constraints in effect here, leading to undesirable injection resolution.

  • In Spring Framework 4.x, we only resolve type variables against the target bean class. If the only place where a type variable is being substituted is the factory method return type declaration, we do not take it into account at this point. #16146 tracks our intentions to overcome this... which still may happen for Spring Framework 5.0.

  • As a consequence, the bean class itself needs to contain the variable substitution. According to Java type erasure, this is the case for extends and implements clauses in a type hierarchy, with anonymous inner classes as a special case thereof. Plain instantiation with construction-time substitution of the variable does not retain the type variable: the effect of type erasure as originally designed in Java.

  • Lastly, collection autowiring in fields does not take the containing class into account, in contrast to collection autowiring in method parameters where this has been working for a long time already. This is why you are seeing empty bean lists even for your class hierarchy substitution cases.

While the former two constraints are currently by design, the latter is an unintended inconsistency. I'll turn this JIRA ticket into a bug fix there, closing the gap between field and method parameter scenarios. For the factory method return type consideration, let's keep tracking this in #16146.

Note that I cannot reproduce an arbitrary bean getting selected. For unresolvable type variables, I always see the base type - or Object in case of no type constraints - getting picked up, leading to a corresponding exception if the result is not unique. This is the case against 5.0 at least; I'll double-check against 4.3.6 which I'm going to backport this fix to.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Jan 24, 2017

Juergen Hoeller commented

We're consistently resolving against the full ResolvableType context now, not delegating to the rather simplistic GenericCollectionTypeResolver anymore (gone in 5.0 now, deprecated in 4.3).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.