本文介绍: 文章目录1. Servlet 介绍1.1 什么是 Servlet1.2 Servlet 的主要工作2. Servlet 程序创建步骤2.1 创建项目2.2 引入依赖2.3 创建目录2.4 编写代码2.5 打包程序2.6 部署程序2.7 验证程序3. 使用 Smart Tomcat 进行部署3.1 安装 Smart Tomcat3.2 配置 Smart Tomcat3.3 使用 Smart Tomcat4. 访问出错解决方案4.1 出现 4044.2 出现 4054.3 出现 5004.4 出现“空白页面”4

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

  • Context Path 这个路径表示一个 webapp,来源于打包的包名
  • Servlet Path 这个路径表示一个 webapp 中的一个页面,来源于对应的 Servlet 类 @WebServlet 注解中的内容

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 对象从创建到销毁的过程,下面来介绍其生命周期过程

  • Servlet 对象是由 Tomcat 来进行实例化的,并且在实例化完毕之后调用 init 方法(只调用一次
  • Tomcat 对于收到的请求,都会通过对应的 Servlet 的 service 方法来进行处理可以调用多次
  • Tomcat 在结束之前,会调用 Servlet 的 destory 方法来进行回收资源(最多调用一次

注意: initservice 能够保证在各自的合适时机被 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("</br>");
        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("</br>");
        }
        html.append("</br>");
        //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: 实现重定向操作

  • 方法一:在响应报头设置状态码和 Location 来实现重定向

    @WebServlet("/redirect")
    public class RedirectServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // 将状态码设置为 3XX
            resp.setStatus(302);
            // 设置一个 Location,重定向到 CSDN 博客主页
            resp.setHeader("Location", "https://blog.csdn.net/weixin_51367845?spm=1000.2115.3001.5343");
        }
    }
    
  • 方法二:直接使用 sendRedirect() 方法来实现重定向

    @WebServlet("/redirect")
    public class RedirectServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            resp.sendRedirect("https://blog.csdn.net/weixin_51367845?spm=1000.2115.3001.5343");
        }
    }
    

    在这里插入图片描述

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

7.1 基本介绍

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

7.2 准备操作

  1. 创建好一个 Servlet 项目

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

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

  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 文件中引入 mysqljar[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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&amp;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 数组删除

    到这里为止,一个完整的服务器表白程序就写好啦!在我自己撸上面的代码时,由于连接 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 的浏览器中保留。

会话本质

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

  • 当用户成功登录之后,服务器在 Session 中会生成一个新的记录,并把 sessionId 返回给客户端(例如 HTTP 响应中可以通过 Set-Cookie 字段返回,其中 Cookie 的 key 为 “JSESSION”,value 为服务器生成sessionId 的具体的值)
  • 然后客户端只需要保存这个 sessionId ,当后续再给服务器发送请求时,请求中就会带上 sessionId(例如 HTTP 请求中会带上 Cookie 字段用于传递 Session)
  • 服务器收到请求后,就会根据请求中的 sessionId 在 Session 中查询应用户的身份信息,在进行后续操作

Session 会话机制的好处:

  • 使得客户端很轻量,不用保存太多数据
  • 客户端和服务器之间传输数据量小,节省带宽
  • 数据都在服务器存储,即使客户端出现问题,数据也不会丢失

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

8.3 Cookie 和 Session 的区别

  • Cookie 是客户端存储数据的一种机制,可以存储身份信息,也可以存储其它信息,是键值对结构

  • Session 是服务器存储数据的一种机制,主要用于存储身份相关的信息,是键值对结构

  • Cookie 和 Session 经常配合使用,但是不是必须的。

    • Cookie 也完全可以保存一些数据在客户端,这些数据不一定是用户身份信息,不一定是 sessionId
    • Session 中的 sessionId 也不需要非得通过 Cookie 和 Set-Cookie 来传递

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("<h3>账号密码不能为空!</h3>");
                return;
            }
            // 3. 判断是否登录成功(假设用户名为 admin密码为 1234。不过账号密码应该用数据库存储,这里只是用来测试)
            if(!username.equals("admin") || !password.equals("1234")){
                resp.getWriter().write("<h3>账号密码错误!</h3>");
                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("<h3>登录为空!</h3>");
                resp.sendRedirect("login2.html");
                return;
            }
            // 表示用户登录过,获取会话中的访问次数
            Integer visitCount = (Integer) session.getAttribute("visitCount");
            visitCount += 1;
            session.setAttribute("visitCount", visitCount);
            resp.getWriter().write("<h3>visitCount = " + visitCount + "</h3>");
        }
    }
    
  4. 到这里为止,一个简单的用户登录功能就实现成功了。效果如下在这里插入图片描述

9. 上传文件操作

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

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

HttpServletRequest 类中的相关方法

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

Part 类中的相关方法

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

9.2 上传文件操作实现

  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="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)]

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

  • 正文的大小和我们上传文件的大小不同,正文的比上传的文件的字节数略大
  • 数据类型multipart/form-data 没有问题,但是后面多了一串 boundary=----WebKitFormBoundaryAl26z0nbP6JzAUGL,这个 boundary 在 body 中表示一个分隔线,第一条分割线下面是上传的文件的属性和文件的内容,当文件的内容结束时还有第二条分割线
  • 由于有这个分割线和文件的一些属性,因此使得请求中正文的大小比上传的文件的内容略大

原文地址:https://blog.csdn.net/weixin_51367845/article/details/124325311

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_21616.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注