Last month, we took a first look at server-side Java, sticking our toes into the water by writing some servlets. Servlets are Java programs that produce dynamic web content. CGI programs are executable programs external to the web server that execute from scratch each time they are invoked. By contrast, Java servlets live inside a servlet container, a Java virtual machine (JVM), that is closely connected to an HTTP server. Whenever the web server needs dynamic content, it makes a request from the servlet container.
In many ways, writing a Java servlet is like writing a mod_perl handler: it gives you a great deal of power, but also requires a fair amount of discipline. It can also be frustrating to write a servlet that is 90% static HTML and 10% Java, and the number of times that you invoke out.println() can become maddening.
An increasingly popular solution to this problem is JavaServer Pages or JSP. JSP is similar in spirit to Microsoft's ASP, as well as to the open-source PHP language and Mason component system. JSP allows you to mix Java with HTML in a number of different ways. The fact that JSPs, like most Java programs, are remarkably platform-independent, means you can write JSPs on a Windows box, run them on a development Linux server and then deploy them on Solaris.
This month, we will take a quick look at JSPs, which are a good way to get to learn Java as well as an easy way for Java programmers to create servlets without having to work too hard.
The idea behind JSPs is remarkably simple: they are servlets in disguise. When a JSP is first invoked, it is automatically turned into a servlet. This servlet is then complied into a Java .class file, which is then executed inside of the servlet container. The first time a JSP is invoked, it will take a little bit longer to return data to the user, due to all the action taking place behind the scenes.
In a JSP, everything is assumed to be static content unless it is placed inside special braces, <% and %>. These tags are known as “scriptlets” in JSP lingo. The following HTML file (which we will name main.jsp) is also a perfectly legitimate JSP:
<HTML> <Head> <Title>Static JSP Title</Title> </Head> <Body> <P>Static JSP Content</P> </Body> </HTML>
The above JSP is rather boring in that it consists exclusively of static content. But the JSP engine doesn't care how many pieces of dynamic content a JSP contains; it will turn the entire thing into a servlet regardless of its complexity. In the case of the above JSP, the resulting servlet will be little more than a long string of out.println() statements inside of the doGet() method.
On my system, I saved the above HTML into main.jsp in the examples directory that comes with Tomcat /usr/java/jakarta-tomcat-3.2.1/webapps/examples/jsp/. This is admittedly not the best place to install it, but it is the easiest.
Once I've installed the JSP, I don't need to do anything else; the system will automatically translate it into a servlet source (.java) file, and then compile it into a Java .class file.
We can execute and view our JSP via the Tomcat server, which operates by default on port 8080, http://localhost:8080/examples/jsp/main.jsp/.
If you've configured Apache and mod_jk to forward servlet and JSP queries to Tomcat, then you should also be able to view main.jsp with this URL: http://localhost/examples/jsp/main.jsp/.
On my system, the .java and .class files generated by the JSP system for main.jsp are in the directory: /usr/java/jakarta-tomcat-3.2.1/work/localhost_8080%2Fexamples. If I list the contents of this directory, I see the following:
_0002fjsp_0002fmain_0002ejspmain.class _0002fjsp_0002fmain_0002ejspmain_jsp_0.java _0002fjsp_0002fmain_0002ejspmain_jsp_1.java _0002fjsp_0002fmain_0002ejspmain_jsp_2.java _0002fjsp_0002fmain_0002ejspmain_jsp_3.java _0002fjsp_0002fmain_0002ejspmain_jsp_4.java _0002fjsp_0002fmain_0002ejspmain_jsp_5.java _0002fjsp_0002fmain_0002ejspmain_jsp_6.java
As you can see, there are seven different .java files, each corresponding to a different version of the original JSP. Each time I modify the JSP, the system must create a new .java file. The Tomcat default keeps previous versions of the JSP-based servlet around; however, there can only be one .class file at a given time, which is clearly the case in this directory.
The names of the .java and .class files are quite long and aren't meant to be entered directly into a web browser. Part of the magic of JSPs is that Tomcat can find the servlet associated with a given URL intelligently and automatically, creating the Java source file as necessary.
You should take a look at the Java source code created by the JSP translator so that you can get a feel for the hard work being done behind the scenes. Our simple, static JSP has been turned into a servlet that takes up more than 100 lines of Java source code. In case we ever have to debug our JSPs from the translated servlet source code—a difficult task, as anyone who has used Perl's HTML::Mason can attest—the servlet includes comments that provide a basic mapping from the line numbers in the original JSP to those in the resulting servlet.
We can jazz things up a bit by adding one of the special JSP tags to main.jsp. The first tag inserts the results of some Java code into the output sent to the user:
<HTML> <Head> <Title>Mini-dynamic JSP Title</Title> </Head> <Body> <P>You are connecting from <%= request.getRemoteHost() %>.</P> </Body> </HTML>
The expression inside of <%= %> is invoked, and its return value is placed in the output stream. Because a JSP is a servlet in disguise, it has access to the objects normally available to a servlet, such as “request” and “response”. Notice how the Java within <%= %> does not end with a semicolon; I can tell you from personal experience that it's difficult to break the habit of inserting semicolons there, but your JSPs will die if you insist on them.
To perform one or more Java computations without having the results sent to the user's browser, use the basic <% %> tags. These tags can be interspersed with HTML, making it possible to have conditional text appear in the response (see Listing 1).
If the hostname of the user's computer is available, we print its name. Otherwise, we print the host's IP address. Notice how the if/then/else block is interspersed with the static HTML. The request.getRemoteAddr() call is invoked only if request.getRemoteHost() returns an empty string (“”).
A number of JSP directives are invoked with the <%@ %> tags. All of these directives take effect at the time of JSP-to-servlet translation. A directive keyword is placed immediately after the @ symbol, followed by zero or more attributes.
For example, let's assume we have a standard sitewide menu bar in a JSP named menubar.jsp:
<table> <tr> <td><a href="one">Option 1</a></td> </tr> <tr> <td><a href="two">Option 2</a></td> </tr> </table>
We can incorporate that into our document using the “include” directive (see Listing 2).
It's important to remember that directives take effect when the JSP is turned into a servlet, not at runtime. Thus, the above example will work fine until you change menubar.jsp. Since the contents of menubar.jsp were incorporated into main.jsp just before the latter was turned into a servlet, the <%@ include %> tag no longer exists and, therefore, will not update things in the way we might expect. The solution is to use a runtime JSP action, described below.
There are two additional special JSP tags. One of them, <%-- --%>, acts as a comment. While it might seem odd to use a JSP comment when HTML comments already exist, the difference is important to remember: JSP comments are removed by the JSP engine when the servlet is created. By contrast, HTML comments are passed through untouched and are visible to any end user who selects the “view source” option on their browser. Given the choice, I tend to put most comments inside of the JSP comment tags, except for those that will help me to debug the JSP's output using the resulting HTML source code.
The final JSP tag, <%! %>, allows you to declare instance variables (also known as fields) for the JSP's resulting servlet. While it might seem tempting to use declaration tags to declare variables you will use in the rest of the JSP, remember that using fields means you must deal with thread safety. Given the headaches associated with threads, it's probably a good idea to avoid them if you can. You can also use the declaration tag to define new methods local to your JSP, although I'm not convinced this is such a good idea.
Directives are useful if we want to affect the way in which the servlet is built. But what if we want to affect the servlet's actions at runtime?
We could, of course, include Java code to perform these actions. But JSP includes a number of special tags that are translated into Java code in the servlet, allowing you to write code without having to know any Java.
JSP action tags are actually XML and are defined in special XML documents known as tag libraries. So while they might appear to be HTML, they aren't, which often means you must pay particular attention to items such as closing tags and slashes.
The built-in JSP tag library includes a number of functions, one of which looks suspiciously similar to the include directive we saw in Listing 2. Listing 3 shows a version of main.html that uses the <jsp:include> action, rather than the directive, to bring in menubar.jsp.
The difference between this version and its predecessor is subtle but significant: whereas the include directive incorporates the named page when the JSP is translated into a servlet, the include action works at runtime. If you were to modify menubar.jsp between invocations of main.jsp, the directive version would ignore the new menu bar, while the action version would display the latest version. Of course, this comes at a cost; the <jsp:include> action performs a runtime request, making it slower and less efficient than the directive.
Because pages requested by <jsp:include> have access to all of the request information from the top-level JSP, it's possible to use <jsp:include> to create dynamically changing menu bars, personalization systems and database access libraries.
There are other JSP actions as well. One of them is <jsp:forward>, which passes the request onto another JSP. As with <jsp:include>, this takes place within the servlet engine, meaning HTTP request and user-session information are still available. For example, Listing 4 shows a version of our JSP that forwards users to another page if their hostname is not identifiable.
If your server does not contain a page named no-reverse.jsp, then the user will get a 404 (file not found) error in their web browser. However, their browser will continue to display the URL of the originally requested page, main.jsp. This is because the JSP forward is performed internally, without the need for an external HTTP forward.
Last month, we wrote some simple servlets that allow us to create and view a web log, sometimes known as blog. Since JSPs are translated into servlets, there is no reason why we cannot create a JSP that accomplishes the same thing as that servlet. It will obviously look a bit different, but the effect should be the same.
Listing 5 contains a JSP (showblog.jsp) that performs the same task as the ShowBlog servlet from last month. In other words, this JSP prints the contents of my web log, as stored in a PostgreSQL database table, sorted from the newest entry to the oldest.
I should note right now that showblog.jsp is a terrible example of how to write JSPs; it is simply meant to demonstrate what is possible, not what is elegant or best. (We'll discuss such issues over the next two months, when we discuss JavaBeans and custom tag libraries.)
Let's go through this JSP, so that you can see exactly how it works.
We begin with two “page” directives. These directives allow us to set up the basic configuration for our JSP, beginning with the MIME ContentType header the page will return, and even permit us to specify the programming language to be interspersed with the nonprogrammatic text. While such functionality does not actually exist, it's theoretically possible to write JSPs that use Perl to produce XML, or Python to produce PNG images.
Notice how we can name one or more attributes in our page directive. The first line of showblog.jsp sets both the language and the ContentType attributes. The second line indicates that the resulting servlet should import the packages within java.sql, which will allow us to connect to our relational database server (PostgreSQL in this case) using JDBC.
After a tiny bit of introductory HTML, we drop deep into Java. We create an SQL connection object and use it to connect to our PostgreSQL server. We retrieve data from the database, and then iterate over the ResultSet on a row-by-row basis.
As I indicated above, this is a terrible way to write JSPs. Not only will the performance be terrible because of the many database connections being created and destroyed, but we have created a horrible mishmash of code and HTML. Indeed, the only thing we seem to have saved here is a bit of effort writing out.println() for producing HTML output.
Moreover, much of the intent behind JSPs is to remove code from the HTML pages, allowing nonprogrammers to create dynamic pages with a minimum of effort. If we must insert this much code in order to create dynamic pages, chances are fairly slim that a nonprogrammer will want to try their hand at web development.
Moreover, our translation of the ShowBlog servlet to a JSP resulted in the removal of several exception-handling routines. Our servlet was intelligent enough to handle the disappearance of the PostgreSQL server and could produce a reasonable error message. Our JSP, by contrast, produced a backtrace containing error messages. This backtrace is useful for developers but is neither friendly nor useful for the end user who will visit our site (in all fairness, we can set the errorPage attribute in the page directive, such that errors are forwarded to a different JSP).
A good solution would remove as much code as possible from JSPs, allowing nonprogrammers to make use of that code in a standard way and separating the programmatic and nonprogrammatic content. And indeed, JSPs come with support for JavaBeans, where each “bean” is actually an object with a variety of methods we can use from within a JSP, using special <jsp: > actions. The trick to a successful JSP deployment depends on, in no small part, an intelligent use of JavaBeans. Moreover, JSPs allow us to create our own tag libraries defining custom actions, so that we can replace even more code with tags that resemble the assorted <jsp: > actions.
JavaServer Pages, or JSPs, provide a coating of syntactic sugar around servlets, which can sometimes be difficult for nonprogrammers to learn. However, this month's most complex example of a JSP demonstrates that they can easily get out of control, containing close to as much code as a typical servlet. While it is easier to work with JSPs overall, the supposed benefits of separate code from HTML fall by the wayside as the JSPs get more complex.
Next month, we will look at JavaBeans, which allow us to push quite a bit of programming to classes defined and maintained outside of the JSP. Following that, we will look at JSP's custom tag libraries, which make it possible to create our own little languages for use inside of our JSPs.