Oliver Drotbohm opened SPR-16179 and commented
The following test fails:
package example;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SpringInjectionTest {
@Configuration
static class Config {
@Bean
PageAssembler<?> assembler() {
return new PageAssembler<>();
}
}
@Autowired(required = false) Assembler<SomeOtherType> assembler;
@Test
public void testname() {
assertThat(assembler, is(nullValue()));
}
interface Assembler<T> {}
static class PageAssembler<T> implements Assembler<Page<T>> {}
interface Page<T> {}
interface SomeOtherType {}
}
Spring injects the configured bean instance here despite the fact that the generic declarations clearly don't match as Page<T>
is not compatible with the requested SomeOtherType
.
Affects: 4.3.12, 5.0.1
Referenced from: commits https://github.com/spring-projects/spring-framework/commit/59d654b0cfa523fb9f5d2ff9961106ad46d92953, https://github.com/spring-projects/spring-framework/commit/e2bb06edbdd23716df7c265db28f65e6c432a2aa
Comment From: spring-projects-issues
Juergen Hoeller commented
This turns out be rather involved since we do mean to accept raw matches as a fallback in case of unresolvable generics there... and in order to differentiate such cases, we'd have to perform a partial generic match where just the unresolvable variable is left open. Let's see what we can do for 5.0.3 here, with a potential backport to 4.3.14.
Comment From: spring-projects-issues
Sergei Ustimenko commented
I'm quite interested in this ticket being resolved and want to contribute the fix if no one minds.
I've already experimented with this bug and was curious about the last assert statement in the Spr16179 test. Please consider slightly modified version of the test:
@Test
public void repro() {
...
//the same assert as in original test
assertSame(bf.getBean("pageAssembler"), bf.getBean(AssemblerInjection.class).assembler6);
}
@Configuration
static class AssemblerConfig {
@Bean
PageAssemblerImpl<?> pageAssembler() {
//anonymous Integer assembler
return new PageAssemblerImpl<Integer>() {};
}
...
}
public static class AssemblerInjection {
...
@Autowired(required = false)
PageAssembler<String> assembler6;
}
I think above should always fail as
PageAssemblerImpl<?> integerAssembler = new PageAssemblerImpl<Integer>() {};
PageAssembler<String> assembler6 = integerAssembler;
wouldn't and shouldn't compile.
Maybe last assert should look like following:
assertNull(bf.getBean(AssemblerInjection.class).assembler6);
instead?
Comment From: spring-projects-issues
Sergei Ustimenko commented
Adding a little bit more on the example above. If test would be like following:
interface PageAssembler<T> extends Assembler<Page<T>> {
T getField();
}
static class PageAssemblerImpl<T> implements PageAssembler<T> {
private final T field;
PageAssemblerImpl(T field) { this.field = field; }
public T getField() { return field; }
}
and then if one would use
String field = bf.getBean(AssemblerInjection.class).assembler6.getField();
that will successfully cause CCE.
Comment From: spring-projects-issues
Sergei Ustimenko commented
Juergen Hoeller Oliver Drotbohm Stéphane Nicoll rstoyanchev
I understand that spring team has things to do, so once anyone will have time, could that one please put more clarification on the questions above? I'm looking forward to know your thoughts and fix the bug.
Comment From: jhoeller
This requires a dedicated matching step within descriptor.fallbackMatchAllowed()
in GenericTypeAwareAutowireCandidateResolver
:
if (targetType.hasUnresolvableGenerics()) {
return dependencyType.isAssignableWithinResolvedPart(targetType);
}
else if (targetType.resolve() == Properties.class) {
return true;
}
Within that special isAssignable
check, matching the nested generics until an unresolvable part (other.isUnresolvableTypeVariable()
or other typeBounds
without ourBounds
) is encountered on the other side and returning true
then - but only after the nested generics matched up until that point. As far as the prototype goes, the test suite passes with such an arrangement. This might be too risky for 6.1.x but easy enough to roll into 6.2.