Servlet进阶API、监听器与过滤器

news2024/12/23 5:47:12

过滤器和监听器是Servlet规范里的两个高级特性, 过滤器的作用是通过对request、response 的修改实现特定的功能,例如请求数据字符编码、IP地址过滤、异常过滤、用户身份认证等。监听器的作用是用于监听Web程序中正在执行的程序, 根据发生的事件做出特定的响应。合理利用这两个特性, 能够轻松解决某些Web特殊问题。

Servlet进阶API

在编写完一个Servlet类后, 通常需要在web.xml中或者通过注解进行相关的配置, 这样Web 容器才能读取Servlet设置的信息, 包括其类地址、初始化等。对于每个Servlet的配置, Web都会生成与之相对应的ServletConfig对象, 从ServletConfig对象中可以得到Servlet的初始化参数。

Servlet、ServletConfig与Generic Servlet

本节将介绍ServletConfig与GenericServlet 的关系,以及如何使用ServletConfig 和ServletContext对象来获取Servlet初始化参数。
在Web容器启动后, 通过加载web.xml文件读取Servlet的配置信息, 实例化Servlet类, 并且为每个Servlet配置信息产生唯一一个ServletConfig对象。在运行Servlet时, 调用Servlet接口的init() 方法, 将产生的ServletConfig作为参数传入Servlet中, 流程如图所示。
在这里插入图片描述
正如前面所介绍的, 初始化方法只会被调用一次, 即容器在启动时实例化Servlet 和创建ServletConfig对象, 且Servlet与ServletConfig是一一对应关系, 之后就直接执行service() 方法。
从Java ServletAPI中可以得知GenericServlet类同时实现了Servlet、ServletConfig两个接口。
在这里插入图片描述
在这里插入图片描述
ServletConfig接口

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package javax.servlet;

import java.util.Enumeration;

public interface ServletConfig {
    String getServletName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}

Servlet接口

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package javax.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

GenericServlet.java

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package javax.servlet;

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

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final long serialVersionUID = 1L;
    private transient ServletConfig config;
    //无参构造函数
    public GenericServlet() {
    }
	//销毁方法
    public void destroy() {
    }
	//获取初始化参数
    public String getInitParameter(String name) {
        return this.getServletConfig().getInitParameter(name);
    }
    //获取初始化参数名称
    public Enumeration<String> getInitParameterNames() {
        return this.getServletConfig().getInitParameterNames();
    }
    //获取servlet配置
    public ServletConfig getServletConfig() {
        return this.config;
    }
    //获取servlet上下文
    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }
    //获取servlet信息
    public String getServletInfo() {
        return "";
    }
    //初始化
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }
    //初始化
    public void init() throws ServletException {
    }
    //日志
    public void log(String message) {
        this.getServletContext().log(this.getServletName() + ": " + message);
    }
    //日志
    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }
    //抽象service方法
    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    //获取servlet名称
    public String getServletName() {
        return this.config.getServletName();
    }
}

在这里插入图片描述
这个类的存在使得编写Servlet更加方便, 它提供了一个简单的方法, 这个方法用来执行有关Servlet生命周期的方法以及在初始化时对ServletConfig对象和ServletContext对象进行说明。
分析上述代码中的getServletName和init方法, 得知GenericServlet类将ServletConfig封装了。
从代码getServletConfig可知, GenericServlet类还定义了获取ServletConfig对象的方法, 当编写Servlet类时就可以通过这些方法来获取所要配置的信息, 而不用重新创建出ServletConfig对象。

使用ServletConfig

前面已经介绍过, 当容器初始化Servlet时, 会为Servlet创建唯一的ServletConfig对象。利用Web容器读取web.xml文件, 将初始化参数传给ServletConfig,而ServletConfig作为对象参数传递到init() 方法中。

public interface ServletConfig {
	//该方法返回一个Servlet实例的名称
    String getServletName();
    //返回一个ServletContext对象的引用
    ServletContext getServletContext();
    //返回一个由参数String name决定的初始化变量的值, 如果该变量不存在, 则返回null
    String getInitParameter(String var1);
    //返回一个存储所有初始化变量的枚举类型。如果Servlet没有初始化变量,则返回一个空枚举类型
    Enumeration<String> getInitParameterNames();
}

从Servlet 3.0开始, 允许以注入的方式配置Servlet,而不仅仅在web.xml中配置。因此配置Servlet的形式可以有如下形式:

@WebServlet(
        name = "servletConfigDemo",
        urlPatterns = {
                "/servletconfig"
        },
        loadOnStartup = 1,
         displayName = "demo",
        initParams = {
                @WebInitParam(name="success",value = "success.html"),
                @WebInitParam(name="error",value = "error.html")
        }
)

等价xml配置

<servlet>
    <display-name>demo</display-name>
    <servlet-name>servletConfigDemo</servlet-name>
    <servlet-class>com.wujialiang.HellServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <init-param>
      <param-name>success</param-name>
      <param-value>success.html</param-value>
    </init-param>
    <init-param>
      <param-name>error</param-name>
      <param-value>error.html</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>servletConfigDemo</servlet-name>
    <url-pattern>/servletconfig</url-pattern>
  </servlet-mapping>

上述的两个配置过程是等价的, 在Servlet 3.0及4.0中可以同时兼容, 在Servlet 2.0中只能在web.xml中配置。@WebServlet和@WebInitParam的主要属性分别参见下表。
@WebServlet的主要属性列表

属性名描述
name指定Servlet的name 属性, 等价于≷servlet-name>, 如果没有指定, 则该Servlet的取值即为类的全名
value该属性与url Patterns属性等价, 但两个属性不能同时使用
urlPatterns指定Servlet的URL匹配模式, 等价于≷url-pattern>标签
loadOnStartup指定Servlet的加载顺序, 等价于标签。当值为0或者大于0时, 表示容器在应用启动时就加载并初始化这个Servlet; 当值小于0或者没有指定时, 则表示容器在该Servlet被选择时才会去加载; 正数的值越小, 该Servlet的优先级越高, 应用启动时就越先加载;当值相同时,容器就会自己选择顺序来加载
initParams指定Servlet的初始化参数, 等价于≷init-param>标签
asyncSupported声明Servlet是否支持异步操作模式, 等价于标签, 该属性在Servlet 3.0中才有

@WebInitParam的主要属性列表

属性名描述
name指定参数的名字, 等价于≷param-name>
value指定参数的值, 等价于≷param-value>
description参数的描述, 等价于≷description>

利用初始化信息设定跳转信息

新建index.jsp

<%@page language="java" import="java.util.*" pageEncoding="utf-8" %>
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<%
String userId = (String)session.getAttribute("user");
%>
你好,<%=userId%>
</body>
</html>

新建error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<h1>
    登录失败
</h1>

新建ServletConfigDemo.java

package com.wujialiang;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
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;
import java.util.Enumeration;

@WebServlet(
        name = "servletConfigDemo",
        urlPatterns = {
                "/servletconfig"
        },
        loadOnStartup = 1,
        displayName = "demo",
        initParams = {
                @WebInitParam(name = "success", value = "index.jsp"),
                @WebInitParam(name = "error", value = "error.jsp")
        }
)
public class ServletConfigDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletConfig servletConfig = getServletConfig();
        //获取参数
        String success = servletConfig.getInitParameter("success");
        String error = servletConfig.getInitParameter("error");
        System.out.println("success:" + success);
        System.out.println("error:" + error);
        //获取全部参数
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String name = (String) initParameterNames.nextElement();
            String value = servletConfig.getInitParameter(name);
            System.out.println(name + ":" + value);
        }
        //获取servlet上下文
        ServletContext servletContext = servletConfig.getServletContext();
        System.out.println("servletContext:" + servletContext);
        //获取servlet名字
        String servletName = servletConfig.getServletName();
        System.out.println("servletName" + servletName);
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");
        String userId = req.getParameter("userid");
        String pwd = req.getParameter("pwd");
        if(userId.equals("admin")&&pwd.equals("123456")){
            HttpSession session = req.getSession();
            session.setAttribute("user",userId);
            RequestDispatcher requestDispatcher = req.getRequestDispatcher(success);
            requestDispatcher.forward(req,resp);
        }else{
            RequestDispatcher requestDispatcher = req.getRequestDispatcher(error);
            requestDispatcher.forward(req,resp);
        }
    }
}

访问http://localhost:8080/web09_war/servletconfig?userid=admin&pwd=123456
在这里插入图片描述
访问http://localhost:8080/web09_war/servletconfig?userid=admin&pwd=1234567
在这里插入图片描述

使用ServletContext

ServletContext对象是Servlet中的全局存储信息, 当服务器启动时, Web容器为Web应用创建唯一的ServletContext 对象, 应用内的Servlet 共享同一个ServletContext。可以认为在ServletContext中存放着共享数据, 应用内的Servlet可以通过ServletContext对象提供的方法获取共享数据。ServletContext对象只有在Web应用被关闭的时候才销毁。
ServletContext接口中定义了运行Servlet应用程序的环境信息, 可以用来获取请求资源的URL、设置与存储全局属性、Web应用程序初始化参数。ServletContext中的常见方法如表所示。

方法描述
getRealPath(String path)获取给定的虚拟路径所对应的真实路径名
getResource(String uri path)返回由path指定的资源路径对应的一个URL对象
getResourceAsStream(String uri path)返回一个指定位置资源的InputStream。返回的InputStream可以是任意类型和长度的。使用时指定路径必须以“/”开头,表示相对于应用程序环境根目录
getRequestDispatcher(String uri path)返回一个特定URL的RequestDispatcher对象, 否则就返回一个空值
getResourcePaths(String path)返回一个存储web-app中所指资源路径的Set(集合) , 如果是一个目录信息,会以“/”作为结尾
getServerInfo获取服务器的名字和版本号
package com.wujialiang;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;

@WebServlet(
        name = "servletcontext",
        urlPatterns = {
                "/servletcontext"
        },
        loadOnStartup = 0,
        displayName = "context",
        initParams = {
                @WebInitParam(name="dir",value = "/"),
                @WebInitParam(name = "success",value = "index.jsp"),
                @WebInitParam(name = "resourcePath",value = "test.txt"),
        }
)
public class ServletContextDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        String dir = getInitParameter("dir");
        String success = getInitParameter("success");
        String resourcePath = getInitParameter("resourcePath");
        ServletContext servletContext = getServletContext();
        //获取真实路径
        String realPath = servletContext.getRealPath(success);
        System.out.println("index.jsp真实路径为:"+realPath);
        Set<String> sets = servletContext.getResourcePaths(dir);
        for (Object set:sets
             ) {
            System.out.println("文件内容:"+(String)set);
        }
        String serveInfo =servletContext.getServerInfo();
        System.out.println("服务器版本为:"+serveInfo);
        //获取资源文件内容
        InputStream resourceAsStream = servletContext.getClassLoader().getResourceAsStream(resourcePath);
        ServletOutputStream outputStream = resp.getOutputStream();
        byte[] buffer = new byte[1024];
        while (resourceAsStream.read(buffer)!=-1){
            outputStream.write(buffer);
        }
        resourceAsStream.close();
        outputStream.close();
    }
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

以“/”作为开头时称为环境相对路径。在Servlet中, 若是环境相对路径, 则直接委托给ServletContext的get RequestDispatcher() 。

应用程序事件、监听器

何谓监听?顾名思义就是监视行为。在Web系统中, 所谓的监听器就是应用监听事件来监听请求中的行为而创建的一组类。HttpServletRequest、HttpSession、ServletContext对象在Web容器中遵循生成、运行、销毁这样的生命周期。当进行相关的监听配置后, Web容器就会调用监听器上的方法,进行对应的事件处理,从而了解运行的情况或者运行其他的程序。各监听器接口和事件类如表所示。
与ServletContext相关

监听接口监听事件
ServletContextListenerServletContextEvent
ServletContextAttributeListenerServletContextAttributeEvent

HttpSession相关

监听接口监听事件
HttpSessionIdListenerHttpSessionEven
HttpSessionListenerHttpSessionEven
HttpSessionActivationListenerHttpSessionEven
HttpSessionAttributeListenerHttpSessionBindingEvent
HttpSessionBindingListenerHttpSessionBindingEvent

ServletRequest相关

监听接口监听事件
ServletRequestListenerServletRequestEvent
ServletRequestAttributeListenerServletRequestAttributeEvent

使用监听器需要实现相应的监听接口。在触发监听事件时,应用服务器会自动调用监听方法。开发人员不需要关心应用服务器如何调用,只需要实现这些方法就行。

ServletContext事件监听器

与ServletContext 有关的监听器有两个,即ServletContextListener 与ServletContextAttributeListener。

ServletContextListener

ServletContext Listener被称为“ServletContext生命周期监听器”,可以用来监听Web程序初始化或者结束时响应的动作事件。ServletContext Listener接口的类是javax.servlet.ServletContext Listener,该接口提供两个监听方法。

  • default void contextInitialized(ServletContextEvents ce) :该方法用于通知监听器,已经加载Web应用和初始化参数。
  • default void contextDestroyed(ServletContextEvents ce) :该方法用于通知监听器, Web 应用即将关闭。

在Web应用程序启动时, 会自动开始监听, 首先调用的是contextInitialized() 方法, 并传入ServletContextEvent 参数, 它封装了ServletContext 对象, 可以通过ServletContextEvent的getServletContext() 方法取得ServletContext对象, 通过getInitParameter() 方法取得初始化参数。在Web应用关闭时, 会自动调用contextDestroyed() 方法, 同样会传入ServletContextEvent参数。在contextInitialized() 中可以实现应用程序资源的准备事件, 在contextDestroyed() 中可以实现对资源的释放。例如, 可以在contextInitialized() 方法中实现Web应用的数据库连接、读取应用程序设置等;在contextDestroyed() 中设置数据库资源的释放。
在Web中, 实现ServletContext Listener的步骤如下:

  • 首先, 编写一个监听类并实现ServletContext Listener接口
  • 进行相关的配置:
<listener>
  <listener-class>com.wujialiang.MyServletListener</listener-class>
</listener>
  • 或者利用注入的方式注入监听类:
@WebListener
public class MyServletContextListener implements ServletContextListener{
}
  • 若需要初始化参数, 则需要在web.xml中进行配置, 例如:
<context-param>
  <param-name>user_name</param-name>
  <param-value>wjl</param-value>
</context-param>

@WebListener也是Servlet 3.0才有的, 因为它没有设置初始化参数的属性, 所以也需要在web.xml中设定。

web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>WebApp</display-name>
  <context-param>
    <param-name>user_name</param-name>
    <param-value>wujialiang</param-value>
  </context-param>
</web-app>

新建MyServletContextListener.java

package com.wujialiang;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyServletContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        //获取ServletContext
        ServletContext servletContext = sce.getServletContext();
        String user_name = servletContext.getInitParameter("user_name");
        System.out.println("获取到user_name的值:" + user_name);
        System.out.println("Tomcat启动中...");
    }

    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("Tomcat正在关闭...");
    }
}

启动tomcat
在这里插入图片描述
关闭tomcat
在这里插入图片描述

ServletContextAttributeListener

ServletContextAttributeListener被称为“ServletContext属性监听器”,可以用来监听Application
属性的添加、移除或者替换时响应的动作事件。
ServletContextAttributeListener接口的类是javax.servlet.ServletContextAttributeListener,该接口
提供3个监听方法。

  • default void attributeAdded(ServletContextAttributeEvent scab):该方法用于通知监听器,有对象或者属性被添加到Application中。
  • default void attributeRemoved(ServletContextAttributeEvent scab):该方法用于通知监听器,有对象或者属性被移除到Application中。
  • default void attributeReplaced(ServletContextAttributeEvent scab):该方法用于通知监听器,有对象或者属性被更改到Application中。

在ServletContext中添加属性、移除属性或者更改属性时,与其相对应的方法就会被调用。同样,在Web应用程序中,实现ServletContextAttributeListener的方法也有两种,形式如下。

  • 利用注入的方式注入监听类:
@WebListener
public class MyServletContextAttributeListener implements ServletContextAttributeListener{
}

在web.xml中配置

<listener>
	<listener-class>
	com.wujialiang.MyServletContextAttributeListener
	</listener-class>
</listener>

HttpSession事件监听器

从刚开始的表中可以发现,与HttpSession有关的监听器有5个:HttpSessionldListener、HttpSessionListener、HttpSessionAttributeListener HttpSessionBindingListener HttpSessionActivationListener.Servlet 3.1版本开始,增加了HttpSessionldListener。.

HttpSessionldListener

HttpSessionIdListener用来监听session ID的变化。
HttpSessionldListener接口的类是javax.servlet…http.HttpSessionldListener,该接口只提供了一个监听方法。
public void sessionldChanged(HttpSessionEvent se,java.lang.String oldSessionld):该方法用于通知监听器session ID发生了改变。

请求的session ID发生变化时,会触发sessionlDChanged()方法,并传入HttpSessionEvent和oldSessionld参数,可以使用HttpSessionEvent中的getSession()-getIdO获取新的session ID,oldSessionld代表改变之前的session ID。
在Web应用程序中,实现HttpSessionldListener的方法同样有两种,形式如下。

  • 利用注入的方式注入监听类:
@WebListener
public class MyHttpSessionldListener implements HttpSessionIdListener{
}
  • 在web.xml中配置:
<listener>
	<listener-class>
	com.wujialiang.MyHttpSessionldListener
	</listener-class>
</listener>

HttpSessionListener

HttpSessionListener是“HttpSession生命周期监听器”,可以用来监听HttpSession对象初始化或者结束时响应的动作事件。
HttpSessionListener接口的类是javax.servlet…http.HttpSessionListener,该接口提供两个监听方法。
default void sessionCreated(HttpSessionEvent se):该方法用于通知监听器,产生了新的会话。
default void sessionDestroyed(HttpSessionEvent se):该方法用于通知监听器,已经消除一个会话。

在HttpSession对象初始化或者结束前,会自动调用sessionCreated(方法和sessionDestroyed()方法,并传入HttpSessionEvent参数,它封装了HttpSession对象,可以通过HttpSessionEvent的getSession()方法取得HttpSession对象。在Web应用程序中,实现HttpSessionListener的方法同样有两种,形式如下。

  • 注入
@WebListener
public class MyHttpSessionListener implements HttpSessionListener{
}
  • web.xml
<listener>
	<listener-class>
	com.wujialiang.MyHttpSessionListener
	</listener-class>
</listener>

新建MyHttpSessionListener.java

package com.wujialiang;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

@WebListener
public class MyHttpSessionListener implements HttpSessionListener {
    private static int count=0;

    public static  int getCount(){
        return count;
    }

    public void sessionCreated(HttpSessionEvent se) {
        MyHttpSessionListener.count++;
        System.out.println("session增加");
    }

    public void sessionDestroyed(HttpSessionEvent se) {
        MyHttpSessionListener.count--;
        System.out.println("session减少");
    }
}

新建LoginServlet.java

package com.wujialiang;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@WebServlet(name = "login",
        loadOnStartup = 1,
        urlPatterns = {
        "/login",
},initParams = {
        @WebInitParam(name = "success",value = "success.jsp")
})
public class LoginServlet extends HttpServlet {
     Map<String,String> users;

     public LoginServlet(){
         users = new HashMap<>();
         users.put("wjl001","123456");
         users.put("wjl002","123456");
         users.put("wjl003","123456");
     }

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        System.out.println("进入LoginServlet");
        String userId = req.getParameter("userid");
        String pwd = req.getParameter("pwd");
        if(users.containsKey(userId)&&users.get(userId).equals(pwd)){
            req.getSession().setAttribute("user",userId);
            req.getSession().setAttribute("count",MyHttpSessionListener.getCount());
        }
        String success =getInitParameter("success");
        resp.sendRedirect(success);
    }
}

新建success.jsp

<%@ page import="java.util.*" contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录成功</title>
</head>
<body>
<h3>目前在线人数:<%=session.getAttribute("count")%></h3>
<p>欢迎您:<%=session.getAttribute("user")%></p>
</body>
</html>

访问http://localhost:8080/web01_war/login?userid=wjl001&pwd=123456
在这里插入图片描述
在这里插入图片描述

HttpSessionAttributeListener

HttpSessionAttributeListener是“HttpSession属性改变监听器”,可以用来监听HttpSession对象加入属性、移除属性或者替换属性时响应的动作事件。
HttpSessionAttributeListener接口的类是javax.servlet…htp.HttpSessionAttributeListener,.该接口提供3个监听方法。

  • default void attributeAdded(HttpSessionBindingEvent se):该方法用于通知监听器,已经在Session中添加一个对象或者变量。
  • default void attributeRemoved(HttpSessionBindingEvent se):该方法用于通知监听器,已经在Session中移除一个对象或者变量。
  • default void attributeReplaced(HttpSessionBindingEvent se):该方法用于通知监听器,已经在Session中替换一个对象或者变量。

当对session范围的对象或者变量进行操作时,Web容器会自动调用与实现接口类相对应的方法。HttpSessionBindingEvent是一个对象,可以利用其getName()方法得到操作对象或者变量的名称,利用getValue()方法得到操作对象或者变量的值。

在Web应用程序中,实现HttpSessionAttributeListener的方法同样有两种,形式如下。

  • 注入方式
@WebListener
public class MyHttpSessionAttributeListener implements HttpSessionAttributeListener{
}
  • web.xml中配置
<listener>
	<listener-class>
	com.wujialiang.MyHttpSessionAttributeListener
	</listener-class>
</listener>

HttpSessionBindingListener

HttpSessionBindingListener是“HttpSession对象绑定监听器”,可以用来监听HttpSession中设置成HttpSession属性或者从HttpSession中移除时得到session的通知。
HttpSessionBindingListener接口的类是javax.servlet.http.HttpSessionBindingListener,该接口提供两个监听方法。

  • default void valueBound(HttpSessionBindingEvent event):该方法用于通知监听器,已经绑定一个session范围的对象或者变量。
  • default void valueUnbound(HttpSessionBindingEvent event):该方法用于通知监听器,已经解绑一个session范围的对象或者变量。

参数HttpSessionBindingEvent是一个对象,可以通过getSession()方法得到当前用户的session,通过getName()方法得到操作的对象或者变量名称,通过getValue()方法得到操作的对象或者变量值。

在Web应用程序中实现HttpSessionBindingListener接口时,不需要注入或者在web.xml中配置,只需将设置成session范围的属性实现HttpSessionBindingListener接口就行。

HttpSessionActivationListener

HttpSessionActivationListener是“HttpSession对象转移监听器”,可以用来实现它对同一会话在不同的JVM中转移,例如,在负载均衡中,Wb的集群服务器中的JVM位于网络中的多台机器中。当session要从一个JVM转移至另一个JVM时,必须先在原来的JVM上序列化所有的属性对象,若属性对象实现HttpSessionActivationListener,就调用sessionWillPassivate()方法,而转移后,就会调用sessionDidActivate(O方法。

HttpSessionActivationListener接口的类是javax.servlet…http.HttpSessionActivationListener,该接口提供两个监听方法。

  • default void sessionDidActivate(HttpSessionEvent se):该方法用于通知监听器,该会话已变为有效状态。
  • default void session WillPassivate(HttpSessionEvent se):该方法用于通知监听器,该会话已变为无效状态。

HttpServletRequest事件监听器

与HttpServletRequest有关的监听器有两个:ServletRequestListener、ServletRequestAttributeListener.

ServletRequestListener

ServletRequestListener是“Request生命周期监听器”,可以用来监听Reuqest对象初始化或者结束时响应的动作事件。
ServletRequestListener接口的类是javax…servlet…ServletRequestListener,该接口提供两个监听方法。

  • default void requestInitialized(ServletRequestEvent sre):该方法用于通知监听器,产生了新的request对象。
  • default void requestDestroyed(ServletRequestEvent sre):该方法用于通知监听器,已经消除一个request对象。

在request对象初始化或者结束前,会自动调用requestInitialized()方法和requestDestroyed()方法,并传入ServletRequestEvent参数,它封装了ServletRequest对象,可以通过ServletRequestEvent的getServletContext()方法取得Servlet上下文对象,通过getServletRequest()方法得到请求对象。
在Web应用程序中,实现ServletRequestListener的方法有两种,形式如下。

  • 注入方式监听
@WebListener
public class MyServletRequestListener implements ServletRequestListener{
}
  • web.xml配置
<listener>
	<listener-class>
	com.wujialiang.MyServletRequestListener
	</listener-class>
</listener>

ServletRequestAttributeListener

ServletRequestAttributeListener是“Request属性改变监听器”,可以用来监听Request对象加入属性、移除属性或者替换属性时响应的动作事件。
ServletRequestAttributeListener接口的类是javax.servlet…http.ServletRequestAttributeListener,该接口提供3个监听方法。

  • default void attributeAdded(ServletRequestAttributeEvent srae):该方法用于通知监听器,已经在Request中添加一个对象或者变量。
  • default void attributeRemoved(ServletRequestAttributeEvent srae):该方法用于通知监听器,已经在Request中移除一个对象或者变量。
  • default void attributeReplaced(ServletRequestAttributeEvent srae):该方法用于通知监听器,已经在Request中替换一个对象或者变量。
    当对request范围的对象或者变量进行操作时,Web容器会自动调用与实现接口类相对应的方法。ServletRequestAttributeEvent是一个对象,可以利用其getName()方法得到操作对象或者变量的名称,利用get Value()方法得到操作对象或者变量的值。
    在Web应用程序中,实现ServletRequestAttributeListener的方法同样有两种,形式如下。
  • 注入方式监听
@WebListener
public class MyServletRequestAttributeListener implements HttpSessionAttributeListener{
}
  • web.xml配置
<listener>
	<listener-class>
	com.wujialiang.MyServletRequestAttributeListener
	</listener-class>
</listener>

maven引入两个包

<dependency>
  <groupId>jstl</groupId>
  <artifactId>jstl</artifactId>
  <version>1.2</version>
</dependency>
<dependency>
  <groupId>taglibs</groupId>
  <artifactId>standard</artifactId>
  <version>1.1.2</version>
</dependency>

MyRequestListener .java

package com.wujialiang;

import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class MyRequestListener implements ServletRequestListener, ServletRequestAttributeListener {

    public void requestInitialized(ServletRequestEvent sre) {
        System.out.println("产生一个新的请求");
    }

    public void requestDestroyed(ServletRequestEvent sre) {
        System.out.println("一个请求消亡了");
    }

    public void attributeAdded(ServletRequestAttributeEvent srae) {
        System.out.println("新增一个request属性,名称为:" + srae.getName() + ",其值为:" + srae.getValue());
    }

    public void attributeRemoved(ServletRequestAttributeEvent srae) {
        System.out.println("移除一个request属性,名称为:" + srae.getName());
    }

    public void attributeReplaced(ServletRequestAttributeEvent srae) {
        System.out.println("修改一个request属性,名称为:" + srae.getName());
        System.out.println("修改前的值为:" + srae.getValue());
    }
}

index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="utf-8" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
    String path = request.getContextPath();
%>
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<p>使用requestlistener监听器</p>
<c:set value="zhangsan" var="username" scope="request"></c:set>
<h1>姓名为:<c:out value="${requestScope.username}"></c:out></h1>
<c:remove var="username" scope="request"></c:remove>
</body>
</html>

在这里插入图片描述
在这里插入图片描述

过滤器

过滤器概念

何为过滤器?顾名思义,它的作用就是阻挡某些事件的发生。在Wb应用程序中,过滤器是介于Servlet之前,既可以拦截、过滤浏览器的请求,也可以改变对浏览器的响应。它在服务器端与客户端起到了一个中间组件的作用,对二者之间的数据信息进行过滤,其处理过程如下图所示。
由下图可以看出,当客户端浏览器发起一个请求时,服务器端的过滤器将检查请求数据中的内容,它可改变这些内容或者重新设置报头信息,再转发给服务器上被请求的目标资源,处理完毕后再向客户端响应处理结果。
在这里插入图片描述
个Wb应用程序,可以有多个过滤器,组成一个过滤器链,如经常使用过滤器完成字符编码的设定和验证用户的合法性。过滤器链中的每个过滤器都各司其职地处理并转发数据。

一般而言,在Wb开发中,经常利用过滤器来实现如下功能:

  • 对用户请求进行身份认证。
  • 对用户发送的数据进行过滤或者替换。
  • 转换图像的数据格式。
  • 数据压缩。
  • 数据加密。
  • XML数据的转换。
  • 修改请求数据的字符集。
  • 日志记录和审核。

实现与设置过滤器

在Servlet中要实现过滤器,必须实现Filter接口,并用注入的方式或者在web.xml中定义过滤器,让Web容器知道应该加载哪些过滤器。

Filter接口

Filter接口的类是javax.servlet.Filter,该接口有3个方法。

  • default void init(FilterConfig filterConfig):该方法用来初始化过滤器。filterConfig参数是一个FilterConfig对象。利用该对象可以得到过滤器中初始化的配置参数信息。
  • public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain):该方法是过滤器中主要实现过滤的方法。当客户端请求目标资源时,Wb应用程序会调用与此目标资源相关的doFilterO方法,在该方法中,实现对请求和响应的数据处理。参数request表示客户端的请求,response表示对应请求的响应,chain是过滤器链对象。在该方法中的特定操作完成后,可调用FilterChain对象的doFilter(request,…response)将请求传给过滤器链中的下一个过滤器,也可以直接返回响应内容,还可以将目标重定向。
  • default void destroy():该方法用于释放过滤器中使用的资源。

FilterConfig接口

过滤器中还有FilterConfig接口,该接口用于在过滤器初始化时由Web容器向过滤器传送初始化配置参数,并传入到过滤器对象的initO方法中。FilterConfig接口中有4个方法可以调用。

  • public String getFilterName():用于得到过滤器的名字。
  • public String getlnitParameter(String name):得到过滤器中初始化的参数值。
  • public Enumeration<String>getInitParameterNames(:得到过滤器配置中的所有初始化参数名字的枚举类型。
  • public ServletContext getServletContext():得到Servlet上下文文件对象。

设置过滤器

若想实现过滤器,有两种方法:注入或者在web.xml中配置,其形式如下。

  • 注入方式
@WebFilter(
description ="demo",
filterName "myfilter",
servletNames ="*.do",
urlPatterns ={"/*"}
initParams ={
	@WebInitParam(name ="param",value ="paramvalue")
},
dispatcherTypes={DispatcherType.REQUEST}
  • web.xml方式
<filter>
	<description>demo</description>
	<!--过滤器名称-->
	<filter-name>myfilter</filter-name>
	<!--过滤器类-->
	<filter-class>com.eshore.MyFilter</filter-class>
	<!--过滤器初始化参数-->
	<init-param>
		<param-name>param</param-name>
		<param-value>paramvalue</param-value>
	</init-param>
</filter>
<!--过滤器映射配置-->
<filter-mapping>
	<filter-name>myfilter</filter-name>
	<servlet-name>*.do</servlet-name>
	<url-pattern>/*</url-pattern>
	<dispatcher>REQUEST</dispatcher>
</filter-mapping>

上述的两个配置过程是等价的,在Servlet3.0及以后的版本中可以同时兼容,在Servlet2.0中只能在web.xml中配置。
@WebFilter的主要属性如下表所示。

属性名描述
value该属性与urlPatterns属性等价,但两个属性不能同时使用
urlPatterns指定Filter的URL匹配模式,等价于<url-pattern>标签
filterName指定Filter的name属性,等价于<filter-name>标签
servletNames指定Filter的Servlet过滤对象,等价于<servlet-name>标签。当与urlPatterns同时存在时,则Web容器先比对urlPatterns中的URL,再比对servletNames中的配置
dispatcherTypes指定Filter的过滤时间,等价于<dispatcher>标签,其值有FORWARD、NCLUDE、REQUEST、ERROR、ASYNC等,默认值是REQUEST
asyncSupported声明Servlet是否支持异步操作模式,等价于<async-supported>标签,该属性在Servlet3.0及以后的版本中才有
initParams设置过滤器的初始参数

请求封装器

请求封装器是指利用HttpServletRequestWrapper类将请求中的内容进行统一修改,例如修改请求字符编码、替换字符、权限验证等。
新建EncodingFilter.java

package com.wujialiang;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(
        description = "字符串编码过滤器",
        filterName = "encodingFilter",
        urlPatterns = {"/*"},
        initParams = {
                @WebInitParam(name = "ENCODING", value = "utf-8")
        }
)
public class EncodingFilter implements Filter {
    private String encodingName = "";
    private String filterName = "";

    public void init(FilterConfig filterConfig) throws ServletException {
        //通过filterconfig获取初始化的值
        encodingName = filterConfig.getInitParameter("ENCODING");
        filterName = filterConfig.getFilterName();
        if (encodingName == "" || "".equals(encodingName)) {
            encodingName = "utf-8";
        }
        System.out.println("获取编码值");
    }


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
        //转换
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //分辨对请求和响应做编码设置
        request.setCharacterEncoding(encodingName);
        response.setCharacterEncoding(encodingName);
        response.setContentType("text/html;charset="+encodingName);
        System.out.println("请求被" + filterName + "过滤");
        filterChain.doFilter(request, response);
        System.out.println("响应被" + filterName + "过滤");
    }

    public void destroy() {
        System.out.println("过滤器销毁");
    }
}

上面注解配置相当于下面的xml配置

<filter>
  <description>字符串编码过滤器</description>
  <filter-name>encodingFilter</filter-name>
  <filter-class>com.wujialiang.EncodingFilter</filter-class>
  <init-param>
    <param-name>ENCODING</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-lname>encodingFilter</filter-lname>
  <url-pattern>/*</url-pattern>
</filter-mapping>

上述编码过滤器对于post请求是没有问题的,但是对于gt请求获取中文参数时还是会出现乱码问题。这是因为利用post方式请求时,参数是在请求数据包的消息体中;而对于gt请求,参数存放在请求数据包的URI字段中。“request.setCharacterEncoding(encoding);”只对消息体中的数据起作用,对URI字段中的参数不起作用。基于这种情况,可到请求包装器包装请求,将字符编码转换的工作添加到getParameter(方法中,这样就可以对请求的参数进行统一转换。
新建requestEncodingWrapper.java

package com.wujialiang;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.nio.charset.StandardCharsets;

public class requestEncodingWrapper extends HttpServletRequestWrapper {
    private String encoding = "";

    public requestEncodingWrapper(HttpServletRequest request) {
        super(request);
    }

    public requestEncodingWrapper(HttpServletRequest request, String encoding) {
        super(request);
        this.encoding = encoding;
    }

    public String getParameter(String name){
        String value = getRequest().getParameter(name);
        try{
            if(value!=null&&!"".equals(value)){
                value = new String(value.trim().getBytes(StandardCharsets.ISO_8859_1),encoding);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return value;
    }
}

修改EncodingFilter过滤器

package com.wujialiang;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(
        description = "字符串编码过滤器",
        filterName = "encodingFilter",
        urlPatterns = {"/*"},
        initParams = {
                @WebInitParam(name = "ENCODING", value = "utf-8")
        }
)
public class EncodingFilter implements Filter {
    private String encodingName = "";
    private String filterName = "";

    public void init(FilterConfig filterConfig) throws ServletException {
        //通过filterconfig获取初始化的值
        encodingName = filterConfig.getInitParameter("ENCODING");
        filterName = filterConfig.getFilterName();
        if (encodingName == "" || "".equals(encodingName)) {
            encodingName = "utf-8";
        }
        System.out.println("获取编码值");
    }


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain) throws IOException, ServletException {
        //转换
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        //分辨对请求和响应做编码设置
        if (request.getMethod().equals("GET")) {
            request = new requestEncodingWrapper(request, encodingName);
        } else {
            request.setCharacterEncoding(encodingName);
        }
        response.setCharacterEncoding(encodingName);
        response.setContentType("text/html;charset=" + encodingName);
        System.out.println("请求被" + filterName + "过滤");
        filterChain.doFilter(request, response);
        System.out.println("响应被" + filterName + "过滤");
    }

    public void destroy() {
        System.out.println("过滤器销毁");
    }
}

新建Test01Servlet.java

package com.wujialiang;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "test01", urlPatterns = {
        "/test01"
})
public class Test01Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String name = req.getParameter("name");
        System.out.println("name的值为:" + name);
        PrintWriter writer = resp.getWriter();
        writer.println("你好");
    }
}

访问http://localhost:8080/web02/test01?name=%E5%B0%8F%E6%98%8E
在这里插入图片描述
在这里插入图片描述
加上过滤器之后访问
在这里插入图片描述
在这里插入图片描述
默认应该是utf-8编码所以不用修改

响应封装器

响应封装器是指利用HttpServletResponse Wrapper类将响应中的内容进行统一修改,例如压缩输出内容、替换输出内容等。有些时候需要对网站的输出内容进行控制,一般有两种方法:一是在保存数据库前对不合法的内容进行替换:二是在输出端进行替换。若是对每一个Servlet都进行输出控制,则任务量将非常大而且烦琐。可利用过滤器对Servlet进行统一处理,但是因为HttpServletResponse不能缓存输出内容,所以需要自定义一个具备缓存功能的response。下面通过两个例子说明响应封装器的实现。

字符替换

修改pom/.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>web02</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>web02 Maven Webapp</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-servlet-api</artifactId>
            <version>9.0.74</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>web02</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>17</source>
                        <target>17</target>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

新建Test02Servlet

package com.wujialiang;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(
        name = "test02",
        urlPatterns = {
                "/test02"
        }
)
public class Test02Servlet extends HttpServlet {

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;utf-8;");
        PrintWriter writer = resp.getWriter();
        writer.println("<p>色情</p>");
        writer.println("<p>情色</p>");
        writer.println("<p>赌博</p>");
        writer.flush();
        writer.close();
    }
}

在这里插入图片描述
新增ResponseReplaceWrapper

package com.wujialiang;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.CharArrayWriter;
import java.io.PrintWriter;

public class ResponseReplaceWrapper extends HttpServletResponseWrapper {

    private CharArrayWriter charArrayWriter = new CharArrayWriter();

    public ResponseReplaceWrapper(HttpServletResponse response) {
        super(response);
    }

    public PrintWriter getWriter(){
        return new PrintWriter(charArrayWriter);
    }

    public CharArrayWriter getCharArrayWriter(){
        return charArrayWriter;
    }
}

resources下新建replace.properties

\u8272\u60c5=****
\u60c5\u8272=****
\u8d4c\u535a=****

新建ReplaceFilter.java

package com.wujialiang;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Properties;

@WebFilter(
        description = "内容替换过滤器",
        filterName = "replaceFilter",
        urlPatterns = {
                "/*"
        },
        initParams = {
                @WebInitParam(name = "filePath", value = "replace.properties")
        }
)
public class ReplaceFilter implements Filter {

    private Properties propert = new Properties();

    public void init(FilterConfig filterConfig) throws ServletException {
        String filePath = filterConfig.getInitParameter("filePath");
        try{
            InputStream resourceAsStream = ReplaceFilter.class.getClassLoader().getResourceAsStream(filePath);
            propert.load(resourceAsStream);
        }catch(Exception e){
            e.printStackTrace();
        }
    }


    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse res  = (HttpServletResponse)servletResponse;
        ResponseReplaceWrapper responseReplaceWrapper = new ResponseReplaceWrapper(res);
        filterChain.doFilter(servletRequest,responseReplaceWrapper);
        String outString =responseReplaceWrapper.getCharArrayWriter().toString();
        for (Object o :propert.keySet()){
            String key =(String)o;
            outString = outString.replace(key,propert.getProperty(key));
        }
        PrintWriter writer = res.getWriter();
        writer.write(outString);
    }


    public void destroy() {
    }
}

开启过滤器之后
在这里插入图片描述

gzip压缩

首先,编写一个自定义的ServletOutputStream类使它具有压缩功能,这里的压缩功能采用GZIP算法实现,这是现在主流浏览器都可以接受的压缩格式,应用JDK自带的GZIPOutputStream类来完成,其源代码如下:

package com.wujialiang;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPOutputStream;

public class GZIPResponseStream extends ServletOutputStream {
    //将压缩后的数据存放到ByteArratOutputStream
    protected ByteArrayOutputStream byteArrayOutputStream = null;
    //JDK自带的GZIP压缩类
    protected GZIPOutputStream gzipOutputStream = null;
    protected boolean closed = false;
    protected HttpServletResponse response = null;
    protected ServletOutputStream outputStream = null;

    public GZIPResponseStream(HttpServletResponse response) throws IOException {
        super();
        this.response = response;
        this.closed = false;
        this.outputStream = response.getOutputStream();
        byteArrayOutputStream = new ByteArrayOutputStream();
        gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream);
    }

    @Override
    public void close() throws IOException {
        if (this.closed) {
            throw new IOException("This output stream has already been closed");
        }
        //执行压缩必须调用这个方法
        gzipOutputStream.finish();
        //将压缩后的数据输出到浏览器中
        byte[] bytes = byteArrayOutputStream.toByteArray();
        //设置压缩算法为gzip,浏览器会自动解压数据
        response.addHeader("Content-Length", Integer.toString(bytes.length));
        response.addHeader("Content-Encoding", "gzip");
        //输出到浏览器
        outputStream.write(bytes);
        outputStream.flush();
        closed = true;
    }

    @Override
    public void flush() throws IOException {
        if (closed) {
            throw new IOException("This output stream has already been closed");
        }
        gzipOutputStream.flush();
    }

    @Override
    public boolean isReady() {
        return true;
    }

    @Override
    public void setWriteListener(WriteListener writeListener) {

    }

    @Override
    public void write(int b) throws IOException {
        if (closed) {
            throw new IOException("This output stream has already been closed");
        }
        gzipOutputStream.write((byte) b);
    }

    @Override
    public void write(byte[] b) throws IOException {
        write(b, 0, b.length);
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (closed) {
            throw new IOException("This output stream has already been closed");
        }
        gzipOutputStream.write(b, off, len);
    }

    public boolean closed() {
        return this.closed;
    }
}

然后,自定义response包装类GZIPResponse Wrapper,它只对输出的内容进行压缩,不进行将内容输出到客户端的操作。因为response要处理的不单单是字符内容,还有压缩的内容,即二进制内容,所以它需要重写getOutputStream(和getWriter()方法,其源代码如下:

package com.wujialiang;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class GZIPResponseWrapper extends HttpServletResponseWrapper {
    //原始response
    private HttpServletResponse response = null;
    //自定义outputstream对数据进行压缩并且输出
    private ServletOutputStream outputStream = null;
    //自定义printwriter,将内容输出到servletouputstream
    private PrintWriter printWriter = null;

    public GZIPResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    /**
     * 利用gzipresponsestream创建输出流
     *
     * @return
     * @throws IOException
     */
    public ServletOutputStream createOutputStream() throws IOException {
        GZIPResponseStream gzipResponseStream = new GZIPResponseStream(response);
        return gzipResponseStream;
    }

    /**
     * 利用这个方法对数据进行gzip压缩,并输出到浏览器中
     */
    public void finishResponse() {
        try {
            if (printWriter != null) {
                printWriter.close();
            } else {
                if (outputStream != null) {
                    outputStream.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void flushBuffer() throws IOException {
        outputStream.flush();
    }

    public ServletOutputStream getOutputStream() throws IOException {
        if (printWriter != null) {
            throw new IOException("getWriter has been called");
        }
        if (outputStream == null) {
            outputStream = createOutputStream();
        }
        return outputStream;
    }

    public PrintWriter getWriter() throws IOException {
        if (printWriter != null) {
            return printWriter;
        }
        if (outputStream != null) {
            throw new RuntimeException("getOutputStream has already called.");
        }
        outputStream = createOutputStream();
        //通过outputstream获取printWriter方法
        printWriter = new PrintWriter(new OutputStreamWriter(outputStream));
        return printWriter;
    }

    /**
     * 压缩后数据长度有变化,所以不用重写该方法
     * @param length
     */
    public void setContentLent(int length){

    }
}

接着,编写压缩过滤器类GZIPFilter,过滤器类中通过检查Accept–Encoding标头是否包含gzip字符来判断浏览器是否支持GZIP压缩算法,如果支持,则进行GZIP压缩数据,否则直接输出,其源代码如下:

package com.wujialiang;


import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Locale;

@WebFilter(
        description = "内容替换过滤器",
        filterName = "gzipFilter",
        urlPatterns = {
                "/*"
        }
)
public class GZIPFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("GZIPFilter初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            //根据浏览器header信息判断支持的编码格式
            String ae = request.getHeader("Accept-Encoding");
            if(ae!=null&&ae.toLowerCase().indexOf("gzip")!=-1){
                GZIPResponseWrapper gzipResponseWrapper = new GZIPResponseWrapper(response);
                filterChain.doFilter(request,response);
                gzipResponseWrapper.finishResponse();
                return;
            }
            filterChain.doFilter(request,response);
        }
    }

    @Override
    public void destroy() {
    }
}

最后,编写一个测试的GzipServlet测试压缩结果。其源代码如下:

package com.wujialiang;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;

@WebServlet(
        name = "gizServlet",
        loadOnStartup = 0,
        urlPatterns = {
                "/gzip"
        }
)
public class GZIPServlet extends HttpServlet {

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8;");
        resp.setCharacterEncoding("utf-8");
        PrintWriter out = resp.getWriter();
        String[] urls = {
                "http://localhost:8080/web02/index.jsp",
                "http://localhost:8080/web02/index.css",
                "http://localhost:8080/web02/aa.png"
        };
        for (String url : urls) {
            out.println("<p>");
            out.println("网址:" + url);
            //模拟一个浏览器
            URLConnection connGZip = new URL(url).openConnection();
            //模拟实质浏览器的表头信息支持GZIP压缩格式
            connGZip.setRequestProperty("Accept-Encoding", "gzip");
            int lengthGZip = connGZip.getContentLength();
            //模拟另一个浏览器
            URLConnection connCommon = new URL(url).openConnection();
            int lengthCommon = connCommon.getContentLength();
            //计算压缩比率
            double rate = new Double(lengthGZip) / lengthCommon;
            out.println("压缩前数据:" + lengthCommon);
            out.println("压缩后数据:" + lengthGZip);
            out.println("压缩比率:" + rate);
            out.println("</p>");
        }
        out.close();
    }
}

异步处理

在Servlet2.0中,一个普通的Servlet工作流程大致是:首先,Servlet接收请求,对数据进行处理:然后,调用业务接口方法,完成业务处理;最后,将结果返回到客户端。在Servlet中最耗时的是第二步的业务处理,因为它会执行一些数据库操作或者其他的跨网络调用等,在处理业务的过程中,该线程占用的资源不会被释放,这有可能造成性能的瓶颈。

异步处理是Servlet3.0以后新增的一个特性,它可以先释放容器被占用的资源,将请求交给另一个异步线程来执行,业务方法执行完成后再生成响应数据。
本节将讲述异步处理接口AsyncContext的使用和一个异步处理应用实例的实现。

AsyncContext简介

在Servlet3.0中,ServletRequest提供了两个方法来启动AsyncContext:

  • AsyncContext startAsync()
  • AsyncContext startAsync(ServletRequest servletRequest,ServletResponse servletResponse)

上述两个方法都能得到AsyncContext接口的实现对象。当一个Servlet调用了startAsync()方法之后,该Servlet的响应就会被延迟,并释放容器分配的线程。AsyncContext接口的主要方法如下表所示。

方法描述
void addListener(AsyncListener listener)添加AsyncListener监听器
complete()响应完成
dispatch()指定URL进行响应完成
getRequest()获取Servlet请求对象
getResponse()获取Servlet响应对象
setTimeout(long timeout)设置超时时间
start(java.lang.Runnable run)异步启动线程

在Servlet3.0中,有两种方式支持异步处理:注入声明和web.xml。其形式分别如下所示

  • 注入声明
@WebServlet(
	asyncSupported-true,
	urlPatterns={"/asyncdemo.do"},
	name="myAsyncServlet"
}
public class MyAsyncServlet extends HttpServlet{
}
  • web.xml中配置
<servlet>
	<servlet-name>myAsyncServlet</servlet-name>
	<!--异步Servlet类路径-->
	<servlet-class>com.eshore.MyAsyncServlet</servlet-class>
	<!--异步支持属性-->
	<async-supported>true</async-supported>
</servlet>

如果支持异步处理的Servlet前面有Filter,则Filter也需要支持异步处理。

异步处理的的servlet

package com.wujialiang;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executor;

@WebServlet(
        asyncSupported = true,
        urlPatterns = {
                "/asyncdemo"
        },
        name = "myasyncservlet"
)
public class MyAsyncServlet extends HttpServlet {
    SimpleDateFormat sdf = new SimpleDateFormat();

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        out.println("开始时间" + sdf.format(new Date()));
        out.flush();
        //在子线程中执行作业调度,并由其负责输出响应,主线程退出
        AsyncContext asyncContext = req.startAsync(req, resp);
        asyncContext.setTimeout(900000000);
        new Thread(new Executor(asyncContext)).start();
        out.println("结束时间:" + sdf.format(new Date()));
        out.flush();
    }

    public class Executor implements Runnable {
        private AsyncContext ctx = null;

        public Executor(AsyncContext ctx) {
            this.ctx = ctx;
        }


        @Override
        public void run() {
            try {
                //等待20秒钟,模拟业务方法执行
                Thread.sleep(20*1000);
                PrintWriter writer = ctx.getResponse().getWriter();
                writer.println("业务处理完毕时间:" + sdf.format(new Date()));
                writer.flush();
                ctx.complete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行时如果报is not surpported错误,可能是因为没有将所有的过滤器或者经过的Servlet都设置成支持异步处理
在这里插入图片描述
在这里插入图片描述

模拟服务器推送

模拟服务器推送是指模拟由服务器端向客户端推送消息。在HTTP协议中,服务器是无法直接对客户端传送消息的,必须得有一个请求服务器端才能够响应。可以利用Servlet3.0以后的异步处理技术,主动推送消息到客户端。下面以一个例子说明这种技术的实现过程。

首先,编写一个负责存储消息的队列类ClientService,该类的作用是利用Queue添加异步所有的AsyncContext对象,利用BlockingQueue阻塞队列存储页面请求的消息队列,当Queue队列中有数据时,启动一个线程,将BlockingQueue阻塞的内容输出到页面中。ClientService的源代码如下:

package com.wujialiang;

import javax.servlet.AsyncContext;
import java.io.PrintWriter;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.LinkedBlockingQueue;

public class ClientService {
    //异步Servlet上下文队列
    private final Queue<AsyncContext> ASYNC_QUEUE = new ConcurrentLinkedDeque<>();
    //消息队列
    private final BlockingQueue<String> INFO_QUEUE = new LinkedBlockingQueue<>();

    private ClientService() {
        new ClientThread().start();
    }

    private static ClientService instance = new ClientService();

    public static ClientService getInstance() {
        return instance;
    }

    /**
     * 添加异步servlet上下文
     *
     * @param asyncContext
     */
    public void addAsyncContext(final AsyncContext asyncContext) {
        ASYNC_QUEUE.add(asyncContext);
    }

    /**
     * 添加异步servlet上下文
     *
     * @param asyncContext
     */
    public void removeAsyncContext(final AsyncContext asyncContext) {
        ASYNC_QUEUE.remove(asyncContext);
    }

    /**
     * 发送消息到异步线程,最终输出到httpresponse流
     *
     * @param str
     */
    public void callClient(final String str) {
        try {
            INFO_QUEUE.put(str);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 将数据发送到response流上
     */
    protected class ClientThread extends Thread {
        public void run() {
            boolean done = false;
            while (!done) {
                try {
                    //当消息队列中有数据时,取出数据
                    final String script = INFO_QUEUE.take();

                    for (AsyncContext ac : ASYNC_QUEUE) {
                        try {
                            PrintWriter writer = ac.getResponse().getWriter();
                            writer.println(script);
                            writer.flush();
                        } catch (Exception e) {
                            ASYNC_QUEUE.remove(ac);
                        }
                    }
                } catch (Exception e) {
                    done = true;
                    throw new RuntimeException(e);

                }
            }
        }
    }
}

其次,编写异步的Servlet类,该类的作用是将客户端注册到发送消息的监听队列中,当产生超时、错误等事件时,将异步上下文对象从队列中移除。同时当访问该Servlet的客户端时,在ASYNC_QUEUE中注册一个AsyncContext对象,这样当服务端需要调用客户端时,就会输出AsyncContext内容到客户端

package com.wujialiang;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(
        name = "asyncontext",
        urlPatterns = {
                "/asynccontextservlet"
        },
        asyncSupported = true
)
public class AsyncContextServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html;charset=utf-8");
        final AsyncContext asyncContext = req.startAsync();
        //设置asynccontext对象超时时间
        asyncContext.setTimeout(10000000);
        asyncContext.addListener(new AsyncListener() {//添加异步监听器
            @Override
            public void onComplete(AsyncEvent asyncEvent) throws IOException {
                ClientService.getInstance().removeAsyncContext(asyncContext);
            }

            @Override
            public void onTimeout(AsyncEvent asyncEvent) throws IOException {
                ClientService.getInstance().removeAsyncContext(asyncContext);
            }

            @Override
            public void onError(AsyncEvent asyncEvent) throws IOException {
                ClientService.getInstance().removeAsyncContext(asyncContext);
            }

            @Override
            public void onStartAsync(AsyncEvent asyncEvent) throws IOException {

            }
        });
        //添加异步asynccontext对象
        ClientService.getInstance().addAsyncContext(asyncContext);
    }
}

为了显示出来,通过一个隐藏的frame去读取这个异步Servlet发出的信息,其源代码如下:
index.jsp

<%@page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script>

</head>
<body>
<p>内容展示如下:</p>
<textarea name="result" id="result" readonly wrap="off">
</textarea>
<iframe id="autoFrame" style="display: none;" src="<%=request.getContextPath()%>/asynccontextservlet"></iframe>
</body>
<script type="text/javascript">
    function update(data) {
        var result = $("#result")[0];
        result.value = result.value + data + "\n";
    }
</script>
</html>

新建Test01Servlet触发消息

package com.wujialiang;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(
        name = "test01",
        urlPatterns = {
                "/test01"
        }
)
public class Test01Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        for (int i = 0; i < 20; i++) {
            final String str = "<script>window.parent.update(\"" + String.valueOf(i) + "\");</script>";
            ClientService.getInstance().callClient(str);
            try {
                Thread.sleep(2 * 1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (i == 10) {
                break;
            }
        }
    }

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

先访问index.jsp,然后访问test01接口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Registration动态注入基础

前面的章节中介绍了Servlet的两种配置方式,一种是通过注解进行注入,一种是通过web.xml进行配置。其中,注入方式是Servlet3.0之后新增的特性。实现动态注入的基础是Registration接口。该接口是Servlet3.0后引入的接口,主要用于向ServletContext中动态注Servlet、.Filter的实例,从而减轻web.xml繁重的配置。

Registration接口的方法

返回值方法说明
java.lang.StringgetClassName()返回类名
java.lang.StringgetInitParameter(java.lang.String name)根据参数name获取启动时的初始化参数
java.util.Map<String>getlnitParameters()获取所有的初始化参数和值,封装到map中
java.lang.StringgetName()返回对应的Servlet或Filter对应的name
booleansetInitParameter(java.lang.String name,java.lang.String value)设置单个初始化参数
java.util.Set<String>setInitParameters(java.util.Map<java.lang.String,java.lang.String>initParameters)批量设置初始化参数

ServletRegistration接口和FilterRegistration接口分别继承了Registration接口,并且添加了各自的内容。

ServletRegistration接口中添加了addMapping、getMappings、getRunAsRole方法,在ServletRegistration.Dynamic接口中,添加了setLoadOnStartup、setMultipartConfig等接口,这些信息与之前@WebServlet注解中介绍的属性内容一致。

FilterRegistration接口中添加了addMappingForServletNames、addMappingForUrlPatterns、getServletNameMappings、getUrlPatternMappings方法,这些信息与之前@WebFilter注解中介绍的属性内容一致。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/531134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

罗德与施瓦茨Rohde Schwarz FSW8 2HZ-8GHZ信号分析仪FSW13收购

罗德与施瓦茨Rohde & Schwarz FSW8 2HZ-8GHZ信号分析仪 附加功能&#xff1a; 10 kHz 偏移&#xff08;1 GHz 载波&#xff09;时的低相位噪声为 –137 dBc (1 Hz) 用于 WCDMA ACLR 测量的 –88 dBc 动态范围&#xff08;带噪声消除&#xff09; 高达 2 GHz 的分析带宽 &…

Camtasia2023.0.1CS电脑录制屏幕动作工具新功能介绍

Camtasia Studio是一款专门录制屏幕动作的工具&#xff0c;它能在任何颜色模式下轻松地记录 屏幕动作&#xff0c;包括影像、音效、鼠标移动轨迹、解说声音等等&#xff0c;另外&#xff0c;它还具有即时播放和编 辑压缩的功能&#xff0c;可对视频片段进行剪接、添加转场效果。…

云原生背景下如何配置 JVM 内存

image.png 背景 前段时间业务研发反馈说是他的应用内存使用率很高&#xff0c;导致频繁的重启&#xff0c;让我排查下是怎么回事&#xff1b; 在这之前我也没怎么在意过这个问题&#xff0c;正好这次排查分析的过程做一个记录。 首先我查看了监控面板里的 Pod 监控&#xff1a;…

指令的运行原理及Linux权限解读

目录 一. 指令的运行原理 二. Linux下的用户 2.1 Linux的用户分类 2.2 用户之间的切换 三. 文件权限的概念 3.1 文件类型 3.2 文件的权限 3.3 ls -l 打印文件全部信息的解读 四. 权限的修改 五. 拥有者和所属组的修改 六. 起始权限问题和权限掩码umask 七. 目录文件…

ArcGIS之克里金插值教学

本文来自&#xff1a;GIS科研实验室 基本概念 1.什么是克里金插值&#xff1f; 克里金插值又称空间局部插值法&#xff0c;是以半变异函数理论和结构分析为基础&#xff0c;在有限区域内对区域化变量进行无偏最优估计的一种方法&#xff0c;是地统计学的主要内容之一。南非矿产…

【消息中间件】RocketMQ消息重复消费场景及解决办法

文章目录 前言那么在什么情况下会发生RocketMQ的消息重复消费呢&#xff1f;消息重复消费的场景大概可以分为生产者端重复消费和消费者端重复消费&#xff0c;那么如何来解决消息的重复消费呢&#xff1f;既然在生产者做幂等性的方案都不是特别靠谱&#xff0c;那就再在消费者端…

信创办公–基于WPS的EXCEL最佳实践系列 (宏的录制)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;宏的录制&#xff09; 目录 应用背景操作步骤1、宏的录制启用2、宏的使用3、宏的保存4、宏的禁用 应用背景 宏是一个自动化完成重复性工作的工具&#xff0c;使用宏可以提高我们的工作效率&#xff0c;那应该怎样使用宏这一…

日志与时间戳,客户端与服务器端,打包压缩解压解包介绍,date,cal,zip,unzip,tar指令等

日志与时间戳 计算机世界里面&#xff0c;时间其实很重要的&#xff0c;首先我们需要有日志这个概念&#xff0c;这个日志其实就跟日记一样&#xff0c;那么在日记里面的话就会有时间。时间真的非常关键&#xff0c;比方在出现问题的时候去找到这个问题出现的时间点&#xff0…

EEPROM读写测试实验(主要记录IIC通信协议)

一、简介 EEPROM&#xff0c;电可擦除可编程只读存储器&#xff0c;是一个非易失性的存储器件。RAM&#xff1a; 随机访问存储器&#xff0c;可读也可写&#xff0c;断电不保存数据&#xff0c;常用的RAM有ddr3、SDRAM。ROM仅支持读&#xff0c;不可写&#xff0c;但断电可以保…

4 通道3.2GSPS(或者配置成2 通道6.4GSPS)采样率的12 位AD 采集FMC+子卡模块

板卡概述 FMC_XM134 是一款4 通道3.2GSPS&#xff08;或者配置成2 通道6.4GSPS&#xff09;采样率的12 位AD 采集FMC子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4 规范&#xff0c;可以作为一个理想的IO 模块耦合至FPGA 前端&#xff0c;射频模拟信号数字化后…

外网远程访问公司内网用友畅捷通T财务软件 - 远程办公

文章目录 前言1.本地访问简介2. cpolar内网穿透3. 公网远程访问4. 固定公网地址 前言 用友畅捷通T适用于异地多组织、多机构对企业财务汇总的管理需求&#xff1b;全面支持企业对远程仓库、异地办事处的管理需求&#xff1b;全面满足企业财务业务一体化管理需求。企业一般将其…

老胡的周刊(第090期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 privateGPT[2] 为保证数据私密性&#xff0c…

antd——实现不分页的表格前端排序功能——基础积累

最近在写后台管理系统时&#xff0c;遇到一个需求&#xff0c;就是给表格中的某些字段添加排序功能。注意该表格是不分页的&#xff0c;因此排序可以只通过前端处理。 如下图所示&#xff1a; 在antd官网上是有关于表格排序的功能的。 对某一列数据进行排序&#xff0c;通过…

字符串运算公式:muParser公式库在linux平台使用

muParser是一个跨平台的公式解析库,它可以自定义多参数函数,自定义常量、变量及一元前缀、后缀操作符,二元操作符等,它将公式编译成字节码,所以计算起来非常快。 1 、muParser源码下载 官方网址http://sourceforge.net/projects/muparser/ gitee下载地址:Gitee 极速下…

使用国产chatglm推理自己的数据文件_闻达

最近大火的chatgpt&#xff0c;老板说让我看看能不能用自己的数据&#xff0c;回答专业一些&#xff0c;所以做了一些调研&#xff0c;最近用这个倒是成功推理了自己的数据&#xff0c;模型也开源了&#xff0c;之后有机会也训练一下自己的数据。 使用国产chatglm推理自己的数…

【C++】引用重新赋值?

&#xff08;点击上方公众号&#xff0c;可快速关注&#xff09; 前段时间解决了一个关于引用的bug&#xff0c;原因是引用“重新赋值”造成的&#xff0c;原来的代码逻辑关于队列的选择&#xff0c;为了凸显问题&#xff0c;这里使用一个简单的例子重写。示例代码如下&#xf…

第七章 TensorFlow实现卷积神经网络

7.2TensorFlow实现简单的CNN import matplotlib.pyplot as plt import numpy as np import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data from tensorflow.python.framework import ops ops.reset_default_graph()# 创建计算图 sess tf.Ses…

SpringBoot及其配置文件

目录 1.SpringBoot简介 2.第一个SpringBoot项目 3.SpringBoot配置文件 3.1 配置文件介绍 3.2 properties配置文件 3.2.1 properties配置文件——写 3.2.2 properties配置文件——读 3.2.3 properties配置文件——缺点 3.3 yml配置文件 3.3.2 yml配置文件——初阶写 …

RocketMQ入门

文章目录 一. 基本概念1. 概述2. 基本概念3. RocketMQ的特性4. 整体架构 二. RocketMQ整体流程1. 流程图2. 流程介绍 一. 基本概念 1. 概述 RocketMQ 是阿里巴巴在 2012 年开源的分布式消息中间件&#xff0c;目前已经捐赠给 Apache 软件基金会&#xff0c;并于 2017 年 9 月…

【数据结构】--- 几分钟走进栈和队列(详解-下)

文章目录 前言&#x1f31f;一、队列的概念及结构&#xff1a;&#x1f31f;二、队列实现的两种方式&#xff1a;&#x1f31f;三、队列的实现&#xff1a;&#x1f30f;3.1队列结构&#xff1a;&#x1f30f;3.2初始化&#xff1a;&#x1f30f;3.3释放(类似单链表)&#xff1…