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包
- 在Maven中央仓库中搜索 "servlet",一般第一个结果就是。
- 选择版本。我们一般使用3.1.0版本
Servlet的版本要和tomcat相匹配,我们使用的是Tomcat8.5,那么就需要使用Servlet 3.1.0。我们可以查看 http://tomcat.apache.org/whichversion.htm 查询版本之间的对应关系。 - 把中央仓库中提供的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 {@Overrideprotected 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部分。
【额外信息介绍】
- 我们的代码不是通过main方法作为入口的。main方法包含在Tomcat里,我们写的代码会被Tomcat在合适的时机调用
- 我们随便写个类都能被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 {@Overrideprotected 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方法。
@Overrideprotected 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 {@Overrideprotected 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());//获取URIret.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 {@Overrideprotected 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 {@Overrideprotected 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);//我们返回OKSystem.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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {resp.sendRedirect("http://www.sogou.com");}
}
5.代码示例:服务器版表白墙
5.1准备工作
- 创建maven项目
- 创建必要的目录: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. 构造新 divlet 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/messagesuccess: 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;@Overridepublic 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<>();//加载已经保存到数据库中的信息@Overrideprotected 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.提交一条信息
@Overrideprotected 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 {@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {//1.先从请求中拿到用户名和密码// 为了保证读出的参数也支持中文,我们要记得设置请求的编码方式时UTF8req.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 {@Overrideprotected 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 {@Overrideprotected 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.使用插件进行运行。