Servlet的原理和基础使用

目录

2.Tomcat伪代码

2.Servlet API详解

2.1HttpServlet

2.2 HttpServletRequest

3.案例分析——实现服务器版本的表白墙


1.Servlet运行原理

本篇内容学习在 Servlet 的代码中对应的 doGet 代码是如何被调用的? ?响应如何返回给浏览器?

首先要明确: Servlet是 HTTP应用层里进行的一些操作,属于上层用户态操作;
而底层依赖着内核态的操作:传输层、网络层、数据链路层、物理层,基础网络通信。

我们的实现是在 Tomcat 基础上运行的,浏览器给服务器发送请求时 ,Tomcat (HTTP Servlet)作为 HTTP 服务器(HTTP 协议作为一个应用层协议, 需要底层协议栈来支持), 就可以接收到请求.下图描述了该请求发送的过程:

在用户态: Tomcat是一个应用程序(基于java实现,属于Java进程)是用户态的一个普通进程;

在内核态:Tomcat是运行在JVM之上的,而servlet是Tomcat上层的内容,由Tomcat完成底层的工作,再交由servlet去做进一步的处理,用户在servlet体系下,去完成应用层的工作。(也即在servlet体系下,根据请求计算响应,通过servlet和Tomcat进行交互,Tomcat拿到数据后,和浏览器进行网络传输(封装分用))

打开一个浏览器(用户主机上的应用程序),浏览器也即是客户端的角色,当用户访问一个浏览器时,浏览器就会通过一系列操作构造一个HTTP请求,详细的过程如图:

(1)(发送请求)在客户端(用户主机上)的过程:

  • 把应用层的 HTTP请求 交给传输层,将其封装成一个TCP数据报(TCP报头+HTTP请求)
  • TCP数据报 交给网络,由IP协议进行封装成IP数据报(IP报头+TCP数据报)
  • 把 IP数据报 交给数据链路层,由以太网协议封装成以太网数据帧(帧头+IP数据报+帧尾)
  • 最后把以太网数据帧(二进制数据)交给物理层,通过把1、0转变成高/低电平(高低电磁波信号),再由网线、光纤等硬件设备将其传输出去

数据在传输过程中,会经过n次转发,最终才能到达目的地(网络传输)

(2)(接收请求并解析,计算响应)服务器上的过程:

  • 先是服务器的物理层收到数据,当其转化为以太网数据帧时,向上传递给数据链路层
  • 数据链路层接收到以太网数据帧,进行帧头、帧尾的去除操作,数据变成了IP数据段,再向上传给网络层
  • 网络层接收到IP数据报,去除IP报头,变为TCP数据段,传递给传输层
  • 传输层拿到数据后,去除TCP报头,数据变成了原始的HTTP请求,再将其向上传递给应用层的Tomcat服务器
  • Tomcat接收到HTTP请求之后,针对HTTP协议做进一步的处理:
    • 根据协议里的路径URL,第一级路径ContextPath 指定要访问哪个webapp(网站)
    • 第二级路径ServletPath 指定我们要访问的是哪个HTTPServlet类
    • 根据类里的doPost/doGet等方法,执行对应代码(也即是根据请求计算响应)

以上便是在浏览器访问页面的整个实现过程,涉及客户端和服务器之间的网络传输,之前的网络原理涉及的内容是应用层一下的各层之间的交互过程,而servlet这里,重点关注的是应用层Tomcat的交互

(3) 返回响应:

  •  doGet / doPost 执行完毕后, Tomcat 自动把 HttpServletResponse 对象转换成一个字符串(符合 HTTP 协议), 并通过 Socket 把这个响应发送出去.
  • 响应数据在服务器的主机上通过网络协议栈层层 封装(也即是(1)发送请求的过程), 最终得到(以太网数据帧)二进制 bit 流, 通过物理层硬件设备转换成光/电信号传输出去.
  • 承载信息的光/电信号通过互联网上的网络设备, 最终到达浏览器所在的主机
  • 浏览器主机收到光/电信号, 又通过网络协议栈逐层进行分用, 层层解析, 最终还原成HTTP 响应, 并交给浏览器处理.
  • 浏览器也通过 Socket 读到响应(字符串), 按照 HTTP 响应的格式来解析这个响应. 并把body 中的数据按照一定的格式显示在浏览器的界面上.

2.Tomcat伪代码

伪代码并不能真正的编译运行,只是为了便于理解,程序员编写的实现某个功能的大致逻辑体系,以助于更好地便于缕清思路,构造出完整的实现逻辑。

1、初始化 / 准备工作:加载 Servlet 实例(从制定目录webapp目录中找到要加载的类)

(加载分懒汉模式和饿汉模式,并非Tomcat启动start就调用,根据情况而定)

2、处理请求:根据 URL找到匹配的 Servlet 类,再调用 Servlet 里面对应的方法

(1)Tomcat初始化流程  的伪代码

class Tomcat {
    // 用来存储所有的 Servlet 对象
    private List<Servlet> instanceList = new ArrayList<>();
    public void start() {
// 根据约定,读取 WEB-INF/web.xml 配置文件;
// 并解析被 @WebServlet 注解修饰的类
// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
        Class<Servlet>[] allServletClasses = ...;
// 这里要做的的是实例化出所有的 Servlet 对象出来;
        for (Class<Servlet> cls : allServletClasses) {
// 这里是利用 java 中的反射特性做的
// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的
// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是
// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。
            Servlet ins = cls.newInstance();
            instanceList.add(ins);
        }
// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.init();
        }
// 利用我们之前学过的知识,启动一个 HTTP 服务器
// 并用线程池的方式分别处理每一个 Request
        ServerSocket serverSocket = new ServerSocket(8080);
// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
        ExecuteService pool = Executors.newFixedThreadPool(100);
        while (true) {
            Socket socket = ServerSocket.accept();
// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
            pool.execute(new Runnable() {
                doHttpRequest(socket);
            });
        }
// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
        for (Servlet ins : instanceList) {
            ins.destroy();
        }
    }
    public static void main(String[] args) {
        new Tomcat().start();
    }
}

(2)Tomcat 处理请求流程  的伪代码

    class Tomcat {
        void doHttpRequest(Socket socket) {
// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建
            HttpServletRequest req = HttpServletRequest.parse(socket);
            HttpServletRequest resp = HttpServletRequest.build(socket);
// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
// 直接使用我们学习过的 IO 进行内容输出
            if (file.exists()) {
// 返回静态内容
                return;
            }
// 走到这里的逻辑都是动态内容了
// 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条
// 最终找到要处理本次请求的 Servlet 对象
            Servlet ins = findInstance(req.getURL());
// 调用 Servlet 对象的 service 方法
// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
            try {
                ins.service(req, resp);
            } catch (Exception e) {
// 返回 500 页面,表示服务器内部错误
            }
        }
    }

(3)Servlet 的 service 方法的实现  的伪代码

    class Servlet {
        public void service(HttpServletRequest req, HttpServletResponse resp) {
            String method = req.getMethod();
            if (method.equals("GET")) {
                doGet(req, resp);
            } else if (method.equals("POST")) {
                doPost(req, resp);
            } else if (method.equals("PUT")) {
                doPut(req, resp);
            } else if (method.equals("DELETE")) {
                doDelete(req, resp);
            }
......
        }
    }
  • 1、init:对象创建好就执行初始化。用户也可重写init方法,来执行一些初始化的操作。
  • 2、destroy:退出主循环,Tomcat 结束运行之前回调用。主要是用来 释放资源。
  • 3、service:在处理请求的阶段来调用,每次来个请求都需要调用一次 service。

2.Servlet API详解

重点掌握HttpServlet、HttpServletRequest、HttpServletResponse这三个类

2.1HttpServlet

写 Servlet 代码时, 先创建类, 继承自 HttpServlet, 并重写其中的某些方法.使得Tomcat可以调用。 该过程和继承有关,会涉及到多态。如何理解多态???

  • 集合类: List list = new ArrayList<>();
  • 多线程:class Mythred extends Thread{ 重写 run方法 }
  • Servlet 也是多态!因为我们自己写的代码也是通过继承重写的方式来实现的。

 实际开发中,主要重写 doXXX 方法,方法的调用时机(称为 "Servlet 生命周期")

注意!!! HttpServlet 实例只在程序启动时创建一次. 而不是每次收到 HTTP 请求都重创建


面试题:Servlet的生命周期:

servlet包括怎么出现的、怎么消失的、以及中间的过程和状态

  • servlet在实例化之后调用一次init
  • servlet每次收到请求,调用一次sevice
  • servletz在销毁之前,调用一次destory

变量生命周期:怎么出现的、怎么消失的

(1)构造一个post请求

浏览器中直接输入URL属于get请求;而post请求的构造需要利用form表单或者ajax(需要创建一个html文件,webapp目录下),注意!!!要想使用ajax,必须引入jQuery: jQuery引入    

 若不想引入依赖,可使用第三方工具postman,可以任意构造HTTP请求:

postman Windows下载

   1)test.html

注意!! 使用ajax构造请求时路径不加/,访问test.html文件的路径是:/classServlet/test.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>Document</title>
</head>
<body>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    <script>
        $.ajax({
            type: 'post',
            url: 'method',
            success: function(body) {
                console.log(body);
            }
        });
    </script>

</body>
</html>

2)methodServlet.java(构造post请求,实现dopost方法)

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("/method")
public class methodServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        super.doPost(req, resp);
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("post响应");
    }
}

浏览页面:127.0.0.1:8080/classServlet/test.html

classServlet 是 contextPath


2.2 HttpServletRequest

 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并按照 HTTP 协议的格式把字符串解析成HttpServletRequest 对象.

 注意!!!

String getRequestURI() :返回该请求的 URL 的一部分。
URL:全球资源定位器(Uniform Resource Locator)
URI:唯一资源标识符(Uniform Resource Identifier)
都表示网上唯一资源, URL 表示的是这个资源的位置,而 URI 表示的是这个资源的 ID

 getQueryString()  得到的是 完整的查询字符串,而下面三个方法把 查询字符串  解析为键值对:

  • getParameterNames是得到所有key,以Enum枚举方式表示 ;
  • getParameter根据key得到value;
  • getParameterValues如果存在多个key相同的情况,得到value是一个数组的形式。

 获取请求报头(键值对),servlet解析报头,得到键值对的结构getHeaderName:获取报头中的所有key,getHeader:根据key获取value

 (1)打印请求信息

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 showRequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        super.doGet(req, resp);
        StringBuilder stringBuilder = new StringBuilder();

        stringBuilder.append(req.getProtocol());  //返回请求协议的名称和版本
        stringBuilder.append("<br>");
        stringBuilder.append(req.getMethod());  //返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
        stringBuilder.append("<br>");
        stringBuilder.append(req.getRequestURI()); //,返回该请求的 URL 的一部分。
        stringBuilder.append("<br>");
        stringBuilder.append(req.getContextPath());  //返回指示请求上下文的请求 URI 部分。
        stringBuilder.append("<br>");
        stringBuilder.append(req.getQueryString());  //返回包含在路径后的请求 URL 中的查询字符串。
        stringBuilder.append("<br>");

        stringBuilder.append("<h3>header 部分</h3>");

        Enumeration<String> headerNames = req.getHeaderNames();
        while(headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = req.getHeader(headerName);
            stringBuilder.append(headerName + ":" + headerValue + "<br>");
        }
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write(stringBuilder.toString());
    }
}

得到如下页面:

(2)获取 GET 请求中的参数,使用getParameter

浏览器通过 query string 给服务器传递了两个参数, userId 和 classId,在服务器端就可以通过 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 java.io.IOException;

@WebServlet("/getParameter")
public class getParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        super.doGet(req, resp);
//  假设浏览器传来一个请求:   getParameter?userId=10&classId=20
       String userId = req.getParameter("userId");
       String classId = req.getParameter("classId");http://
       resp.getWriter().write("userId=" + userId +",classId=" + classId );
    }
}
浏览器输入:127.0.0.1:8080/classServlet/getParameter?userId=10&classId=20,得到页面:

(3)获取 POST 请求中的参数

服务器如何获取参数(post请求body格式):

  • x-www-form-urlencoded (使用form表单或postman在前端构造此请求)
  • form-data
  • json

1)x-www-form-urlencoded   ---->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 java.io.IOException;

@WebServlet("/postGetParameter")
public class postGetParameterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     //假设传过来的参数 userId=10&classId=20  服务器也是通过req.getPararmeter获取内容
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");http://
        resp.getWriter().write("userId=" + userId +",classId=" + classId );
    }
}

webapp目录下利用form表单完成的静态页面test.html:127.0.0.1:8080/classServlet/postGetParameter

<!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>Document</title>
</head>
<body>
    <form action="postGetParameter" method="post">
        <input type="text" name="userId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>

</body>
</html>

 使用postman获取请求


2)json获取参数——借助第三方库Jackson(Spring官方推荐库)

maven中央仓库中下载

在浏览器前端代码中,,通过js构造json格式的请求;后端代码中,通过Jackson来处理

<!--构造json格式的请求 不适用form,使用ajax-->
    <input type="text" name="userId">
    <input type="text" name="classId">
    <input type="submit" value="提交">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    <script>
        let userIdInput = document.querySelector('#userId');
        let classIdInput = document.querySelector('#classId');
        let button = document.querySelector('#submit');
        button.onclick = function() {
            $.ajax({
                type: 'post',
                url: 'postJson',
                contentType: 'application/json',
                data: JSON.stringify({
                    userId:userIdInput.value,
                    classId:classIdInput,
                }),
                success: function(body) {
                    console.log(body);
                }
            });
        }
    </script>
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 int userId;
    public int classId;

}

@WebServlet("/postJson")
public class postJsonServlet extends HttpServlet {
    //创建一个Jackson核心对象
    ObjectMapper objectMapper = new ObjectMapper();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //读取body中的请求,使用ObjectMapper解析成需要对对象;
        // readValue:把JSON格式字符串转为java对象
        // 第一个参数:表示对那个字符串进行转换,可写成String对象,也可InputStream对象
        // 第二个参数:把这个JSON格式字符串,转为java对象
      User user = objectMapper.readValue(req.getInputStream(),User.class);
      resp.getWriter().write("userId=" + user.userId +",classId=" + user.classId );
    }
}

readValue如何完成转换的???

  • 先把getInputStream对应的流对象里面的数据读取出来
  • 针对这个JSON字符串进行解析,字符串------>键值对
  • 遍历键值对,获取每一个key,根据key和类里面的属性名称对比一下,看是否有匹配的
    • 若有匹配的属性,则把当前key对应的value赋值给User类的属性中
    • 若无匹配,就跳过,取下一个key

反射:根据类对象获取里面的关键信息

使用ajax提交数据,默认不会跳转页面 ,在控制台中会显示服务器的相应数据

2.3 HttpServletResponse

Tomcat 把 HttpServletResponse 对象(servlet中利用doxx方法计算出来的响应)按照 HTTP 协议的格式, 转成一个字符串, 并通过Socket 写回给浏览器.

(1) 服务器返回的状态码,只是告诉浏览器当前的状态,不影响浏览器正常显示body中的内容

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 setStateServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(404);
        resp.getWriter().write("hello");
    }
}

(2) 让浏览器每秒钟自动刷新一次. 并显示当前的时间戳.(自动刷新header.Refresh)

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("/autoRefresh")
public class AutoRefresh extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Refresh", "1");
        resp.getWriter().write("timeStamp: " + System.currentTimeMillis());
    }
}

(3)构造重定向响应:返回一个重定向 HTTP 响应, 自动跳转到另外一个页面

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);
        resp.setHeader("location","https://www.sogou.com");
    }
}

3.案例分析——实现服务器版本的表白墙

服务器内存版(非持久化存储) 

实现一个服务器,需要考虑提供的什么服务?服务如何触发?如何实现交互??

对于本项目来说:表白墙主要提供两个接口

  • 1. 告诉服务器当前的留言是什么数据
    • 触发:用户点击提交时,会给服务器发送一个Http请求,服务器保存请求
  • 2. 从服务器获取到,当前有哪些留言
    • 触发:页面加载获取所有留言的数据

3.1代码编写

一个项目的创建流程:

(1)创建项目

(2)引入依赖:pom.xml文件内写入依赖

(3)构建目录

(4)编写代码

(5)打包部署

(2)引入依赖:pom.xml文件内写入依赖

   <dependencies>
<!--  servlet依赖  https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

 <!-- Jackson依赖 https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind    -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.2.1</version>
        </dependency>
    </dependencies>

(4)编写代码和打包部署

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("/message")
public class MessageServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理提交消息请求
        resp.getWriter().write("helloPost");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //获取消息列表
        resp.getWriter().write("helloGet");
    }
}

 (5)实现代码:后端逻辑

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{  //创建一个类Message表示提交的一个请求记录
    public String from;
    public String to;
    public String message;
}

@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();//通过ObjectMapper类解析(构造)JSON对象

    List<Message> messageList = new ArrayList<>();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理提交消息请求
 //1.从请求流对象中读取当前的数据内容,再把读取到的对象转成Message.class形式的对象
        Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.保存数据(保存到服务器内存),需提前创建一个对象用于保存数据
        messageList.add(message);
//3.通过setContentType告知页面返回数据的格式是JSON
        resp.setContentType("application/json;charset=utf8");  // jquery ajax自动把字符串转化为js对象
        resp.getWriter().write("{\"ok\":true}");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       //获取消息列表,把所有内容返回给客户端,,objectMapper把java对象转为JSON对象
        String jsonString = objectMapper.writeValueAsString(messageList);
        System.out.println("jsonString:"+jsonString);
        resp.setContentType("application/json;charset=utf8");  // jquery ajax自动把字符串转化为js对象
        resp.getWriter().write("helloGet");
    }
}

使用postman进行连接检查:

 (6)前端代码

<!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>
    <!-- 通过内部样式style标签,引入CSS样式 -->
    <style>
        *{
            /* 首先先去除浏览器样式 */
            /* 将 内外边距设置为0,设置盒子模型为向内挤压 */
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        .containner{
            width: 100%;
        }
        h3{
            /* 文本居中 */
            text-align: center;
            /* 上下边距为 20px,左右边距为0 */
            padding: 20px 0;
            font-size: 24px;
        }

        p{
            text-align: center;
            color: #666;
            padding: 10px 0;
        }

        .row{
            width: 400px;
            height: 50px;
            /* 上下外边距为零,左右外边距自适应 */
            /* 就是元素水平居中操作 */
            margin: 0 auto;
            /* 弹性布局 */
            display: flex;
            /* 水平居中 */
            justify-content: center;
            /* 垂直居中 */
            align-items: center;
        }

        .row span{
            width: 60px;
            font-size: 17px;
        }
        .row input{
            width: 300px;
            height: 40px;
            line-height: 40px;
            font-size: 20px;
            text-indent: 0.5em;
            outline: none;
        }

        .row #submit{
            width: 360px;
            height: 40px;
            font-size: 20px;
            line-height: 40px;
            margin: 0 auto;
            color: white;
            background-color: orange;
            border: none;
            border-radius: 15px;
            outline: none;
        }
        /* 当鼠标点击按钮的时候,会改变按钮颜色 */
        .row #submit:active{
            background-color: grey;
        }
    </style>
    <div class="container">
        <h3>表白墙</h3>
        <p>输入后点击提交,会将信息显示在表格中</p>
        <br>
        <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>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
    <script>
//加入ajax代码,此处要加入的逻辑有两部分: 
//1.点击提交按钮时,ajax要构造数据发送给服务器
//2.页面加载时,从服务器获取消息列表,并在界面上直接显示 

        function getMessages() {
            $.ajax({
                type: 'get', 
                url: 'message',
                success: function(body) {
                    let newDiv = document.createElement('div');
                    
                    for(let message of body) {   //依次取数组中的每个元素
                        newDiv.innerHTML = from + "对" + to +"说:" + say;
                        newDiv.className = 'row';
                        let container = document.querySelector('.container');  // 将新建节点,挂在 container 这个节点下面
                        container.appendChild(newDiv);
                    }
                }
            });
        }
         
 //当用户点击submit,就会获取input的内容,从而把内容构造成一个div,查到页面尾部       
        let submitBtn = document.querySelector('#submit');
        submitBtn.onclick = function(){
            // 1、获取 3个input 文本框中的数据
            let inputs = document.querySelectorAll('input');
            let from = inputs[0].value;
            let to = inputs[1].value;
            let say = inputs[2].value;
            if(from == ''|| to == '' || say == ''){
                // 用户填写的数据,并不完整。所以不提交。
                return;
            }
            // 2、生成一个新的 div,内容就是 三个 input 文本框的内容拼接
            // 再把这个 元素,挂在DOM树上。
            let newDiv = document.createElement('div');
            newDiv.innerHTML = from + "对" + to +"说:" + say;
            newDiv.className = 'row';
            // 将新建节点,挂在 container 这个节点下面
            let container = document.querySelector('.container');
            container.appendChild(newDiv);
            //3.清空之前输入框的内容
            for(let i = 0; i < inputs.length; i++) {
                inputs[i].value = '';
            }
            //4.把当前获取到的输入框的内容,构造成一个http post请求,通过ajax发给服务器
            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) { 
                    alert("消息提交成功");
                },
                error: function() {
                    alert("消息提交失败!");
                }
            });
        }
    </script>
</body>
</html> 

127.0.0.1:8080/messageServlet/test.html

 test.html前端页面的书写只能保证写入的数据能够正常显示出来,但不能保存,不能刷新,服务器重启也会导致页面写入的内容不见了

 对象和JSON对象之间的转换:

Java:

objectMapper.readValue  :json字符串转成对象

objectMapper.writeValueAsString:对象转成json字符串

JS:

JSON.parse:json字符串转成对象

JSON.stringify:对象转成json字符串

硬盘能持久存储,内存不能持久存储,也有一种介于硬盘和内存之间的设备(像内存一样访问快、也能像硬盘一样持久存储)


使用JDBC基本流程:

  • 创建数据源
  • 和数据库建立连接
  • 构造SQL语句
  • 执行SQL语句
  • 如果是查询语句,需要遍历结果集,若是插入/删除/修改,则不需要
  • 关闭资源,释放连接

为解决上述数据内容的丢失,引入数据库进行数据保存。保证页面刷新后,数据不丢失,但不能保证服务器重启后数据不丢失(把数据保存到硬盘上(可以是文件、也可是数据库))

(0)第一步在pom.xml文件中添加数据库依赖(确保和自己的版本对应)

(1)选中数据库java102,创建表message(from、to 是表中的关键字,需要加上反引号``)

(2)DButil - 数据库访问程序 DBUtil.java文件

主要是简化MessageMysqlServlet中的程序:

  • 将 数据库连接,创建数据源,资源释放,具体实现细节给封装成一个类。
  • 来供 MessageServlet 中的程序使用
import com.mysql.cj.jdbc.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/java102?characterEncoding=utf8&useSSL=false";  //连接数据库的地址
    private static final String USERNAME = "root";  //用户名
    private static final String PASSWORD = "123456";  //密码

    private static DataSource dataSource = null;   //创建实例dataSource

    private static DataSource getDataSource() {   //单例模式中的——懒汉模式
        if (dataSource == null) {
            dataSource = new MysqlDataSource();
            ((MysqlDataSource)dataSource).setUrl(URL);
            ((MysqlDataSource)dataSource).setUser(USERNAME);
            ((MysqlDataSource)dataSource).setPassword(PASSWORD);
        }
        return  dataSource; 
    }

    public static Connection getConnection() throws SQLException {  //数据库建立连接
        return getDataSource().getConnection();
    }

    public void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

 

  是否存在线程安全的问题???

看当前调用的方法doXXX处于什么环境,当处于多线程环境下是存在线程安全的问题的

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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/messageMysql")
public class MessageMysqlServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();//通过ObjectMapper类解析(构造)JSON对象


    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //处理提交消息请求
        //1.从请求流对象中读取当前的数据内容,再把读取到的对象转成Message.class形式的对象
        Message message = objectMapper.readValue(req.getInputStream(),Message.class);
//2.保存数据(保存到服务器内存),需提前创建一个对象用于保存数据

//3.通过setContentType告知页面返回数据的格式是JSON
        resp.setContentType("application/json;charset=utf8");  // jquery ajax自动把字符串转化为js对象
        resp.getWriter().write("{\"ok\":true}");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取消息列表,把所有内容返回给客户端,,objectMapper把java对象转为JSON对象
        List<Message> messageList = load();
        String jsonString = objectMapper.writeValueAsString(messageList);
        System.out.println("jsonString:"+jsonString);
        resp.setContentType("application/json;charset=utf8");  // jquery ajax自动把字符串转化为js对象
        resp.getWriter().write(jsonString);
    }

    private void save(Message message)  {
        //把消息保存到数据库
        Connection connection = null;
        PreparedStatement statement = null;
        //1.和数据库建立连接
        try {
            connection = DBUtil.getConnection();
            //2.构造SQL语句
            String sql = "insert into message values(?,?,?)";  //3个“?“进行占位
            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 e) {
            e.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,null);
        }
    }

    private List<Message> load()  {
        List<Message> messageList = new ArrayList<>();
        //1.从数据库中获取所有信息
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;  //定义一个resultSet存放结果
        try {
            connection = DBUtil.getConnection();
            String sql = "select * from message";  //查询语句
            statement = connection.prepareStatement(sql);
            resultSet = statement.executeQuery();
        //2.遍历结果集
            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);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return messageList;
    }
}

开发一个简单网站的基本步骤:

  • 约定前后端交互的接口
  • 开发服务器代码
    • 编写servlet能够处理前端发来的请求
    • 编写数据库代码,存储/获取关键信息
  • 开发客户端代码
    • 基于ajax构造请求以及解析响应
    • 响应用户操作(点击按钮之后,触发给服务器发送请求的行为)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值