The Java servlet specification supports annotations since version 3.0. In this article I want to show how to set up a servlet via annotations. There is no more web.xml (what a relief!), except when you need to give your filters a sort-order.
Besides the annnotations I played around with url-patterns and page-forwards and the relation between them. I used as many explicit names and constants as possible to make these relations clear.
Application Specification
There are two servlets, one for log-in, the other displays date and time and provides a log-out button. A filter intercepts every request and checks if log-in has been done. If not, the log-in page is shown. For simplicity there is just one text field and a fixed user name "fri". Once you log in with another name, you will be notified about the correct name.
As of the Maven artifact name and url-patterns given below, the application is loadable via following URLs:
- http://localhost:8080/servletViaAnnotations/
- http://localhost:8080/servletViaAnnotations/log-in
- http://localhost:8080/servletViaAnnotations/date-time
Maven Project and Source
Here are the project's directories and files. I recommend to first build this structure, then compile it with Maven, then import it into Eclipse as "Maven Project".
Click onto the expand controls to see the sources.
servletViaAnnotations
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>fri</groupId> <artifactId>servletViaAnnotations</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>Minimal Servlet with Annotations</name> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> <!-- without this, Eclipse complains about "Dynamic Web Module 4.0 requires Java 1.8 or newer" --> <failOnMissingWebXml>false</failOnMissingWebXml> <!-- without this, Eclipse complains about missing web.xml --> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> </project>
src
main
java
fri
testServlet40
AuthenticationFilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | package fri.testServlet40; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; @WebFilter( urlPatterns = "/*" ) public class AuthenticationFilter implements Filter { private static final String THE_ONE_AND_ONLY = "fri"; @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { final String userName = request.getParameter(LoginServlet.USERNAME_FIELDNAME); if (isAuthenticated(userName)) { chain.doFilter(request, response); // call other filters and finally servlet } else { request.setAttribute( // display error in LoginServlet LoginServlet.LOGIN_ERROR_MESSAGE, (userName != null) ? "Wrong name: '"+escapeHtml(userName)+"', must be '"+THE_ONE_AND_ONLY+"'" : null ); request.getRequestDispatcher(LoginServlet.RELATIVE_URL_PATTERN).forward(request, response); } } private boolean isAuthenticated(String userName) { return THE_ONE_AND_ONLY.equals(userName); } private String escapeHtml(String userName) { return userName .replace("&", "&") .replace("<", "<") .replace(">", ">") .replace("\"", """) .replace("'", "'"); } } |
LoginServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package fri.testServlet40; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet( urlPatterns = "/"+LoginServlet.RELATIVE_URL_PATTERN ) public class LoginServlet extends HttpServlet { public static final String RELATIVE_URL_PATTERN = "log-in"; public static final String USERNAME_FIELDNAME = "userName"; public static final String LOGIN_ERROR_MESSAGE = "LoginErrorMessage"; @Override protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final Object errorMessage = request.getAttribute(LOGIN_ERROR_MESSAGE); final String errorHtml = (errorMessage != null ? "<div>Error: "+errorMessage+"</div>" : ""); response.setContentType("text/html;charset=UTF-8"); final String html = "<html><body>"+ " <form method='POST' action='"+TimeServlet.RELATIVE_URL_PATTERN+"'>"+ " Your Name: <input name='"+USERNAME_FIELDNAME+"' type='text'/>"+ " <input type='submit' value='Log In'/>"+ " </form>"+ errorHtml+ "</body></html>"; final PrintWriter out = response.getWriter(); out.println(html); out.close(); } @Override protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } |
TimeServlet.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package fri.testServlet40; import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Calendar; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet( urlPatterns = "/"+TimeServlet.RELATIVE_URL_PATTERN ) public class TimeServlet extends HttpServlet { public static final String RELATIVE_URL_PATTERN = "date-time"; @Override protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { final Calendar now = Calendar.getInstance(); response.setContentType("text/html;charset=UTF-8"); final String html = "<html><body>"+ " <div>Date: "+date(now)+"</div>"+ " <div>Time: "+time(now)+"</div>"+ " <form method='GET' action='"+LoginServlet.RELATIVE_URL_PATTERN+"'>"+ " <input type='submit' value='Log Out'/>"+ " </form>"+ "</body></html>"; final PrintWriter out = response.getWriter(); out.println(html); out.close(); } @Override protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } private String date(Calendar now) { return new SimpleDateFormat("yyyy-MM-dd").format(now.getTime()); } private String time(Calendar now) { return new SimpleDateFormat("HH:mm:ss").format(now.getTime()); } } |
There are only Java classes, no more XML except the Maven pom.xml.
You can drive such a servlet with any web-server that supports servlet 3.0.
It will search for annotations in all Java classes additionally to reading
the web.xml deployment descriptor.
This is expensive but seems to work well e.g. in Tomcat,
despite the fact that there is no component-scan
directive like Spring has,
which would restrict the time-consuming class-scanning to certain packages.
Explanations
AuthenticationFilter
The @WebFilter annotation on line 11 marks the class as servlet-filter. Its url-pattern is rendered as annotation-attribute urlPatterns, which can be an array of strings. The authentication-filter is interested in every HTTP-request, so it declares the wildcard "/*". Remember that a filter always is called before any servlet.
As soon as a request arrives, the filter reads the USERNAME_FIELDNAME parameter from it (line 25). This is sent by the login-page via a POST parameter in LoginServlet on line 35.
If the input name is not "fri" (line 27 and 42), the request-dispatcher for the LoginServlet's RELATIVE_URL_PATTERN is loaded, and the request gets forwarded to it (line 37). Before that, an error message is built together that renders the wrong and the right log-in name (line 33). Mind that you must escape every user input to avoid XSS attacks (line 34 and 45). The error message is passed to the LoginServlet via a request attribute LOGIN_ERROR_MESSAGE (line 31 and 32).
In case the input name is "fri", the request is dispatched to the servlet that the LoginServlet specifies on line 34 in its form POST action: the TimeServlet's RELATIVE_URL_PATTERN.
Thus the filter uses constants from LoginServlet, but it doesn't know the TimeServlet.
LoginServlet
The LoginServlet is marked by the @WebServlet annotation on line 11. It declares its own url-pattern on line 12. Mind that the servlet specification demands a leading slash "/" for url-patterns, but forwarding to other servlets works via relative paths, as shown e.g. in AuthenticationFilter on line 37.
Note that I specify the url-pattern in the annotation via a Java constant of the class below. This is important to make clear how all these names relate to each other. For example, servlet-names are needed just when filters relate to them, so here are none (although the @WebServlet annotation allows it).
The RELATIVE_URL_PATTERN on line 16 enables others to forward to this servlet without hardcoding strings. The USERNAME_FIELDNAME on line 18 is the name of the request-parameter that carries the user-name. This builds upon an HTML form mechanism, thus you see the constant used on line 35 in the login-form, and in AuthenticationFilter on line 25.
On ine 28, the optional error message coming from AuthenticationFilter is retrieved. Starting from line 33 the HTML for the login form is built together. The POST to the TimeServlet is secified on line 34, the name of the HTML input-field that represents the authentiction request parameter is on line 35. From line 40 on the response is sent to the client browser.
Both servlets in this example support GET and POST HTTP methods, simply by delegating POST to GET. The LoginServlet launches a POST request to not render the user-input in the URL address, which would be a security leak. Thus the forward page will hold POST data, and if you click the browser's "Reload" button, you would have to confirm that POST data get sent again. If you set the method on line 34 to GET, this confirmation would not be displyed. (Supporting both methods makes it easy to switch!)
TimeServlet
The TimeServlet has its @WebServlet annotation on line 13, and its url-pattern below.
It build its output starting from line 30. It displays date and time, and a "Log Out" button below. That button does a GET call to the LoginServlet. If you then click browser "Back", you will see the TimeServlet again, because the browser kept the POST form data for it, thus it looks like you are still logged in. This is just an example servlet, do not use this authentication implementation in a real application!
Conclusion
Using servlets without some framework like JSP or Velocity is tiresome, I would not recommend such. Having HTML source inside Java classes is hardly maintainable. This example is not a realistic application. But it is interesting to uncover the original servlet mechanisms from the end of last century :-)
Keine Kommentare:
Kommentar veröffentlichen