eclipse-ee4j / eclipse-ee4j/jersey

SpringComponentProvider does not work well with context hierarchy

Open
#3,885 2 comments 0 reactions 0 assignees View on GitHub
spring
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.