SpringMVC with Embedded Jetty and Thymeleaf

December 13 2012

As mentioned in my previous post on using embedded Jetty with SpringMVC, I was going to look at simplifying the application by using Thymeleaf instead of JSPs as a view technology.

Well, it turns out that it is much more straightforward.

First we can remove the JSPC plugin from our pom.xml. Second we can completely remove the web.xml file.

Bootstrapping Jetty is much simpler now. We do not have to hook into the Jetty startup process with a lifecycle listener, instead we can directly create the dispatcher servlet and add it to a ServletContextHandler like we would any other servlet:

From JettyConfiguration.java:


@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;  
}  

@Bean  
public ServletContextHandler servletContext() throws IOException {  
    ServletContextHandler handler = new ServletContextHandler();  
    handler.setContextPath("/");  
    handler.setResourceBase(  
        new ClassPathResource("webapp").getURI().toString());  
    handler.addServlet(AdminServlet.class, "/metrics/*");  
    handler.addServlet(dispatcherServlet(), "/");  
    return handler;  
}  

@Bean(initMethod = "start", destroyMethod = "stop")  
public Server jettyServer() throws IOException {  
    Server server = new Server();  
    server.setHandler(servletContext());  
    server.setConnectors(jettyConnectors());  
    return server;  
}  

This is much more straightforward, everything wired up with Spring without any lifecycle callback hooks.

The setup for Spring to render the Thymeleaf views, is a little more complex, but not fussy. What we do is replace:


@Bean  
public InternalResourceViewResolver configureInternalResourceViewResolver() {  
    InternalResourceViewResolver resolver =   
        new InternalResourceViewResolver();  
    resolver.setPrefix("/WEB-INF/views/");  
    resolver.setSuffix(".jsp");  
    return resolver;  
}  

with


@Bean  
public ServletContextTemplateResolver thymeleafTemplateResolver() {  
    ServletContextTemplateResolver resolver =   
        new ServletContextTemplateResolver();  
    resolver.setPrefix("/WEB-INF/thymeleaf/");  
    resolver.setSuffix(".html");  
    resolver.setTemplateMode("HTML5");  
    resolver.setCacheable(true);  
    return resolver;  
}  

@Bean  
public SpringTemplateEngine thymeleafTemplateEngine() {  
    SpringTemplateEngine engine = new SpringTemplateEngine();  
    engine.setTemplateResolver(thymeleafTemplateResolver());  
    return engine;  
}  

@Bean  
public ThymeleafViewResolver thymeleafViewResolver() {  
    ThymeleafViewResolver resolver = new ThymeleafViewResolver();  
    resolver.setTemplateEngine(thymeleafTemplateEngine());  
    return resolver;  
}  

in MvcConfiguration.java.

Our templates now look a lot more like plain HTML, and will render better when loaded directly in a browser:


<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <title>Home</title>  
</head>  
<body>  
<h1>Hello World!</h1>  

<p>Server time: <span th:text="${serverTime}"></span></p>  

<p>Here are some items:</p>  
<ul>  
    <li th:each="item : ${someItems}" th:text="${item}"></li>  
</ul>  

<p>Do we have a message from the dummy service:</p>  

<div th:if="${dummyService == null}">  
    <p>No, dummy service is null.</p>  
</div>  
<div th:if="${dummyService != null}">  
    <p>Yes: <span th:text="${dummyService.getMessage()}"></span></p>  
</div>  

<p><a href="resources/static.txt">A static file.</a></p>  

<p><a href="metrics">Yammer Metrics</a></p>  

</body>  
</html>  

With the build process being simple (no dependence on maven plugins) we can switch to a much less verbose Gradle build file:


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

version = '0.0.1-SNAPSHOT'  

mainClassName = "ca.unx.template.Main"  
applicationName = "jetty-springmvc-thymeleaf-template"  

repositories {  
    mavenCentral()  
}  

dependencies {  
    compile("org.springframework:spring-webmvc:3.1.3.RELEASE") {  
        // Commons-logging excluded in favour of SLF4j.  
        exclude module: 'commons-logging'  
    }  

    compile "cglib:cglib:2.2.2"  
    compile "org.thymeleaf:thymeleaf-spring3:2.0.14"  
    compile "org.eclipse.jetty:jetty-webapp:8.1.8.v20121106"  
    compile "com.yammer.metrics:metrics-servlet:2.2.0"  

    /* Logging. */  
    def slf4jVersion = "1.7.1"  
    compile "ch.qos.logback:logback-classic:1.0.9"  
    compile "org.slf4j:slf4j-api:$slf4jVersion"  
    compile "org.slf4j:jcl-over-slf4j:$slf4jVersion"  
    compile "org.codehaus.groovy:groovy:1.8.6"  
}  

A complete template project can be found here. Note that this links to tag which is the state of the code at the time of this writing.