How to Embed Jetty with Spring MVC

Published: 24 Nov 2012

This post will walk you through how to embed Jetty with a simple Spring MVC application which can be run standalone or deployed to CloudFoundry.com with no code changes.

Why Embed Jetty?

The traditional approach for deploying Java web applications has been to package a WAR and have it deployed to a servlet container. While this seems like a fairly simple approach it does have its drawbacks including:

  1. classloader issues between your app and the shared libraries in your servlet container
  2. servlet container differences between environments

Prerequisites

CloudFoundry Runtime

The code below gracefully handles running the app either standalone or in the CloudFoundry.com environment but you still need to include the cloudfoundry-runtime dependency in your Maven pom.xml:

<dependency>
   <groupId>org.cloudfoundry</groupId>
   <artifactId>cloudfoundry-runtime</artifactId>
   <version>${cloudfoundry.runtime.version}</version>
</dependency>

MainApp

The following is the entry point for your application.

package com.digitalsanctum.app.web;

// imports omitted for brevity

public class MainApp {

   private static final Logger log = LoggerFactory.getLogger(MainApp.class);

   public static void main(String[] args) throws Exception {

      final AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();

      CloudEnvironment cloudEnvironment = new CloudEnvironment();
      String profile = (cloudEnvironment.isCloudFoundry()) ? "cloud" : "default";

      ConfigurableEnvironment configurableEnvironment = applicationContext.getEnvironment();
      configurableEnvironment.setActiveProfiles(profile);
      log.info("Active Profile(s) >>> " + Arrays.toString(configurableEnvironment.getActiveProfiles()));

      applicationContext.register(WebConfig.class);

      final ServletHolder servletHolder = new ServletHolder(new DispatcherServlet(applicationContext));
      final ServletContextHandler context = new ServletContextHandler();

      context.setErrorHandler(null); // use Spring exception handler(s)
      context.setContextPath("/");
      context.addServlet(servletHolder, "/");

      String webPort = System.getenv("VCAP_APP_PORT");
      if (webPort == null || webPort.isEmpty()) {
         webPort = "8080";
      }

      final Server server = new Server(Integer.valueOf(webPort));

      server.setHandler(context);

      server.start();
      server.join();
   }
}

The code above is pretty straight forward. First, Spring's AnnotationConfigWebApplicationContext is initialized and we determine if the app is running in CloudFoundry and set the active profile accordingly.

Second, we register the WebConfig.class which configures Spring MVC using annotations (no XML required). Initialize Spring's DispatcherServlet and add it to Jetty's ServletContextHandler.

Finally, determine what port to start the Jetty server with and start it. Note that the VCAP_APP_PORT system variable is the port that's assigned to your app when running in CloudFoundry.

Spring MVC Configuration

The following class configures a bare minimum configuraiton for Spring MVC:

package com.digitalsanctum.app.web;

// imports omitted for brevity

@Configuration
@EnableWebMvc
@Import({DefaultConfig.class, CloudConfig.class})
@ComponentScan(basePackages = {"com.digitalsanctum.app.web.controller"})
public class WebConfig extends WebMvcConfigurerAdapter {

    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Autowired
    public Environment env;

    @Override
    public void configureMessageConverters(List> converters) {
        converters.add(stringConverter());
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/favicon.ico").addResourceLocations("/favicon.ico");
    }

    private StringHttpMessageConverter stringConverter() {
        StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
        stringConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("text", "plain", UTF8)));
        return stringConverter;
    }
}

Here we use the @Import annotation to point to additional configuration classes.

DefaultConfig.class contains the configuration used when running locally (for development). We explicitly declare the "default" profile and point to a local properties file:

@Configuration
@Profile("default")
@PropertySource("file:${user.home}/.app/app.properties")
public class DefaultConfig {
}

CloudConfig.class contains CloudFoundry-specific configuration which again explicity declares the "cloud" profile and makes the CloudEnvironment bean available. Since CloudFoundry doesn't support external properties files, we omit the @PropertySource annotation and favor CloudFoundry's environment variables instead:

@Configuration
@Profile("cloud")
public class CloudConfig {

    @Bean
    public CloudEnvironment cloudEnvironment() {
        return new CloudEnvironment();
    }
}

Conclusion

In summary, I've outlined the major components required to embed Jetty with a Spring MVC app. The full source is available via github.com/digitalsanctum/embed-jetty-spring-mvc.


Jetty and Spring MVC Jobs

jobs by Indeed job search