【愚公系列】2023年04月 Java教学课程 098-Servlet服务器的Listener
一、Servlet服务器的Listener
1.涉及的设计模式
1、观察者模式
观察者设计模式是一种行为型设计模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它会通知所有的观察者对象,使它们能够自动更新自己。
该模式的核心是抽象主题类和抽象观察者类。主题类包含一个观察者列表,并提供注册、删除和通知观察者的方法;而观察者类则包含一个更新方法,在接收到主题对象的通知时调用。
使用观察者模式可以实现松耦合,让对象之间的依赖关系更加灵活,同时也可以减少重复代码,提高代码的可维护性和可扩展性。例如,在GUI程序中,当用户输入数据时,可以使用观察者模式来实现数据的实时更新和显示。
观察者模式由以下组成部分:
• Subject(主题):它是被观察的对象,它可以有多个观察者。主题提供一个接口,可以用来注册和删除观察者对象。
• Observer(观察者):观察者将自己注册到主题中,以便在主题状态改变时接收通知。观察者一般提供一个更新方法,用来接收主题发来的通知。
• ConcreteSubject(具体主题):它是实现了主题接口的对象,它包含了一个或多个观察者对象,当状态发生改变时,向所有注册过的观察者发出通知。
• ConcreteObserver(具体观察者):实现了观察者接口的对象,它存储着与主题相关的状态,当状态发生改变时,通常会调用主题的更新方法来更新自己的状态。
下图描述了观察者设计模式的组成:
2、发布订阅模式
发布订阅模式(Publish/Subscribe Pattern)是一种消息模型,也被称为消息队列模式(Message Queue Pattern)。在该模式中,消息发布者(Publisher)不会直接将消息发送给特定的接收者(Subscriber),而是将消息发布到一个中心主题(Topic)或消息代理(Message Broker),订阅者(Subscriber)则通过订阅该主题来接收消息。
发布订阅模式的好处是解耦合,消息发布者和订阅者不需要知道彼此的存在,它们只需要知道消息代理即可。这种模式使得系统更加灵活,可以支持多个订阅者同时订阅同一个主题,也支持动态增加或删除订阅者。
在实际应用中,发布订阅模式被广泛应用于消息队列、事件驱动架构、分布式系统等领域。常见的消息代理有 RabbitMQ、Apache Kafka、ActiveMQ 等。
发布订阅模式的组成部分包括:
• 发布者(Publisher):负责发布消息或事件的对象。
• 订阅者(Subscriber):接收并处理发布者发出的消息或事件的对象。
• 消息或事件(Message/Event):发布者发布给订阅者的信息或事件。
• 订阅(Subscription):订阅者向发布者注册,以便可以接收发布者发布的消息或事件。
• 中介者(Mediator):负责协调发布者和订阅者之间的通信,以确保发布者的消息能够正确地传递给订阅者。
• 通道(Channel):发布者和订阅者之间传递消息的管道,可以是硬件通道,也可以是软件通道。
• 事件总线(Event Bus):用于管理和协调发布者和订阅者之间的通信的软件组件。
下图描述了发布订阅模式的组成:
1.1 Servlet规范中的8个监听器简介
1.1.1 监听对象创建的
1)ServletContextListener
/**
* 用于监听ServletContext对象创建和销毁的监听器
* @since v 2.3
*/
public interface ServletContextListener extends EventListener {
/**
* 对象创建时执行此方法。该方法的参数是ServletContextEvent事件对象,事件是【创建对象】这个动作
* 事件对象中封装着触发事件的来源,即事件源,就是ServletContext
*/
public default void contextInitialized(ServletContextEvent sce) {
}
/**
* 对象销毁执行此方法
*/
public default void contextDestroyed(ServletContextEvent sce) {
}
}
2)HttpSessionListener
/**
* 用于监听HttpSession对象创建和销毁的监听器
* @since v 2.3
*/
public interface HttpSessionListener extends EventListener {
/**
* 对象创建时执行此方法。
*/
public default void sessionCreated(HttpSessionEvent se) {
}
/**
* 对象销毁执行此方法
*/
public default void sessionDestroyed(HttpSessionEvent se) {
}
}
3)ServletRequestListener
/**
* 用于监听ServletRequest对象创建和销毁的监听器
* @since Servlet 2.4
*/
public interface ServletRequestListener extends EventListener {
/**
* 对象创建时执行此方法。
*/
public default void requestInitialized (ServletRequestEvent sre) {
}
/**
* 对象销毁执行此方法
*/
public default void requestDestroyed (ServletRequestEvent sre) {
}
}
1.1.2 监听域中属性发生变化的
1)ServletContextAttributeListener
/**
* 用于监听ServletContext域(应用域)中属性发生变化的监听器
* @since v 2.3
*/
public interface ServletContextAttributeListener extends EventListener {
/**
* 域中添加了属性触发此方法。参数是ServletContextAttributeEvent事件对象,事件是【添加属性】。
* 事件对象中封装着事件源,即ServletContext。
* 当ServletContext执行setAttribute方法时,此方法可以知道,并执行。
*/
public default void attributeAdded(ServletContextAttributeEvent scae) {
}
/**
* 域中删除了属性触发此方法
*/
public default void attributeRemoved(ServletContextAttributeEvent scae) {
}
/**
* 域中属性发生改变触发此方法
*/
public default void attributeReplaced(ServletContextAttributeEvent scae) {
}
}
2)HttpSessionAttributeListener
/**
* 用于监听HttpSession域(会话域)中属性发生变化的监听器
* @since v 2.3
*/
public interface HttpSessionAttributeListener extends EventListener {
/**
* 域中添加了属性触发此方法。
*/
public default void attributeAdded(HttpSessionBindingEvent se) {
}
/**
* 域中删除了属性触发此方法
*/
public default void attributeRemoved(HttpSessionBindingEvent se) {
}
/**
* 域中属性发生改变触发此方法
*/
public default void attributeReplaced(HttpSessionBindingEvent se) {
}
}
3)ServletRequestAttributeListener
/**
* 用于监听ServletRequest域(请求域)中属性发生变化的监听器
* @since Servlet 2.4
*/
public interface ServletRequestAttributeListener extends EventListener {
/**
* 域中添加了属性触发此方法。
*/
public default void attributeAdded(ServletRequestAttributeEvent srae) {
}
/**
* 域中删除了属性触发此方法
*/
public default void attributeRemoved(ServletRequestAttributeEvent srae) {
}
/**
* 域中属性发生改变触发此方法
*/
public default void attributeReplaced(ServletRequestAttributeEvent srae) {
}
}
1.1.3 和会话相关的两个感知型监听器
和会话域相关的两个感知型监听器是无需配置的,直接编写代码即可。
1)HttpSessionBinderListener
/**
* 用于感知对象和和会话域绑定的监听器
* 当有数据加入会话域或从会话域中移除,此监听器的两个方法会执行。
* 加入会话域即和会话域绑定
* 从会话域移除即从会话域解绑
*/
public interface HttpSessionBindingListener extends EventListener {
/**
* 当数据加入会话域时,也就是绑定,此方法执行
*/
public default void valueBound(HttpSessionBindingEvent event) {
}
/**
* 当从会话域移除时,也就是解绑,此方法执行
*/
public default void valueUnbound(HttpSessionBindingEvent event) {
}
}
2)HttpSessionActivationListener
/**
* 用于感知会话域中对象钝化和活化的监听器
*/
public interface HttpSessionActivationListener extends EventListener {
/**
* 当会话域中的数据钝化时,此方法执行
*/
public default void sessionWillPassivate(HttpSessionEvent se) {
}
/**
* 当会话域中的数据活化时(激活),此方法执行
*/
public default void sessionDidActivate(HttpSessionEvent se) {
}
}
1.2 监听器的使用
在实际开发中,我们可以根据具体情况来从这8个监听器中选择使用。感知型监听器由于无需配置,只需要根据实际需求编写代码,所以此处我们就不再演示了。我们在剩余6个中分别选择一个监听对象创建销毁和对象域中属性发生变化的监听器演示一下。
1.2.1 ServletContextListener的使用
第一步:创建工程
第二步:编写监听器
package com.itheima.domain;
import jakarta.servlet.*;
/**
* 用于监听ServletContext对象创建和销毁的监听器
*/
public class ServletContextListenerDemo implements ServletContextListener {
/**
* 对象创建时,执行此方法
* @param sce
*/
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ok-------------------------------------");
//1.获取事件源对象
ServletContext servletContext = sce.getServletContext();
System.out.println(servletContext);
}
/**
* 对象销毁时,执行此方法
* @param sce
*/
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("no--------------------------------------------");
}
}
第三步:在web.xml中配置监听器
<!--配置监听器-->
<listener>
<listener-class>com.itheima.web.listener.ServletContextListenerDemo</listener-class>
</listener>
第四步:测试结果
1.2.2 ServletContextAttributeListener的使用
第一步:创建工程
沿用上一个案例的工程
第二步:编写监听器
package com.itheima.domain;
import jakarta.servlet.*;
/**
* 监听域中属性发生变化的监听器
*/
public class ServletContextAttributeListenerDemo implements ServletContextAttributeListener {
/**
* 域中添加了数据
* @param scae
*/
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("add----------------------------------------");
/**
* 由于除了我们往域中添加了数据外,应用在加载时还会自动往域中添加一些属性。
* 我们可以获取域中所有名称的枚举,从而看到域中都有哪些属性
*/
//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("rm-----------------------------------");
}
/**
* 域中属性发生了替换
* @param scae
*/
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("change------------------------------------");
}
}
同时,我们还需要借助第一个ServletContextListenerDemo监听器,往域中存入数据,替换域中的数据以及从域中移除数据,代码如下:
package com.itheima.domain;
import jakarta.servlet.*;
import java.util.Enumeration;
/**
* 监听域中属性发生变化的监听器
*/
public class ServletContextAttributeListenerDemo implements ServletContextAttributeListener {
/**
* 域中添加了数据
* @param scae
*/
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
System.out.println("add----------------------------------------");
/**
* 由于除了我们往域中添加了数据外,应用在加载时还会自动往域中添加一些属性。
* 我们可以获取域中所有名称的枚举,从而看到域中都有哪些属性
*/
//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("rm-----------------------------------");
}
/**
* 域中属性发生了替换
* @param scae
*/
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
System.out.println("change------------------------------------");
}
}
第三步:在web.xml中配置监听器
<?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.itheima.domain.ServletContextListenerDemo</listener-class>
</listener>
<!--配置监听器-->
<listener>
<listener-class>com.itheima.domain.ServletContextAttributeListenerDemo</listener-class>
</listener>
</web-app>
第四步:测试结果
二、综合案例-学生管理系统改造
1.乱码问题过滤器
创建EncodingFilter类,解决乱码
package com.itheima.filter;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/*
解决全局乱码问题
*/
@WebFilter("/*")
public class EncodingFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
try{
//1.将请求和响应对象转换为和HTTP协议相关
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//2.设置编码格式
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
//3.放行
filterChain.doFilter(request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.检查登录过滤器
检查登录,创建LoginFilter 类
package com.itheima.filter;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/*
检查登录
*/
@WebFilter(value = {"/addStudent.jsp","/listStudentServlet"})
public class LoginFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
try{
//1.将请求和响应对象转换为和HTTP协议相关
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//2.获取会话域对象中数据
Object username = request.getSession().getAttribute("username");
//3.判断用户名
if(username == null || "".equals(username)) {
//重定向到登录页面
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
}
//4.放行
filterChain.doFilter(request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.jsp页面的改造
1,修改addStudent.jsp的虚拟访问路径
<form action="${pageContext.request.contextPath}/addStudentServlet" method="get" 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>
2,修改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}/addStudent.jsp">添加学生</a>
<a href="${pageContext.request.contextPath}/listStudentServlet">查看学生</a>
</c:if>
</body>
</html>
3,修改listStudent.jsp
<%@ page import="com.itheima.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="s">
<tr align="center">
<td>${s.username}</td>
<td>${s.age}</td>
<td>${s.score}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
4,修改login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>学生登录</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/loginStudentServlet" method="get" autocomplete="off">
姓名:<input type="text" name="username"> <br>
密码:<input type="password" name="password"> <br>
<button type="submit">登录</button>
</form>
</body>
</html>
- 点赞
- 收藏
- 关注作者
评论(0)