How to Initialize Jersey Application (ResourceConfig) With Spring?

17,763

Solution 1

So now I'm stuck: is there any way to configure a Jersey ResourceConfig application object using Spring?

I don't think you can configure Jersey to obtain your ResourceConfig from Spring as a Spring managed bean. It's a bit hackish, but you could do something like this. Note that you'll end up with two instance of your ResourceConfig: one managed by Spring and another by Jersey:

public class MyApplication extends ResourceConfig {

    // static, available to all instances
    private static MyConfiguration myConfiguration;

    public MyApplication() {
        // when Spring creates the first instance of MyApplication, myConfiguration
        // will be null because the setter wasn't called yet
        if (myConfiguration != null)
        {
            // second instance created by jersey... Spring will have autowired
            // the first instance, and myConfiguration is static
            if (myConfiguration.isEnabled()) 
                packages("com.mycompany.resources.search");
        }
    }

    @Autowired
    public void setMyConfiguration(MyConfiguration config)
    {
        // instance level setter saves to a static variable to make it available for
        // future instances (i.e. the one created by jersey)
        MyApplication.myConfiguration = config;
    }
}

Again, this is fairly hackish. You'll want to make sure Spring is initialized before Jersey and look closely at any threading issues that could occur during initialization.

Solution 2

Expanding on my previous comment:

Trying to extend ResourceConfig is dangerous if you don't know what you're doing. Jersey becomes unpredictable, and if you try to subclass it into an Abstract class, Jersey crashes.

Instead, the JAX-RS specification provides us with a very useful interface called Feature: It allows you to register any classes you want as if you were configuring your own application. Furthermore, you don't need to use the awkward AbstractBinder, you just specify what contracts you register your classes with.

import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;

// Don't use @Component here, we need to inject the Spring context manually.
public class MySpringFeature implements Feature {

    @Context
    private ServletContext servletContext;

    private ApplicationContext applicationContext;

    @Autowired
    private MySecurityDAO mySecurityDAO;

    @Autowired
    private MySpringResponseFilter myResponseFilter;

    @Override
    public boolean configure(FeatureContext context) {
        if(this.servletContext == null) {
            return false; // ERROR!
        }
        this.applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        if(this.applicationContext == null) {
            return false; // ERROR!
        }

        // This is where the magic happens!
        AutowireCapableBeanFactory bf = applicationContext.getAutowireCapableBeanFactory();
        bf.autowireBean(this);

        // From here you can get all the beans you need

        // Now we take a Spring bean instance,
        // and register it with its appropriate JAX-RS contract
        context.register(myResponseFilter, ContainerResponseFilter.class);

        // Or, we could do this instead:
        SomeSecurityFilter mySecurityFilter = new SomeSecurityFilter();
        mySecurityFilter.setSecurityDAO(mySecurityDAO);
        context.register(mySegurityFilter, ContainerRequestFilter.class);

        // Or even this:
        SomeOtherSpringBean someOtherBean = applicationContext.getBean(SomeOtherSpringBean.class);
        context.register(someOtherBean, SomeOtherJerseyContract.class);

        // Success!
        return true;
    }
}

And in your ResourceConfig:

public class MyApplication extends ResourceConfig() {

    public MyApplication() {
        register(MySpringFeature.class);
    }
}

Ta-da!

Share:
17,763
Michael Iles
Author by

Michael Iles

Updated on July 26, 2022

Comments

  • Michael Iles
    Michael Iles almost 2 years

    I'm using Jersey 2 and Spring, and I'm trying to initialize my Jersey application (i.e. the class derived from ResourceConfig) with parameters from the Spring context.

    Background: I have a single Jersey application that I build (i.e. a single WAR) and I deploy it across a server cluster with different Spring configurations on different servers to enable or disable different parts of the server, e.g. some of the servers have /search resources turned on, etc. This was really easy in Jersey 1.0: I just put,

    <context:component-scan base-package="com.mycompany.resources.search"/>
    

    in a Spring config to have Jersey scan that particular package and enable the JAX-RS resource providers in it.

    Now in Jersey 2.0 the Spring <context:component-scan ... /> doesn't work, so resources have to be programmatically registered in a startup class derived from ResourceConfig:

    public class MyApplication extends ResourceConfig {
    
        public MyApplication() {
            packages("com.mycompany.resources.search");
        }
    }
    

    So far so good, but I need to conditionally scan that package, and I can't figure out how to get any Spring configuration into the MyApplication class. I thought that constructor injection might work:

    public class MyApplication extends ResourceConfig {
    
        @Autowired
        public MyApplication(@Qualifier("my-config") MyConfiguration myConfiguration) {
            if (myConfiguration.isEnabled()) {
                packages("com.mycompany.resources.search");
            }
        }
    }
    

    However HK2 complains that it can't find a default constructor to use... so this indicates to me that DI is in play in the construction of this class, but that the DI isn't using Spring.

    Similarly, using the the Spring bean lifecycle doesn't work:

    public class MyApplication extends ResourceConfig implements InitializingBean {
    
        @Autowired
        private MyConfiguration myConfiguration;
    
        public MyApplication() {
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            if (myConfiguration.isEnabled()) {
                packages("com.mycompany.resources.search");
            }
        }
    }
    

    (The afterPropertiesSet method isn't called.)

    So now I'm stuck: is there any way to configure a Jersey ResourceConfig application object using Spring?

    UPDATE:

    I accepted @JohnR's answer below but I'll also include my eventual solution which I think is a bit cleaner. @JohnR's answer was to have the object initialized twice: first by Spring and then by Jersey/HK2. When Spring initializes the object you cache the dependencies in a static member, and then when Jersey/HK2 initializes it later you can retrieve the dependencies.

    I ended up doing this:

    public class MyApplication extends ResourceConfig {
    
        public MyApplication() {
            ApplicationContext rootCtx = ContextLoader.getCurrentWebApplicationContext();
            MyConfiguration myConfiguration = rootCtx.getBean(MyConfiguration.class);
    
            if (myConfiguration.isEnabled()) {
                packages("com.mycompany.resources.whatever");
            }
        }
    }
    

    Rather than having the object initialized twice, we let Jersey/HK2 initialize it but then we retrieve the dependencies from Spring.

    Both solutions are vulnerable to timing: they both assume that Spring is initialized before Jersey/HK2.

  • Rick Garcia
    Rick Garcia over 4 years
    I've managed to solve this. If you absolutely need to configure your application with Spring classes, please don't extend ResourceConfig, that's a jersey proprietary feature. Instead, implement a javax.ws.rs.core.Feature, from which you can obtain Spring's ApplicationContext using a @Context ServletContext field. This will save you lots of headaches. Then you just register your feature from your ResourceConfig class, and ta-da! (You can use the Jersey-Spring4 dependency, but in my case I can't since I'm forced to use Jersey 2.21, for which there's no such package.)