【Java Web编程 十一】深入理解Servlet监听器

简介: 【Java Web编程 十一】深入理解Servlet监听器

上篇Blog详细介绍了Servlet的过滤器,了解到过滤器类似一个AOP的概念,这篇Blog就来学习下Servlet的监听器,了解下Java Web的第三大组件的用处是什么,又是怎么工作的。

监听器基本概念

在JSP技术中我们学习了4大作用域:pageContext、request、session、application,用于实现数据共享,其中request、session、application三个作用域分别对应于我们

  • HttpServletRequest接口:请求作用域对象
  • HttpSession接口:会话作用域对象
  • ServletContext接口:全局作用域对象

虽然有了数据共享的作用域,但是数据的具体流转过程我们看不到,比如作用域对象什么时候创建和销毁的,什么时候存取、改变和删除的,所以无法在指定的时机对数据和对象进行操作,这个时候就需要Servlet监听器来发挥作用了:

  • 事件:方法调用、属性改变、状态改变等,这里对应对象的创建与销毁事件,属性改变事件,以及额外对HttpSession附加的监听 HttpSession中的对象状态改变事件。
  • 事件源:被监听的对象,这里是HttpServletRequest、HttpSession、ServletContext。
  • 监听器:用于监听事件源对象 ,事件源对象状态的变化都会触发监听器,这里是我们创建的一个Servlet监听器
  • 注册监听器:将监听器与事件源进行绑定

所以一句话描述就是:给事件源注册好监听器后,当某个事件发生引起事件源变化时,监听器监听到这一变化进行逻辑处理

监听器作用

Servlet监听器是Servlet规范中定义的一种特殊类,用于监听servletContext(application)、httpsession(session)、servletRequest(request)三个作用域对象的创建与销毁事件,以及监听这些作用域对象中属性发生修改的事件,说白了就是监听三个作用域对象的生命周期。

创建监听器

用IDEA可以很轻松的创建一个监听器出来,按照如下步骤:

可以看到IDEA自动帮我们生成了监听器代码模板:

package com.example.myfirstweb.controller; /**
 * * @Name ${NAME}
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/7/27
 */
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebListener
public class ServletListener implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
    public ServletListener() {
    }
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        /* This method is called when the servlet context is initialized(when the Web application is deployed). */
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        /* This method is called when the servlet Context is undeployed or Application Server shuts down. */
    }
    @Override
    public void sessionCreated(HttpSessionEvent se) {
        /* Session is created. */
    }
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        /* Session is destroyed. */
    }
    @Override
    public void attributeAdded(HttpSessionBindingEvent sbe) {
        /* This method is called when an attribute is added to a session. */
    }
    @Override
    public void attributeRemoved(HttpSessionBindingEvent sbe) {
        /* This method is called when an attribute is removed from a session. */
    }
    @Override
    public void attributeReplaced(HttpSessionBindingEvent sbe) {
        /* This method is called when an attribute is replaced in a session. */
    }
}

可以看到监听器是通过注解定义的,我们来看下注解的源代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.servlet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebListener {
    String value() default "";
}

监听器常用方法

Servlet 规范中定义了 8 个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期和属性变化事件。开发 Servlet 监听器需要实现相应的监听器接口并重写接口中的方法:

监听对象创建和销毁的监听器

Servlet 规范定义了监听 ServletContext、HttpSession、HttpServletRequest 这三个对象创建和销毁事件的监听器

监听属性变更的监听器

Servlet 规范定义了监听 ServletContext、HttpSession、HttpServletRequest 这三个对象中的属性变更事件的监听器,这三个监听器接口分别是 ServletContextAttributeListener、HttpSessionAttributeListener 和 ServletRequestAttributeListener。这三个接口中都定义了三个方法,用来处理被监听对象中属性的增加,删除和替换事件。同一种事件在这三个接口中对应的方法名称完全相同,只是参数类型不同

监听 Session中对象状态改变的监听器

Session 中的对象可以有多种状态:绑定到 Session 中、从 Session 中解除绑定、随 Session 对象持久化到存储设备中(钝化)、随 Session 对象从存储设备中恢复(活化)。Servlet 规范中定义了两个特殊的监听器接口,用来帮助对象了解自己在 Session 中的状态:HttpSessionBindingListener 接口和 HttpSessionActivationListener 接口 ,实现这两个接口的类不需要进行注册

监听器实现网站在线人数监听

监听网站的在线人数,也就是统计服务器中的session被创建多少次,创建一次在线人数+1;session销毁一次在线人数-1。

JSP页面

假设登录成功,则跳转到index.jsp页面,和上一篇的过滤器梦幻联动,所有页面请求先到login.jsp,登录后才会跳转,这里我们模拟的是已经登录成功后的跳转:

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" import="java.util.*" errorPage="jsp/error.jsp" %>
<%@ include file="jsp/top.jsp" %>
<!DOCTYPE html>
<html>
<head>
  <title>JSP - Hello World</title>
  <%!
    int number = 0;// 声明全局变量
    int count() {
      number++;//number 自增
      return number;
    }%>
</head>
<h1><%= "Hello World!" %>
</h1>
<br/>
<body>
<%
  String print = "我是脚本语句内容";
  List<String> strList = new ArrayList<>();
  strList.add("脚本语句1");
  strList.add("脚本语句2");
  strList.add("脚本语句3");
  //  strList.add("脚本语句4");  //这是一个Java注释,用来注释Java代码,查看源码时不可见
%>
<a href="hello-servlet">我的第一个JavaWeb项目</a> <!-- 这是一个html注释,可见<%=new Date().getTime() %> -->
<br/>
<%--这是一个JSP注释,查看源码时不可见--%>
声明语句示例:这是第 <%=count()%> 次访问该页面
<br/>
表达式语句示例: <%="我是一个表达式语句"%>
<br/>
脚本语句示例: <%=print%>, 打印列表 <%=strList%>
</body>
</html>

Servlet页面

我们准备两个页面,一个页面用于跳转Servlet到index.jsp,60秒后自动退出:

package com.example.MyFirstJavaWeb;
import java.io.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //假设登录成功
        HttpSession session = request.getSession();
        session.setMaxInactiveInterval(60); //60秒后自动推出
        response.sendRedirect("index.jsp");
    }
    public void destroy() {
    }
}

还有一个用于手动退出session的页面:

package com.example.MyFirstJavaWeb;
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;
import java.io.IOException;
@WebServlet(name = "StopSession", value = "/stop-servlet")
public class StopSession extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //假设退出成功
        HttpSession session = request.getSession();
        session.invalidate();
    }
    public void destroy() {
    }
}

Servlet监听器

接下来就是我们监听器的代码了,在application初始化时我们增加一个数量统计,然后在session创建时进行累加,销毁时累减:

package com.example.MyFirstJavaWeb.listener;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.util.Date;
@WebListener
public class CountListener implements HttpSessionListener,ServletContextListener {
    public CountListener() {
    }
    //监听application销毁事件--即服务器关闭
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("application销毁~~~~~~~~");
    }
    //监听application创建事件--即服务器开启
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("application创建~~~~~~~~");
        int sessionCount = 0;//服务器开启的时候,定义变量用于统计在线人数,保存起来
        ServletContext application = sce.getServletContext();
        application.setAttribute("sessionCount", sessionCount);
    }
    //监听session创建事件--即有人登陆成功,进入项目主页面,则在线人数+1
    @Override
    public void sessionCreated(HttpSessionEvent hse) {
        System.out.println("登陆成功"+hse.getSession().getId()+"创建时间:"+new Date());
        ServletContext application = hse.getSession().getServletContext();
        int sessionCount = (int)application.getAttribute("sessionCount");
        application.setAttribute("sessionCount", ++sessionCount);
        System.out.println("当前在线用户数为:"+application.getAttribute("sessionCount"));
    }
    //监听session销毁事件--销毁有很多种方式:强制销毁、自动过期等,无所谓什么方式,只要session销毁就可以监听到
    @Override
    public void sessionDestroyed(HttpSessionEvent hse) {
        System.out.println("退出成功"+hse.getSession().getId()+"退出时间:"+new Date());
        ServletContext application = hse.getSession().getServletContext();
        int sessionCount = (int)application.getAttribute("sessionCount");
        application.setAttribute("sessionCount", --sessionCount);
        System.out.println("当前在线用户数为:"+application.getAttribute("sessionCount"));
    }
}

实现效果

这样当我们每次通过不同浏览器访问页面都会增加一个新的用户,整个变化过程如下:

注意要把这个勾掉,否则idea在启动时会额外给你生成一个session,那么启动时就会有两个session了。

总结一下

其实Servlet监听器最大的作用就是被当作状态机,其实我们任何流程中,抽象出来都是在通过事件对事件源的生命周期管理来实现的,对象在事件驱动下在流程中流转。系统设计时也一样,所以其实监听器能让我们感悟更多的其实是事件驱动生命周期这种设计思想,正如过滤器给我们更多的感悟是切面AOP对所有服务的控制一样,更重要的是思想,而组件只是思想的落地。有了思想我们可以应用再更多其它地方。

相关文章
|
5月前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
537 1
|
5月前
|
存储 前端开发 Java
【JAVA】Java 项目实战之 Java Web 在线商城项目开发实战指南
本文介绍基于Java Web的在线商城技术方案与实现,涵盖三层架构设计、MySQL数据库建模及核心功能开发。通过Spring MVC + MyBatis + Thymeleaf实现商品展示、购物车等模块,提供完整代码示例,助力掌握Java Web项目实战技能。(238字)
554 0
|
6月前
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
719 64
|
6月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
463 1
|
8月前
|
Java 数据库连接 API
2025 更新必看:Java 编程基础入门级超级完整版指南
本教程为2025更新版Java编程基础入门指南,涵盖开发环境搭建(SDKMAN!管理JDK、VS Code配置)、Java 17+新特性(文本块、Switch表达式增强、Record类)、面向对象编程(接口默认方法、抽象类与模板方法)、集合框架深度应用(Stream API高级操作、并发集合)、模式匹配与密封类等。还包括学生成绩管理系统实战项目,涉及Maven构建、Lombok简化代码、JDBC数据库操作及JavaFX界面开发。同时提供JUnit测试、日志框架使用技巧及进阶学习资源推荐,助你掌握Java核心技术并迈向高级开发。
828 5
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
157 1
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
161 3
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。