Integrating Custom Servlets, Filters, and Listeners in Spring MVC
Custom Servlet Implementation
package com.example.web.component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CustomHttpServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("CustomServlet Invoked");
resp.getWriter().write("Response from Custom Servlet: " + req.getContextPath());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
Custom Filter Implementation
package com.example.web.filter;
import javax.servlet.*;
import java.io.IOException;
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("LoggingFilter: Request intercepted");
chain.doFilter(request, response);
}
}
Static Resource Filter
package com.example.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class AssetDeliveryFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) request;
String uri = httpReq.getServletPath();
String diskPath = httpReq.getServletContext().getRealPath(uri);
try (OutputStream out = response.getOutputStream();
FileInputStream fis = new FileInputStream(diskPath)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.flush();
}
}
}
Custom Listener Implementation
package com.example.web.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
public class RequestAuditListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("Request Initialized");
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("Request Destroyed");
}
}
Method 1: Using WebApplicationInitializer (Servlet 3.0+)
To register these components programmatically, implement the WebApplicationInitializer interface. Spring detects this via the Servlet 3.0 ServletContainerInitializer mechanism.
package com.example.web.setup;
import com.example.web.component.CustomHttpServlet;
import com.example.web.filter.AssetDeliveryFilter;
import com.example.web.filter.LoggingFilter;
import com.example.web.listener.RequestAuditListener;
import org.springframework.web.WebApplicationInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
public class AppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext context) throws ServletException {
// Register Servlet
context.addServlet("customServlet", CustomHttpServlet.class)
.addMapping("/custom");
// Register Filters
context.addFilter("loggingFilter", LoggingFilter.class)
.addMappingForUrlPatterns(null, false, "/*");
context.addFilter("assetFilter", AssetDeliveryFilter.class)
.addMappingForUrlPatterns(null, false, "/assets/*");
// Register Listeners
context.addListener(RequestAuditListener.class);
}
}
Registration API Overloads
The ServletContext provides several overloads for adding components:
- Servlets:
addServlet(String name, String className | Servlet instance | Class<?>) - Filters:
addFilter(String name, String className | Filter instance | Class<?>) - Listeners:
addListener(String className | Listener instance | Class<?>)
Filter Execution Order
When using addMappingForUrlPatterns(EnumSet<DispatcherType>, boolean isMatchAfter, String...), the execution order is determined by the registration sequence in the onStartup method. Filters registered with isMatchAfter = false take precedence over those set to true.
Mechanism Behind the Magic
In a Servlet 3.0+ container, the server scans for implementations of ServletContainerInitializer. Spring provides SpringServletContainerInitializer, which specifically looks for classes annotated with @HandlesTypes(WebApplicationInitializer.class). It then instantiates and runs the onStartup method of all discovered implementations.
// Simplified logic from SpringServletContainerInitializer
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) {
List<WebApplicationInitializer> initializers = new ArrayList<>();
for (Class<?> clazz : webAppInitializerClasses) {
// Ensure it's a concrete class implementing the interface
if (!clazz.isInterface() && WebApplicationInitializer.class.isAssignableFrom(clazz)) {
initializers.add((WebApplicationInitializer) clazz.newInstance());
}
}
// Sort and execute
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext); // Your custom logic runs here
}
}