Servlet

1.Servlet是什么

Servlet是一种实现动态页面的技术。是一组Tomcat提供给程序员的API,帮助程序员简单高效的开发一个web app

回顾 动态页面 VS 静态页面

静态页面也就是内容固定的页面,即使 用户不同/时间不同/输入参数不同,页面的内容也不会发生改变。(除非网站的开发人员修改源代码)

对应的,动态页面指的是 用户不同/时间不同/输出的参数不同,页面也会跟着改变。

【举一个栗子】

Tomcat的主页就是一个静态页面,什么时候打开,什么人打开都是相同的页面。

 而B站的页面是一个动态的页面,根据不同的时间、不同的地区、不同的人员推送不同的内容

构建动态页面的技术有很多,每种语言都有一些相关的库/框架来做这些事情。

Servlet就是Tomcat这个HTTP服务器提供给Java的一组API,来完成构建动态页面这个任务。

Servlet主要做的工作

  • 允许程序员注册一个类,在Tomcat收到某个特定的HTTP请求的时候,执行类中的一些代码;
  • 帮助程序猿解析HTTP请求,把HTTP请求从一个字符串解析成一个HttpRequest对象;
  • 帮助程序员构造HTTP响应。程序员只要给指定的HttpResponse对象填写一些属性字段,Servlet就会自动的安装HTTP 协议的方式构造出一个HTTP响应字符串,并通过Socket写回客户端。

简而言之,Servlet是一组Tomcat提供的API,让程序员自己写的代码能很好的和Tomcat配合起来,从而更简单的实现一个web app

2.第一个Servlet程序

2.1创建项目

打开IDEA -> 创建maven文件 -> 设置路径,给文件命名 -> 完成创建

这个是创建成功后的样子。

2.2引入依赖

Maven项目创建完毕后,会自动生成一个pom.xml文件(如上图)

我们需要在pom.xml种引入Servlet API依赖的jar包

  1. 在Maven中央仓库中搜索 "servlet",一般第一个结果就是。
  2. 选择版本。我们一般使用3.1.0版本

    Servlet的版本要和tomcat相匹配,我们使用的是Tomcat8.5,那么就需要使用Servlet 3.1.0。我们可以查看 http://tomcat.apache.org/whichversion.htm 查询版本之间的对应关系。
  3. 把中央仓库中提供的xml复制到项目的pom.xml中。

    格式如下:

 【注意】如果我们发现粘贴上去,一些内容出现红色或者是出现报错,我们刷新一下,这里IDEA会自动下载这些资源。

关于groupId, artifactId, version

这几个东西我们不必关注。如果我们要把这个写的代码发布到中央仓库上,那么我们就要设定好这几个ID

groupId:表示组织名称

artifactId:表示项目名称

version:表示版本号

中央仓库就是按照这三个字段来确定唯一一个包(下面红色框里面的就是)

 2.3创建目录

当项目创建好了以后,IDEA会帮我们自动创建出一些目录。形如

 这些目录中:

  • src 表示源代码所在的目录
  • main/java 表示源代码的根目录。后续创建 .java 就放到这个目录中
  • main/resources 表示项目的一些资源文件所在的目录
  • test/java 表示测试代码的根目录
  • 同时目录不够,我们还需要一些新的目录

1)创建相关目录

在main目录下,和java目录并列,创建一个webapp目录(注意:不是webapps),同时在里面创建WEB-INF目录,同时在此目录下创建web.xml文件,如下图(文件的名字不要更改,基本上是固定格式)

 2)编写web.xml

往web.xml中拷贝以下代码,我们在这里并不要关注细节(注意:下面代码第三行,可能会报错,这是IDEA不是识别的问题,我们不必理会)

<!DOCTYPE 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 Application</display-name>
</web-app>

webapp目录就是未来部署到Tomcat中的一个重要目录,当前我们往webapp中放一些静态资源,比如html,css等

在这个目录中国还有一个重要的文件web.xml。Tomcat找到这个文件才能正确处理webapp中的动态资源

2.4编写代码

在Java目录中创建一个类HelloServlet,代码如下:

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        System.out.println("hello");
        resp.getWriter().write("hello");
    }
}
  • 创建一个类HelloServlet,继承自HttpServlet
  • 在这个类的上方加上 @WebServlet("/hello") 注解,表示Tomcat收到的请求,路径为 /hello 的请求才会被调用HelloServlet这个类的代码
  • 重写 doGet 方法。doGet的参数有两个,分别表示 收到的HTTP的请求 和 要构造的HTTP响应。这个方法会在Tomcat收到GET请求时触发。
  • HttpServletRequest表示HTTP请求。Tomcat按照HTTP请求的格式把字符串格式的请求转化成一个HttpServletRequest对象。后续我们从此对象可以获取请求中的相关信息。
  • HttpServletResponse表示HTTP响应。代码中把响应对象构造好
  • response.getWriter()会获取一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被放在HTTP响应的body部分。

额外信息介绍

  1. 我们的代码不是通过main方法作为入口的。main方法包含在Tomcat里,我们写的代码会被Tomcat在合适的时机调用
  2. 我们随便写个类都能被Tomcat调用吗?满足啥样的条件才能被调用?
  • 创建的类需要继承自HttpServlet
  • 这个类需要使用@WebServlet注解关联上一个HTTP的路径
  • 这个类需要实现 doXXX 方法        

当这三个条件满足的时候,Tomcat就可以找到这个类,并在合适的时机进行调用。

2.5打包程序

使用maven进行打包,打开maven窗口(一般在IDEA右侧就能看到Maven窗口,如果看不到,点击菜单 -> View -> Tool Window -> Maven打开)

然后展开Lifecycle,双击package即可进行打包

 打包成功后,可以看到target目录下,会生成一个jar包

这样的jar包并不是我们需要的,Tomcat需要识别的是另一种war包格式(可以理解为一种不同格式的压缩包),同时这个jar包的名字太复杂了,我们也希望这个名字能更简单一点

【war包和jar包的不同区别】

jar包是普通的Java程序打包后的结果,里面会包含一些 .class 文件

war包是 java web 的程序,里面除了会包含 .class 文件之外,还会包含HTML,CSS,JS,图片,以及其他的jar包,打成 war 包格式才能被Tomcat识别

在 pom.xml 中新增一个packing标签,表示打包的方式是打成一个 war 包,同时我们再添加一个build标签,内置了一个finalName标签,表示打出的war包的名字是HelloServlet

 我们重新打包即可

2.6部署程序

把war拷贝到Tomcat的webapps目录下,启动Tomcat,Tomcat会自动把war包解压

2.7验证程序

此时通过浏览器访问 127.0.0.1:8080/ServletHelloworld/hello 就能看到响应的结果了

 【注意】URL中的PATH分成两个部分,其中 ServletHelloworld 是 Context Path,hello为 Servlet Path

 

 3.更方便的部署

手动拷贝到war包到Tomcat的过程比较的麻烦,我们可以使用更加简单的方式。

此处我们使用IDEA中的Smart Tomcat插件完成这个工作。

什么是插件?

像IDEA这样的程序软件非常的强大,但是也是无法做到面面俱到。对于一些特殊场景的功能,比如在开发的时候,我们要求自动播放音乐或者能获得鼓励的语音,如果我们需要,就能自己单独安装。

插件就是对程序的一些特定场景,做出一些特定的功能的扩展。

3.1安装Smart Tomcat插件

1.菜单 -> 文件 -> Setting

 2.选择Plugins,选择Marketplace,搜索"tomcat",点击"Install"

 3.安装完成后,重新启动

3.2配置Smart Tomcat插件

1.点击页面右上角的”Add Configuration"

 2.选择左侧"Smart Tomcat"

 3.在Name这一栏填写一个名字(可以随便写)

在Tomcat Server这一栏选择Tomcat所在的目录。其他选项不用修改

 

 4.点击OK之后,右上角变成了这样(如下图所示),我们点击绿色的三角,IDEA就会自动进行编译,部署,启动Tomcat的过程

5.访问页面

在浏览器中输入 127.0.0.1:8080/ServletHelloWorld/hello  访问页面

使用此插件的好处:

我们不需要打包和部署,直接点击运行即可,这方便了在开发过程中让项目跑起来的过程。

4.Servlet API详解

4.1HttpServlet

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

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

在实际的开发过程中,我们重点写的是doXXX方法,很少会重写init/destory/service

这些方法的调用时机,就称为“Servlet生命周期”(描述了Servlet从创建到销毁的全过程)

 【注意】HttpServlet的实例只是在程序启动的时候创建一次,而不是在每次收到HTTP请求的时候创建。

1.代码示例:处理GET请求

创建MethodServler.java,创建doGet方法

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

我们访问构造一个请求对其进行访问(注意路径问题,不要输入错误)

关于乱码问题

我们如果在响应中输入中文,例如:

resp.getWriter().write("GET 方法");

我们再次访问浏览器,看看能否正常访问

 关于“乱码”

中文的编码方式有很多,其中最常见的就是UTF-8

如果没有显示的指定编码方式,则浏览器不能正确识别,就会出现乱码的情况。

我们在代码中,通过 resp.setContentType("test/html; charset=utf8");显示的指定编码方式

 2.代码示例:处理POST请求

在MethodServlet.java中,新增doPost方法。

@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("Post 方法");
    }

我们构造POST请求,看看有啥效果(注意每次写完新的代码要重启服务器)

 4.2HttpServletRequest

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

核心方法】

方法描述
String getProtocol()返回请求协议的名称和版本
String getMethod()返回请求的HTTP方法的名称
String getRequestURI()从协议名称直到HTTP请求的第一行的查询字符串中,返回该请求的URI的一部分
String getContextPath()返回请示上下文的请求URI部分
String getQueryString()返回包含在路径后的请求URL中的查询字符串

Enumeration

getParameterName()

返回一个String对象的枚举,包含在该请求中包含的参数的名称
String getParameter(String name)以字符串的形式返回请求参数的值,如果不存在返回NULL
String[] getParameterValues(String name)返回一个字符串对象的数组,包含所有给定参数的值,如果参数不存在返回NULL
String getHeader(String name)以字符串的形式返回请求头的值
String getCharacterEncoding()请求返回主体中使用的字符编码的名称
String getContentType()返回请求主体中MIME类型,如果不知道类型返回NULL
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知返回 -1
InputStream getInputStream()用于读取请求的body内容,返回一个InputStream对象

1.代码示例:打印请求信息

创建ShowRequest类

@WebServlet("/ShowRequest")
public class ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        StringBuilder ret = new StringBuilder();
        ret.append(req.getProtocol());//获取协议名称和版本号
        ret.append("<br>");//换行标签
        ret.append(req.getMethod());//获取方法名
        ret.append("<br>");
        ret.append(req.getRequestURI());//获取URI
        ret.append("<br>");
        ret.append(req.getContextPath());//获取上下文路径
        ret.append("<br>");
        ret.append(req.getQueryString());//获取查询字符串
        ret.append("<br>");
        //获取中参数名称和参数的值
        Enumeration<String> headerNames = req.getHeaderNames();
        while(headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            ret.append(headerName + " : " + req.getHeader(headerName));
            ret.append("<br>");
        }
        resp.getWriter().write(ret.toString());
    }
}

 2.代码示例:获取GET请求中的参数

GET请求种的传输一般都是通过query string传递给服务器的,形如

创建GetParameter类

@WebServlet("/getParameter")
public class getParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html; charset=utf8");
        String useId = req.getParameter("useId");
        String classId = req.getParameter("classId");
        resp.getWriter().write(useId + " : " + classId);
    }
}

 3.代码示例:获取POST请求中的参数(1)

POST请求的参数一般通过body传递给服务器。body种的数据格式有很多种。如果我们采取form表单的形式,仍然可以通过getParameter获取参数的值

创建PostParameter

@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        
        resp.setContentType("text/html; charset=utf-8");
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write(userId + " : " + classId);
        System.out.println(userId + " : " + classId);
    }
}

 4.代码示例:获取POST请求中的参数(2)

如果POST请求中的body是按照JSON的格式来传递,那么获取参数的代码也要发生调整。

首先我们要引入依赖:Maven中央仓库搜索 jackson (一般第一个就是我们需要的),然后随便选一个版本,我们复制相关的代码。(我们复制过去后,可能刷新才能开始下载)

 然后我们开始编写代码

  •  我们创建一个User,用于表示json传过来的每个信息;
  • 然后创建一个objectMapper对象,用于等会将json转化为Java对象
  • 我们将json转化为Java对象,并在控制台输出,然后resp返回OK即可。
//用于储存json传过来的内容
class User {
    public String username;
    public String password;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //将json转化一个Java对象
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        //我们返回OK
        System.out.println(user.username + " : " + user.password);
        resp.getWriter().write("OK");
    }
}

JSON的格式:

  • 每个元素使用{ }
  • 多个元素之间使用 ”," 分割
  • 所有元素外面使用 [ ] 括起来,表示数组。
  • 元素内,键与值之间使用 :分割,左边是键,右边是值,同时用 " " 引起来

4.3HttpServletResponse

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

核心方法

 方法描述
void setStatus(int c)为响应设置一个状态码
void setHeader(String name, String value)设置一个带有给定的名称和值的header.如果name已经存在,则会覆盖旧的值。
void addHeader(String name, String value)添加一个带有给定的名称和值的header.如果name已经存在,不会覆盖旧的值,并列添加新的键值对
void setContentType(String type)设置被发送到客户端的响应的内容类型
void setCharacterEncoding(String charset)设置被发送到客户端的响应的字符编码方式
void sendRedirect(String location)使用指定的重定向位置URL发送临时重定向相应到客户端
PrintWriter getWriter()用于往body中写入文本格式数据
OutputStream getOutputStream()用于往body中写入二进制格式数据

注意:对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前,否则可能会设置失败

1.代码示例:设置状态码

设置一个程序,用户在浏览器通过参数指定要返回的状态码。

@WebServlet("/statusServlet")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String statusString = req.getParameter("status");
        if(statusString != null) {
            resp.setStatus(Integer.parseInt(statusString));
        }
        resp.getWriter().write(statusString);
    }
}

2.代码示例:自动刷新 

实现一个程序,让浏览器每秒钟自动刷新一次,并且显示当前的时间戳

@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setHeader("Refresh", "1");
        resp.getWriter().write("time : " + System.currentTimeMillis());

    }
}

 3.代码重定向

实现一个程序,返回一个重定向HTTP响应,自动跳转到另外的一个页面

@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.sendRedirect("http://www.sogou.com");
    }
}

5.代码示例:服务器版表白墙

5.1准备工作

  1. 创建maven项目
  2. 创建必要的目录:webapp -> WEB-INF -> web.xml
<!DOCTYPE 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 Application</display-name>
        </web-app>

3.调整pom.xml

引入依赖(servlet,mysql,jackson),配置生成war包,以及war包的名字

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>ConfessionWall</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

        <!-- 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>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

    </dependencies>

    <packaging>war</packaging>
    <build>
        <finalName>ConfessionWall</finalName>
    </build>

</project>

4.把表白墙前端页面拷贝到webapp目录中(我们简单看一下代码,了解即可,可以直接复制过去)(属于前端代码)

<!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>

5.2约定前后端交互接口

“前后端交互接口”是进行Web开发中的关键环节。

具体来说,就是允许页面给服务器发送哪些HTTP请求,并且每种请求预期获取什么样的HTTP响应

1.创建数据库相关表格表

create table Message(id int unique auto_increment, 
`from` varchar(20),
`to` varchar(20), 
message text);

2.查询所有表白墙的信息

  • 创建表白墙信息类,表示谁对谁说什么
  • 查询原来的数据中的信息并返回list
  • 把这个list传入resp
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();
    List<Message> messageList = new ArrayList<>();
    //加载已经保存到数据库中的信息
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //通过list来保存相关信息,通过load来从数据库调用信息
        messageList = new ArrayList<>();//防止每次刷新时,messageList中有的信息与数据库中的重复导致再次往里面存储
        messageList = load();
        String respString = objectMapper.writeValueAsString(messageList);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respString);
    }
//从数据库中读信息
    private List<Message> load(){


        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("1020118096");
        try {
            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");
                messageList.add(message);
            }
            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return messageList;
    }
}

 3.提交一条信息

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        save(message);

    }

    private void save(Message message) {
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("1020118096");
        try {
            Connection connection = dataSource.getConnection();
            String sql = "insert into message(`from`, `to`, 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();
        }
    }

4.输入响应的网站进行测试

在搜索框中输入响应的URL:表白墙http://127.0.0.1:8080/ConfessionWall/ConfessionWall.html

 我们写如响应的信息并提交:

 我们刷新页面:

 关闭服务器,并重启后打开页面信息也不会丢失。

6.Cookie和Session

6.1回顾Cookie

HTTP协议自身是属于“无状态”协议。

“无状态”的含义是:

默认情况下HTTP协议的客户端和服务器之间的这次通信,和下次通信之间是没有联系的。

【栗子】有直接联系的通信:比如一个后台管理系统(包含登录页面和管理页面),我们正常情况下,我们要先打开登录页面进行登录,才能进入管理页面。我们如果直接输入管理页面的URL,我们理论上是不允许的。这时进行的就是有联系的通信。

 此时服务器这边就需要记录令牌信息,以及令牌对应的用户信息,这个就是Session机制所作的工作。

 6.2理解会话机制(Session)

什么是“令牌”?令牌就是规定一个特殊的字符串,这个字符串对应着每个用户的详细信息,以便后续操作不需要再次登录。

会话的本质是一个“哈希表”,储存了一些键值对。key就是令牌的ID(token/sessionID),value就是用户信息。

sessionID就是服务器生成的一个“唯一性字符串”,从session机制的角度来看,这个唯一性字符串称为“sessionId"。但是站在整个登录流程看,也可以把这个唯一性字符串称为“token"。本质上是一个东西的不同叫法。

 【登录的全过程】

  • 用户打卡登录页面,输入用户名和密码进行提交。
  • 服务器收到这些信息,与数据库中的信息进行匹配,匹配成功,服务器中新增一条新的记录,并返回响应的结果和sessionId给客户端。
  • 客户端进入登录页面,并返回其他响应的页面或者重新打开登录页面,客户端会把响应的sessionId一并提交过去,从而达到不用反复登录的目的。

Servlet的Session默认是保存到内存中的。如果重启服务器则Session数据会丢失。

6.3Cookie和Session的区别

  • Cookie是客户端机制(主要保存到客户端主机)。Session是服务器端的机制(主要保存到服务器端)
  • Cookie和Session经常会一起配合来使用,但不是必须配合。
    • 完全可以使用Cookie来保存一些数据在客户端。这些数据不一定是用户的身份信息,也不一定是token/sessionId,可以是一些用户自定义设置或主题等。

6.4核心方法

HttpServletRequest类中的关于Session或Cookie的方法

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

HttpServletResponse类中的关于Session或Cookie的方法

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

HttpSession类中的相关方法:

方法描述
Object getAttribute(String name)

该方法返回在该session会话中具有指定名称的对象,如果没有指定名称的对象,则返回null.

void setAttribute(String name, object value)该方法使用指定的名称绑定一个对象到该session会话
boolean isNew()判断当前是否是新创建的会话

Cookie类中的相应的方法

每个Cookie对象就是一个键值对。

方法描述
String getName()该方法返回Cookie的名称,名称在创建后不能改变。(这个值是SetCookie字段设置给浏览器的)
String getValue()该方法获取与cookie关联的值
void setValue(String newValue)该方法设置与cookie关联的值
  • HTTP的Cookie字段中存储的实际上是多组键值对。每个键值对在Servlet中对应了一个Cookie对象
  • 通过HttpServletRequest.getCookies()获取到请求中一系列Cookie键值对。
  • 通过HttpServletResponse.addCookie()可以向响应中添加新的Cookie键值对。

6.5代码示例:实现用户登录

1. 引入相应的依赖

我们引入Servlet API依赖的jar包和JDBC

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Login</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 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>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
    </dependencies>
</project>

2. 创建相应的目录

<!DOCTYPE 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 Application</display-name>
</web-app>

 3. 编写相应的前端代码(我们学习的是后端,这里我们复制即可)

<!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>

 4. 编写相应的后端代码

  • 登录页面提交账号密码所需要的后端代码(用于验证密码的正确性,以及给用户提示):
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

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 javax.sql.DataSource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Describe:
 * User:lenovo
 * Date:2023-07-01
 * Time:17:20
 */
//这个类用来实现登录时的校验

@WebServlet("/login")
public class LoginServletServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws 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;
        }
        //3.验证用户名和密码的正确性
        boolean ret = normal(username, password);
        if(ret) {
            System.out.println(ret);
            // 用户名和密码正确,我们创建相应的session,并存储
            HttpSession session = req.getSession(true);
            session.setAttribute("username", username);
            //4.登录成功后,自动跳转主页
            resp.sendRedirect("index");
        }else {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("用户名或密码错误");
        }
    }

    //验证账号密码是否正确
    private boolean normal(String username, String password) {
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("1020118096");

        try {
            Connection connection = dataSource.getConnection();
            String sql = "select password from login where username = ?";
            PreparedStatement statement = connection.prepareStatement(sql);
            statement.setString(1, username);
            ResultSet resultSet = statement.executeQuery();
            while(resultSet.next()) {
                String ret = resultSet.getString("password");
                if(ret.equals(password)) {
                    return true;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }


}
  • 用户输入密码后返回的结果:
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;

/**
 * Describe:
 * User:lenovo
 * Date:2023-07-02
 * Time:9:50
 */
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 此处禁止建立会话,如果没有找到,认为用户时未登录状态
        // 如果找到了才认为是登录状态
        HttpSession session = req.getSession(false);
        if(session == null) {
            System.out.println("Session");
            // 未登录状态
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前用户未登录!");
            return;
        }
        String username = (String)session.getAttribute("username");
        if(username == null) {
            System.out.println("userName");
            // 虽然有会话对象,但是里面没有必要的属性,也认为是登录异常状态
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前用户未登录!");
            return;
        }
        //上述检查都ok,接下来就直接生成一个动态页面
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("欢迎你!" + username);
    }
}

 

 5.打包和运行(略)

7.上传文件

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

7.1核心方法

HttpServletRequest类方法

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

Part类方法

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

7.2代码示例

实现代码,通过页面提交一个图片到服务器

1.创建相关目录和文件

 2.引入相应的依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Upload pictures</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>


    <dependencies>
        <!-- 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>
    </dependencies>

</project>

3.创建upload.html,放到webapp目录中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="upload" enctype="multipart/form-data" method="POST">
        <input type="file" name="MyImage">
        <input type="submit" value="提交图片">
    </form>
</body>

4.创建UploadServlet类

@MultipartConfig
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Part part = req.getPart("MyImage");
        System.out.println(part.getSubmittedFileName());
        System.out.println(part.getContentType());
        System.out.println(part.getSize());
        part.write("e:/MyImage.jpg");
        resp.getWriter().write("upload ok");
    }
}

5.使用插件进行运行。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值