2

I have a Spring app which launches a REST Service within an embedded instance of Jetty, which itself is launched from Spring.

The initial Spring context has an integration and database layer, and launches the Jetty instance. Jetty then calls its own application context file which exposes the REST service.

I would like to know if there's some way of exposing the initial Spring context to the web context run from within Jetty. Unfortunately I can't deploy a full J2EE server, and I'm hesitant to run everything from the Web context, relying on Jetty to manage threading and such.

Spencer Kormos
  • 8,381
  • 3
  • 28
  • 45

2 Answers2

3

I am assuming that you have a ContextLoaderListener in Jetty's web.xml, and that is how the Spring web context is getting created.

  1. Remove the ContextLoaderListener from jetty's web.xml (but keep the context-param with the contextConfigLocation)

  2. Subclass ContextLoader, overriding loadParentContext() to return your initial Spring context.

  3. Create an instance of your ContextLoader subclass after you start Jetty.

  4. Call initWebApplicationContext(context.getServletContext().getContext()) on this instance, where "context" is the org.mortbay.jetty.servlet.Context

sourcedelica
  • 23,940
  • 7
  • 66
  • 74
  • Totally missed this. I don't have the time right now to try it, but I'm actually excited to do so, since it would mean once less dependency. Will update soon. Thanks. – Spencer Kormos Nov 09 '11 at 19:14
  • This works, though there are a couple of caveats. The first is that you need to provide the contextPath to the .getContext() method. The second is that you can't load any servlets on start-up as the lookup for a default servlet context will fail. For my purposes though, that's not at all a big deal, and this works flawlessly otherwise. I'd vote this up twice if I could. – Spencer Kormos Dec 08 '11 at 20:54
1

So I found yet a better answer based off the one from ericacm above. The only reason that it's better is that you can still use <load-on-startup> for the servlets in the web.xml file.

When embedding the Jetty server, you need to create a WebAppContext. The super-class ContextHandler lets you set an array of EventListener which includes ServletContextListener.

So the solution is extend ContextLoader and implement both Spring's ApplicationContextAware and the ServletContextListener interface. The loader lets you return the parent context set by the contextaware interface, and the listener provides you the ServletContext via contextInitialized().

You can then initialize this before any of the Jetty components, and get access to the fully populated ServletContext as the Jetty server is loading, which gets called before any of the web app's themselves are initialized.

Listener implementation:

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.ContextLoader;

public final class EmbeddedJettySpringContextLoaderListener 
    extends ContextLoader 
    implements ApplicationContextAware,
               ServletContextListener
{
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * Returns the parent application context as set by the
     * {@link ApplicationContextAware} interface.
     * 
     * @return The initial ApplicationContext that loads the Jetty server.
     */
    @Override
    protected ApplicationContext loadParentContext(ServletContext servletContext) {
        return this.applicationContext;
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        super.initWebApplicationContext(sce.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //not needed
    }
}

Jetty config for WebAppContext (eventually referenced by the server):

<!-- Loads this application context as the parent of the web application context. -->
<bean id="parentContextLoaderListener" class="com.citi.matrix.server.restapi.EmbeddedJettySpringContextLoaderListener" />

<bean id="restApiWebAppContext" class="org.mortbay.jetty.webapp.WebAppContext">
  <property name="displayName" value="RestApi" />
  <property name="contextPath" value="/" />
  <!-- the value for war, must be a relative path to the root of the application, and does not use the classpath. -->
  <property name="war" value="${WEBAPPS_HOME}/rest-api" />
  <property name="eventListeners">
    <ref  local="parentContextLoaderListener" />
  </property>
  <property name="configurationClasses">
    <list>
      <value>org.mortbay.jetty.webapp.WebInfConfiguration</value>
      <value>org.mortbay.jetty.webapp.WebXmlConfiguration</value>
      <value>org.mortbay.jetty.webapp.JettyWebXmlConfiguration</value>
    </list>
  </property>
  <property name="logUrlOnStart" value="true" />
</bean>
Nick Legend
  • 789
  • 1
  • 7
  • 21
Spencer Kormos
  • 8,381
  • 3
  • 28
  • 45
  • You'll still need to not declare ContextLoaderListener in web.xml, as the embedded implementation needs to be declared in the parent context. Since you're not calling the ContextLoaderListener, you still need to set the contextConfigLocation context-param in web.xml. – Spencer Kormos Dec 09 '11 at 15:16