Spring version: 5.2.8.RELEASE.
Code:
public class A {}
public class B extends A {}
public interface AService<T extends A> {}
public interface BService<T extends B> {}
public abstract class AbstractAService<T extends A> implements AService<T> {}
@Component
public class BServiceImpl<T extends B> extends AbstractAService<T> implements BService<T> {}
public class TempIT {
@Autowired
private AService<A> autoService;
@Autowired
private ApplicationContext context;
@Test//PASSES
public void test_autowired() {
assertThat(autoService, is(notNullValue()));
}
@Test//FAILS
public void test_manual() {
AService<A> manualService = null;
String[] beanNamesForType = context.getBeanNamesForType(ResolvableType.forClassWithGenerics(AService.class, A.class));
if (beanNamesForType.length != 0) {
manualService = (AService<A>) context.getBean(beanNamesForType[0]);
}
assertThat(manualService, is(notNullValue()));
}
}
So, the problem is, that when AService<A>
is auto wired everything works. However, when we try to get the same bean using ResolvableType.forClassWithGenerics
we can't do it. I think this is a bug, but I am not a spring expert.
Comment From: encircled
Hi,
Actually, it does not really work as you expect in neither case, because ? extends A
!= A
. The reason why it seem to work when using Autowired, is because Spring does not check the generic types accurately during dependencies injection. To reproduce it, change your service component to
@Component
public class BServiceImpl<T extends B> extends AbstractAService<B> implements BService<B> {}
This would lead to AService<BeanB>
to be injected into AService<BeanA>
field, caused by presence of <T extends B>
for some reason. Which is a different bug I think (@sbrannen please confirm, or am I missing smthing?)
As for forClassWithGenerics
in general, it works correctly according to your generic types variance. You just need to specify the correct generic type bounds. Your BServiceImpl
is ? extends B
, but you are passing A
. Tbh I don't know how to construct such type apart from getting it from the field like:
BServiceImpl<...> {
public AService<? extends BeanA> service;
}
context.getBeanNamesForType(ResolvableType.forField(BServiceImpl.class.getDeclaredField("service")))
Comment From: snicoll
Actually, it does not really work as you expect in neither case, because ? extends A != A. The reason why it seem to work when using Autowired, is because Spring does not check the generic types accurately during dependencies injection.
Yes, this is correct, the unresolved generics handling will kick in eventually and inject the dependency in best effort style.