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

SpringComponentProvider does not work well with context hierarchy #3885

Open
radarsh opened this issue Jul 15, 2018 · 2 comments
Open

SpringComponentProvider does not work well with context hierarchy #3885

radarsh opened this issue Jul 15, 2018 · 2 comments
Labels

Comments

@radarsh
Copy link
Contributor

radarsh commented Jul 15, 2018

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) 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<Class<?>> 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 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:

@Override
public boolean bind(Class<?> component, Set<Class<?>> 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;
        }
@jansupol
Copy link
Contributor

Yes, please, provide a PR. Thank you.

@radarsh
Copy link
Contributor Author

radarsh commented Mar 20, 2019

PR #4080 provided. Contribution guidelines could be improved to link to the project's wiki for developer resources such as CI, signing off commits and a lot more. I had to trawl through existing pull requests to find the required information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants