December 2008
Introduction
All
modern Java web applications use Servlets and Filters. They are the backbone
of Java EE, the communication gateway to the World Wide Web.
Now
there is a new specification coming, Servlet 3.0 (JSR-315). The Early Draft
of this specification features some new really neat features, and in my
opinion some mistakes. In this article I'm going to show the new additions
to the EOD (ease of development), comment on them, and try to improve them.
Servlets
Since
Servlet 2.3 we've been using the Servlet interface to create our Servlets.
This interface has one main method called service(ServletRequest reand
a ServletResponse. The abstract class HttpServlet helps us to create a
HTTP suitable Servlet. It contains methods like doGet and doPost.
The
easiest way to create custom behavior was to extend the HttpServlet and
implement your own doGet and/or doPost.
public class GetPostServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
To
instruct a Servlet Container to use your Servlet you have to add them to
a file called the web.xml. This file contains mapping information from
URL to Servlet, so when a user requests the mapped URL the Servlet will
be called and asked for a response.
JSR-315
Servlet
3.0 has a couple of proposed changes. One of the big changes is adding
support for continuations, in the new API you can call request.suspend()
to suspend the request, and later (from another thread) call request.resume(),
the Servlet is then again called to resume the request.
The
second important addition is pluggability. Servlets can now be added to
the container during runtime. If you create an instance of the ServletContextListener
you can call servletContext.addServlet() and servletContext.addServletMapping()
to add Servlets on runtime. Of course this poses a security risk, because
included JARs could register their own Servlets, something you definitely
don't want. But the specification is aware of this, and working on making
it safer (for example, giving you a switch to turn auto-detection off).
The
third major addition are the new annotations. Here is an example of these
annotations in use:
@Servlet(urlMapping={"/myServlet"}, name="MyServlet")
public class PojoServlet() {
@GET
public void handleGet(HttpServletRequest req, HttpServletResponse resp) {
....
}
}
As
you can see there are a couple of new features. First of all, the Servlet
is a POJO; there is no more interface/abstract class. Also notice the url
mapping in the @Servlet annotation; no need for the mapping in the web.xml.
Then
there are the Filters:
@ServletFilter
@FilterMapping(urlPatter="/myFilter")
public class PojoFilter() {
public void doFilter(HttpServletRequest req, HttpServletResponse resp) {
....
}
}
Here
we can see a ServletFilter with the new annotations. This again has the
mapping in the annotation, no need for the web.xml and it is a POJO again,
no more interface/abstract class.
What is the problem?
So
you might ask, what is wrong with this proposals new ease of development?
Well,
maybe a couple of things. First of all, lets see what these new annotations
add:
- The
mapping information is in the Servlet
- The
Servlets are POJO's
- You
can specify which method handles the GET and POST
Well,
first of all the mapping information, which is a great feature. Although
the naming of the annotations is a bit unfortunate. Why does the @Servlet
annotation have the mapping inside, and does the @ServletFilter have a
seperate annotation @FilterMapping? It would be much cleaner to have one
way of declaring these mappings.
The
second and third change (the ability to specify the GET and POST with @GET,
@POST, @HEAD, @PUT etc.) has only one small advantage. Who hasn't written
the following code while creating Servlets:
public class GetPostServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
handleRequest(req,res);
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
handleRequest(req,res);
}
/* Handle GET and POST */
public void handleRequest(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
This
could be done a lot easier with the new annotations:
@Servlet
public class GetPostServlet {
@GET
@POST
public void handleRequest(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
So,
nothing wrong here! Well, I asked if you could specify two of these annotation
on one method during the launch of the specification on JavaOne 2008, but
the spec-lead wasn't sure this could be done. So this is probably not the
way its meant to be used.
And
there is more, this also adds problems to the code, for example what if
I coded this:
@Servlet
public class GetPostServlet {
@GET
public void requestOne(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
@GET
public void requestTwo(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
Now
which method will be called? I have no idea! This will probably not even
be documented, just like having two Servlets mapped on the same URL, you
just can't predict what will happen, its up to the container.
These
annotations are (supposed to be) added to help the developers, but there
are more problems. The IDE doesn't know you are making a Servlet just because
you add one annotation, so there is no support from the current generation
of IDE's. When we had the interface or abstract class you would just override
the methods you want, and you have code-completion. This is a huge advantage
we'll loose when programming with these annotations!
It
gets even worse if you look at the @ServletFilter. When you add this annotation
to your code it turns the POJO into a Filter. But there is no check if
you have implemented the doFilter() method. Also, it is much more error-prone,
what if you accidently misspell "doFilter"?
The
same thing also applies to the @ServletListenerContext, just look at this
example:
@ServletContextListener
public class MyListener {
public void contextInitialized(ServletContextEvent sce) {
....
}
}
Again,
nothing will check if you correctly spelled "contextInitialized" (which
is a pretty hard word to type over and over without error-checking), you'll
always have to manually double check. I'd rather type "implements
ServletContextListener" instead of "@ServletContextListener" and
then let my IDE write the implementing methods.
Okay,
that is enough tough feedback. Next I would like to do something constructive
and explain what direction I would like to see Servlet 3.0 go.
Proposal 1
The
idea of having annotations for the url-mapping if very good. URLs are a
good example of metadata, it doesn't say anything about the class or inner
workings, but it does tell the container when to call it. This is something
you would use an annotation for, also because its not mandatory to have
mapping in the class, it would work perfectly without it. But as I said
before, the naming is a bit off, I'd rather see this:
@ServletMapping(name="....", mapping={"...","..."}) //Use for both Servlets and Filters
The
second thing is the annotations (@GET, @POST). I don't think this will
help the user in any way, so just leave them out. I know the standard reply
will be "Just don't use them if you don't like them...".
But
there are already a lot of Java features we should just 'not use'. The
problem is, people WILL use them, and it doesn't help them! In fact it
will only add more choices/options for the programmers, making it all less
clear if you incidentally have to work with Servlets. I personally haven't
heard anybody complain about the interfaces/abstract classes before...
And I can't seem to find
any new advantage that it adds, only loosing the type-safety!
This
was exactly the thing I was afraid of when they introduced annotations,
people who start using them because they can, not because they have some
metadata they want to have near the classes instead of in separate XML
files.
Proposal 2
But
*sigh* if they really want to use the annotations on the methods, then
please add functionality to the Servlets. Currently they'll just serve
as a (bad) alternative way of writing Servlets. But with a slight change
you could actually add new features with these annotations, like this:
public class ExampleServlet {
@ServletMethod(name="LoginServlet", mapping="/login", type={ServletType.GET, ServletType.POST})
public void handleLogin(...) { ... }
//And:
@FilterMethod(name="AuthorizationFilter", mapping="/login", type={ServletType.POST})
public void doAuthorization(...) { ... }
}
This
way you can have one class (POJO) with all the handling methods for a specific
Use Case. More or less like the new Spring MVC Controllers. You'll still
have the same functionality as the proposed JSR-315 annotations, but now
you can group Servlet methods in one class! You still have the drawbacks
I mentioned before, regarding the type-safety, but by using these annotations
you actually gain some functionality and freedom.
Conclusion
My
preference would still be not using annotations at all for GET/POST methods,
but I've read the Java EE 6 specification is promoting the use of annotations
like this and the JSR-315 writers have 'no choice' (bad excuse). The comments
I made in this article have been send to the JSR-group, but I haven't got
a response yet.
Also
I've been unable to reach members for a reaction and/or explanation and/or
clarification. Recently the Java EE 6 specification went on public review,
and it contains references to this Servlet 3.0 specification, so it will
become part of Java EE 6. This must mean they are working actively, maybe
even franticly, to finish it on time. But I plead them to take the time
to reconsider their choices about the annotations.
PRINTER FRIENDLY VERSION
|