Announcement Announcement Module
Collapse
No announcement yet.
Using DispatcherServlet with Jetty 7.6 and Java 7 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Using DispatcherServlet with Jetty 7.6 and Java 7

    We had been using the JVM web server to host some HTTP remote services in Java 6, but some change in Java 7 introduced a one second delay into every response, which killed the performance of our application. I had written a subclass of SimpleHttpServerFactoryBean to simplify our bean configuration, and I wanted a drop in replacement that would run an embedded Jetty server.

    All of the examples that I found assumed that the DispatcherServlet would be configured by calling setContextConfigLocation(), which assumes that the HTTP service exporter beans are defined in one or more XML files that are external to the one that contains whatever bean is creating the DispatcherServlet in the first place. Such a change would have required massive refactoring of our XML configuration, and it's not clear that we could have managed the dependencies anyway. So, I set about to create a factory bean that could work within an existing XML configuration.

    The result is the JettyHttpServiceExporterFactoryBean, shown below:

    Code:
    package jetty;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import org.apache.log4j.Logger;
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.servlet.ServletHolder;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.FactoryBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.context.support.StaticApplicationContext;
    import org.springframework.remoting.support.RemoteExporter;
    import org.springframework.web.context.support.GenericWebApplicationContext;
    import org.springframework.web.servlet.DispatcherServlet;
    import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
    
    /**
     * Provides a factory bean that can create and start an embedded Jetty servlet container, and register a map of contexts
     * and HTTP service exporters.
     *
     */
    public class JettyHttpServiceExporterFactoryBean implements FactoryBean<Server>, InitializingBean, DisposableBean
    {
       private static final Logger LOG = Logger.getLogger(JettyHttpServiceExporterFactoryBean.class);
       private static final String HANDLER_MAPPING_BEAN = "handlerMapping";
       private static final String SERVICE_PROP_NAME = "service";
       private static final String SERVICE_INTERFACE_PROP_NAME = "serviceInterface";
       private static final String REMOTING = "/remoting/*";
       private static final String SLASH = "/";
       private ExecutorService myExecutor;
       private Server myServer;
       private int myPort = 9090;
       private String myContextRoot = SLASH;
       private String myServletPrefix = REMOTING;
       private final Map<String, RemoteExporter> myContexts = new HashMap<String, RemoteExporter>();
    
       /**
        * Sets the server HTTP port number. Default value is 9090.
        *
        * @param port the server HTTP port number
        */
       public void setPort(int port)
       {
          myPort = port;
       }
    
       /**
        * Sets the context root for the web app. Default value is "/".
        *
        * @param contextRoot the context root for the web app
        */
       public void setContextRoot(String contextRoot)
       {
          myContextRoot = contextRoot;
       }
    
       /**
        * Adds beans for the HTTP context handlers.
        *
        * @param contexts
        */
       public void setContexts(final Map<String, RemoteExporter> contexts)
       {
          myContexts.putAll(contexts);
       }
    
       /**
        * Sets the servlet prefix for the dispatcher servlet. Default value is "/remoting/*".
        *
        * @param servletPrefix the servlet prefix for the dispatcher servlet
        */
       public void setServletPrefix(String servletPrefix)
       {
          myServletPrefix = servletPrefix;
       }
    
       /**
        * {@inheritDoc }.
        */
       @Override
       public Server getObject() throws Exception
       {
          return myServer;
       }
    
       /**
        * {@inheritDoc }.
        */
       @Override
       public Class<Server> getObjectType()
       {
          return Server.class;
       }
    
       /**
        * {@inheritDoc }.
        */
       @Override
       public boolean isSingleton()
       {
          return false;
       }
    
       /**
        * {@inheritDoc }.
        */
       @Override
       public void afterPropertiesSet() throws Exception
       {
          myExecutor = Executors.newSingleThreadExecutor();
          myExecutor.submit(new Runnable()
          {
             @Override
             public void run()
             {    // create the jetty server
                myServer = new Server(myPort);
    
                // create the jetty servlet handler and attach it to the server
                ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
                context.setClassLoader(Thread.currentThread().getContextClassLoader());
                context.setContextPath(myContextRoot);
                myServer.setHandler(context);
    
                // need to add the beans here because they can't be added directly to a web application context
                StaticApplicationContext sac = new StaticApplicationContext();
                sac.registerSingleton(HANDLER_MAPPING_BEAN, BeanNameUrlHandlerMapping.class);
                for (Map.Entry<String, RemoteExporter> handler : myContexts.entrySet())
                {
                   MutablePropertyValues mpv = new MutablePropertyValues();
                   mpv.addPropertyValue(SERVICE_PROP_NAME, handler.getValue().getService());
                   mpv.addPropertyValue(SERVICE_INTERFACE_PROP_NAME, handler.getValue().getServiceInterface());
                   sac.registerSingleton(handler.getKey(), handler.getValue().getClass(), mpv);
                }
                sac.refresh();
    
                // create the web application context and set the static application context as its parent
                GenericWebApplicationContext applicationContext = new GenericWebApplicationContext();
                applicationContext.setParent(sac);
                applicationContext.refresh();
    
                // create the dispatcher servlet and add it to the jetty servlet handler
                DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
                ServletHolder servletHolder = new ServletHolder(dispatcherServlet);
                context.addServlet(servletHolder, myServletPrefix);
                try
                {
                   // start the server
                   myServer.start();
                   if (LOG.isDebugEnabled())
                   {
                      LOG.debug("Jetty server started");
                   }
                   myServer.join();
                }
                catch (Exception ex)
                {
                   LOG.error(ex.getMessage(), ex);
                }
             }
          });
       }
    
       /**
        * {@inheritDoc }.
        */
       @Override
       public void destroy() throws Exception
       {
          if (myServer != null)
          {
             myServer.stop();
          }
          if (myExecutor != null)
          {
             myExecutor.shutdown();
          }
       }
    }
    Here is a simple example of the XML configuration required to use it:

    Code:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:util="http://www.springframework.org/schema/util"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
    
       <bean id="GoodbyeService" class="jetty.goodbye.GoodbyeServiceImpl"/>
       <bean id="HelloService" class="jetty.hello.HelloServiceImpl"/>
          
       <bean class="jetty.JettyHttpServiceExporterFactoryBean">
          <property name="contextRoot" value="/jetty"/>
          <property name="servletPrefix" value="/remoting/*"/>
          <property name="port" value="9090"/>
          <property name="contexts">
             <map>
                <entry key="/GoodbyeService">
                   <bean class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
                      <property name="serviceInterface" value="jetty.goodbye.GoodbyeService"/>
                      <property name="service" ref="GoodbyeService"/>                
                   </bean>
                </entry>
                <entry key="/HelloService">
                   <bean class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
                      <property name="serviceInterface" value="jetty.hello.HelloService"/>
                      <property name="service" ref="HelloService"/>                
                   </bean>
                </entry>
             </map>
          </property>
       </bean>
    </beans>
    Just point your HTTP invoker proxy at http://localhost:9090/jetty/remoting/GoodbyeService and you are good to go.

    I hope this helps anyone else who has a similar requirement.
Working...
X