跳转至
内容字体
东观体
上图东观体
OPPO Sans
江城黑体
霞鹜臻楷
代码字体
DejaVu Sans Mono
DejaVu Sans Mono
Google Sans Code
JetBrains Mono
主题切换
返回顶部

Servlet开发

约 7644 个字 641 行代码 6 张图片 预计阅读时间 33 分钟

动态资源和静态资源介绍

静态资源:无需在程序运行时通过代码运行生成的资源,在程序运行之前就写好的资源。例如:HTML、CSS、JS、图片、音频文件和视频文件

动态资源:需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据,运行时动态生成。例如Servlet、Thymeleaf……

Note

动态资源指的不是视图上的动画效果或者是简单的人机交互效果

Servlet简介和工作流程介绍

Servlet工作流程可以大致如下图所示:

image-20241204145352774

基本过程如下:

  1. 客户端向服务器端发出请求,服务器端的软件Tomcat接收到用户请求后会将请求报文的信息转换为HttpServletRequest对象,该对象中包含着请求中的所有信息,例如请求头、请求行。需要注意,这一过程中的HttpServletRequest对象并不是由程序员手动创建的,而是由Tomcat自动创建,并且此时除了存在HttpServletRequest对象以外,还有一个HttpServletResponse对象,该对象用于存储响应报文信息
  2. 在整个过程中,Tomcat根据请求中的URL路径找到指定的Servlet类,此时将类UserServlet实例化,调用其service方法,同时传递实参给HttpServletRequest reqHttpServletResponse resp,此时在service方法中需要处理请求的信息,再将处理结果存储到响应对象resp中返回给客户端即可

Servlet初使用

有了前面对Servlet工作流程的简单了解,接下来就可以根据上面的流程分析出以下的步骤:

  1. 创建一个类并且实现Servlet接口
  2. 重写其中的service方法
  3. 获取客户端的请求信息
  4. 处理客户端的请求信息(处理业务)
  5. 将处理结果放入响应对象中

Note

需要注意,整个过程中的两个对象HttpServletRequestHttpServletResponse都是引入传递,所以service方法不需要返回值

根据上面的代码就可以写出下面的UserServlet类:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class UserServlet implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {}

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {}

    @Override
    public String getServletInfo() {
        return "";
    }

    @Override
    public void destroy() {}
}

但是实际上只需要使用到service方法,而不需要重写其他方法,但是因为Servlet本身是一个接口,其中的所有方法都是抽象方法,所以直接实现该接口的子类UserServlet就必须重写所有方法:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

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

    String getServletInfo();

    void destroy();
}

为了避免这种问题,可以考虑使用继承,但并不是继承Servlet接口,而是继承其子类HttpServlet,其子类HttpServlet继承自GenericServletGenericServlet继承自Servlet,而因为HttpServlet实现了Servlet中的所有接口,所以此时UserServlet继承该子类就可以按需重写对应的方法,此时上面的UserServlet就可以修改为如下代码:

Java
1
2
3
4
5
6
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}

现在有一个需求:判断用户输入的用户名是否是admin,如果是admin,就提示Wrong Username,否则提示Correct Username

根据需求,首先需要一个页面获取用户的输入信息:

HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>校验</title>
</head>
<body>
<!--  action填写映射地址  -->
<!-- get请求时,键值对放置在url后面,即url?username=值 -->
<!-- post请求时,键值对放置在请求体中 -->
<form action="" method="get">
    用户:<input type="text" name="username" placeholder="请输入用户名"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

在上面的页面中,创建了一个form表单,但是其action属性暂时留空,因为需要先处理好请求映射路径,再将该路径作为action的值,method可以为get也可以为post

接着编写Java代码,实现一个UserServlet类,该类继承自HttpServlet并重写service方法,根据前面的分析,在这个方法中需要进行三步:

  1. 根据name的值username获取到用户输入框中的内容
  2. 判断输入框内容是否是admin,如果是,则向客户端响应Wrong Username,否则响应Correct Usernamme
  3. 将结果写入响应对象中

根据上面的三步,细化到代码中的步骤如下:

  1. 调用请求对象req的方法getParameter("username")获取到输入框中的值。之所以可以这样获取,本质是因为不论是get请求还是post请求,输入框传递的参数都是键值对的形式,获取时只要有了键,就可以根据这个键获取其对应的值
  2. 判断getParameter方法的返回值是否与admin相等,如果相等,说明用户输入的内容是admin,此时结果为Wrong Username,否则结果为Correct Username,因为需要将结果存储到响应对象,所以此处还需要一个变量存储最终结果
  3. 调用响应对象的getWriter()方法创建一个向响应体中打印字符的响应流,将结果变量的值写入到响应流中

示例代码如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取req中的内容
        String username = req.getParameter("username");
        String ret = "<h1>Correct Username</h1>";
        // 业务处理
        if(username.equals("admin")){
            ret = "<h1>Wrong Username</h1>";
        }

        // 将内容转换为写入响应报文中
        PrintWriter writer = resp.getWriter();
        writer.write(ret);
    }
}

有了页面和处理请求的UserServlet,接下来就是让页面向该UserServlet发送请求,此时就需要编写Servlet请求映射路径,这个请求路径在WEB-INF文件夹中的web.xml文件中编写,编写步骤如下:

  1. 创建<servlet></servlet>标签,其中有两个子标签:
    1. <servlet-name></servlet-name>:表示目标Servlet类的别名
    2. <servlet-class></servlet-class>:表示目标Servlet类的全路径名
  2. <servlet></servlet>标签下创建一个兄弟标签<servlet-mapping></servlet-mapping>,其中有两个子标签:
    1. <servlet-name></servlet-name>:表示需要指向的目标Servlet类的别名,与<servlet></servlet>中的<servlet-name></servlet-name>内容相同
    2. <url-pattern></url-pattern>:表示目标Servlet类的映射路径

例如下面的代码:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <!--  配置UserServlet映射  -->
    <servlet>
        <!--    UserServlet别名    -->
        <servlet-name>userServlet</servlet-name>
        <!--   UserServlet类全路径名     -->
        <servlet-class>UserServlet</servlet-class>
    </servlet>

    <!-- userServlet映射   -->
    <servlet-mapping>
        <!--   指定是哪一个userServlet需要映射地址     -->
        <servlet-name>userServlet</servlet-name>
        <!--   配置访问地址     -->
        <!-- /前面的就是url -->
        <url-pattern>/userServlet</url-pattern>
    </servlet-mapping>
</web-app>

有了请求映射地址,现在就可以完善HTML中的form标签中的action属性值,注意action属性值不要带请求映射地址的/

HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>校验</title>
</head>
<body>
<!--  action填写映射地址  -->
<!-- get请求时,键值对放置在url后面,即url?username=值 -->
<!-- post请求时,键值对放置在请求体中 -->
<form action="userServlet" method="get">
    用户:<input type="text" name="username" placeholder="请输入用户名"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

编写完上面的内容后,配置好Tomcat运行环境就可以启动Tomcat服务器测试效果。需要注意,因为当前并没有提及Ajax,所以此时服务器端响应的结果会在一个新页面展示

响应体中的Content-Type

Content-Type是一种MIME类型的响应头(如果是上传文件,则请求体中也会有Content-Type)内容,MIME类型告诉客户端从服务器端响应的数据类型,从而使客户端可以正常对响应的数据进行解码

如果使用上面的UserServlet进行测试会发现,在响应头中不存在这个Content-Type,此时客户端默认就是按照HTML进行解析,而HTML对应的Content-Type就是text/html

例如下面的示例响应头:

Text Only
1
2
3
4
5
HTTP/1.1 200
Content-Length: 23
Date: Thu, 05 Dec 2024 02:39:07 GMT
Keep-Alive: timeout=20
Connection: keep-alive

既然是默认以HTML解析,那么此时也就可以说明为什么前面在写UserServlet类时,在返回的结果字符串中使用了<h1></h1>标签可以正常被浏览器识别为一级标题

实际上,在一般的响应中都会有指定的Content-Type值,而这个值就是根据响应的文件后缀在Tomcat的web.xml文件中的<mime-mapping></mime-mapping>标签中的<extension></extension>内容进行比对,如果比对成功就使用其<mime-type></mime-type>的内容作为Content-Type的值。但是在web.xml文件中无法指定动态资源的后缀名,也就无法确定UserServlet类的Content-Type值了

但是此时如果服务器端向客户端响应的内容无法使用HTML进行解析,就会出现文件无法正常在客户端呈现,所以在编写代码中要指定Content-Type的值。在UserServlet类的service方法中,可以使用响应对象的setHeader()方法,其有两个参数,第一个参数表示响应头中内容的键,第二个参数表示键对应的值,使用这个方法改写前面的UserServlet类中的service方法如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取req中的内容
        String username = req.getParameter("username");
        String ret = "<h1>Correct Username</h1>";
        // 业务处理
        if(username.equals("admin")){
            ret = "<h1>Wrong Username</h1>";
        }

        // 设置Content-Type
        resp.setHeader("Content-Type", "text/html");

        // 将内容转换为写入响应报文中
        PrintWriter writer = resp.getWriter();
        writer.write(ret);
    }
}

此时再运行Tomcat,在浏览器就可以看到响应头中存在一个Content-Type:

Text Only
1
2
3
...
Content-Type: text/html;charset=UTF-8
...

除了可以使用setHeader()方法以外,还可以直接使用响应对象的setContentType()方法,参数传递对应的值即可

url-pattern的特殊写法

前面在配置<servlet-mapping></servlet-mapping>时提到请求映射地址,对应的标签就是<url-pattern></url-pattern>,但是当时只是简单的给出了最基本的格式,实际上,这个格式的写法根据需要的匹配模式决定,分为两种匹配模式:

  1. 精准匹配:每一个字符都必须完全一样,例如:/servlet1
  2. 模糊匹配:部分字符一样,其他的字符随意,主要用到通配符*,常见有三种形式:

    1. /:表示匹配全部,但是不包括JSP文件
    2. /*:表示匹配全部且包括JSP文件
    3. /任意内容/*:表示精确匹配「任意内容」,但是其斜线后面的内容可以随意
    4. *.后缀名:表示匹配所有满足指定后缀的内容

url-pattern存在两个特点:

  1. 一个Servlet可以对应多个url-pattern,但是这些url-pattern不能相同
  2. 多个Servlet中的url-pattern彼此不能相同

尽管url-pattern可以进行模糊匹配,但是大部分情况下还是使用精准匹配,并且一般情况下一个Servlet会对应一个url-pattern。因为当前的前后端文件是自己进行编写,所以自己可以自定义,但是在实际开发中,因为前后端分离,所以需要前后端相互约定该路径

使用@WebServlet注解

前面在Servlet初使用时在web.xml进行了Servlet请求映射地址,但是如果有多个Servlet,这个过程难免会有些繁琐并且如果有太多的Servlet也有可能存在忘记配置某一个Servlet的情况,为了解决这个问题,可以使用@WebServlet注解

下面是使用注解的方式指定UserServlet的请求映射路径示例:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用@WebServlet注解配置请求映射地址
@WebServlet("/userServlet")
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取req中的内容
        String username = req.getParameter("username");
        String ret = "<h1>Correct Username</h1>";
        // 业务处理
        if(username.equals("admin")){
            ret = "<h1>Wrong Username</h1>";
        }

        // 设置Content-Type
        resp.setHeader("Content-Type", "text/html");

        // 将内容转换为写入响应报文中
        PrintWriter writer = resp.getWriter();
        writer.write(ret);
    }
}

@WebServlet中,直接写的值就相当于<url-pattern></url-pattern>中的内容,格式和规则也是相同的(参考上面的url-pattern的格式),除了写映射路径,还可以指定Servlet的别名name值以及loadOnStartup的值,下面是@WebServlet的部分源码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public @interface WebServlet {
    String name() default "";

    String[] value() default {};

    String[] urlPatterns() default {};

    int loadOnStartup() default -1;

    // ...
}

其中:

  1. name:表示Servlet别名
  2. valueurlPatterns:表示Servlet请求映射地址,因为一个Servlet可以配置多个映射地址,所以此处是一个String数组。注意,如果设置了value值,就不要设置urlPatterns的值,二者相互排斥,每一次只会有一个生效。之所以会有value是因为value是存在默认值的,所以使用该注解给value赋值时,可以不用写value属性名,并且因为value被设计成可以不写属性名直接赋值
  3. loadonStartup:表示Servlet是否在项目加载时实例化,如果其值为负数,则表示否,否则表示是,并且此时的值表示初始化的顺序(在下面Servlet声明周期会提到)

Servlet生命周期

对象生命周期:应用程序中的对象不仅在空间上有层次结构的关系,在时间上也会因为处于程序运行过程中的不同阶段而表现出不同状态和不同行为

Servlet容器:Servlet对象是Servlet容器创建的,生命周期方法都是由容器(目前我们使用的是Tomcat)调用的。这一点和之前所编写的代码有很大不同,在之后越来越多的对象交给容器或框架来创建,越来越多的方法由容器或框架来调用,开发人员要尽可能多的将精力放在业务逻辑的实现上

Servlet主要的生命周期执行特点

生命周期 对应方法 执行时机 执行次数
构造对象 构造器 第一次请求或者容器启动 1
初始化 init() 构造完毕后 1
处理服务 service(HttpServletRequest req,HttpServletResponse resp) 每次请求 多次(具体次数取决于请求次数)
销毁 destory() 容器关闭 1

可以使用下面的代码测试Servlet的生命周期问题:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@WebServlet("/s1")
public class ServletLifeCycle extends HttpServlet {

    public ServletLifeCycle() {
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("Servlet--init");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet--service");
    }

    @Override
    public void destroy() {
        System.out.println("Servlet--destroy");
    }
}

在地址栏中的URL后输入s1即可访问到ServletLifeCycle,可以看到第一次访问时打印下面的内容:

Text Only
1
2
Servlet--init
Servlet--service

在进行六次对该页面进行刷新的步骤后,可以看到下面的内容:

Text Only
1
2
3
4
5
6
Servlet--service
Servlet--service
Servlet--service
Servlet--service
Servlet--service
Servlet--service

点击结束Tomcat按钮后,可以看到下面的内容:

Text Only
1
Servlet--destroy

从上面的过程可以看出初始化方法和销毁方法都只会执行一次,并且执行时机分别在第一次访问页面以及结束Tomcat服务器,只有service方法在多次请求后会执行多次,与表中的结果对应

根据这个结果可以推出一个结论,一个Servlet类的对象在一次进程中是单例的,所有线程共享,如果当前Servlet类中有一个成员变量,并且在service方法中对该变量进行修改,这就可能会出现并发请求时的线程安全问题。如果加锁进行同步,那么就会出现延迟问题导致用户体验效果差,所以解决这个问题的关键还是尽可能不在service方法中对Servlet类中的成员变量进行修改

前面提到初始化方法会在第一次请求页面时进行,那么是否存在一种方式可以使得Servlet在启动Tomcat服务时就创建好?的确存在,就是使用@WebSerlet中的loadOnStartup,默认情况下laodOnStartup的值为-1,表示第一次请求时创建Servlet对象,如果希望在启动Tomcat服务时创建好Servlet对象,那么就建议将该参数的值设置为6及以后

Note

理论上来说,loadOnstartup的值可以设置为1及以后的数,并且就算是出现冲突,Tomcat也会权衡哪一个对象先创建,但是还是建议不要与默认的laodOnStartup值冲突,具体可以看web.xml文件中的<load-on-startup></laod-on-otartup>中有多少个。在当前Tomcat10中,一共占用了1、2、3、4、5,所以建议将loadOnStartup参数设置为6及以后

下面提供两种设置loadOnStartup值的方式:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
        version="6.0">
    <servlet>
        <servlet-name>servletLifeCycle</servlet-name>
        <servlet-class>ServletLifeCycle</servlet-class>
        <!-- 配置Servlet对象实例化的顺序 -->
        <load-on-startup>6</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>servletLifeCycle</servlet-name>
        <url-pattern>/s1</url-pattern>
    </servlet-mapping>
</web-app>
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 配置Servlet对象实例化的顺序和请求映射地址
@WebServlet(value = "/s1", loadOnStartup = 6)
public class ServletLifeCycle extends HttpServlet {

    public ServletLifeCycle() {
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("Servlet--init");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet--service");
    }

    @Override
    public void destroy() {
        System.out.println("Servlet--destroy");
    }
}

使用其中一种方式进行前面的执行步骤,先启动Tomcat服务而不请求s1时,当前控制台输出结果如下:

Text Only
1
Servlet--init

接着请求6次s1,输出如下:

Text Only
1
2
3
4
5
6
Servlet--service
Servlet--service
Servlet--service
Servlet--service
Servlet--service
Servlet--service

结束Tomcat服务,输出如下:

Text Only
1
Servlet--destroy

需要注意,接下来的问题联系到后面的SpringMVC框架。观察web.xml文件中<load-on-startup>1</load-on-startup>,这个意味着别名为defaultServlet类(后面称为default-servlet)在Tomcat启动时第一个被实例化,对应的url-pattern如下:

XML
1
2
3
4
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

可以看到,其值为/,这个与请求的资源是动态还是静态有关。如果请求的资源是静态资源,则可能不存在一个动态资源对应的自定义Servlet类,如果不存在,此时就会走到default-servlet,而这个Servlet类就会去找指定的静态资源,接着使用IO流将该静态资源读取到响应对象中,再设置好需要的内容,最后转换成报文发送给客户端。而这里之所以和SpringMVC有关是因为SpringMVC默认会提供一个Servlet类,此时会把Tomcat提供的default-servlet给覆盖掉,导致default-servlet的功能失效,此时再请求静态资源,就会出现找不到静态资源的问题。所以在后面的SpringMVC中,如果没有进行前后端分离,就需要额外配置使default-servlet重新生效

Servlet继承结构

Servlet接口

Servlet源码
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

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

    String getServletInfo();

    void destroy();
}

接口及方法说明:

Servlet 规范接口,所有的Servlet必须实现

  1. public void init(ServletConfig config) throws ServletException;:初始化方法,容器在构造Servlet对象后,自动调用的方法,容器负责实例化一个ServletConfig对象,并在调用该方法时传入,其中ServletConfig对象可以为Servlet提供初始化参数

  2. public ServletConfig getServletConfig();:获取ServletConfig对象的方法,后续可以通过该对象获取Servlet初始化参数

  3. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;:处理请求并做出响应的服务方法,每次请求产生时由容器调用。容器创建一个ServletRequest对象和ServletResponse对象,容器在调用service方法时,传入这两个对象

  4. public String getServletInfo();:获取ServletInfo信息的方法

  5. public void destroy();Servlet实例在销毁之前调用的方法

GenericServlet抽象类

GenericServlet源码
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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();
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        return this.getServletConfig().getServletContext();
    }

    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) {
        ServletContext var10000 = this.getServletContext();
        String var10001 = this.getServletName();
        var10000.log(var10001 + ": " + message);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

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

    public String getServletName() {
        return this.config.getServletName();
    }
}

解释如下:

GenericServlet抽象类是对Servlet接口一些固定功能的粗糙实现,以及对service方法的再次抽象声明,并定义了一些其他相关功能方法:

  1. private transient ServletConfig config;:初始化配置对象作为属性
  2. public GenericServlet() {}:构造器,为了满足继承而准备
  3. public void destroy() {}:销毁方法的平庸实现(无方法体实现抽象方法)
  4. public String getInitParameter(String name):获取初始参数的快捷方法
  5. public Enumeration<String> getInitParameterNames():返回所有初始化参数名的方法
  6. public ServletConfig getServletConfig():获取初始Servlet初始配置对象ServletConfig的方法
  7. public ServletContext getServletContext():获取上下文对象ServletContext的方法
  8. public String getServletInfo():获取Servlet信息的平庸实现
  9. public void init(ServletConfig config) throws ServletException():初始化方法的实现,并在此调用了init的重载方法
  10. public void init() throws ServletException:重载init方法,为了让我们自己定义初始化功能的方法
  11. public void log(String msg)public void log(String message, Throwable t):打印日志的方法及重载
  12. public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;:服务方法再次声明
  13. public String getServletName() :获取ServletName的方法

HttpServlet抽象类

HTTPServlet部分源码
Java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
public abstract class HttpServlet extends GenericServlet {
    // ...
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_get_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (!DispatcherType.INCLUDE.equals(req.getDispatcherType()) && this.cachedUseLegacyDoHead) {
            NoBodyResponse response = new NoBodyResponse(resp);
            this.doGet(req, response);
            if (req.isAsyncStarted()) {
                req.getAsyncContext().addListener(new NoBodyAsyncContextListener(response));
            } else {
                response.setContentLength();
            }
        } else {
            this.doGet(req, resp);
        }

    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_post_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_put_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_delete_not_supported");
        this.sendMethodNotAllowed(req, resp, msg);
    }

    private void sendMethodNotAllowed(HttpServletRequest req, HttpServletResponse resp, String msg) throws IOException {
        String protocol = req.getProtocol();
        if (protocol.length() != 0 && !protocol.endsWith("0.9") && !protocol.endsWith("1.0")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String allow = this.getCachedAllowHeaderValue();
        if (HttpServlet.TomcatHack.getAllowTrace(req)) {
            if (allow.length() == 0) {
                allow = "TRACE";
            } else {
                allow = allow + ", TRACE";
            }
        }

        resp.setHeader("Allow", allow);
    }

    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String CRLF = "\r\n";
        StringBuilder buffer = (new StringBuilder("TRACE ")).append(req.getRequestURI()).append(' ').append(req.getProtocol());
        Enumeration<String> reqHeaderNames = req.getHeaderNames();

        while(true) {
            String headerName;
            do {
                if (!reqHeaderNames.hasMoreElements()) {
                    buffer.append(CRLF);
                    int responseLength = buffer.length();
                    resp.setContentType("message/http");
                    resp.setContentLength(responseLength);
                    ServletOutputStream out = resp.getOutputStream();
                    out.print(buffer.toString());
                    out.close();
                    return;
                }

                headerName = (String)reqHeaderNames.nextElement();
            } while(this.isSensitiveHeader(headerName));

            Enumeration<String> headerValues = req.getHeaders(headerName);

            while(headerValues.hasMoreElements()) {
                String headerValue = (String)headerValues.nextElement();
                buffer.append(CRLF).append(headerName).append(": ").append(headerValue);
            }
        }
    }

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }

        this.service(request, response);
    }

    // ...
}

解释如下:

  1. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException:对服务方法的实现。在该方法中,将请求和响应对象转换成对应HTTP协议的HttpServletRequestHttpServletResponse对象,调用重载的service方法
  2. public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException:重载的service方法,被重写的service方法所调用。在该方法中,通过请求方式判断,调用具体的do***方法完成请求的处理
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExceptionprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExceptionprotected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExceptionprotected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExceptionprotected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExceptionprotected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOExceptionprotected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException:对应不同请求方式的处理方法。除了doOptionsdoTrace方法,其他的do***方法都在故意响应错误信息

继承结构结论

自定义Servlet中,必须要对处理请求的方法进行重写,有两种重写方式:

  1. 重写service方法
  2. 重写doGet或者doPost方法,或者两种都重写,在调用时,如果doGetdoPost逻辑基本一致,只是因为请求方式不同,则可以在doGet方法或者doPost方法中调用另外一个即可

实际上,不论是第一种方式还是第二种方式,都没有什么本质的区别,唯一的区别就是第二种方式不会覆盖service方法中的错误处理,但是目前学习下不会遇到这些问题,也就可以不用深究。在后面使用框架后更不会关心这两种方法,所以使用哪一种方法都可以

ServletConfigServletContext

ServletConfig对象

ServletConfig为Servlet提供初始配置参数的一种对象,每个Servlet类都有自己独立唯一的ServletConfig对象。Tomcat容器会为每个Servlet类实例化一个ServletConfig对象,并通过Servlet生命周期的init方法传入给Servlet类对象作为属性

示意图如下:

ServletConfig是一个接口,定义了如下API:

方法名 作用
getServletName() 获取<servlet-name></servlet-name>定义的Servlet名称
getServletContext() 获取ServletContext对象
getInitParameter() 获取配置Servlet时设置的初始化参数,根据名字获取值
getInitParameterNames() 获取所有初始化参数名组成的Enumeration对象

源码如下:

Java
1
2
3
4
5
6
7
8
package jakarta.servlet;
import java.util.Enumeration;
public interface ServletConfig {
    String getServletName();
    ServletContext getServletContext();
    String getInitParameter(String var1);
    Enumeration<String> getInitParameterNames();
}

Note

Enumeration是旧式的迭代器,使用方式和现在的迭代器类似,其中有两个方法:

  1. hasMoreElements():判断是否还有下一个元素
  2. nextElement():获取下一个元素

为每一个Servlet类对象提供初始化参数有两种方式,一种是在web.xml文件中配置,另外一种就是使用@WebServlet注解:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
        version="6.0">
    <servlet>
        <servlet-name>servlet1</servlet-name>
        <servlet-class>Servlet1</servlet-class>
        <init-param>
            <param-name>key1</param-name>
            <param-value>value1</param-value>
        </init-param>
        <init-param>
            <param-name>key2</param-name>
            <param-value>value2</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>servlet1</servlet-name>
        <url-pattern>/s1</url-pattern>
    </servlet-mapping>
</web-app>
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@WebServlet(
        value = "/s1",
        initParams = {
                @WebInitParam(name = "key1", value = "value1"),
                @WebInitParam(name = "key2", value = "value2")
        })
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}

以注解的方式进行测试,测试代码如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@WebServlet(
        value = "/s1",
        initParams = {
                @WebInitParam(name = "key1", value = "value1"),
                @WebInitParam(name = "key2", value = "value2")
        })
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 调用GenericServlet类中的getServletConfig()方法获取ServletConfig对象
        ServletConfig servletConfig = getServletConfig();
        // 通过该对象获取其中的内容(已知某个键获取其值)
        String value = servletConfig.getInitParameter("key1");
        System.out.println(value);

        // 通过该对象获取所有内容(不知道具体有哪些键值对)
        // 获取所有键
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        // 遍历所有键
        while (initParameterNames.hasMoreElements()) {
            // 获取到键
            String s = initParameterNames.nextElement();
            // 根据键获取值
            System.out.println(s+":"+servletConfig.getInitParameter(s));
        }
    }
}

运行Tomcat服务器请求s1可以发现控制台输出如下:

Text Only
1
2
3
value1
key1:value1
key2:value2

ServletContext对象

ServletContext对象有称呼为上下文对象,或者叫应用域对象(后面会介绍域对象)。Tomcat容器会为每个APP创建一个独立的唯一的ServletContext对象。因为该对象为所有的Servlet所共享,所以其可以为所有的Servlet提供初始配置参数

ServletConfig对象和ServletContext对象的区别如下图所示:

ServletContext对象获取初始化参数的方法和ServletConfig对象的方法一致,此处不再赘述

因为ServletConfig对象是每一个Servlet类所独有的,所以在配置ServletConfig对象之前需要存在Servlet类,但是ServletContext对象不同,它不依赖认为一个Servlet类,所以在配置时通常在web.xml中配置,并且不放置在任何<servlet></servlet>中:

XML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
         version="6.0">
    <context-param>
        <param-name>key1</param-name>
        <param-value>value1_ServletContext</param-value>
    </context-param>
    <context-param>
        <param-name>key2</param-name>
        <param-value>value2_ServletContext</param-value>
    </context-param>
</web-app>

使用下面的Servlet类进行测试:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过ServletConfig对象获取ServletContext对象
        ServletConfig servletConfig = getServletConfig();
        ServletContext servletContext = servletConfig.getServletContext();
        // 也可以直接调用ServletContext()方法获取ServletContext对象,但是这个方法的本质也是上面的步骤
        // ServletContext servletContext1 = getServletContext();

        // 根据键获取值
        String value = servletContext.getInitParameter("key1");
        System.out.println(value);

        // 未知键时获取全部的键,再获取值
        Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
        while (initParameterNames.hasMoreElements()) {
            String s = initParameterNames.nextElement();
            System.out.println(s + ":" + servletContext.getInitParameter(s));
        }
    }
}

运行Tomcat并请求s1后可以看到控制台输出如下:

Text Only
1
2
3
value1_ServletContext
key1:value1_ServletContext
key2:value2_ServletContext

对于除了上面提到的获取键值对的方法外,ServletContext对象还有其他的API:

  1. getRealPath():用于获取资源的真实路径,也就是构建的资源在当前电脑的硬盘路径。在副本Tomcat中的conf->Catalina->localhost中的xml文件可以看到有个Context标签,其docBase值即为该方法的值。该方法可以传递一个参数,表示指定该目录下的子目录或者子文件
  2. getContextPath():用于获取资源的上下文路径,也就是访问指定资源时的路径。在与上面同一位置的xml文件中的Context标签的path值即为该方法的值
  3. 域对象API:在下面讲解域对象时具体说明

Note

getRealPath()方法的一个作用就是获取到构建的资源所处的位置,通过该位置找到其中的某一个子文件夹,如果直接写成固定的位置,那么换一个设备位置可能不一样,而使用getRealPath()方法就可以确保不包括子文件夹的父级路径是动态获取的

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取资源的真实路径
        String realPath = servletContext.getRealPath("upload");
        System.out.println(realPath);

        // 获取资源的上下文路径
        String contextPath = servletContext.getContextPath();
        System.out.println(contextPath);
    }
}

域对象

域对象:即存在作用范围的对象,这些对象一般被用于存储数据以及获取数据,而其中的作用范围就是「域」

在WebAPP中,存在三种域对象:

  1. 应用域
  2. 会话域
  3. 请求域

前面提到的ServletContext对象就是应用域对象,应用域是WebAPP中最大的域,所以其对象的作用范围也最广。一般来说,应用域对象可以在本Web应用中进行数据共享和传递

三大域对象都有下面的三种API:

API 功能解释
void setAttribute(String key,Object value); 向域中存储/修改数据
Object getAttribute(String key); 获得域中的数据
void removeAttribute(String key); 移除域中的数据

例如,当前应用中存在两个Servlet,分别是Servlet1Servlet2。二者按下面的顺序执行:

  1. Servlet1ServletContext对象中存储数据
  2. Servlet1取出ServletContext对象中的数据
  3. Servlet2ServletContext对象中读取数据
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向ServletContext对象中存储数据
        ServletContext servletContext = getServletContext();
        servletContext.setAttribute("key1", "value1");
        servletContext.setAttribute("key2", "value2");

        // 从Servlet1中获取ServletContext中的数据
        Enumeration<String> attributeNames = servletContext.getAttributeNames();
        while(attributeNames.hasMoreElements()) {
            String key = attributeNames.nextElement();
            System.out.println(key + ":" + servletContext.getAttribute(key));
        }
    }
}
Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@WebServlet("/s2")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 直接从ServletContext对象中获取数据
        ServletContext servletContext = getServletContext();
        Enumeration<String> attributeNames = servletContext.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            String key = attributeNames.nextElement();
            System.out.println(key + ":" + servletContext.getAttribute(key));
        }
    }
}

启动Tomcat服务器后查看控制台输出如下:

  1. 先访问Servlet1

    Text Only
    1
    2
    key1:value1
    key2:value2
    
  2. 再访问Servlet2

    Text Only
    1
    2
    key1:value1
    key2:value2
    

HttpServletRequest对象常见API

HttpServletRequest是一个接口,其父接口是ServletRequest,其对象是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入。HttpServletRequest代表客户端发来的请求,所有请求中的信息都可以通过该对象获得

其对象常见的API如下:

  • 获取请求行信息相关(请求方式,请求的URL和协议及版本)
API 功能解释
StringBuffer getRequestURL(); 获取客户端请求的URL
String getRequestURI(); 获取客户端请求项目中的具体资源
int getServerPort(); 获取客户端发送请求时的端口
int getLocalPort(); 获取本应用所在容器的端口
int getRemotePort(); 获取客户端程序的端口
String getScheme(); 获取请求协议
String getProtocol(); 获取请求协议及版本号
String getMethod(); 获取请求方式

Note

在上面的方法中,需要注意下面的两点:

    • URI(Uniform Resource Identifier,统一资源标识符)是不包括协议、IP和端口的资源路径
    • URL(Uniform Resource Locator,统一资源定位器)是包括协议、IP和端口的全路径

    URI和URL的区别:

    可以理解为URL是URI的一种扩展,其具有URI的内容(资源路径),也有URI不具有的内容(协议、IP和端口)。例如:http://127.0.0.1:8080/demo1/servlet1,其中URI为demo1/servlet1,URL为http://127.0.0.1:8080/demo1/servlet1

  1. 「客户端发送请求时的端口」和「本应用所在容器的端口」的区别:

    所谓「客户端发送请求时的端口」就是客户端发送请求时请求的服务器所在的端口,如果客户端和服务器端没有代理服务器,那么「客户端发送请求时的端口」和「本应用所在容器的端口」是一致的。但是如果存在代理服务器,则「客户端发送请求时的端口」就是代理服务器的端口号,此时可能就与「本应用所在容器的端口」不同

  • 获得请求头信息相关
API 功能解释
String getHeader(String headerName); 根据头名称获取请求头
Enumeration<String> getHeaderNames(); 获取所有的请求头名字
String getContentType(); 获取Content-Type请求头
  • 获得请求参数相关
API 功能解释
String getParameter(String parameterName); 根据请求参数名获取请求单个参数值
String[] getParameterValues(String parameterName); 根据请求参数名获取请求多个参数值数组
Enumeration<String> getParameterNames(); 获取所有请求参数名
Map<String, String[]> getParameterMap();` 获取所有请求参数的键值对集合
BufferedReader getReader() throws IOException; 获取读取请求体的字符输入流
ServletInputStream getInputStream() throws IOException; 获取读取请求体的字节输入流
int getContentLength(); 获得请求体长度的字节数
  • 其他API
API 功能解释
String getServletPath(); 获取请求的Servlet的映射路径
ServletContext getServletContext(); 获取ServletContext对象
Cookie[] getCookies(); 获取请求中的所有cookie
HttpSession getSession(); 获取Session对象
void setCharacterEncoding(String encoding); 设置请求体字符集

HttpServletResponse对象常见API

HttpServletResponse是一个接口,其父接口是ServletResponse。与HttpServletRequest对象一样,是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入。HttpServletResponse代表对客户端的响应该对象会被转换成响应的报文发送给客户端,通过该对象可以设置响应信息

其对象常见的API如下:

  • 设置响应行相关
API 功能解释
void setStatus(int code); 设置响应状态码
  • 设置响应头相关
API 功能解释
void setHeader(String headerName, String headerValue); 设置/修改响应头键值对
void setContentType(String contentType); 设置Content-Type响应头及响应字符集(设置MIME类型)
  • 设置响应体相关
API 功能解释
PrintWriter getWriter() throws IOException; 获得向响应体放入信息的字符输出流
ServletOutputStream getOutputStream() throws IOException; 获得向响应体放入信息的字节输出流
void setContentLength(int length); 设置响应体的字节长度,其实就是在设置Content-Length响应头
  • 其他API
API 功能解释
void sendError(int code, String message) throws IOException; 向客户端响应错误信息的方法,需要指定响应码和响应信息
void addCookie(Cookie cookie); 向响应体中增加cookie
void setCharacterEncoding(String encoding); 设置响应体字符集

请求转发和响应重定向

请求转发和响应重定向是Web应用中间接访问项目资源的两种手段,也是Servlet控制页面跳转的两种手段。其中,除了使用请求转发外,其他方式都不可以访问WEB-INF文件夹中的文件

请求转发通过HttpServletRequest实现,响应重定向通过HttpServletResponse实现

请求转发

请求转发逻辑图:

请求转发的特点:

  1. 请求转发通过HttpServletRequest对象获取请求转发器实现
  2. 请求转发是服务器内部的行为,对客户端是屏蔽的,所以客户端只发送了一次请求,客户端地址栏不变
  3. 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源。因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递
  4. 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转,但是请求转发不能转发到本项目以外的外部资源
  5. 请求转发可以转发给WEB-INF下受保护的资源

使用请求转发时需要使用到下面的方法:

  1. 使用HttpServletRequest对象的方法getRequestDispatcher()获取请求转发器,参数传递请求转发的资源路径
  2. 通过请求转发器调用forward()方法,参数传递HttpServletRequest对象和HttpServletResponse对象

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Sevlet1执行");

        // Servlet1请求转发Servlet2
        req.getRequestDispatcher("s2").forward(req, resp);
    }
}
Java
1
2
3
4
5
6
7
@WebServlet("/s2")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet2执行");
    }
}

配置Tomcat后启动可以看到控制台输出如下:

Text Only
1
2
Sevlet1执行
Servlet2执行

请求转发访问WEB-INF下的资源:

当前在WEB-INF下有一个test.html文件,其内容如下:

HTML
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
</head>
<body>
<h1>这是WEB-INF下的测试文件</h1>
</body>
</html>

对应的Servlet1代码如下:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Sevlet1执行");

        // Servlet1请求转发WEB-INF下的test.html文件
        req.getRequestDispatcher("WEB-INF/test.html").forward(req, resp);
    }
}

配置Tomcat后启动可以看到控制台输出和网页显示内容如下:

Text Only
1
Sevlet1执行

响应重定向

响应重定向逻辑图:

响应重定向的特点:

  1. 响应重定向通过HttpServletResponse对象的sendRedirect()方法实现,该方法传递目标资源路径
  2. 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,这个是客户端在服务端提示下的行为
  3. 客户端至少发送了两次请求,客户端地址栏是要变化的,并且因为服务端产生了多对HttpServletRequset对象和HttpServletResponse对象,所以请求和响应对象不会传递给下一个资源,因此请求参数和请求域中的数据也不可以传递
  4. 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转,并且重定向可以到本项目以外的外部资源
  5. 重定向不可以到给WEB-INF下受保护的资源

例如下面的代码:

Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@WebServlet("/s1")
public class Servlet1 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet1执行");

        // Servlet1重定向到Servlet2
        resp.sendRedirect("s2");
    }
}
Java
1
2
3
4
5
6
7
@WebServlet("/s2")
public class Servlet2 extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Servlet2执行");
    }
}

配置Tomcat后启动可以看到控制台输出如下:

Text Only
1
2
Sevlet1执行
Servlet2执行