使用IDEA创建一个Maven项目

创建好的Maven如下

通过上图我们可以了解到一些目录结构,这是Maven项目的标准结构,其中
Maven项目创建完成之后,就i会自动生成一个pom.xml文件,我们需要在这个文件中引入Servlet API依赖的jar包



一个项目中可以有多个依赖,每个以来都是一个
Web项目对于目录结构还有自己的要求只有Maven的标准目录还是不够的,需要再创建以下目录进行配置

- web-app PUBLIC
- "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
- "http://java.sun.com/dtd/web-app_2_3.dtd" >
- <web-app>
- <display-name>Archetype Created Web Applicationdisplay-name>
- web-app>
以下编写一个让相应返回一个自定义的字符串代码:

HttpServlet 这个类来自于pom.xml中引入的Servlet API依赖的jar包

doGet是HttpServlet类中的方法,此处是在子类中重写了父类的doGet

通过doGet的源码我们可以大致的了解,它的作用就是根据收到的请求通过响应返回一个405或者400,那么我们可以重写这个方法,根据收到的请求执行自己的业务逻辑,把结果构造成响应对象

resp.getWriter()会获取到一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个HTTP相应的body部分,Tomcat会把整个响应转换成字符串,通过Socket写回给浏览器

上述注解表示Tomcat收到请求中,URL的Servlet Path路径为/test的请求才会调用TestServlet这个类的代码,注解中的字符串表示着URL的Servlet Path
到这里程序的编写已经完成!但是你可能会议或上述代码不是通过main方法作为入口,这是因为main方法包含在Tomcat中,我们写的程序并不能单独执行,而是需要Tomcat才能执行起来(Tomcat的伪代码我们会具体的分析这个问题)
在程序编写好之后,就可以使用Maven进行打包

- packaging标签中用于设置打包类型(如果不进行修改,默认的类型是jar包,jar包是普通Java程序打包的结果,里面包含一i写.class文件;而部署在Tomcat中的压缩包一般为war包,war包里面是Java Web程序,里面除了.class文件以外还包含HTML、CSS、JavaScript、图片等...)
- finaName标签中用于设置打包好后的名字(包名很重要,它对应着请求中的URL的Context Path)

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


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

注意:URL的路径分为两部分 Context Path 和 Servlet Path
为了简化上述操作流程,其实是有一些更加简单的方式:




- name:这一栏可以随意填写
- Tomcat Servlet:表示Tomcat所在的目录
- Deployment Directory:表示项目发布的目录
- Context Path:表示项目路径,默认值就是项目名称
- Servlet Port:表示服务端口
- Admin Port:表示管理端口
- VM options:表示JVM参数



在servlet代码中,我们并没有写main方法,那么对应的doGet方法是如何被调用的呢?响应又是如何返回给服务器的呢?
我们自己实现的Servlet实在Tomcat基础上运行的,下图显示了Web应用程序中的位置

当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器,就可以接收到这个请求,Tomcat的工作就是解析HTTP请求,并把请求交给Servlet的代码来进行进一步的处理,Servlet的代码根据请求计算生成响应对象,Tomcat再把这个响应对象构造成HTTP响应,返回给浏览器,并且Servlet的代码也经常会和数据库进行数据的传递。
下面是通过Tomcat的伪代码的形式来描述Tomcat 初始化和处理请求两部分核心逻辑
Tomcat初始化流程
- class Tomcat {
-
- // 用来存储所有的 Servlet 对象
- private List
instanceList = new ArrayList<>(); -
- public void start() {
- // 根据约定,读取 WEB-INF/web.xml 配置文件
- // 并解析被 @WebServlet 注解修饰的类
-
- // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.
- Class
[] allServletClasses = ...; -
- // 这里要做的的是实例化出所有的 Servlet 对象出来;
- for (Class
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();
- }
- }
- Tomcat的代码内置了main方法,当我们启动Tomcat的时候,就是从Tomcat的main方法开始执行的
- 被@WebServlet注释修饰的类就会在Tomcat启动的时候就会被获取到,并且集中管理
- Tomcat通过反射这样的语法机制来创建被@WebServlet注释修饰的类的实例
- 这些实例被创建完之后,就会调用其中的init方法进行初始化
- 这些实例被销毁之前,就会调用其中的destory方法进行收尾工作
- Tomcat内部也是通过Socket API进行网络通信
- Tomcat为了能够同时处理多个HTTP请求,采取了多线程的方式实现,因此Servlet是运行在多线程环境下的
- class Tomcat {
-
- void doHttpRequest(Socket socket) {
- // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析和响应构建
- HttpServletRequest req = HttpServletRequest.parse(socket);
- HttpServletRequest resp = HttpServletRequest.build(socket);
-
- // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态内容
-
- // 直接使用 IO 进行内容输出
- if (file.exists()) {
- // 返回静态内容
- return;
- }
-
- // 走到这里的逻辑都是动态内容了
- // 找到要处理本次请求的 Servlet 对象
- Servlet ins = findInstance(req.getURL());
-
- // 调用 Servlet 对象的 service 方法
- // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
- try {
- ins.service(req, resp);
- } catch (Exception e) {
- // 返回 500 页面,表示服务器内部错误
- }
- }
- }
- Tomcat从socket中读到的HTTP请求是一个字符串,然后Tomcat会按照HTTP协议的格式解析成一个HttpServletRequest对象
- Tomcat会根据URL中的Path判定这个请求是请求一个静态资源还是动态资源,如果是静态资源,直接找到对应的文件,把文件的容通过Socket返回;如果是动态资源,才会执行Servlet相关逻辑
- Tomcat会根据URL中的Context Path和Servlet Path确定要调用哪个Servlet实例的service方法
- 通过service方法,就会进一步调用我们重写的doGet或者doPost方法
- 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);
- }
- ......
- }
- }
- Servlet的service方法内部会根据当前的请求方式,决定调用其中的某个do...方法
- 在调用do...方法的时候,就会触发多态机制,从而执行到我们自己写好的子类do...方法
对于Servlet主要介绍三个类,分别是HttpServlet、HttpServletRequest和HttpServletResponse
其中HttpSerletRequest和HttpServletResponse是Servlet规范中规定的两个接口,HttpServlet中并灭有实现这两个接口的成员变量,它们知识HttpServlet的service和do...等方法的参数,这两个接口类的实例化是在Servlet容器中实现的。
核心方法
| 方法名称 | 调用时机 |
| 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请求示例:
- @WebServlet("/get")
- public class TestServlet extends HttpServlet{
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/plain");
- resp.setCharacterEncoding("UTF-8");
- resp.getWriter().write("响应了一个Get请求");
- }
- }

处理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");
- }
- }

核心方法
| 方法 | 作用 |
| String getProtocol() | 返回协议的名称和版本号 |
| String getMethod() | 返回请求的HTTP方法名称 |
| String getRequesURL() | 返回请求的URL,不带查询字符串 |
| String getRequestURI() | 返回URL的一部分,不带协名,端口号,查询字符串 |
| String getContextPath() | 返回指示请求URL中的Context Path 部分 |
| String getServletPath() | 返回首行中的Servlet Path 部分 |
| String getQueryString() | 返回首行后面的查询字符串 |
| 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 对象 |
- @WebServlet("/showRequest")
- public class TestServlet 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
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("");
- resp.getWriter().write(html.toString());
- }
- }

如果body的内容格式是x-www-form-urlencoded(这是form表单提交的数据格式,此时body的格式就类似于 query string (是键值对的结构,键值对之间使用&分割,键与值之间使用=进行分割),使用getParameter(获取键值对)进行处理
- 此处是要获取body的数据,由于GET方法一般没有body,这里使用POST方法演示
- 约定body的数据格式是:x-www-form-urlencoded
- 约定body的数据内容是:username=123&password=111
- @WebServlet("/postParameter")
- public class TestServlet extends HttpServlet{
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html;charset=utf-8");
- String username = req.getParameter("username");
- String password = req.getParameter("password");
- resp.getWriter().write("username=" + username + "" +"passwd=" + password);
- }
- }

如果body的内容格式是json,首先将整个body都读取出来,再借助第三方库方法按照json的格式来进行解析,Java标准库没有内置对于json解析的方法
- 此处是要获取body的数据,由于GET方法一般没有body,这里使用POST方法进行演示
- 约定body的数据格式为:json
- 约定body的数据内容为:
{
username:123,
password:111
}
- 此处使用jackson第三方库,使用之前需要去Maven的中央仓库将jackson的依赖引入pom.xml中
jacjkson中的核心类是ObjectMapper,通过这个类的readValue(Stringcontent,Class
valueType)方法,就可以将json字符串转化为一个类的对象(第一个参数是json字符串,第二个参数是类对象),ObjectMapper会遍历定义的类中的每一个成员的名称,去json字符串中的key中查找,如果找到了就将对应的值返回给该成员
- /自定义的将json字符串转化的类
- class UserInfo{
- public String username;
- public String password;
- }
- @WebServlet("/jsonParameter")
- public class TestServlet extends HttpServlet{
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html;charset=utf-8");
- //将整个body中的数据读取出来
- String body = readBody(req);
- //按照 json、格式进行解析
- ObjectMapper objectMapper = new ObjectMapper();
- UserInfo userInfo = objectMapper.readValue(body,UserInfo.class);
- resp.getWriter().write("username=" + userInfo.username + "" + "passwd=" + userInfo.password);
- }
-
- private String readBody(HttpServletRequest req) throws IOException {
- int contextLength = req.getContentLength();
- //准备一个字节数组,来存放body内容
- byte[] buffer = new byte[contextLength];
- //获取·到InputStream对象
- InputStream inputStream = req.getInputStream();
- inputStream.read(buffer);
- //将存放在body内容的字节数组转换成字符串
- return new String(buffer,"utf-8");
- }
- }

核心方法
| 方法 | 作用 |
| 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中写入二进制格式数据 |
示例一:通过代码,构造出不同的响应状态码
- @WebServlet("/status")
- public class TestServlet extends HttpServlet{
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- int status = 404;
- resp.setStatus(status);
- resp.getWriter().write("status"+status);
- }
- }

Fiddler抓包结果:

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

方法一:在响应报头设置状态码和Location来实现重定向
- @WebServlet("/redirect")
- public class TestServlet extends HttpServlet{
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- //将状态码设置为3XX
- resp.setStatus(302);
- //设置一个Location
- resp.setHeader("Location","https://blog.csdn.net/loss_rose777?type=blog");
- }
- }
方法二:直接使用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");
- }
- }

下面是表白墙的内容展示

下面是程序代码:
- html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Documenttitle>
- <style>
- *{
- margin: 0px;
- padding: 0px;
- }
- .container{
- width: 400px;
- margin: 0 auto;
- }
- h1{
- text-align: center;
- padding-bottom: 20px;
- }
- p{
- text-align: center;
- color: gray;
- line-height:40px;
- }
- .button{
- text-align: center;
- }
- .edit{
- margin-bottom: 20px;
- width: 200px;
- height: 30px;
- }
- .n1{
- margin-right: 60px;
- }
- .n2{
- margin-right: 41px;
- }
- .n3{
- margin-right: 60px;
- }
- .button{
- height: 40px;
- width: 300px;
- background-color: orange;
- color: white;
- border: none;
- }
- span{
- font-size: 20px;
- }
- style>
- head>
-
- <body>
- <div class="container">
- <h1>表白墙h1>
- <p>输入后点击提交,会将信息显示在表格中p>
- <div><span class="n1">谁:span><input type="text" class="edit">div>
- <div><span class="n2">对谁:span><input type="text" class="edit">div>
- <div><span class="n3">说:span><input type="text" class="edit">div>
- <div><input type="button" value="提交" class="button" onclick="submit()">div>
- div>
- body>
- <script>
- let edits = document.querySelectorAll('.edit')
- function submit(){
- let n1 = edits[0].value
- let n2 = edits[1].value
- let n3 = edits[2].value
- let div = document.createElement('div')
- div.className = "div1"
- div.innerHTML = n1+"对"+n2+"说"+n3
- let container = document.querySelector('.container')
- container.appendChild(div)
- alert(n1+"对"+n2+"说"+n3)
- }
- script>
- html>
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、更改前端代码
后端代码实现:
- 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;
- }
- @WebServlet("/message")
- public class messageServlet extends HttpServlet {
- //通过这个数组来表示所有消息
- private List
messages = new ArrayList<>(); - //获取服务器所有消息操作
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("application/json;charset=utf-8");
- //获取到消息列表
- //此处需要将message数组中的数据转换成json格式返回给浏览器
- ObjectMapper objectMapper = new ObjectMapper();
- //通过ObjectMapper 的 writeValueAsString() 方法就可以将一个对象转换成一个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("提交成功!");
- }
- }
前端代码实现:
- html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>表白墙title>
-
- <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">script>
- <style>
- * {
- margin: 0px;
- padding: 0px;
- }
-
- .container {
- width: 400px;
- margin: 0 auto;
- }
-
- h1 {
- text-align: center;
- padding-bottom: 20px;
- }
-
- p {
- text-align: center;
- color: gray;
- line-height: 40px;
- }
-
- .button {
- text-align: center;
- }
-
- .edit {
- margin-bottom: 20px;
- width: 200px;
- height: 30px;
- }
-
- .n1 {
- margin-right: 60px;
- }
-
- .n2 {
- margin-right: 41px;
- }
-
- .n3 {
- margin-right: 60px;
- }
-
- .button {
- height: 40px;
- width: 300px;
- background-color: orange;
- color: white;
- border: none;
- }
-
- span {
- font-size: 20px;
- }
- style>
- head>
-
- <body>
- <div class="container">
- <h1>表白墙h1>
- <p>输入后点击提交,会将信息显示在表格中p>
- <div><span class="n1">谁:span><input type="text" class="edit">div>
- <div><span class="n2">对谁:span><input type="text" class="edit">div>
- <div><span class="n3">说:span><input type="text" class="edit">div>
- <div><input type="button" value="提交" class="button">div>
- div>
- body>
- <script>
- let edits = document.querySelectorAll('.edit')
- let Button = document.querySelector('.button');
- Button.onclick = function () {
- //1、获取到输入框中的三个请求
- let n1 = edits[0].value
- let n2 = edits[1].value
- let n3 = edits[2].value
- if (n1 == '' || n2 == '' || n3 == '') {
- return;
- }
- //2、构造新的div
- let div = document.createElement('div')
- div.className = "div1"
- div.innerHTML = n1 + "对" + n2 + "说" + n3
- let container = document.querySelector('.container')
- container.appendChild(div)
- //3、清空之前输入框的内容
- for (let input of edits) {
- input.value = '';
- }
- //4、通过ajax构造post请求,把这个请求提交给服务器
- //构造一个js对象
- let body = {
- from: n1,
- to: n2,
- message: n3
- };
- $.ajax({
- type: 'post',
- contentType: "application/json;charset=utf-8",
- //设置服务器地址的所在位置 使用相对路径表示
- url: 'message',
- //完成json对象和json格式字符串转换
- data: JSON.stringify(body),
- success: function (body) {
- //这是响应成功之后,要调用的回调函数
- console.log("消息发送给服务器成功!")
- }
- });
- }
- //在页面加载的时候,希望能够从服务器获取到所有的消息,并显示到网页中
- $.ajax({
- type: 'get',
- url: 'message',
- success: function (body) {
- //body是收到响应的正文 由于在响应中我们设置的是 application/json 所以此时收到的body会被jquery自动把它
- //从字符串转换为js对象数组,此处就不需要手动进行JSON.parse
- for (let message of body) {
- //构造新的div
- let div = document.createElement('div')
- div.className = "div1"
- div.innerHTML = message.from + "对" + message.to + "说" + message.message
- let container = document.querySelector('.container')
- container.appendChild(div)
- }
- }
- });
- script>
-
- html>

上述代码,我们实现了,前端与后端的交互,以及服务器的部署,但是我么可以保证在页面刷新的时候不会将数据清楚,但是当我们服务器关闭的时候,数据还是会消失,为了解决这个问题,就需要让数据能够持久化存储。
持久化存储:是把数据保存到可以永久保存的存储设备中(如硬盘),是一种将程序数据在持久状态和瞬时状态间转换的机制
持久化存储机制包括: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包

3、将代码与数据库进行连接(主要是将list数组删除,添加一个save和load方法)
- import com.fasterxml.jackson.databind.ObjectMapper;
- import com.mysql.cj.jdbc.MysqlDataSource;
-
- 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 List
messages = new ArrayList<>(); - //获取服务器所有消息操作
- @Override
- protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("application/json;charset=utf-8");
- //获取到消息列表
- //此处需要将message数组中的数据转换成json格式返回给浏览器
- ObjectMapper objectMapper = new ObjectMapper();
- List
messages = null; - try {
- messages = load();
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- //通过ObjectMapper 的 writeValueAsString() 方法就可以将一个对象转换成一个json字符串
- String jsonString = objectMapper.writeValueAsString(messages);
- resp.getWriter().write(jsonString);
- }
- //这个方法用来往数据库中存一条数据
- private List
load() throws SQLException { - List
messages = new ArrayList<>(); - DataSource dataSource = new MysqlDataSource();
- ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message?characterEncoding=utf-8&useSSL=false");
- ((MysqlDataSource)dataSource).setUser("root");
- ((MysqlDataSource)dataSource).setPassword("");
- Connection connection = dataSource.getConnection();
- String sql = "select* from message";
- PreparedStatement statement = connection.prepareStatement(sql);
- ResultSet resultSet = statement.executeQuery();
- 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);
- }
- resultSet.close();
- statement.close();
- connection.close();
- return messages;
- }
-
- @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);
- try {
- save(message);
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
- System.out.println("消息提交成功! message=" + message);
- resp.getWriter().write("提交成功!");
- }
-
- private void save(Message message) throws SQLException {
- DataSource dataSource = new MysqlDataSource();
- ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/message?characterEncoding=utf-8&useSSL=false");
- ((MysqlDataSource)dataSource).setUser("root");
- ((MysqlDataSource)dataSource).setPassword("");
- Connection connection = dataSource.getConnection();
- String sql = "insert into message value(?,?,?)";
- 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();
- }
- }
Cookie介绍我们已经在上一篇Http中说的很详细了Cookie介绍
在了解Cookie之后,我们发现Cookie是不能够用于存储和用户直接相关的信息的,一是Cookie的存储空间有限,二是发送请求时占用的带宽很多,三是不安全。即这些数据不适合保存到客户端,保险粗在服务器是最合适的,通过会话(Session)的方式就能够保存这些数据。
基本介绍:
在计算机中,尤其是网络应用中,Session称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息,当用户在应用程序的Web页面跳转时,存储在Session对象中变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来程序的Web页时,如果该用户还没有会话,则Web服务器将自动会创建一个Session对象,当会话过期或放弃后,服务器会终止该会话。Session对象最常见的一个用法就是存储用户首选项,例如,如果用户知名不喜欢查看图形的时候,就可以将该信息储存在Session对象中,注意会话状态仅在支持Cookie的浏览器中保存。
会话本质:
会话本质就是一个哈希表,其中存储一些键值对结构,key叫做sessionnId,是一个不随机的,不重复的,唯一的字符串,value就是要保存的身份信息,通过HttpSession对象来保存,key和value都是Servlet自动创建的。
每个用户登录都会产生一个会话,服务器会以哈希表的方式将这些会话管理起来
一个会话的详细数据通过一个HttpSession对象来存储,并且HttpSession对象中存储的数据也是键值对结构,key和value都是程序员自定义的
接着Cookie不适合用于存储用户相关的直接信息来讲,由于客户端不适合存储这些数据,服务器这边可以通过Session会话方式来进行保存。下面将会以用户的登录流程来介绍Session会话机制
Session会话机制的好处
注意:Servlet的session默认是保存在服务器内存中的,如果重启服务器Session数据会消失
HttpServletRequest类中相关的方法
| 方法 | 作用 |
| HttpSession getSession(参数) | 在服务器中获取会话,参数如果是true,当不存在会话时,会创建一个会话(包括生成一个新的sessionId和HttpSession对象),并通过Set-Cookie将sessionId返回给客户端;参数如果是false,当不存在会话时会返回null。如果存在sessionIOd且合法,就根据这个sessionId找到对应的HttpSession对象并返回 |
| Cookie[] getCookies() | 返回一个数组,包含客户端发送请求的所有Cookie对象,会自动把Cookie中的格式解析成键值对 |
HttpServletresponse类中的方法
| 方法 | 作用 |
| void addCookie(Cookie cookie) | 把指定的cookie添加到响应中 |
HttpSession类中相关方法
| 方法 | 作用 |
| Object getAttribute(String name) | 该方法返回在Session会话中会有指定名称的对象,如果没有指定名称的对象,返回null |
| void setAttribute(String name,Object value) | 该方法使用指定名称绑定一个对象到该Session会话中 |
| boolean isNew() | 判断当前会话是否是先创建的 |
Cookie类中的相关方法
| 方法 | 作用 |
| String getName() | 该方法返回的Cookie名称(这个值是Set-Cookie字段设置给浏览器的,创建之后不会改变) |
| String getValue() | 该方法获取与Cookie关联的值 |
| void setValue(String newValue) | 该方法设置与Cookie关联的值 |
接下来将使用上述的Session和Cookie相关的方法来实现一个用户登录功能,并且可以记录访问页面次数
登录功能实现思路:
登录功能实现流程:
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Documenttitle>
- head>
- <body>
- <form action="login" method="post">
- <input type="text" name="username"><br>
- <input type="password" name="password"><br>
- <input type="submit" value="登录">
- form>
- body>
- html>

- 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=utf-8");
- //1、从请求中获取到用户名和密码
- String username = req.getParameter("username");
- String password = req.getParameter("password");
- //2、对账号密码进行校验
- if(username==null||username.isEmpty()||password==null||password.isEmpty()){
- 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增加自定义信息,如访问次数
- session.setAttribute("visitCount",0);
- //5、把登录成功的结果返回给客户端(这里的反馈不是简单的提示登陆成功,而是直接跳转到指定页面)
- resp.sendRedirect("index");
- }
- }
- 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.sendRedirect("login.html");
- return;
- }
- //表示用户登录过,获取会话中的访问次数
- Integer visitCount = (Integer) session.getAttribute("visitCount");
- visitCount+=1;
- session.setAttribute("visitCount",visitCount);
- resp.getWriter().write("
visitCount = "
+ visitCount + ""); - }
- }
实现效果如下:

上传文件是日常开发中的一类常见的需求,在Servlet中也进行支持
HttpServletrequest类中的核心方法
| 方法 | 作用 |
| Part getPart(String name) | 获取请求中给定name的文件 |
| Collection | 获取所有的文件 |
Part类中的相关方法
| 方法 | 作用 |
| String getSubmittedFileName() | 获取提交的文件名 |
| String getContentType() | 获取提交文件类型 |
| long getSize() | 获取文件大小,单位为字节 |
| void write(String path) | 把提交的文件数据写入磁盘文件 |
1、写一个前端页面,用于上传文件
- html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>Documenttitle>
- head>
- <body>
- <form action="upload" method="post" enctype="multipart/form-data">
- <input type="file" name="Myfile" id="">
- <input type="submit" value="上传">
- form>
- body>
- html>
2、写一个Servlet用于处理上传的文件
- 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 fileServlet extends HttpServlet {
- @Override
- protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
- resp.setContentType("text/html;charset=utf-8");
- //通过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("D:\\test\\text.mp4");
- resp.getWriter().write("上传成功!");
- }
- }
