Tracking HTTP Request Lifecycle and Attribute Changes in Java Servlets
The Java Servlet specification provides event-driven mechanisms to observe state transitions within the HTTP request scope. Developers can monitor request object instantiation and termination, as well as track modifications to bound attributes, by implementing two core interfaces: ServletRequestListener and ServletRequestAttributeListener.
The ServletRequestListener interface intercepts the creation and disposal phases of a request object. Each time an HTTP request reaches the container, a new request instance is generated, triggering the initialization callback. When the container completes response processing or the request chain concludes, the instance is discarded, invoking the destruction callback.
The ServletRequestAttributeListener interface focuses specifically on data bound to the request object. It captures three distinct operations: introducing a new attribute, deleting an existing attribute, and overwriting the value associated with an existing key.
Consolidated Event Handler Implementation
A single class can implement both interfaces to centralize scope monitoring. The following implementation tracks lifecycle transitions and attribute mutations, using unique object references for clear console tracing.
package com.example.web;
import javax.servlet.*;
import javax.servlet.annotation.WebListener;
@WebListener
public class RequestScopeMonitor implements ServletRequestListener, ServletRequestAttributeListener {
@Override
public void requestInitialized(ServletRequestEvent evt) {
ServletRequest activeReq = evt.getServletRequest();
System.out.println("[Lifecycle] Request instance [" + System.identityHashCode(activeReq) + "] initialized.");
}
@Override
public void requestDestroyed(ServletRequestEvent evt) {
ServletRequest activeReq = evt.getServletRequest();
System.out.println("[Lifecycle] Request instance [" + System.identityHashCode(activeReq) + "] terminated.");
}
@Override
public void attributeAdded(ServletRequestAttributeEvent evt) {
ServletRequest activeReq = evt.getServletRequest();
String key = evt.getName();
Object attachedVal = evt.getValue();
System.out.println("[Attr] Bound '" + key + "'=" + attachedVal + " to request [" + System.identityHashCode(activeReq) + "].");
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent evt) {
ServletRequest activeReq = evt.getServletRequest();
String key = evt.getName();
Object detachedVal = evt.getValue();
System.out.println("[Attr] Unbound '" + key + "' (was " + detachedVal + ") from request [" + System.identityHashCode(activeReq) + "].");
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent evt) {
ServletRequest activeReq = evt.getServletRequest();
String key = evt.getName();
Object previousVal = evt.getValue();
Object updatedVal = activeReq.getAttribute(key);
System.out.println("[Attr] Replaced '" + key + "' from " + previousVal + " to " + updatedVal + " in request [" + System.identityHashCode(activeReq) + "].");
}
}
Lsitener Registration
Modern servlet containers automatically scan for the @WebListener annotation during deployment. Alternatively, explicit registration can be configured within the application's deployment descriptor.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<listener>
<listener-class>com.example.web.RequestScopeMonitor</listener-class>
</listener>
</web-app>
Triggering Scope Events
To validate the listener configuration, a basic HTTP servlet can be deployed to perform standard attribute operations during request handling.
package com.example.web;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/scope-test")
public class AttributeHandlerServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
// Triggers attributeAdded callback
req.setAttribute("session_flag", "active");
// Triggers attributeReplaced callback
req.setAttribute("session_flag", "verified");
// Triggers attributeRemoved callback
req.removeAttribute("session_flag");
res.setContentType("text/plain");
res.getWriter().write("Request scope listeners executed.");
}
}
Dispatching a GET request to the /scope-test endpoint sequentially activates the registered callbacks, producing console output that reflects each intercepted lifecycle and data-binding operation.