Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Initialize Jersey Application (ResourceConfig) With Spring?

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.

like image 286
Michael Iles Avatar asked Jan 08 '14 14:01

Michael Iles


1 Answers

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!

like image 99
Rick Garcia Avatar answered Oct 21 '22 12:10

Rick Garcia