eclipse-ee4j / eclipse-ee4j/jersey
SpringComponentProvider does not work well with context hierarchy
- Dominant language
- Java
- Stars
- 730
- Forks
- 382
- PR merge metrics
- No merged PRs in 30d
Description
`SpringComponentProvider` does not seem to honour application context hierarchies. Say we have a parent context with common filters and other Jersey components defined and then we have a child context where we have everything in the parent context made available (via [`ApplicationContext.getParent`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html#getParent--)) plus some additional components.
When we try to start such an application (I'm using Spring Boot), we get the below errors:
```
The following warnings have been detected: WARNING: HK2 service reification failed for [com.foo.MyFilter] with an exception:
MultiException stack 1 of 2
java.lang.NoSuchMethodException: Could not find a suitable constructor in com.foo.MyFilter class.
at org.glassfish.jersey.inject.hk2.JerseyClassAnalyzer.getConstructor(JerseyClassAnalyzer.java:192)
at org.jvnet.hk2.internal.Utilities.getConstructor(Utilities.java:180)
at org.jvnet.hk2.internal.ClazzCreator.initialize(ClazzCreator.java:129)
at org.jvnet.hk2.internal.ClazzCreator.initialize(ClazzCreator.java:180)
at org.jvnet.hk2.internal.SystemDescriptor.internalReify(SystemDescriptor.java:740)
at org.jvnet.hk2.internal.SystemDescriptor.reify(SystemDescriptor.java:694)
at org.jvnet.hk2.internal.ServiceLocatorImpl.reifyDescriptor(ServiceLocatorImpl.java:464)
at org.jvnet.hk2.internal.ServiceLocatorImpl.narrow(ServiceLocatorImpl.java:2310)
at org.jvnet.hk2.internal.ServiceLocatorImpl.access$1200(ServiceLocatorImpl.java:128)
at org.jvnet.hk2.internal.ServiceLocatorImpl$9.compute(ServiceLocatorImpl.java:1395)
at org.jvnet.hk2.internal.ServiceLocatorImpl$9.compute(ServiceLocatorImpl.java:1390)
at org.glassfish.hk2.utilities.cache.internal.WeakCARCacheImpl.compute(WeakCARCacheImpl.java:128)
at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetAllServiceHandles(ServiceLocatorImpl.java:1452)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getAllServiceHandles(ServiceLocatorImpl.java:1377)
at org.jvnet.hk2.internal.ServiceLocatorImpl.getAllServiceHandles(ServiceLocatorImpl.java:1366)
```
The definition of `MyFilter` is a rather straightforward one:
```
@Component
public class MyFilter implements ContainerResponseFilter {
private MyService myService;
public MyFilter(MyService myService) {
this.myService = myService;
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
myService.doSomething(requestContext);
}
}
```
The same filter works absolutely fine when loaded from the parent context. It's as though the child context doesn't seem to be able to see that this bean has already been defined in the parent context and therefore the Spring-HK2 bridge gives up and tries to do DI using HK2 instead of Spring.
Upon further investigation the root cause seems to be the below block of code in `SpringComponentProvider`.
```
@Override
public boolean bind(Class component, Set> providerContracts) {
if (ctx == null) {
return false;
}
if (AnnotationUtils.findAnnotation(component, Component.class) != null) {
String[] beanNames = ctx.getBeanNamesForType(component);
if (beanNames == null || beanNames.length != 1) {
LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
return false;
}
```
The problem lies in the line `String[] beanNames = ctx.getBeanNamesForType(component);`. [`ApplicationContext.getBeanNamesForType`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/ListableBeanFactory.html#getBeanNamesForType-java.lang.Class-) clearly states that it doesn't trace back the hierarchy:
> Does not consider any hierarchy this factory may participate in. Use BeanFactoryUtils' beanNamesForTypeIncludingAncestors to include beans in ancestor factories too.
So in this case, `SpringComponentProvider` just expects to find `MyFilter` to be registered in the child context which obviously it isn't and just falls back to HK2 which isn't aware of the constructor injection already set up in Spring.
Let me know if you're accepting PRs and I'd be happy to submit one with the below change:
```diff
@Override
public boolean bind(Class component, Set> providerContracts) {
if (ctx == null) {
return false;
}
if (AnnotationUtils.findAnnotation(component, Component.class) != null) {
- String[] beanNames = ctx.getBeanNamesForType(component);
+ String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(ctx, component);
if (beanNames == null || beanNames.length != 1) {
LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
return false;
}
```
Contributor guide
Assessment
This issue has not been assessed yet.