【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对所有服务的控制一样,更重要的是思想,而组件只是思想的落地。有了思想我们可以应用再更多其它地方。

相关文章
|
10天前
|
Java Maven Spring
Java Web 应用中,资源文件的位置和加载方式
在Java Web应用中,资源文件如配置文件、静态文件等通常放置在特定目录下,如WEB-INF或classes。通过类加载器或Servlet上下文路径可实现资源的加载与访问。正确管理资源位置与加载方式对应用的稳定性和可维护性至关重要。
|
12天前
|
存储 安全 搜索推荐
理解Session和Cookie:Java Web开发中的用户状态管理
理解Session和Cookie:Java Web开发中的用户状态管理
37 4
|
12天前
|
Java 持续交付 项目管理
使用Maven进行项目管理:提高Java Web开发的效率
Maven 是一款强大的项目管理和构建自动化工具,广泛应用于Java社区。它通过依赖管理、构建生命周期管理、插件机制和多模块项目支持等功能,简化了项目的构建过程,提高了开发效率。本文将介绍Maven的核心功能及其在Java Web开发中的应用。
30 0
WK
|
18天前
|
安全 Java 编译器
C++和Java哪个更适合开发web网站
在Web开发领域,C++和Java各具优势。C++以其高性能、低级控制和跨平台性著称,适用于需要高吞吐量和低延迟的场景,如实时交易系统和在线游戏服务器。Java则凭借其跨平台性、丰富的生态系统和强大的安全性,广泛应用于企业级Web开发,如企业管理系统和电子商务平台。选择时需根据项目需求和技术储备综合考虑。
WK
26 0
|
1月前
|
前端开发 Java API
JAVA Web 服务及底层框架原理
【10月更文挑战第1天】Java Web 服务是基于 Java 编程语言用于开发分布式网络应用程序的一种技术。它通常运行在 Web 服务器上,并通过 HTTP 协议与客户端进行通信。
23 1
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
101 3
|
17天前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
103 45
|
12天前
|
前端开发 API 开发者
Python Web开发者必看!AJAX、Fetch API实战技巧,让前后端交互如丝般顺滑!
在Web开发中,前后端的高效交互是提升用户体验的关键。本文通过一个基于Flask框架的博客系统实战案例,详细介绍了如何使用AJAX和Fetch API实现不刷新页面查看评论的功能。从后端路由设置到前端请求处理,全面展示了这两种技术的应用技巧,帮助Python Web开发者提升项目质量和开发效率。
28 1
|
15天前
|
XML 安全 PHP
PHP与SOAP Web服务开发:基础与进阶教程
本文介绍了PHP与SOAP Web服务的基础和进阶知识,涵盖SOAP的基本概念、PHP中的SoapServer和SoapClient类的使用方法,以及服务端和客户端的开发示例。此外,还探讨了安全性、性能优化等高级主题,帮助开发者掌握更高效的Web服务开发技巧。
|
18天前
|
安全 数据库 开发者
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第26天】本文详细介绍了如何在Django框架下进行全栈开发,包括环境安装与配置、创建项目和应用、定义模型类、运行数据库迁移、创建视图和URL映射、编写模板以及启动开发服务器等步骤,并通过示例代码展示了具体实现过程。
30 2