Migrating from Spring MVC to Dropwizard

I’ve been going through the process of moving my Spring MVC web application to DropWizard. One benefit of this technology I hope to utilize is the ability to release an application and not affect cohabitating applications within a web container.

Starting Point

Before the migration, it’s important to know the structure of my application. I use annotation-based beans, and component-scan to construct them.

  • src
    • main
      • java
    • test
  • web
    • resources
    • WEB-INF
      • app.properties
      • database-context.xml
      • root-context.xml
      • servlet-context.xml
      • web.xml
  • target

The configuration XML files are all in WEB-INF, and Maven packaged it as a war for Tomcat to deploy.

Diving into DropWizard, I suggest you follow their tutorial, writing the Configuration and Application in your code.

Migration

Because DropWizard runs the application through a main method, the Spring dependency-injection requirements needs a new approach. Tutorials would indicate migrating completely to a Configuration bean. My solution was a little easier.

  1. Consolidate `root` and `servlet` contexts into one context, `applicationContext` we’ll call it.
  2. The new `applicationContext` can still import resources, but targeting those resources needs to respect the classpath existing within DropWizard.
  3. Create a `Configuration` bean looking like this:
@Configuration
@ImportResource({ "file:applicationContext.xml" })
public class MyAppSpringConfig {
}

The result of this configuration is that it will import the configuration in the `applicationContext`, thus alleviating the work required to move all configuration into a Java bean.

4. Create the DropWizard Configuration (pretty much empty, so not including it) and Application classes. My application follows the example in Jacek99’s GitHub project.

public class MyApplication extends Application<MyConfiguration> {

    public static void main(String[] args) throws Exception {
        new MyApplication().run(args);
    }

    @Override
    public void initialize(Bootstrap<MyConfiguration> bootstrap) {
        String resourcePath = "/assets", uriPath = "/example";
        bootstrap.addBundle(new ConfiguredAssetsBundle(resourcePath, uriPath));
    }

    public void run(MyConfiguration configuration, Environment environment) {
        AnnotationConfigWebApplicationContext rootCtx = new AnnotationConfigWebApplicationContext();
        AnnotationConfigWebApplicationContext servletCtx = new AnnotationConfigWebApplicationContext();

        rootCtx.refresh();
        rootCtx.getBeanFactory().registerSingleton("configuration", configuration);
        rootCtx.registerShutdownHook();
        rootCtx.start();

        // the real main app context has a link to the parent context
        servletCtx.setParent(rootCtx);
        servletCtx.register(MyConfiguration.class);

        WebAppContext webApp = new WebAppContext("localhost:8080", "/");
        servletCtx.setServletContext(webApp.getServletContext());

        servletCtx.refresh();
        servletCtx.registerShutdownHook();
        servletCtx.start();

        // add health checks
        Map<String, HealthCheck> healthChecks = servletCtx.getBeansOfType(HealthCheck.class);
        for (Map.Entry<String, HealthCheck> entry : healthChecks.entrySet()) {
            environment.healthChecks().register(entry.getKey(), entry.getValue());
        }

        // add resources
        Map<String, Object> resources = servletCtx.getBeansWithAnnotation(Path.class);
        for (Map.Entry<String, Object> entry : resources.entrySet()) {
            environment.jersey().register(entry.getValue());
        }

        // add tasks
        Map<String, Task> tasks = servletCtx.getBeansOfType(Task.class);
        for (Map.Entry<String, Task> entry : tasks.entrySet()) {
            environment.admin().addTask(entry.getValue());
        }

        // Add the Spring servlet context to the servlets in DropWizard
        environment.servlets().addServletListeners(new ContextLoaderListener(servletCtx));
    }
}

Controllers

DropWizard will not use view resolvers like Spring, nor will jersey dispatch to @RequestMappings. So the next step is to migrate Spring annotations to Javax WS annotations.

  • @RequestMapping becomes @Path – and the full path is required, not just a suffix
  • RequestMethod.GET becomes @GET (etc)
  • @RequestParam becomes @QueryParam
  • @PathVariable becomes @PathParam
  • @ModelAttribute is removed, but the method annotated with @Consumes(MediaType.APPLICATION_JSON) (media type can vary)

Ending File Structure

After the migration, I moved the configuration xml files to a resources directory beneath main, and view resources inside the web dir.

  • src
    • main
      • java
      • resources
    • test
  • web
    • css
    • js
    • img
  • target

Hurdles

No resources for rendering

Note that DropWizard will not support Java Servlet Pages, as it serves static resources. That’s okay, DropWizard supports some template views and other Javascript frameworks. Web resources can be externally accessible (outside of the jar) using ConfiguredAssetsBundle, and setting the directory name to replace /example with in the yaml:

assets:
    overrides:
        /example: web

Cannot resolve ServletContextResource without ServletContext (WebAppInitializer)

The ApplicationContext requires a ServletContext. To remedy this error, create a WebAppContext to grab the ServletContext, and put it on the ApplicationContext:

AnnotationConfigWebApplicationContext servletCtx = new AnnotationConfigWebApplicationContext();
WebAppContext webApp = new WebAppContext("localhost:8080", "/");
servletCtx.setServletContext(webApp.getServletContext());

1 thought on “Migrating from Spring MVC to Dropwizard

  1. Fab

    Hi Ben,

    Thanks for this post. I found your article, basically trying to add a web context to my existing dropwizard rest application.
    I am trying to implement what you’ve done here but do not get it fully. Would you have a repo to share where I could also see the actual resource files you use (*applicationContext.xml, etc…) ?

Comments are closed.