Introduction
I have been trying to come up with a way to highlight these fascinating classes in a way that demonstrates their power and usefulness. In my post Java EE 7 Tip of the Day: Programmatic Control of Session Tracking with ServletContextListener I highlighted the use of theServletContextListener
to set session tracking programmatically. However, there are a number of listener classes that can help you. Here they are in generally decreasing scope:ServletContainerInitializer
ServletContextListener
ServletContextAttributeListener
HttpSessionListener
HttpSessionAttributeListener
HttpSessionIdListener
HttpSessionBindingListener
ServletRequestListener
ServletRequestAttributeListener
Web Application Lifecycle Listeners
ServletContainerInitializer
TheServletContainerInitializer
is a special case that must be registered with the Service Provider Interface (SPI) loading mechanism. Please see the Javadocs for more details on its usage. That is a topic for a different post.
ServletContextListener
TheServletContextListener
has been previously mentioned in another post as noted above. This listener is notified of lifecycle changes, e.g. context initialized and destroyed. This gives the developer the opportunity to perform application setup such as setting a logger, or enabling other listeners. It also provides a clean mechanism to handle application cleanup when an application is shutdown, or disabled.
ServletContextAttributeListener
TheServletContextAttributeListener
listens for events that occur when attributes are added, modified, or removed from the ServletContext
. This could be used to modify those attributes, or perhaps log them. Interestingly, the order in which the implementations are invoked is not specified.
HttpSessionListener
TheHttpSessionListener
listens for session creation, and destruction events. I have found this to be one of the most useful classes in a web application. This can be used to set up a persistent data connection while the session is valid, and close the connection when the session expires, or is invalidated.The listener implementations are invoked in the order of declaration, and destroyed in reverse order. A way to think of this is like the layers of filo dough. If you go from top to bottom, then you must reverse order from bottom to top.
HttpSessionAttributeListener
The sibling interfaceHttpSessionAttributeListener
is the second most used class in my toolbox for listeners behind the ServletContextListener and HttpSessionListener. I have found that I often need to examine when attributes are added, modified, or removed from a session. This is the tool I use to manage that.Keep in mind that the HttpSessionAttributeListener behaves like the ServletContextAttributeListener in that the order in which the implementations are invoked is unspecified. This means that you can not rely on a specified ordering to take place across containers. Usually, the ordering is consistent on a container basis, but the contract is explicit that the ordering is undefined.
HttpSessionIdListener
This is a new to Java EE 7. TheHttpSessionBindingListener
is used to handle when a HttpSession
ID changes. This is usually the result of a HttpServletRequest.changeSessionId()
command. This was added in Java EE 7 to help solve a security issue called Session Fixation. Typically, you would change the session id after the user successfully authenticates. This can help you with this transition. Otherwise, you need to do a lot of work to make it happen. In this case, it is just great to be able to let the API handle it.This interface will allow you to modify your application based on the ID change. Please note that the order in which the implementations are invoked is not specified.
HttpSessionBindingListener
TheHttpSessionBindingListener
listens for binding events. This occurs when an object is bound, or unbound from a session. There are a couple of examples from BalusC on Stackoverflow. Here are a couple: Getting SessionScoped bean from HttpSessionListener? and How to access HTTP sessions in Java. These are just a couple of examples on its usage. I personally have never used it directly. I was just thinking I should come up with my own example for it.
ServletRequestListener
TheServletRequestListener
is another listener that I use. However its usage is not as frequent as the other aforementioned ones. I usually end up checking the ServletRequest
with an instanceof
for HttpServletRequest
and casting it. This listener will allow you to modify an individual request.Since it has every request pass through it, you should make sure that there is no excessive overhead in the listener such as database lookups, or excessive logging. Abuse of this listener can result in massive performance issues.
ServletRequestAttributeListener
Finally, we have theServletRequestAttributeListener
that listens for request attribute changes. The same warning as noted in the ServletRequestListener
applies here. Any excessive overhead will result in dramatic performance decreases.Like the other attribute listeners, this listener does not have a specific order in which they are invoked.
Code Examples
The code for this project was developed using NetBeans 7.4, Apache Maven, Mercurial, and is hosted on Bitbucket. The code in the examples is somewhat contrived, but does demonstrate the functionality of the listeners.I strongly recommend downloading the code, executing it, and looking at the output. It will give you a better picture of how the code works, and also show you some hidden "features" of your application that you may be unaware of.
The source code can be downloaded from here: web-application-listeners.
ServletContextListenerImpl.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 | package com.bluelotussoftware.web.listener.impl; import java.text.MessageFormat; import java.util.EnumSet; import java.util.HashMap; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.SessionTrackingMode; import javax.servlet.annotation.WebListener; /** * An implementation of {@link ServletContextListener} web application lifecycle * listener. * * @author John Yeary * @version 1.0 */ @WebListener public class ServletContextListenerImpl implements ServletContextListener { /** * {@inheritDoc} * <p> * We add a {@code HashMap<String,Object>} attribute to the * {@link ServletContext}, and set the session tracking to use Cookie * tracking. This will override the web.xml file.</p> */ @Override public void contextInitialized(ServletContextEvent sce) { System.out.println(MessageFormat.format( "ServletContext initialized {0}" , sce.getServletContext().getServerInfo())); // Add an attribute to the ServletContext. sce.getServletContext().setAttribute( "com.bluelotussoftware.SERVLET_CONTEXT_MAP" , new HashMap<String, Object>()); // Set the session tracking globally for this servlet context to Cookie. This will override web.xml session tracking. Set<sessiontrackingmode> modes = EnumSet.noneOf(SessionTrackingMode. class ); modes.add(SessionTrackingMode.COOKIE); sce.getServletContext().setSessionTrackingModes(modes); } /** * {@inheritDoc} * <p> * Destroy the {@code HashMap<String,Object>} attribute when the context is * destroyed.</p> */ @Override public void contextDestroyed(ServletContextEvent sce) { // Remove the attribute we set in the ServletContext. sce.getServletContext().removeAttribute( "com.bluelotussoftware.SERVLET_CONTEXT_MAP" ); System.out.println( "ServletContext destroyed." ); } } |
ServletContextAttributeListenerImpl.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 | package com.bluelotussoftware.web.listener.impl; import java.text.MessageFormat; import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeListener; import javax.servlet.annotation.WebListener; /** * Web application lifecycle listener. * * @author John Yeary * @version 1.0 */ @WebListener public class ServletContextAttributeListenerImpl implements ServletContextAttributeListener { /** * {@inheritDoc} */ @Override public void attributeAdded(ServletContextAttributeEvent event) { System.out.println(MessageFormat.format( "{0} added servlet context attribute {1}" , event.getName(), event.getValue())); } /** * {@inheritDoc} */ @Override public void attributeRemoved(ServletContextAttributeEvent event) { System.out.println(MessageFormat.format( "{0} removed servlet context attribute {1}" , event.getName(), event.getValue())); } /** * {@inheritDoc} */ @Override public void attributeReplaced(ServletContextAttributeEvent event) { System.out.println(MessageFormat.format( "{0} replaced servlet context attribute {1}" , event.getName(), event.getValue())); } } |
HttpSessionListenerImpl.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 | package com.bluelotussoftware.web.listener.impl; import java.text.DateFormat; import java.text.MessageFormat; import java.util.Date; import java.util.HashMap; import java.util.UUID; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; /** * Web application lifecycle listener. * * @author John Yeary * @version 1.0 */ @WebListener public class HttpSessionListenerImpl implements HttpSessionListener { private static final DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); /** * {@inheritDoc} */ @Override public void sessionCreated(HttpSessionEvent se) { System.out.println(MessageFormat.format( "Session ID {0} created at {1}" , se.getSession().getId(), df.format( new Date(se.getSession().getCreationTime())))); // Add a session attribute and add the key to the ServletContext attribute com.bluelotussoftware.SERVLET_CONTEXT_MAP. UUID uuid = UUID.randomUUID(); se.getSession().setAttribute(uuid.toString(), uuid); HashMap<String, Object> map = (HashMap<String, Object>) se.getSession().getServletContext().getAttribute( "com.bluelotussoftware.SERVLET_CONTEXT_MAP" ); map.put(se.getSession().getId(), uuid.toString()); } /** * {@inheritDoc} */ @Override public void sessionDestroyed(HttpSessionEvent se) { // Remove ServletContext attribute. We don't need to remove our session attribute since the session is being destroyed. HashMap<String, Object> map = (HashMap<String, Object>) se.getSession().getServletContext().getAttribute( "com.bluelotussoftware.SERVLET_CONTEXT_MAP" ); map.remove(se.getSession().getId()); System.out.println(MessageFormat.format( "Session ID {0} destroyed at {1}" , se.getSession().getId(), df.format( new Date(System.currentTimeMillis())))); } } |
HttpSessionAttributeListenerImpl.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 | package com.bluelotussoftware.web.listener.impl; import java.text.MessageFormat; import javax.servlet.annotation.WebListener; import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionBindingEvent; /** * Web application lifecycle listener. * * @author John Yeary * @version 1.0 */ @WebListener public class HttpSessionAttributeListenerImpl implements HttpSessionAttributeListener { /** * {@inheritDoc} */ @Override public void attributeAdded(HttpSessionBindingEvent event) { System.out.println(MessageFormat.format( "Session ID {0} {1} added {2}" , event.getSession().getId(), event.getName(), event.getValue())); } /** * {@inheritDoc} */ @Override public void attributeRemoved(HttpSessionBindingEvent event) { System.out.println(MessageFormat.format( "Session ID {0} {1} removed {2}" , event.getSession().getId(), event.getName(), event.getValue())); } /** * {@inheritDoc} */ @Override public void attributeReplaced(HttpSessionBindingEvent event) { System.out.println(MessageFormat.format( "Session ID {0} {1} replaced {2}" , event.getSession().getId(), event.getName(), event.getValue())); } } |
ServletRequestListenerImpl.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 60 61 62 63 64 65 66 67 68 69 70 71 | package com.bluelotussoftware.web.listener.impl; import java.text.MessageFormat; import java.util.HashMap; import java.util.UUID; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.WebListener; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * Web application lifecycle listener. * * @author John Yeary * @version 1.0 */ @WebListener public class ServletRequestListenerImpl implements ServletRequestListener { /** * {@inheritDoc} */ @Override public void requestDestroyed(ServletRequestEvent sre) { if (sre.getServletRequest() instanceof HttpServletRequest) { HttpServletRequest hsr = (HttpServletRequest) sre.getServletRequest(); HttpSession session = hsr.getSession( false ); // We will remove the old UUID and set a new UUID if (session != null ) { HashMap<String, Object> map = (HashMap<String, Object>) session.getServletContext().getAttribute( "com.bluelotussoftware.SERVLET_CONTEXT_MAP" ); String uuid_key = (String) map.get(session.getId()); session.removeAttribute(uuid_key); UUID uuid = UUID.randomUUID(); session.setAttribute(uuid.toString(), uuid); map.put(session.getId(), uuid.toString()); hsr.removeAttribute( "com.bluelotussoftware.UUID_TOKEN" ); } } System.out.println( "Servlet Request destroyed." ); } /** * {@inheritDoc} */ @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println( "Servlet Request initialized." ); if (sre.getServletRequest() instanceof HttpServletRequest) { HttpServletRequest hsr = (HttpServletRequest) sre.getServletRequest(); Cookie[] cookies = hsr.getCookies(); if (cookies != null && cookies.length > 0 ) { for (Cookie cookie : cookies) { System.out.println(MessageFormat.format( "Cookie --> {0} value --> {1} domain --> {2} max-age --> {3}" , cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getMaxAge())); } } // We want to set a unique UUID for each request. HttpSession session = hsr.getSession( false ); if (session != null ) { HashMap<String, Object> map = (HashMap<String, Object>) session.getServletContext().getAttribute( "com.bluelotussoftware.SERVLET_CONTEXT_MAP" ); String uuid_key = (String) map.get(session.getId()); UUID uuid = (UUID) session.getAttribute(uuid_key); hsr.setAttribute( "com.bluelotussoftware.UUID_TOKEN" , uuid.toString()); } } } } |
ServletRequstAttributeListenerImpl.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 | package com.bluelotussoftware.web.listener.impl; import java.text.MessageFormat; import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeListener; import javax.servlet.annotation.WebListener; /** * Web application lifecycle listener. * * @author John Yeary * @version 1.0 */ @WebListener public class ServletRequstAttributeListenerImpl implements ServletRequestAttributeListener { /** * {@inheritDoc} */ @Override public void attributeAdded(ServletRequestAttributeEvent srae) { System.out.println(MessageFormat.format( "{0} added request attribute {1}" , srae.getName(), srae.getValue())); } /** * {@inheritDoc} */ @Override public void attributeRemoved(ServletRequestAttributeEvent srae) { System.out.println(MessageFormat.format( "{0} removed request attribute {1}" , srae.getName(), srae.getValue())); } /** * {@inheritDoc} */ @Override public void attributeReplaced(ServletRequestAttributeEvent srae) { System.out.println(MessageFormat.format( "{0} replaced request attribute {1}" , srae.getName(), srae.getValue())); } } |