Servlet 原来是这个玩意!看完恍然大悟!

5,483 阅读27分钟

1. 什么是 Servlet?

先让时间回到 25 年前,我国刚刚接入互联网不到两年时间。那时候的电脑长这个样子:

当时的网页技术还不是很发达,大家打开浏览器只能浏览一些静态的页面,例如图片、文本信息等。

随着时间的发展,静态页面已经不能满足于大部分用户的需求了。

用户打开浏览器不能只浏览个图片和文字吧?用户还想提交个表单、点个按钮、还想和浏览器做交互怎么办?

为了解决用户和浏览器动态交互的问题,到了1997年,SUN 公司(发明了JDK) 推出了Servlet技术。

Servlet 是一个基于 Java 技术开发的 web 组件,它运行在服务器端,由 Servlet容器管理。主要用于处理用户的请求并生成动态的信息。

Servlet 其实就是按照 Servlet规范编写的一个 Java 类。

我们知道一个类要想独立运行必须要有 main 方法,但是 Servlet 这个类没有 main 方法。那怎么运行?

答案是放到 servlet 容器里运行。

你可以把 servlet 看成是一个个的 app ,而手机就相当于一个 servlet 容器。app 不能单独运行,必须放在手机上运行。 servlet 因为没有 main 方法,也不能独立运行,必须放在 servlet 容器里面运行。

2. Tomcat 的安装和使用

2.1 Tomcat 简介

前面我们讲到 servlet 必须放到servlet容器里面运行,而 Tomcat 就是一个可以运行 servlet 的容器。

为什么推荐使用 Tomcat?因为它免费、开源、好用。

Tomcat 响应用户请求的过程如下:

    1. 用户通过浏览器向 Tomcat(web服务器)发送一个 HTTP 请求。
    1. Tomcat 接收到请求后,将请求信息发送给 Servlet 容器,并交给servlet 容器一个请求对象和一个响应对象
    1. Servlet 容器加载 Servlet,先创建一个 Servlet实例。然后告诉这个 servlet 实例说:嘿!小伙,我这里有一个用户的请求对象响应对象,你来处理一下。
    1. Servlet 实例从请求对象拿到客户端的请求信息,然后进行相应的处理。
    1. Servlet 实例将处理结果交给响应对象,通过响应对象发送到客户端。

注:

  1. 请求对象:HttpServletRequest
  2. 响应对象:HttpServletResponse

2.2 Tomcat 的安装与配置

:安装 Tomcat 之前需要先安装配置 JDK。

  • 下载解压
链接:https://pan.baidu.com/s/1Ey-gg8tpHT9P-xNOUcrZmg 
提取码:1234 

  • 启动 tomcat

进入 bin 目录下,双击 startup.sh 打开浏览器,输入:

http://localhost:8080/

当出现如下画面,说明你安装成功了!!!

3. Servlet 规范

    1. servlet 规范定义了动态资源文件(其实就是servlet)的开发步骤。
    1. servlet 规范定义了 Tomcat 服务器调用 servlet 的规则。
    1. servlet 规范定义了 Tomcat 服务器管理 servlet 实例的规则。

为什么要定义这些规范?

因为无规矩不成方圆。因为 Tomcat 同样也遵守了人家 Servlet 的规范,不然你写完程序交给 Tomcat 运行的时候,Tomcat 一看你没有遵守 Servlet 的规范,人家懒得管你。

4. 开发 Servlet

4.1 新建一个 javaweb 项目

这里我们使用 idea 创建一个 javaweb 项目。 项目目录:

4.2 idea 配置 Tomcat

1.顶部菜单栏选择 Run,选择 Edit Configuration。

2.点击“+”号,选择 Tomcat Server,选择 Local。

3.Application server 选择 Tomcat 本地安装目录。Http port 默认 8080 即可。 4.选择 Deployment,点击右边的+号,选择 Artifact。 5.选择 web 项目,Application context 可以自定义,它相当于我们项目的默认访问路径。 6.编写 index.jsp 7.运行 Tomcat 浏览器网址输入:

http://localhost:8080/myProject/

4.3 开发 Servlet

1.引入 jar 包

在 WEB-INF 目录下新建 lib 目录,将 Tomcat bin 目录下的 servlert-api.jar放到 lib 项目下。

2.将 jar 包作为库添加到项目中。

注:因为 jar 包就是编译后的 java 代码,所以这里就相当于在项目中添加了 Tomcat 指定规范下的 java 代码。

选择 lib 目录,右键选择 Add as a Library。 3.新建 Servlet 类

Servlet规范:新建的 Servlet 类要想被 Tomcat 管理,必须继承 Tomcat jar 包下的 HttpServlet 类。

创建一个 Java 类继承 HttpServlet 父类,使之成为一个 Servlet 接口实现类,并重写父类doGetdoPost方法。

public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       // 这里用来处理用户的 get 请求
        System.out.println("接收到用户的 get 请求");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

4.配置 web.xml

为什么要配置 web.xml ?

你根据 servlet 规范创建了一个 servlet 类,但是大哥 Tomcat 不知道啊!所以你需要告诉大哥一声。

web.xml 相当于 Tomcat 的门卫,你需要在门卫处做一下登记,将Servlet接口实现类信息注册到Tomcat服务器。

你需要登记 servlet 类的名字、类所在项目的路径以及请求路径。

<?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接口实现类交给Tomcat管理-->
    <servlet>
        <servlet-name>userServlet</servlet-name> <!--Servlet接口实现类名称-->
        <servlet-class>com.xxl.controller.UserServlet</servlet-class><!--声明servlet接口实现类类路径-->
    </servlet>
    <servlet-mapping>
        <servlet-name>userServlet</servlet-name>
        <url-pattern>/user</url-pattern>  <!--设置Servlet类的请求路径,必须以 / 开头-->
    </servlet-mapping>
</web-app>
  • servlet-name:servlet 类的名字。
  • servlet-class:servlet 类所在项目的路径。
  • url-pattern:用户请求该 servlet 的路径。

5.测试

运行项目,浏览器输入:

http://localhost:8080/myProject/user

查看控制台打印:

注:浏览器直接输入网址,按回车键是 GET 请求,和 html 中的超链接一样,例如:

<a href="/myProject/user">点击访问 UserServlet</a>

6.注解式开发

时间从敲代码的键盘声中划过,貌似一切风平浪静,可是有一天却出事了。

那是一个黑云压城城欲摧的下午,Tomcat 的门卫web.xml正在门卫室一边嗑瓜子一遍看报纸,门外突然传来了一阵阵急促的马蹄声。

原来这一天下午突然来了一万个 servlet 找 web.xml 做登记。

web.xml 一看这阵势蚌埠住了!,赶紧禀告 Tomcat 大官人。

Tomcat 一想:"这一万个 servlet 得登记到猴年马月啊?算了,不让他们做登记了,让他们自己身上带个标记吧,这个标记我认得就行。"

所以在servlet3.0版本之后,我们不需要在 web.xml 里面给每个 servlet 做配置了,只需要加一个注解就行。注解格式:

@WebServlet(name = "servlet的名字",value = "访问路径")
或者:
@WebServlet("/访问路径") //默认以类名作为名字

例如:

@WebServlet(name = "userServlet",value = "/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       // 这里用来处理用户的 get 请求
       System.out.println("哈哈哈哈哈哈我头上有注解");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

或者:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       // 这里用来处理用户的 get 请求
       System.out.println("哈哈哈哈哈哈我头上有注解");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

这时的 web.xml 就是空空如也: 即便 web.xml 什么也不配置,因为我们加了注解,所以 Tomcat 也能识别到,我们再来请求一下 servlet,发现也能请求成功。

运行项目,浏览器输入:

http://localhost:8080/myProject/user

:名字和请求路径要保持唯一性,不然 Tomcat 获取用户的请求之后不知道给哪一个 Servlet。

5. Http 协议

5.1 简介

为什么要学习 Http 协议?

因为只有知道了 Http 协议,你才能弄清楚信息在网络上传递的过程,你才能知道 servlet 是干什么的。

Htttp 协议主要应用在客户端-服务端架构上。浏览器作为 Http 客户端通过 URL 向 Web 服务器发送请求,其中 Http 常用的请求的方式主要有 GET 请求和 POST 请求。Web 服务器处理用户的请求后,向客户端发送响应信息。

信息在网络上都是以二进制形式进行传递的,传递的信息被封装成一个个的 Http 协议包。Http 协议包包括请求包和响应包。

5.2 Http 请求

HTTP 请求的消息格式主要包含:请求行、请求头、请求空行、请求数据。

例如 GET 请求的消息体:

GET /login.html    HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1

请求行主要包括:请求方式、请求的URL、请求的 Http 版本协议,例如:

GET /login.html HTTP/1.1

请求头格式:请求头名称: 请求头值,例如:

 Host: localhost

下面是一个完整的请求头:

Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1

请求空行就是一个空行,意思就是告诉服务器从下一行开始就没有请求头了。

GET 请求是没有请求体的,只有 POST 请求有请求体,请求体是以键值对的方式传输的,POST 的请求体就是键值对。

5.3 Http 响应

HTTP 响应的消息格式主要包含:状态行、响应头、空行、相应数据。 例如下面就是一个完整的响应体:

HTTP/1.1 200 OK
Date: Wed, 22 Dec 2021 09:07:21 GMT
Content-Type: text/html; charset=UTF-8

<html>
      <head></head>
      <body>
           一颗雷布斯
      </body>
</html>

状态行:HTTP协议版本号、状态码、状态消息。

第一行为状态行,(HTTP/1.1) 表示 HTTP 版本为 1.1 版本,状态码为 200,状态消息是 ok。

第二行和第三行为响应头,用来说明客户端要使用的一些附加信息。

第四行是空行。

最后的 html 代码就是响应的正文。

5.4 GET 和 POST 区别

  • GET 把请求参数包含在 URL 中,POST 通过请求体传递参数。
  • GET 不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息。
  • GET 请求在 URL 中传递的参数是有长度限制的,而 POST 没有限制。

6. 欢迎页面

我们平时访问一个网站,都是输入一个简单的网址,例如:

https://www.baidu.com

而不是:

https://image.baidu.com/search/index?tn=baiduimage&ct=201326592

因为这种太长的路径我们根本记不住。所以当用户访问一个网站时,我们一般会设置一个默认的页面,例如百度默认首页: 那如何在我们的项目中设置默认的欢迎页面?

也是在 web.xml 中配置。设置规则:

<welcome-file-list>
  <welcome-file>index.html</welcome-file>
  <welcome-file>index.htm</welcome-file>
  <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

然后我们需要在web或者webapp目录下创建欢迎页面。

欢迎页面默认显示顺序是从上到下,如果不配置也是从这几个文件中找。

比如我们刚创建这个项目时没有配置欢迎页面,但是程序运行之后默认显示的就是 index.jsp 的内容。

当然了我们也可以设置自定义的欢迎页面,例如: login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login.html</title>
</head>
<body>
<h1>家人们!家人们!欢迎来到登录页面!</h1>
</body>
</html>

运行程序:

7. HttpServletRequest

7.1 HttpServletRequest 是啥?

我们通过浏览器向 Http 服务器发送了一个 Http请求协议包, Http 服务器会自动为这个Http请求协议包生成一个请求对象和一个响应对象

而 HttpServletRequest 就代表客户端的请求,HTTP 请求中的所有信息都封装在这个对象中,我们可以使用这个对象干如下事情:

  • 读取 Http 请求协议包中的请求信息
  • 向 Http 服务器申请资源文件

7.2 获取请求行信息

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 1. 获取请求行中【url】信息
        String url = request.getRequestURL().toString();
        // 2. 获取请求行中【method】信息
        String method = request.getMethod();
        // 3. 获取请求行中【URI】信息,URI: "/网站名/资源文件名"
        String uri = request.getRequestURI();
        
        System.out.println("请求 URL: " + url);
        System.out.println("请求 method: " + method);
        System.out.println("请求 URI: " + uri);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

运行项目,浏览器输入:

http://localhost:8080/myProject/user

控制台打印结果:

7.3 获取请求头的参数信息

我们使用如下方法获取请求头的参数信息:

String parameter = request.getParameter("参数名");

7.3.1 获取 GET 请求头的参数信息

首先我们在浏览器请求路径追加请求参数,多个参数用&分隔,例如:

http://localhost:8080/myProject/user?name=一颗雷布斯&age=23

修改代码:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取请求头的 name 参数
        String name = request.getParameter("name");
        // 获取请求头的 age 参数
        String age = request.getParameter("age");
        System.out.println("name: "+name);
        System.out.println("age: "+age);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

运行程序,查看控制台打印信息:

7.3.2 获取 POST 请求头的参数信息

我们先修改一下默认欢迎页面 login.html 的代码,增加一个 form 表单信息:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login.html</title>
    <style>
        div {
            width: 300px;
            height: 300px;
            margin: 0 auto;
        }

    </style>
</head>
<body>
<div>
    <form action="/myProject/user" method="post">
        <lable>姓名:</lable>
        <input type="text" name="name"></br>
        <lable>密码:</lable>
        <input type="password" name="password"></br>
        <input type="submit" value="登录">
    </form>
</div>
</body>
</html>

修改 servlet 代码:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 get 请求
        System.out.println("接收到用户的 get 请求");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取 post 请求的 name 参数
        String name = request.getParameter("name");
        // 获取 post 请求的 password 参数
        String password = request.getParameter("password");
        System.out.println("name: "+name);
        System.out.println("password: "+password);
    }
}

浏览器页面输入信息,密码是123456,点击登录按钮: 查看控制台打印信息:

7.3.3 解决 POST 请求获取参数乱码问题

上面的例子中我们发现英文和数字可以正常输出,但是中文却乱码了,这是为什么?

当浏览器以 GET 方式发送请求,请求参数保存在请求头中,Http 服务器获取请求协议包之后先对其解码。

Tomcat 负责对其解码。因为 Tomcat 默认使用utf-8字符集,这个字符集可以解释所有国家的文字,所以即便是中文也不会乱码。

当浏览器以 POST 方式发送请求,请求参数保存在请求体中,Http 服务器获取请求协议包之后先对其解码。

请求对象 request 负责对请求体的内容进行解码。因为 request 默认使用 ISO-8859-1 字符集,这个字符集是东欧语字符集,所以遇到中文会乱码。

所以在Post请求方式下要想解决乱码问题,应该先让请求对象使用 utf-8 字符集对请求体内容进行一次重新解码。

 request.setCharacterEncoding("utf-8");

我们重新修改一下 servlet 的代码:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 get 请求
        System.out.println("接收到用户的 get 请求");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Tomcat 通知请求对象,使用 utf-8 字符集对请求体内容重写解码
        request.setCharacterEncoding("utf-8");
        // 获取 post 请求的 name 参数
        String name = request.getParameter("name");
        // 获取 post 请求的 password 参数
        String password = request.getParameter("password");
        System.out.println("name: "+name);
        System.out.println("password: "+password);
    }
}

浏览器页面输入信息,点击登录按钮查看控制台打印信息:

8. HttpServletResponse

8.1 HttpServletRequest 是啥?

我们处理完用户的请求之后肯定要给用户做出响应,而 HttpServletResponse 就是响应对象,它能干如下事情:

  • 将结果以二进制形式写入到响应体
  • 设置响应头中content-type属性值
  • 设置响应头中location属性,让浏览器向指定 Http 服务器发送请求。

8.2 将响应结果发送到浏览器

我们将响应结果放到Http响应包的响应体中,然后在浏览器对响应体的二进制内容进行编译解析,从而显示其内容。

语法格式:

PrintWriter writer = response.getWriter();
writer.println(响应结果内容);

我们修改一下 servlet 的代码:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String result1 = "<h1>2022: Where there is a well,there is a way<h2>";
        String result2 = "海鸟跟鱼相爱,只是一场意外";
        PrintWriter writer = response.getWriter();
        writer.println(result1);
        writer.println(result2);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

运行项目,浏览器输入:

http://localhost:8080/myProject/user

查看浏览器响应结果:

8.3 设置响应头 content-type

从上面的例子中,我们看到英文和数字可以正常输出,但是中文乱码了,我们可以设置一下响应头 content-type 的格式,让浏览器可以输出中文。

设置 content-type 的语法格式:

 response.setContentType("content-type 的格式");

我们修改一下 servlet 的代码:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String result1 = "<h1>2022: Where there is a well ,there is a way<h2>";
        String result2 = "海鸟跟鱼相爱,只是一场意外";
        
        // 设置响应头 content-type 的格式
        response.setContentType("text/html;charset=utf-8");
        
        PrintWriter writer = response.getWriter();
        writer.println(result1);
        writer.println(result2);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

运行项目,浏览器输入:

http://localhost:8080/myProject/user

查看浏览器响应结果:

8.4 向指定 Http 服务器发送请求

我们可以在响应头中设置 location 属性,从而让浏览器跳转到指定的网址。语法格式:

 response.sendRedirect("网址");

我们修改一下 servlet 的代码:

@WebServlet("/user")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 浏览器接收到 http 响应包之后,如果发现响应头中存在 location 属性
        // 2. 则自动通过地址栏向 location 指定网站发送请求
        // 3. sendRedirect 方法控制浏览器的请求行为
        response.sendRedirect("http://www.baidu.com");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 这里用来处理用户的 post 请求
        System.out.println("接收到用户的 post 请求");
    }
}

运行项目,浏览器输入:

http://localhost:8080/myProject/user

查看浏览器响应结果:

9. 重定向和请求转发

9.1 Servlet 之间互相访问

假如有一天你对 Http 服务器说:我想吃黄焖鸡。结果你的访问场景是这样的:

后来你彻底破防了:“老子就想吃个黄焖鸡,需要访问这么多次?啥破网站啊,再也不用了!”

所以为了提升用户使用体验,servlet 规范定义了多个 servlet 之间互相调用的规则。

无论该请求涉及到多少个 Servlet ,用户只需要向浏览器发起一次请求,Servlet 之间互相协作,用户得到响应结果。

多个 Servlet 之间互相调用的规则有两个:

    1. 重定向解决方案
    1. 请求转发解决方案

9.2 重定向

  1. 原理:

用户先通过浏览器访问 oneServlet,但是 oneServlet 不能解决用户的问题。于是 oneServlet 就将 TwoServlet 的访问地址写入到响应头 的location属性中。

浏览器拿到 Http 响应包之后,会根据响应头中的 location 的值再次向 Http 服务器中的 twoServlet 发送请求。twoServlet 再去处理用户的请求。

其实重定向就相当于你去找刘备借钱,刘备说我没钱你去找曹操吧。然后你又去找曹操借钱,最后借到了钱。

  1. 特征:
  • 浏览器至少发送两次请求,只有第一次是用户自己通过浏览器发送的,后面的都是浏览器自动发送的。
  • 重定向请求方式是 GET 方式。
  • 因为请求地址发生变化,所以浏览器地址栏会发生变化。
  • 除了可以访问 http 服务器内部的资源,还可以访问外部网站资源。例如百度等。
  • 浏览器多次访问服务器,增加用户等待时间。
  1. 实现方式
 response.sendRedirect("网址");
  1. 案例

新建 OneServlet

@WebServlet("/one")
public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        response.sendRedirect("/myProject/two");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       
    }
}

新建 TwoServlet

@WebServlet("/two")
public class TwoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.println("我是 twoServlet,我帮你解决问题!");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

运行项目,使用浏览器访问 OneServlet:

http://localhost:8080/myProject/one

查看浏览器响应结果:

9.3 请求转发

1.原理

用户先通过浏览器访问 oneServlet,但是 oneServlet 不能解决用户的问题。于是 oneServlet 将 request 对象和 response 对象传递给 TwoServlet,然后 twoServlet 去处理用户的请求。

其实请求转发就相当于你去找刘备借钱,刘备一摸兜没钱,但是刘备这人挺仗义,他就带着你去找曹操借钱,最后你从曹操那里借到了钱。

2.特征

  • 在请求转发过程中,浏览器只发送一次请求。
  • 在 HTTP 服务器内部做请求转发,只能请求服务器内部资源。
  • 在请求转发过程中,浏览器只发送一个 Http 请求协议包。 所以参与本次请求的所有 Servlet 共享同一个请求协议包。
  • 因为在 Http 服务器内部做请求转发,所以浏览器地址栏不变。

3.实现方式

RequestDispatcher requestDispatcher = request.getRequestDispatcher("下一个servlet请求路径");
// 将本次 Http 请求协议包的【请求对象】和【响应对象】交给下一个 servlet
requestDispatcher.forward(request,response);

4.案例

只修改 OneServlet

@WebServlet("/one")
public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 因为是http 服务器内部做请求转发,相当于调用同一个项目的资源,所以直接写 servlet的访问路径就行
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/two");
        // 将本次 Http 请求协议包的【请求对象】和【响应对象】交给下一个 servlet
        requestDispatcher.forward(request,response);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

运行项目,使用浏览器访问 OneServlet:

http://localhost:8080/myProject/one

查看浏览器响应结果: 总结:重定向的是重新、再次的意思,也就是浏览器向服务器发送了两次以上的请求。而请求转发发生在服务器内部,浏览器只请求了一次。

10. Servlet 之间数据共享

数据共享:第一个 Servlet 处理请求之后,将数据交给第二个 Servlet 来使用。

Servlet 规范提供了四种方式来解决数据共享问题。

10.1. ServletContext

1.全局作用域

ServletContext 对象是全局作用域对象。 全局作用域对象相当于教室里面一个公用的区域,老师把一些公共物品放到这里,所有同学(Servlet)都可以使用。

2.使用方法

将共享数据添加到全局作用域对象

// 1. 获取全局作用域对象
ServletContext servletContext = request.getServletContext();
// 2. 将共享数据添加到全局作用域对象
servletContext.setAttribute("key",value);

从全局作用域对象获取共享数据

// 1. 获取全局作用域对象
ServletContext servletContext = request.getServletContext();
// 2. 从全局作用域对象获取共享数据
Object value = servletContext.getAttribute("key");

3.案例 修改 OneServlet

@WebServlet("/one")
public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取全局作用域对象
        ServletContext servletContext = request.getServletContext();
        // 2. 将共享数据添加到全局作用域对象
        servletContext.setAttribute("info","祝今年期末考试的你科科必过!祝今年考研、考公的你一定上岸!");
        // 3. 重定向
        response.sendRedirect("/myProject/two");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

修改 TwoServlet

@WebServlet("/two")
public class TwoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取全局作用域对象
        ServletContext servletContext = request.getServletContext();
        // 2. 从全局作用域对象获取共享数据
        String info = (String)servletContext.getAttribute("info");
        response.setContentType("text/html;charset=utf-8");
        // 3. 将共享数据发送到浏览器
        response.getWriter().println(info);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

运行项目,使用浏览器访问 OneServlet:

http://localhost:8080/myProject/one

查看浏览器响应结果:

10.2. Cookie

1.简介

Cookie 是 Servlet 规范中的一个对象。如果两个 Servlet 来自于同一个网站,可以使用 Cookie 对象进行数据共享。

2.原理

用户请求 OneServlet,OneServlet 创建一个 Cookie 存储与当前用户相关的数据。OneServlet 执行完毕,将 Cookie 写入到响应头交还给当前浏览器。

浏览器获取 Http 响应包之后,将 cookie 信息存储在浏览器的缓存中。

过了一段时间,用户通过同一个浏览器访问 TwoServlet。浏览器将缓存中的 Cookie 信息写入到 Http 请求包的请求头中。

此时 TwoServlet 就可以通过读取请求头中 的cookie信息,获取 OneServlet 提供的共享数据。

其实 Cookie 的原理就像你(浏览器)第一次去健身房(Http服务器),健身房的工作人员给你办了张会员卡(Cookie)。你下次再去这家健身房,里面的工作人员拿到会员卡之后就知道你是谁了。

3.使用方式

创建 cookie,保存共享数据

// 1.创建一个 cookie 对象,保存共享数据
Cookie cookie = new Cookie("key","value");
// 2. 将 cookie 信息写入到响应头,交给浏览器
response.addCookie(cookie);

获取共享数据

// 1. 调用请求对象从请求头得到浏览器返回的 Cookie 信息
Cookie cookies[] = request.getCookies();
// 2. 遍历数据获取 cookie 的 key 与 value
for(Cookie cookie:cookies) {
    // 3. 获取 key 的值
    String key = cookie.getName();
    // 4. 获取 value 的值
    String value = cookie.getValue();
}

4.案例 修改 OneServlet

@WebServlet("/one")
public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1.创建一个 cookie 对象,保存共享数据
        Cookie cookie = new Cookie("name","jay");
        // 2. 将 cookie 信息写入到响应头,交给浏览器
        response.addCookie(cookie);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

修改 TwoServlet

@WebServlet("/two")
public class TwoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 调用请求对象从请求头得到浏览器返回的 Cookie 信息
        Cookie cookies[] = request.getCookies();
        // 2. 遍历数据获取 cookie 的 key 与 value
        for(Cookie cookie:cookies) {
            // 3. 获取 key 的值
            String key = cookie.getName();
            // 4. 获取 value 的值
            String value = cookie.getValue();
            System.out.println("key: "+key+" value: "+value);
        }
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

运行项目,先访问 OneServlet:

http://localhost:8080/myProject/one

再访问 TwoServlet:

http://localhost:8080/myProject/two

查看控制台打印结果:

注意:

  • cookie 相当于一个 map。
  • 一个 cookie 中只能存放一个键值对。
  • 键值对的 key 与 value 只能是 String 类型。
  • 键值对中 key 不能是中文。

5.cookie 的销毁时机

默认情况下,Cookie 对象保存在浏览器的缓存中。所以只要关闭浏览器,Cookie 对象就会被销毁。

为了延长 Cookie 的存活时间,我们可以将 Cookie 信息存储在本地计算机的硬盘上,并设置 Cookie 的存活时间。

在存活时间范围内,关闭浏览器或者服务器,都不会导致 Cookie 被销毁。当存活时间时到了,Cookie 自动被删除。

设置 Cookie 在硬盘存活时间:

Cookie cookie = new Cookie("name","jay");
//cookie 在硬盘上存活 2 分钟
cookie.setMaxAge(120); 

10.3. Session

1.简介

Session 是 Servlet 规范中的一个对象。如果两个 Servlet 来自于同一个网站,可以使用 Session 对象进行数据共享。我们习惯将 Session 叫做会话对象。

2.原理

用户请求 OneServlet,OneServlet 创建一个 Session 对象存储与当前用户相关的数据。OneServlet 执行完毕,将 Session 保存到 Http 服务器的内存中。

然后用户通过同一个浏览器访问 TwoServlet。TwoServlet 就可以通过Http 服务器内存中的 Session,获取 OneServlet 提供的共享数据。

其实 Session 可以看做服务器端的保险柜。

3.使用方式

创建 Session,保存共享数据

// 1. 创建 session 对象 
HttpSession session = request.getSession();
// 2. 将数据保存到服务器内存中(保险柜)
session.setAttribute("key",共享数据);

获取共享数据

// 1. 获取 session
HttpSession session = request.getSession();
// 2. 从 session 中获取共享数据
Object 共享数据 = session.getAttribute("key");

4.案例

修改 OneServlet

@WebServlet("/one")
public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 创建 session 对象
        HttpSession session = request.getSession();
        // 2. 将数据保存到服务器内存中(保险柜)
        session.setAttribute("key",123);

    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

修改 TwoServlet

@WebServlet("/two")
public class TwoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取 session
        HttpSession session = request.getSession();
        // 2. 从 session 中获取共享数据
        Object info = session.getAttribute("key");
        System.out.println("从session中获取信息:"+info);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

运行项目,先访问 OneServlet:

http://localhost:8080/myProject/one

再访问 TwoServlet:

http://localhost:8080/myProject/two

查看控制台打印结果: 注意:

  • HttpSession 对象可以存储任意类型的共享数据。
  • HttpSession 相当于一个 map集合,所以可以存储任意数量共享数据。

10.4. HttpServletRequest

1.简介

在同一个网站中,两个 Servlet 之间通过请求转发方式进行调用, 因为他们共享同一个请求协议包,所以 servlet 之间共享同一个请求对象。因此可以利用这个请求对象在两个 Servlet 之间实现数据共享。

这个请求对象叫做请求作用域对象

2.使用方式

使用请求对象保存共享数据

// 数据类型可以是任意类型
req.setAttribute("key",数据); 

使用请求对象获取共享数据

// 从请求对象获取共享数据
Object 数据 = req.getAttribute("key");

3.案例

修改 OneServlet

@WebServlet("/one")
public class OneServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 使用请求对象保存共享数据
        request.setAttribute("info","我一路向北,离开有你的季节。");
        // 请求转发
        request.getRequestDispatcher("/two").forward(request,response);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

修改 TwoServlet

@WebServlet("/two")  
public class TwoServlet extends HttpServlet {  
    @Override  
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
        // 从请求对象中获取共享数据  
  Object info = request.getAttribute("info");  
  response.setContentType("text/html;charset=utf-8");  
  response.getWriter().println(info);  
  }  
    @Override  
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  
    }  
}

运行项目,使用浏览器访问 OneServlet:

http://localhost:8080/myProject/one

查看浏览器响应结果:

11. Filter 过滤器

1.简介

Filter 是 Servlet 规范下的一个接口,主要用来告诉 Tomcat 在调用某一个资源文件之前,对用户的请求进行拦截

比如用户还没有登录网站就想查看自己购物车的信息,这时候就需要对用户的请求进行拦截。

2.使用方式

  • 新建 filter 类,实现 Filter 接口中的 doFilter 方法。
  • 在 web.xml 中配置 filter 类
 <!--配置过滤器-->
<filter>
  <filter-name>userFilter</filter-name>
  <filter-class>com.xxl.filter.UserFilter</filter-class>
</filter>
<!-- 告诉 Tomcat 在用户调用何种资源文件时需要被当前过滤器拦截-->
<filter-mapping>
  <filter-name>userFilter</filter-name>
  <url-pattern>拦截路径</url-pattern>
</filter-mapping>

3.案例

新建 UserFilter

public class UserFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }
}

在项目中放一个美女照片: 在 web.xml 中配置 UserFilter,拦截该照片。

<?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">

    <!--配置过滤器-->
    <filter>
        <filter-name>userFilter</filter-name>
        <filter-class>com.xxl.filter.UserFilter</filter-class>
    </filter>
    <!-- 告诉 Tomcat 在用户调用何种资源文件时需要被当前过滤器拦截-->
    <filter-mapping>
        <filter-name>userFilter</filter-name>
        <url-pattern>/girl.jpg</url-pattern>
    </filter-mapping>
</web-app>

完善 UserFilter 代码,判断 age 参数是否大于 18 岁。

public class UserFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 1. 通过【拦截请求对象】获取请求参数信息
        String age = servletRequest.getParameter("age");
        // 2. 如果年龄合法
        if (Integer.parseInt(age) > 18) {
            // 3. 将拦截请求对象和响应对象交还给 Tomcat,由 Tomcat 继续调用资源文件,放行
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            servletResponse.setContentType("text/html;charset=utf-8");
            servletResponse.getWriter().println("看啥美女!写你的作业去!");
        }
    }
}

运行结果:

4.注解式开发

不需要在 web.xml 中配置,直接在 filter 类上加注解:

@WebFilter(filterName = "filter名字",urlPatterns = "拦截路径")

修改 UserFilter 代码:

@WebFilter(filterName = "userFilter",urlPatterns = "/girl.jpg")
public class UserFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 1. 通过【拦截请求对象】获取请求参数信息
        String age = servletRequest.getParameter("age");
        // 2. 如果年龄合法
        if (Integer.parseInt(age) > 18) {
            // 3. 将拦截请求对象和响应对象交还给 Tomcat,由 Tomcat 继续调用资源文件,放行
            filterChain.doFilter(servletRequest,servletResponse);
        }else{
            servletResponse.setContentType("text/html;charset=utf-8");
            servletResponse.getWriter().println("看啥美女!写你的作业去!");
        }
    }
}

运行效果一样。