Document last updated April 4, 2001. We are very interested in feedback that would make these materials better. Feel free to write the author, Jonathan Revusky.
(Este documento en español) (Ce document en français)In this document, we dissect the various parts of this first minimal example. We assume that you managed to go through the concrete steps outlined in the accompanying instructions that explain how to build and deploy the example.
The minimal servlet consists of the following parts:
HelloNiggleServletInteraction.java
hello.nhtml
Conventionally, to create a new servlet using the base Java Servlet API,
you define your own concrete subclass of the abstract class
javax.servlet.http.HttpServlet
and implement the
methods doGet() and doPost(), as well as init() and destroy() if it is necessary
to initialize/cleanup any resources used while the servlet is running.
However, when you write a Niggle-based servlet, there will usually be no need
to redefine the above methods, since the framework's base servlet class,
com.revusky.niggle.servlet.NiggleServlet
provides its own
implementations of doGet(), doPost() and init() that you can use directly.
Of course, the question remains: where do I define my web app's functionality?
The answer is that Niggle defines a separate abstract base class,
com.revusky.niggle.servlet.ServletInteraction
. This is really
the comparable subclassing point in the Niggle framework: you write your
own subclass of ServletInteraction and put your web app's functionality there.
Now, this leads us to a very basic design/architecture difference between
Niggle and the base Servlet API. A servlet is basically a singleton object. One
servlet instance is created and this instance services many HTTP requests over the
course of its lifetime. However, in the Niggle framework, a new instance of your ServletInteraction
subclass is instantiated each time the doPost() or doGet() method in
com.revusky.niggle.servlet.NiggleServlet
is invoked. In other words,
a ServletInteraction
object is a throw-away
object that encapsulates the servicing of one HTTP request.
Now, if you have not done so already, take a peek at the HelloNiggleServlet.java file. You may be surprised to discover that the class is empty! You see, actually, there is no need to define any functionality here, since, as we pointed out above, the parent NiggleServlet class already defines all of the methods we typically override when writing a servlet: doGet(), doPost() and init(). Actually, the only role that this class plays is that the framework uses the name of the servlet to deduce the ServletInteraction subclass to use. Since the name of the servlet is HelloNiggleServlet, the ServletInteraction subclass is assumed to be HelloNiggleServletInteraction. (Read this technical note for a more complete technical explanation of why an application programmer must subclass both NiggleServlet and ServletInteraction.)
Below is the full code of HelloNiggleServletInteraction.java:
import com.revusky.niggle.servlet.*; import javax.servlet.http.*; import java.io.IOException; public class HelloNiggleServletInteraction extends ServletInteraction { /** * The obligatory constructor. Your ServletInteraction subclass must have a * constructor with this signature. This is the case even though we don't * actually have to do anything in the constructor (besides invoking * the superclass's constructor, that is.) */ public HelloNiggleServletInteraction(HttpServletRequest request, HttpServletResponse response, NiggleConfig config) throws IOException { super(request, response, config); } /** * The "default" action handler. This will be invoked * if there is no "action" parameter defined in the request parameters. * Otherwise, if your request defines a parameter "action=foo" * the framework will take this as a signal to invoke the * ServletInteraction's execFoo() method. Our simple "Hello, World" * servlet has only a single "default" action so we only define execDefault(). */ public void execDefault() throws IOException { this.page = getPage("hello.nhtml"); page.expose("title", "Hello, World"); page.expose("message", "Welcome to the brave new niggle world!"); } }
Take a look at the execDefault()
method above. In the first of the three lines,
the page
variable is set and in the next 2 lines, a couple of template
variables are set or exposed -- title
and message
.
So, what is going on here? Well, as you likely know at this point, the philosophy
of the Niggle framework is based on a rigorous separation of presentation from
application logic. Thus, the presentation details of any output to the client are
encapsulated in a page template, which is completely separate from the application
logic per se. So, the ServletInteraction class has a member variable called
page
and this must be set in the course of any execXXXX
method.
The above code is a case in point.
In short, the ServletInteraction instance has a member variable called page
that encapsulates this template construct. Note that the page variable
is an instance of the com.revusky.niggle.templates.Page
interface.
The last section deliberately (or necessarily) left certain questions unanswered. The code in the above example assumes the existence of a page template corresponding to "hello.nhtml" and assumes a mechanism for exposing the template variables in the page was left deliberately vague.
Now it is time to get concrete about this. Below is the trivial "hello.nhtml" template that uses the 2 template variables that the java code sets.
<html> <head> <title>${title}</title> </head> <body> <P>Message: ${message}</P> </body> </html>
Note that the above page template code looks just like an HTML page.
Well, it basically is an HTML page. The only thing special about it is
the presence of the two strings ${title}
and ${message}
that are treated specially. As you likely notice, they correspond to the 2
template variables defined in the java code above via the calls to
page.expose()
.
Essentially, the difference between a page template
and a plain old static HTML page is that some of the content is determined
dynamically. Note that, by default, the niggle framework leverages
the open-source Freemarker
template engine. A key feature of freemarker's syntax is that strings enclosed
in ${...}
are escaped, i.e. determined dynamically.
If you have gone through this example and are comfortable with it, I would encourage you to move on to the next example, a simple guestbook.
One interesting question is why you have to subclass NiggleServlet if it already contains all the functionality you need. I would be the first to admit that having 2 parallel subclassing points -- NiggleServlet and ServletInteraction in this case -- is normally suggestive of somewhat bad design in a class library. It certainly violates the basic KISS (keep it simple, stupid) rule of thumb.
Well, there are actually two reasons for this. The more important reason
is actually kind of hypertechnical in nature. You see, a servlet runner will typically isolate
different web application contexts from one another by having each one run in its own
ClassLoader. If, as is likely, you put niggle.jar on the system classpath, then the
class com.revusky.niggle.servlet.NiggleServlet
would be loaded by the system
ClassLoader. This means that it would not be able to find the various supporting classes
located in <app-base>/WEB-INF/classes -- since those are only visible in the context
of their own ClassLoader. By forcing the application programmer to define a subclass
of NiggleServlet and place it in <app-base>/WEB-INF/classes, we make
sure that the various other supporting classes, such as XXXServletInteraction can be found.
Also, a side benefit of forcing the application programmer to create a subclass of NiggleServlet is that this provides a default class name for the corresponding ServletInteraction subclass. If you name your servlet class XXX, then it is assumed that the ServletInteraction subclass (which, of course, really contains the application's functionality) is named XXXInteraction. Note, however, that, if you do not want to rely on this automatic naming scheme lookup, you can override this by specifying a INTERACTION_CLASS init parameter that specifies a different name for the ServletInteraction subclass. In fact, in prior versions of Niggle, you always had to specify this parameter. However, the default naming scheme means that the Hello, Niggle example can work without the need to specify any init parameters, which is a big win for newbie-friendliness.