Source code is available here. This article is part of a series, the commit in the repository for this article is: b60fe34
  1. Prerequisites
  2. Introduction
  3. Setup
  4. Server Code
  5. Web Configuration
  6. Sample Controller
  7. Spring Configuration
  8. Conclusion

Prerequisites

* Warning: while you may be able to get by with Oracle's JDK or other variants for local development, Heroku runs on OpenJDK.

Introduction

This article is intended for an audience with Java development experience. Basic knowledge of Git, Jetty, and Spring/SpringMVC will be immensely useful here. For those unfamiliar:

Heroku is a cloud based web host for a variety of languages including Java. Heroku gives us a blank slate to run within and does not mandate any particular container. Instead, Heroku expects us to bring your own server (BYOS). Heroku tightly integrates with the Git version control system. This allows for a tight and seamless development loop, at the cost of making the VCS decision for us and increasing the learning curve for anyone unfamiliar with Git.

Git is a distributed version control system. This article assumes a working knowledge of Git basics. Their online documentation comes from the wonderfully written "ProGit" book by Scott Chacon. I also highly recommend Mark Lodato's Visual Git Reference, handy for both absolute beginners and seasoned professionals alike.

Jetty is a modular web server (also known as a "container" in Java web application parlance) that can run in a standalone configuration similar to other popular containers (e.g. Tomcat). In addition to running in a standalone manner, Jetty is designed with embeddability in mind - rather than designing your application around the standard Java webapp life cycle with a single war file as an artifact, we can launch the web server from within our application essentially defining our own application life cycle. This gives us an exceptional amount of control and power over the entire web stack.

Gradle is the next gen build tool. This previous article discusses its usage and merits at length.

Spring is an Open Source Dependency Injection framework with modules available for just about any enterprise need. Spring can be both configured through XML or annotation driven. This article sticks to XML for most configuration at the container and context level (since that is what gets overlooked in most examples with Jetty) and switches back to the more convenient annotation driven configuration for the RequestMappings and Controller declarations in SpringMVC. We will see three separate Spring contexts: one for the server, one for the web context, and one for the dispatcher servlet.

Setup

Directory Layout

Assume we are working in a project folder called jetTest

We need to create the following directory structure:


src/main/java
src/main/resources
src/main/webapp

We also need to construct the following files in our project root:


build.gradle
settings.gradle
Procfile
.gitignore
system.properties

Gradle Buildfile

We have a relatively simple build file:


apply plugin: 'java'
apply plugin: 'application'

task wrapper(type: Wrapper) {
    gradleVersion = '1.6'
}

targetCompatibility = '1.7'
sourceCompatibility = '1.7'

group = 'com.toastedbits'
version = '1.0-SNAPSHOT'

mainClassName = 'com.toastedbits.jettest.SpringLauncher'

repositories {
    mavenCentral();
}

dependencies {
    compile 'org.eclipse.jetty.aggregate:jetty-all:9+'
    compile 'org.springframework:spring-webmvc:3.2.3.RELEASE'
    
    runtime 'org.eclipse.jetty:jetty-jsp:9+' //ironically, jetty-jsp is not part of jetty-all
}

task stage(dependsOn: [clean, installApp]) {} //Be careful here, Gradle executes task dependencies in alphabetical order, not order listed.

To support multiple build tools, Heroku works off a series of "build-packs". When a build pack discovers it is appropriate to use (e.g. the Gradle build pack looks for build.gradle in the root project folder) it performs a standardized command. For Gradle this is "gradle stage". The installApp task depends on the build task. In addition to building our application, the installApp task constructs a distributable file tree. It gathers all dependencies into a lib folder and constructs execution scripts in a bin folder to allow simple execution our applicataion with the lib files profided as part of the environment. These bin and lib folders get constructed in build/install/<appName>/{bin,lib}. To execute our application, it becomes a simple matter of ./build/install/jetTest/bin/jetTest

We also make use of the Gradle Wrapper to force usage of the version of Gradle we want or need. The heroku gradle build pack defaults to an ancient version of Gradle that doesn't handle transitive dependencies well (or at all, I didn't stick around to find out) To fix this problem run "gradle wrapper". This will generate the wrapper files necessary to lock into a particular version (1.6 at the time of this article) and check these files into git. Normally checking binaries into git is a travesty, but it is necessary in this case. Heroku will now make use of the gradle wrapper instead of its deprecated version.

Notice we do not make use of the war plugin. Since we are embedding Jetty into our application, there is no need to construct the war file anymore. Furthermore, we are going to be sharing the classpath entries residing in src/main/resources between the server and the running webapp. Performing additional configuration to generate the war file is possible, but will unnecessarily complicate our situation with Heroku.

settings.gradle

When we push code to Heroku, our code is placed in a folder named app. Since Gradle uses the containing folder as the project name by default, it is necessary to override the project name, or rename all references from "jetTest" to "app" (including the project folder name). To force Gradle to use a project name of our chosing, create settings.gradle with the following contents:


rootProject.name = "jetTest"

Note: settings.gradle is also used in configuration of hierarchal multi-project gradle builds

Procfile

The Procfile is unique to Heroku. It gives the Heroku environment instructions on what entry points are available to execute on our application. The "web" entry point gets executed after a successful build. In it we will make use of the distribution scripts previously made available to us from installApp


web: ./build/install/jetTest/bin/jetTest

system.properties

Heroku defaults to JDK 1.6, but the version of Jetty we will be using (Jetty 9) requires JDK 1.7. To fix this, add the following to system.properties:


java.runtime.version=1.7

.gitignore

There are a variety of output artifacts that we do not care to check into our repository, lets be sure to always exclude them:


.gradle/
build/

Environment

Don't forget to set the PORT environment variable so you can perform "gradle stage run" locally. Most development environments use port 8080.

Spring Configuration

To have ultimate control over our Jetty server, we need to embed the Jetty server programmatically. The Jetty documentation shows examples of how we can do this directly in code with simple main methods. It also suggets using a dependency injection framework like Spring, but sadly lacks examples - Challenge Accepted!

Here we construct three tiers with three separate application contexts to define each layer: Container, Webapp, and Servlet. We place the spring context configuration files for each of these in src/main/resources so they are made available to our application's classpath at runtime.

Server Code

We need a main entry point into our application. This is a minimal layer since most all of the server configuration will be performed in Spring.

SpringLauncher.java


package com.toastedbits.jettest;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.ApplicationContext;
import org.eclipse.jetty.server.Server;

public class SpringLauncher {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("serverContext.xml");
        Server server = ctx.getBean("server", Server.class);
        server.start();
        server.join();
    }
}

Web Configuration

In our web context we wire up the remaining two spring layers to a simple SpringMVC annotation driven webapp. The dispatcher servlet is configured to read from servletContext.xml and the central applicationContext is configured to read from webappContext.xml both from the classpath provided by src/main/resources. Any url matching /spring/* will be served by the dispatcher servlet.

web.xml


<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0"
      metadata-complete="true">
    
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
    <servlet>
        <servlet-name>webapp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:/servletContext.xml</param-value>
        </init-param>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>webapp</servlet-name>
        <url-pattern>/spring/*</url-pattern>
    </servlet-mapping>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/webappContext.xml</param-value>
    </context-param>
</web-app>

Sample Controller

Here is a sample controller that makes use of a JSP view.

SampleController.java


package com.toastedbits.jettest.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.ui.ModelMap;

@Controller
@RequestMapping("/hello")
public class SampleController {

    @RequestMapping(method = RequestMethod.GET)
    public String hello(ModelMap model) {
        model.addAttribute("msg", "hello world");
        
        return "hello";
    }
}

Container Context

To construct the spring configuration for a jetty server running a webapp we can inspect the examples provided in Jetty's documentation and translate Jetty components into bean declarations.

serverContext.xml:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
                        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    
    <bean name="webappContext" class="org.eclipse.jetty.webapp.WebAppContext">
        <property name="descriptor" value="src/main/webapp/WEB-INF/web.xml"/>
        <property name="resourceBase" value="src/main/webapp"/>
        <property name="contextPath" value="/"/>
        <property name="parentLoaderPriority" value="true"/>
    </bean>
    
    <bean name="handlerList" class="org.eclipse.jetty.server.handler.HandlerList">
        <property name="handlers">
            <list value-type="org.eclipse.jetty.server.Handler">
                <ref bean="webappContext"/>
                <!--<bean class="org.eclipse.jetty.server.handler.DefaultHandler"/>-->
            </list>
        </property>
    </bean>
    
    <bean name="server" class="org.eclipse.jetty.server.Server">
        <constructor-arg value="#{systemEnvironment['PORT']}"/>
        <property name="handler" ref="handlerList"/>
    </bean>
</beans>

Note we do not hardcode the server's port, this is provided to us by Heroku via the PORT environment variable. We can get this in our spring configuration by use of the Spring Expression Language (SpEL). In Jetty, handlers act similar to Tomcat's valves. The WebappContext handler allows us to provide Jetty a standard War exploded directory or archive file. Here we are providing an exploded war directory in our source tree at src/main/webapp complete with a WEB-INF and META-INF folder in the docroot. We can chain handlers together with the HandlerList handler. The DefaultHandler in this list is commented out. With it enabled, requests that are not handled by other handlers are served with a listing of available contexts running on the server instead of a regular 404 page.

Webapp Context

The webapp context is empty. In the future, services that are shared amongst servlets like database connections and JNDI resources are declared here. Our sample webapp does not make use of them yet (perhaps a future post!)

webappContext.xml:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
                        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
                        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
</beans>

Servlet Context

This servlet context is made available to the dispatcher servlet (front controller) in our web applicaiton. In it we allow for annotation driven configuration and a simple view resolver for jsp pages:

servletContext.xml:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
                        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
                        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
                        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
                        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
                        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
    
    <context:component-scan base-package="com.toastedbits.jettest.controllers"/>
    
    <!-- All views are JSPs loaded from /WEB-INF/jsp -->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

Conclusion

There is a large ammount of configuration that is necessary to drive a web server full stack. Hopefully this article has shed some light on how this can be achieved. During the creation of this webapp there were a number of pitfalls encountered, mostly due to misunderstood conventions. While conventions are powerful productivity boosters when fully understood, they add a considerable ammount to the learning curve. Hopefully this article has helped to expose some of the tricks needed in getting the conventions put forth by Gradle, Heroku, and Jetty to play nice together.