I thought it would be fun to see if I could create a completely self-contained runnable web application that wasn't bound to the traditional application server plus WAR file pattern. After playing with embedded Jetty, and Maven's jar, dependency, and assembly plugins, I came up with a working solution. One option I decided not to go with is a monolithic JAR file tool such as One-JAR, because I didn't want to introduce any third party class loading.
Domain
I put together a very simple Spring MVC "Hello world!" application using Freemarker. Below is the controller, view template, and Spring context configuration, as well as the Jetty server runner.
HelloController.java:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController {
@RequestMapping("/hello")
public void hello(Model model) {
model.addAttribute("greeting", "Hello world!");
}
}
hello.ftl:
mvc-config.xml:
<context:component-scan base-package="com.earldouglas.selfcontained" />
<mvc:annotation-driven />
<bean id="freemarkerConfig"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="classpath:com/earldouglas/selfcontained/" />
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="false" />
<property name="suffix" value=".ftl" />
</bean>
</beans>
ServerRunner.java:
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.Context;
import org.mortbay.jetty.servlet.ServletHolder;
import org.springframework.web.servlet.DispatcherServlet;
public class ServerRunner {
public static void main(String[] arguments) throws Exception {
Server server = new Server(8080);
Context context = new Context(server, "/", Context.SESSIONS);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet
.setContextConfigLocation("classpath:com/earldouglas/selfcontained/mvc-config.xml");
ServletHolder servletHolder = new ServletHolder(dispatcherServlet);
context.addServlet(servletHolder, "/*");
server.start();
server.join();
}
}
This ServerRunner differs from the one I describe here in that it doesn't expect a WAR file or expanded web application directory with the conventional WEB-INF directory structure. In this ServerRunner, the Spring DispatcherServlet is added directly to the Jetty context, eliminating the need for a WEB-INF directory, web.xml file, etc. This is not necessarily a good thing; straying from a standard means you're rolling your own convention and you could have trouble integrating the pattern with other parties. In this case, the goal is to develop a simple embedded web application so there's no need to stick to the standard.
Packaging
Maven makes packing this code very easy. The first step is creating a JAR file containing the above code.
pom.xml:
The Maven JAR plugin packages the compiled classes and resources into a JAR file and sets the Main-Class and Class-Path parameters in the MANIFEST.MF file. In this case, the class path entries are all preceded by "lib/". This will let the dependency JAR files be packaged under a lib subdirectory later.
pom.xml:
The Maven Dependency plugin copies the Maven dependencies into the a directory under the build path. This will allow inclusion of dependent JAR files in the final package.
pom.xml:
The Maven Assembly plugin performs the final packaging as specified by the package.xml descriptor.
package.xml:
The package.xml descriptor generates a .tar.gz file containing the project JAR file as well as the dependency JAR files under a lib subdirectory.
Now a package can be created by running:
This package can be extracted anywhere, and the web application run with: