Using Spring Boot with JSPs in Executable Jars
Sample Repository
This sample repository is available at: https://github.com/ghillert/spring-boot-jsp-demo
Build Status |
This repository contains the following 2 examples:
-
jsp-demo-tomcat - Demonstrates using JSPs and Spring Boot executable Jars using Tomcat
-
jsp-demo-undertow - Demonstrates using JSPs and Spring Boot executable Jars using Undertow
Introduction
As you may know, I am a co-organizer for the DevNexus conference, the second-largest enterprise Java conference in North-America in Atlanta, GA. Together with Summers Pittman, I also maintain the Spring-based web-application that drives the website, schedule, call-for-paper (CFP) process and nowadays ticket registrations as well.
Goal
When we started planing for DevNexus 2016, I wanted to modernize the DevNexus application. Specifically, I planned to improve the underlying infrastructure of the app.
The goal was to move away from a stand-alone Tomcat-based servlet-container, which we had been using for the past couple of years. We endured several minutes of down-time whenever a new version of the app was deployed. Sometimes, the Tomcat instance or the server itself gave us grief. Furthermore, I longed for the ability to make blue/green deployments.
Therefore, the goal emerged to move the application over to a Platform as a Service (PaaS) offering, specifically Pivotal Web Services (PWS). I did not want to worry any longer about infrastructure issues, and blue/green deployments would be a breeze to accomplish using PWS.
In order to make this all happen, it became apparent, that migrating the application to Spring Boot would help in that endeavor. Luckily the application was generally architected in a way that made the migration to Spring Boot fairly straightforward. The migration also would simplify things greatly overall as we could take full advantage of Spring Boot’s defaults and also remove some duplicate functionality that was already baked into Spring Boot.
One main sticking point, though, was the used view technology. The DevNexus application has been using JavaServer Pages (JSP) for several years, and we accumulated a non-trivial amount of them. Ultimately, the plan is to migrate the user interface (UI) to a Single Page Application (SPA) but for the 2016 conference (February) that intent was unattainable due to time constraints.
Therefore, the whole migration was a bit in perils initially. As of the current version of Spring Boot at the time of this blog post 1.3.3
, the reference guide states:
JSPs should be avoided if possible, there are several known limitations when using them with embedded servlet containers.
The reference guide then goes on to provide a list of JSP limitations in chapter 27.3.5. Specifically it states that:
An executable jar will not work because of a hard coded file pattern in Tomcat.
What a bummer…
Solution using Tomcat
Just to recap my requirement, I want to serve JSPs out of my classpath so that I can create executable Jar files. Basically, Eliminate the webapps folder.
Note
|
An interesting aspect of this is, that one can compose web applications out of multiple JARs, each possibly containing JSPs that are automatically served. |
Unfortunately, taking my Maven-based project, putting your JSPs into e.g. src/main/resources/public
or src/main/resources/static
does not work. While reading the JSR-245 JavaServer™ Pages 2.1 Specification as well as the following in interesting blog post titled Serving Static Content with Servlet 3.0, it became apparent that I should also be able to store static resources in the META-INF/resources
directory. Heureka it worked!
So the simple thing to remember is to store your JSPs in a folder like /src/main/resources/META-INF/resources/WEB-INF/jsp
and you’re good to go (Plus some minor configuration around). To make things easy, lets go over a little example project.
Getting started with Spring Initializr
The best way to start a Spring Boot project is to head over to http://start.spring.io/. Using Spring Initializr underneath, the website lets you customize and create Spring Boot starter projects. For our requirement, we want to create a simple web
project.
Selecting web
enables Full-stack web development with Tomcat and Spring MVC. Now you can press the Generate Project
button, which will start the download of a Zip file containing your customized project.
Note
|
Instead of following the individual steps, you can also download the fully configured sample project from GitHub. Just clone the Demo Project using: $ git clone https://github.com/ghillert/spring-boot-jsp-demo.git
$ cd spring-boot-jsp-demo/jsp-demo-tomcat |
Unzip the project to a directory of your choosing and cd into jsp-demo-tomcat
.
Add Maven Dependencies
In order to enable JSP support using Tomcat we need to add a few dependencies to
our new project in pom.xml
.
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
Define the location of your JSP templates
Next we need to define the template prefix and suffix for our JSP files in application.properties
. Thus add:
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
Important
|
Keep in mind that we will ultimately, place the JSP templates under src/main/resources/META-INF/resources/WEB-INF/jsp/
|
Create a Spring Web Controller
Create a simple web controller:
package com.hillert.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloWorldController {
@RequestMapping("/")
public String helloWorld(Model model) {
model.addAttribute("russian", "Добрый день");
return "hello-world";
}
}
Create the JSP Template
Next, create the corresponding JSP file hello-world.jsp
in the directory src/main/resources/META-INF/resources/WEB-INF/jsp/
:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %><%
response.setHeader("Cache-Control","no-cache");
response.setHeader("Pragma","no-cache");
response.setHeader("Expires","0");
%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<c:set var="ctx" value="${pageContext['request'].contextPath}"/>
<html>
<body>
<h1>Hello World - ${russian}</h1>
</body>
</html>
Run the Sample Application
Now it is time to run the application - execute:
$ mvn clean package
$ java -jar jsp-demo-tomcat/target/jsp-demo-tomcat-1.0.0-BUILD-SNAPSHOT.jar
In your console you should start seeing how the application starts up.
Once started, open your browser and go to the following Url http://localhost:8080/
What about Jetty?
I still need to look more closely at Jetty. Consider it "Work in Progress". Below you will find a a few pointers on the subject:
What about Undertow?
Undertow is another alternative for using an embedded container with Spring Boot. You can find general information in the Spring Boot reference guide chapter Use Undertow instead of Tomcat.
While I was working on updating the Spring Boot documentation regarding the JSP support
for Tomcat, I noticed the following line in the reference guide for Spring Boot 1.3.3
:
Undertow does not support JSPs.
Being a good citizen, I dug a little deeper and discovered the Undertow JSP sample application by Chris Grieger. It turns out that Undertow has indeed JSP support by using jastow, which is a Jasper fork for Undertow. The key was to adapt the Undertow JSP sample application for Spring Boot.
Doing so was actually fairly straightforward. The actual Undertow configuration
uses Spring Boot`s
EmbeddedServletContainerCustomizer
:
final UndertowDeploymentInfoCustomizer customizer = new UndertowDeploymentInfoCustomizer() {
@Override
public void customize(DeploymentInfo deploymentInfo) {
deploymentInfo.setClassLoader(JspDemoApplication.class.getClassLoader())
.setContextPath("/")
.setDeploymentName("servletContext.war")
.setResourceManager(new DefaultResourceLoader(JspDemoApplication.class))
.addServlet(JspServletBuilder.createServlet("Default Jsp Servlet", "*.jsp"));
final HashMap<String, TagLibraryInfo> tagLibraryInfo = TldLocator.createTldInfos();
JspServletBuilder.setupDeployment(deploymentInfo, new HashMap<String, JspPropertyGroup>(), tagLibraryInfo, new HackInstanceManager());
}
};
The full source is available in the
JspDemoApplication
class.
The main issue is more or less the retrieval and configuration of the used Taglibraries. The Undertow JSP sample provides the TldLocator class, which does the heavy lifting. For our example, I am [adapting that class] so that it works in the context of Spring Boot.
In Spring Boot we are dealing with über-Jars, meaning the resulting executable jar file will contain other jar files representing its dependencies.
Spring provides some nifty helpers to retrieve the needed Tag Library Descriptors (TLD) files.
In TldLocator#createTldInfos
I use a ResourcePatternResolver
, specifically
a PathMatchingResourcePatternResolver
with a location pattern of classpath*:*/.tld
.
final URLClassLoader loader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
final ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(loader);
final Resource[] resources;
final String locationPattern = "classpath*:**/*.tld";
try {
resources = resolver.getResources(locationPattern);
}
catch (IOException e) {
throw new IllegalStateException(String.format("Error while retrieving resources"
+ "for location pattern '%s'.", locationPattern, e));
}
Important
|
Don’t forget the asterix right after |
Once we have the TLD resources, they will be parsed and ultimately used to create
a collection of org.apache.jasper.deploy.TagLibraryInfo
.
With those at hand, we create a JSP deployment for Undertow using the DeploymentInfo
and the TagLibraryInfo
collection.
final HashMap<String, TagLibraryInfo> tagLibraryInfo = TldLocator.createTldInfos();
JspServletBuilder.setupDeployment(deploymentInfo, new HashMap<String, JspPropertyGroup>(), tagLibraryInfo, new HackInstanceManager());
And that’s it. Simply build and run the application and you should have a working JSP-based application.
$ mvn clean package
$ java -jar jsp-demo-tomcat/target/jsp-demo-tomcat-1.0.0-BUILD-SNAPSHOT.jar
In your console you should start seeing how the application starts up.
Once started, open your browser and go to the following Url http://localhost:8080/.
Conclusion
In this blog post I have shown how easy it is to use JSP templates with Spring Boot in executable Jars by simply putting your templates into src/main/resources/META-INF/resources/WEB-INF/jsp/
.
While JSPs are often touted as being legacy, I see several reasons why they stay relevant today (2016):
-
You need to migrate an application to Spring Boot but have an existing sizable investment in JSP templates, that can’t be migrated immediately (My use-case)
-
While Single Page Applications (SPA) are all the rage, you may have use-cases where the traditional Spring Web MVC approach is still relevant
-
Even for SPA scenarios, you may still use dynamically-created wrapper pages (e.g. to inject data into the zero-payload HTML file)
-
Also JSP are battle-tested in large scale environments, e.g. at Ebay
-
Even with alternative frameworks, you may run into issues
In any event, I hope this expands your toolbox when using Spring Boot. There is simply no reason why you shouldn’t enjoy the benefits of Spring Boot to the fullest extent permissible by law. Remember, Make JAR, not WAR.