0
# Listeners and Events
1
2
The Java Servlet API provides a comprehensive event-driven architecture through various listener interfaces. This enables applications to respond to lifecycle events, attribute changes, and other significant events in the servlet container.
3
4
## ServletContext Listeners
5
6
### ServletContextListener Interface
7
8
```java { .api }
9
/**
10
* Listener interface for receiving notifications about ServletContext
11
* lifecycle changes (application startup and shutdown).
12
*/
13
public interface ServletContextListener extends EventListener {
14
15
/**
16
* Called when the ServletContext is initialized (application startup).
17
* This method is invoked before any servlets, filters, or listeners
18
* are initialized.
19
*/
20
void contextInitialized(ServletContextEvent sce);
21
22
/**
23
* Called when the ServletContext is about to be destroyed (application shutdown).
24
* This method is invoked after all servlets and filters have been destroyed.
25
*/
26
void contextDestroyed(ServletContextEvent sce);
27
}
28
```
29
30
### ServletContextAttributeListener Interface
31
32
```java { .api }
33
/**
34
* Listener interface for receiving notifications about ServletContext
35
* attribute changes.
36
*/
37
public interface ServletContextAttributeListener extends EventListener {
38
39
/**
40
* Called when an attribute is added to the ServletContext.
41
*/
42
void attributeAdded(ServletContextAttributeEvent event);
43
44
/**
45
* Called when an attribute is removed from the ServletContext.
46
*/
47
void attributeRemoved(ServletContextAttributeEvent event);
48
49
/**
50
* Called when an attribute in the ServletContext is replaced.
51
*/
52
void attributeReplaced(ServletContextAttributeEvent event);
53
}
54
```
55
56
## ServletContext Event Classes
57
58
```java { .api }
59
/**
60
* Event class for ServletContext lifecycle events.
61
*/
62
public class ServletContextEvent extends java.util.EventObject {
63
64
/**
65
* Create a ServletContextEvent from the given ServletContext.
66
*/
67
public ServletContextEvent(ServletContext source) {
68
super(source);
69
}
70
71
/**
72
* Get the ServletContext that changed.
73
*/
74
public ServletContext getServletContext() {
75
return (ServletContext) super.getSource();
76
}
77
}
78
79
/**
80
* Event class for ServletContext attribute changes.
81
*/
82
public class ServletContextAttributeEvent extends ServletContextEvent {
83
84
private String name;
85
private Object value;
86
87
/**
88
* Create a ServletContextAttributeEvent.
89
*/
90
public ServletContextAttributeEvent(ServletContext source, String name, Object value) {
91
super(source);
92
this.name = name;
93
this.value = value;
94
}
95
96
/**
97
* Get the name of the attribute that changed.
98
*/
99
public String getName() {
100
return name;
101
}
102
103
/**
104
* Get the value of the attribute.
105
* For attributeAdded: the value that was added.
106
* For attributeRemoved: the value that was removed.
107
* For attributeReplaced: the old value that was replaced.
108
*/
109
public Object getValue() {
110
return value;
111
}
112
}
113
```
114
115
## ServletRequest Listeners
116
117
### ServletRequestListener Interface
118
119
```java { .api }
120
/**
121
* Listener interface for receiving notifications about ServletRequest
122
* lifecycle events (request initialization and destruction).
123
*/
124
public interface ServletRequestListener extends EventListener {
125
126
/**
127
* Called when a ServletRequest is about to go out of scope.
128
*/
129
void requestDestroyed(ServletRequestEvent sre);
130
131
/**
132
* Called when a ServletRequest is about to come into scope.
133
*/
134
void requestInitialized(ServletRequestEvent sre);
135
}
136
```
137
138
### ServletRequestAttributeListener Interface
139
140
```java { .api }
141
/**
142
* Listener interface for receiving notifications about ServletRequest
143
* attribute changes.
144
*/
145
public interface ServletRequestAttributeListener extends EventListener {
146
147
/**
148
* Called when an attribute is added to the ServletRequest.
149
*/
150
void attributeAdded(ServletRequestAttributeEvent srae);
151
152
/**
153
* Called when an attribute is removed from the ServletRequest.
154
*/
155
void attributeRemoved(ServletRequestAttributeEvent srae);
156
157
/**
158
* Called when an attribute in the ServletRequest is replaced.
159
*/
160
void attributeReplaced(ServletRequestAttributeEvent srae);
161
}
162
```
163
164
## ServletRequest Event Classes
165
166
```java { .api }
167
/**
168
* Event class for ServletRequest lifecycle events.
169
*/
170
public class ServletRequestEvent extends java.util.EventObject {
171
172
private ServletContext servletContext;
173
174
/**
175
* Create a ServletRequestEvent.
176
*/
177
public ServletRequestEvent(ServletContext sc, ServletRequest request) {
178
super(request);
179
this.servletContext = sc;
180
}
181
182
/**
183
* Get the ServletRequest that is changing.
184
*/
185
public ServletRequest getServletRequest() {
186
return (ServletRequest) super.getSource();
187
}
188
189
/**
190
* Get the ServletContext of the request.
191
*/
192
public ServletContext getServletContext() {
193
return servletContext;
194
}
195
}
196
197
/**
198
* Event class for ServletRequest attribute changes.
199
*/
200
public class ServletRequestAttributeEvent extends ServletRequestEvent {
201
202
private String name;
203
private Object value;
204
205
/**
206
* Create a ServletRequestAttributeEvent.
207
*/
208
public ServletRequestAttributeEvent(ServletContext sc, ServletRequest request,
209
String name, Object value) {
210
super(sc, request);
211
this.name = name;
212
this.value = value;
213
}
214
215
/**
216
* Get the name of the attribute that changed.
217
*/
218
public String getName() {
219
return name;
220
}
221
222
/**
223
* Get the value of the attribute.
224
*/
225
public Object getValue() {
226
return value;
227
}
228
}
229
```
230
231
## HTTP Session Listeners
232
233
### HttpSessionListener Interface
234
235
```java { .api }
236
/**
237
* Listener interface for receiving notifications about HttpSession
238
* lifecycle changes (session creation and destruction).
239
*/
240
public interface HttpSessionListener extends EventListener {
241
242
/**
243
* Called when an HttpSession is created.
244
*/
245
void sessionCreated(HttpSessionEvent se);
246
247
/**
248
* Called when an HttpSession is destroyed (invalidated or timed out).
249
*/
250
void sessionDestroyed(HttpSessionEvent se);
251
}
252
```
253
254
### HttpSessionAttributeListener Interface
255
256
```java { .api }
257
/**
258
* Listener interface for receiving notifications about HttpSession
259
* attribute changes.
260
*/
261
public interface HttpSessionAttributeListener extends EventListener {
262
263
/**
264
* Called when an attribute is added to an HttpSession.
265
*/
266
void attributeAdded(HttpSessionBindingEvent event);
267
268
/**
269
* Called when an attribute is removed from an HttpSession.
270
*/
271
void attributeRemoved(HttpSessionBindingEvent event);
272
273
/**
274
* Called when an attribute in an HttpSession is replaced.
275
*/
276
void attributeReplaced(HttpSessionBindingEvent event);
277
}
278
```
279
280
### HttpSessionActivationListener Interface
281
282
```java { .api }
283
/**
284
* Listener interface for objects that need to be notified when
285
* they are bound/unbound from a session that is being passivated/activated.
286
*/
287
public interface HttpSessionActivationListener extends EventListener {
288
289
/**
290
* Called when the session containing this object is about to be passivated.
291
* Objects can use this notification to release resources or prepare for serialization.
292
*/
293
void sessionWillPassivate(HttpSessionEvent se);
294
295
/**
296
* Called when the session containing this object has been activated.
297
* Objects can use this notification to reacquire resources.
298
*/
299
void sessionDidActivate(HttpSessionEvent se);
300
}
301
```
302
303
### HttpSessionBindingListener Interface
304
305
```java { .api }
306
/**
307
* Listener interface for objects that want to be notified when
308
* they are bound to or unbound from an HttpSession.
309
*/
310
public interface HttpSessionBindingListener extends EventListener {
311
312
/**
313
* Called when the object is bound to a session.
314
*/
315
void valueBound(HttpSessionBindingEvent event);
316
317
/**
318
* Called when the object is unbound from a session.
319
*/
320
void valueUnbound(HttpSessionBindingEvent event);
321
}
322
```
323
324
### HttpSessionIdListener Interface
325
326
```java { .api }
327
/**
328
* Listener interface for receiving notifications about HttpSession ID changes.
329
*/
330
public interface HttpSessionIdListener extends EventListener {
331
332
/**
333
* Called when an HttpSession ID is changed.
334
*/
335
void sessionIdChanged(HttpSessionEvent event, String oldSessionId);
336
}
337
```
338
339
## HTTP Session Event Classes
340
341
```java { .api }
342
/**
343
* Event class for HttpSession lifecycle and ID change events.
344
*/
345
public class HttpSessionEvent extends java.util.EventObject {
346
347
/**
348
* Create an HttpSessionEvent from the given HttpSession.
349
*/
350
public HttpSessionEvent(HttpSession source) {
351
super(source);
352
}
353
354
/**
355
* Get the HttpSession that changed.
356
*/
357
public HttpSession getSession() {
358
return (HttpSession) super.getSource();
359
}
360
}
361
362
/**
363
* Event class for HttpSession attribute binding operations.
364
*/
365
public class HttpSessionBindingEvent extends HttpSessionEvent {
366
367
private String name;
368
private Object value;
369
370
/**
371
* Create an HttpSessionBindingEvent.
372
*/
373
public HttpSessionBindingEvent(HttpSession session, String name) {
374
super(session);
375
this.name = name;
376
}
377
378
/**
379
* Create an HttpSessionBindingEvent with a value.
380
*/
381
public HttpSessionBindingEvent(HttpSession session, String name, Object value) {
382
super(session);
383
this.name = name;
384
this.value = value;
385
}
386
387
/**
388
* Get the name of the attribute that was bound/unbound.
389
*/
390
public String getName() {
391
return name;
392
}
393
394
/**
395
* Get the value of the attribute.
396
*/
397
public Object getValue() {
398
return value;
399
}
400
}
401
```
402
403
## Listener Implementation Examples
404
405
### Application Lifecycle Manager
406
407
```java { .api }
408
/**
409
* Comprehensive application lifecycle listener that manages startup and shutdown
410
*/
411
@WebListener
412
public class ApplicationLifecycleManager implements ServletContextListener,
413
ServletContextAttributeListener {
414
415
private static final Logger logger = LoggerFactory.getLogger(ApplicationLifecycleManager.class);
416
417
@Override
418
public void contextInitialized(ServletContextEvent sce) {
419
ServletContext context = sce.getServletContext();
420
421
logger.info("Application startup initiated");
422
423
try {
424
// Initialize application components
425
initializeDatabase(context);
426
initializeScheduler(context);
427
initializeCaching(context);
428
initializeExternalServices(context);
429
430
// Set application metadata
431
context.setAttribute("app.startTime", System.currentTimeMillis());
432
context.setAttribute("app.version", getClass().getPackage().getImplementationVersion());
433
context.setAttribute("app.status", "RUNNING");
434
435
// Initialize application configuration
436
Properties config = loadApplicationConfig();
437
context.setAttribute("app.config", config);
438
439
// Start background tasks
440
startBackgroundTasks(context);
441
442
logger.info("Application startup completed successfully");
443
444
} catch (Exception e) {
445
logger.error("Application startup failed", e);
446
context.setAttribute("app.status", "FAILED");
447
throw new RuntimeException("Application initialization failed", e);
448
}
449
}
450
451
@Override
452
public void contextDestroyed(ServletContextEvent sce) {
453
ServletContext context = sce.getServletContext();
454
455
logger.info("Application shutdown initiated");
456
457
try {
458
// Set shutdown status
459
context.setAttribute("app.status", "SHUTTING_DOWN");
460
461
// Stop background tasks
462
stopBackgroundTasks(context);
463
464
// Shutdown application components in reverse order
465
shutdownExternalServices(context);
466
shutdownCaching(context);
467
shutdownScheduler(context);
468
shutdownDatabase(context);
469
470
// Clear application attributes
471
clearApplicationAttributes(context);
472
473
logger.info("Application shutdown completed successfully");
474
475
} catch (Exception e) {
476
logger.error("Error during application shutdown", e);
477
}
478
}
479
480
@Override
481
public void attributeAdded(ServletContextAttributeEvent event) {
482
String name = event.getName();
483
Object value = event.getValue();
484
485
logger.debug("ServletContext attribute added: {} = {}", name, value);
486
487
// Monitor critical attributes
488
if ("app.status".equals(name)) {
489
logger.info("Application status changed to: {}", value);
490
}
491
}
492
493
@Override
494
public void attributeRemoved(ServletContextAttributeEvent event) {
495
String name = event.getName();
496
Object value = event.getValue();
497
498
logger.debug("ServletContext attribute removed: {} (was: {})", name, value);
499
}
500
501
@Override
502
public void attributeReplaced(ServletContextAttributeEvent event) {
503
String name = event.getName();
504
Object newValue = event.getServletContext().getAttribute(name);
505
Object oldValue = event.getValue();
506
507
logger.debug("ServletContext attribute replaced: {} = {} (was: {})",
508
name, newValue, oldValue);
509
}
510
511
private void initializeDatabase(ServletContext context) {
512
// Initialize database connections and connection pool
513
logger.info("Initializing database connections");
514
515
DataSource dataSource = createDataSource();
516
context.setAttribute("app.dataSource", dataSource);
517
518
// Test database connection
519
try (Connection conn = dataSource.getConnection()) {
520
logger.info("Database connection established successfully");
521
} catch (SQLException e) {
522
throw new RuntimeException("Failed to establish database connection", e);
523
}
524
}
525
526
private void initializeScheduler(ServletContext context) {
527
logger.info("Initializing task scheduler");
528
529
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
530
context.setAttribute("app.scheduler", scheduler);
531
532
// Schedule periodic maintenance tasks
533
scheduler.scheduleAtFixedRate(() -> {
534
logger.debug("Running periodic maintenance");
535
performMaintenance();
536
}, 1, 1, TimeUnit.HOURS);
537
}
538
539
private void initializeCaching(ServletContext context) {
540
logger.info("Initializing cache system");
541
542
// Initialize cache manager (e.g., EhCache, Redis, etc.)
543
CacheManager cacheManager = createCacheManager();
544
context.setAttribute("app.cacheManager", cacheManager);
545
}
546
547
private void initializeExternalServices(ServletContext context) {
548
logger.info("Initializing external service connections");
549
550
// Initialize HTTP clients, web service clients, etc.
551
HttpClient httpClient = HttpClient.newBuilder()
552
.connectTimeout(Duration.ofSeconds(30))
553
.build();
554
context.setAttribute("app.httpClient", httpClient);
555
}
556
557
private void startBackgroundTasks(ServletContext context) {
558
logger.info("Starting background tasks");
559
560
ScheduledExecutorService scheduler =
561
(ScheduledExecutorService) context.getAttribute("app.scheduler");
562
563
// Start application-specific background tasks
564
scheduler.scheduleAtFixedRate(new SessionCleanupTask(), 0, 30, TimeUnit.MINUTES);
565
scheduler.scheduleAtFixedRate(new LogRotationTask(), 1, 24, TimeUnit.HOURS);
566
}
567
568
private Properties loadApplicationConfig() {
569
Properties config = new Properties();
570
571
try (InputStream input = getClass().getResourceAsStream("/application.properties")) {
572
if (input != null) {
573
config.load(input);
574
}
575
} catch (IOException e) {
576
logger.warn("Could not load application.properties", e);
577
}
578
579
return config;
580
}
581
582
// Shutdown methods
583
private void stopBackgroundTasks(ServletContext context) {
584
ScheduledExecutorService scheduler =
585
(ScheduledExecutorService) context.getAttribute("app.scheduler");
586
587
if (scheduler != null && !scheduler.isShutdown()) {
588
scheduler.shutdown();
589
try {
590
if (!scheduler.awaitTermination(30, TimeUnit.SECONDS)) {
591
scheduler.shutdownNow();
592
}
593
} catch (InterruptedException e) {
594
scheduler.shutdownNow();
595
}
596
}
597
}
598
599
private void shutdownDatabase(ServletContext context) {
600
DataSource dataSource = (DataSource) context.getAttribute("app.dataSource");
601
if (dataSource instanceof Closeable) {
602
try {
603
((Closeable) dataSource).close();
604
logger.info("Database connections closed");
605
} catch (IOException e) {
606
logger.error("Error closing database connections", e);
607
}
608
}
609
}
610
611
// Helper methods
612
private DataSource createDataSource() {
613
// Create and configure DataSource
614
return null; // Implementation specific
615
}
616
617
private CacheManager createCacheManager() {
618
// Create and configure cache manager
619
return null; // Implementation specific
620
}
621
622
private void performMaintenance() {
623
// Perform periodic maintenance tasks
624
}
625
626
private void shutdownExternalServices(ServletContext context) {
627
// Close external service connections
628
}
629
630
private void shutdownCaching(ServletContext context) {
631
// Shutdown cache manager
632
}
633
634
private void shutdownScheduler(ServletContext context) {
635
// Already handled in stopBackgroundTasks
636
}
637
638
private void clearApplicationAttributes(ServletContext context) {
639
// Clear non-essential attributes
640
List<String> attributesToClear = Arrays.asList(
641
"app.dataSource", "app.scheduler", "app.cacheManager", "app.httpClient"
642
);
643
644
for (String attribute : attributesToClear) {
645
context.removeAttribute(attribute);
646
}
647
}
648
649
// Background task implementations
650
private static class SessionCleanupTask implements Runnable {
651
@Override
652
public void run() {
653
// Cleanup expired sessions, temporary files, etc.
654
}
655
}
656
657
private static class LogRotationTask implements Runnable {
658
@Override
659
public void run() {
660
// Rotate application logs
661
}
662
}
663
}
664
```
665
666
### Session Activity Monitor
667
668
```java { .api }
669
/**
670
* Listener that monitors HTTP session activity and user behavior
671
*/
672
@WebListener
673
public class SessionActivityMonitor implements HttpSessionListener,
674
HttpSessionAttributeListener,
675
HttpSessionActivationListener,
676
HttpSessionIdListener {
677
678
private static final Logger logger = LoggerFactory.getLogger(SessionActivityMonitor.class);
679
private static final AtomicLong activeSessions = new AtomicLong(0);
680
private static final Map<String, SessionInfo> sessionRegistry = new ConcurrentHashMap<>();
681
682
@Override
683
public void sessionCreated(HttpSessionEvent se) {
684
HttpSession session = se.getSession();
685
long sessionCount = activeSessions.incrementAndGet();
686
687
SessionInfo sessionInfo = new SessionInfo(
688
session.getId(),
689
session.getCreationTime(),
690
session.getMaxInactiveInterval()
691
);
692
sessionRegistry.put(session.getId(), sessionInfo);
693
694
logger.info("Session created: {} (Total active: {})", session.getId(), sessionCount);
695
696
// Store session metrics in servlet context
697
ServletContext context = session.getServletContext();
698
context.setAttribute("sessions.active.count", sessionCount);
699
context.setAttribute("sessions.total.created",
700
((Long) context.getAttribute("sessions.total.created")) + 1);
701
}
702
703
@Override
704
public void sessionDestroyed(HttpSessionEvent se) {
705
HttpSession session = se.getSession();
706
long sessionCount = activeSessions.decrementAndGet();
707
708
SessionInfo sessionInfo = sessionRegistry.remove(session.getId());
709
if (sessionInfo != null) {
710
long duration = System.currentTimeMillis() - sessionInfo.getCreationTime();
711
logger.info("Session destroyed: {} (Duration: {}ms, Total active: {})",
712
session.getId(), duration, sessionCount);
713
714
// Log session statistics
715
logSessionStatistics(sessionInfo, duration);
716
}
717
718
// Update servlet context metrics
719
ServletContext context = session.getServletContext();
720
context.setAttribute("sessions.active.count", sessionCount);
721
}
722
723
@Override
724
public void attributeAdded(HttpSessionBindingEvent event) {
725
String sessionId = event.getSession().getId();
726
String attributeName = event.getName();
727
Object attributeValue = event.getValue();
728
729
logger.debug("Session attribute added: {} -> {} = {}",
730
sessionId, attributeName, attributeValue);
731
732
// Track user login
733
if ("authenticated".equals(attributeName) && Boolean.TRUE.equals(attributeValue)) {
734
String username = (String) event.getSession().getAttribute("username");
735
logUserLogin(sessionId, username);
736
}
737
738
// Update session info
739
SessionInfo sessionInfo = sessionRegistry.get(sessionId);
740
if (sessionInfo != null) {
741
sessionInfo.addAttribute(attributeName, attributeValue);
742
}
743
}
744
745
@Override
746
public void attributeRemoved(HttpSessionBindingEvent event) {
747
String sessionId = event.getSession().getId();
748
String attributeName = event.getName();
749
Object attributeValue = event.getValue();
750
751
logger.debug("Session attribute removed: {} -> {} (was: {})",
752
sessionId, attributeName, attributeValue);
753
754
// Track user logout
755
if ("authenticated".equals(attributeName)) {
756
String username = (String) attributeValue;
757
logUserLogout(sessionId, username);
758
}
759
760
// Update session info
761
SessionInfo sessionInfo = sessionRegistry.get(sessionId);
762
if (sessionInfo != null) {
763
sessionInfo.removeAttribute(attributeName);
764
}
765
}
766
767
@Override
768
public void attributeReplaced(HttpSessionBindingEvent event) {
769
String sessionId = event.getSession().getId();
770
String attributeName = event.getName();
771
Object oldValue = event.getValue();
772
Object newValue = event.getSession().getAttribute(attributeName);
773
774
logger.debug("Session attribute replaced: {} -> {} = {} (was: {})",
775
sessionId, attributeName, newValue, oldValue);
776
777
// Update session info
778
SessionInfo sessionInfo = sessionRegistry.get(sessionId);
779
if (sessionInfo != null) {
780
sessionInfo.updateAttribute(attributeName, newValue);
781
}
782
}
783
784
@Override
785
public void sessionWillPassivate(HttpSessionEvent se) {
786
String sessionId = se.getSession().getId();
787
logger.debug("Session will passivate: {}", sessionId);
788
789
SessionInfo sessionInfo = sessionRegistry.get(sessionId);
790
if (sessionInfo != null) {
791
sessionInfo.setPassivated(true);
792
}
793
}
794
795
@Override
796
public void sessionDidActivate(HttpSessionEvent se) {
797
String sessionId = se.getSession().getId();
798
logger.debug("Session did activate: {}", sessionId);
799
800
SessionInfo sessionInfo = sessionRegistry.get(sessionId);
801
if (sessionInfo != null) {
802
sessionInfo.setPassivated(false);
803
}
804
}
805
806
@Override
807
public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
808
HttpSession session = event.getSession();
809
String newSessionId = session.getId();
810
811
logger.info("Session ID changed: {} -> {}", oldSessionId, newSessionId);
812
813
// Update session registry
814
SessionInfo sessionInfo = sessionRegistry.remove(oldSessionId);
815
if (sessionInfo != null) {
816
sessionInfo.setSessionId(newSessionId);
817
sessionRegistry.put(newSessionId, sessionInfo);
818
}
819
}
820
821
private void logUserLogin(String sessionId, String username) {
822
logger.info("User login: {} (Session: {})", username, sessionId);
823
824
// Update login metrics
825
// In a real application, you might store this in a database
826
}
827
828
private void logUserLogout(String sessionId, String username) {
829
logger.info("User logout: {} (Session: {})", username, sessionId);
830
}
831
832
private void logSessionStatistics(SessionInfo sessionInfo, long duration) {
833
logger.info("Session statistics - ID: {}, Duration: {}ms, Attributes: {}, Passivated: {}",
834
sessionInfo.getSessionId(),
835
duration,
836
sessionInfo.getAttributeCount(),
837
sessionInfo.isPassivated());
838
}
839
840
// Helper class to track session information
841
private static class SessionInfo {
842
private String sessionId;
843
private final long creationTime;
844
private final int maxInactiveInterval;
845
private final Map<String, Object> attributes;
846
private boolean passivated;
847
848
public SessionInfo(String sessionId, long creationTime, int maxInactiveInterval) {
849
this.sessionId = sessionId;
850
this.creationTime = creationTime;
851
this.maxInactiveInterval = maxInactiveInterval;
852
this.attributes = new ConcurrentHashMap<>();
853
this.passivated = false;
854
}
855
856
public String getSessionId() { return sessionId; }
857
public void setSessionId(String sessionId) { this.sessionId = sessionId; }
858
public long getCreationTime() { return creationTime; }
859
public int getMaxInactiveInterval() { return maxInactiveInterval; }
860
public boolean isPassivated() { return passivated; }
861
public void setPassivated(boolean passivated) { this.passivated = passivated; }
862
863
public void addAttribute(String name, Object value) {
864
attributes.put(name, value);
865
}
866
867
public void removeAttribute(String name) {
868
attributes.remove(name);
869
}
870
871
public void updateAttribute(String name, Object value) {
872
attributes.put(name, value);
873
}
874
875
public int getAttributeCount() {
876
return attributes.size();
877
}
878
}
879
}
880
```
881
882
### Request Performance Monitor
883
884
```java { .api }
885
/**
886
* Listener that monitors request performance and resource usage
887
*/
888
@WebListener
889
public class RequestPerformanceMonitor implements ServletRequestListener,
890
ServletRequestAttributeListener {
891
892
private static final Logger logger = LoggerFactory.getLogger(RequestPerformanceMonitor.class);
893
private static final String REQUEST_START_TIME = "request.startTime";
894
private static final AtomicLong requestCounter = new AtomicLong(0);
895
private static final AtomicLong activeRequests = new AtomicLong(0);
896
897
@Override
898
public void requestInitialized(ServletRequestEvent sre) {
899
long requestId = requestCounter.incrementAndGet();
900
long activeCount = activeRequests.incrementAndGet();
901
long startTime = System.currentTimeMillis();
902
903
ServletRequest request = sre.getServletRequest();
904
905
// Store request metadata
906
request.setAttribute("request.id", requestId);
907
request.setAttribute(REQUEST_START_TIME, startTime);
908
request.setAttribute("request.thread", Thread.currentThread().getName());
909
910
if (request instanceof HttpServletRequest) {
911
HttpServletRequest httpRequest = (HttpServletRequest) request;
912
913
logger.debug("Request initialized: {} {} {} (ID: {}, Active: {})",
914
httpRequest.getMethod(),
915
httpRequest.getRequestURI(),
916
httpRequest.getProtocol(),
917
requestId,
918
activeCount);
919
920
// Log detailed request information for monitoring
921
logRequestDetails(httpRequest, requestId);
922
}
923
924
// Update servlet context metrics
925
ServletContext context = sre.getServletContext();
926
context.setAttribute("requests.active.count", activeCount);
927
context.setAttribute("requests.total.count", requestId);
928
}
929
930
@Override
931
public void requestDestroyed(ServletRequestEvent sre) {
932
ServletRequest request = sre.getServletRequest();
933
long activeCount = activeRequests.decrementAndGet();
934
935
Long requestId = (Long) request.getAttribute("request.id");
936
Long startTime = (Long) request.getAttribute(REQUEST_START_TIME);
937
938
if (requestId != null && startTime != null) {
939
long duration = System.currentTimeMillis() - startTime;
940
String threadName = (String) request.getAttribute("request.thread");
941
942
if (request instanceof HttpServletRequest) {
943
HttpServletRequest httpRequest = (HttpServletRequest) request;
944
945
logger.debug("Request destroyed: {} {} (ID: {}, Duration: {}ms, Active: {})",
946
httpRequest.getMethod(),
947
httpRequest.getRequestURI(),
948
requestId,
949
duration,
950
activeCount);
951
952
// Log performance metrics
953
logPerformanceMetrics(httpRequest, requestId, duration, threadName);
954
955
// Check for slow requests
956
if (duration > 5000) { // 5 seconds threshold
957
logger.warn("Slow request detected: {} {} (Duration: {}ms, ID: {})",
958
httpRequest.getMethod(),
959
httpRequest.getRequestURI(),
960
duration,
961
requestId);
962
}
963
}
964
}
965
966
// Update servlet context metrics
967
ServletContext context = sre.getServletContext();
968
context.setAttribute("requests.active.count", activeCount);
969
}
970
971
@Override
972
public void attributeAdded(ServletRequestAttributeEvent srae) {
973
String attributeName = srae.getName();
974
Object attributeValue = srae.getValue();
975
976
// Log important attribute additions
977
if (isImportantAttribute(attributeName)) {
978
Long requestId = (Long) srae.getServletRequest().getAttribute("request.id");
979
logger.debug("Request attribute added: {} = {} (Request ID: {})",
980
attributeName, attributeValue, requestId);
981
}
982
}
983
984
@Override
985
public void attributeRemoved(ServletRequestAttributeEvent srae) {
986
String attributeName = srae.getName();
987
Object attributeValue = srae.getValue();
988
989
// Log important attribute removals
990
if (isImportantAttribute(attributeName)) {
991
Long requestId = (Long) srae.getServletRequest().getAttribute("request.id");
992
logger.debug("Request attribute removed: {} (was: {}) (Request ID: {})",
993
attributeName, attributeValue, requestId);
994
}
995
}
996
997
@Override
998
public void attributeReplaced(ServletRequestAttributeEvent srae) {
999
String attributeName = srae.getName();
1000
Object oldValue = srae.getValue();
1001
Object newValue = srae.getServletRequest().getAttribute(attributeName);
1002
1003
// Log important attribute replacements
1004
if (isImportantAttribute(attributeName)) {
1005
Long requestId = (Long) srae.getServletRequest().getAttribute("request.id");
1006
logger.debug("Request attribute replaced: {} = {} (was: {}) (Request ID: {})",
1007
attributeName, newValue, oldValue, requestId);
1008
}
1009
}
1010
1011
private void logRequestDetails(HttpServletRequest request, long requestId) {
1012
StringBuilder details = new StringBuilder();
1013
details.append("Request Details (ID: ").append(requestId).append(")\n");
1014
details.append(" Method: ").append(request.getMethod()).append("\n");
1015
details.append(" URI: ").append(request.getRequestURI()).append("\n");
1016
details.append(" Query: ").append(request.getQueryString()).append("\n");
1017
details.append(" Remote: ").append(request.getRemoteAddr()).append("\n");
1018
details.append(" User-Agent: ").append(request.getHeader("User-Agent")).append("\n");
1019
details.append(" Content-Type: ").append(request.getContentType()).append("\n");
1020
details.append(" Content-Length: ").append(request.getContentLengthLong()).append("\n");
1021
1022
// Log session info if available
1023
HttpSession session = request.getSession(false);
1024
if (session != null) {
1025
details.append(" Session: ").append(session.getId()).append("\n");
1026
}
1027
1028
logger.trace(details.toString());
1029
}
1030
1031
private void logPerformanceMetrics(HttpServletRequest request, long requestId,
1032
long duration, String threadName) {
1033
1034
// In a real application, you might send these metrics to a monitoring system
1035
StringBuilder metrics = new StringBuilder();
1036
metrics.append("Performance Metrics (ID: ").append(requestId).append(")\n");
1037
metrics.append(" Duration: ").append(duration).append("ms\n");
1038
metrics.append(" Thread: ").append(threadName).append("\n");
1039
metrics.append(" Method: ").append(request.getMethod()).append("\n");
1040
metrics.append(" URI: ").append(request.getRequestURI()).append("\n");
1041
1042
// Memory usage
1043
Runtime runtime = Runtime.getRuntime();
1044
long totalMemory = runtime.totalMemory();
1045
long freeMemory = runtime.freeMemory();
1046
long usedMemory = totalMemory - freeMemory;
1047
1048
metrics.append(" Memory Used: ").append(usedMemory / 1024 / 1024).append("MB\n");
1049
1050
logger.debug(metrics.toString());
1051
1052
// Store metrics for aggregation
1053
storeMetrics(request.getMethod(), request.getRequestURI(), duration);
1054
}
1055
1056
private boolean isImportantAttribute(String attributeName) {
1057
// Define which attributes are important to log
1058
return attributeName.startsWith("user.") ||
1059
attributeName.startsWith("security.") ||
1060
attributeName.startsWith("business.") ||
1061
"authenticated".equals(attributeName) ||
1062
"currentUser".equals(attributeName);
1063
}
1064
1065
private void storeMetrics(String method, String uri, long duration) {
1066
// In a real application, store these metrics in a database or monitoring system
1067
// for analysis and alerting
1068
}
1069
}
1070
```
1071
1072
### Smart Attribute Binding Object
1073
1074
```java { .api }
1075
/**
1076
* Example object that implements HttpSessionBindingListener and
1077
* HttpSessionActivationListener to manage its own lifecycle
1078
*/
1079
public class UserProfile implements HttpSessionBindingListener,
1080
HttpSessionActivationListener,
1081
Serializable {
1082
1083
private static final Logger logger = LoggerFactory.getLogger(UserProfile.class);
1084
private static final long serialVersionUID = 1L;
1085
1086
private String username;
1087
private String email;
1088
private Date lastLoginTime;
1089
private Map<String, Object> preferences;
1090
1091
// Transient fields that will be reinitialized after activation
1092
private transient DatabaseConnection dbConnection;
1093
private transient CacheManager cacheManager;
1094
1095
public UserProfile(String username, String email) {
1096
this.username = username;
1097
this.email = email;
1098
this.lastLoginTime = new Date();
1099
this.preferences = new HashMap<>();
1100
1101
// Initialize transient resources
1102
initializeResources();
1103
}
1104
1105
@Override
1106
public void valueBound(HttpSessionBindingEvent event) {
1107
String sessionId = event.getSession().getId();
1108
logger.info("UserProfile bound to session: {} (User: {})", sessionId, username);
1109
1110
// Record user login
1111
recordUserActivity("LOGIN", sessionId);
1112
1113
// Initialize any session-specific resources
1114
initializeSessionResources(event.getSession());
1115
}
1116
1117
@Override
1118
public void valueUnbound(HttpSessionBindingEvent event) {
1119
String sessionId = event.getSession().getId();
1120
logger.info("UserProfile unbound from session: {} (User: {})", sessionId, username);
1121
1122
// Record user logout
1123
recordUserActivity("LOGOUT", sessionId);
1124
1125
// Cleanup resources
1126
cleanupResources();
1127
}
1128
1129
@Override
1130
public void sessionWillPassivate(HttpSessionEvent se) {
1131
String sessionId = se.getSession().getId();
1132
logger.debug("UserProfile will passivate: {} (User: {})", sessionId, username);
1133
1134
// Save state before passivation
1135
saveUserState();
1136
1137
// Release non-serializable resources
1138
releaseTransientResources();
1139
}
1140
1141
@Override
1142
public void sessionDidActivate(HttpSessionEvent se) {
1143
String sessionId = se.getSession().getId();
1144
logger.debug("UserProfile did activate: {} (User: {})", sessionId, username);
1145
1146
// Reinitialize transient resources
1147
initializeResources();
1148
1149
// Restore user state
1150
restoreUserState();
1151
}
1152
1153
public void updateLastActivity() {
1154
this.lastLoginTime = new Date();
1155
1156
// Update activity in persistent storage
1157
if (dbConnection != null) {
1158
try {
1159
dbConnection.updateLastActivity(username);
1160
} catch (Exception e) {
1161
logger.warn("Failed to update last activity for user: " + username, e);
1162
}
1163
}
1164
}
1165
1166
public void setPreference(String key, Object value) {
1167
preferences.put(key, value);
1168
1169
// Persist preference change
1170
persistPreference(key, value);
1171
}
1172
1173
public Object getPreference(String key) {
1174
return preferences.get(key);
1175
}
1176
1177
private void initializeResources() {
1178
try {
1179
// Initialize database connection
1180
this.dbConnection = DatabaseConnectionFactory.createConnection();
1181
1182
// Initialize cache manager
1183
this.cacheManager = CacheManagerFactory.getInstance();
1184
1185
logger.debug("Transient resources initialized for user: {}", username);
1186
1187
} catch (Exception e) {
1188
logger.error("Failed to initialize resources for user: " + username, e);
1189
}
1190
}
1191
1192
private void initializeSessionResources(HttpSession session) {
1193
// Set up session-specific resources
1194
session.setAttribute("user.loginTime", lastLoginTime);
1195
session.setAttribute("user.preferences.count", preferences.size());
1196
}
1197
1198
private void releaseTransientResources() {
1199
if (dbConnection != null) {
1200
try {
1201
dbConnection.close();
1202
} catch (Exception e) {
1203
logger.warn("Error closing database connection for user: " + username, e);
1204
}
1205
dbConnection = null;
1206
}
1207
1208
cacheManager = null;
1209
1210
logger.debug("Transient resources released for user: {}", username);
1211
}
1212
1213
private void cleanupResources() {
1214
// Release all resources when object is unbound
1215
releaseTransientResources();
1216
1217
// Clear preferences cache
1218
if (cacheManager != null) {
1219
cacheManager.evict("user.preferences." + username);
1220
}
1221
}
1222
1223
private void recordUserActivity(String activity, String sessionId) {
1224
// Record user activity in audit log or analytics system
1225
logger.info("User activity: {} - {} (Session: {})", username, activity, sessionId);
1226
}
1227
1228
private void saveUserState() {
1229
// Save current state to persistent storage before passivation
1230
try {
1231
if (dbConnection != null) {
1232
dbConnection.saveUserPreferences(username, preferences);
1233
dbConnection.updateLastActivity(username);
1234
}
1235
} catch (Exception e) {
1236
logger.error("Failed to save user state for: " + username, e);
1237
}
1238
}
1239
1240
private void restoreUserState() {
1241
// Restore state after activation
1242
try {
1243
if (dbConnection != null) {
1244
Map<String, Object> savedPreferences = dbConnection.loadUserPreferences(username);
1245
if (savedPreferences != null) {
1246
preferences.putAll(savedPreferences);
1247
}
1248
}
1249
} catch (Exception e) {
1250
logger.error("Failed to restore user state for: " + username, e);
1251
}
1252
}
1253
1254
private void persistPreference(String key, Object value) {
1255
// Persist individual preference changes
1256
try {
1257
if (dbConnection != null) {
1258
dbConnection.saveUserPreference(username, key, value);
1259
}
1260
1261
// Update cache
1262
if (cacheManager != null) {
1263
cacheManager.put("user.preference." + username + "." + key, value);
1264
}
1265
} catch (Exception e) {
1266
logger.warn("Failed to persist preference {} for user: {}", key, username, e);
1267
}
1268
}
1269
1270
// Getters and setters
1271
public String getUsername() { return username; }
1272
public String getEmail() { return email; }
1273
public Date getLastLoginTime() { return lastLoginTime; }
1274
public Map<String, Object> getPreferences() { return new HashMap<>(preferences); }
1275
1276
// Mock helper classes (would be real implementations)
1277
private static class DatabaseConnectionFactory {
1278
static DatabaseConnection createConnection() { return new DatabaseConnection(); }
1279
}
1280
1281
private static class DatabaseConnection {
1282
void updateLastActivity(String username) throws Exception {}
1283
void saveUserPreferences(String username, Map<String, Object> preferences) throws Exception {}
1284
Map<String, Object> loadUserPreferences(String username) throws Exception { return null; }
1285
void saveUserPreference(String username, String key, Object value) throws Exception {}
1286
void close() throws Exception {}
1287
}
1288
1289
private static class CacheManagerFactory {
1290
static CacheManager getInstance() { return new CacheManager(); }
1291
}
1292
1293
private static class CacheManager {
1294
void evict(String key) {}
1295
void put(String key, Object value) {}
1296
}
1297
}
1298
```
1299
1300
This comprehensive coverage of listeners and events provides all the tools needed for implementing sophisticated event-driven architectures and monitoring systems in servlet applications, enabling applications to respond intelligently to lifecycle changes and state modifications.