Embedded Jetty and Spring: The Lightweight Antidote for Java EE Complexity
Embedded Jetty and Spring: The Lightweight Antidote for Java EE Complexity
Enterprise Java development does not need to be heavyweight and Ant-driven; with embedded Jetty and Spring, it can be lean, mean, fast, and—above all—productive.by
Jacek Furmankiewicz
n my 15 years as a software professional I've never seen a software stack as unproductive—not to mention as verbose or complicated—as J2EE, despite its enterprise-grade features. Every single J2EE project I have ever worked on ended the same way: me looking at long lines of Ant build scripts constantly rebuilding WARs and EARs (starting/stopping/re-deploying, and so on). No developer should spend half of his or her day waiting for even the simplest of code changes to be redeployed by an Ant script.
When my current company evaluated the future maintenance of our J2EE-based applications, it became clear that some of the core components needed a wholesale rewrite. Choosing Spring over Java EE 5 was an obvious choice (more on that later), but choosing the appropriate web container took a bit more time. After struggling with some of the "bugginess" of Eclipse WTP (especially when it comes to re-deploying project code into the web container), a simpler, leaner alternative emerged: using Spring with embedded Jetty.
Embedded Jetty Versus Standalone Web Containers
Jetty is widely recognized for its speed among Java web containers (mostly due to its NIO-based networking code), but the ability to start it in embedded mode has been one of its killer features for years. When you use Jetty in embedded mode you simply start it up from your regular main() method and it just runs within the context of your application.
Using embedded Jetty also has a few huge productivity boosts, namely:
- It's very fast to start up (approx. one second on my dev box).
- Running it in debug mode enables hot code replacement, where the JVM instantly reloads any code you change in your Java classes (business components, servlets, etc.) or JSPs. Not a single second is wasted on redeploying WARs or EARs.
- It works in any standard Java IDE; there is no IDE or vendor lock-in.
The JVM's hot code replacement in particular has been a tremendous boost to our day-to-day productivity. I haven't seen a single Ant build in weeks. In those rare cases when I actually needed to restart the application (e.g., upon changing some Spring configuration), it took no more than 3-4 seconds.
Benefits of Spring Versus Java EE/J2EE
Often when Java EE 5 is compared with Spring, the focus is mostly on basic features such as dependency injection. But Spring is so much more than just that. In particular, its template classes are an amazing example of simplifying an API to maximize productivity. After discovering the Spring JdbcTemplate, which made working with raw JDBC so easy and fast, I lost all interest in developing a JPA model for our database. Spring also has many other template/helper classes, such as those for JMS or remoting (e.g., integrating the Hessian binary protocol into our application was a walk in the park).
Also, most of the best open-source Java libraries (such as Apache CXF or Apache Active MQ) now come with Spring integration out of the box. This gives you the flexibility to choose whichever best-of-breed components you need, instead of being stuck with the typical monolithic Java EE container's all-or-nothing approach.
Moreover, Spring sub-projects such as Spring Security or Spring BlazeDS are so far ahead of the Java EE stack's offerings (or have no equivalents in the stack at all), that anyone selecting Java EE as the basis for new application development really needs to reconsider that choice.
What You Need Jetty 6 (the latest stable release) Spring Framework 2.5 Eclipse IDE for Java Developers Apache CXF (to add web services) Apache ActiveMQ (to add messaging)
Setting Up Eclipse for Development
You can set up embedded Jetty in a variety of ways, but this is the way I do it most of the time:
- Create a separate Eclipse Java project for the main server. Add the Jetty and Spring JARs as external JARs to make them accessible to the server. Also, create three folders underneath the main project folder:
- etc: This is where all the config files will go.
- logs: This is where Jetty will place its log files.
- webapps: This is a dummy directory that Jetty needs just to start up.
- Copy the jetty.xml config file from your Jetty/etc folder into the project's /etc folder. This will allow you to control the characteristics of your Jetty server (e.g., web port, number of threads, and so on).
Your main server project should look something like Figure 1 after being set up. Remember to make all the third-party JARs available to other projects by exporting them in the project's build properties configuration (see Figure 2).
Figure 1. A Configured Embedded Jetty Server Project: Your main server project should look something like this.
Figure 2. Exported Jetty and Spring JARs: Make all the third-party JARs available to other projects.
The Main Server Class
Your main server class (effectively the main() method of your application) is a thin layer responsible only for starting and configuring Jetty. I usually prefer to keep the actual application in a separate project. That way, the application is independent of embedded Jetty and I can build it as a regular WAR and deploy it to a standalone Jetty instance later for QA or production.
Here is the minimal code required for a typical embedded Jetty start up:
Server jetty = new Server(); String[] configFiles = {"etc/jetty.xml"}; for(String configFile : configFiles) { XmlConfiguration configuration = new XmlConfiguration(new File(configFile).toURI().toURL()); configuration.configure(jetty); } jetty.start();
That's it! The best part is that running this code makes Jetty start up in less than one second (on my machine). Starting and stopping your application is much faster than re-deploying a WAR could ever be, and it makes enterprise/web development in Java as easy as developing a regular application.
Open up your browser and point it to localhost:8080 and you should see a message from Jetty informing you that it's running. However, it won't have any contexts configured. That comes next.
Setting Up the Application
To configure Jetty to serve an application at a particular context path, you will set up a separate application project in Eclipse.
Begin by adding a new Java project to your workspace, called simply MyApp. Besides the default src folder for Java classes, also add a new folder called web, which will contain all the web content (e.g., JSPs, web config files, and so on). Because this is a regular Java project and not an Eclipse WTP dynamic web project, no special plugins are required. This makes the whole approach easy to reproduce in other IDEs such as NetBeans or IntelliJ.
Under the web folder, create the WEB-INF folder. In it, you will add the following standard web.xml file, which registers Spring:
<web-app> <context-param> <param-name>contextConfigLocation</param-name> <param-value>WEB-INF/beans.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app>
Next, add a Spring beans.xml file in the WEB-INF folder. To avoid excessive XML configuration, configure it to use annotations for business components:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:annotation-config /> <context:component-scan base-package="com.devx.myapp" /> </beans>
Obviously, replace the base package com.devx.myapp with one that is specific to your application.
Last but not least, go back to your server project and explicitly configure your web application:
Server jetty = new Server(); String[] configFiles = {"etc/jetty.xml"}; for(String configFile : configFiles) { XmlConfiguration configuration = new XmlConfiguration(new File(configFile).toURI().toURL()); configuration.configure(jetty); } //configure your web application WebAppContext appContext = new WebAppContext(); appContext.setContextPath("/myapp"); File rd = new File("./"); File warPath = new File(rd,"../MyApp/web"); appContext.setWar(warPath.getAbsolutePath()); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[]{ appContext, new DefaultHandler()}); jetty.setHandler(handlers); jetty.start();
The section starting with WebAppContext configures MyApp under the /myapp context. When you run the main server class, you should see output like this in the console:
2009-06-21 07:47:13.585::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2009-06-21 07:47:13.672::INFO: Statistics on = false for SelectChannelConnector @ 0.0.0.0:8080 2009-06-21 07:47:13.756::INFO: jetty-6.1.3 2009-06-21 07:47:13.962:/myapp:INFO: Initializing Spring root WebApplicationContext 21-Jun-2009 7:47:13 AM org.springframework.web.context.ContextLoader initWebApplicationContext INFO: Root WebApplicationContext: initialization started 21-Jun-2009 7:47:14 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.web.context.support.XmlWebApplicationContext@1950198: display name [Root WebApplicationContext]; startup date [Sun Jun 21 07:47:13 EDT 2009]; root of context hierarchy 21-Jun-2009 7:47:14 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/beans.xml] 21-Jun-2009 7:47:14 AM org.springframework.context.support.AbstractApplicationContext obtainFreshBeanFactory INFO: Bean factory for application context [org.springframework.web.context.support.XmlWebApplicationContext@1950198]: org.springframework.beans.factory.support.DefaultListableBeanFactory@5d391d 21-Jun-2009 7:47:14 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@5d391d: defining beans [org.springframework.context.annotation.internalCommonAnnotationProcessor, org.springframework.context.annotation.internalAutowiredAnnotationProcessor, org.springframework.context.annotation.internalRequiredAnnotationProcessor]; root of factory hierarchy 21-Jun-2009 7:47:14 AM org.springframework.web.context.ContextLoader initWebApplicationContext INFO: Root WebApplicationContext: initialization completed in 384 ms 2009-06-21 07:47:14.419::INFO: Started SelectChannelConnector @ 0.0.0.0:8080
Even with a configured, Spring-enabled web application now being served, start-up time is still extremely fast, as this line indicates:
INFO: Root WebApplicationContext: initialization completed in 384 ms
With this in place, your daily productivity will not suffer from the constant overhead of re-deploying your WAR and EARs when your web or business logic changes. Also, unlike the often buggy hot-deploy features of Eclipse WTP, this approach works 100 percent of the time.
Bear in mind one small gotcha: You must remember to add the MainServer project as a dependency to your application (see Figure 3) so that it can access all the Spring classes it exports. When configured, your application project should look like the one in Figure 4.
Figure 3. Adding a Dependency on the Server Project: Add the MainServer project as a dependency to your application.
Figure 4. A Configured Web Application Project: When configured, your application project should look like this.
Annotation-Enabled Business Components
To avoid the traditional verbose Spring XML configuration, the example used the new annotation-based approach instead, meaning that to create a Spring-managed component you simply need to annotate it with @Component, for example:
import org.springframework.stereotype.Component; @Component public class MyComponent { }
In order to use the component in a service using dependency injection, all you need is the @Autowired annotation:
@Service public class MyService { @Autowired(required=true) private MyComponent myComponent; }
The only entries in the Spring beans.xml file are those that usually require external configuration anyway (e.g., JDBC entries for datasources), but you can configure all the business logic using annotations, which is much easier.
However, as stated previously, Spring's benefits go far beyond its dependency injection features. Its extremely useful template classes (e.g., JdbcTemplate) are what really give you that extra boost in day-to-day productivity. If you're new to Spring, you certainly should explore them.
Web Services, Messaging, and Logging
If you need web services (SOAP and/or REST) in your project, I recommend the Apache CXF framework. It comes with Spring integration out of the box. Refer to their documentation for details.
If you need message-driven POJOs in your app, I recommend you integrate Apache ActiveMQ. It too is Spring-enabled out of the box. The Spring messaging documentation has ample examples of how to post and consume messages using message-driven POJOs
If you want to use Log4J in your projects, just add the Apache Log4J libraries to the main server and place the standard log4j.properties file in the server's /etc folder.
Jetty Deployment Options
I hope this article has provided you a path out of the nightmare that is J2EE development and provided a good start to any new projects you may be launching. Enterprise Java development does not need to be heavyweight and Ant-driven; with embedded Jetty and Spring, it can be lean, mean, fast, and—above all—productive. You just need to choose the best combination of available open source tools and libraries to make it happen.
Although you used embedded Jetty for development in the example presented here, nothing is stopping you from compiling your application project (i.e., MyApp in the example) as a typical WAR and deploying it to a standalone Jetty installation for the QA/integration phase of your project. You can still reap the benefits of embedded Jetty during your day-to-day development.
Jacek Furmankiewicz is a Senior Java EE and Erlang/OTP developer at Radialpoint. He has 15 years of professional IT experience and is a Sun Certified Java Programmer (SCJP).