jersey-spring3 instantiating Spring-managed bean (null!) jersey-spring3 instantiating Spring-managed bean (null!) spring spring

jersey-spring3 instantiating Spring-managed bean (null!)


What's loading first? Spring or Jersey? It could be that your Spring context isn't initialized when SpringComponentProvider calls WebApplicationContextUtils.getWebApplicationContext(sc);. Try using Spring's ContextLoaderListener so that Spring does its initialization right after the app is deployed.

I ran into a lot of the same issues that you're experiencing with the jersey-spring3 library. It had problems finding my Spring ApplicationContext (looks like this is where you're stuck) and it blew up injecting setters that took a generic parameter as an argument.

If you get past the app context issue, I don't think what you have will work anyway. You defined the ViewResource and LocationResource beans in XML. From what I can tell, Jersey will only get the resource instance from Spring if the the resource class is annotated with @Component. Take a look at org.glassfish.jersey.server.spring.SpringComponentProvider, specifically component.isAnnotationPresent(Component.class):

// detect JAX-RS classes that are also Spring @Components.// register these with HK2 ServiceLocator to manage their lifecycle using Spring.@Overridepublic 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;}

An unrelated issue was that we also wanted to move all of our JAX-RS annotations to interfaces. Whenever I tried it, I got "Could not find a suitable constructor for com.foo.ResourceInterface".

In the end, I solved all of my issues by not using jersey-spring3 and rolling my own Jersey to Spring connector. Here's what I did:

  1. Configured all of my resources as regular Spring beans. You can use XML if you want.
  2. In my Application, I added bindings to the HK2 container to use a factory whenever it needs an instance of one of the resources. My factory class simply returns the Spring managed instance of the resource.
  3. Before the factory returns the Spring-managed bean, I use the Jersey/HK2 ServiceLocator to inject things that Jersey provides. For example, anything annotated with @Context.

My javax.ws.rs.Application looks like this:

public class RestConfig extends ResourceConfig {private static final Log log = LogFactory.getLog(RestConfig.class);@Injectpublic RestConfig(ServiceLocator locator) {    super();    // specific to my app. get your spring beans however you like    Collection<Object> beans = BeanLocator.beansByAnnotation(RestResource.class);    DynamicConfiguration c = Injections.getConfiguration(locator);    for (Object bean : beans)    {                    // tell jersey to use a factory for any interface that the bean implements.  since your resources don't implement interfaces,                     // you'll want to do something a bit different here.        for (Class<?> currentInterface : bean.getClass().getInterfaces())        {            if (log.isTraceEnabled())                log.trace("binding " + currentInterface.getSimpleName() + " to Spring managed bean");            ServiceBindingBuilder<Object> bb = Injections.newFactoryBinder(new StaticLookupFactory(locator, bean));            bb.to(currentInterface);                    Injections.addBinding(bb, c);        }    }            // commit the changes to the HK2 container (don't skip this step!)            c.commit();    property("jersey.config.disableMoxyJson.server", true);    packages("com.foo.web.rest");    register(MoxyXmlFeature.class);}// a "factory" where the provide() method returns the spring managed bean    // that was passed to the constructor.private static class StaticLookupFactory implements Factory<Object> {    private ServiceLocator locator;    private Object bean;    StaticLookupFactory(ServiceLocator locator, Object bean)    {        this.locator = locator;        this.bean = bean;    }    @Override    public Object provide() {                    // inject this annotated with @Context, @Inject, etc        locator.inject(bean);        return bean;    }    @Override    public void dispose(Object instance) {    }}}

BeanLocator is a utility class that I wrote that makes it easy to grab bean instances using static methods when autowiring isn't available. For example, when working outside of Spring managed beans. Not too much going on there:

public static Collection<Object> beansByAnnotation(Class<? extends Annotation> annotation){    return applicationContext.getBeansWithAnnotation(annotation).values();}

RestResource is also specific to our app. It's a custom stereotype that works like @Component, @Service, etc:

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Componentpublic @interface RestResource {    String value() default "";}

Note that Jersey allows you to register custom implementations of org.glassfish.jersey.server.spring.ComponentProvider to manage the lifecycle of resources on your own. I tried it but couldn't get it to recognize my implementation no matter what I did.

One other note... the locator.inject(bean) call that activates the Jersey dependency injection mechanism will processes anything marked with @Inject. Use @Autowired within your classes or configure your beans with XML to avoid having both Spring and Jersey attempt to resolve values for things annotated with @Inject.


We have a custom, asynchronous ContextLoader, so the interim solution required placing a total hack in the Jersey-Spring3 source to wait for the application to initialize before the custom component provider initializes.

P.S. For any poor soul who finds themselves having to do something like this, make sure META-INF/settings contains the SpringComponentProvider configuration.


(2014-04-18) Elaborating for @Scott

Note that this is a terrible hack and I would only attempt such a thing as a last resort when all other attempts have failed, like in my case. Also I would consult the Jersey mailing group about your problem before attempting anything like this.

That said... this is what I did to solve my problem:

  • Literally copied the source code of spring-jersey3 into my application/server, modifying the header of every file with the appropriate tags as per the license;

  • Created the following class --

===>

  /**   * Hack class for RN-8979.   *   * @author ryan   *   */  public class ContextLoadWaiter {    private static final Logger logger = Logger.getLogger(ContextLoadWaiter.class);    public void doWait() {      try {        while (ContextLoaderHttpInterceptor.isNotStarted()) {          logger.info("Waiting for ContextLoader to start...");          Thread.sleep(1000);        }      } catch (InterruptedException e) {        logger.error("SpringComponentProvider was interrupted!");      }    }  }

Note that this is specific to *our* code-base as ContextLoaderHttpInterceptor is an http servlet where isNotStarted returns true if our custom ContextLoader (which happens to be asynchronous) is not yet loaded.

The custom asynchronous ContextLoader was put in place sometime by somebody for some reason along the lines of allowing the UI to display a "loading" page while the server boots up. (Probably not the correct way to add this UI "feature", but the code was there and the UI depended on it, so I had to deal with it...)

Since this part will not apply directly to you, the key thing is to debug through SpringComponentProvider (from here) and look at the value of the ClassPathXmlApplicationContext. If it is null, as it is in our case, then you need to figure out why it is null and wait on whatever ContextLoader you use to load before you initialize this component.

  • Placed this hacky line in SpringComponentProvider --

==>

  ...  private final ContextLoadWaiter waiter = new ContextLoadWaiter();  ...  @Override  public void initialize(ServiceLocator locator) {    waiter.doWait(); // Wait on our asynchronous context loader.    this.locator = locator;    if (LOGGER.isLoggable(Level.FINE)) {      LOGGER.fine("Context lookup started");    }    ...
  • Created this file: META-INF/services/org.glassfish.jersey.server.spi.ComponentProvider with the contents being the fully qualified classpath to the SpringComponentProvider, e.g. com.company.server.nbi.rest.internal.jspring.SpringComponentProvider

  • Added the custom Jersey-spring3 package as a package to scan in the application; see below...

==>

/** * Application configuration. * * @author ryan * */public class MyJerseyApplication extends ResourceConfig {  private static final class Messages {    static final String INF_STARTING_APPLICATION = "Starting %s!";  }  private static final Logger logger = Logger.getLogger(MyJerseyApplication.class);  public MyJerseyApplication() {    packages(    /* Internal providers */    "com.company.server.nbi.rest.providers",    /* Internal filters */    "com.company.server.nbi.rest.filters",    /* Spring injection support */    "com.company.server.nbi.rest.internal.jspring", // HERE!!!    /* Json providers */    "com.fasterxml.jackson.jaxrs.json",    /* Jackson exception mappers */    "com.fasterxml.jackson.jaxrs.base");    /* Resources */    register(ResourceA.class);    register(ResourceB.class);    register(ResourceC.class);    /* Miscellaneous features */    register(MultiPartFeature.class);    register(LoggingFilter.class);    logger.info(format(Messages.INF_STARTING_APPLICATION, this.getClass().getName()));  }}

That's "it". Definitely not a solution to be proud of, but if you are in desperation mode like I was, it probably doesn't hurt to give it a shot.


This is the message that is key to understanding the issue. It indicates that Spring is failing to initialise correctly:

SEVERE: Spring context lookup failed, skipping spring component provider initialization.

(On a side note: because Spring is failing to initialise, the only JSR-330 implementation to try and resolve the @Inject is HK2 - which is why you're seeing the other issue).

Anyway, the problem is likely that your container isn't performing a scan for the annotations that make all the jersey-spring3 magic happen.

This behaviour is part of the Servlet 3.0 Specification (JSR-33, Section 1.6.2), so you should double check that your container supports this.

In the case of Tomcat - unless you're running Tomcat 7.0.29 or newer, you'll actually need to make sure that the Servlet version is specified in your web.xml.

http://tomcat.apache.org/tomcat-7.0-doc/changelog.html#Tomcat_7.0.29_(markt)

I hit this problem recently and it drove me nuts, and fixing the web.xml was easier than upgrading from Ubuntu/Precise!

Hope this helps!