Web基础知识(3)- Java Servlet (三) | Servlet创建方式、Servlet生命周期、Servlet注解


1. Servlet创建方式

    Servlet 规范的最顶层是一个名为 javax.servlet.Servlet 的接口,所有的 Servlet 类都要直接或者间接地实现该接口。

    Servlet 内置了两个 Servlet 接口的实现类(抽象类),分别为 GenericServlet 和 HttpServlet。GenericServlet 是实现了 Servlet 接口的抽象类。HttpServlet 是 GenericServlet 的子类,具有 GenericServlet 的一切特性。

    创建 Servlet 类的三种方式:

        (1) 实现 javax.servlet.Servlet 接口,重写其全部方法。
        (2) 继承 javax.servlet.GenericServlet 抽象类,重写 service() 方法。
        (3) 继承 javax.servlet.http.HttpServlet 抽象类,重写 doGet() 或 doPost() 方法。

    1) Servlet 接口

        Servlet 接口中定义了 5 个方法,如下。

方法 描述
void init(ServletConfig config) Servlet 实例化之后,由 Servlet 容器调用,用来初始化 Servlet 对象。该方法只能被调用一次。参数 config 用来向 Servlet 传递配置信息。
void service(ServletRequest req, ServletResponse res) Servlet 容器调用该方法处理客户端请求。
void destroy() 服务器关闭、重启或者 Servlet 对象被移除时,由 Servlet 容器调用,负责释放 Servlet 对象占用的资源。
ServletConfig getServletConfig() 用来获取 ServletConfig 对象,该对象中包含了 Servlet 的初始化参数。
String getServletInfo()  用于获取 Servlet 的信息,例如作者、版本、版权等。


        示例:

 1             package com.example;
 2 
 3             import javax.servlet.*;
 4             import java.io.IOException;
 5             import java.io.PrintWriter;
 6 
 7             public class OneServlet implements Servlet {
 8 
 9                 // Servlet 实例被创建后,调用 init() 方法进行初始化,该方法只能被调用一次
10                 @Override
11                 public void init(ServletConfig servletConfig) throws ServletException {
12                 }
13 
14                 // 返回 ServletConfig 对象,该对象包含了 Servlet 的初始化参数
15                 @Override
16                 public ServletConfig getServletConfig() {
17                     return null;
18                 }
19 
20                 // 每次请求,都会调用一次 service() 方法
21                 @Override
22                 public void service(ServletRequest servletRequest, ServletResponse servletResponse)
23                                     throws ServletException, IOException {
24                     servletResponse.setContentType("text/html;charset=UTF-8");
25 
26                     PrintWriter writer = servletResponse.getWriter();
27                     writer.write("OneServlet implements Servlet");
28                     writer.close();
29                 }
30                 // 返回 Servlet 的信息
31                 @Override
32                 public String getServletInfo() {
33                     return null;
34                 }
35 
36                 // Servlet 被销毁时调用
37                 @Override
38                 public void destroy() {
39                 }
40 
41             } 


    2) GenericServlet 抽象类

        GenericServlet 实现了 Servlet 接口,并提供了除 service() 方法以外的其他四个方法的简单实现。通过继承 GenericServlet 类创建 Servlet ,只需要重写 service() 方法即可,大大减少了创建 Servlet 的工作量。

        GenericServlet 类中还提供了以下方法,用来获取 Servlet 的配置信息。

方法

描述

String getInitParameter(String name) 返回名字为 name 的初始化参数的值,初始化参数在 web.xml 中进行配置。如果参数不存在,则返回 null。
Enumeration<String>    getInitParameterNames() 返回 Servlet 所有初始化参数的名字的枚举集合,若 Servlet 没有初始化参数,返回一个空的枚举集合。
ServletContext getServletContext() 返回 Servlet 上下文对象的引用。
String getServletName() 返回此 Servlet 实例的名称。


        示例:

 1             package com.example;
 2 
 3             import javax.servlet.*;
 4             import java.io.IOException;
 5             import java.io.PrintWriter;
 6 
 7             public class TwoServlet extends GenericServlet {
 8 
 9                 @Override
10                 public void service(ServletRequest servletRequest, ServletResponse servletResponse)
11                         throws ServletException, IOException {
12                     servletResponse.setContentType("text/html;charset=UTF-8");
13 
14                     PrintWriter writer = servletResponse.getWriter();
15                     writer.write("TwoServlet extends GenericServlet");
16                     writer.close();
17                 }
18 
19             }


    3) HttpServlet 抽象类

        HttpServlet 继承了 GenericServlet 抽象类,用于开发基于 HTTP 协议的 Servlet 程序。由于 Servlet 主要用来处理 HTTP 的请求和响应,所以通常情况下,编写的 Servlet 类都继承自 HttpServlet。

        在 HTTP/1.1 协议中共定义了 7 种请求方式,即 GET、POST、HEAD、PUT、DELETE、TRACE 和 OPTIONS。

        HttpServlet 针对这 7 种请求方式分别定义了 7 种方法,即 doGet()、doPost()、doHead()、doPut()、doDelete()、doTrace() 和 doOptions()。

        HttpServlet 重写了 service() 方法,该方法会先获取客户端的请求方式,然后根据请求方式调用对应 doXxx 方法。

        示例:

 1             package com.example;
 2 
 3             import javax.servlet.ServletException;
 4             import javax.servlet.http.HttpServlet;
 5             import javax.servlet.http.HttpServletRequest;
 6             import javax.servlet.http.HttpServletResponse;
 7             import java.io.IOException;
 8             import java.io.PrintWriter;
 9 
10             public class ThreeServlet extends HttpServlet {
11 
12                 public void doGet(HttpServletRequest req, HttpServletResponse resp)
13                                     throws ServletException, IOException {
14 
15                     resp.setContentType("text/html;charset=UTF-8");
16                     PrintWriter writer = resp.getWriter();
17                     writer.write("ThreeServlet extends HttpServlet");
18                     writer.close();
19                 }
20 
21                 public void doPost(HttpServletRequest req, HttpServletResponse resp)
22                                     throws ServletException, IOException {
23 
24                     //doGet(req, resp);
25                 }
26 
27             }   


    4)总结

        上面三种创建 Servlet 的方式,下面分析和对比一下。

        (1) Servlet 接口

            通过实现 Servlet 接口创建 Servlet 类,需要重写其全部的方法,比较繁琐,所以我们很少使用该方法创建 Servlet。

        (2) GenericServlet 类

            GenericServlet 抽象类实现了 Servlet 接口,并对 Servlet 接口中除 service() 方法外的其它四个方法进行了简单实现。通过继承 GenericServlet 创建 Servlet,只需要重写 service() 方法即可,大大减少了创建 Servlet 的工作量。

            Generic 是“通用”的意思,正如其名,GenericServlet 是一个通用的 Servlet 类,并没有针对某种场景进行特殊处理,尤其是 HTTP 协议,我们必须手动分析和封装 HTPP 协议的请求信息和响应信息。

        (3) HttpServlet 类

            HttpServlet 是 GenericServlet 的子类,它在 GenericServlet 的基础上专门针对 HTPP 协议进行了处理。HttpServlet 为 HTTP 协议的每种请求方式都提供了对应的方法,名字为 doXxx(),例如:

                处理 GET 请求的方法为 doGet();
                处理 POST 请求的方法为 doPost()。

            HttpServlet 就是专为 HTTP 协议而量身打造的 Servlet 类。

            使用最频繁的就是 GET 方式和 POST 方式,因此,我们通常基于 HttpServlet 来创建 Servlet 类,这样就省去了处理 HTTP 请求的过程。

2. Servlet生命周期

    在 javax.servlet.Servlet 接口中定义了 3 个方法:init()、service()、destory(),它们分别在 Servlet 生命周期的不同阶段被 Servlet 容器调用。Servlet 的生命周期由 Servlet 容器管理,主要分为以下 3 个阶段:

    1) 初始化阶段

        Servlet 初始化是其生命周期的第一个阶段,也是其他阶段的基础。只有完成了初始化,Servlet 才能处理来自客户端的请求。Servlet 初始化阶段分为以下 2 步:

            (1) 加载和实例化 Servlet

                Servlet 容器负责加载和实例化 Servlet。当容器启动或首次请求某个 Servlet 时,容器会读取 web.xml 或 @WebServlet 中的配置信息,对指定的 Servlet 进行加载。加载成功后,容器会通过反射对 Servlet 进行实例化。

                因为 Servlet 容器是通过 Java 的反射 API 来创建 Servlet 实例的,需要调用 Servlet 的默认构造方法(default constructor,即不带参数的构造方法),所以在编写 Servlet 类时,不能只提供一个带参数的构造方法。

           (2) 调用 init() 方法进行初始化

                加载和实例化完成后,Servlet 容器调用 init() 方法初始化 Servlet 实例。

                初始化的目的:让 Servlet 实例在处理请求之前完成一些初始化工作,例如建立数据库连接,获取配置信息等。

                在 Servlet 的整个生命周期内,init() 方法只能被调用一次。

        初始化期间,Servlet 实例可以通过 ServletConfig 对象获取在 web.xml 或者 @WebServlet 中配置的初始化参数。

        load-on-startup 是 web.xml 中的一个节点,是 servlet 元素的子元素,用来标记 Servlet 容器启动时是否初始化当前 Servlet,以及当前 Servlet 的初始化顺序。

        load-on-startup 元素取值规则如下:

            (1) 它的取值必须是一个整数;
            (2) 当值小于 0 或者没有指定时,则表示容器在该 Servlet 被首次请求时才会被加载;
            (3) 当值大于 0 或等于 0 时,表示容器在启动时就加载并初始化该 Servlet,取值越小,优先级越高;
            (4) 当取值相同时,容器就会自行选择顺序进行加载。

        @WebServlet 注解的 loadOnStartup 属性与 web.xml 中的 load-on-startup 元素相对应,取值的规则和含义相同。

    2) 运行时阶段

        运行时阶段是 Servlet 生命周期中最重要的阶段。Servlet 容器接收到来自客户端请求时,容器会针对该请求分别创建一个 ServletRequst 对象和 ServletResponse 对象,将它们以参数的形式传入 service() 方法内,并调用该方法对请求进行处理。

        这里需要注意的是,执行 service() 方法前,init() 方法必须已成功执行。

        在 service() 方法中,Servlet 通过 ServletRequst 对象获取客户端的相关信息和请求信息。在请求处理完成后,通过 ServletResponse 对象将响应信息进行包装,返回给客户端。当 Servlet 容器将响应信息返回给客户端后,ServletRequst 对象与 ServletResponse 对象就会被销毁。

        在 Servlet 的整个生命周期内,对于 Servlet 的每一次请求,Servlet 容器都会调用一次 service() 方法,并创建新的 ServletRequest 和 ServletResponse 对象。即 service() 方法在 Servlet 的整个生命周期中会被调用多次。

    3) 销毁阶段

        当 Servlet 容器关闭、重启或移除 Servlet 实例时,容器就会调用 destory() 方法,释放该实例使用的资源,例如:关闭数据库连接,关闭文件的输入流和输出流等,随后该实例被 Java 的垃圾收集器所回收。

        对于每个 Servlet 实例来说,destory() 方法只能被调用一次。

    在 Servlet 的整个生命周期中,创建 Servlet 实例、init() 方法和 destory() 方法都只执行一次。当初始化完成后,Servlet 容器会将该实例保存在内存中,通过调用它的 service() 方法,为接收到的请求服务。

    示例:

 1         package com.example;
 2 
 3         import javax.servlet.ServletException;
 4         import javax.servlet.http.HttpServlet;
 5         import javax.servlet.http.HttpServletRequest;
 6         import javax.servlet.http.HttpServletResponse;
 7         import java.io.IOException;
 8         import java.io.PrintWriter;
 9 
10         public class ThreeServlet extends HttpServlet {
11             private static final long serialVersionUID = 1L;
12             private int initCount = 0;
13             private int httpCount = 0;
14             private int destoryCount = 0;
15 
16             @Override
17             public void destroy() {
18                 super.destroy();
19                 System.out.println("ThreeServlet -> destroy(): destoryCount = " + (++destoryCount));
20             }
21 
22             @Override
23             public void init() throws ServletException {
24                 super.init();
25                 System.out.println("ThreeServlet -> init(): initCount = " + (++initCount));
26             }
27 
28             public void doGet(HttpServletRequest req, HttpServletResponse resp)
29                                 throws ServletException, IOException {
30                 System.out.println("ThreeServlet -> doGet():httpCount = " + (++httpCount));
31 
32                 resp.setContentType("text/html;charset=UTF-8");
33                 PrintWriter writer = resp.getWriter();
34 
35                 writer.write("ThreeServlet -> doGet()<br/>initCount: " + initCount + "<br/>"
36                                     + "httpCount: " + httpCount + "<br/>"
37                                     + "destoryCount: " + destoryCount);
38             }
39 
40             public void doPost(HttpServletRequest req, HttpServletResponse resp)
41                                 throws ServletException, IOException {
42 
43                 //doGet(req, resp);
44             }
45 
46         }


3. Servlet注解

    在 Servlet 中,web.xml 扮演的角色十分的重要,它可以将所有的 Servlet 的配置集中进行管理,但是若项目中 Servelt 数量较多时,web.xml 的配置会变得十分的冗长。这种情况下,注解(Annotation)就是一种更好的选择。

    与 XML 不同,注解不需要依赖于配置文件,它可以直接在类中使用,其配置只对当前类有效,这样就避免了集中管理造成的配置冗长问题。那么 Servelt 支持注解吗?

    为了简化 Servlet 的配置,Servlet 3.0 中增加了注解支持,例如:@WebServlet、@WebInitParm 、@WebFilter 和 @WebLitener 等,这使得 web.xml 从 Servlet 3.0 开始不再是必选项了。下面我们对 @WebServlet 进行介绍。

    1) @WebServlet 注解的属性

        @WebServlet 用于将一个类声明为 Servlet,该注解会在部署时被容器处理,容器根据其具体的属性配置将相应的类部署为 Servlet。该注解具有下表给出的一些常用属性。

属性名 类型 标签  描述
name String <servlet-name> 指定 Servlet 的 name 属性。如果没有显式指定,则取值为该 Servlet 的完全限定名,即包名+类名。
value String[] <url-pattern> 等价于 urlPatterns 属性,两者不能同时指定。如果同时指定,通常是忽略 value 的取值。
urlPatterns String[] <url-pattern> 指定一组 Servlet 的 URL 匹配模式。
loadOnStartup int <load-on-startup> 指定 Servlet 的加载顺序。
initParams WebInitParam[] <init-param> 指定一组 Servlet 初始化参数。
asyncSupported boolean <async-supported> 声明 Servlet 是否支持异步操作模式。
description String <description> 指定该 Servlet 的描述信息。
displayName String <display-name> 指定该 Servlet 的显示名。


    2) @WebServlet 注解的使用

        (1) 启用注解支持

            web.xml 的顶层标签 <web-app> 中有一个属性:metadata-complete,该属性用于指定当前 web.xml 是否是完全的。若该属性设置为 true,则容器在部署时将只依赖 web.xml,忽略所有的注解。若不配置该属性,或者将其设置为 false,则表示启用注解支持。

                <?xml version="1.0" encoding="UTF-8"?>
                <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
                    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
                    id="WebApp_ID" metadata-complete="false" version="4.0">
                    <!-- metadata-complete取值为true,表示关闭注解支持 -->
                    <!-- metadata-complete取值为false,表示启用注解支持 -->
                </web-app>

            由于 metadata-complete 属性的默认值是 false,即默认启用 Servlet 注解支持,所以默认情况下,使用该注解时,不必创建 web.xml 文件。

        (2) 使用 @WebServlet 注解

            @WebServlet 属于类级别的注解,标注在继承了 HttpServlet 的类之上。常用的写法是将 Servlet 的相对请求路径(即 value)直接写在注解内,如下所示。

                @Web​Servlet("/four")

            该写法省略了 urlPatterns 属性名,其完整的写法如下所示。

                @WebServlet(​urlPatterns = "/four")。

            注意事项:

                a) 通过实现 Serlvet 接口或继承 GenericServlet 创建的 Servlet 类无法使用 @WebServlet 注解。
                b) 使用 @WebServlet 注解配置的 Servlet 类,不要在 web.xml 文件中再次配置该 Servlet 相关属性。若同时使用 web.xml 与 @WebServlet 配置同一 Servlet 类,则 web.xml 中 <servlet-name> 的值与注解中 name 取值不能相同,否则容器会忽略注解中的配置。

    3) @WebServlet 注解 和 web.xml 的优缺点

        (1) @WebServlet 注解配置 Servlet

            优点:@WebServlet 直接在 Servlet 类中使用,代码量少,配置简单。每个类只关注自身业务逻辑,与其他 Servlet 类互不干扰,适合多人同时开发。

            缺点:Servlet 较多时,每个 Servlet 的配置分布在各自的类中,不便于查找和修改。

        (2) web.xml 配置文件配置 Servlet

            优点:集中管理 Servlet 的配置,便于查找和修改。

            缺点:代码较繁琐,可读性不强,不易于理解。

    示例:

 1         package com.example;
 2 
 3         import java.io.IOException;
 4         import java.io.OutputStream;
 5 
 6         import javax.servlet.ServletConfig;
 7         import javax.servlet.ServletException;
 8         import javax.servlet.annotation.WebInitParam;
 9         import javax.servlet.annotation.WebServlet;
10         import javax.servlet.http.HttpServlet;
11         import javax.servlet.http.HttpServletRequest;
12         import javax.servlet.http.HttpServletResponse;
13 
14         @WebServlet(asyncSupported = true,
15                     name = "FourServlet",
16                     description = "Four servlet description",
17                     loadOnStartup = 3,
18                     urlPatterns = {
19                         "/four",
20                         "/four/"
21                     },
22                     initParams = {
23                         @WebInitParam(name = "param1", value = "param value 1"),
24                         @WebInitParam(name = "param2", value = "param value 2")
25                     })
26         public class FourServlet extends HttpServlet {
27             private static final long serialVersionUID = 1L;
28 
29             protected void doGet(HttpServletRequest request, HttpServletResponse response)
30                     throws ServletException, IOException {
31 
32                 ServletConfig config = getServletConfig();
33                 String strOutPut = config.getServletName() + " -> doGet()<br/>";
34                 strOutPut +=  "param1: " + config.getInitParameter("param1") + "<br/>";
35                 strOutPut += "param2: " + config.getInitParameter("param2") + "<br/>";
36 
37                 response.setContentType("text/html;charset=UTF-8");
38                 OutputStream os = response.getOutputStream();
39                 os.write(strOutPut.getBytes());
40 
41             }
42 
43             protected void doPost(HttpServletRequest request, HttpServletResponse response)
44                     throws ServletException, IOException {
45             }
46 
47         }

 

posted @ 2022-03-14 22:10  垄山小站  阅读(357)  评论(0)    收藏  举报