Servlet的使用(JavaEE初阶系列17)

目录

前言:

[1.Servlet API的使用](#1.Servlet API的使用)

1.1HttpServlet

1.2HttpServletRequest

1.3HttpServletResponse

2.表白墙的更新

2.1表白墙存在的问题

2.2前后端交互接口

2.3环境准备

2.4代码的编写

2.5数据的持久化

2.5.1引入JDBC依赖

2.5.2创建数据库

2.5.3编写数据库代码

3.Cookie回忆

4.Session机制

4.1getSession

4.2模拟实现登录功能

4.2.1登录页的核心代码。

4.2.2编写登录页面的Servlet

4.2.3编写生成主页的Servlet

4.2.4起启动浏览器

结束语:


前言:

上一节中小编主要与大家分享了有关于Tomcat和Servlet的基础的一些知识,接下来小编将会给大家介绍一下有关于Servlet的API以及对上一次编写的表白墙案例的更新。话不多说一起来学起来吧!

1.Servlet API的使用

Servlet中的API有很多这里我们只需要掌握里面的三个即可。接下来我们来具体看一下。

1.1HttpServlet

我们在写Servlet代码的时候,首先第一步就是先创建类,继承自HttpServlet,并重写其中的某些方法。

核心方法:

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

Servlet的生命周期:

  • **init:**是在初识情况下调用一次。
  • **destory:**是在结束之前调用一次。
  • **service:**每次收到路径匹配的请求都要调用一次。

代码展示:

java 复制代码
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;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("这是一个doGet方法");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("这是一个doPost方法");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("这是一个doPut");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("这是一个doDelete方法");
    }
}

结果展示:

1.2HttpServletRequest

它是一个HTTP请求,当Tomcat通过Socket API读取到HTTP请求之后就会解析生成上述的HttpServletRequest对象。这里就是HTTP请求报文里有啥,这里类里面就会有啥。

核心方法:

|--------------------------------------------------------|----------------------------------------------|
| 方法 | 描述 |
| String getProtocol() | 返回请求协议的名称和版本。 |
| String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。 |
| String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请 求的 URL 的一部分。 |
| String getContextPath() | 返回指示请求上下文的请求 URI 部分。 |
| 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() | 用于读取请求的 body 内容. 返回一个 InputStream 对象。 |

注意:上述中有一个是URI,注意不是URL,URL是唯资源定位符,而URI是唯一资源标识符,这两概念非常相似,以至于很多时候我们都是直接混着使用了。

代码展示:

java 复制代码
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.util.Enumeration;

@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StringBuilder result = new StringBuilder();
        //返回协议请求的名称和版本
        result.append(req.getProtocol());
        result.append("<br>");
        //返回请求的HTTP方法的名称,例如GET、POST或PUT
        result.append(req.getMethod());
        result.append("<br>");
        //从协议名称直到HTTP请求的第一行的查询字符中,返回该请求的URL的一部分。
        result.append(req.getRequestURI());
        result.append("<br>");
        //返回包含在路径后的请求URL中的查询字符串
        result.append(req.getQueryString());
        result.append("<br>");
        //返回请求指示上下文的请求URI部分
        result.append(req.getContextPath());
        result.append("<br>");

        result.append("========================<br>");
        //返回一个枚举类,包含在该请求中所有的头名
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = req.getHeader(headerName);
            result.append(headerName + ":" + headerValue + "<br>");
        }
        //在响应中设置上body的类型,方便浏览器进行解析
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write(result.toString());
    }
}

结果展示:

其中重点给大家讲解一下getParameter,它是最常用的API之一,在开发中前端给后端传递数据的时候是非常常见的需求。在传递中主要有以下三种传递方式:

  • 通过query string传递
  • 通过body(form)
  • 通过body(json)

下面我们来具体看一下。

①通过query string来传递

此时我们约定前端是通过query string来传递username和password的。

代码展示:

java 复制代码
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;

@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //前端通过url的query传递username和password两个属性
        String username = req.getParameter("username");
        if (username == null) {
            System.out.println("username这个key在query string中不存在");
        }
        String password = req.getParameter("password");
        if (username == null) {
            System.out.println("password这个key在query string中不存在");
        }
        System.out.println("username=" + username + ", password=" + password);
        resp.getWriter().write("ok");
    }
}
复制代码

结果展示:

在服务器上输入一下请求之后得到的结果是:

​​​​​​http://127.0.0.1:8080/hello_servlet/getParameter​​​​​​

如果输入的是下面的请求之后得到的结果是:

http://127.0.0.1:8080/hello_servlet/getParameter?username=zhangsan&password=123

注意:query string中的键值对都是程序猿来自定义的。所以这样就可以根据需求来定义了。

②通过body(form)

相当于body里存档数据格式和query string一样,但是Content-Type是application/x-www-form-urlencoed,此时也是通过getParameter来获取到键值对的。这里我们是借助与postman来构造一个请求的。如下所示:

代码展示:

java 复制代码
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;

@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //前端通过body,以form表单的格式,把username和password传递给服务器
        String username = req.getParameter("username");
        if (username == null) {
            System.out.println("username这个key在body中不存在");
        }
        String password = req.getParameter("password");
        if (username == null) {
            System.out.println("password这个key在body中不存在");
        }
        System.out.println("username=" + username + ", password=" + password);
        resp.getWriter().write("ok");
    }
}

结果展示:

但是如果我们这里输入的是中文的张三会有啥问题呢?

在url中query string如果是包含中文或者是特殊字符,那么请务必使用urlencode(☞在线URL编码urlencode工具 - UU在线工具)来进行转码,如果是直接写中文会存在很大的风险。如果不转码,在有些浏览器中/http服务器下对中文支持不好的话,就会出现问题。

转码如下所示:

那么这里我们就直接将转码之后的直接放在url的构造中,如下所示:

http://127.0.0.1:8080/hello_servlet/getParameter?username=%E5%BC%A0%E4%B8%89&password=123

这样在结果中他会自动转回来。

但是如果我们是在postman中使用中文就会出现如下情况。

一般我们在使用postman的时候是utf8的,所以此时我们就需要显示的告诉后端代码请求使用的是utf编码。

代码展示:

java 复制代码
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;

@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //给请求设置utf8
        req.setCharacterEncoding("utf8");
        //前端通过body,以form表单的格式,把username和password传递给服务器
        String username = req.getParameter("username");
        if (username == null) {
            System.out.println("username这个key在body中不存在");
        }
        String password = req.getParameter("password");
        if (username == null) {
            System.out.println("password这个key在body中不存在");
        }
        System.out.println("username=" + username + ", password=" + password);
        resp.getWriter().write("ok");
    }
}

结果展示:

设置之后就解决了乱码的问题。

③通过body(json)

通过body(json)是最常见的方式,这里需要我们重点掌握。json也是一个键值对的格式,但是Servlet自身没有内置json的解析功能,所以这里我们就需要借助其他的第三方库了。这里我们使用的是jackson。我们直接从maven中央仓库来下载。具体步骤看下面。

① 搜索jackson选择第一个。

②然后随便选择一个版本。


③直接复制maven到dependencies中。

④点击刷新maven。

这里我们使用postman来构造请求。

代码展示:

java 复制代码
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;
class User {
    public String username;
    public String password;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    //使用Jackson,最核心的对象就是ObjectMapper
    //通过这个对象,就可以把json字符串解析成java对象,也可以把一个java对象 转化成一个json格式的字符串
    private ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过post请求的body传递过来的一个json格式的字符串
        //第一个参数是表示对谁进行解析
        //第二个参数表示要将传入的参数解析成什么样的java对象。
        User user = objectMapper.readValue(req.getInputStream(),User.class);
        System.out.println("username= " + user.username + ",password =" + user.password);
        resp.getWriter().write("OK");
    }
}

结果展示:

解析readValue里面做的事情:

①解析json字符串,转换成若干个键值对。

②根据第二个参数User.class去找到User里面的的所有的public的属性或者是有public的getter和setter的属性依次进行遍历。

③遍历属性,根据属性的名字,去上述准备好的键值对里查询看看这个属性的名字是否存在对应的value里,如果存在就把value赋值到该属性中。

1.3HttpServletResponse

Servlet中的doXXX方法的目的就是根据请求计算得到响应,然后把响应的数据设置到HttpServletResponse对象中,然后Tomcat就会把这个HttpServletResponse对象按照HTTP协议的格式,转换成一个字符串,并通过Socket写回给浏览器。

核心方法:

|-------------------------------------------------------|--------------------------------------------------------|
| 方法 | 描述 |
| 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) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如, UTF-8。 |
| void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端。 |
| PrintWriter getWriter() | 用于往 body 中写入文本格式数据. |
| OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据. |

代码展示:

java 复制代码
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;

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //为该响应设置状态码
        resp.setStatus(404);
        //设置被发送到客户端的响应的内容类型
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("返回404响应!");
    }
}

结果展示:

自动重定向:

代码展示:

java 复制代码
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;

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //用户访问这个路径的时候,会自动重定向到搜狗主页
//        resp.setStatus(302);
//        //设置一个带有给定的名称和值的header,如果name已经存在,则覆盖旧的值。
//        resp.setHeader("Location","https://www.soguo.com");
        //使用指定的重定向位置url发送临时重定向响应到客户端
        resp.sendRedirect("https://www.sogou.com/");
    }
}
复制代码

结果展示:

页面将自动跳转到搜狗的页面。

通过header来实现自动刷新的效果。

代码展示:

java 复制代码
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;

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置为每隔1s刷新一次
        resp.setHeader("Refresh","1");
        resp.getWriter().write("time=" + System.currentTimeMillis());
    }
}

结果展示:

2.表白墙的更新

之前写的前端表白墙的博客链接☞http://t.csdn.cn/NdwmC

2.1表白墙存在的问题

我们之前只是实现了表白墙的前端,它存在最大的问题就是1.页面重启之后数据会丢失,2.数据只是在本地,别人是看不到的。

那么针对上述的两个问题我们只需要引入后端服务器即可,当用户打开页面的时候,就需要从服务器加载当前已经提交过的数据,当用户新增数据到时候也会把数据交给服务器,让服务器持久化保存。

2.2前后端交互接口

这里当我在写后端代码的时候我们先来明确前后端的交互接口。

  1. 页面加载完毕之后,需要给服务器发一个请求获取到当前的留言数据。
  2. 用户点击提交的时候就需要告诉服务器,当前用户发了的消息是啥。

这在交互的过程中涉及到了一个重要的问题,请求和响应具体是啥样子的,这里都需要我们来做出一个约定。

注意:json中使用[]表示数组,[]中的每一个元素,是一个{}json对象,每个对象里,又有三个属性from,to,message。

对于接口二服务器要做的事情就是解析请求中的body,转成message对象,然后把这个message对象给保存起来。

2.3环境准备

①创建maven项目,然后处理处理静态页面,将静态页面导入到项目中。

静态页面内的代码可以参考小编之前的一篇博客里面的表白墙的代码 ☞

JavaScript(JavaEE初阶系列13)_奶油酒窝✧٩(ˊωˋ*)و✧的博客-CSDN博客

2.4代码的编写

我们想要达到的效果是要在页面下面在加上留言的数据。我们可以直接在前端页面上进行修改然后再在VScode中进行修改。

①选中父元素。

②创建一个新的div标签。

③将新的标签赋值给rowDiv。

④加入数据。

⑤给rowDiv起一个class属性名。

⑥将新加的div标签挂到父标签下面。

这样我们就可以在下面加上数据了。那么接下来我们就需要在前端代码中按照上述的步骤进行改进。

代码展示:

前端代码的展示:

html 复制代码
<!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>
    <!-- 引入 jquery -->
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <style>
        /* * 通配符选择器, 是选中页面所有元素 */
        * {
            /* 消除浏览器的默认样式. */
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        .container {
            width: 600px;
            margin: 20px auto;
        }

        h1 {
            text-align: center;
        }

        p {
            text-align: center;
            color: #666;
            margin: 20px 0;
        }

        .row {
            /* 开启弹性布局 */
            display: flex;
            height: 40px;
            /* 水平方向居中 */
            justify-content: center;
            /* 垂直方向居中 */
            align-items: center;
        }

        .row span {
            width: 80px;
        }

        .row input {
            width: 200px;
            height: 30px;
        }

        .row button {
            width: 280px;
            height: 30px;
            color: white;
            background-color: orange;
            /* 去掉边框 */
            border: none;
            border-radius: 5px;
        }

        /* 点击的时候有个反馈 */
        .row button:active {
            background-color: grey;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>表白墙</h1>
        <p>输入内容后点击提交, 信息会显示到下方表格中</p>
        <div class="row">
            <span>谁: </span>
            <input type="text">
        </div>
        <div class="row">
            <span>对谁: </span>
            <input type="text">
        </div>
        <div class="row">
            <span>说: </span>
            <input type="text">
        </div>
        <div class="row">
            <button id="submit">提交</button>
        </div>
        <div class="row">
            <button id="revert">撤销</button>
        </div>
        <!-- <div class="row">
            xxx 对 xx 说 xxxx
        </div> -->
    </div>

    <script>
        // 实现提交操作. 点击提交按钮, 就能够把用户输入的内容提交到页面上显示. 
        // 点击的时候, 获取到三个输入框中的文本内容
        // 创建一个新的 div.row 把内容构造到这个 div 中即可. 
        let containerDiv = document.querySelector('.container');
        let inputs = document.querySelectorAll('input');
        let button = document.querySelector('#submit');
        button.onclick = function() {
            // 1. 获取到三个输入框的内容
            let from = inputs[0].value;
            let to = inputs[1].value;
            let msg = inputs[2].value;
            if (from == '' || to == '' || msg == '') {
                return;
            }
            // 2. 构造新 div
            let rowDiv = document.createElement('div');
            rowDiv.className = 'row message';
            rowDiv.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
            containerDiv.appendChild(rowDiv);
            // 3. 清空之前的输入框内容
            for (let input of inputs) {
                input.value = '';
            }
            // 4. 通过 ajax 构造 post 请求, 把这个新的消息提交给服务器. 
            let body = {
                "from": from,
                "to": to,
                "message": msg
            };
            $.ajax({
                type: 'post',
                url: 'message',
                contentType: "application/json;charset=utf8",
                data: JSON.stringify(body),
                success: function(body) {
                    // 这是响应成功返回之后, 要调用的回调. 
                    console.log("消息发送给服务器成功!");
                }
            });
        }
        let revertButton = document.querySelector('#revert');
        revertButton.onclick = function() {
            // 删除最后一条消息. 
            // 选中所有的 row, 找出最后一个 row, 然后进行删除
            let rows = document.querySelectorAll('.message');
            if (rows == null || rows.length == 0) {
                return;
            }
            containerDiv.removeChild(rows[rows.length - 1]);
        }

        // 在页面加载的时候, 希望能够从服务器获取到所有的消息, 并显示在网页中. 
        $.ajax({
            type: 'get',
            url: 'message',  // url 都是使用相对路径的写法. 相对路径意味着工作路径就是当前文件所在的路径. 
                             // 当前文件所在路径是 /message_wall/ , 因此此时构造的请求就是 /message_wall/message
            success: function(body) {
                // body 是收到的响应的正文部分. 如我们之前的约定, body 应该是 json 数组
                // 由于响应的 Content-Type 是 application/json, 此时收到的 body 会被 jquery 自动的把它从 字符串 
                // 转成 js 对象数组. 此处就不需要手动的进行 JSON.parse 了. 
                // 此处的 body 已经是一个 JSON.parse 之后得到的 js 对象数组了. 
                // 就需要遍历这个 body 数组, 取出每个元素, 再依据这样的元素构造出 html 标签, 并添加到页面上. 
                let container = document.querySelector('.container');
                for (let message of body) {
                    let rowDiv = document.createElement('div');
                    rowDiv.className = "row";
                    rowDiv.innerHTML = message.from + " 对 " + message.to + " 说: " + message.message;
                    container.appendChild(rowDiv);
                }
            }
        });
    </script>
</body>
</html>

后端代码的改进:

java 复制代码
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.util.ArrayList;
import java.util.List;

class Message {
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    private List<Message> messageList = new ArrayList<>();
    //通过这个方法来"获取所有留言消息"
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //需要返回一个json字符串数组,jackson直接可以帮助我们来进行转换
        String respString = objectMapper.writeValueAsString(messageList);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respString);
    }

    //通过这个方法来实现"提交新消息"
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Message message = objectMapper.readValue(req.getInputStream(),Message.class);
        messageList.add(message);
        System.out.println("消息提交成功!message=" + message);
        //响应只是返回200报文,body为空,此时不需要额外处理,默认就是返回200
    }
}

结果展示:

2.5数据的持久化

上述我们只是实现了在前端页面中在页面重启的时候数据的持久化以及可以让别人也访问到我们的页面,但是如果我们重启服务器的话数据还在吗?答案是不在了,我们的服务器保存的数据是在一个ArrayList中(内存)所以只要进程重启,内存数据可定就没有了,那么我们该怎么将数据进行持久化呢?下面有两种方法。

  • 写到文件中。
  • 写到数据库中。

但是我们一般是将数据写到数据库中来进行数据的持久化。所以接下来我们就使用数据库来进行数据的持久化操作吧。

2.5.1引入JDBC依赖

在maven中找到下面的maven直接引入到pom.xml中。

2.5.2创建数据库

2.5.3编写数据库代码

四步走:

  • DataSource
  • Connection
  • PreparedStatement
  • ResultSet

代码展示:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

import javax.management.loading.MLet;
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.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

class Message {
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
//    private List<Message> messageList = new ArrayList<>();
    //通过这个方法来"获取所有留言消息"
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //需要返回一个json字符串数组,jackson直接可以帮助我们来进行转换
        List<Message> messageList = load();
        String respString = objectMapper.writeValueAsString(messageList);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respString);
    }



    //通过这个方法来实现"提交新消息"
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Message message = objectMapper.readValue(req.getInputStream(),Message.class);
        save(message);
        System.out.println("消息提交成功!message=" + message);
        //响应只是返回200报文,body为空,此时不需要额外处理,默认就是返回200
    }

    //这个方法用来往数据库中存储一条记录
    private void save(Message message) {
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("123456");
        try {
            Connection connection = dataSource.getConnection();
            String sql = "insert into message values(?,?,?)";
            PreparedStatement statement = connection.prepareStatement(sql);
            statement.setString(1,message.from);
            statement.setString(2,message.to);
            statement.setString(3,message.message);
            statement.executeUpdate();

            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    //这个方法用来从数据库中查询所有记录
    private List<Message> load() {
        //用来承装查询到的数据
        List<Message> messageList = new ArrayList<>();
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("123456");
        try {
            Connection connection = dataSource.getConnection();
            String sql = "select * from message";
            PreparedStatement statement = connection.prepareStatement(sql);
            ResultSet resultSet = statement.executeQuery();
            //按照格式进行解析,然后将取到的数据放入到message中
            while (resultSet.next()) {
                Message message = new Message();
                message.from = resultSet.getString("from");
                message.to = resultSet.getString("to");
                message.message = resultSet.getString("message");
                messageList.add(message);
            }
            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return messageList;
    }
}

结果展示:

这样就实现了将数据持久化。即使你重新启动服务器或者是重开机数据也都会存在。

3.Cookie回忆

Cookie是浏览器在本地存储数据的一种机制。

**①Cookie从哪里来:**Cookie是从服务器来的,服务器在响应中会带有Set-Cookie字段,通过这个字段就可以把要保存在浏览器本地的数据给返回回去。

**②Cookie到哪里去:**后续浏览器在访问服务器的时候,就会把本地所有Cookie都通过http请求给带过去。

**③Cookie的作用:**其中一种典型的应用就是使用Cookie保存当前用户登录状态。

在Cookie保存用户身份标,这样的应用场景中,此时身份标识如何分配以及身份信息如何存储,都是需要服务器的支持。其中Session就是服务器这边用来实现用户身份区分的一种机制。它通常是和Cookie配合使用的,它是给当前用户先分配一个SessionId同时记录下当前用户的一些身份信息,这样sessionId就会被返回到浏览器的cookie中后续浏览器访问服务器都会带着这个sessionId从而让服务器识别当前用户身份。

4.Session机制

4.1getSession

通过上述的讲解相信大家对session有了一定的了解,接下来我们就来具体看一下在Servlet中针对cookie和session提供了哪些支持!

HttpServletRequest类中相关的方法:

|------------------------------|-------------------------------------------------------------|
| 方法 | 描述 |
| HttpSession getSession() | 在服务器中获取会话,参数如果为true,则当不存在会话时创建会话,参数如果为false,则当不存在会话时返回null。 |
| Cookie[] getCookies() | 返回一个数组,包含客户端发送请求的所有Cookie对象,会自动把Cookie中的格式解析成键值对。 |

getSession()有一个参数是boolean,如果是false,getSession的行为就是:

  1. 读取请求中Cookie里sessionId
  2. 在服务器这边根据sessionId来查询对应session对象。
  3. 如果查到了,就会直接返回这个session对象,如果没有查到,返回null。

如果参数是true,getSession的行为是:

  1. 读取请求中Cookie里sessionId
  2. 在服务器这边根据sessionId来查询对应session对象。
  3. 如果查到了,就会直接返回这个session对象。
  4. 如果没有查到就会创建一个Session对象,同时说生成一个sessionId,以sessionId为key,Session对象为value,把这个键值对存储到服务器里的一个hash表中,同时把sessionId以Set-Cooki的方式返回给浏览器。

下面我们通过模拟实现登录功能来实践一下。

4.2模拟实现登录功能

首先我们提供两个页面:

  1. 登录页(包含两个输入框,输入用户密码,还有一个登录按钮)点击登录按钮,就会发起一个http请求。当服务器处理这个请求的时候就会验证用户名和密码。如果用户名密码OK,就会跳转到主页。
  2. 主页,这里只是单纯的显示当前用户的用户名(欢迎XXX)。

4.2.1登录页的核心代码。

代码展示:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <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>

结果展示:

其中form就会组织这里的数据以键值对的形式提交给服务器,其中key就是input中的name属性,value就是input用户输入的内容,最终会构成post请求,在body里以键值对(类似于query string)的格式进行组织。

4.2.2编写登录页面的Servlet

登录的请求如下所示:

一般像登录页面这样的请求都是POST比较多,这是属于习惯的用法。

代码展示:

java 复制代码
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 {
        //1.先从请求中拿到用户名和密码
        //为了保证读出来的参数也能支持中文,要记得设置请求的编码方式是utf8
        req.setCharacterEncoding("utf8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //2.验证用户名和密码是否正确
        if (username == null || password == null || username.equals("") || password.equals("")) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前输入的用户名和密码不能为空!");
            return;
        }
        //此处假定用户名只能是zhangsan或者是lisi,密码都是123
        //正常的登录逻辑,验证用户名密码都是从数据库读取的。
        if (!username.equals("zhangsan") && !username.equals("lisi")) {
            //用户名有问题
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("用户名或密码有误!");
            return;
        }
        if (!password.equals("123")) {
            //密码有问题
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("用户名或密码有误!");
            return;
        }
        //3.用户名和密码验证ok,接下来就创建一个会话。
        //当前用户处于未登录状态,此时请求的cookie中没有sessionId
        //此处的getSession是无法从服务器的哈希表中找到该session对象的。
        //由于此处把参数设置为true了,所以就允许getSession在查询不到的时候,创建新的session对象和sessionId
        //并且会自动把这个sessionId和session对象存储在哈希表中。
        //同时返回这个session对象,并且在接下来的响应中会自动把这个sessionId返回给客户端浏览器。
        HttpSession session = req.getSession(true);
        //接下来可以让刚刚创建好的session对象存储咱们定义的数据。就可以在这个对象中存储用户的身份信息。
        session.setAttribute("username",username);
        //4.登录成功之后,自动跳转到主页
        resp.sendRedirect("index");
    }
}

4.2.3编写生成主页的Servlet

代码展示:

java 复制代码
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;
//这个Servlet用来动态生成主页面
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //此处禁止创建会话,如果没有找到,认为用户是未登录的状态
        //如果找到了才认为是登录状态。
        HttpSession session = req.getSession(false);
        if (session == null) {
            //未登录状态
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前用户为登录!");
            return;
        }
        String username = (String) session.getAttribute("username");
        if (username == null) {
            //虽然有会话对象,但是里面没有必要的属性,也认为是登录状态异常
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前用户未登录!");
            return;
        }
        //如果上述检查都OK,接下来就直接生产一个动态页面
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("欢迎你!" + username);
    }
}

4.2.4起启动浏览器

启动浏览器之后我们就可以看到下面的内容了。

同样我们也可以借助fiddler来查看发送的内容。

上述的sessionId也不会一致存储下去,比如服务器重新启动原来的hash表中内容就会消失不见,此时再次访问的时候就可能会出现sessionId无法查询到,于是就会被识别成未登录状态。但是这里我们使用了Smart Tomcat,他为了方便程序猿调试程序,会在停止的服务器的时候,会把会话持久化保存,并且在下次启动的时候,自动那个把会话恢复到内存中,这个时候会话时不会丢失的。上述这一套是否生效,这取决于你使用的Smart Tomcat的版本。

结束语:

好了这节小编就给大分享到这里啦,希望这节对大家学习JavaEE初阶有一定帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)

相关推荐
精神病不行计算机不上班4 小时前
【数据仓库与数据挖掘基础】第二章 数据仓库原理
数据仓库·数据挖掘·spark
火龙谷16 小时前
hadoop案例实践:气象大数据离线分析
大数据·hadoop·分布式
小小打工人22219 小时前
《SQL性能优化指南:新手如何写出高效的数据库查询
hadoop·mysql·hbase
Data-Miner1 天前
31页PPT解析数据湖架构、数据湖和数据仓库的区别、湖仓一体化湖仓一体建设解决方案
大数据·数据仓库
Lowe-小码1 天前
Hive-基础入门
数据仓库·hive·hadoop
viperrrrrrrrrr71 天前
大数据学习(61)-Impala与Hive计算引擎
hive·hadoop·学习·yarn·impala
小刘爱喇石( ˝ᗢ̈˝ )2 天前
hadoop框架与核心组件刨析(五)ZOOKEEPER及选举深度刨析
hadoop·zookeeper·云原生
winner88812 天前
Hive SQL 精进系列:COALESCE 手册
hive·hadoop·sql
闯闯桑2 天前
hive 中各种参数
数据仓库·hive·hadoop
西域编娃2 天前
Hadoop 集群部署与配置详解
大数据·linux·运维·hadoop·分布式