Servlet 是一种实现动态页面的技术, 是一组 Tomcat 提供给程序猿的 API,帮助我们简单高效的开发一个 web app
Servlet 主要做的工作:
怎么创建第一个 Servlet 程序: 参考 这篇文章 中的 Maven 部分
代码部分:
war 包和 jar 包的区别:
我们自己写的代码,就是通过继承这个类,重写其中的方法,来被 Tomcat 调用执行的
这个过程和 继承 有关,会涉及到Java中的一个核心语法 " 多态 "
HttpServlet 的实例只是在程序启动时创建一次, 而不是每次收到 HTTP 请求都重新创建实例
核心方法:
方法名称 | 调用时机 |
---|---|
init | 在 HttpServlet 实例化之后被调用一次 |
destroy | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut / doDelete / doOptions / … | 收到其他请求的时候调用(由 service 方法调用) |
doPost:
doGet 在前面创建的时候已经演示了,此处演示 doPost
构建 post 请求:
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>Documenttitle>
head>
<body>
<form action = "https://www.baidu.com/" method="get">
姓名:<input type = "text" name = "userName"/><br/>
密码:<input type = "password" name = "psw"/><br/>
<input type = "submit" value = "提交">
form>
body>
html>
可能会让选择使用哪个浏览器,电脑安装了哪个就选哪个
把 method 改为 post:
和之前的一样保存后开始运行
打开 Fiddler , 找到相应的地址
点击第三步,就能看见刚才输入的姓名和密码
ajax:就是通过 js 代码,来构造出 HTTP 请求,再通过 js 代码 来处理这里的响应,并且把得到的一些数据给更新到页面上
同步 和 异步 之间的区别:
主要就是看这个结果是 调用者 主动关注,还是 被调用者 来给 调用者 通知
阻塞 和 非阻塞 区别是:等的过程中,能不能做其他事情
- 阻塞:做不了其他的事情
- 非阻塞:可以做其他的事情
同步非阻塞等待 和 异步等待 的不同:
异步有结果通知,同步需要自己去查询结果
也就是说,同步非阻塞等待 对于调用者来说,开销更大,因为需要反复去查询结果
ajax 所使用的等待方式:异步等待
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>Documenttitle>
head>
<body>
<script src="jquery.js">script>
<script>
// 使用jquery的ajax
// $在js中可做变量名的一部分,这个$就是jquery中最核心的对象,jquery的各种api,都是通过$来触发的
// 通过$对象来调用ajax函数,它的参数只有一个,但是是一个“对象”
//浏览器禁止ajax进行跨域访问
// 让当前页面处在www.sogou.com中,页面在通过ajax请求url,域名为www.sogou.com这种就不算跨域
// 如何正确处理???
// 需要自己有一个服务器,让页面和ajax的地址都是这一个服务器就行了
$.ajax({
type: 'get',//http请求的方法
url: 'https://www.sogou.com/',//对应到HTTP请求的url
success: function(body){
// success对应一个回调函数,这个函数就会在正确到HTTP响应之后来调用
// 体现了异步
// 回调函数的参数,就是HTTP响应的body部分
console.log("获取到响应数据!" + body);
},
error: function(){
// error也对应一个回调函数
// 这个函数也会在请求失败之后触发
// 也体现了异步
console.log("获取响应失败! ");
},
});
script>
body>
html>
此时运行页面出现空白:
官网下载安装 postman
安装完成后进行操作:
HttpServletRequest 对应到一个 HTTP 请求
HTTP 请求中有什么,HttpServletRequest 里面就有什么
核心方法:
方法 | 描述 |
---|---|
String getProtocol() | 返回请求协议的名称和版本 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分 |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串 |
Enumeration getParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称 |
String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回null |
String[] getParameterValues(String name) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null |
Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有的头名 |
String getHeader(String name) | 以字符串形式返回指定的请求头的值 |
String getCharacterEncoding() | 返回请求主体中使用的字符编码的名称 |
String getContentType() | 返回请求主体的 MIME 类型,如果不知道类型则返回 null |
int getContentLength() | 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1 |
InputStream getInputStream() | 用于读取请求的 body 内容. 返回一个 InputStream 对象 |
案例:
@WebServlet("/showReq")
public class showRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
StringBuilder sb = new StringBuilder();
sb.append(" 首行部分
");
sb.append(req.getProtocol());//协议版本
sb.append("
");
sb.append(req.getMethod());//请求方法
sb.append("
");
sb.append(req.getRequestURI());//请求路径
sb.append("
");
sb.append(req.getContextPath());//第一级路径
sb.append("
");
sb.append(req.getQueryString());//路径后的请求 URL 中的查询字符串
sb.append("
");
sb.append("header 部分
");
Enumeration<String> headerNames = req.getHeaderNames();//报头中的 key 值
while(headerNames.hasMoreElements()){
String headerName = headerNames.nextElement();//key 值
String headerValue = req.getHeader(headerName);//val 值
sb.append(headerName+" : "+headerValue + "
");//组成键值对的形式
}
resp.setContentType("text/html;charset=utf-8");//防止乱码
resp.getWriter().write(sb.toString());
}
}
重新启动 Tomcat:
@WebServlet("/getPar")
public class getPar extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
String name = req.getParameter("userName");
String password = req.getParameter("password");
resp.getWriter().write("userName="+name+", password="+password);
}
}
重启 Tomcat:
后续可以借助这样的操作来通过 queryString 携带一些相关的参数交给服务器
POST 请求 的body 格式
- x-www-form-urlencoded
- form-data
- json
获取参数的方式 和 GET一样,也是 getParameter
如何在前端构造一个 x-www-form-urlencoded 格式的请求:
form表单:
输入的参数通过 post 的 body 部分把参数传递进去,并且由服务器借助 getParameter 方法来获取到将其拼接成字符串,写入响应返回给浏览器,再由浏览器将其显示出来
1.编写代码:
@WebServlet("/postGetPar")
public class postGetPar extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doPost(req, resp);
resp.setContentType("text/html;charset=utf-8");
String name = req.getParameter("userName");
String psw = req.getParameter("password");
resp.getWriter().write("name = "+name+" , password = " + psw);
}
}
在 java生态中,用来处理 json 的第三库的种类也很多,这里主要使用的库是 Jackson
- 在浏览器前端代码中,先通过 JS 构造出 body 为 json 格式的请求
- 在Java后端代码中,通过 Jackson 来进行处理
class User{
public int userName;//保证和 test.html 里面写的一致
public int password;
}
@WebServlet("/postJson")
public class postJson extends HttpServlet {
//1.创建 Jackson 核心对象
private ObjectMapper objM = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//2.读取 body 中的请求,使用 objM 解析成需要的对象
//readValue : 把 JSON 格式的字符串转换成 Java 对象
//第一个参数:表示对哪个字符串进行转换
//第二个参数:表示要把这个 JSON 格式的字符串转成哪个 Java 对象
User user = objM.readValue(req.getInputStream(),User.class);//按照数据流的方式读取请求,将其转换为 User 对象
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("userName = "+user.userName+" , password = "+ user.password);
}
}
重新启动 tomcat
HttpServletResponse 对应到 一个 HTTP 响应
HTTP 响应中有什么,这里就有什么
对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前,否则可能设置失效
核心方法:
方法 | 描述 |
---|---|
void setStatus(int sc) | 为该响应设置状态码 |
void setHeader(String name, String value) | 设置一个带有给定的名称和值的 header. 如果 name 已经存在,则覆盖旧的值 |
void addHeader(String name, String value) | 添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值,并列添加新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的内容类型 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8 |
void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据 |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据 |
实现一个程序, 用户在浏览器通过参数指定要返回响应的状态码
@WebServlet("/status")
public class status extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
resp.setStatus(200);//设置状态码
resp.getWriter().write("hello");
}
}
启动 Tomcat
可以看到:状态码不影响浏览器显示 body 内容
实现一个程序, 让浏览器每秒钟自动刷新一次,并显示当前的时间戳
@WebServlet("/autoR")
public class autoRefresh extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
resp.setHeader("Refresh","4");//每隔4秒刷新一次
resp.getWriter().write("timeStamp: " + System.currentTimeMillis());
}
}
启动 Tomcat:
实现一个程序,返回一个重定向 HTTP 响应, 自动跳转到另外一个页面
@WebServlet("/redirect")
public class redirect extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
//返回一个 302 重定向响应,让浏览器自动跳转到百度
resp.setStatus(302);
resp.setHeader("Location","https://www.baidu.com");
}
}
启动 Tomcat:
也可以直接用一行代码:
@WebServlet("/redirect")
public class redirect extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
//返回一个 302 重定向响应,让浏览器自动跳转到百度
/*resp.setStatus(302);
resp.setHeader("Location","https://www.baidu.com");*/
resp.sendRedirect("https://www.baidu.com");//直接跳转
}
}
表白墙
需要的前置代码:
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>
*{
margin:0;
padding:0;
box-sizing: border-box;
}
.container{
width:100%;
}
h3{
text-align: center;
padding:20px 0;
font-size: 20px;
}
p{
text-align: center;
color: rgb(240, 205, 157);
padding:10px 0;
}
.row{
width: 400px;
height:40px;
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
}
span{
width: 60px;
font-size: 15px;
}
.row input{
width:290px;
height:35px;
line-height: 30px;
font-size: 20px;
text-indent: 0.5em;
/* 去掉输入框的边框线 */
outline: none;
border-radius: 10px;
}
.row #submit{
width:350px;
height:30px;
font-size: 20px;
line-height: 30px;
margin: 0 auto;
color: white;
background-color: orange;
/* 去掉边框 */
border:none;
border-radius: 10px;
}
.row #submit:active{
background-color: grey;
}
style>
<div class="container">
<h3>表白墙h3>
<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>
<script>
//当用户点击提交就会和获取到input中的内容
let submitBtn = document.querySelector('#submit');
submitBtn.onclick = function(){
//获取到3个input的内容
let inputs = document.querySelectorAll('input');
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
//用户没填写完,暂时先不提交数据
return;
}
//生成一个新的div,内容就是input里的内容,把这个div加到页面中
let div = document.createElement('div');
console.log(div);
div.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
div.className = 'row';
let container = document.querySelector('.container');
container.appendChild(div);
//清空之前输入框的内容
for (let i = 0; i < inputs.length; i++) {
inputs[i].value = '';
}
}
script>
body>
html>
效果图:
对于这个表白墙来说,主要提供两个接口
- 告诉服务器,当前留言了一条什么样的数据
- 从服务器获取到:当前都有那些留言数据
- 约定客户端发送一个什么样的 HTTP 请求,服务器返回一个什么样的 HTTP 响应
- 页面加载的时候,需要从服务器获取到曾经存储的的消息内容
方法 | 作用 |
---|---|
objectMapper.readValue | 把 json 字符串转换为 Java 对象 |
objectMapper.writeValueAsString | 把 Java 对象转换为 json 字符串 |
JSON.parse | 把 json 字符串转换为 对象 |
JSON.stringify | 把 对象 转换为 json 字符串 |
class Message{
public String send;
public String receive;
public String msg;
}
@WebServlet("/message")
public class messageServlet extends HttpServlet {
private ObjectMapper objM = new ObjectMapper();//读取请求并解析
private List<Message> messages = new ArrayList<>();//存储数据
//处理获取消息列表请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取消息列表的元素,返回给客户端,使用 ObjectMapper 把Java对象转换成 json 格式字符串
String jsonString = objM.writeValueAsString(messages);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write(jsonString);
}
//处理提交消息的请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解析数据
Message message = objM.readValue(req.getInputStream(),Message.class);
//保存数据
messages.add(message);
//返回响应
resp.setContentType("application/json;charset=utf-8");//告知浏览器是 json 格式的数据
resp.getWriter().write("{ \"ok\": true}");
}
}
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js">script>
<script>
function getMessages(){
$.ajax({
type:"get",
url:"message",
success:function(body){
//当前的 body 已经是一个 js 对象数组
let container = document.querySelector('.container');
for(let message of body){//依次获取数组中的每个元素
let newDiv = document.createElement('div');//创建一个 div 标签存放记录
//提取元素中的关键信息,构成一个字符串
newDiv.innerHTML = message.send +" 对 "+ message.receive+" 说 " + message.msg;
//添加 css 样式
newDiv.className = 'row';
//将新建节点挂在 container 这个节点下面
container.appendChild(newDiv);
}
}
});
}
getMessages();//函数调用
//当用户点击提交就会和获取到input中的内容
let submitBtn = document.querySelector('#submit');
submitBtn.onclick = function(){
//获取到3个input的内容
let inputs = document.querySelectorAll('input');
let from = inputs[0].value;
let to = inputs[1].value;
let msg = inputs[2].value;
if (from == '' || to == '' || msg == '') {
//用户没填写完,暂时先不提交数据
return;
}
//生成一个新的div,内容就是input里的内容,把这个div加到页面中
let div = document.createElement('div');
console.log(div);
div.innerHTML = from + ' 对 ' + to + ' 说: ' + msg;
div.className = 'row';
let container = document.querySelector('.container');
container.appendChild(div);
//清空之前输入框的内容
for (let i = 0; i < inputs.length; i++) {
inputs[i].value = '';
}
//把当前获取到的输入框的内容构造成一个 http post 请求
let body = {
"send": from,
"receive": to,
"msg": msg
}
$.ajax({
type: "post",
url: "message",
contentType: "application/json;charset=utf-8",
data:JSON.stringify(body),
success:function(body){
alert("提交成功!");
},
error:function(){
alert("提交失败!");
}
});
}
script>
对于上一个实现,服务器重启数据就会丢失,此时可以利用 MySQL 解决
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;
class MessageM{
public String send;
public String receive;
public String msg;
}
@WebServlet("/messageM")
public class messageMysql extends HttpServlet {
private ObjectMapper objM = new ObjectMapper();//读取请求并解析
//处理获取消息列表请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<MessageM> messages = load();
//获取消息列表的元素,返回给客户端,使用 ObjectMapper 把Java对象转换成 json 格式字符串
String jsonString = objM.writeValueAsString(messages);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write(jsonString);
}
//处理提交消息的请求
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//解析数据
MessageM messageM = objM.readValue(req.getInputStream(),MessageM.class);
//保存数据
save(messageM);
//返回响应,告知页面返回 json 格式的数据
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write("{ \"ok\": true}");
}
//将数据保存到数据库中
private void save(MessageM messageM){
Connection connection = null;
PreparedStatement statement = null;
try{
connection = dataB.getConnection();
//构造 sql 语句
String sql = "insert into messageMysql values(?,?,?)";
statement = connection.prepareStatement(sql);
statement.setString(1,messageM.send);
statement.setString(2,messageM.receive);
statement.setString(3,messageM.msg);
//执行 sql
statement.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dataB.close(connection,statement,null);
}
}
//从数据库获取数据
private List<MessageM> load(){
List<MessageM> messageMS = new ArrayList<>();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try{
connection = dataB.getConnection();
//构造 sql 语句
String sql = "select * from messageMysql";
statement = connection.prepareStatement(sql);
//执行 sql
resultSet = statement.executeQuery();
//遍历结果集
while(resultSet.next()){
MessageM messageM = new MessageM();
messageM.send = resultSet.getString("send");
messageM.receive = resultSet.getString("receive");
messageM.msg = resultSet.getString("msg");
messageMS.add(messageM);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
dataB.close(connection,statement,resultSet);
}
return messageMS;
}
}
新建了 dataB
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 dataB {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/demo01?characterEncoding=utf-8&useSSL=false";
private static final String User = "root";
private static final String Password = "******";//自己数据库密码
//懒汉模式 , 防止编译器优化
private static volatile DataSource dataSource = null;
private static DataSource getDataSource(){
if(dataSource == null){
synchronized (dataB.class){
if(dataSource == null){
//创建数据源
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setURL(URL);
((MysqlDataSource)dataSource).setUser(User);
((MysqlDataSource)dataSource).setPassword(Password);
}
}
}
return dataSource;
}
//与数据库建立连接,可能会在多线程下调用
public static Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
//释放资源
public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet){
if(resultSet != null){
try{
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(statement != null){
try{
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(connection != null){
try{
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
因为新建了代码,所以 html 文件中的路径要更换