文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

【Servlet】Servlet 详解(使用+原理)

2023-09-29 16:58

关注

文章目录

1. Servlet 介绍

1.1 什么是 Servlet

1.2 Servlet 的主要工作

2. Servlet 程序创建步骤

2.1 创建项目

以下使用 IDEA 带大家编写一个简单的 Servlet 程序,主要是让大家了解一个大致的流程

2.2 引入依赖

Maven 项目创建完成后,会自动生成一个 pom.xml 文件,我们需要在这个文件中引入 Servlet API 依赖的 jar 包

2.3 创建目录

Web 项目对于目录结构还有自己的要求,只有 Maven 的标准目录是不够的,需要再创建以下目录并进行配置

2.4 编写代码

以下编写一个让响应返回一个自定义字符换的简单代码

2.5 打包程序

在程序编写好之后,就可以使用 Maven 进行打包

2.6 部署程序

接下来我们就可以进行程序的部署

2.7 验证程序

此时通过浏览器访问 http://127.0.0.1:8080/testServlet/test 就可以看到程序实现的结果了

在这里插入图片描述

注意:URL 中的路径分成了两个部分 Context Path 和 Servlet Path

3. 使用 Smart Tomcat 进行部署

为了简化上述操作流程,其实是有一些更简单的方式

3.1 安装 Smart Tomcat

3.2 配置 Smart Tomcat

3.3 使用 Smart Tomcat

4. 访问出错解决方案

4.1 出现 404

出现 404 原因: 用户访问的资源不存在,大概率是 URL 的路径写的不正确

错误实例1: 少写了 Context Path 或者 Context Path 写错了在这里插入图片描述

错误实例2: 少写了 Servlet Path 或者 Servlet Path 写错了

在这里插入图片描述
错误实例3: web.xml 写错了(如清空 web.xml 中的内容)

在这里插入图片描述

4.2 出现 405

出现 405 原因: 访问的服务器不能支持请求中的方法或者不能使用该请求中的方法

错误实例1: 没有重写 doGet 方法

在这里插入图片描述

错误实例2: 重写了 doGet 方法,但是没有删除父类的 doGet 方法

在这里插入图片描述

4.3 出现 500

出现 500 原因: 服务器出现内部错误,往往是 Servlet 代码中抛出异常导致的

错误实例: 代码中出现空指针异常

在这里插入图片描述

4.4 出现“空白页面”

出现空白页原因: 响应的 body 中并没有内容

错误实例:resp.getWriter().write() 操作删除

在这里插入图片描述

4.5 出现“无法访问此网站”

出现“无法访问此网站”原因: 一般是不能正确访问到 Tomcat(可能是 Tomcat 没启动,也可能是 IP/端口号写错了)

错误实例: 注解 @WebServlet 中少写了 /在这里插入图片描述

4.6 出现中文乱码问题

响应出现中文乱码问题原因: 使用的编译器的编码方式(一般是 utf-8)和浏览器的编码方式不同,浏览器默认跟随系统编码方式,win10 系统默认是 GBK 编码

解决方式: 通过响应对象的 setContentType() 方法来修改浏览器对于响应正文的编码格式

@WebServlet("/test")public class TestServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.setContentType("text/html;charset=utf-8");        resp.getWriter().write("吞吞吐吐大魔王");    }}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKLlqalS-1650529905418)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220413163421106.png)]

5. Servlet 运行原理

在 Servlet 的代码中,我们并没有写 main 方法,那么对应的 doGet 代码是如何被调用呢?响应又是如何返回给浏览器的呢?

5.1 Servlet 的架构

我们自己实现的 Servlet 是在 Tomcat 基础上运行的,下图显示了 Servlet 在 Web 应用程序中的位置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGlWiBzk-1650529905419)(C:/Users/bbbbbge/Pictures/img/202203180021103.jpeg)]

当浏览器给服务器发送请求时,Tomcat 作为 HTTP 服务器,就可以接收到这个请求。Tomcat 的工作就是解析 HTTP 请求,并把请求交给 Servlet 的代码来进行进一步的处理。Servlet 的代码根据请求计算生成响应对象,Tomcat 再把这个响应对象构造成 HTTP 响应,返回给浏览器。并且 Servlet 的代码也经常会和数据库进行数据的传递。

5.2 Tomcat 的伪代码

下面通过 Tomcat 的伪代码的形式来描述 Tomcat 初始化和处理请求两部分核心逻辑

6. Servlet API 详解

对于 Servlet 主要介绍三个类,分别是 HttpServlet、HttpServletRequest 和 HttpServletResponse。

其中 HttpServletRequest 和 HttpServletResponse 是 Servlet 规范中规定的两个接口,HttpServlet 中并没有实现这两个接口的成员变量,它们只是 HttpServlet 的 service 和 doXXX 等方法的参数。这两个接口类的实例化是在 Servlet 容器中实现的。

6.1 HttpServlet

核心方法

方法名称调用时机
init在 HttpServlet 实例化之后被调用一次
destory在 HttpServlet 实例不再使用的时候调用一次
service收到 HTTP 请求的时候调用
doGet收到 GET 请求的时候调用(由 service 方法调用)
doPost收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/...收到其它对应请求的时候调用(由 service 方法调用)

Servlet 的生命周期: Servlet 的生命周期就是 Servlet 对象从创建到销毁的过程,下面来介绍其生命周期的过程

注意: init 和 service 能够保证在各自的合适时机被 Tomcat 调用,但是 destory 不一定,它是否能够被调用取决于 Tomcat 是如何结束的

处理 GET 请求示例:

直接通过浏览器 URL 发送一个 GET 方法的请求,来对这个请求进行处理

@WebServlet("/get")public class TestServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.getWriter().write("get");    }}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JLEwJSSm-1650529905419)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220414010557437.png)]

处理 POST 请求示例:

由于通过浏览器 URL 发送的请求是 GET 方法的请求,因此我们需要通过其它方式来发送一个 POST 请求然后用于处理。发送 POST 请求的方式有通过 Ajax、form 表单或者 socket api 进行构造,如果单纯的用于测试就比较麻烦,这里推荐使用软件 postman,这是一个很强大的 API 调试、Http 请求的工具。

@WebServlet("/post")public class TestServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.getWriter().write("post");    }}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjHoxMyG-1650529905419)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220413162336658.png)]

6.2 HttpServletRequest

核心方法

方法描述
String getProtocol()返回协议的名称和版本号
String getMethod()返回请求的 HTTP 方法的名称
String getRequestURL()返回请求的 URL,不带查询字符串
String getRequestURI()返回该请求的 URL 的一部分,不带协议名、端口号、查询字符串
String getContextPath()返回指示请求 URL 中 Context Path 部分
String getServletPath()返回指示请求 URL 中 ServletPath 部分
String getQueryString()返回请求首行中 URL 后面的查询字符串
Enumeration getParameterNames()返回一个 String 对象的枚举,包括在该请求中的参数的名称
String getParameter(String name)以字符串形式返回请求参数的值,如果参数不存在则返回 null
String[] getParameterValues(String name)返回一个字符串对象的数组,包括所有给定的请求的参数,如果参数不存在则返回 null
Enumeration getHeaderNames()返回一个枚举,包括该请求中所有的头名
String getHeader(String name)以字符串形式返回指定的请求头的值
String getCharacterEncoding()返回请求正文中使用的字符编码的名称
String getContentType()返回请求正文的 MIME 类型,如果不知道类型则返回 null
int getContentLength()以字节为单位返回请求正文的长度,并提供输入流,如果长度未知则返回-1
InputStream getInputStream()用于读取请求的正文内容,返回一个 InputStream 对象

示例1: 通过上述方法返回一个页面是该请求的具体 HTTP 请求格式

@WebServlet("/showRequest")public class ShowRequestServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        // 此处返回一个 HTML,在 HTML 中显示 HttpRequestServlet 类中的一些核心方法        // 把这些 API 的返回结果通过 StringBuilder 进行拼接        resp.setContentType("text/html;charset=utf-8");        StringBuilder html = new StringBuilder();        html.append(req.getMethod());        html.append(" ");        html.append(req.getRequestURL());        html.append("?");        html.append(req.getQueryString());        html.append(" ");        html.append(req.getProtocol());        html.append("
"
); Enumeration<String> headerNames = req.getHeaderNames(); while(headerNames.hasMoreElements()){ String headName = headerNames.nextElement(); String header = req.getHeader(headName); html.append(headName); html.append(": "); html.append(header); html.append("
"
); } html.append("
"
); //InputStream body = req.getInputStream(); resp.getWriter().write(html.toString()); }}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AfoyYGZK-1650529905419)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220414160226859.png)]

示例2: 处理 HTTP 请求的 body 中的数据格式

6.3 HttpServletResponse

核心方法

方法描述
void setStatus(int sc)为该响应设置状态码
void setHeader(String name, String value)设置一个带有给定的名称和值的 header,如果 name 已经存在,则覆盖旧的值
void addHeader(String name, String value)添加一个带有给定的名称和值的 header,如果 name 已经存在,不覆盖旧的值,而是添加新的键值对
void setContentType(String type)设置被发送到客户端的响应的内容类型
void setCharacterEncoding(String charset)设置被发送到客户端的响应的字符编码,例如 utf-8
void sendRedirect(String location)设置 Location 字段,实现重定向
PrintWriter getWriter()用于往 body 中写入文本格式数据
OutputStream getOutputStream()用于往 body 中写入二进制格式数据

示例1: 通过代码,构造出不同的响应状态码

@WebServlet("/status")public class StatusServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        int status = 404;        resp.setStatus(status);        resp.getWriter().write("status=" + status);    }}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onfxNEdj-1650529905420)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220415004153808.png)]

示例2: 在响应报头设置一个 Refresh 字段,实现字段刷新程序

Refresh 的值表示每秒刷新的时间,当程序是毫秒级刷新的时候,可能存在误差

@WebServlet("/autoRefresh")public class AutoRefreshServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        // 给响应设置一个 Refresh 的 header,每隔 1s 钟刷新一次        resp.setHeader("Refresh", "1");        // 返回一个当前的时间,用来显示刷新的效果        resp.getWriter().write("timestamp=" + System.currentTimeMillis());    }}

在这里插入图片描述

示例3: 实现重定向操作

7. 实现服务器版表白墙程序

7.1 基本介绍

在之前的文章《【Web 三件套】 JavaScript WebAPI》中实现过了一个纯前端的表白墙代码,实现后的效果如下。这次将会结合上述的知识,实现一个服务器版的表白墙程序在这里插入图片描述

7.2 准备操作

  1. 创建好一个 Servlet 项目

  2. 将之前写好的纯前端的表白墙代码拷贝到 webapp 目录下

  3. 约定好前后端交互的接口,该程序只需约定两个接口

    • 从服务器获取全部留言

      • 约定请求:方法为 GET,请求路径为 /message

      • 约定响应:版本号为 HTTP/1.1,状态码为 200 OK,采用 JSON 数据格式

      • JSON 具体格式为:

        [{​from: "",​to: "",​message: ""}]
    • 通过客户端给服务器新增一个留言

      • 约定请求:方法为 POST,请求路径为 /message
      • 约定响应:版本号为 HTTP/1.1,状态码为 200 OK,提交成功后响应页面显示“提交成功”
  4. 创建一个 MessageServlet 类,@WebServlet 注解为 /message,对应着约定的请求路径,通过上方的约定完成服务器段的代码

  5. 更改前端的代码

7.3 代码实现

后端代码实现:

import com.fasterxml.jackson.databind.ObjectMapper;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.net.HttpRetryException;import java.util.ArrayList;import java.util.List;// 这个类表示一条消息的详细情况class Message{    public String from;    public String to;    public String message;}@WebServlet("/message")public class MessageServlet extends HttpServlet {    // 通过这个数组来表示所有的消息    private List<Message> messages= new ArrayList<>();    // 通过这个代码来完成获取服务器所有消息的操作    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.setContentType("application/json;charset=utf-8");        // 获取到消息列表        // 此处要做的就是把当前的 messages 数组转成 json 格式返回给浏览器        ObjectMapper objectMapper = new ObjectMapper();        // 通过 ObjectMapper 的 writeValuesAsString() 方法就可以将一个对象转换成 json 字符串        // 由于这里的 message 是一个 List,那么得到的结果是一个 json 数组        String jsonString = objectMapper.writeValueAsString(messages);        resp.getWriter().write(jsonString);    }    // 通过这个代码来完成新增消息的操作    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.setContentType("text/html;charset=utf-8");        ObjectMapper objectMapper = new ObjectMapper();        Message message = objectMapper.readValue(req.getInputStream(), Message.class);        messages.add(message);        resp.getWriter().write("提交成功!");    }}

前端代码实现:

DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>表白墙title>    <style>        * {            margin: 0;            padding: 0;            box-sizing: border-box;        }        .container {            width: 600px;            margin: 0 auto;        }        h1 {            text-align: center;            padding: 20px 0;            color: pink;        }        p {            text-align: center;            font-size: 15px;            color: grey;            padding: 5px 0;        }        .row {            display: flex;            height: 40px;            justify-content: center;            align-items: center;        }        .row span {            width: 80px;        }        .row .edit {            width: 250px;            height: 35px;        }        .row .submit {            width: 330px;            height: 40px;            background-color: orange;            color: #fff;            border: none;        }        .row .submit:active {            background-color: grey;        }    style>head><body>    <div class="container">        <h1>表白墙h1>        <p>输入后点击提交,将会把消息显示在在墙上p>        <div class="row">            <span>谁:span>            <input type="text" class="edit">        div>        <div class="row">            <span>对谁:span>             <input type="text" class="edit">        div>        <div class="row">            <span>说什么:span>            <input type="text" class="edit">        div>        <div class="row">            <input type="button" value="提交"  class="submit">        div>    div>    <script src="http://code.jquery.com/jquery-2.1.1.min.js">script>    <script>        let submitButton = document.querySelector('.submit');        submitButton.onclick = function() {            // 1. 获取到输入框里的内容            let edits = document.querySelectorAll('.edit');            let from = edits[0].value;            let to = edits[1].value;            let message = edits[2].value;            // 2. 根据输入框的内容,构造 HTML 元素,添加到页面中            if(from == '' || to == '' || message == '') {                return;            }            let div = document.createElement('div');            div.innerHTML = from + '对' + to + '说:' + message;            div.className = 'row';            let container = document.querySelector('.container');            container.appendChild(div);            // 3. 把上次输入的内容清空            for(let i = 0; i < edits.length; i++){                edits[i].value = '';            }            // 4. 把当前新增的消息发送给服务器            let body = {                from: from,                to: to,                message: message            };            $.ajax ({                url: "message",                method: "post",                contentType: "application/json;charset=utf8",                // 通过 JSON.stringify 将对象转成字符串                data: JSON.stringify(body),                success: function(data, status){                    console.log(data);                }            })        }        // 服务器版本        // 1. 在页面加载的时候,从服务器获取到消息列表,并显示在网页上        function load() {            $.ajax({                method: "get",                url: "message",                success: function(data, status) {                    // 此处得到的响应 data 其实已经被 jquery 转成了一个对象数组                    // 但是这里的自动转换有个前提,服务器响应的 header 中 ContentType 是 json                    let container = document.querySelector('.container');                    for(let message of data){                        // 遍历每个元素,针对每个元素拆功能键一个 div 标签                        let div = document.createElement('div');                        div.className = 'row';                        div.innerHTML = message.from + "对" + message.to + " 说:" + message.message;                        container.append(div);                    }                }            })        }        load();    script>body>html>

7.4 持久化存储

通过上述修改,原本的纯前端代码就加上了服务器,只要服务器开启后,即使刷新网页,已经添加的数据也不会消失。但是如果重启服务器的话,原本的数据就会丢失,为了解决这个问题,就需要让数据能够持久化存储。

持久化存储: 是把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘),是一种将程序数据在持久状态和瞬时状态间转换的机制。

持久化存储机制包括: JDBC文件 IO

接下来将通过增加一个数据库来让上述表白墙程序可以持久化存储

  1. 先建库建表(可以先创建一个文件,将要建的数据库和表都写好)

    drop database if exits messagewall;create database messagewall;use messagewall;drop table if exits message;create table message (    `from` varchar(50),    `to` varchar(50),    `message` varchar(1024));
  2. 在 pom.xml 文件中引入 mysql 的 jar 包[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-23JXKeIU-1650529905421)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220416172438996.png)]

  3. 连接数据库,创建一个 DBUtil 类,用于封装数据库的建立连接和资源释放操作

    import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;import javax.sql.DataSource;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;// 通过这个类来封装数据库的建立连接操作public class DBUtil {    private static final String URL = "jdbc:mysql://127.0.0.1:3306/messagewall?characterEncoding=utf8&setSSL=false";    private static final String USERNAME = "root";    private static final String PASSWORD = "1234";    private static DataSource dataSource = new MysqlDataSource();    static {        ((MysqlDataSource)dataSource).setURL(URL);        ((MysqlDataSource)dataSource).setUser(USERNAME);        ((MysqlDataSource)dataSource).setPassword(PASSWORD);    }    public static Connection getConnection() throws SQLException {        return dataSource.getConnection();    }    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){        if(resultSet != null){            try {                resultSet.close();            } catch (SQLException throwables) {                throwables.printStackTrace();            }        }        if(statement != null){            try {                statement.close();            } catch (SQLException throwables) {                throwables.printStackTrace();            }        }        if(connection != null){            try {                connection.close();            } catch (SQLException throwables) {                throwables.printStackTrace();            }        }    }}
  4. 修改 MessageWall 类的代码,主要修改的地方有两处,将原本的 messages 数组删除

    • 在获取消息时,可以增加一个 getMessages() 方法,用于拿到数据库中的所有消息

      // 从数据库获取到所有消息private List<Message> getMessages() {    Connection connection = null;    PreparedStatement statement = null;    ResultSet resultSet = null;    List<Message> messages = new ArrayList<>();    try {        // 1. 和数据库建立连接        connection = DBUtil.getConnection();        // 2. 构造 sql        String sql = "select * from message";        statement = connection.prepareStatement(sql);        // 3. 执行 sql        resultSet = statement.executeQuery();        // 4. 遍历结果集合        while(resultSet.next()){            Message message = new Message();            message.from = resultSet.getString("from");            message.to = resultSet.getString("to");            message.message = resultSet.getString("message");            messages.add(message);        }    } catch (SQLException throwables) {        throwables.printStackTrace();    } finally {        DBUtil.close(connection, statement, resultSet);    }    return messages;}
    • 在新增消息是,可以新增一个 addMessage() 方法,用于往数据库存储一条新消息

      // 往数据库新增一条消息private void addMessage(Message message) {    Connection connection = null;    PreparedStatement statement = null;    try {        // 1. 和数据库建立连接        connection = DBUtil.getConnection();        // 2. 构造 sql        String sql = "insert into message values(?, ?, ?)";        statement = connection.prepareStatement(sql);        statement.setString(1, message.from);        statement.setString(2, message.to);        statement.setString(3, message.message);        // 3. 执行 sql        statement.executeUpdate();    } catch (SQLException throwables) {        throwables.printStackTrace();    } finally {        DBUtil.close(connection, statement, null);    }}

    到这里为止,一个完整的服务器表白程序就写好啦!在我自己撸上面的代码时,由于连接 MySQL 的 URL 中的端口号写错了,导致自己找了很久的 bug,所以大家如果尝试上述代码时遇到问题,一定要看清是不是自己哪个地方打错了!

8. Cookie 和 Session

8.1 Cookie 介绍

在之前的文章《HTTP 协议详解》中,就介绍过了 Cookie,可以结合本文的内容来搭配理解。

在了解 Cookie 以后,我们发现 Cookie 是不能够用于存储和用户相关的直接信息的,一是 Cookie 的存储容量有限,二是发送请求时占用带宽很多,三是不太安全。即这些数据不适合保存在客户端,保存在服务器是更合适的,通过会话(Session)的方式就能够保存这些数据。

8.2 Session 会话机制介绍

基本介绍:

在计算机中,尤其是在网络应用中,Session 称为“会话控制”。Session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session 对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在 Session 对象中。注意会话状态仅在支持 Cookie 的浏览器中保留。

会话的本质:

  • 会话的本质就是一个哈希表,其中存储了一些键值对结构,key 叫做 sessionId,是一个不随机的、不重复的、唯一的字符串,value 就是要保存的身份信息,通过 HttpSession 对象来保存。key 和 value 都是 Servlet 自动创建的。
  • 每个用户登录都会生成一个会话,服务器会以哈希表的方式将这些会话管理起来
  • 一个会话的详细数据通过一个 HttpSession 对象来存储,并且 HttpSession 对象中存储的数据也是键值对结构,key 和 value 都是程序员自定义的

接着 Cooike 不适合用于存储用户相关的直接信息来讲,由于客户端不适合存储这些数据,服务器这边可以通过 Session 会话的方式来进行保存。下面将会以用户登录的流程来介绍 Session 会话机制

Session 会话机制的好处:

注意: Servlet 的 Session 默认是保存在内存中的,如果重启服务器 Session 数据将会丢失

8.3 Cookie 和 Session 的区别

8.4 Servlet 中 Cookie 和 Session 的核心方法

HttpServletRequest 类中的相关方法

方法描述
HttpSession getSession(参数)在服务器中获取会话,参数如果为 true,当不存在会话时会新建会话(包括生成一个新的 sessionId 和 HttpSession 对象),并通过 Set-Cookies 将 sessionId 返回给客户端;参数如果为 false,当不存在会话时会返回 null。如果存在 sessionId 且合法,就会根据这个 sessionId 找到对应的 HttpSession 对象并返回
Cookie[] getCookies()返回一个数组,包含客户端发送请求时的所有 Cookie 对象,会自动把 Cookie 中的格式解析成键值对

HttpServletResponse 类中的相关方法

方法描述
void addCookie(Cookie cookie)把指定的 cookie 添加到响应中

HttpSession 类中的相关方法

  • HttpSession是 Java平台对 session 机制的实现规范,因为它仅仅是个接口,具体实现为每个 web 应用服务器的提供商。
  • 服务器会为每一个用户创建一个独立的 HttpSession,表示为一个会话,并且一个 HttpSession 对象里包含了多个键值对,可以往 HttpSession 中存储需要的数据
方法描述
Object getAttribute(String name)该方法返回在 Session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null
void setAttribute(String name, Object value)该方法使用指定的名称绑定一个对象到该 Session 会话中
boolean isNew()判定当前的会话是否是新创建的

Cookie 类中的相关方法

  • 这个类描述了一个 Cookie,通过 Cookie 类创建的对象,每个对象就是一个键值对
  • HTTP 的 Cookie 字段中实际上存储的是多个键值对,每个键值对在 Servlet 中都对应一个 Cookie 对象
方法描述
String getName()该方法返回 cookie 的名称(这个值是 Set-Cookie 字段设置给浏览器的,创建之后不能改变)
String getValue()该方法获取与 Cookie 关联的值
void setValue(String newValue)该方法设置与 Cookie 关联的值

8.5 实现用户登录功能

接下来将使用上述的 Session 和 Cookie 的相关方法来实现一个用户登录功能,并且可以记录访问页面的次数

登录功能实现思路:

  1. 读取用户提交的用户和密码
  2. 对用户密码进行校验
  3. 判定是否登录成功
  4. 创建会话,保存自定义信息
  5. 重定向到指定页面

登录功能实现流程:

  1. 先实现一个登录页面

    DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>登录页面title>head><body>    <form action="login" method="post">        <input type="text" name="username">        <input type="password" name="password">        <input type="submit" value="登录">    form>body>html>

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OvzuJIvC-1650529905422)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220420200000423.png)]

  2. 实现一个 Servlet 来处理上面的登录请求

    由于这里是通过 form 表单来构造的 post 请求,那么通过 HttpServletRequest 类中的 getParameter() 方法就能够获取请求正文中参数的值

    import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;@WebServlet("/login")public class LoginServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.setContentType("text/html;charset=utf8");        // 1. 从请求中获取到用户名和密码        String username = req.getParameter("username");        String password = req.getParameter("password");        // 2. 对用户密码进行校验        if(username == null || "".equals(username) || password == null || "".equals(password)){            resp.getWriter().write("

    账号或密码不能为空!

    "
    ); return; } // 3. 判断是否登录成功(假设用户名为 admin,密码为 1234。不过账号密码应该用数据库存储,这里只是用来测试) if(!username.equals("admin") || !password.equals("1234")){ resp.getWriter().write("

    账号或密码错误!

    "
    ); return; } // 4. 登录成功,创建一个会话,用来记录当前用户的信息 HttpSession session = req.getSession(true); // 通过这个操作,就给会话中新增了一个程序员自定义的信息,访问次数 session.setAttribute("visitCount", 0); // 5. 把登录成功的结果反馈给客户端(这里的反馈不是简单的提示“登录成功”,而是直接跳转到指定页面) resp.sendRedirect("index"); }}
  3. 通过实现一个 Servlet 来表示登录成功后重定向的页面

    import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;@WebServlet("/index")public class IndexServlet extends HttpServlet {    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.setContentType("text/html;charset=utf8");        // 只有登录成功参数才能是 true,这里是拿参数,所以要填 false        HttpSession session = req.getSession(false);        // 判断当前用户是否登录        if(session == null){            // 可以提示未登录,也可以重定向到登录页面            // resp.getWriter().write("

    登录为空!

    ");
    resp.sendRedirect("login2.html"); return; } // 表示用户登录过,获取会话中的访问次数 Integer visitCount = (Integer) session.getAttribute("visitCount"); visitCount += 1; session.setAttribute("visitCount", visitCount); resp.getWriter().write("

    visitCount = " + visitCount + "

    "
    ); }}
  4. 到这里为止,一个简单的用户登录功能就实现成功了。效果如下在这里插入图片描述

9. 上传文件操作

上传文件是日常开发中的一类常见需求,在 Servlet 中也进行了支持

9.1 Servlet 中上传文件的核心方法

HttpServletRequest 类中的相关方法

方法描述
Part getPart(String name)获取请求中给定 name 的文件
Collection getParts()获取所有的文件

Part 类中的相关方法

方法描述
String getSubmittedFileName()获取提交的文件名
String getContentType()获取提交的文件类型
long getSize()获取文件的大小,单位为字节
void write(String path)把提交的文件数据写入磁盘文件

9.2 上传文件操作实现

  1. 先写一个前端页面,用于上传文件

    • 上传文件一般使用 post 请求的表单实现
    • 通过 form 表单构造上传文件,要加上一个 enctype 字段,它表示 body 中的数据格式,它的默认值为:x-www-form-urlencoded,这里要修改成:multipart/form-data,它是上传文件或者图片的数据格式
    • 第一个 input 中 type 的值为 file,它表示第一个输入框为文件选择框,name 的值与后端中通过 getPart 获取指定文件的操作有关
    DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <meta http-equiv="X-UA-Compatible" content="IE=edge">    <meta name="viewport" content="width=device-width, initial-scale=1.0">    <title>上传文件title>head><body>    <form action="upload" method="post" enctype="multipart/form-data">        <input type="file" name="MyFile">        <input type="submit" value="上传">    form>body>html>
  2. 写一个 Servlet 用于处理上传的文件

    • 上传文件操作还需要给 Servlet 加上一个 @MultipartConfig 注解,否则服务器代码无法使用 getPart() 方法
    • getPart() 方法中的参数和 form 表单 input="file" 标签的 name 属性对应
    • 客户端一次可以提交多个文件,getPart() 方法根据 name 属性来获取不同的文件
    • 写入磁盘文件操作的路径之间可以使用两个反斜杠 \\ ,也可以使用一个正斜杠 /
    • 写入磁盘文件操作的路径最后为保存后的文件名,包括文件后缀
    import javax.servlet.ServletException;import javax.servlet.annotation.MultipartConfig;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Part;import java.io.IOException;@MultipartConfig@WebServlet("/upload")public class UploadServlet extends HttpServlet {    @Override    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {        resp.setContentType("text/html;charset=utf8");        // 通过 getPart 方法获取到前端传来的文件        Part part = req.getPart("MyFile");        // 获取文件名        String fileName = part.getSubmittedFileName();        System.out.println("文件名为: " + fileName);        // 获取提交的文件类型        String fileType = part.getContentType();        System.out.println("文件类型为: " + fileType);        // 获取文件的大小        long fileSize = part.getSize();        System.out.println("文件大小为: " + fileSize);        // 把文件数据写入磁盘文件        part.write("C:\\Users\\bbbbbge\\Desktop\\upload.jpg");        resp.getWriter().write("上传成功!");    }}

    在这里插入图片描述

到这里为止,一个简单的文件上传操作就实现好了,我们可以通过抓包来观察下文件上传操作的请求是怎样的?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-agQADJIH-1650529905422)(C:/Users/bbbbbge/Pictures/%E6%8E%A5%E5%8D%95/image-20220421131825048.png)]

通过抓包操作我们会发现几点问题:

来源地址:https://blog.csdn.net/weixin_51367845/article/details/124325311

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯