目錄
前言:
1.Servlet?API的使用
1.1HttpServlet
1.2HttpServletRequest
1.3HttpServletResponse
2.表白墻的更新
2.1表白墻存在的問題
2.2前后端交互接口
2.3環(huán)境準備
2.4代碼的編寫
2.5數(shù)據(jù)的持久化
2.5.1引入JDBC依賴
2.5.2創(chuàng)建數(shù)據(jù)庫
2.5.3編寫數(shù)據(jù)庫代碼
3.Cookie回憶
4.Session機制
4.1getSession
4.2模擬實現(xiàn)登錄功能
4.2.1登錄頁的核心代碼。
4.2.2編寫登錄頁面的Servlet
4.2.3編寫生成主頁的Servlet
4.2.4起啟動瀏覽器
結束語:
前言:
上一節(jié)中小編主要與大家分享了有關于Tomcat和Servlet的基礎的一些知識,接下來小編將會給大家介紹一下有關于Servlet的API以及對上一次編寫的表白墻案例的更新。話不多說一起來學起來吧!
1.Servlet?API的使用
Servlet中的API有很多這里我們只需要掌握里面的三個即可。接下來我們來具體看一下。
1.1HttpServlet
我們在寫Servlet代碼的時候,首先第一步就是先創(chuàng)建類,繼承自HttpServlet,并重寫其中的某些方法。
核心方法:
方法名稱 | 調用時機 |
init | 在HttpServlet實例化之后被調用一次 |
destory | 在HttpServlet實例不再使用的時候會調用一次 |
service | 收到HTTP請求的時候調用 |
doGet | 收到GET請求的時候調用(由service方法調用) |
doPost | 收到POST請求的時候調用(由service方法調用) |
doPut/doDelete/doOptions | 收到其他請求的時候調用(由service方法調用) |
Servlet的生命周期:
- init:是在初識情況下調用一次。
- destory:是在結束之前調用一次。
- service:每次收到路徑匹配的請求都要調用一次。
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("這是一個doGet方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("這是一個doPost方法");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("這是一個doPut");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("這是一個doDelete方法");
}
}
結果展示:
1.2HttpServletRequest
它是一個HTTP請求,當Tomcat通過Socket?API讀取到HTTP請求之后就會解析生成上述的HttpServletRequest對象。這里就是HTTP請求報文里有啥,這里類里面就會有啥。
核心方法:
方法 |
描述 |
String getProtocol() |
返回請求協(xié)議的名稱和版本。 |
String getMethod() | 返回請求的 HTTP 方法的名稱,例如,GET、POST 或 PUT。 |
String getRequestURI() | 從協(xié)議名稱直到 HTTP 請求的第一行的查詢字符串中,返回該請 求的 URL 的一部分。 |
String getContextPath() | 返回指示請求上下文的請求 URI 部分。 |
String getQueryString() | 返回包含在路徑后的請求 URL 中的查詢字符串。 |
Enumeration getParameterNames() |
返回一個 String 對象的枚舉,包含在該請求中包含的參數(shù)的名 稱。 |
String getParameter(String name) |
以字符串形式返回請求參數(shù)的值,或者如果參數(shù)不存在則返回 null。 |
String[] getParameterValues(String name) |
返回一個字符串對象的數(shù)組,包含所有給定的請求參數(shù)的值,如 果參數(shù)不存在則返回 null。 |
Enumeration getHeaderNames() |
返回一個枚舉,包含在該請求中包含的所有的頭名。 |
String getHeader(String name) |
以字符串形式返回指定的請求頭的值。 |
String getCharacterEncoding() |
返回請求主體中使用的字符編碼的名稱。 |
String getContentType() | 返回請求主體的 MIME 類型,如果不知道類型則返回 null。 |
int getContentLength() | 以字節(jié)為單位返回請求主體的長度,并提供輸入流,或者如果長 度未知則返回 -1。 |
InputStream getInputStream() |
用于讀取請求的 body 內容. 返回一個 InputStream 對象。 |
注意:上述中有一個是URI,注意不是URL,URL是唯資源定位符,而URI是唯一資源標識符,這兩概念非常相似,以至于很多時候我們都是直接混著使用了。
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
StringBuilder result = new StringBuilder();
//返回協(xié)議請求的名稱和版本
result.append(req.getProtocol());
result.append("<br>");
//返回請求的HTTP方法的名稱,例如GET、POST或PUT
result.append(req.getMethod());
result.append("<br>");
//從協(xié)議名稱直到HTTP請求的第一行的查詢字符中,返回該請求的URL的一部分。
result.append(req.getRequestURI());
result.append("<br>");
//返回包含在路徑后的請求URL中的查詢字符串
result.append(req.getQueryString());
result.append("<br>");
//返回請求指示上下文的請求URI部分
result.append(req.getContextPath());
result.append("<br>");
result.append("========================<br>");
//返回一個枚舉類,包含在該請求中所有的頭名
Enumeration<String> headerNames = req.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = req.getHeader(headerName);
result.append(headerName + ":" + headerValue + "<br>");
}
//在響應中設置上body的類型,方便瀏覽器進行解析
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write(result.toString());
}
}
結果展示:
其中重點給大家講解一下getParameter,它是最常用的API之一,在開發(fā)中前端給后端傳遞數(shù)據(jù)的時候是非常常見的需求。在傳遞中主要有以下三種傳遞方式:
- 通過query?string傳遞
- 通過body(form)
- 通過body(json)
下面我們來具體看一下。
①通過query?string來傳遞
此時我們約定前端是通過query?string來傳遞username和password的。
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//前端通過url的query傳遞username和password兩個屬性
String username = req.getParameter("username");
if (username == null) {
System.out.println("username這個key在query string中不存在");
}
String password = req.getParameter("password");
if (username == null) {
System.out.println("password這個key在query string中不存在");
}
System.out.println("username=" + username + ", password=" + password);
resp.getWriter().write("ok");
}
}
結果展示:
在服務器上輸入一下請求之后得到的結果是:
??????http://127.0.0.1:8080/hello_servlet/getParameter??????
??
?
如果輸入的是下面的請求之后得到的結果是:
http://127.0.0.1:8080/hello_servlet/getParameter?username=zhangsan&password=123
?
?
注意:query?string中的鍵值對都是程序猿來自定義的。所以這樣就可以根據(jù)需求來定義了。
②通過body(form)
相當于body里存檔數(shù)據(jù)格式和query?string一樣,但是Content-Type是application/x-www-form-urlencoed,此時也是通過getParameter來獲取到鍵值對的。這里我們是借助與postman來構造一個請求的。如下所示:
?
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//前端通過body,以form表單的格式,把username和password傳遞給服務器
String username = req.getParameter("username");
if (username == null) {
System.out.println("username這個key在body中不存在");
}
String password = req.getParameter("password");
if (username == null) {
System.out.println("password這個key在body中不存在");
}
System.out.println("username=" + username + ", password=" + password);
resp.getWriter().write("ok");
}
}
結果展示:
?
??
但是如果我們這里輸入的是中文的張三會有啥問題呢?
在url中query?string如果是包含中文或者是特殊字符,那么請務必使用urlencode(?在線URL編碼urlencode工具 - UU在線工具)來進行轉碼,如果是直接寫中文會存在很大的風險。如果不轉碼,在有些瀏覽器中/http服務器下對中文支持不好的話,就會出現(xiàn)問題。
轉碼如下所示:
??
?那么這里我們就直接將轉碼之后的直接放在url的構造中,如下所示:
http://127.0.0.1:8080/hello_servlet/getParameter?username=%E5%BC%A0%E4%B8%89&password=123
這樣在結果中他會自動轉回來。?
?
但是如果我們是在postman中使用中文就會出現(xiàn)如下情況。
?
?
一般我們在使用postman的時候是utf8的,所以此時我們就需要顯示的告訴后端代碼請求使用的是utf編碼。
?
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//給請求設置utf8
req.setCharacterEncoding("utf8");
//前端通過body,以form表單的格式,把username和password傳遞給服務器
String username = req.getParameter("username");
if (username == null) {
System.out.println("username這個key在body中不存在");
}
String password = req.getParameter("password");
if (username == null) {
System.out.println("password這個key在body中不存在");
}
System.out.println("username=" + username + ", password=" + password);
resp.getWriter().write("ok");
}
}
結果展示:
?
設置之后就解決了亂碼的問題。
③通過body(json)
通過body(json)是最常見的方式,這里需要我們重點掌握。json也是一個鍵值對的格式,但是Servlet自身沒有內置json的解析功能,所以這里我們就需要借助其他的第三方庫了。這里我們使用的是jackson。我們直接從maven中央倉庫來下載。具體步驟看下面。
①?搜索jackson選擇第一個。
?
②然后隨便選擇一個版本。?
?
③直接復制maven到dependencies中。?
?④點擊刷新maven。?
?
這里我們使用postman來構造請求。
?
代碼展示:
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
class User {
public String username;
public String password;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
//使用Jackson,最核心的對象就是ObjectMapper
//通過這個對象,就可以把json字符串解析成java對象,也可以把一個java對象 轉化成一個json格式的字符串
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//通過post請求的body傳遞過來的一個json格式的字符串
//第一個參數(shù)是表示對誰進行解析
//第二個參數(shù)表示要將傳入的參數(shù)解析成什么樣的java對象。
User user = objectMapper.readValue(req.getInputStream(),User.class);
System.out.println("username= " + user.username + ",password =" + user.password);
resp.getWriter().write("OK");
}
}
結果展示:?
解析readValue里面做的事情:
①解析json字符串,轉換成若干個鍵值對。
②根據(jù)第二個參數(shù)User.class去找到User里面的的所有的public的屬性或者是有public的getter和setter的屬性依次進行遍歷。
③遍歷屬性,根據(jù)屬性的名字,去上述準備好的鍵值對里查詢看看這個屬性的名字是否存在對應的value里,如果存在就把value賦值到該屬性中。?
1.3HttpServletResponse
Servlet中的doXXX方法的目的就是根據(jù)請求計算得到響應,然后把響應的數(shù)據(jù)設置到HttpServletResponse對象中,然后Tomcat就會把這個HttpServletResponse對象按照HTTP協(xié)議的格式,轉換成一個字符串,并通過Socket寫回給瀏覽器。
核心方法:
方法 |
描述 |
void setStatus(int sc) |
為該響應設置狀態(tài)碼。 |
void setHeader(String name, String value) |
設置一個帶有給定的名稱和值的 header. 如果 name 已經(jīng)存在, 則覆蓋舊的值. |
void addHeader(String name, String value) |
添加一個帶有給定的名稱和值的 header. 如果 name 已經(jīng)存在, 不覆蓋舊的值, 并列添加新的鍵值對 |
void setContentType(String type) |
設置被發(fā)送到客戶端的響應的內容類型。 |
void setCharacterEncoding(String charset) |
設置被發(fā)送到客戶端的響應的字符編碼(MIME 字符集)例如, UTF-8。 |
void sendRedirect(String location) |
使用指定的重定向位置 URL 發(fā)送臨時重定向響應到客戶端。 |
PrintWriter getWriter() |
用于往 body 中寫入文本格式數(shù)據(jù). |
OutputStream getOutputStream() |
用于往 body 中寫入二進制格式數(shù)據(jù). |
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/status")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//為該響應設置狀態(tài)碼
resp.setStatus(404);
//設置被發(fā)送到客戶端的響應的內容類型
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("返回404響應!");
}
}
結果展示:
?
自動重定向:
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//用戶訪問這個路徑的時候,會自動重定向到搜狗主頁
// resp.setStatus(302);
// //設置一個帶有給定的名稱和值的header,如果name已經(jīng)存在,則覆蓋舊的值。
// resp.setHeader("Location","https://www.soguo.com");
//使用指定的重定向位置url發(fā)送臨時重定向響應到客戶端
resp.sendRedirect("https://www.sogou.com/");
}
}
結果展示:
頁面將自動跳轉到搜狗的頁面。
通過header來實現(xiàn)自動刷新的效果。
代碼展示:
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設置為每隔1s刷新一次
resp.setHeader("Refresh","1");
resp.getWriter().write("time=" + System.currentTimeMillis());
}
}
結果展示:
?
2.表白墻的更新
之前寫的前端表白墻的博客鏈接?http://t.csdn.cn/NdwmC
2.1表白墻存在的問題
我們之前只是實現(xiàn)了表白墻的前端,它存在最大的問題就是1.頁面重啟之后數(shù)據(jù)會丟失,2.數(shù)據(jù)只是在本地,別人是看不到的。
那么針對上述的兩個問題我們只需要引入后端服務器即可,當用戶打開頁面的時候,就需要從服務器加載當前已經(jīng)提交過的數(shù)據(jù),當用戶新增數(shù)據(jù)到時候也會把數(shù)據(jù)交給服務器,讓服務器持久化保存。
2.2前后端交互接口
這里當我在寫后端代碼的時候我們先來明確前后端的交互接口。
- 頁面加載完畢之后,需要給服務器發(fā)一個請求獲取到當前的留言數(shù)據(jù)。
- 用戶點擊提交的時候就需要告訴服務器,當前用戶發(fā)了的消息是啥。
?這在交互的過程中涉及到了一個重要的問題,請求和響應具體是啥樣子的,這里都需要我們來做出一個約定。
?
注意:json中使用[]表示數(shù)組,[]中的每一個元素,是一個{}json對象,每個對象里,又有三個屬性from,to,message。
?
對于接口二服務器要做的事情就是解析請求中的body,轉成message對象,然后把這個message對象給保存起來。
2.3環(huán)境準備
①創(chuàng)建maven項目,然后處理處理靜態(tài)頁面,將靜態(tài)頁面導入到項目中。
靜態(tài)頁面內的代碼可以參考小編之前的一篇博客里面的表白墻的代碼??
JavaScript(JavaEE初階系列13)_奶油酒窩??(ˊωˋ*)??的博客-CSDN博客
2.4代碼的編寫
我們想要達到的效果是要在頁面下面在加上留言的數(shù)據(jù)。我們可以直接在前端頁面上進行修改然后再在VScode中進行修改。
①選中父元素。
?
②創(chuàng)建一個新的div標簽。
③將新的標簽賦值給rowDiv。
?④加入數(shù)據(jù)。
⑤給rowDiv起一個class屬性名。
?
⑥將新加的div標簽掛到父標簽下面。
?
這樣我們就可以在下面加上數(shù)據(jù)了。那么接下來我們就需要在前端代碼中按照上述的步驟進行改進。
代碼展示:
前端代碼的展示:
<!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>
// 實現(xiàn)提交操作. 點擊提交按鈕, 就能夠把用戶輸入的內容提交到頁面上顯示.
// 點擊的時候, 獲取到三個輸入框中的文本內容
// 創(chuàng)建一個新的 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("消息發(fā)送給服務器成功!");
}
});
}
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]);
}
// 在頁面加載的時候, 希望能夠從服務器獲取到所有的消息, 并顯示在網(wǎng)頁中.
$.ajax({
type: 'get',
url: 'message', // url 都是使用相對路徑的寫法. 相對路徑意味著工作路徑就是當前文件所在的路徑.
// 當前文件所在路徑是 /message_wall/ , 因此此時構造的請求就是 /message_wall/message
success: function(body) {
// body 是收到的響應的正文部分. 如我們之前的約定, body 應該是 json 數(shù)組
// 由于響應的 Content-Type 是 application/json, 此時收到的 body 會被 jquery 自動的把它從 字符串
// 轉成 js 對象數(shù)組. 此處就不需要手動的進行 JSON.parse 了.
// 此處的 body 已經(jīng)是一個 JSON.parse 之后得到的 js 對象數(shù)組了.
// 就需要遍歷這個 body 數(shù)組, 取出每個元素, 再依據(jù)這樣的元素構造出 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>
后端代碼的改進:
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;
@Override
public String toString() {
return "Message{" +
"from='" + from + '\'' +
", to='" + to + '\'' +
", message='" + message + '\'' +
'}';
}
}
@WebServlet("/message")
public class MessageServlet extends HttpServlet {
private ObjectMapper objectMapper = new ObjectMapper();
private List<Message> messageList = new ArrayList<>();
//通過這個方法來"獲取所有留言消息"
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//需要返回一個json字符串數(shù)組,jackson直接可以幫助我們來進行轉換
String respString = objectMapper.writeValueAsString(messageList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respString);
}
//通過這個方法來實現(xiàn)"提交新消息"
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
messageList.add(message);
System.out.println("消息提交成功!message=" + message);
//響應只是返回200報文,body為空,此時不需要額外處理,默認就是返回200
}
}
結果展示:
2.5數(shù)據(jù)的持久化
上述我們只是實現(xiàn)了在前端頁面中在頁面重啟的時候數(shù)據(jù)的持久化以及可以讓別人也訪問到我們的頁面,但是如果我們重啟服務器的話數(shù)據(jù)還在嗎?答案是不在了,我們的服務器保存的數(shù)據(jù)是在一個ArrayList中(內存)所以只要進程重啟,內存數(shù)據(jù)可定就沒有了,那么我們該怎么將數(shù)據(jù)進行持久化呢?下面有兩種方法。
- 寫到文件中。
- 寫到數(shù)據(jù)庫中。
但是我們一般是將數(shù)據(jù)寫到數(shù)據(jù)庫中來進行數(shù)據(jù)的持久化。所以接下來我們就使用數(shù)據(jù)庫來進行數(shù)據(jù)的持久化操作吧。
2.5.1引入JDBC依賴
在maven中找到下面的maven直接引入到pom.xml中。
??
?
2.5.2創(chuàng)建數(shù)據(jù)庫
?
?
2.5.3編寫數(shù)據(jù)庫代碼
四步走:
- DataSource
- Connection
- PreparedStatement
- ResultSet
代碼展示:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.management.loading.MLet;
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 ObjectMapper objectMapper = new ObjectMapper();
// private List<Message> messageList = new ArrayList<>();
//通過這個方法來"獲取所有留言消息"
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//需要返回一個json字符串數(shù)組,jackson直接可以幫助我們來進行轉換
List<Message> messageList = load();
String respString = objectMapper.writeValueAsString(messageList);
resp.setContentType("application/json; charset=utf8");
resp.getWriter().write(respString);
}
//通過這個方法來實現(xiàn)"提交新消息"
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Message message = objectMapper.readValue(req.getInputStream(),Message.class);
save(message);
System.out.println("消息提交成功!message=" + message);
//響應只是返回200報文,body為空,此時不需要額外處理,默認就是返回200
}
//這個方法用來往數(shù)據(jù)庫中存儲一條記錄
private void save(Message message) {
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123456");
try {
Connection connection = dataSource.getConnection();
String sql = "insert into 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();
}
}
//這個方法用來從數(shù)據(jù)庫中查詢所有記錄
private List<Message> load() {
//用來承裝查詢到的數(shù)據(jù)
List<Message> messageList = new ArrayList<>();
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
((MysqlDataSource)dataSource).setUser("root");
((MysqlDataSource)dataSource).setPassword("123456");
try {
Connection connection = dataSource.getConnection();
String sql = "select * from message";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery();
//按照格式進行解析,然后將取到的數(shù)據(jù)放入到message中
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;
}
}
結果展示:
?
?
這樣就實現(xiàn)了將數(shù)據(jù)持久化。即使你重新啟動服務器或者是重開機數(shù)據(jù)也都會存在。
3.Cookie回憶
Cookie是瀏覽器在本地存儲數(shù)據(jù)的一種機制。
①Cookie從哪里來:Cookie是從服務器來的,服務器在響應中會帶有Set-Cookie字段,通過這個字段就可以把要保存在瀏覽器本地的數(shù)據(jù)給返回回去。
②Cookie到哪里去:后續(xù)瀏覽器在訪問服務器的時候,就會把本地所有Cookie都通過http請求給帶過去。
③Cookie的作用:其中一種典型的應用就是使用Cookie保存當前用戶登錄狀態(tài)。
?在Cookie保存用戶身份標,這樣的應用場景中,此時身份標識如何分配以及身份信息如何存儲,都是需要服務器的支持。其中Session就是服務器這邊用來實現(xiàn)用戶身份區(qū)分的一種機制。它通常是和Cookie配合使用的,它是給當前用戶先分配一個SessionId同時記錄下當前用戶的一些身份信息,這樣sessionId就會被返回到瀏覽器的cookie中后續(xù)瀏覽器訪問服務器都會帶著這個sessionId從而讓服務器識別當前用戶身份。
4.Session機制
4.1getSession
通過上述的講解相信大家對session有了一定的了解,接下來我們就來具體看一下在Servlet中針對cookie和session提供了哪些支持!
HttpServletRequest類中相關的方法:
方法 | 描述 |
HttpSession?getSession() | 在服務器中獲取會話,參數(shù)如果為true,則當不存在會話時創(chuàng)建會話,參數(shù)如果為false,則當不存在會話時返回null。 |
Cookie[]?getCookies() | 返回一個數(shù)組,包含客戶端發(fā)送請求的所有Cookie對象,會自動把Cookie中的格式解析成鍵值對。 |
getSession()有一個參數(shù)是boolean,如果是false,getSession的行為就是:
- 讀取請求中Cookie里sessionId
- 在服務器這邊根據(jù)sessionId來查詢對應session對象。
- 如果查到了,就會直接返回這個session對象,如果沒有查到,返回null。
如果參數(shù)是true,getSession的行為是:
- 讀取請求中Cookie里sessionId
- 在服務器這邊根據(jù)sessionId來查詢對應session對象。
- 如果查到了,就會直接返回這個session對象。
- 如果沒有查到就會創(chuàng)建一個Session對象,同時說生成一個sessionId,以sessionId為key,Session對象為value,把這個鍵值對存儲到服務器里的一個hash表中,同時把sessionId以Set-Cooki的方式返回給瀏覽器。
下面我們通過模擬實現(xiàn)登錄功能來實踐一下。
4.2模擬實現(xiàn)登錄功能
首先我們提供兩個頁面:
- 登錄頁(包含兩個輸入框,輸入用戶密碼,還有一個登錄按鈕)點擊登錄按鈕,就會發(fā)起一個http請求。當服務器處理這個請求的時候就會驗證用戶名和密碼。如果用戶名密碼OK,就會跳轉到主頁。
- 主頁,這里只是單純的顯示當前用戶的用戶名(歡迎XXX)。
4.2.1登錄頁的核心代碼。
代碼展示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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>
結果展示:
其中form就會組織這里的數(shù)據(jù)以鍵值對的形式提交給服務器,其中key就是input中的name屬性,value就是input用戶輸入的內容,最終會構成post請求,在body里以鍵值對(類似于query?string)的格式進行組織。
4.2.2編寫登錄頁面的Servlet
登錄的請求如下所示:
一般像登錄頁面這樣的請求都是POST比較多,這是屬于習慣的用法。
?
代碼展示:
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 {
//1.先從請求中拿到用戶名和密碼
//為了保證讀出來的參數(shù)也能支持中文,要記得設置請求的編碼方式是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;
}
//此處假定用戶名只能是zhangsan或者是lisi,密碼都是123
//正常的登錄邏輯,驗證用戶名密碼都是從數(shù)據(jù)庫讀取的。
if (!username.equals("zhangsan") && !username.equals("lisi")) {
//用戶名有問題
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("用戶名或密碼有誤!");
return;
}
if (!password.equals("123")) {
//密碼有問題
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("用戶名或密碼有誤!");
return;
}
//3.用戶名和密碼驗證ok,接下來就創(chuàng)建一個會話。
//當前用戶處于未登錄狀態(tài),此時請求的cookie中沒有sessionId
//此處的getSession是無法從服務器的哈希表中找到該session對象的。
//由于此處把參數(shù)設置為true了,所以就允許getSession在查詢不到的時候,創(chuàng)建新的session對象和sessionId
//并且會自動把這個sessionId和session對象存儲在哈希表中。
//同時返回這個session對象,并且在接下來的響應中會自動把這個sessionId返回給客戶端瀏覽器。
HttpSession session = req.getSession(true);
//接下來可以讓剛剛創(chuàng)建好的session對象存儲咱們定義的數(shù)據(jù)。就可以在這個對象中存儲用戶的身份信息。
session.setAttribute("username",username);
//4.登錄成功之后,自動跳轉到主頁
resp.sendRedirect("index");
}
}
4.2.3編寫生成主頁的Servlet
?
代碼展示:
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;
//這個Servlet用來動態(tài)生成主頁面
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//此處禁止創(chuàng)建會話,如果沒有找到,認為用戶是未登錄的狀態(tài)
//如果找到了才認為是登錄狀態(tài)。
HttpSession session = req.getSession(false);
if (session == null) {
//未登錄狀態(tài)
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("當前用戶為登錄!");
return;
}
String username = (String) session.getAttribute("username");
if (username == null) {
//雖然有會話對象,但是里面沒有必要的屬性,也認為是登錄狀態(tài)異常
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("當前用戶未登錄!");
return;
}
//如果上述檢查都OK,接下來就直接生產(chǎn)一個動態(tài)頁面
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("歡迎你!" + username);
}
}
4.2.4起啟動瀏覽器
啟動瀏覽器之后我們就可以看到下面的內容了。
?
同樣我們也可以借助fiddler來查看發(fā)送的內容。
上述的sessionId也不會一致存儲下去,比如服務器重新啟動原來的hash表中內容就會消失不見,此時再次訪問的時候就可能會出現(xiàn)sessionId無法查詢到,于是就會被識別成未登錄狀態(tài)。但是這里我們使用了Smart Tomcat,他為了方便程序猿調試程序,會在停止的服務器的時候,會把會話持久化保存,并且在下次啟動的時候,自動那個把會話恢復到內存中,這個時候會話時不會丟失的。上述這一套是否生效,這取決于你使用的Smart?Tomcat的版本。
結束語:
好了這節(jié)小編就給大分享到這里啦,希望這節(jié)對大家學習JavaEE初階有一定幫助,想要學習的同學記得關注小編和小編一起學習吧!如果文章中有任何錯誤也歡迎各位大佬及時為小編指點迷津(在此小編先謝過各位大佬啦?。?mark hidden color="red">文章來源:http://www.zghlxwxcb.cn/news/detail-683384.html
?文章來源地址http://www.zghlxwxcb.cn/news/detail-683384.html
到了這里,關于Servlet的使用(JavaEE初階系列17)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!