跳至主要內容

Servlet服务端编程

wangdx大约 42 分钟

Servlet

Servlet 是 Java 实现的 CGl 技术(Common GatewayInterface 公共网关接口),是 Java 为实现动态 WEB 开发在较早时期推出一项技术,同时也是现在项目开发中必不可少的一项技术。

传统 CGI 多进程处理

CGI 仅仅是一个通信标准,受到早期硬件性能与软件技术的限制,大多数采用的都是多进程的处理技术,即:每一个用户请求对于操作系统内部都要启动一个相应的进程进行请求处理

Java 实现 CGI

通过 Java 实现的 CGI 技术,充分的发挥了 Java 语言中多线程的技术特点,采用线程的形式实现了用户的 HTTP 请求与响应,这样每一个进程的启动速度更快,服务器的处理性能更高

Servlet 存在的问题

早期的 Servet 技术由于需要实现动态 WEB 的响应,所以要通过 Servlet 实现页面的最终显示,这样在程序中就会存在有大量的 IO 输出操作,如下所示

out.println("<html>")
out.println("<header>")
out.println("</header>")
out.println("</html>")

页面模版

虽然通过这样的技术可以实现最终的 HTML 页面响应,但是一定会带来程序代码的维护困难,所以早期的开发者会考虑定义一个 HTML 显示模版文件,而后利用 IO 流加载模版文件,最终完成页面展示

JSP 产生

Servlet 技术受到了微软 ASP 技术的启发,将 Servet 改进,推出了 JSP 技术,但是这并不表示 Servlet 技术已经被 JSP 所取代,两者之间在开发中有很强的互补性,因为 Servet 使用纯 Java 编写所以更加适合于编写 Java 程序代码,而 JSP 将更加适合于动态页面的展示。

Servlet 编程起步

Servlet 继承结构

Servlet 程序是 Java 类对象的形式来处理用户请求的,对于每一个处理 HTTP 请求的 Servet 类都需要明确的继承 jakarta.servlet.http.HttpServlet 父类

package w01;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author wangdx
 */
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 在doGet()方法中已经明确的给出了HttpServletRequest、HttpServletResponse接口实例
        // 这个接口实例是在每一次进行请求处理的时候,由容器负责提供的
        // 在Java中提供了两个打印流:PrintStream、PrintWriter,HTML实际上是文本,所以字符流合适
        PrintWriter out = resp.getWriter(); // 获得了客户端浏览器的输出流
        out.println("<html>");
        out.println("<head>");
        out.println("   <title>Yootk - JavaWEB</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("   <h1>www.yootk.com</h1>");
        out.println("</body>");
        out.println("</html>");
        out.close(); // 关闭输出流
    }
}

Servlet 映射访问

所有的 Servlet 定义完成后都需要保存在 WEB-INF/classes 目录之中,这样才能够被 WEB 容器所加载,但是如果要想让其可以进行用户的请求处理,则必须修改 web.xmI 配置文件,追加 Servlet 的映射访问路径

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <servlet> <!-- 定义一个Servlet程序配置类 -->
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>w01.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello.action</url-pattern> <!-- 映射路径必须使用“/”开头 -->
    </servlet-mapping>
</web-app>

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <servlet> <!-- 定义一个Servlet程序配置类 -->
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>w01.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello.action</url-pattern> <!-- 映射路径必须使用“/”开头 -->
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/muyan1.yootk</url-pattern> <!-- 映射路径必须使用“/”开头 -->
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/muyan/yootk/*</url-pattern> <!-- 映射路径必须使用“/”开头 -->
    </servlet-mapping>
</web-app>

Servlet 与表单

表单处理

动态 WEB 请求中最重要的是与访问用户的交互性,而交互性的基本的体现形式就是 HTML 表单,而表单中常用的提交模式为 POST,这样就必须在处理的 Servet 类中明确的进行 doPost()方法覆写。

路径匹配

在进行表单提交时需要注意请求处理的 Servlet 映射路径要与 HTML 页面路径相匹配,例如:此时的 input.html 页面储存在了“pages/front/param”父路径之中,为了便于访问一般都会将 Servlet 也定义在同样的父路径下

...
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    this.doGet(req, resp); // 调用doGet()处理
}
...
<html>
<head>
    <title>沐言科技:www.yootk.com</title>
    <meta charset="UTF-8">
</head>
<body>
<form action="/input.action" method="post">
    请输入信息:<input type="text" name="message" value="沐言科技:www.yootk.com">
    <button type="submit">提交</button>
</form>
<form action="input.action" method="post">
    请输入信息:<input type="text" name="message" value="沐言科技:www.yootk.com">
    <button type="submit">提交</button>
</form>
</body>
</html>


web.xml

<servlet>   <!-- 定义一个Servlet程序配置类 -->
    <servlet-name>InputServlet</servlet-name>
    <servlet-class>w02.com.yix.servlet.InputServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>InputServlet</servlet-name>
    <url-pattern>/pages/message/input.action</url-pattern> <!-- 映射路径必须使用“/”开头 -->
</servlet-mapping>
<servlet-mapping>
    <servlet-name>InputServlet</servlet-name>
    <url-pattern>/input.action</url-pattern> <!-- 映射路径必须使用“/”开头 -->
</servlet-mapping>

@WebServlet 注解

@WebServlet 注解

传统的 Servlet 程序定义之后往往都需要通过 web.xml 进行配置但是这样的配置结构会随着项目代码的不断完善而造成 web.xml 文件过大的问题,而为了简化 Servlet 的配置形式,从 Servlet 3.0 之后提供了基于 Annotation 注解的配置模式,开发者可以直接在 Servet 程序类中使用“@WebServlet”注解实现 Servet 配置

@WebServlet("/annotation.action")
public class AnnotationServlet extends HttpServlet {
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 在doGet()方法中已经明确的给出了HttpServletRequest、HttpServletResponse接口实例
        // 这个接口实例是在每一次进行请求处理的时候,由容器负责提供的
        // 在Java中提供了两个打印流:PrintStream、PrintWriter,HTML实际上是文本,所以字符流合适
        PrintWriter out = resp.getWriter(); // 获得了客户端浏览器的输出流
        out.println("<html>");
        out.println("<head>");
        out.println("   <title>Yootk - JavaWEB</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("   <h1>www.yootk.com</h1>");
        out.println("</body>");
        out.println("</html>");
        out.close(); // 关闭输出流
    }
}

@WebServlet(description = "使用注解实现的Servlet的配置,适用于Servlet 3.0标准之后",
        urlPatterns = {"/w03/hello.action", "/w03/muyan.yootk", "/w03/muyan/yootk/*"}) // 直接使用默认的value属性进行匹配
public class HelloServlet extends HttpServlet {
....代码珞
}

Servlet 生命周期

Servlet 生命周期简介

Servlet 生命周期

Servlet 程序是一个运行在 WEB 容器中的 Java 类,所有的 Java 类在执行时都需要通过实例化对象来进行类中功能调用,在 WEB 容器中会自动进行 Servet 实例化对象的管理,并且规定了一套完整的 Servlet 生命周期

Servet 生命周期处理流程

  • 1、【容器加载】Servlet 运行在特定的 WEB 应用之中,所以当 Tomcat 启动后首先就需要进行 Servlet 上下文初始化,随后会依据配置的 Servlet 进行类加载,如果加载失败,则容器启动失败;
  • 2、【对象实例化】所有的 Servlet 程序都是通过反射机制将类名称加载到 WEB 容器,当 servlet 类被加载后会自动实例化唯一的 servlet 对象,如果实例化失败,则容器启动失败;
  • 3、【初始化】进行一些 Servet 初始化的工作处理,此操作是在构造方法执行之后进行,可以利用此方式获取一些初始化的配置信息(例如:初始化参数),正常情况下,一个 servlet 只会初始化一次;
  • 4、【服务支持】对用户所发送来的请求进行服务的请求和响应处理,服务操作部分会重复执行
  • 5、【销毁】一个 servlet 如果不再使用则执行销毁处理,销毁的时候可以对 servlet 所占用的资源进行释放
  • 6、【对象回收】servlet 实例化对象不再使用后会由 JVM 进行对象回收与内存空间释放;
  • 7、【容器卸载】关闭 WEB 容器,停止当前服务。

Servlet 基础生命周期

在 JakartaEE 标准中为了便于用户进行 Servet 生命周期的控制专门提供了一系列的生命周期控制方法,开发者只需要在子类中覆写表所列出的方法即可实现 Servet 基础生命周期控制,

package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/life")
public class LifeCycleServlet extends HttpServlet {
    public LifeCycleServlet() {
        System.out.println("【LifeCycleServlet.()】构造方法。");
    }
    @Override
    public void init() throws ServletException {
        System.out.println("【LifeCycleServlet.init()】Servlet初始化。");
    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【LifeCycleServlet.doGet()】处理GET请求。");
    }
    @Override   // 进行GET请求的处理
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【LifeCycleServlet.doPost()】处理POST请求。");
    }

    @Override
    public void destroy() {
        System.out.println("【LifeCycleServlet.destroy()】Servlet销毁。");
    }
}

生命周期执行操作

在第一次访问此 Servet 映射路径时,会由 WEB 容器自动调用无参构造方法实例化指定本类对象,随后会通过 init()方法进行 Servet 初始化控制,最后在进行请求处理,如图所示。而如果现在用户再次发出请求,由于该 Servet 对象已经存在,所以直接调用服务方法进行请求处理即可,最后在容器关闭或者该 Servlet 长期不使用时才会调用 destroy()方法进行资源释放

Servlet 扩展生命周期

Servlet 生命周期控制方法全部都在 GenericServlet 类以及其 Servlet 父接口中进行了定义,在实际的 Servlet 开发中,往往都采用如图所示的继承结构图

package com.yootk.servlet;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(value="/life", loadOnStartup = 1, initParams = {
        @WebInitParam(name = "message", value = "www.yootk.com")
})
public class LifeCycleServlet extends HttpServlet {
    public LifeCycleServlet() {
        System.out.println("【LifeCycleServlet.()】构造方法。");
    }
    @Override
    public void init() throws ServletException {
        System.out.println("【LifeCycleServlet.init()】Servlet初始化。");
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 此时要通过Servlet的配置获取指定名称的初始化参数内容
        System.out.println("【LifeCycleServlet.init(ServletConfig config)】Servlet初始化,message = " + config.getInitParameter("message"));
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【LifeCycleServlet.service()】处理用户HTTP请求。");
    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【LifeCycleServlet.doGet()】处理GET请求。");
    }
    @Override   // 进行POST请求的处理
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【LifeCycleServlet.doPost()】处理POST请求。");
    }

    @Override
    public void destroy() {
        System.out.println("【LifeCycleServlet.destroy()】Servlet销毁。");
    }
}

Servlet 与内置对象

ServletConfig

Servlet 可以实现全部 JSP 程序功能,所以在 Servlet 中可以直接进行内置对象的处理操作,在所有的 Servlet 实现子类中只要覆写了父类中的 doXxx()方法,就可以直接获取到 HttpServletRequest、HttpServletResponse 的对象实例,如图 5-12 所示,同时由于所有的 Servet 类都是 Servet 接口的子类,那么就可以直接通过 Servet 接口实例获取 ServletConfig 内置对象,实现初始化参数的获取。

package com.yootk.servlet;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

// 在实际的项目开发中,初始化参数一般都会使用一些英文或字母进行的标记配置,很少会出现中文
@WebServlet(urlPatterns = {"/inner"}, initParams = {
        @WebInitParam(name = "message", value = "www.yootk.com"),
        @WebInitParam(name = "teacher", value = "Small Lee")},
        loadOnStartup = 1) // 直接使用默认的value属性进行匹配
public class InnerObjectServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        Enumeration<String> enu = config.getInitParameterNames(); // 获取全部初始化参数名称
        while (enu.hasMoreElements()) {
            String paramName = enu.nextElement(); // 获取初始化参数名称
            System.out.println("【InnerObjectServlet.init(ServletConfig config)】" + paramName + " = " + config.getInitParameter(paramName));
        }
    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    }

    @Override   // 进行GET请求的处理
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Enumeration;

// 在实际的项目开发中,初始化参数一般都会使用一些英文或字母进行的标记配置,很少会出现中文
@WebServlet(urlPatterns = {"/inner"}, initParams = {
        @WebInitParam(name = "message", value = "www.yootk.com"),
        @WebInitParam(name = "teacher", value = "Small Lee")},
        loadOnStartup = 1) // 直接使用默认的value属性进行匹配
public class InnerObjectServlet extends HttpServlet {
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Enumeration<String> enu = super.getServletConfig().getInitParameterNames(); // 获取全部初始化参数名称
        while (enu.hasMoreElements()) {
            String paramName = enu.nextElement(); // 获取初始化参数名称
            System.out.println("【InnerObjectServlet.init(ServletConfig config)】" + paramName + " = " + super.getServletConfig().getInitParameter(paramName));
        }
    }

    @Override   // 进行GET请求的处理
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

获取 application 内置对象

获取 ServletContext 接口实例 application 内置对象对应的类型为“jakarta.servlet.ServletContext”接口实例,是一个保存在 WEB 容器中的内置对象,所有的用户都可以直接使用同一个 application 对象。在 Servet 中如果要想获取 ServetContext 接囗实例,则可以通过 GenericServet 类或者 ServletRequest 接囗中的“getServetContext”方法来完成

package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/inner")
public class InnerObjectServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        System.out.println("【InnerObjectServlet.init()】RealPath = " + super.getServletContext().getRealPath("/"));
    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("【InnerObjectServlet.doGet()】{GenericServlet}RealPath = " + super.getServletContext().getRealPath("/"));
        System.out.println("【InnerObjectServlet.doGet()】{ServletRequest}RealPath = " + req.getServletContext().getRealPath("/"));
    }

    @Override   // 进行GET请求的处理
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

获取 session 内置对象

Servlet 获取 HttpSession 接

session 是进行请求用户身份认证的重要内置对象,session 内置对象对应的类型为“jakarta.servlet.http.HttpSession”所有的 session 都必须通过 HTTP 客户端的 Cookie 获取相应的标识后才可以实现服务器状态的获取,所以如果要想在 Servlet 中获取 session 内置对象就只有依靠 HttpServletRequest 接来实现

package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;

@WebServlet("/inner")
public class InnerObjectServlet extends HttpServlet {

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HttpSession session = req.getSession(); // 获取HttpSession对象
        System.out.println("SESSIONID = " + session.getId()); // 获取当前的SessionID
        session.invalidate(); // Session失效
    }

    @Override   // 进行GET请求的处理
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

Servlet 跳转

简介

Servlet 请求处理与转发

Servlet 作为原生的 Java 程序,可以非常方便的实现各种业务逻辑的调用,同时也可以更加方便的从指定的数据源中获取所需要的数据信息,然而 Servet 却存在有一个非常严重的问题,就是其并不适合于 HTML 页面展示,这样在实际的开发中就会考虑将所需的数据传递到 JSP 中进行数据展示

客户端跳转

客户端跳转

每一个 Servlet 中进行的请求处理操作都会自动提供有 HttpServetResponse 内置对象,在 HttpServetResonse 接中提供了一个请求重定向的处理方法:sendRedirect(),利用此方法即可实现 Servlet 跳转到 JSP 页面的功能:

1、
<%@ page pageEncoding="UTF-8" %>    <%-- 设置显示编码 --%>
<%
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
    <title>沐言科技:www.yootk.com</title>
    <base href="<%=basePath%>">
</head>
<body>
<div><img src="images/yootk.png"></div>
<h1>【Request属性接收】request-msg = <%=request.getAttribute("request-msg")%></h1>
<h1>【Session属性接收】session-msg = <%=session.getAttribute("session-msg")%></h1>
</body>
</html>

2、
package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/jump")
public class JumpServlet extends HttpServlet {
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("request-msg", "www.yootk.com"); // request属性传递
        req.getSession().setAttribute("session-msg", "edu.yootk.com"); // session属性传递
        resp.sendRedirect("/show.jsp"); // 页面在根路径之中,所以可以直接跳转
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

3、http://localhost/jump.jsp

服务器端跳转

RequestDispatcher 接口

由于 Servlet 传递到 JSP 页面中的属性内容往往会包含有大量的数据信息,这样就需要在每次请求后将所传递的属性内容进行清空,所以这样就需要使用到服务器端跳转,在 Servlet 中的服务器端跳转主要通过 RequestDispatcher 接口来实现

package com.yootk.servlet;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/jump")
public class JumpServlet extends HttpServlet {
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setAttribute("request-msg", "www.yootk.com"); // request属性传递
        req.getSession().setAttribute("session-msg", "edu.yootk.com"); // session属性传递
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("/show.jsp"); // 具备了跳转的功能
        requestDispatcher.forward(req, resp); // 服务端跳转
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

Servlet 异步处理

Servlet 异步响应

传统的 Servlet 在进行用户请求处理与响应时都是在一个线程上完成的,这样当处理与响应时间过长时就会造成该线程的长期占用,从而导致系统资源耗尽的问题,为了解决此类问题,在 Servlet 3.0 标准中提供了异步响应支持,可以由开发者自行创建一个异步线程进行客户端响应

ServletRequest 异步支持方法

在 Servlet 类中提供了 AsyncContext 接口进行异步响应操作而要想获取此接口的实例化对象,就必须通过 ServletRequest 接口实现

异步请求响应

Servlet 异步响应

jakarta.servlet.AsyncContext 接囗是实现异步响应处理的核心接口,此接口的对象实例通过 Servlet 创建,在该接口进行异步处理时,必须明确的传递一个 Runnable 接口实例,而后可以直接在 run()方法中进行请求的接收与响应处理

AsyncContext 接口方法

由于最终的异步响应全部由 AsyncContext 接口完成,同时在响应处理时还需要明确的获取 ServetRequest、ServletResponse 接口实例

1package com.yootk.servlet;

import jakarta.servlet.AsyncContext;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@WebServlet(value = "/async", asyncSupported = true) // 默认情况下是不开启异步支持的
public class AsyncServlet extends HttpServlet {
    private class WorkerThread implements Runnable {   // 创建一个线程类
        private AsyncContext asyncContext; // 负责最终的异步处理
        public WorkerThread(AsyncContext asyncContext) {
            this.asyncContext = asyncContext;
        }
        @Override
        public void run() { // 进行异步响应
            System.err.println("【WorkerThread.run()】ThreadName = " + Thread.currentThread().getName());
            String msg = this.asyncContext.getRequest().getParameter("info"); // 获取当前用户的请求参数
            try {
                TimeUnit.SECONDS.sleep(2); // 模拟处理延迟
                // 通过异步上下文获取一个响应对象,并且输出响应内容,但是对于异步处理完成之后需要给出一个信号
                this.asyncContext.getResponse().getWriter().println("<h1>【ECHO】" + msg +"</h1>");
                this.asyncContext.complete(); // 异步处理完成
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.err.println("【AsyncServlet.doGet()】ThreadName = " + Thread.currentThread().getName());
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        // 所有的AsyncContext相关的处理操作全部都是在ServletRequest接口中定义
        if (req.isAsyncSupported()) { // 判断当前是否支持有异步请求处理能力
            AsyncContext asyncContext = req.startAsync(); // 创建异步处理的上下文对象
            asyncContext.start(new WorkerThread(asyncContext)); // 启动异步线程
        } else {
            resp.getWriter().println("<h1>骚蕊,我还不支持异步请求处理,请放过我吧!</h1>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}


2、

http://localhost/async?info=沐言科技:www.yootk.com

异步响应监听

异步响应监听

为方便 Servlet 进行异步处理线程的操作与控制,在进行异步响应结构设计时,专门提供了一个监听接口,利用这个接口可以方便的实现异步处理线程的若干状态的监听,例如:异步开启监听(StartAsyn)、 异步处理完毕监听(Complete)、错误响应监听(Error)、超时监听(Timeout),为便于用户实现异步监听,专门提供了 AsyncListener 接

package com.yootk.servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@WebServlet(value = "/async", asyncSupported = true) // 默认情况下是不开启异步支持的
public class AsyncServlet extends HttpServlet {
    private class WorkerThread implements Runnable {   // 创建一个线程类
        private AsyncContext asyncContext; // 负责最终的异步处理
        public WorkerThread(AsyncContext asyncContext) {
            this.asyncContext = asyncContext;
        }
        @Override
        public void run() { // 进行异步响应
            System.err.println("【WorkerThread.run()】ThreadName = " + Thread.currentThread().getName());
            String msg = this.asyncContext.getRequest().getParameter("info"); // 获取当前用户的请求参数
            try {
                TimeUnit.SECONDS.sleep(2); // 模拟处理延迟
                // 通过异步上下文获取一个响应对象,并且输出响应内容,但是对于异步处理完成之后需要给出一个信号
                this.asyncContext.getResponse().getWriter().println("<h1>【ECHO】" + msg +"</h1>");
                this.asyncContext.complete(); // 异步处理完成
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private class WorkerAsyncListener implements AsyncListener {   // 异步监听操作
        @Override
        public void onComplete(AsyncEvent asyncEvent) throws IOException {
            System.out.println("【WorkerAsyncListener.onComplete()】异步线程处理完毕,接收参数内容:" +
                    asyncEvent.getSuppliedRequest().getParameter("info"));
        }
        @Override
        public void onTimeout(AsyncEvent asyncEvent) throws IOException {
            System.out.println("【WorkerAsyncListener.onTimeout()】异步处理时间超时,接收参数内容:" +
                    asyncEvent.getSuppliedRequest().getParameter("info"));
        }
        @Override
        public void onError(AsyncEvent asyncEvent) throws IOException {
            System.out.println("【WorkerAsyncListener.onError()】异步线程处理错误,接收参数内容:" +
                    asyncEvent.getSuppliedRequest().getParameter("info"));
        }
        @Override
        public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
            System.out.println("【WorkerAsyncListener.onStartAsync()】开启了一个异步处理线程,接收参数内容:" +
                    asyncEvent.getSuppliedRequest().getParameter("info"));
        }
    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.err.println("【AsyncServlet.doGet()】ThreadName = " + Thread.currentThread().getName());
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        // 所有的AsyncContext相关的处理操作全部都是在ServletRequest接口中定义
        if (req.isAsyncSupported()) { // 判断当前是否支持有异步请求处理能力
            AsyncContext asyncContext = req.startAsync(); // 创建异步处理的上下文对象
            asyncContext.addListener(new WorkerAsyncListener()); // 绑定异步监听
            asyncContext.setTimeout(5000); // 200毫秒应该结束异步操作
            asyncContext.start(new WorkerThread(asyncContext)); // 启动异步线程
        } else {
            resp.getWriter().println("<h1>骚蕊,我还不支持异步请求处理,请放过我吧!</h1>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

ReadListener

在 Servlet 异步请求处理模式中,所使用的依然是传统的 BIO 结构,这样会极大的限制程序的可扩展性,当客户端发送的请求数据较大或者数据流传输速度慢,都有可能会影响到服务器的处理性能,所以从 Servet3.1 版本提供了-个 ReadListener 接口,可以实现非阻塞的数据流读取监听

1、
<%@ page pageEncoding="UTF-8" %>    <%-- 设置显示编码 --%>
<%
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
    <title>沐言科技:www.yootk.com</title>
    <base href="<%=basePath%>">
</head>
<body>
<form action="async.input" method="post">
    信息:<input type="text" name="info" value="www.yootk.com"/>
    <button type="submit">提交</button>
</form>
</body>
</html>

2、
<%@ page pageEncoding="UTF-8" %>    <%-- 设置显示编码 --%>
<%
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
<head>
    <title>沐言科技:www.yootk.com</title>
    <base href="<%=basePath%>">
</head>
<body>
<div><img src="images/yootk.png"></div>
<h1>【ECHO数据回显】<%=request.getAttribute("message")%></h1>
</body>
</html>

3、
package com.yootk.servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

@WebServlet(value = "/async.input", asyncSupported = true) // 默认情况下是不开启异步支持的
public class AsyncReadServlet extends HttpServlet {
    private class ServletReadListener implements ReadListener { // 异步数据读取
        private AsyncContext asyncContext;
        private ServletInputStream servletInputStream; // 异步读取由此触发
        public ServletReadListener(AsyncContext asyncContext, ServletInputStream servletInputStream) {
            this.asyncContext = asyncContext;
            this.servletInputStream = servletInputStream;
        }
        @Override
        public void onDataAvailable() throws IOException {  // 数据可以读取时触发
            try {
                TimeUnit.SECONDS.sleep(5); // 强制性的模拟延迟5秒
                StringBuilder builder = new StringBuilder(); // 实现数据的保存,“info=www.yootk.com”
                int len = -1; // 读取数据的标记
                byte temp [] = new byte[1024]; // 设置字节数组
                while (this.servletInputStream.isReady() && (len = this.servletInputStream.read(temp)) != -1) {
                    String data = new String(temp, 0, len); // 将字节数据变为字符串
                    builder.append(data); // 将接收到的数据保存在StringBuilder之中
                }
                String info = builder.toString().split("=")[1]; // 获取info参数的内容
                this.asyncContext.getRequest().setAttribute("message", "{ECHO}" + info); // 设置request属性
                this.asyncContext.dispatch("/echo.jsp");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onAllDataRead() throws IOException {
            System.out.println("【ServletReadListener.onAllDataRead()】所有的请求数据读取完毕。");
        }

        @Override
        public void onError(Throwable throwable) {
            System.out.println("【ServletReadListener.onError()】数据读取出错," + throwable);
        }
    }
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (req.isAsyncSupported()) { // 判断当前是否支持有异步请求处理能力
            AsyncContext asyncContext = req.startAsync(); // 创建异步处理的上下文对象
            ServletInputStream servletInputStream = req.getInputStream();
            servletInputStream.setReadListener(new ServletReadListener(asyncContext, servletInputStream));
        } else {
            resp.getWriter().println("<h1>骚蕊,我还不支持异步请求处理,请放过我吧!</h1>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

WriteListener

WriteListener

请求和响应是 HTTP 服务处理的核心主题,在请求数据较大时可以通过 ReadListener 实现异步接收,而在响应数据过多时也可以通过 WriteListener 进行异步处理

WriteListener 接囗

WriteListener 是一个基于异步响应处理的服务接口标准,是在 Servlet 3.1 标准中正式提供的,当用户在程序中需要通过 ServletOutputStream 直接进行数据响应时,就可以将直接绑定 WriteListener 子类实例,而后在'onWritePossible()”方法触发后进行数据输出

package com.yootk.servlet;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

@WebServlet(value = "/async.output", asyncSupported = true) // 默认情况下是不开启异步支持的
public class AsyncWriteServlet extends HttpServlet {
    private class ServletWriteListener implements WriteListener {
        private AsyncContext asyncContext;
        private ServletOutputStream servletOutputStream;
        public ServletWriteListener(AsyncContext asyncContext, ServletOutputStream servletOutputStream) {
            this.asyncContext = asyncContext;
            this.servletOutputStream = servletOutputStream;
        }
        @Override
        public void onWritePossible() throws IOException { // 输出准备就绪执行
            try {
                TimeUnit.SECONDS.sleep(5);  // 模拟延迟
                PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(this.servletOutputStream, "UTF-8"));
                printWriter.print("<h1>www.yootk.com</h1>");
                printWriter.close();
                this.asyncContext.complete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onError(Throwable throwable) { // 出现异常时执行
        }
    }
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (req.isAsyncSupported()) { // 判断当前是否支持有异步请求处理能力
            AsyncContext asyncContext = req.startAsync(); // 创建异步处理的上下文对象
            // 一旦项目中得到了Servlet输出流之后,不能够再跳转了
            ServletOutputStream outputStream = resp.getOutputStream(); // 获取输出流
            outputStream.setWriteListener(new ServletWriteListener(asyncContext, outputStream));

        } else {
            resp.getWriter().println("<h1>骚蕊,我还不支持异步请求处理,请放过我吧!</h1>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp);
    }
}

过滤器

简介

过滤器

传统的 WEB 开发中,由于只关心用户请求与响应的基本结构处理。所以就需要开发者在 Servlet 程序中编写大量的非业务核心的处理逻辑,例如:“登录认证检查”,“编码设置”等,为了解决这些重复的公共操作,在 Servet 2.3 标准中提供了过滤器的概念,利用过滤器可以自动的实现在请求和响应中间处理操作,使得程序的开发更加的简洁,也更加便于代码的维护

提示

传统的过滤器需要覆写 Filter 接口中提供三个抽象方法,但是最新版本的过滤器中只需要覆写一个 doFilter(0)方法即可,但是随着 Servet 技术版本的不断提升,对于过滤器的实现也发生了新的改变,

过滤器实现结构

由于 HTTP 协议中包含有两“请求”和“响应”两个组成部分,所以每一个过滤器都分为两部分:请求过滤、响应过滤,同时在项目中允许提供有多个过滤器,这多个过滤器也将按照定义顺序依次执行。开发者定义过滤器时必须提供有一个过滤器的处理类,该类需要明确继承 HttpFilter 父类

在项目之中 FilterChain 接口类型本质上描述的就是将请求继续向下传递,传递给目标路径。对于以上的处理结构会在后续通过具体的代码进行解释,现在只需要记住,在新版本的 Fiter 实现结构之中已经不再去直接实现 Filter 接口了,而是要像 Servlet-样去继承专属协议的抽象类,这样做的目的是为了统一实现结构。

过滤器编程起步

1.编写第一个过滤器

package w04;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpFilter;

import java.io.IOException;

/**
 * @author wangdx
 */
public class BaseFilter extends HttpFilter { // 实现一个Http过滤器

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【BaseFilter.doFilter()】用户请求过滤...");
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println("【BaseFilter.init()】过滤器初始化,初始化参数:message = " + config.getInitParameter("message"));
    }

    @Override
    public void destroy() {
        System.out.println("【BaseFilter.destroy()】过滤器销毁");
    }
}

2.由于此时的过滤器属于一个普通的 Java 程序,所以如果要想让当前的过滤器可以执行,则需要在 web.xm| 文件之中进行配置,修改 web.xml 文件,添加过滤器以及初始化参数的定义。

<!--    过滤器配置 -->
    <filter>
        <filter-name>BaseFilter</filter-name>
        <filter-class>w04.BaseFilter</filter-class>
        <init-param>
            <param-name>message</param-name>
            <param-value>www.yootk.com</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>BaseFilter</filter-name>
        <url-pattern>/*</url-pattern>   <!-- 过滤器的执行路径,此时表示匹配所有WEB路径 -->
    </filter-mapping>

3.修改过滤器让请求通过

@Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【BaseFilter.doFilter()】用户请求过滤...");
        chain.doFilter(request, response); // 放行
        System.out.println("【BaseFilter.doFilter()】服务端响应过滤...");
    }

Dispatcher 转发模式

过滤器转发模式

在默认情况下只要项目中配置了过滤器,并且用户所请求的路径与过滤路径相匹配时,都会自动的进行过滤器的执行,但是在 Servet3.0 标准中进一步规范化了过滤器的执行范围,例如:跳转触发、包含触发等,这些转发模式的范围全部都通过 jakarta.servlet.DispatcherType 枚举类定义

过滤器 FORWARD 转发

public class ForwardFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【BaseFilter.doFilter()】Request属性:" +
                request.getAttribute("request-msg") + "、Session属性:" +
                request.getSession().getAttribute("session-msg"));
        chain.doFilter(request, response); // 跳转到目标处理路径
    }

}
    <filter>
        <filter-name>ForwardFilter</filter-name>
        <filter-class>w04.ForwardFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ForwardFilter</filter-name>
        <url-pattern>/*</url-pattern>   <!-- 过滤器的执行路径,此时表示匹配所有WEB路径 -->
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
    <filter>
        <filter-name>BaseFilter</filter-name>
        <filter-class>com.yootk.filter.BaseFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>BaseFilter</filter-name>
        <url-pattern>/*</url-pattern>   <!-- 过滤器的执行路径,此时表示匹配所有WEB路径 -->
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
        <dispatcher>ASYNC</dispatcher>
    </filter-mapping>

http://localhost:8080/serviceJump

@WebFilter 注解

@WebFilter 注解属性

Servlet 3.0 版本后,为了简化过滤器的配置,提供了“@WebFilter”注解项,直接在过滤器类的定义上使用此注解,随后设置好相应的属性即可

@WebFilter(urlPatterns = {"/w03/*", "/admin/*"}, initParams = {@WebInitParam(name = "message", value = "www.yootk.com")})
public class AnnotationFilter extends HttpFilter {
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【AnnotationFilter.doFilter()】message = " + super.getInitParameter("message"));
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

过滤器执行顺序

过滤器执行顺序

利用过滤器可以方便的实现请求数据的处理,同时在 JavaWEB 中允许开发者配置多个不同的过滤器,而这多个过滤器之间依靠类名称的字母顺序进行执行,例如,现在有两个 Fiter 类同时映射到了一个过滤路径,一个类名称为“AFilter”,另一个类名称为“BFilter”,则按照字母顺序“AFilter”会先执行,顺序一句类名称决定,和”@WebFilter“ filterName 无关

1package com.yootk.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter("/*")
public class AFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【AFilter.doFilter()】AAAAAAAAAAAAAAAAAAAAAAA");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

2package com.yootk.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class BFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【BFilter.doFilter()】BBBBBBBBBBBBB");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

3package com.yootk.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter("/*")
public class CFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【CFilter.doFilter()】CCCCCCCCCCCCC");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

4package com.yootk.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter(filterName = "YootkZFilter", value="/*")
public class AFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【AFilter.doFilter()】AAAAAAAAAAAAAAAAAAAAAAA");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

5package com.yootk.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter(filterName = "YootkAFilter", value="/*")
public class BFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【BFilter.doFilter()】BBBBBBBBBBBBB");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}


6package com.yootk.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebFilter(filterName = "YootkCFilter", value="/*")
public class CFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【CFilter.doFilter()】CCCCCCCCCCCCC");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

编码过滤

请求编码设置

在每一次 WEB 请求时为了可以获得正确的数据内容,都需要对请求和响应数据进行编码处理,这样一来,几乎所有的 Servet 与 JSP 程序都需要调用“setCharacterEncoding()”方法,这样一来就使得编码的维护困难

编码过滤

虽然在实际开发中会广泛的使用“UTF-8”编码,但是在项目中依然有可能会出现其他的程序编码,而为了便于程序编码的管理,可以将所有的编码设置交由过滤器完成,同时利用初始化参数的形式实现项目编码的动态配置这样以来就使得编码的配置更加方便,提高了代码的可重用性。

1package com.yootk.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

public class EncodingFilter extends HttpFilter { // 定义编码过滤类
    // 默认的编码,如果用户没有配置任何的编码,则使用此编码
    public static final String DEFAULT_ENCODING = "UTF-8";
    private String charset; // 接收初始化参数

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.charset = filterConfig.getInitParameter("charset"); // 接收初始化参数
        if (this.charset == null || "".equals(this.charset)) {  // 没有配置编码
            this.charset = DEFAULT_ENCODING; // 使用默认编码
        }
    }
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        chain.doFilter(request, response);
    }
}

2<filter>
        <filter-name>EncodingFilter</filter-name>
        <filter-class>com.yootk.filter.EncodingFilter</filter-class>
        <init-param>
            <param-name>charset</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3、
package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/input.action")
public class InputServlet extends HttpServlet {
    public InputServlet() {
        System.out.println("【InputServlet.()】构造方法。");
    }

    @Override
    public void init() throws ServletException {
        System.out.println("【InputServlet.init()】Servlet初始化。");
    }

    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8"); // 如果不设置就出现显示乱码
        String msg = req.getParameter("message"); // 接收请求参数
        PrintWriter out = resp.getWriter(); // 获得了客户端浏览器的输出流
        out.println("<html>");
        out.println("<head>");
        out.println("   <title>Yootk - JavaWEB</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("   <h1>" + msg + "</h1>"); // 输出请求参数
        out.println("</body>");
        out.println("</html>");
        out.close(); // 关闭输出流
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doGet(req, resp); // 调用doGet()处理
    }
}

http://localhost/input.action?message=沐言科技:www.yootk.com

@WebFilter(value = "/*",
        initParams = {@WebInitParam(name = "charset", value = "UTF-8")})
public class EncodingFilter extends HttpFilter {....}

登录检测过滤

用户认证过滤

为了保证项目的运行安全,所有的使用者都必须经过认证,并且通过 session 实现认证信息的保存,这样就要求在每一个请求前都自动进行认证检查,当认证检查通过后才允许进行请求处理,而当认证检查失败后就会自动跳转到登录页,这样就可以将认证检查的代码直接放在过滤器中,在每次请求前自动进行相关登录认证检查的处理操作

代码地址open in new window

ServletRequest 监听器

ServletRequest 监听器简介

请求监听

监听器是 Servlet 3.1 提供的重要组件,利用监听器可以方便实现 WEB 中指定操作状态的监控处理,在用户每次向服务器端发送请求时,实际上都会自动的被请求监听器所监听,同时也会自动的产生有一个相应的请求事件,这样开发者就可以编写专属的事件处理类,对请求的状态进行控制

ServletRequestListener 监听

jakarta.servlet.ServletRequestListener 提供了客户端请求的监听控制,开发者可以利用此接口实现用户请求初始化监听,以及用户请求销毁监听,当事件被监听到后都会自动的产生一个“ServletRequestEvent”事件源对象,开发者可以通过此对象获取 ServletRequest 与 ServletContext 接实例

视频播放量统计监听

本次将通过 ServetRequestListener 监听接口实现一个视频访问量更新的操作,现在假设有一个视频播放页面“video.jsp”,每当用户需要进行视频播放时,都需要通过 Servlet 访问该页,同时利用“/video/{vid}”的形式传递一个要播放的视频编号,这样在每次访问时可以直接通过请求监听器对要播放的视频编号进行记录,同时将其更新到对应的数据库之中

1、

package com.yootk.listener;

import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.http.HttpServletRequest;

public class VideoCountListener implements ServletRequestListener  {
    @Override
    public void requestInitialized(ServletRequestEvent sre) { // 请求初始化
        System.out.println("【VideoCountListener.requestInitialized()】进行本次请求的处理。");
        // 一般来讲对于视频播放是有一个前提:打开之后跳转了再进行视频的播放,所以在播放之前要实现一个访问量的更新
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String previousUrl = request.getHeader("Referer"); // 之前的访问路径
        // 视频的访问路径:localhost:8080/muyan/yootk/video/10
        String pattern = ".+/video/vid=\\d+"; // 路径的正则匹配
        if (previousUrl.matches(pattern)) { // 路径拼配
            System.out.println("【VideoCountListener.requestInitialized()】视频访问量增加处理。");
        }
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("【VideoCountListener.requestDestroyed()】本次请求处理完成。");
    }
}

2、
    <listener>
        <listener-class>com.yootk.listener.VideoCountListener</listener-class>
    </listener>

3、
package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/video/*")
public class VideoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("/video.jsp").forward(req, resp);
    }
}

4、

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="com.yootk.dbc.DatabaseConnection" %>
<%@ page import="java.sql.*" %>
<%@include file="/pages/plugins/include_basepath.jsp" %>
<html>
<head>
<base href="<%=basePath%>">
<jsp:include page="/pages/plugins/include_javascript.jsp" />
<script type="text/javascript" src="js/index.js"></script>
</head>
<body>
	<div class="container contentback">
		<div class="row">
			<div class="col-sm-12">
				<video width="1140" height="715" controls="controls" autoplay="autoplay" loop="loop">
					<source src="images/main.mp4" type="video/mp4">
				</video>
			</div>
		</div>
	</div>
</body>
</html>

http://localhost:8080/video/vid=10

ServletRequestAttributeListener

请求属性监听

在 JavaWEB 开发中,属性传递属于最核心的数据处理操作,在每一次服务器跳转中,都可以实现 request 属性的传递,但是在跨越多个 JSP/Servlet 时也有可能会产生属性修改(设置的性名称相同),属性删除等操作,那么这些就可以通过 jakarta.servlet.ServletRequestAttributeListener 监听接来实现

package com.yix.listener;

import jakarta.servlet.ServletRequestAttributeEvent;
import jakarta.servlet.ServletRequestAttributeListener;
import jakarta.servlet.ServletRequestListener;

/**
 * @author wangdx
 */
public class GlobalRequestAttributeRecord implements ServletRequestAttributeListener {
    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) { // 属性增加时触发
        System.out.println("【GlobalRequestAttributeRecord.attributeAdded() - 属性增加】name = " + srae.getName() + "、value = " + srae.getValue());
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) { // 属性替换时触发
        System.out.println("【GlobalRequestAttributeRecord.attributeReplaced() - 属性替换】name = " + srae.getName() + "、value = " + srae.getValue());
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) { // 属性删除时触发
        System.out.println("【GlobalRequestAttributeRecord.attributeRemoved() - 属性删除】name = " + srae.getName() + "、value = " + srae.getValue());
    }
}


    <listener>
        <listener-class>
            com.yootk.listener.GlobalRequestAttributeRecord
        </listener-class>
    </listener>


<%@ page pageEncoding="UTF-8" %>    <%-- 设置显示编码 --%>
<%
  request.setAttribute("message", "沐言科技:www.yootk.com"); // 设置request属性
%>
<jsp:forward page="request_attribute_replace.jsp"/>


<%@ page pageEncoding="UTF-8" %>    <%-- 设置显示编码 --%>
<%
  request.setAttribute("message", "沐言科技:www.yootk.com"); // 设置request属性
  request.removeAttribute("message"); // 属性删除
%>

@WebListener 注解

@WebListener
public class GlobalRequestAttributeRecord implements ServletRequestAttributeListener { // 请求属性监听

    @Override
    public void attributeAdded(ServletRequestAttributeEvent srae) { // 属性增加时触发
        System.out.println("【GlobalRequestAttributeRecord.attributeAdded() - 属性增加】name = " + srae.getName() + "、value = " + srae.getValue());
    }

    @Override
    public void attributeReplaced(ServletRequestAttributeEvent srae) { // 属性替换时触发
        System.out.println("【GlobalRequestAttributeRecord.attributeReplaced() - 属性替换】name = " + srae.getName() + "、value = " + srae.getValue());
    }

    @Override
    public void attributeRemoved(ServletRequestAttributeEvent srae) { // 属性删除时触发
        System.out.println("【GlobalRequestAttributeRecord.attributeRemoved() - 属性删除】name = " + srae.getName() + "、value = " + srae.getValue());
    }
}

HttpSession 监听器

简介

Session 监听结构

在 WEB 开发中为了便于用户管理,每一个用户的请求都会为其分配一个唯一的 Session,以方便进行状态的维护,而在用户进行 session 创建时就可以利用监听器进行状态的监控,例如 session 创建、销毁、属性操作等,这些操作状态都提供有完善的事件监控,开发者只要捕获到这些事件就可以进行监听处理

HttpSessionListener

jakarta.servlet.http.HttpSessionListener 提供了 HttpSession 状态的监听操作标准,在该接口中可以实现 session 创建与销毁的状态监听,同时所有的监听都会返回一个 HttpSessionEvent 事件对象,用于实现当前 Session 的操作

1package com.yootk.listener;

import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
@WebListener
public class UserStateMonitor implements HttpSessionListener { // 针对于Session的状态实现监听

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("【UserStateMonitor.sessionCreated()】SessionID = " + se.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("【UserStateMonitor.sessionDestroyed()】SessionID = " + se.getSession().getId());
    }
}


2<%
	session.invalidate(); // 手工注销
%>

3<session-config>
        <session-timeout>30</session-timeout>
    </session-config>

HttpSessionldListener

当用户成功的通过服务器获取 HTTP 资源后,服务器会自动通过 Cookie 的形式将当前客户的 SessionID 保存在客户端浏览器这样每当通过该浏览器发出请求后,都可以自动的匹配上 WEB 容器中所保存的 SessionID,在最初的 JavaWEB 开发中,一日 SessionlD 生成则不可修改,而在 Serlet3.1 标准中对这一限制进行了扩充,允许用户通过 HttpServletRequest 子接口提供的“changeSessionld()”方法获取一个新的 SessionlD,同时提供了-个 “iakarta.servlet.http.HttpSessionldListener” 接口可以对每次 SessionID 修改的状态进行监听

1、

package com.yootk.listener;

import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionIdListener;

@WebListener
public class UserSessionChange implements HttpSessionIdListener {
    @Override
    public void sessionIdChanged(HttpSessionEvent httpSessionEvent, String s) {
        System.out.println("【UserSessionChange.sessionIdChanged()】新的SessionID = " + httpSessionEvent.getSession().getId() + "、旧的SessionID = " + s + "、Session属性:" + httpSessionEvent.getSession().getAttribute("message"));
    }
}

2、
<%
	request.changeSessionId(); // 更改SessionID
%>
<h1>【Session属性】message = <%=session.getAttribute("message")%></h1>


3、
<%
   session.setAttribute("message", "沐言科技:www.yootk.com"); // 设置request属性
%>

HttpSessionAttributeListener

session 属性范围可以在一次用户请求中始终进行用户数据信息的存储,为了可以实现属性操作的监听,在 JavaWEB 编程开发中提供了"jakarta.servlet.http.HttpSessionAttributeListener” 监听接利用该接口可以实现属性设置、属性替换以及属性删除的事件监听处理

1、

package com.yootk.listener;

import jakarta.servlet.annotation.WebListener;
import jakarta.servlet.http.HttpSessionAttributeListener;
import jakarta.servlet.http.HttpSessionBindingEvent;

@WebListener
public class SessionAttributeMonitor implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent se) {    // 属性增加时触发
        System.out.println("【SessionAttributeMonitor.attributeAdded()】属性增加,属性名称:" + se.getName() + "、属性内容:" + se.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent se) { // 属性删除时触发
        System.out.println("【SessionAttributeMonitor.attributeRemoved()】属性删除,属性名称:" + se.getName() + "、属性内容:" + se.getValue());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent se) { // 属性替换时触发
        System.out.println("【SessionAttributeMonitor.attributeReplaced()】属性替换,属性名称:" + se.getName() + "、属性内容:" + se.getValue());
    }
}

2、

<%
	session.setAttribute("message", "沐言科技:www.yootk.com"); // 设置session属性
%>

3、
<%
	session.setAttribute("message", "沐言科技:www.yootk.com"); // 设置session属性
	session.removeAttribute("message"); // 删除属性
%>

HttpSessionBindingListener

项目开发中由于 session 经常需要保存有用户的数据信息,所以在 session 对象中最为重要的操作就是属性的保存与删除,除了使用 HttpSessionAttributeListener 接囗实现属性状态的跟踪外,在 JavaWEB 中又提供了一个不需要进行注册的监听接口,该接口为"jakarta.servet.http.HttpSessionBindingListener"在实际使用中,开发者只需要在进行 session 属性操作时,将属性内容设置为 HttpSessionBindingListener 子类对象实例,即可自动的进行 valueBound()与 valueUnbound()两个监听方法的调用

1、
package com.yootk.listener;

import jakarta.servlet.http.HttpSessionBindingEvent;
import jakarta.servlet.http.HttpSessionBindingListener;

public class UserAuthenticationListener implements HttpSessionBindingListener { // 是一个特殊结构的类
    private String userid; // 保存用户id
    public UserAuthenticationListener(String userid) {  // 不提供无参构造方法
        this.userid = userid; // 保存用户id信息
    }
    public String getUserid() {
        return userid;
    }

    @Override
    public void valueBound(HttpSessionBindingEvent event) { // 绑定处理
        // 在实际的项目之中,此处可以编写很多种操作代码的形式,例如:可以考虑将该数据保存在Redis里面。
        System.out.println("【UserAuthenticationListener.valueBound()】属性绑定,name = " + event.getName() + "、value = " + event.getValue());
    }

    @Override
    public void valueUnbound(HttpSessionBindingEvent event) {
        System.out.println("【UserAuthenticationListener.valueUnbound()】属性解绑,name = " + event.getName() + "、value = " + event.getValue());
    }
}


2、
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ page import="com.yootk.listener.UserAuthenticationListener" %>	<%-- 导入程序的处理类 --%>
<%@include file="/pages/plugins/include_basepath.jsp" %>
<html>
<head>
<base href="<%=basePath%>">
<jsp:include page="/pages/plugins/include_javascript.jsp" />
<script type="text/javascript" src="js/index.js"></script>
</head>
<body>
<%
	UserAuthenticationListener user = new UserAuthenticationListener("yootk.com"); // 设置用户名
	session.setAttribute("user", user); // 属性保存
	Thread.sleep(3000); // 等待3秒的时间
	session.removeAttribute("user"); // 属性删除
%>
</body>
</html>

HttpSessionActivationListener

Session 钝化与激活

为了便于保持用户的状态,就需要在 WEB 容器中存储有大量的用户 session 信息,但是随着并发访问用户的增多,此种保存模式必然会带来较大的内存占而导致频繁的 GC 操作,从而影响服务器的处理性能,为了解决这一问用,题,JavaWEB 提供了 session 的序列化管理机制,可以将暂时不活跃的而 session 信息保存到其对应的二进制文件之中(此操作称为“钝化”)后再需要的时候可以根据 SessionID 通过磁盘恢复对应的 Session 数据(此操作称为“激活”),这样就可以极大的减少服务器内存占用的问题,从而实现较高的服务器处理性能。

<Context>        <!-- 配置上下文 -->
    <!-- 配置Session持久化管理策略,当session超过1分钟不活跃时,则进行持久化处理 -->
    <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
        <!-- 定义文件存储,同时设置文件存储的路径 -->
        <Store className="org.apache.catalina.session.FileStore" directory="h:/session"/>
    </Manager>
</Context>

Session 持久化管理

在 JavaWEB 中,针对于 Session 数据持久化管理提供了一个 jakarta.servlet.http.HttpSessionActivationListener 接囗,在该接口中可以针对于 session 数据序列化(钝化方法 sessionWilPassivate()”)以及 session 数据反序列化(激活方法 sessionDidActivate()”)实现状态监听

ServeContext 监听器

简介

ServeContext 临听器简介

在 Tomcat 中会同时拥有多个不同的 WEB 上下文环境,每一个 WEB 上下文都拥有独立的应用环境,在 WEB 容器中会针对 WEB 上下文的创建、销毁、属性操作等实现相应的处理事件,以方便用户进行不同状态的监控

ServletContextListener

ServletContext 监听每一个 WEB 应用启动后都存在有一个 ServetContext 实例化对象,所以在 SerletContext 初始化完成以及 ServletContext 销毁都会产生有相应的处理事件,事件产生后可以通过 jakarta.servlet.ServletContextListener 接回实现事件的监听处理

1package com.yootk.listener;

import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class WebContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("【WebContextListener.contextInitialized()】Servlet上下文初始化:" + sce.getServletContext().getVirtualServerName());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("【WebContextListener.contextDestroyed()】Servlet上下文销毁:" + sce.getServletContext().getVirtualServerName());
    }
}

ServetContextAttributeListener

ServletContextAttributeListener

每一个 WEB 应用都可以通过 application 内置对象实现上下文属性的操作配置,如果需要对属性的操作事件进行处理,则可以直接通过 jakarta.servlet.ServletContextAttributeListener 接完成

1package com.yootk.listener;

import jakarta.servlet.ServletContextAttributeEvent;
import jakarta.servlet.ServletContextAttributeListener;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class WebAttributeListener implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent scae) {
        System.out.println("【WebAttributeListener.attributeAdded()】属性增加,属性名称:" + scae.getName() + "、属性内容:" + scae.getValue());
    }

    @Override
    public void attributeReplaced(ServletContextAttributeEvent scae) {
        System.out.println("【WebAttributeListener.attributeReplaced()】属性替换,属性名称:" + scae.getName() + "、属性内容:" + scae.getValue());
    }

    @Override
    public void attributeRemoved(ServletContextAttributeEvent scae) {
        System.out.println("【WebAttributeListener.attributeRemoved()】属性删除,属性名称:" + scae.getName() + "、属性内容:" + scae.getValue());
    }
}


2<%
	application.setAttribute("message", "沐言科技:www.yootk.com");
%>

组件动态注册

动态注册 WEB 组件

在传统的 JavaWEB 项目开发中,如果要想使用 Servet、Filter 或 Listener 都需要在 web.xml 文件中进行手工配置后才可以生效,到了 Servlet 3.0 标准后提供了基于注解的形式进行组件配置,但是依然需要开发者明确的在代码定义时进行配置,为了便于开发者灵活的维护组件的管理,又提供了更加方便的服务注册功能,开发者可以直接在容器启动时,利用反射机制实现 WEB 组件类的引入

动态注册 Servlet 组件

ServletContext 提供的 Servlet 注册方法

Servet 程序类是 JavaWEB 开发中的主要程序组件,如果开发者需要通过程序动态的进行 Servet 的注册,则可以直接使用 ServletContext 接口提供的方法。

1package com.yootk.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class HelloServlet extends HttpServlet {
    @Override   // 进行GET请求的处理
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 在doGet()方法中已经明确的给出了HttpServletRequest、HttpServletResponse接口实例
        // 这个接口实例是在每一次进行请求处理的时候,由容器负责提供的
        // 在Java中提供了两个打印流:PrintStream、PrintWriter,HTML实际上是文本,所以字符流合适
        PrintWriter out = resp.getWriter(); // 获得了客户端浏览器的输出流
        out.println("<html>");
        out.println("<head>");
        out.println("   <title>Yootk - JavaWEB</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("   <h1>www.yootk.com</h1>");
        out.println("   <h1>message = " + super.getInitParameter("message") + "</h1>");
        out.println("</body>");
        out.println("</html>");
        out.close(); // 关闭输出流
    }
}


2package com.yootk.listener;

import com.yootk.servlet.HelloServlet;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.ServletRegistration;
import jakarta.servlet.annotation.WebListener;

@WebListener
public class DynamicRegistrationListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) { // 容器初始化进行组件注册
        // 所有的组件的动态注册都是基于ServletContext接口实例完成的
        ServletContext application = sce.getServletContext(); // 获取上下文接口实例
        ServletRegistration.Dynamic registration = application.addServlet("helloServlet", HelloServlet.class); // 反射类加载
        registration.setLoadOnStartup(1); // 设置Servlet启动时的加载
        registration.setInitParameter("message", "www.yootk.com");
        registration.addMapping("/hello.action", "/muyan.yootk", "/muyan/yootk/*");
    }
}

动态注册 Filter 组件

1package com.yootk.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
public class BaseFilter extends HttpFilter { // 实现一个Http过滤器
    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("【BaseFilter.doFilter()】执行过滤处理操作...");
        chain.doFilter(request, response); // 跳转到目标处理路径
    }
}

2package com.yootk.listener;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebListener;

import java.util.EnumSet;

@WebListener
public class DynamicRegistrationListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) { // 容器初始化进行组件注册
        // 所有的组件的动态注册都是基于ServletContext接口实例完成的
        ServletContext application = sce.getServletContext(); // 获取上下文接口实例
        FilterRegistration.Dynamic filter = application.addFilter("baseFilter", "com.yootk.filter.BaseFilter");
        filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), false, "/*");
    }
}


动态注册 Listener 组件

1package com.yootk.listener;

import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
public class UserStateMonitor implements HttpSessionListener { // 针对于Session的状态实现监听

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        System.out.println("【UserStateMonitor.sessionCreated()】SessionID = " + se.getSession().getId());
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("【UserStateMonitor.sessionDestroyed()】SessionID = " + se.getSession().getId());
    }
}

2package com.yootk.listener;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebListener;

import java.util.EventListener;

@WebListener
public class DynamicRegistrationListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) { // 容器初始化进行组件注册
        // 所有的组件的动态注册都是基于ServletContext接口实例完成的
        ServletContext application = sce.getServletContext(); // 获取上下文接口实例
        try {
            EventListener listener = application.createListener(UserStateMonitor.class); // 创建一个事件监听类
            application.addListener(listener);
            // 等价操作:application.addListener("com.yootk.listener.UserStateMonitor");
        } catch (ServletException e) {
            e.printStackTrace();
        }
    }
}

ServletContainernitializer

ServetContainerlnitializer

虽然在 ServetContext 接口中提供了 WEB 组件的动态注册支持但是在使用前开发者必须自定义监听器,而后还需要进行一系列的代码配置后才能够生效,这样的做法实际上并不利于组件的动态扩展应用。在一个完整的 WEB 程序中,为了便于代码的管理,往往需要引入一系列的 Java 程序库(*.jar 文件),这些 JAR 文件中的内部可能就包含有程序所需要的 WEB 组件,而此时最佳的做法就是在项目直接引入此 JAR 文件,而后让这个 JAR 文件中的 WEB 组件自动进行配置,当不再需要使用到这些组件的时候只要从项目中移除 JAR 文件即可

在线用户管理项目实战

项目地址open in new window

demo


上次编辑于: