For true programmers, there is an inherent joy in programming. Our goal is to maximize the time that we spend in the zone, and our tools should help us do just that. No matter what the craft, the ideal tool is an extension of your mind and limbs: without drawing attention to itself, it humbly enables you to do your work better.
In the world of Java web development, such a tool would be described as lightweight. Java microframeworks have recently emerged as the newest and leanest of frameworks yet. Rather than consolidate a massive feature set, as did Java EE, or even a lighter MVC framework like Spring, microframeworks put the programmer at the center of a coherent set of increasingly powerful, lightweight tools.
This article is the first in a four-part series. We'll start with an overview of three popular Java microframeworks, Spark, Ninja, and Play. Comparing the frameworks will give you a high-level view of what makes microframeworks tick and where they differentiate. You'll also get started with quick setup instructions and a sample application for each framework. In the next three articles we'll dive into the frameworks individually, building a more complex application that highlights the strongest features of each one.
Now let's take our first look at the Ninja framework.
Ninja: Full stack, high scalability
Ninja installs quickly and painlessly. Just download Ninja from Github and run the provided Maven archetype by typing:
mvn archetype:generate -DarchetypeGroupId=org.ninjaframework -DarchetypeArtifactId=ninja-servlet-archetype-simple
You will be prompted for your group and artifact IDs. Once the the download is complete, you can generate a project for your IDE with a simple Maven command, for example: mvn eclipse:eclipse
. Import your new project, and you're ready to code!
To import the project in Eclipse, go to File, and select Import, as shown in Figure 1.
Figure 1. Eclipse project import, Step 1
Next, select Existing Projects into Workspace, as shown in Figure 2:
Figure 2. Eclipse project import, Step 2
Select Browse and choose the directory where your project is located. Recall that Maven created a directory based on your project name, which in my case is /micro-ninja
. Select OK.
Figure 3. Eclipse project import, Step 3
Your project should now appear in the main pane, already selected with a checkbox, as shown in Figure 4.
Figure 4. Eclipse project import, Step 4
When you hit Finish, the fully formed Ninja project should import into your workspace. We'll follow these same steps to import the projects for Spark and Play later on.
When you crack open the Ninja project's navigation pane, you might wonder if you're really looking at a microframework. Ninja's curated set of open-source libraries places it in the category of a full-stack microframework.
A package of interest is Guice. Guice is an IOC (Inversion of Control) library for connecting application dependencies in a de-coupled style. If you're familiar with Spring, Guice performs the same role as Spring's core IOC library.
Dev mode
Drop back to the command-line for a moment, and run: mvn ninja:run
. Now open your browser to localhost:8080
. A simple app is already running; and moreover, it is hot-deploying your code.
You can set the port for dev mode with the command:
mvn ninja:run -Dninja.port=9090
In the Project menu, make sure that Eclipse is set to "Build Project Automatically," as shown in Figure 5. When Eclipse compiles your classes, Ninja will watch for them and make the changes live. Ninja also watches the static resources directory.
Figure 5. Eclipse project import, Step 5
To be sure it's working, let's run a quick test. Open the file /src/main/java/conf/messages.properties
, and make the change shown in Listing 1.
Listing 1. messages.properties
...
# This file is utf-8
header.title=Hello, JavaWorld!
header.home=BAM!
hello.world=Hello, JavaWorld!
...
Reload the page and you'll see the new message: "Hello, JavaWorld!" Ninja's built-in internationalization support and template system inserted it automatically. We'll take a closer look at Ninja's template library in the next section.
Ninja views with FreeMarker
Open up index.ftl.html
, which is the page that renders our localhost:8080
request. Consider the first couple of lines in Listing 2:
Listing 2. index.ftl.html
<#import "../layout/defaultLayout.ftl.html" as layout>
<@layout.myLayout "Home page">
FreeMarker is importing a template, and then invoking it. In the template's header.ftl.html
you'll see a simple HTML template. Now look at this line:
<a class="navbar-brand" href="/">${i18n("header.title")}</a>
This is a template token, which will eventually be replaced by a model value. You'll remember that when we changed the value in messages.properties
, the value was reflected in the generated index.html
. Ninja's internationalization feature, denoted as i18n
above, automatically inserts the value from the message file.
Model values and controllers
Now let's try a more interesting way to get values into the template pages, by setting them in the model.
First, we'll add the following line to our index.ftl.html
page:
Favorite Beatle: ${simplePojo.favBeatle}
Next, we need to set the simplePojo.favBeatle
value. We do that in Ninja's routes file, Routes.java
, which defines all the handlers for each URL. Note that we're able to define our routes in pure Java, with no XML or JSON config at all.
Routes.java
In Routes.java
, you'll see the root URL of the application is configured with a controller. Listing 3 has the relevant line. Note that in Eclipse, you can quickly find this file with ctrl-shift-t, and the start type "Routes."
Listing 3. A Ninja route
router.GET().route("/").with(ApplicationController.class, "index");
Let's unpack this line:
Router.GET
means that we are talking about GET requests (i.e., the HTTP GET method)..route("/")
denotes the root URL..with(ApplicationController.class, "index")
simply says (via a bit of behind-the-scenes reflection): when the URL is matched, send the request to the class (first parameter) using the method (second method).
Next we need to take a look at the ApplicationController.index()
method, because that is what will be called to set up the model for the view. Remember, we are making sure that simplePojo.favBeatle
will resolve to our preferred Englishman.
Listing 4 shows how this is done.
Listing 4. ApplicationController.index() updated
public Result index() {
return Results.html();
}
// …
public static class SimplePojo {
public String content;
}
To get our new index page working, just make it looks like this:
Listing 5. A Ninja route
public Result index() {
SimplePojo simplePojo = new SimplePojo();
simplePojo.favBeatle = "George";
return Results.html().render(simplePojo);
}
// ...
public static class SimplePojo {
public String content;
public String favBeatle;
}
In Listing 5 we've instantiated a SimplePojo
class, setting its favBeatle
member and then rendering a response with the simplePojo
instance, which is exposed to the view template. (Note that favBeatle
is a public member. I'm sure some purists would frown on that design decision, but it does help us avoid getter/setter resolution kruft.)
This is fairly similar to old fashioned JavaServer Pages and Expression Language template variable resolution. simplePojo
is just a value object used to ferry objects to the view. Since we are in dev mode, we can just reload the browser page and view our musical choice.
Notes about Ninja
You've had a quick look at Ninja, and we'll go much more in-depth with this framework in the next article in this series. For now, just note that Ninja is a stateless architecture. All session information is intended to be stored client-side, and the framework has support for that. This in principle means that scaling is as simple as adding more Ninja nodes to your cluster, without concern for sticky sessions.
For this quick demo, we kind of backed into Ninja's request processing architecture, which consists of routes, views, and controllers. We started at the view and moved into routing and controllers. This pattern is repeated in the other frameworks. As I mentioned earlier, I call these types of frameworks RVC frameworks -- the routes, views, and controllers architecture being their core commonality.
Spark 2: Extremely lightweight RVC for Java 8 and up
Now let's turn our attention to Spark. Not to be confused with Apache's big data processing engine, this Spark is the most lightweight of our three RVC frameworks. It draws inspiration from the Ruby framework, Sinatra.
We'll use Spark 2, the latest version as of this writing, for our demo. Spark 2 doesn't support any Java versions below Java 8, so be sure to install the most recent Java update if you want to follow along. It might seem extreme to drop support for Java 7 but it does keep the project very tight. Moreover, Java 8 is a real winner, and it's great to see how Spark integrates some of its standout features. We'll explore Spark's support for lambda expressions when the time comes.
For now, just as we did with Ninja, start by creating a new Maven project, as shown in Listing 6. In this case, there is no Spark specific archetype, so we'll just use a generic Maven archetype.
Listing 6. Creating a new Spark project
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
cd
into our freshly minted project dir
and add Listing 7 to your pom.xml
using a text editor.
Listing 7. Adding Spark core to the Maven POM
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.3</version>
</dependency>
As we did for Ninja, create an Eclipse project (mvn eclipse:eclipse
) and import it as described before. After importing the project to Eclipse, you'll notice a significantly smaller number of libraries than you saw for Ninja. This project holds just the core Spark package, some Jetty libraries for the server, two logging jars, and some websocket support libs. Spark is really compact -- and just wait till you see how fast you can start handling requests.
Next, in Eclipse, go to the App.java
file that Maven added to our project. Notice that this is actually a Java class with a public static voice main()
method -- a standalone executable class. We'll commandeer that for our main class. We can add routes right here in the main class. Update the class as shown in Listing 8.
Listing 8. Adding a Spark route
package org.mtyson;
import spark.Spark;
public class App {
public static void main( String[] args ){
Spark.get("/jw", (req, res) -> "Hello JavaWorld");
}
}
Now run the app -- in eclipse, you can right-click and select run as
, or you can use the alt-shift-x and then the the j key -- and yes, it's really that simple. The above get()
method call invokes an embedded Jetty server to host the app. Spark really is micro.
Figure 6. Executing the Spark main class in Eclipse
Spark request mapping and Java 8 lambdas
Let's take a minute to dissect Spark's mapping from Listing 8. It isn't unlike Ninja's Routes.java
file, but it has its own character.
Spark.get()
defines the HTTP method."/jw"
defines the URL path.- You provide the handler.
Items 1 and 2 are probably obvious, but item 3 might be new to you. Since Java 8 is still fairly fresh, and functional programming is one of its coolest new language features, I'll take a moment to introduce this form of the Java lambda expression.
First, take a look at the message signature for the Spark.get()
called in Listing 8:
Listing 9. Spark.get() method signature
public static void get(final String path, final Route route)
This method uses the Route
object to define the output of the route. Listing 10 shows the Route
interface's one and only method (it's important that it be the one and only).
Listing 10. Spark's Route interface
Object handle(Request request, Response response)