关于java:一文快速回顾-ServletFilterListener

46次阅读

共计 23114 个字符,预计需要花费 58 分钟才能阅读完成。

什么是 Servlet?

前置常识

Web 服务器:能够指硬件上的,也能够指软件上的。从硬件的角度来说,Web 服务器指的就是一台存储了网络服务软件的计算机;从软件的角度来说,Web 服务器指的是一种软件,比方 Tomcat。

Servlet 容器:目前支流的 Servlet 容器软件包含 Tomcat、Jetty、Jboss 等。

Web 服务器 ≠ Servlet 容器,Tomcat 是一种 Web 服务器,同时它还是一个 Servlet 容器。

打开 Servlet 源码,有这样的一句话:

A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.

一个 Servlet 就是一个小型的运行在 Web 服务器外面的 Java 程序。每个 Servlet 都会接管并且响应来自 Web 客户端的每一个申请,申请指的是 HTTP 申请(超文本传输协定)。

Servlet(Server Applet)能够说是一种在 Web 服务器中的 Java 应用程序,这种程序与咱们平时写的 Java 程序的区别在于,它封装了对 HTTP 申请的解决,同时须要 Servlet 容器的反对。

实质上,Servlet 就是按 Servlet 标准编写的 Java 类,特别之处就是它能够解决相干的 HTTP 申请。它的标准,或者说规范,是由 Sun 定义的,具体的细节是在 Servlet 容器中实现的,没错,比方 Tomcat(老伙计又进去了)。

Servlet 的孩子们

javax.servlet 包中,定义了 Servlet、ServletConfig 这两个接口,这两个接口定义了 Servlet 的根本办法以及相干的配置信息。

Servlet 接口:

public interface Servlet {

    // 初始化的办法,当 Servlet 对象实例化后,Servlet 容器会调用该办法来实现初始化工作
    public void init(ServletConfig config) throws ServletException;

    // 服务的办法,用于解决客户端(浏览器)发送的申请,并返回响应,简略点,解决业务逻辑的
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;

    // 销毁的办法,当 Servlet 对象将要从 Servlet 容器中移除时,Servlet 容器会调用该办法,就会将 Servlet 对象进行垃圾回收,开释内存资源
    public void destroy();
    
    // 用于获取 Servlet 对象的配置信息,返回 ServletConfig 对象
    public ServletConfig getServletConfig();
    
    // 用于获取 Servlet 本身的信息,比方作者,版本
    public String getServletInfo();}

init()、service()、destroy(),这 3 个办法的定义,也是定义了 Servlet 的生命周期,这个前面讲。

ServletConfig 接口:

public interface ServletConfig {

    // 获取 Servlet 对象的名称
    public String getServletName();

    // 获取 Servlet 对象的上下文
    public ServletContext getServletContext();

    // 依据初始化的参数名获取其参数值
    public String getInitParameter(String name);

    // 获取所有初始化的参数名的枚举汇合
    public Enumeration<String> getInitParameterNames();}

还定义了一个名为 GenericServlet 的抽象类,这个抽象类实现了 Servlet、ServletConfig 和 Serializable 接口,它为 Servlet、ServletConfig 提供了一些实现,但没有对 HTTP 申请解决进行实现,对于 HTTP 申请的解决,是由 GenericServlet 的子类—— HttpServlet 实现的。

HttpServlet 也是一个抽象类,它对 HTTP 申请中的 GET、POST 等申请提供了具体的实现,所以个别状况下,咱们本人写的 Servlet 根本都是去继承 HttpServlet,进而进行下一步操作。

HttpServlet 抽象类:

public abstract class HttpServlet extends GenericServlet {
    
    ...
    
    // 并没有具体实现,所以咱们本人写的时候须要重写这些办法,来实现咱们解决 HTTP 申请的逻辑
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }
    
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
        } else {resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
        }
    }

}

Servlet 和 JSP 的关系

  • Servlet 先呈现,JSP 后呈现。
  • Servlet 能够解决页面的问题,返回页面给客户端,然而它次要用于解决业务逻辑。因为没有 JSP 的时候,页面和逻辑都是 Servlet 解决的,代码耦合度是十分高的,操作也是很简单,所以 JSP 的呈现就是解决这种问题,将 HTML、CSS、JS 间接写到 JSP 页面中。

在 IDEA 中创立 Servlet 程序

新建 Web 我的项目并进行配置

新建一个 Web 我的项目,File -> New -> Project… -> Java -> Java EE -> Web Application

  1. web/WEB-INF 上面新建两个文件夹,即 classes 和 lib 目录。
  2. 按 Ctrl + Alt + Shift + S 进入 Project Structure 进行配置(配置刚刚新建的两个目录)。
  3. 配置编译后的 class 文件输入门路。
  4. 配置依赖项(Web 利用依赖的 Jar 包)地位。
  1. 将 tomcat/lib 目录下的 servlet-api.jar 复制到刚刚创立的 lib 目录下(是吧,具体的细节是在 Servlet 容器中实现的
  2. 配置 Tomcat

还能够配置拜访地址(以后我的项目的拜访门路),通过 Application context 这个配置项进行配置,我这里一开始默认是 demo_servlet_war_exploded,批改成 demo_servlet,于是拜访门路会变成 localhost:8080/demo_servlet

编写 Servlet

在你喜爱的中央创立一个 Servlet,不过目前我把它放在 cn.god23bin.demo.controller 包下。

HelloServlet:继承 HttpServlet,并重写 doGet() 和 doPost() 办法。

public class HelloServlet extends HttpServlet {
    /**
     * 解决 GET 形式的 HTTP 申请
     * @param req 申请对象
     * @param resp 响应对象
     * @throws ServletException 异样对象
     * @throws IOException IO 异样对象
     * @return 返回 HTML 页面
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head><title>Hello Servlet</title></head>");
        out.println("<body>Servlet 实例对象:"+ this.getClass() + "</body>");
        out.println("</html>");
        out.flush();
        out.close();}

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

为什么咱们只须要重写 doGet()/doPost(),不须要重写 service() 办法?

因为 HttpServlet 的 service() 办法曾经具体实现了,在该办法判断申请是什么类型的(通过 if else 进行判断,判断 GET、POST、PUT、DELETE 等),而后再分出一个个独自的办法来调用,所以咱们只须要重写 doGet()/doPost() 就行了。

配置 Servlet

想要 Servlet 失常运行,那么须要进行配置,告知 Web 服务器哪一个申请调用哪一个 Servlet 对象进行解决。业余的说法:「注册 Servlet」

在 Servlet 3.0 之前,是通过 web.xml 对 Servlet 进行配置的,3.0 开始,能够通过注解进行配置。

web.xml 的形式

在咱们创立这个 Web 利用的时候,该版本为 Servlet 4.0,能够从这个 web.xml 中看到版本信息。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

</web-app>

应用 xml 配置文件进行配置,次要用到这么几个标签:

  • <servlet>:申明一个 Servlet 对象
  • <servlet-name>:指定 Servlet 的名称,命名的作用
  • <servlet-class>:指定 Servlet 对象的全限定类名(全门路、残缺的地位)
  • <servlet-mapping>:申明 Servlet 对象后,须要映射拜访 Servlet 的 URL(对立资源定位符)
  • <url-pattern>:指定映射拜访的 URL,个别本人依据业务进行编写申请的门路
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>cn.god23bin.demo.controller.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

此时,启动 Tomcat,拜访 http://localhost:8080/demo_servlet/hello,失去 Servlet 解决的返回后果:

Servlet 实例对象:class cn.god23bin.demo.controller.HelloServlet

@WebServlet 的形式

@WebServlet 注解,间接加在本人编写的 Servlet 类上,通过它的属性进行配置,比方 name 属性,urlPatterns 属性和 initParams 属性。这里配置了两个申请映射门路 /no_web/web_no,这两个门路都由以后的 Servlet 对象来解决。

@WebServlet(name = "NoServlet", urlPatterns = {"/no_web", "/web_no"})
public class NoWebXmlServlet extends HttpServlet {
    /**
     * 解决 GET 形式的 HTTP 申请
     * @param req 申请对象
     * @param resp 响应对象
     * @throws ServletException 异样对象
     * @throws IOException IO 异样对象
     * @return 返回 HTML 页面
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head><title>No Web XML Servlet</title></head>");
        out.println("<body>Servlet 实例对象 - 应用注解的形式配置的:"+ this.getClass() + "</body>");
        out.println("</html>");
        out.flush();
        out.close();}

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

此时拜访 http://localhost:8080/demo_servlet/no_webhttp://localhost:8080/demo_servlet/web_no 都会失去 Servlet 解决的返回后果:

Servlet 实例对象 - 应用注解的形式配置的:class cn.god23bin.demo.controller.NoWebXmlServlet

这两个如何抉择?

按目前的局势,看集体喜爱,我是喜爱写注解的形式来进行配置。

对于对申请和响应的封装

每当有一个 HTTP 申请过去,Servlet 容器就会将以后 HTTP 申请的信息封装为一个 HttpServletRequest 对象,而每一个 HttpServletResponse 对象将会转成 HTTP 响应返回给客户端。

HttpServletRequest 接口

在 Servlet API 中,定义了一个 HttpServletRequest 接口,它继承自 ServletRequest 接口。HttpServletRequest 对象专门用于封装 HTTP 申请音讯。

HTTP 申请音讯分为 申请行、申请头和申请体 三局部,所以 HttpServletRequest 接口中定义了获取申请行、申请头和申请体的相干办法。

  1. 获取申请行信息

HTTP 申请的申请行中蕴含申请办法、申请资源名、申请门路等信息,HttpServletRequest 接口定义了一系列获取申请行信息的办法,如下表:

返回值类型 办法申明 形容
String getMethod() 该办法用于获取 HTTP 申请形式(如 GET、POST 等)。
String getRequestURI() 该办法用于获取申请行中的资源名称局部,即位于 URL 的主机和端口之后,参数局部之前的局部。
String getQueryString() 该办法用于获取申请行中的参数局部,也就是 URL 中“?”当前的所有内容。
String getContextPath() 返回以后 Servlet 所在的利用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此办法返回空字符串 ””。
String getServletPath() 该办法用于获取 Servlet 所映射的门路。
String getRemoteAddr() 该办法用于获取客户端的 IP 地址。
String getRemoteHost() 该办法用于获取客户端的残缺主机名,如果无奈解析出客户机的残缺主机名,则该办法将会返回客户端的 IP 地址。
  1. 获取申请头信息

当浏览器发送申请时,须要通过申请头向服务器传递一些附加信息,例如客户端能够接管的数据类型、压缩形式、语言等。为了获取申请头中的信息,HttpServletRequest 接口定义了一系列用于获取 HTTP 申请头字段的办法,如下表:

返回值类型 办法申明 形容
String getHeader(String name) 该办法用于获取一个指定头字段的值。如果申请音讯中蕴含多个指定名称的头字段,则该办法返回其中第一个头字段的值。
Enumeration getHeaders(String name) 该办法返回指定头字段的所有值的枚举汇合,在少数状况下,一个头字段名在申请音讯中只呈现一次,但有时可能会呈现屡次。
Enumeration getHeaderNames() 该办法返回申请头中所有头字段的枚举汇合。
String getContentType() 该办法用于获取 Content-Type 头字段的值。
int getContentLength() 该办法用于获取 Content-Length 头字段的值。
String getCharacterEncoding() 该办法用于返回申请音讯的字符集编码。
  1. 获取申请参数信息

在理论开发中,咱们常常须要获取用户提交的表单数据,例如用户名和明码等。为了不便获取表单中的申请参数,ServletRequest 定义了一系列获取申请参数的办法,如下表:

返回值类型 办法申明 性能形容
String getParameter(String name) 返回指定参数名的参数值。
String [] getParameterValues (String name) 以字符串数组的模式返回指定参数名的所有参数值(HTTP 申请中能够有多个雷同参数名的参数)。
Enumeration getParameterNames() 以枚举汇合的模式返回申请中所有参数名。
Map getParameterMap() 用于将申请中的所有参数名和参数值装入一个 Map 对象中返回。

测试 API

@WebServlet(name = "ReqInfoServlet", urlPatterns = "/reqInfo")
public class ReqInfoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {resp.setContentType("text/html;charset=UTF-8");
        PrintWriter out = resp.getWriter();
        out.println(
                "申请行 ------------------------------------<br/>" +
                "申请形式:"     + req.getMethod() + "<br/>" +
                "客户端的 IP 地址:"    + req.getRemoteAddr() + "<br/>" +
                "利用名字(上下文):"  + req.getContextPath() + "<br/>" +
                "URI:"                + req.getRequestURI() + "<br/>" +
                "申请字符串:"          + req.getQueryString() + "<br/>" +
                "Servlet 所映射的门路:" + req.getServletPath() + "<br/>" +
                "客户端的残缺主机名:"   + req.getRemoteHost() + "<br/>");
        out.println("<br/>");
        out.println("申请头 ------------------------------------<br/>");
        // 取得所有申请头字段的枚举汇合
        Enumeration<String> headers = req.getHeaderNames();
        while (headers.hasMoreElements()) {
            // 取得申请头字段的值
            String value = req.getHeader(headers.nextElement());
            out.write(headers.nextElement() + ":" + value + "<br/>");
        }
        out.println("<br/>");
        out.println("申请参数 ----------------------------------<br/>");
        out.println("keyword:" + req.getParameter("keyword"));
        out.println("value:" + req.getParameter("value"));
        out.flush();
        out.close();}

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

HttpServletResponse 接口

在 Servlet API 中,定义了一个 HttpServletResponse 接口,它继承自 ServletResponse 接口。HttpServletResponse 对象专门用来封装 HTTP 响应音讯。

HTTP 响应音讯由 响应行、响应头、响应体 三局部组成,所以 HttpServletResponse 接口中定义了向客户端发送响应状态码、响应头、响应体的办法。

  1. 响应行相干的办法

当 Servlet 返回响应音讯时,须要在响应音讯中设置状态码。因而,HttpServletResponse 接口定义了发送状态码的办法,如下表:

返回值类型 办法 形容
void setStatus(int status) 用于设置 HTTP 响应音讯的状态码,并生成响应状态行。
void sendError(int sc) 用于发送示意错误信息的状态码。
  1. 响应头相干的办法

HttpServletResponse 接口中定义了一系列设置 HTTP 响应头字段的办法,如下表:

返回值类型 办法 形容
void addHeader(String name,String value) 用于减少响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
void setHeader (String name,String value) 用于设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
void addIntHeader(String name,int value) 用于增加值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。
void setIntHeader(String name, int value) 用于设置值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。
void setContentType(String type) 用于设置 Servlet 输入内容的 MIME 类型以及编码格局。
void setCharacterEncoding(String charset) 用于设置输入内容应用的字符编码。
  1. 响应体相干的办法

因为在 HTTP 响应音讯中,大量的数据都是通过响应体传递的。因而 ServletResponse 遵循以 I/O 流传递大量数据的设计理念,在发送响应音讯体时,定义了两个与输入流相干的办法。

返回值类型 办法 形容
ServletOutputStream getOutputStream() 用于获取字节输入流对象。
PrintWriter getWriter() 用于获取字符输入流对象。

留神:getOutputStream() 和 getWriter() 办法相互排挤,不可同时应用,否则会产生 IllegalStateException 异样。

这里就本人去手动测试一波吧!

Servlet 的生命周期

生命周期,从 Servlet 创立到开始工作解决申请,再到被销毁进行垃圾回收的过程。从 Service 接口提供的办法也体现进去了。

  • Servlet 初始化后,Servlet 容器就会调用 init () 办法。
  • Servlet 调用 service() 办法来解决客户端的申请。
  • Servlet 要销毁前,Servlet 容器就会调用 destroy() 办法。
  • 最初,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

init() 办法被设计成只调用一次,它在第一次创立 Servlet 时被调用,在后续每次用户申请时不再被调用。因而,它是用于一次性初始化。

service() 办法是执行理论工作的次要办法。每次服务器接管到一个 Servlet 申请时,服务器会产生一个新的线程并调用服务。service() 办法查看 HTTP 申请类型,并在适当的时候调用 doGet、doPost、doPut,doDelete 等办法。

destroy() 办法只会被调用一次,在 Servlet 生命周期完结时被调用。destroy() 办法能够让咱们写一些操作,比方敞开数据库连贯。在调用 destroy() 办法之后,servlet 对象被标记为垃圾。

过滤器

除了 Servlet 自身,Java Web 利用中还有两个重要的组件:过滤器(Filter)和监听器(Listener)。上面别离回顾。

什么是过滤器?

Servlet 过滤器是再 Servlet 2.3 标准中退出的性能。过滤器能够动静地 拦挡申请和响应,以变换或应用蕴含在申请或响应中的信息。

某些状况下,咱们须要再业务代码执行前获取申请中的某些信息,就能够应用过滤器。

简而言之:

  • 在客户端的申请拜访服务器中的资源之前,拦挡这些申请。
  • 在服务器的响应发送回客户端之前,解决这些响应。

如果咱们应用一个过滤器不能解决业务需要,那么就用多个,多个过滤器能够对申请和响应进行屡次解决。多个过滤器组合而成的就是「过滤器链」,申请会顺次依照过滤器的程序一一进入,直到最初一个过滤器为止。当返回响应的时候,也是一样,从最初一个过滤器顺次传递到第一个过滤器,最初达到客户端。

过滤器相干的接口

有 3 个接口须要晓得,就是 Filter 接口、FilterConfig 接口、FilterChain 接口。

每一个过滤器都要间接或间接地实现 Filter 接口,在 Filter 中定义了 3 个办法,别离是 init()、doFilter()、destroy() 办法。

public interface Filter {
    // 初始化办法,初始化的时候会被调用
    public default void init(FilterConfig filterConfig) throws ServletException {}

    // 对申请进行过滤解决
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

    // 销毁办法,开释资源
    public default void destroy() {}
}

FilterConifg 接口由 Servlet 容器实现,次要用于获取过滤器中的配置信息。

public interface FilterConfig {

    // 获取过滤器名称
    public String getFilterName();

    // 获取 Servlet 上下文
    public ServletContext getServletContext();

    // 依据初始化的参数名获取的参数值
    public String getInitParameter(String name);

    // 获取所有初始化参数名的枚举类型汇合
    public Enumeration<String> getInitParameterNames();}

FilterChain 接口依然由 Servlet 容器实现,这个接口只有一个办法。

public interface FilterChain {

    // 用于将过滤后的申请传递给下一个过滤器,如果这个过滤器是最初一个,那么将申请传递给指标资源(比方交给了某个 Servlet)public void doFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException;

}

编写一个过滤器

一个过滤器须要实现 Filter 接口,实现该接口后,须要对它的 3 个办法进行实现,其中对于初始化和销毁的办法,如果没有什么特地的须要解决,能够是空实现(空办法)。

编写过滤器和编写 Servlet 也是差不多的,须要一个 Java 类来作为过滤器,并通过 web.xml 进行配置,过滤器也有它对应的标签,不过我这里目前应用注解的形式对本人的过滤器进行配置。

间接在过滤器这个类上应用 @WebFilter 注解进行配置,有 filterName 属性,urlPatterns 属性(映射门路,是一个数组),initParams 属性(用于配置过滤器初始化参数的)等等。

HelloFilter:

@WebFilter(filterName = "第一个过滤器", 
        urlPatterns = {"/count", "/add"}, 
        initParams = {@WebInitParam(name = "count", value = "23"), @WebInitParam(name = "add", value = "32")})
public class HelloFilter implements Filter {
    
    private Integer iCount;
    private Integer iAdd;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 通过 filterConfig 对象获取咱们给过滤器配置的初始化参数
        String count = filterConfig.getInitParameter("count");
        String add = filterConfig.getInitParameter("add");
        iCount = Integer.valueOf(count);
        iAdd = Integer.valueOf(add);
        System.out.println("第一个过滤器初始化实现!");
        System.out.println("获取初始化的参数 ------count:" + iCount + "-add:" + iAdd);
    }

    @Override
    public void destroy() {System.out.println("第一个过滤器曾经销毁");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 解决过滤申请
        iCount++;
        iAdd++;
        // 将 ServletRequest 转成 HttpServletRequest
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        // 获取上下文
        ServletContext context = req.getServletContext();
        // 将拜访数量的值放入上下文
        context.setAttribute("iCount", iCount);
        context.setAttribute("iAdd", iAdd);
        // 传递到下一个过滤器
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

下面的代码中,配置了过滤的申请门路有 /count/add,当客户端拜访这两个门路时,就会进入该过滤器,第一次拜访就会执行初始化办法,接着会执行 doFilter 办法,将两个变量自增存储到 Servlet 上下文中(上下文能够了解成整个 Servlet 容器 存取 数据的区域(环境),能够被其余 Servlet 共享),最初传递到下一个过滤器,如果没有下一个过滤器,就间接到指标资源。

当初,写多一个 Servlet,作为申请拜访的指标资源,如下:

@WebServlet(urlPatterns = {"/count", "/add"})
public class AServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("解决业务,这里就是指标资源");
        ServletContext context = req.getServletContext();
        Integer iCount = (Integer) context.getAttribute("iCount");
        Integer iAdd = (Integer) context.getAttribute("iAdd");
        System.out.println("欢送拜访" + iCount + "---" + iAdd);
    }

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

从新运行 Tomcat,拜访 http://localhost:8080/demo_servlet/counthttp://localhost:8080/demo_servlet/add

控制台输入如下:

第一个过滤器初始化实现!获取初始化的参数 ------count:23-add:32
[2023-02-19 03:21:32,340] Artifact demo-servlet:war exploded: Artifact is deployed successfully
[2023-02-19 03:21:32,340] Artifact demo-servlet:war exploded: Deploy took 562 milliseconds
解决业务,这里就是指标资源
欢送拜访 24---33
解决业务,这里就是指标资源
欢送拜访 25---34

利用

字符编码过滤器

示例:这里有一个 Servlet,获取申请中携带的参数并返回其拼接到 HTML 中的内容返回给客户端。这个 Servlet 作为解决 /addSomething 的 POST 申请。

@WebServlet(name = "AddServlet", urlPatterns = "/addSomething")
public class AddServlet 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 {String name = req.getParameter("name");
        String author = req.getParameter("author");
        PrintWriter out = resp.getWriter();
        out.print("<h2> 名称:" + name +"</h2>");
        out.print("<h2> 作者:" + author +"</h2>");
        out.flush();
        out.close();}
}

此时,如果没有进行字符编码的过滤操作,从新设置字符集的话,返回的后果是有中文乱码的。所以须要一个字符编码过滤器,对申请和响应进行操作。

CharacterFilter:这个字符编码过滤器指定了须要过滤的 Servlet 是哪一个(通过 servletNames 指定),并配置了初始化的一个名为 encoding 的参数,其值为 UTF-8。在过滤的办法中,设置申请和响应的字符编码为 UTF-8,这样后续达到指标资源的申请和响应的编码格局就是反对中文的 UTF-8 编码了。

@WebFilter(servletNames = "AddServlet", initParams = @WebInitParam(name = "encoding", value = "UTF-8"))
public class CharacterFilter implements Filter {

    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 获取配置的初始化参数值
        encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {if (encoding != null) {
            // 设置 request 编码格局
            servletRequest.setCharacterEncoding(encoding);
            servletResponse.setContentType("text/html; charset=" + encoding);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {}
}

测试后果:

当然,过滤器还有其余利用场景:

  • 认证和受权:验证用户是否具备拜访某个资源的权限
  • 日志记录:记录客户端申请和服务器响应的详细信息,用于调试和监控
  • 数据压缩和解压缩:压缩响应数据以缩小网络带宽的应用
  • 图像处理:对申请的图像进行解决,如缩放、裁剪等
  • 过滤器链:将多个过滤器组合起来造成一个过滤器链,顺次解决客户端申请和服务器响应

监听器

什么是监听器?

有这么一个需要:就是当某件事产生的时候,咱们能够做出某些动作。如何实现呢?那就是通过监听器实现。

Servlet 监听器能够监听 Web 应用程序的某些事件,并当某件事(比方应用程序的启动和敞开)产生的时候,咱们进行相干的解决。

监听什么?

能够监听 ServletContext 的相干操作,能够监听 HTTP Session(HTTP 会话)的操作,能够监听客户端发送过去的申请(申请到 Servlet)。

监听器是监听 Web 容器的无效事件,所以它是由容器治理的。总共有 8 个 Listener 接口 和 6 个 Event 类

如何监听?

它们都有各自的监听器:

  • 监听 ServletContext(Servlet 上下文 – application):ServletContextListener
  • 监听 HTTP Session:HttpSessionListener
  • 监听客户端的申请(request):ServletRequestListener

监听器的相干接口

对于 ServletContext 上下文的监听,有 2 个接口:ServletContextListener 和 ServletAttributeListener 接口。

  1. ServletContextListener 接口:
public interface ServletContextListener extends EventListener {

    // 告诉所有的 Servlet 上下文监听器对象,Web 应用程序曾经被加载,之后才持续加载 过滤器或者 Servlet,能够调用该办法了
    public default void contextInitialized(ServletContextEvent sce) { }

    // 在所有的 Servlet 和 过滤器都被销毁后,告诉所有的 Servlet 上下文监听器对象,Web 应用程序曾经被销毁,能够调用该办法了
    public default void contextDestroyed(ServletContextEvent sce) {}}
  1. ServletContextAttributeListener 接口:
public interface ServletContextAttributeListener extends EventListener {
    
    // 当有新的属性被退出到 Servlet 上下文后,就告诉所有的上下文监听器调用这个办法
    public default void attributeAdded(ServletContextAttributeEvent scae) { }

    // 当已有属性被移除后,告诉所有上下文监听器调用这个办法
    public default void attributeRemoved(ServletContextAttributeEvent scae) { }

    // 当已有属性的被替换后,告诉所有上下文监听器调用这个办法
    public default void attributeReplaced(ServletContextAttributeEvent scae) {}}

对于 HTTP Session 的监听,有 4 个接口:HttpSessionListener,HttpSessionActivationListener,HttpBindingListener 和 HttpSessionAttributeListener 接口。

  1. HttpSessionListener 接口:
public interface HttpSessionListener extends EventListener {

    // 告诉正在监听的对象,HttpSession 曾经被创立并且初始化了,能够调用该办法了
    public default void sessionCreated(HttpSessionEvent se) { }

    // 告诉正在监听的对象,HttpSession 曾经被销毁了,能够调用该办法了
    public default void sessionDestroyed(HttpSessionEvent se) {}}
  1. HttpSessionActivationListener 接口:

这里波及到的事件就是 Session 的钝化以及活化

钝化:其实就是应用序列化和反序列化技术把 Session 从内存保留到硬盘。

活化:反过来,把 Session 从硬盘加载到内存。

举个例子,如果 A 类没有实现 Serializable 接口,那么当 Session 钝化时就不会钝化 A 对象,而是把 A 对象从 Session 中移除再钝化。活化的时候,A 对象是不存在的。

public interface HttpSessionActivationListener extends EventListener {

    // 告诉正在监听的对象,Session 对象将要钝化,能够调用该办法了
    public default void sessionWillPassivate(HttpSessionEvent se) { }

    // 告诉正在监听的对象,Session 对象刚刚活化,能够调用该办法了
    public default void sessionDidActivate(HttpSessionEvent se) {}}
  1. HttpBindingListener 接口:

该接口监听 HTTP 会话中对象的绑定信息。

public interface HttpSessionBindingListener extends EventListener {

    // 告诉正在监听的对象,当有对象退出(绑定)到 Session 范畴时,能够调用该办法了
    public default void valueBound(HttpSessionBindingEvent event) { }

    // 告诉正在监听的对象,当有对象从 Session 范畴移除(解绑)时,能够调用该办法了
    public default void valueUnbound(HttpSessionBindingEvent event) {}}
  1. HttpSessionAttributeListener 接口:
public interface HttpSessionAttributeListener extends EventListener {

    // 当有新的属性被退出到 Session 后,就告诉所有的 HttpSession 监听器调用这个办法
    public default void attributeAdded(HttpSessionBindingEvent se) { }

    // 当有新的属性从 Session 中移除后,就告诉所有的 HttpSession 监听器调用这个办法
    public default void attributeRemoved(HttpSessionBindingEvent se) { }

    // 当已有属性的被替换后,就告诉所有的 HttpSession 监听器调用这个办法
    public default void attributeReplaced(HttpSessionBindingEvent se) {}}

对于 Servlet 申请的监听(客户端申请的监听),有 2 个接口:ServletRequestListener 和 ServletRequestAttributeListener 接口。

  1. ServletRequestListener 接口:
public interface ServletRequestListener extends EventListener {
    
    // 告诉正在监听的对象,ServletRequest 曾经被加载和初始化,能够调用该办法了
    public default void requestInitialized (ServletRequestEvent sre) { }

    // 告诉正在监听的对象,ServletRequest 曾经被销毁,能够调用该办法了
    public default void requestDestroyed (ServletRequestEvent sre) {}}
  1. ServletRequestAttributeListener 接口:
public interface ServletRequestAttributeListener extends EventListener {
    
    // 当有新的属性被退出到 ServletRequest 后,就告诉所有的 ServletRequest 监听器调用这个办法
    public default void attributeAdded(ServletRequestAttributeEvent srae) { }

    // 当有新的属性从 ServletRequest 中移除后,就告诉所有的 ServletRequest 监听器调用这个办法
    public default void attributeRemoved(ServletRequestAttributeEvent srae) { }

    // 当已有属性的被替换后,就告诉所有的 ServletRequest 监听器调用这个办法
    public default void attributeReplaced(ServletRequestAttributeEvent srae) {}}

编写一个监听器

能够看到,这些监听器无非就是监听那么几个对象的创立、销毁、其对象属性的创立、销毁、替换等等的事件。

HelloListener:该监听器实现了 ServletContextListener, ServletRequestListener 接口,并实现了接口定义的办法。

@WebListener
public class HelloListener implements ServletContextListener, ServletRequestListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {ServletContext context = sce.getServletContext();
        System.out.println("输入这句话阐明 Servlet 上下文曾经创立了:" + context);
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {ServletContext context = sce.getServletContext();
        System.out.println("输入这句话阐明 Servlet 上下文曾经销毁了:" + context);
    }

    @Override
    public void requestInitialized(ServletRequestEvent sre) {ServletRequest request = sre.getServletRequest();
        System.out.println("输入这句话阐明 ServletRequest 曾经创立了:" + request.getProtocol());
    }

    @Override
    public void requestDestroyed(ServletRequestEvent sre) {ServletRequest request = sre.getServletRequest();
        System.out.println("输入这句话阐明 ServletRequest 曾经销毁了:" + request.getProtocol());
    }
}

当 Web 应用程序启动时,Servlet 上下文就会创立,该监听器就会监听到该事件,打印输出咱们写的内容,同理每一次的 HTTP 申请也是,会被监听。控制台输入如下:

输入这句话阐明 Servlet 上下文曾经创立了:org.apache.catalina.core.ApplicationContextFacade@6df13dcc
第一个过滤器初始化实现!获取初始化的参数 ------count:23-add:32
[2023-02-19 09:48:11,272] Artifact demo-servlet:war exploded: Artifact is deployed successfully
[2023-02-19 09:48:11,272] Artifact demo-servlet:war exploded: Deploy took 735 milliseconds
输入这句话阐明 ServletRequest 曾经创立了:HTTP/1.1
输入这句话阐明 ServletRequest 曾经销毁了:HTTP/1.1
输入这句话阐明 ServletRequest 曾经创立了:HTTP/1.1
输入这句话阐明 ServletRequest 曾经销毁了:HTTP/1.1

利用

统计在线用户数:

public class UserCounterListener implements HttpSessionListener {

    private static int activeUsers = 0;

    public static int getActiveUsers() {return activeUsers;}

    public void sessionCreated(HttpSessionEvent event) {activeUsers++;}

    public void sessionDestroyed(HttpSessionEvent event) {activeUsers--;}
}

监听器能够用于以下场景:

  • 统计在线用户数:监听 HttpSession 的创立和销毁事件,并记录以后在线用户数
  • 初始化应用程序:监听 ServletContext 的创立事件,并在应用程序启动时执行初始化操作
  • 缓存预热:监听 ServletContext 的创立事件,并在应用程序启动时预加载缓存数据

总结

1. 编写 Servlet 的几种形式:

  • 实现 Servlet 接口(很少用)

    • 须要实现接口里的办法
    • 能够通过重写 init()、service()、destroy() 等办法来实现 Servlet 的生命周期治理
  • 继承 GenericServlet 类(很少用)

    • GenericServlet 实现了 Servlet 接口除了 service() 办法
    • 能够通过重写 service() 办法来实现 Servlet 的具体逻辑
  • 继承 HttpServlet 办法(最罕用)

    • HttpServlet 继承了 GenericServlet 类,提供了解决 HTTP 申请的办法
    • 能够通过重写 doGet()、doPost() 等办法来实现 Servlet 的具体逻辑

2. Servlet 解决 HTTP 申请的过程

咱们个别编写 Servlet 是通过继承 HttpServlet 类并重写其中的 doGet()、doPost() 等办法来解决 HTTP 申请的。

当客户端向服务器发送 HTTP 申请时,Servlet 容器会创立一个 HttpServletRequest 对象和一个 HttpServletResponse 对象,并将这两个对象作为参数传递给 Servlet 的 service() 办法。service() 办法会依据申请办法(GET、POST 等)调用 doGet()、doPost() 等办法来解决申请。

3. 过滤器和监听器

过滤器是一个 Java 类,它能够拦挡客户端申请和服务器响应,对它们进行解决,而后将它们传递给指标 Servlet 或 JSP 页面。

监听器也是一个 Java 类,它能够监听 Web 利用中的事件,如 ServletContext、HttpSession、ServletRequest 等对象的创立、销毁和属性更改等事件。

最初的最初

由自己程度所限,不免有谬误以及不足之处,屏幕前的靓仔靓女们 如有发现,恳请指出!

最初,谢谢你看到这里,谢谢你认真对待我的致力,心愿这篇博客对你有所帮忙!

你轻轻地点了个赞,那将在我的心里世界削减一颗亮堂而夺目的星!

正文完
 0