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#bind() doesn't find Proxied beans #4096

Open
pjindracek opened this issue Apr 10, 2019 · 7 comments
Open

SpringComponentProvider#bind() doesn't find Proxied beans #4096

pjindracek opened this issue Apr 10, 2019 · 7 comments

Comments

@pjindracek
Copy link

SpringComponentProvider#bind() method doesn't find proxied beans.

public boolean bind(Class<?> component, Set<Class<?>> providerContracts) {

        if (ctx == null) {
            return false;
        }

        if(component.isAnnotationPresent(Component.class)) {
            DynamicConfiguration c = Injections.getConfiguration(locator);
            String[] beanNames = ctx.getBeanNamesForType(component);
            if(beanNames == null || beanNames.length != 1) {
                LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
                return false;
            }
            String beanName = beanNames[0];

            ServiceBindingBuilder bb = Injections.newFactoryBinder(new SpringComponentProvider.SpringManagedBeanFactory(ctx, locator, beanName));
            bb.to(component);
            Injections.addBinding(bb, c);
            c.commit();

            LOGGER.config(LocalizationMessages.BEAN_REGISTERED(beanName));
            return true;
        }
        return false;
    }

Preconditions:

  • have Spring JAX-RS (REST) resource class which is implementing some interface
  • initialize it as a Spring Component (put Component annotation at the class level)
  • use proxy mechanism for that resource, e.g. use Cachable on some of the methods

SpringComponentProvider#bind() uses Spring's ListableBeanFactory#getBeanNamesForType(Class<?>) method to identify Spring beans of the REST resources in the Spring context by a class object of the Spring Component marked class. But if the resource classes are initialized as JDK AOP Proxies in the context (e.g Spring needs to use AOP to provide the cache functionality) then the JDK proxy is just a facade for the interface, not the actual implementing class.

In my opinion, the Spring Components should be found regardless whether they are Proxies - the result of the binding should be the same as if they are not.

@senivam
Copy link
Contributor

senivam commented Apr 11, 2019

I was trying to reproduce the issue per given description and everything works, could you please provide your version of a reproducer?

@pjindracek
Copy link
Author

@senivam

I'm using quite outdated version 2.6 (jersey-spring3 artifact) but I have just tried to migrate to the newest 2.28 and the problem still appears (even from the repository it seems the mentioned class hasn't been changed since then).

The problem is visible from the log message during the server startup:

09:41:26,907 ERROR [SpringComponentProvider] None or multiple beans found in Spring context for type class com.example.XYZResource, skipping the type.

I'm not sure if this is a harmless message or it if it has some effects. In my opinion, when using solely Spring container it should be fine unless you need integration with HK2 for which this binding is done.

I hope it makes sense, if you need me to provide more detailed example in order to replicate this, let me know.

Thanks!

@jansupol
Copy link
Contributor

A reproducer would really come handy here

@jansupol
Copy link
Contributor

@pjindracek Does it relate to #4080?

@jansupol
Copy link
Contributor

Relates to this older issue.

@pjindracek
Copy link
Author

pjindracek commented May 22, 2019

@jansupol The change in #4080 does not fix this issue. It assures that the beans will be searched throughout the Spring context hierarchy. The problem here is, that the search is done based on the class type of the resource, which is not the same because it is wrapped inside a java.lang.reflect.Proxy.

A reproducer for this case:
Have a Spring context with the following bean/resource implementing an interface (note the cacheable annotation which is resulting in creating a Proxy for the resource object):

@Component
@Path("/my-resource")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class MyResource implements MyService {
    @GET
    @Path("/my-resource")
	@Cacheable(value = "myvalue", key = "mykey")
    public MyResponse getMyResource() {
        return new MyResponse();
    }
}

public interface MyService {
	public MyResponse getMyResource();
}

Then if you put a breakpoint inside the org.glassfish.jersey.server.spring.SpringComponentProvider#bind method into the following condition:

if(beanNames == null || beanNames.length != 1) {
	LOGGER.severe(LocalizationMessages.NONE_OR_MULTIPLE_BEANS_AVAILABLE(component));
	return false;
}

you should see that there has been found zero beanNames for the defined resource

@jbescos
Copy link
Member

jbescos commented Nov 12, 2019

@pjindracek I don't know why is the issue you are having. In the past (6 or 7 years ago) I was having a similar issue with Google Guice. The resources were injected by HK2, and all the other classes we injected by Guice. So I was not able to enhance classes with Guice aspects in the resources. Maybe you are having the same problem.

But find here a working example with aspects in the resource, so you can take a look. You can run it in this way:
$ git clone https://github.com/jbescos/Questions
$ cd jersey-spring
$ mvn clean install && java -jar target/jersey-spring-0.0.1-SNAPSHOT.jar
And then visit http://localhost:8080/test

This resource is supposed to return always "Response", but it is annotated with @MyAspect, and Spring should proxy the class to execute the code of MyAspectImpl. So that resource will always return "@MyAspect modified the response".


@Path("/test")
@Component
public class MyResource {
    
    @GET
    @Produces("application/json")
    @MyAspect
    public Response get() {
        return Response.ok("Response").build();
    }
}
@Aspect
@Component
public class MyAspectImpl {
    
    @Around("@annotation(MyAspect)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        joinPoint.proceed();
        return Response.ok("@MyAspect modified the response").build();
    }
    
}

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

No branches or pull requests

4 participants