Listener 介紹
觀察者設計模式
在介紹 Listener(監聽器)之前,需要先了解觀察者設計模式,因為所有的監聽器都是觀察者設計模式的體現。
那么什么是觀察者設計模式呢?
它是事件驅動
的一種體現形式。就好比在做什么事情的時候被人盯着,當做了某件事時,就會觸發事件。
觀察者模式通常由以下三部分組成:
-
事件源
:觸發事件的對象。 -
事件
:觸發的動作,里面封裝了事件源。 -
監聽器
:當事件源觸發事件時,要做的事情。一般是一個接口,由使用者來實現。(此處還涉及一種設計模式的思想:策略模式)
下圖描述了觀察者設計模式組成:
Listener 介紹
在程序當中我們可以對以下情況進行監聽:對象的創建銷毀、域對象中屬性的變化、會話相關內容。
Servlet 規范中共計 8 個監聽器,監聽器都是以接口形式提供的,具體功能需要我們自己來完成。
Listener 配置方式
Listender 有兩種配置方法:
-
注解方式
@WebListener
-
web.xml 配置方式
<!-- 配置監聽器 -->
<listener>
<listener-class>com.listener.ServletContextListenerDemo</listener-class>
</listener>
<listener>
<listener-class>com.listener.ServletContextAttributeListenerDemo</listener-class>
</listener>
Servlet 規范中的 8 個監聽器
-
監聽對象的
- ServletContextListener
- HttpSessionListener
- ServletRequestListener
-
監聽域中屬性變化的
- ServletContextAttributeListener
- HttpSessionAttributeListener
- ServletRequestAttributeListener
-
會話相關的感知型
- HttpSessionBindingListener
- HttpSessionActivationListener
監聽對象的監聽器
1)ServletContextListener
用於監聽 ServletContext 對象的創建和銷毀。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | contextlnitialized(ServletContextEvent sce) | 對象創建時執行該方法 |
void | contextDestroyed(ServletContextEvent sce) | 對象銷毀時執行該方法 |
ServletContextEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 ServletContext
- 直正的事件指的是創建或銷毀 ServletContext 對象的操作
2)HttpSessionListener
用於監聽 HttpSession 對象的創建和銷毀核心方法。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | sessionCreated(HttpSessionEventse) | 對象創建時執行該方法 |
void | sessionDestroyed(HttpSessionEvent se | 對象銷毀時執行該方法 |
HttpSessionEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 HttpSession
- 真正的事件指的是創建或銷毀 HttpSession 對象的操作
3)ServletRequestListener
用於監聽 ServletRequest 對象的創建和銷毀核心方法。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | requestinitialized(ServletRequestEvent sre) | 對象創建時執行該方法 |
void | requestDestroyed(ServletRequestEvent sre) | 對象銷毀時執行該方法 |
ServletRequest5vent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 ServletRequest
- 真正的事件指的是創建或銷毀 ServletRequest 對象的操作
監聽域中屬性變化的監聽器
4)ServletContextAttributeListener
用於監聽 ServletContext 應用域中屬性的變化核心方法。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | attributeAdded(ServletContextAttributeEvent scae) | 域中添加屬性時執行該方法 |
void | attributeRemoved(ServletContextAttributeEvent scae) | 域中移除屬性時執行該方法 |
void | attributeReplaced(ServletContextAttributeEvent scae) | 域中替換屬性時執行該方法 |
ServletContextAttributeEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 ServletContext
- 直正的事件指的是添加、移除、替換應用域中屬性的操作
5)HttpSessionAttributeListener
用於監聽 HttpSession 會話域中屬性的變化。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | attributeAdded(HttpSessionBindingEvent se) | 域中添加屬性時執行該方法 |
void | attributeRemoved(HttpSessionBindingEvent se) | 域中移除屬性時執行該方法 |
void | attributeReplaced(HttpSessionBindingEvent se) | 域中替換屬性時執行該方法 |
HttpSessionBindingEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 HttpSession
- 真正的事件指的是添加、移除、替換會話域中屬性的操作
6)ServletRequestAttributeListener
用於監聽 ServletRequest 請求域中屬性的變化。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | attributeAdded(ServletRequestAttributeEvent srae) | 域中添加屬性時執行該方法 |
void | attributeRemoved(ServletRequestAttributeEvent srae) | 域中移除屬性時執行該方法 |
void | attributeReplaced(ServletRequestAttributeEvent srae) | 域中替換屬性時執行該方法 |
ServletRequestAttributeEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 ServletRequest
- 真正的事件指的是添加、移除、替換請求域中屬性的操作
監聽會話相關的感知型監聽器
注意:監聽會話相關的感知型監聽器,只要定義了即可使用,無需進行配置。
7)HttpSessionBindingListener
用於感知對象和會話域綁定的監聽器。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | valueBound(HttpSessionBindingEvent event) | 數據添加到會話域中(綁定)時執行該方法 |
void | valueUnbound(HttpSessionBindingEvent event) | 數據從會話域中移除(解綁)時執行該方法 |
HttpSessionBindingEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 HttpSession
- 直正的事件指的是添加、移除會話域中數據的操作
8)HttpSessionActivationListener
用於感知會話域中對象鈍化(序列化)和活化(反序列化)的監聽器。
核心方法:
返回值 | 方法名 | 作用 |
---|---|---|
void | sessionWillPassivate(HttpSessionEvent se) | 會話域中數據鈍化時執行該方法 |
void | sessionDidActivate(HttpSessionEvent se) | 會話域中數據活化時執行該方法 |
HttpSessionEvent 參數:代表事件對象
- 事件對象中封裝了事件源,也就是 HttpSession
- 直正的事件指的是會話域中數據鈍化、活化的操作
Listener 使用示例
ServletContextListener 使用示例
1)編寫監聽器:
/**
* 用於監聽ServletContext對象創建和銷毀的監聽器
*/
@WebListener
public class ServletContextListenerDemo implements ServletContextListener {
/**
* 對象創建時,執行此方法
* @param sce
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("監聽到了對象的創建");
// 獲取事件源對象
ServletContext servletContext = sce.getServletContext();
System.out.println(servletContext);
}
/**
* 對象銷毀時,執行此方法
* @param sce
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("監聽到了對象的銷毀");
}
}
2)啟動並停止 web 服務:
ServletContextAttributeListener 使用示例
1)編寫監聽器:
/**
* 監聽域中屬性發生變化的監聽器
*/
public class ServletContextAttributeListenerDemo implements ServletContextAttributeListener {
/**
* 域中添加了數據
* @param scae
*/
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("監聽到域中加入了屬性");
/**
* 由於除了我們往域中添加了數據外,應用在加載時還會自動往域中添加一些屬性。
* 我們可以獲取域中所有名稱的枚舉,從而看到域中都有哪些屬性
*/
//1.獲取事件源對象ServletContext
ServletContext servletContext = scae.getServletContext();
//2.獲取域中所有名稱的枚舉
Enumeration<String> names = servletContext.getAttributeNames();
//3.遍歷名稱的枚舉
while(names.hasMoreElements()){
//4.獲取每個名稱
String name = names.nextElement();
//5.獲取值
Object value = servletContext.getAttribute(name);
//6.輸出名稱和值
System.out.println("name is "+name+" and value is "+value);
}
}
/**
* 域中移除了數據
* @param scae
*/
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
System.out.println("監聽到域中移除了屬性");
}
/**
* 域中屬性發生了替換
* @param scae
*/
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("監聽到域中屬性發生了替換");
}
}
同時,我們還需要借助上個示例的 ServletContextListenerDemo 監聽器,往域中存入數據、替換域中的數據以及從域中移除數據,代碼如下:
/**
* 用於監聽ServletContext對象創建和銷毀的監聽器
*/
public class ServletContextListenerDemo implements ServletContextListener {
/**
* 對象創建時,執行此方法
* @param sce
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("監聽到了對象的創建");
//1.獲取事件源對象
ServletContext servletContext = sce.getServletContext();
//2.往域中加入屬性
servletContext.setAttribute("servletContext","test");
}
/**
* 對象銷毀時,執行此方法
* @param sce
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
//1.取出事件源對象
ServletContext servletContext = sce.getServletContext();
//2.往域中加入屬性,但是名稱仍采用servletContext,此時就是替換
servletContext.setAttribute("servletContext","demo");
System.out.println("監聽到了對象的銷毀");
//3.移除屬性
servletContext.removeAttribute("servletContext");
}
}
2)在 web.xml 中配置監聽器:
<!--配置監聽器-->
<listener>
<listener-class>com.listener.ServletContextListenerDemo</listener-class>
</listener>
<!--配置監聽器-->
<listener>
<listener-class>com.listener.ServletContextAttributeListenerDemo</listener-class>
</listener>
3)啟動 web 服務:
綜合案例
優化需求:
- 解決亂碼:使用過濾器統一實現請求和響應亂碼問題的解決。
- 檢查登錄:使用過濾器統一實現身份認證。
- 優化 JSP 頁面:使用 EL 表達式和 JSTL 。
1)亂碼問題過濾器
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 解決全局亂碼問題
@WebFilter("/*")
public class EncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 將請求和響應對象轉換為和HTTP協議相關
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 設置編碼格式
httpServletRequest.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("text/html;charset=UTF-8");
// 放行
chain.doFilter(httpServletRequest, httpServletResponse);
}
}
2)檢查登錄過濾器
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 檢查登錄態
@WebFilter(value={"/add.jsp", "/queryServlet"})
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 將請求和響應對象轉換為和HTTP協議相關
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// 判斷會話域對象中的身份數據
Object username = httpServletRequest.getSession().getAttribute("username");
if ("".equals(username) || username == null) {
// 重定向到登錄頁
httpServletResponse.sendRedirect(httpServletRequest.getContextPath()+"/login.jsp");
return;
}
// 放行
chain.doFilter(httpServletRequest, httpServletResponse);
}
}
3)優化 JSP:使用 EL 表達式和 JSTL
- 修改 add.jsp 的虛擬訪問路徑:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/addServlet" method="post" autocomplete="off">
學生姓名:<input type="text" name="username"><br/>
學生年齡:<input type="number" name="age"><br/>
學生成績:<input type="number" name="score"><br/>
<button type="submit">保存</button>
</form>
</body>
</html>
- 修改 index.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>學生管理系統首頁</title>
</head>
<body>
<%--
獲取會話域的數據
如果獲取到了,則顯示添加和查詢功能
如果獲取不到,則顯示登錄功能
--%>
<c:if test="${sessionScope.username eq null}">
<a href="${pageContext.request.contextPath}/login.jsp">登錄<a/>
</c:if>
<c:if test="${sessionScope.username ne null}">
<a href="${pageContext.request.contextPath}/add.jsp">添加<a/>
<a href="${pageContext.request.contextPath}/queryServlet">查詢<a/>
</c:if>
</body>
</html>
- 修改 query.jsp :
<%@ page import="com.demo.bean.Student" %>
<%@ page import="java.util.ArrayList" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
<head>
<title>學生列表頁面</title>
</head>
<body>
<table width="600px" border="1px">
<tr>
<th>學生姓名</th>
<th>學生年齡</th>
<th>學生成績</th>
</tr>
<c:forEach items="${students}" var="student">
<tr align="center">
<td>${student.username}</td>
<td>${student.age}</td>
<td>${student.score}</td>
<tr/>
</c:forEach>
</table>
</body>
</html>
- 修改 login.jsp :
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登錄頁面</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/loginServlet" method="get" autocomplete="off">
姓名:<input type="text" name="username"><br/>
密碼:<input type="password" name="password"><br/>
<button type="submit">登錄</button>
</form>
</body>
</html>