ServletContextListener là một trong số nhiều Servlet Listener (trình theo dõi sự kiện trong Servlet) hiện có.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu về Servlet Listener, lợi ích của chúng, một số tác vụ phổ biến có thể thực hiện với Listener, các interface Listener của servlet API và các đối tượng Event. Cuối cùng, ta sẽ tạo một dự án web đơn giản để minh họa cách triển khai các Listener thường dùng cho ServletContext, Session và ServletRequest.
Tại sao chúng ta cần Servlet Listener?
Bằng cách sử dụng ServletContext, ta có thể tạo một attribute (thuộc tính) với phạm vi toàn ứng dụng để tất cả các servlet khác đều truy cập được. Tuy nhiên, trong file mô tả triển khai (web.xml), ta chỉ có thể khởi tạo các khởi tạo tham số của ServletContext dưới dạng chuỗi String. Vậy nếu ứng dụng chủ yếu làm việc với cơ sở dữ liệu và ta muốn đặt một attribute trong ServletContext cho các kết nối cơ sở dữ liệu thì sao?
Nếu ứng dụng chỉ có một truy cập vào duy nhất (ví dụ: trang đăng nhập), ta có thể thực hiện việc này trong request đầu tiên cho servlet. Nhưng nếu có nhiều điểm vào, việc lặp lại logic này ở khắp nơi sẽ dẫn đến trùng lặp code. Hơn nữa, nếu cơ sở dữ liệu gặp sự cố hoặc cấu hình sai, ta sẽ không phát hiện vấn đề ra cho đến khi có request đầu tiên từ client.
Để giải quyết những tình huống này, servlet API cung cấp các interface Listener. Ta có thể triển khai và cấu hình các Listener này để lắng nghe một event và thực hiện một hành động cụ thể.
Event (sự kiện) là một hành động nào đó xảy ra. Trong thế giới ứng dụng web, event có thể là việc khởi tạo hoặc hủy ứng dụng, một request từ client, tạo/hủy một session, thay đổi attribute trong session, v.v.
Servlet API cung cấp nhiều loại interface Listener khác nhau. Ta có thể triển khai và cấu hình chúng trong web.xml
để xử lý tác vụ khi một event cụ thể xảy ra. Ví dụ, với tình huống trên, ta có thể tạo một Listener cho event khởi động ứng dụng. Listener này sẽ đọc các tham số khởi tạo ngữ cảnh, tạo kết nối cơ sở dữ liệu và gán nó vào tham số ngữ cảnh để các tài nguyên khác có thể sử dụng.
Các interface Listener và đối tượng Event trong Servlet
Servlet API cung cấp nhiều loại Listener khác nhau cho các loại Event khác nhau. Các interface Listener định nghĩa các phương thức để xử lý một nhóm các event tương tự. Ví dụ, ServletContext Listener lắng nghe event khởi động (startup) và tắt (shutdown) của context.
Mỗi phương thức trong interface Listener đều nhận một đối tượng Event làm tham số đầu vào. Đối tượng Event này đóng vai trò như một lớp vỏ bọc (wrapper), cung cấp đối tượng cụ thể cho Listener.
Servlet API cung cấp các đối tượng event sau:
- javax.servlet.AsyncEvent: Event được kích hoạt khi một hoạt động bất đồng bộ (asynchronous) được bắt đầu trên một ServletRequest (thông qua lời gọi đến ServletRequest#startAsync hoặc ServletRequest#startAsync(ServletRequest, ServletResponse)) đã hoàn thành, hết thời gian (timeout), hoặc gây ra lỗi.
- javax.servlet.http.HttpSessionBindingEvent: Loại event này được gửi đến một đối tượng triển khai HttpSessionBindingListener khi nó được gán (bind) hoặc gỡ (unbind) khỏi một session. Hoặc, nó được gửi đến một HttpSessionAttributeListener (đã được cấu hình trong web.xml) khi một attribute bất kỳ được gán, gỡ, hoặc thay thế trong session. Session gán đối tượng thông qua lời gọi đến HttpSession.setAttribute và gỡ đối tượng bằng HttpSession.removeAttribute. Ta có thể dùng event này cho các hoạt động dọn dẹp khi đối tượng bị xóa khỏi session.
- javax.servlet.http.HttpSessionEvent: Đây là class đại diện cho các thông báo event về những thay đổi đối với session trong một ứng dụng web.
- javax.servlet.ServletContextAttributeEvent: Class event dành cho các thông báo về sự thay đổi attribute của ServletContext trong một ứng dụng web.
- javax.servlet.ServletContextEvent: Đây là class event dành cho các thông báo về sự thay đổi đối với ngữ cảnh servlet của một ứng dụng web.
- javax.servlet.ServletRequestEvent: Loại event này báo hiệu các sự kiện trong vòng đời của một ServletRequest. Nguồn (source) của event chính là ServletContext của ứng dụng web.
- javax.servlet.ServletRequestAttributeEvent: Đây là class event dành cho các thông báo về sự thay đổi attribute của servlet request trong một ứng dụng.
Servlet API cung cấp các interface Listener sau đây:
- javax.servlet.AsyncListener: Listener sẽ được thông báo trong trường hợp một hoạt động bất đồng bộ được khởi tạo trên ServletRequest (mà Listener đã được thêm vào) hoàn thành, hết thời gian, hoặc gây ra lỗi.
- javax.servlet.ServletContextListener: Interface để nhận thông báo về các thay đổi trong vòng đời của ServletContext.
- javax.servlet.ServletContextAttributeListener: Interface để nhận thông báo về các thay đổi thuộc tính của ServletContext.
- javax.servlet.ServletRequestListener: Interface để nhận thông báo sự kiện về các request được khởi tạo và kết thúc trong một ứng dụng web.
- javax.servlet.ServletRequestAttributeListener: Interface để nhận thông báo về các thay đổi thuộc tính của ServletRequest.
- javax.servlet.http.HttpSessionListener: Interface để nhận thông báo về các thay đổi trong vòng đời của HttpSession.
- javax.servlet.http.HttpSessionBindingListener: Cho phép một đối tượng nhận được thông báo khi nó được gắn vào hoặc gỡ ra khỏi một session.
- javax.servlet.http.HttpSessionAttributeListener: Interface để nhận thông báo về các thay đổi thuộc tính của HttpSession.
- javax.servlet.http.HttpSessionActivationListener: Các đối tượng được gắn vào một session có thể lắng nghe các sự kiện từ container, nhận thông báo khi session được tạm ngừng hoặc kích hoạt. Một container có cơ chế di chuyển session giữa các máy ảo hoặc lưu trữ session, bắt buộc phải thông báo cho tất cả các thuộc tính được gắn vào session có triển khai HttpSessionActivationListener.
Cấu hình Servlet Listener
Ta có thể dùng annotation @WebListener
để khai báo một class là Listener
. Tuy nhiên, class này phải triển khai một hoặc nhiều interface Listener. Ta cũng có thể định nghĩa listener trong web.xml
như sau:
<listener>
<listener-class>
com.journaldev.listener.AppContextListener
</listener-class>
</listener>
Ví dụ về Servlet Listener
Hãy cùng tạo một ứng dụng web đơn giản để xem cách Servlet Listener hoạt động trong thực tế. Ta sẽ tạo một dự án web động trong Eclipse tên là ServletListenerExample với cấu trúc thư mục như hình bên dưới.
web.xml: Trong file mô tả triển khai, ta sẽ định nghĩa một số tham số khởi tạo ngữ cảnh và cấu hình cho Listener.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="<https://www.w3.org/2001/XMLSchema-instance>" xmlns="<https://java.sun.com/xml/ns/javaee>" xsi:schemaLocation="<https://java.sun.com/xml/ns/javaee> <https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd>" id="WebApp_ID" version="3.0">
<display-name>ServletListenerExample</display-name>
<context-param>
<param-name>DBUSER</param-name>
<param-value>pankaj</param-value>
</context-param>
<context-param>
<param-name>DBPWD</param-name>
<param-value>password</param-value>
</context-param>
<context-param>
<param-name>DBURL</param-name>
<param-value>jdbc:mysql://localhost/mysql_db</param-value>
</context-param>
<listener>
<listener-class>com.journaldev.listener.AppContextListener</listener-class>
</listener>
<listener>
<listener-class>com.journaldev.listener.AppContextAttributeListener</listener-class>
</listener>
<listener>
<listener-class>com.journaldev.listener.MySessionListener</listener-class>
</listener>
<listener>
<listener-class>com.journaldev.listener.MyServletRequestListener</listener-class>
</listener>
</web-app>
DBConnectionManager: Đây là class dùng để kết nối cơ sở dữ liệu. Để cho đơn giản, ta sẽ không cung cấp code kết nối cơ sở dữ liệu thực tế. Ta sẽ đặt đối tượng này làm attribute cho ngữ cảnh servlet.
package com.journaldev.db;
import java.sql.Connection;
public class DBConnectionManager {
private String dbURL;
private String user;
private String password;
private Connection con;
public DBConnectionManager(String url, String u, String p){
this.dbURL=url;
this.user=u;
this.password=p;
//create db connection now
}
public Connection getConnection(){
return this.con;
}
public void closeConnection(){
//close DB connection here
}
}
MyServlet: Một class servlet đơn giản, nơi ta sẽ làm việc với session, attribute, v.v.
package com.journaldev.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/MyServlet")
public class MyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletContext ctx = request.getServletContext();
ctx.setAttribute("User", "Pankaj");
String user = (String) ctx.getAttribute("User");
ctx.removeAttribute("User");
HttpSession session = request.getSession();
session.invalidate();
PrintWriter out = response.getWriter();
out.write("Hi "+user);
}
}
Bây giờ, ta sẽ triển khai các class Listener. Dưới đây là các class Listener mẫu cho những listener thường dùng nhất: ServletContextListener, ServletContextAttributeListener, ServletRequestListener và HttpSessionListener.
ServletContextListener
Ta sẽ đọc các tham số khởi tạo của ngữ cảnh servlet để tạo đối tượng DBConnectionManager và gán nó làm thuộc tính cho đối tượng ServletContext.
package com.journaldev.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import com.journaldev.db.DBConnectionManager;
@WebListener
public class AppContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext ctx = servletContextEvent.getServletContext();
String url = ctx.getInitParameter("DBURL");
String u = ctx.getInitParameter("DBUSER");
String p = ctx.getInitParameter("DBPWD");
//create database connection from init parameters and set it to context
DBConnectionManager dbManager = new DBConnectionManager(url, u, p);
ctx.setAttribute("DBManager", dbManager);
System.out.println("Database connection initialized for Application.");
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext ctx = servletContextEvent.getServletContext();
DBConnectionManager dbManager = (DBConnectionManager) ctx.getAttribute("DBManager");
dbManager.closeConnection();
System.out.println("Database connection closed for Application.");
}
}
ServletContextAttributeListener
Đây là một triển khai đơn giản để log lại event khi một thuộc tính được thêm, xóa hoặc thay thế trong ngữ cảnh servlet.
package com.journaldev.listener;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppContextAttributeListener implements ServletContextAttributeListener {
public void attributeAdded(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext attribute added::{"+servletContextAttributeEvent.getName()+","+servletContextAttributeEvent.getValue()+"}");
}
public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext attribute replaced::{"+servletContextAttributeEvent.getName()+","+servletContextAttributeEvent.getValue()+"}");
}
public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
System.out.println("ServletContext attribute removed::{"+servletContextAttributeEvent.getName()+","+servletContextAttributeEvent.getValue()+"}");
}
}
HttpSessionListener
Một triển khai đơn giản để log lại event khi một session được tạo hoặc bị hủy.
package com.journaldev.listener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class MySessionListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent sessionEvent) {
System.out.println("Session Created:: ID="+sessionEvent.getSession().getId());
}
public void sessionDestroyed(HttpSessionEvent sessionEvent) {
System.out.println("Session Destroyed:: ID="+sessionEvent.getSession().getId());
}
}
ServletRequestListener
Một triển khai đơn giản của interface ServletRequestListener để log địa chỉ IP của ServletRequest khi request được khởi tạo và bị hủy.
package com.journaldev.listener;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class MyServletRequestListener implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
ServletRequest servletRequest = servletRequestEvent.getServletRequest();
System.out.println("ServletRequest destroyed. Remote IP="+servletRequest.getRemoteAddr());
}
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
ServletRequest servletRequest = servletRequestEvent.getServletRequest();
System.out.println("ServletRequest initialized. Remote IP="+servletRequest.getRemoteAddr());
}
}
Bây giờ, khi ta deploy ứng dụng và truy cập MyServlet trên trình duyệt bằng URL http://localhost:8080/ServletListenerExample/MyServlet
, ta sẽ thấy các dòng log sau trong file log của server.
ServletContext attribute added::{DBManager,com.journaldev.db.DBConnectionManager@4def3d1b}
Database connection initialized for Application.
ServletContext attribute added::{org.apache.jasper.compiler.TldLocationsCache,org.apache.jasper.compiler.TldLocationsCache@1594df96}
ServletRequest initialized. Remote IP=0:0:0:0:0:0:0:1%0
ServletContext attribute added::{User,Pankaj}
ServletContext attribute removed::{User,Pankaj}
Session Created:: ID=8805E7AE4CCCF98AFD60142A6B300CD6
Session Destroyed:: ID=8805E7AE4CCCF98AFD60142A6B300CD6
ServletRequest destroyed. Remote IP=0:0:0:0:0:0:0:1%0
ServletRequest initialized. Remote IP=0:0:0:0:0:0:0:1%0
ServletContext attribute added::{User,Pankaj}
ServletContext attribute removed::{User,Pankaj}
Session Created:: ID=88A7A1388AB96F611840886012A4475F
Session Destroyed:: ID=88A7A1388AB96F611840886012A4475F
ServletRequest destroyed. Remote IP=0:0:0:0:0:0:0:1%0
Database connection closed for Application.
Hãy để ý thứ tự của các dòng log. Chúng được ghi theo đúng trình tự thực thi và dòng log cuối cùng sẽ xuất hiện khi bạn tắt ứng dụng hoặc tắt container.
Tổng kết
Trên đây là toàn bộ nội dung về ServletContextListener trong các ứng dụng web Java. Bạn có thể tải code project ví dụ ở trên từ liên kết này và tự mình vọc vạch để hiểu sâu hơn. Trong bài tiếp theo, chúng ta sẽ khám phá cách sử dụng cookie và áp dụng trong các ví dụ servlet đơn giản khác.