SpringMVC with Embedded Jetty

December 8, 2012

As an accidental Java developer I've never been comfortable deploying applications into a container, especially when the web interface is secondary to the primary purpose of the application. Instead I prefer to programatically create and manage the web interface rather than have it manage me. Currently SpringMVC is my Java web framework of choice (due to company convention more than anything else) and it is built around the idea of being managed by a container such as Jetty or Tomcat.

While there is no shortage of existing posts on using SpringMVC with embedded Jetty, they fail for me due to the following reasons:

Compile Those JSPs

By compiling the JSPs prior to deployment we can save ourselves from the hassle of classpath issues, especially relating to tag libs.


<plugin>  
  <groupId>org.mortbay.jetty</groupId>  
  <artifactId>jetty-jspc-maven-plugin</artifactId>  
  <version>${jetty.version}</version>  
  <executions>  
    <execution>  
      <phase>compile</phase>  
      <id>jspc</id>  
      <goals>  
        <goal>jspc</goal>  
      </goals>  
      <configuration>  
        <webAppSourceDirectory>  
          ${basedir}/src/main/resources/webapp  
        </webAppSourceDirectory>  
        <webXml>  
          ${basedir}/src/main/resources/webapp/WEB-INF/web.xml  
        </webXml>  
        <webXmlFragment>  
          ${basedir}/target/classes/webapp/WEB-INF/web.xml-frag  
        </webXmlFragment>  
      </configuration>  
    </execution>  
  </executions>  
</plugin>  

This plugin will compile all the JSPs found under src/main/resources/webapp and modify the existing web.xml to direct requests for the JSPs to the pre-compiled versions. This avoids compiling them at runtime and speeds up the time it takes to respond to the first request for a JSP.

You may also notice that the JSPs are under src/main/resources/webapp instead of the more common src/main/webapp. This is to avoid extra lines in the pom.xml to pull in webapp as a resource, as we are packaging as a jar.

Bootstrapping Jetty

I use Spring annotations to configure Jetty and import its @Configuration class into my root context configuration class. In order for the SpringMVC dispatcher servlet to access beans created in the root context, this class needs to be application context aware:


@Configuration  
public class JettyConfiguration implements ApplicationContextAware {  
    private ApplicationContext applicationContext;  

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

this is used as the parent context for the new context that will be created for the SpringMVC dispatcher servlet:


@Bean  
public ServletHolder dispatcherServlet() {  
    AnnotationConfigWebApplicationContext ctx =   
        new AnnotationConfigWebApplicationContext();  
    ctx.setParent(applicationContext);  
    ctx.register(MvcConfiguration.class);  
    DispatcherServlet servlet = new DispatcherServlet(ctx);  
    ServletHolder holder = new ServletHolder("dispatcher-servlet",   
        servlet);  
    holder.setInitOrder(1);  
    return holder;  
}  

There are still a few more tricks required to get the dispatcher servlet registered without defining it in your web.xml. We must also create a Jetty web application context, this creates the JSP servlet as well as a default servlet as would be created during the instantiation of a classic war based application:


@Bean  
public WebAppContext jettyWebAppContext() throws IOException {  
    WebAppContext ctx = new WebAppContext();  
    ctx.setContextPath("/");  
    ctx.setWar(new ClassPathResource("webapp").getURI().toString());  

     /* We can add the Metrics servlet right away. */  
    ctx.addServlet(AdminServlet.class, "/metrics/*");  

    return ctx;  
}  

Notice that I've added the metrics servlet, but have yet to add the dispatcher servlet. For some reason, adding the dispatcher servlet here causes JSP views to fail. One option is to fall back to defining the dispatcher servlet in your web.xml (which may be the cleanest option), or you can register the dispatcher servlet in the Jetty lifeCycleStarted callback which we'll do here:


@Bean  
public LifeCycle.Listener lifeCycleStartedListener() {  
    return new AbstractLifeCycle.AbstractLifeCycleListener() {  
        @Override  
        public void lifeCycleStarted(LifeCycle event) {  
            try {  
                ServletHolder dispatcherServlet = dispatcherServlet();  
                jettyWebAppContext().getServletHandler()  
                        .addServletWithMapping(dispatcherServlet, "/");  
                dispatcherServlet.start();  
            } catch (Exception e) {  
                logger.error(  
                        "Failed to start Spring MVC dispatcher servlet", e);  
            }  
        }  
    };  
}  

@Bean(initMethod = "start", destroyMethod = "stop")  
public Server jettyServer() throws IOException {  
    Server server = new Server();  
    server.setHandler(jettyWebAppContext());  

    /* Add a life cycle listener so we can register the SpringMVC dispatcher  
     * servlet after the web application context has been started. */  
    server.addLifeCycleListener(lifeCycleStartedListener());  

    server.setConnectors(jettyConnectors());  
    return server;  
}  

In summary, this is just another variation of James Ward's post on Containerless Spring MVC. The complete code for this project (which I use as a template) can be at https://github.com/jasonish/jetty-springmvc-jsp-template. The code as referenced in this post can be found in the 20121207 tag.

Next - As I'm not really a fan of JSPs, or the extra hoops required to make this work right I'll probably look at updating the template with Thymeleaf support, which should greatly simplify things.