B/S結構系統的會話機制(session)
- B/S結構系統的會話機制(session)
- 每博一文案
- 1. session 會話機制的概述
- 2. 什么是 session 的會話
- 3. session 的作用
- 4. session 的實作原理解釋
- 5. 補充: Cookie禁用了,session還能找到嗎 ?
- 6. 總結一下到目前位置我們所了解的域物件:
- 7. oa 專案的優化體驗:使用上 session 會話機制:
- 8. 總結:
- 9. 最后:
每博一文案
你跑得快,22歲有個家,身邊全是贊嘆,你跑得慢,30歲還在路上追求夢想,有的人為了車,房拼了一輩子,
有的人買輛摩托車走遍了大好江山,你想成為怎樣的人,過怎樣的生活,只要你不后悔就行,
并不是所有人都能在早上七點鐘起床的,也別拿一碗飯來衡量一個人的胃口的大小,
有的人喜歡狼吞虎咽,有的人喜歡細嚼慢咽,允許別人做,別人允許自己做自己,
一歲有一歲的味道,跟著自己的心就好,不是所有選擇都要做正確的選項的,只要你想,你可以選擇
你喜歡的選項,沿途的花會一直開,以后的路也是,祝你祝我,
1. session 會話機制的概述
在Web應用程式中,我們經常要跟蹤用戶身份,當一個用戶登錄成功后,如果他繼續訪問其他頁面,Web程式如何才能識別出該用戶身份?
因為HTTP協議是一個無狀態協議,即Web應用程式無法區分收到的兩個HTTP請求是否是同一個瀏覽器發出的,為了跟蹤用戶狀態,服務器可以向瀏覽器分配一個唯一ID,并以Cookie的形式發送到瀏覽器,瀏覽器在后續訪問時總是附帶此Cookie,這樣,服務器就可以識別用戶身份,
我們把這種基于唯一ID識別用戶身份的機制稱為Session,每個用戶第一次訪問服務器后,會自動獲得一個Session ID,如果用戶在一段時間內沒有訪問服務器,那么Session會自動失效,下次即使帶著上次分配的Session ID訪問,服務器也認為這是一個新用戶,會分配新的Session ID,
2. 什么是 session 的會話
會話對應的英語單詞:session
當用戶打開瀏覽器,進行一系列操作,然后最終將瀏覽器關閉,這個整個程序叫做:一次會話,會話在服務器端也有一個對應的java物件,這個java物件叫做:session,
什么是一次請求:用戶在瀏覽器上點擊了一下,然后到頁面停下來,可以粗略認為是一次請求,請求對應的服務器端的java物件是:request, 這里提前透露一點后面的內容: session 物件是用服務器端生成的,所以這里是通過 request 請求的方式向服務器獲取到一個 session 會話物件
- 一個會話當中包含多次請求(一次會話對應N次請求,)
這里我們可以列印顯示我們的 session 地址資訊
package com.RainbowSea.session;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/session")
public class TestSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
HttpSession session = request.getSession();
response.setContentType("text/html;charSet=UTF-8");
PrintWriter out = response.getWriter();
out.println(" session物件: " + session);
}
}
從 session 物件當中存在于: org.apache.catalina.session.StandardSession 的位置,
在Java的servlet 的規范當中,session 對應的類名為: HttpSession(jarkata.servlett.http.HttpSession) ,
注意:
sessioin 機制屬于 B/S結構的一部分,如果使用php語言開發WEB專案,同樣也是有session這種機制的,session機制實際上是一個規范,然后不同的語言對這種會話機制都有實作,
獲取 sessoin 的物件方法:
// 注意: sessio 是存盤在服務器端的,所以我們這里使用的是 request 請求的方式,向服務器請求獲取到 session 物件
// 該訪問獲取到 session 物件,如果服務器端沒有 session 物件會自動創建出 session 物件
HttpSession session = request.getSession();
// 獲取到 session 物件,(引數為 false )表示:如果服務器當中沒有 session 是不會自動創建的,
HttpSession session1 = request.getSession(false);
3. session 的作用
session物件最主要的作用是:保存會話狀態,(用戶登錄成功了,這是一種登錄成功的狀態,你怎么把登錄成功的狀態一直保存下來呢?使用session物件可以保留會話狀態,)
那我們為什么需要session 物件來保存會話狀態呢?
因為HTTP協議是一種無狀態協議,
什么是無狀態:請求的時候,B和S是連接的,但是請求結束之后,連接就斷了,為什么要這么做?HTTP協議為什么要設計成這樣?因為這樣的無狀態協議,可以降低服務器的壓力,請求的瞬間是連接的,請求結束之后,連接斷開,這樣服務器壓力小,
只要B和S斷開了,那么關閉瀏覽器這個動作,服務器知道嗎?
因為 HTTP 協議是無狀態的連接的,所以當我們關閉了 瀏覽器的時候,我們的服務器端是無法接收到瀏覽器被關閉的一個資訊的,所以:我們的服務器自然也就無法知道瀏覽器關閉了,
一個會話對應一個 sessoin 物件,一個 session 對應上一個 ID也就是 (JSESSIONID) ,
比如:張三打開一個瀏覽器 A,李四打開一個瀏覽器B,訪問服務器之后,在服務端會生成:
- 張三專屬的session物件,同時會標記上一個 對應的 ID 資訊
- 李四專屬的session物件 ,同時會標記上一個對應的 ID 資訊,
- 注意了:這兩者之間的 ID資訊是不一樣的,
代碼舉例:
package com.RainbowSea.serssion;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/test/session")
public class TestSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// request 和 session 都是在服務端的java物件,都在JVM當中
// request物件代表一次請求,(一次請求對應一個request物件,再次請求就會對應兩個不同的request物件)
// session物件代表一次會話,(一次會話對應一個session 物件)
// 獲取session,如何服務器當中沒有 session 物件就會自動創建一個,
HttpSession session = request.getSession();
// 獲取到服務器端的 session ,如果沒有不會自動創建 session 物件
//HttpSession session1 = request.getSession(false);
//session.setAttribute(); 將資料存盤到 session 會話當中,
//session.getAttribute() 將資料從 session 會話當中取出
// 將session 物件回應到瀏覽器端
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("會話物件:" + session);
}
}
結果:
什么表示一個會話
粗略的可以認為一個:當我們打開一個瀏覽器訪問一個A服務器的時候,如果這個瀏覽器不關閉的情況下,該瀏覽器發送的請求都是向 A服務器,那么哪個瀏覽器發送對于這個A服務器發送的所有的請求都可以理解為是一個 session 會話,
我們也可以再精細一點的再說一下:就是我們在京東網站,A用戶登錄以后,在京東網站當中的,進行查詢商品,購買商品,添加商品購物車,等等,都是屬于該 A用戶專屬的一個 session 的會話,當我們再在京東網站當中,B用戶登錄以后,在京東網站當中的,進行查詢商品,購買商品,添加商品購物車,等等這些是 B用戶請求操作的都是專屬于一個 session 會話,
為什么不使用request物件保存會話狀態?為什么不使用ServletContext物件保存會話狀態?
- request.setAttribute()存資料,request.getAttribute()取資料,ServletContext也有這個方法,request是請求域,ServletContext是應用域,
- request是一次請求一個物件,
- ServletContext物件是服務器啟動的時候創建,服務器關閉的時候銷毀,這個ServletContext物件只有一個,
- ServletContext物件的域太大,
- request請求域(HttpServletRequest)、session會話域(HttpSession)、application應用域(ServletContext)
- 三個域之間的作用域的大小關系:request (請求域)< session(會話域) < application(應用域) ,
4. session 的實作原理解釋
HttpSession session = request.getSession();
這行代碼很神奇,張三訪問的時候獲取的 session 物件就是張三專屬的,李四訪問的時候獲取的 session 物件就是李四專屬的,
這是如何做到的呢?我們可以舉一個有關于我們實際生活當中的一個例子:
比如: 我們張三,李四都是在同一個大學的班級當中,張三和李四上的都是同一個籃球課(體育課),當他們上課的時候
,他們的體育老師帶來了(一筐籃球)(就是 session ),讓同學們自行挑選好自己的籃球,用于上籃球課,這時候我們的張三認真的挑選到了一個籃球,并且試了試手感,感覺十分的不錯,心里就有了一點小心思:就是想要,自己每次上籃球課的時候,都可以找到,并拿到這個手感不錯的籃球,怎么實作這個想法呢?于是,張三同學就在,這個他手中的(手感不錯)籃球上做了一個標記(SESSIONID=xxxxxx),這個標記只有張三自己知道是干什么的,其他同學都不知道,這樣當下次以后的每一節籃球課,張三都可以根據自己所作的這個標記,從眾多籃球當中,找到這個,自己標記到的籃球了,
這個例子當中的: 一筐籃球就可以比作是 : 服務器的當中的 session 會話物件,而其中的 張三自己在籃球上作的標記就可以比作是: SESSIONID=xxxxxx 是 session 物件的 ID 了,
session 生成的程序:
一個 session 會話物件 對應一個 JSESSIONID=xxxxxx (就是一個標記 session 會話物件的 ID (類似于一個人的身份證資訊)是唯一的),
服務器當中是有一個類似于 Map 的一個 session 串列,該 session 串列當中存在兩樣東西: key 對應的是 JSESSIONID=xxxxxx (也就是 session 的ID的標記) ,而 value 對應的則是 session 物件,
key (session 的 ID) | value ( session 物件) |
---|---|
JSESSIONID=123 | session1 |
JSESSIONID=456 | session2 |
當用戶第一次請求服務器的時候,服務器會為該用戶生成一個 session 會話物件,同時服務器會將該 session 對應 JSESSIONID=123,也就是: sessionID 發送給客戶端,客戶端會接收到服務器發送過來的 JSESSIONID ,并存盤到 客戶端的快取(Cookie) 當中,同時需要注意的是: JSESSIONID=xxxxxx 這個是以Cookie的形式保存在瀏覽器的記憶體中的,瀏覽器只要關閉,這個cookie就沒有了,(當然這是默認的情況下,你是可以自定義設定的,關于 Cookie 的內容這里就不會說明了,)
舉例:具體代碼詳細如下:
package com.RainbowSea.session;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/session")
public class TestSessionServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
HttpSession session = request.getSession();
//
response.setContentType("text/html;charSet=UTF-8");
PrintWriter out = response.getWriter();
//
out.println(" session物件: " + session);
}
}
如下是我們的瀏覽器(客戶端) 向服務器 第一次 發送請求(response) 的效果圖:如下:
第二次,我們的瀏覽器(客戶端)向服務器發送 第二次請求(response) ,因為我們瀏覽器(客戶端)第一次請求的時候,已經將服務器回應過來的 JSESSIONID 存盤到了,自己客戶端的 Cookie 當中去了,所以,當我們的客戶端再次向上一個服務器發送請求的時候,這是同屬于同一個會話的,所以我們的客戶端將第一次請求的時候,獲取到的 JSESSIONID 發送給 服務器,服務器根據 JSESSIONID 查找session物件, 回傳給客戶端,所以兩者之間的 session 物件的地址是一樣的,因為是同屬于同一個會話的,測驗效果如下:
注意:我們的瀏覽器是遵循 HTTP 協議的,而 HTTP 協議是 無狀態的,導致我們的服務器無法知道瀏覽器關閉了,所以我們的 會話銷毀存在一種(延遲銷毀的機制:簡單的說就是,當一個 session 會話,在一定的時間段內沒有,任何的請求發發送了,服務器就會認為該 sessoin 沒有用了,會自動銷毀該 session 會話物件了),當我們關閉瀏覽器,記憶體消失,Cookie 消失,Cookie 消失了,那存在其中的 JSESSIONID (也就是 sessionID ) 自然也就消失了,而 JSESSIONID 消失了,我們的客戶端也就無法根據該 JSESSIONID 獲取到,訪問到 對應的 session 物件了,當到達一定的時間段后,還是沒有任何客戶端訪問該 Session 會話,服務器就會自動銷毀該 session 會話物件了,
關閉瀏覽器,重新發送請求,測驗效果如下圖所示:
session物件的銷毀:
session 物件是什么時候銷毀:
瀏覽器關閉的時候,服務器是不知道的,服務器無法監測到瀏覽器關閉了(HTTP協議是無狀態協議),所以 session 的銷毀要依靠 session 超時機制,
但也有一種可能,系統提供了 “安全退出”,用戶可以點擊這個按鈕,這樣服務器就知道你退出了,然后服務器會自動銷毀 session 物件,
- 第一種: 手動銷毀
// 銷毀 session 物件的 session.invalidate();
- 第二種:自動銷毀(超時銷毀)
為什么關閉瀏覽器,會話結束?
關閉瀏覽器之后,瀏覽器中保存的 JSESSIONID (也就是 session 的ID)消失,下次重新打開瀏覽器之后,
瀏覽器快取中沒有這個 session的ID,自然找不到 服務器中對應的 session 物件,session 物件找不到,等同于會話結束,(超時銷毀,當一個 session 一段時間內沒有,被訪問了,就會自動被服務器銷毀,這里我們的 JSESSIONID 都沒有了,我們就無法找到對應 session 的物件,無法找到 session 物件,就更無法訪問了,)
session 超時銷毀機制的設定的時間點,默認是 Tomcat apache-tomcat-10.0.12\conf\web.xml的
web.xml
配置當中,默認配置為了 30 分鐘
<!-- ==================== Default Session Configuration ================= --> <!-- You can set the default session timeout (in minutes) for all newly --> <!-- created sessions by modifying the value below. --> <session-config> <session-timeout>30</session-timeout> </session-config>
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sJQQVVaF-1682775113687)(E:\博客\javaWed博客庫\image-20230424221802452.png)]
當然,這個 session 超時銷毀的時間點,我們也是可以設定的,
我們可以根據自己的需要設定,比如:如果是一個銀行的安全資訊的話,可以設定為 1~5 分鐘,如果是一個長久使用的話可以設定為 24 小時,7天等等,根據實際業務需要靈活的設定,
重點:如下是 session 的生成,銷毀的原理圖示:
5. 補充: Cookie禁用了,session還能找到嗎 ?
cookie禁用是什么意思?服務器正常發送cookie給瀏覽器,但是瀏覽器不要了,拒收了,并不是服務器不發了,
如下是: Google Chrome 瀏覽器禁用 Cookie 的設定:
當我們禁用了瀏覽器的 Cookie 設定,再次訪問我們的 Servlet 服務器的效果如下:
下面這個是 Firefox火狐瀏覽器的禁用 Cookie 的設定,
結論:當瀏覽器禁用了Cookie 快取功能,服務器正常發送cookie資訊(包括了 JSESSIONID 資訊)給瀏覽器,但是瀏覽器不要了,拒收了,并不是服務器不發了,所以導致的結果就是:客戶端不會發送給服務器 JSESSIONID資訊了,找不到了,每一次請求都會獲取到新的session物件,
問題:cookie禁用了,session機制還能實作嗎?
可以,需要使用 URL 重寫機制,
如下:演示:當我們訪問服務器時,通過瀏覽器的 檢查功能中的 ——> 網路(NetWork) 當中的第一次請求服務器,服務器回應給客戶端的 JSESSIONID 的資訊會顯示在其中的:response headers (請求頭當中 ),
將其中的
jsessionid=19D1C99560DCBF84839FA43D58F56E16
拼接到我們訪問的 URL當中,中間使用;
分號隔開,如下:需要注意的是,將其中的 JSESSIONID 寫成小寫的:jsessionid
http://127.0.0.1:8080/servlet14/session;jsessionid=F247C2C5CBE489F45383D116224F071B
原理:是雖然我們瀏覽器沒有保存住服務器回應過來的JSESSIONID資訊,但是我們手動將其中的SESSIOND給記住了,并通過地址欄的方式,get的方式發送給了服務器,服務器就會幫我們去session串列當中找到該對過的JSESSIONID的
session物件,而不是新建esssion物件了,
URL重寫機制會提高開發者的成本,開發人員在撰寫任何請求路徑的時候,后面都要添加一個sessionid,給開發帶來了很大的難度,很大的成本,所以大部分的網站都是這樣設計的:你要是禁用cookie,你就別用了,
怎么理解這個: 你要是禁用了 Cookie 快取機制,你就別用了,就是說,如果你把 Cookie 禁用了一些網站你可能打不開來,或者說無法顯示全部內容資訊,當你開始這個設定 禁用Cookie 都會有一些提示的資訊給到你的,比如:
,如下當我們把 Firefox火狐瀏覽器的禁用 Cookie 打開,訪問
- 京東網站:https://www.jd.com/
- 訪問淘寶:https://www.taobao.com/
- 訪問唯品會:https://www.vip.com/
- 訪問12306 網站:https://www.12306.cn/index/
6. 總結一下到目前位置我們所了解的域物件:
- request(對應的類名:HttpServletRequest)請求域(請求級別的)
- session(對應的類名:HttpSession)會話域(用戶級別的)
- application(對應的類名:ServletContext)應用域(專案級別的,所有用戶共享的,)
- 這三個域物件的大小關系:request < session < application
- 他們三個域物件都有以下三個公共的方法:
- setAttribute(向域當中系結資料)
- getAttribute(從域當中獲取資料)
- removeAttribute(洗掉域當中的資料)
- 使用原則:盡量使用小的域,
7. oa 專案的優化體驗:使用上 session 會話機制:
閱讀如下內容,大家可以先移步至: Servlet注解的使用,簡化配置 以及,使用模板方法設計模式優化oa專案_ChinaRainbowSea的博客-CSDN博客看看有助于閱讀理解,
session掌握之后,我們怎么解決oa專案中的登錄問題:就是我們的登錄頁面是一個擺設,當用戶沒有登錄的情況下,可以直接通過在地址欄上輸入 URL 可以訪問到對應的資源資訊,
這里我們可以使用: session 會話機制,讓登錄起作用:就是如果用戶直接通過在地址欄上輸入 URL 可以訪問到對應的資源資訊的時候,判斷用戶是否登錄過,如果登錄過,則可以直接訪問,如果沒有登錄過就跳轉到登錄頁面,進行一個正確的登錄成功的操作,才可以訪問,同時設定一個安全退出系統,銷毀 session 物件的按鈕設定,
登錄成功之后,可以將用戶的登錄資訊存盤到session當中,也就是說session中如果有用戶的資訊就代表用戶登錄成功了,session中沒有用戶資訊,表示用戶沒有登錄過,則跳轉到登錄頁面,
優化原始碼如下:
首先是登錄頁面的優化:當用戶登錄成功,將用戶的登錄資訊存盤到session當中(這里我們存盤到用戶的用戶名資訊,)
核心優化代碼:
// 登錄成功與否
if (success) {
// 成功,跳轉到用戶串列頁面
// 這里使用重定向(沒有資源的共享):重定向需要加/專案名 +
// 獲取session 物件(這里的要求是: 必須獲取到 session ,沒有session 也要新建一個 session 物件)
// 注意:我們下面的這個會話是不能洗掉的,因為上面我們雖然通過 welcome Servlet 進行了一個會話
// 但是 welcome 當中是當我們cookie 當中存在并且用戶名和密碼正確的時候才會進行一個 session 的
HttpSession session = request.getSession(); // 服務器當中沒有 session 會話域自動創建
session.setAttribute("username", username); // 將用戶名存盤到 session 會話域當中
response.sendRedirect(request.getContextPath() + "/dept/list");
} else {
// 失敗,跳轉到失敗頁面
response.sendRedirect(request.getContextPath() + "/error.jsp");
}
全部的代碼:
package com.RainbowSea.servlet;
import com.RainbowSea.DBUtil.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@WebServlet({"/user/login", "/user/exit"})
public class UserServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 獲取到瀏覽器地址欄上的URL路徑
String servletPath = request.getServletPath();
if ("/user/login".equals(servletPath)) {
doLogin(request, response);
} else if ("/user/exit".equals(servletPath)) {
doExit(request, response);
}
}
private void doExit(HttpServletRequest request, HttpServletResponse response) throws IOException {
}
protected void doLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 一個用戶登錄驗證的方式:驗證用戶名和密碼是否正確
// 獲取用戶名和密碼
// 前端提交是資料是:username=111&password=fads
// 注意:post 提交的資料是在請求體當中,而get提交的資料是在請求行當中
boolean success = false; // 標識登錄成功
String username = request.getParameter("username");
String password = request.getParameter("password");
String exempt = request.getParameter("exempt");
// 連接資料庫驗證用戶名和密碼
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 1. 獲取連接,注冊驅動
connection = DBUtil.getConnection();
// 2. 獲取操作資料物件,預編譯sql陳述句, ? 占位符不要加,“”,'' 單雙引號,成了字串了,無法識別成占位符了,
String sql = "select username,password from t_user where username = ? and password = ?";
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符,真正執行sql陳述句
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
resultSet = preparedStatement.executeQuery();
// 4. 處理查詢結果集
// 只有一條結果集
if (resultSet.next()) {
// 登錄成功
success = true;
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 5. 關閉資源,最后使用的最先關閉,
DBUtil.close(connection, preparedStatement, resultSet);
}
// 登錄成功與否
if (success) {
// 成功,跳轉到用戶串列頁面
// 這里使用重定向(沒有資源的共享):重定向需要加/專案名 +
// 獲取session 物件(這里的要求是: 必須獲取到 session ,沒有session 也要新建一個 session 物件)
// 注意:我們下面的這個會話是不能洗掉的,因為上面我們雖然通過 welcome Servlet 進行了一個會話
// 但是 welcome 當中是當我們cookie 當中存在并且用戶名和密碼正確的時候才會進行一個 session 的
HttpSession session = request.getSession(); // 服務器當中沒有 session 會話域自動創建
session.setAttribute("username", username); // 將用戶名存盤到 session 會話域當中
response.sendRedirect(request.getContextPath() + "/dept/list");
} else {
// 失敗,跳轉到失敗頁面
response.sendRedirect(request.getContextPath() + "/error.jsp");
}
}
}
其次是當用戶想要直接通過 URL訪問的時候,判斷用戶是否登錄成功過,登錄成功過可以訪問,沒有登錄成功過無法訪問:
思路是:
我們通過 session 會話機制,判斷用戶是否登錄過,如果用戶沒有登錄就想要訪問 到其資訊,不可以,因為我們這里判斷了一次是否登錄過,只有登錄入過了,才會將中登錄到用戶名為 “username” 的資訊存盤到 session 會話當中,如果沒有的話是查詢不到的,回傳的是 null,需要注意的一點就是,我們的jsp 當中的內置物件,是會自動創建一個 session 會話物件的(所以就會導致,就算我們沒有登錄成功 ,session 物件也是不為空的,因為JSP創建了 session 物件,我們可以通過JSP 指令禁止 JSP 生成 session 內置物件
<%@page session = false %>
,需要所有會被訪問,生成的 Jsp 檔案都需要設定該指令,這里 所謂的禁用了就是,對應的訪問生成的 xxx_jsp.java) 當中不會翻譯生成其中內置的 session 物件),但是因為這里我們進行了一個 雙重的判斷機制,if(session != null && session.getAttribute("username") != null) // 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中存盤了名為 "username" 的資訊,可以用戶登錄的資訊可以從 session 找到,如果找不到 ,回傳 null ,找到不為 null ,這樣就解決了 JSP 內置session 物件的沒有登錄 session 不為 null 的影響了,
需要注意一點的就是:這里我們要使用
HttpSession session = request.getSession(false)
HttpSession session = request.getSession(false); // 獲取到服務器當中的session ,沒有不會創建的, // session 是用戶登錄成功才創建的,其他情況不要創建 session 會話物件,
核心代碼:
// 可以使用模糊查詢 @WebServlet("/dept/*")
@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"})
public class DeptServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
String servletPath = request.getServletPath(); // 獲取到瀏覽器當中的uri
// 獲取session 這個 session 是不不需要新建的
// 只是獲取當前session ,獲取不到這回傳null,
HttpSession session = request.getSession(false); // 獲取到服務器當中的session ,沒有不會創建的
/**
* 說明這里我們通過 session 會話機制,判斷用戶是否登錄過,如果用戶沒有登錄就想要訪問
* 到其資訊,不可以,因為我們這里判斷了一次是否登錄過,只有登錄入過了,才會將中登錄到
* 用戶名為 “username” 的資訊存盤到 session 會話當中,如果沒有的話是查詢不到的,回傳的是 null
* 需要注意的一點就是,我們的jsp 當中的內置物件,是會自動創建一個 session 會話物件的,但是
* 因為這里我們進行了一個 雙重的判斷機制,注意:需要先將對應的 xx_jsp.java 生成才行,同時
* 使用 <%@page session = false %> 指令的話,需要所有會被訪問,生成的 Jsp 檔案都需要設定,
*
* jakarta.servlet.http.HttpSession session = null;
* session = pageContext.getSession();
*/
if(session != null && session.getAttribute("username") != null) {
// 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中存盤了名為 "username" 的資訊
if ("/dept/list".equals(servletPath)) {
doList(request, response);
} else if ("/dept/detail".equals(servletPath)) {
doDetail(request, response);
} else if ("/dept/delete".equals(servletPath)) {
doElete(request,response);
} else if("/dept/save".equals(servletPath)) {
doSave(request,response);
} else if("/dept/modify".equals(servletPath)) {
doModify(request,response);
}
} else {
response.sendRedirect(request.getContextPath()); // 訪問的web 站點的根即可,自動找到的是名為 index.jsp
}
}
}
最后就是:用戶點擊安全退出系統,銷毀 session 物件的實作了,
當我們點擊 安全退出,手動將 session 會話物件銷毀了,就需要重新登錄了,只有重新登錄,建立新的登錄成功的 session 會話資訊,才能再次通過URL訪問,
核心代碼如下:
session.invalidate(); // 銷毀 session 物件,
/**
* 用戶手動點擊安全退出,銷毀 session 物件
* @param request
* @param response
* @throws IOException
*/
private void doExit(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 獲取到客戶端發送過來的 sessoin
HttpSession session = request.getSession();
if (session != null) {
// 手動銷毀 session 物件
// 注意:會話銷毀的了,自然需要重寫登錄了,沒有登錄過,無法進行一個路徑的訪問的
session.invalidate();
// 跳轉會登錄的頁面
response.sendRedirect(request.getContextPath()); // 專案名路徑默認就是訪問的index.html 的歡迎頁面
}
}
全部具體代碼:
package com.RainbowSea.servlet;
import com.RainbowSea.DBUtil.DBUtil;
import com.RainbowSea.bean.Dept;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
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;
// 可以使用模糊查詢 @WebServlet("/dept/*")
@WebServlet({"/dept/list", "/dept/detail", "/dept/delete", "/dept/save", "/dept/modify"})
public class DeptServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
String servletPath = request.getServletPath(); // 獲取到瀏覽器當中的uri
// 獲取session 這個 session 是不不需要新建的
// 只是獲取當前session ,獲取不到這回傳null,
HttpSession session = request.getSession(false); // 獲取到服務器當中的session ,沒有不會創建的
/**
* 說明這里我們通過 session 會話機制,判斷用戶是否登錄過,如果用戶沒有登錄就想要訪問
* 到其資訊,不可以,因為我們這里判斷了一次是否登錄過,只有登錄入過了,才會將中登錄到
* 用戶名為 “username” 的資訊存盤到 session 會話當中,如果沒有的話是查詢不到的,回傳的是 null
* 需要注意的一點就是,我們的jsp 當中的內置物件,是會自動創建一個 session 會話物件的,但是
* 因為這里我們進行了一個 雙重的判斷機制,注意:需要先將對應的 xx_jsp.java 生成才行,同時
* 使用 <%@page session = false %> 指令的話,需要所有會被訪問,生成的 Jsp 檔案都需要設定,
*
* jakarta.servlet.http.HttpSession session = null;
* session = pageContext.getSession();
*/
if(session != null && session.getAttribute("username") != null) {
// 雙重的判斷,一個是 session 會話域要存在,其次是 會話域當中存盤了名為 "username" 的資訊
if ("/dept/list".equals(servletPath)) {
doList(request, response);
} else if ("/dept/detail".equals(servletPath)) {
doDetail(request, response);
} else if ("/dept/delete".equals(servletPath)) {
doElete(request,response);
} else if("/dept/save".equals(servletPath)) {
doSave(request,response);
} else if("/dept/modify".equals(servletPath)) {
doModify(request,response);
}
} else {
response.sendRedirect(request.getContextPath()); // 訪問的web 站點的根即可,自動找到的是名為 index.jsp
}
}
/**
* 修改部門資訊
*
* @param request
* @param response
*/
private void doModify(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8"); // 設定獲取的的資訊的編碼集
Connection connection = null;
PreparedStatement preparedStatement = null;
// 影響資料庫的行數
int count = 0;
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
try {
// 1. 注冊驅動,連接資料庫
connection = DBUtil.getConnection();
// 2. 獲取到操作資料庫的物件,預編譯sql陳述句,sql測驗
String sql = "update dept set dname = ?,loc = ? where depton = ?";
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符,真正執行sql陳述句
// 從下標 1開始
preparedStatement.setString(1, dname);
preparedStatement.setString(2, loc);
preparedStatement.setString(3, deptno);
count = preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 4. 釋放資源,最后使用的優先被釋放
DBUtil.close(connection, preparedStatement, null);
}
if (count == 1) {
// 更新成功
// 跳轉到部門串列頁面(部門串列表面是通過java程式動態生成的,所以還需要再次執行另一個Servlet)
// 轉發是服務器內部的操作,“/” 不要加專案名
// request.getRequestDispatcher("/dept/list/").forward(request,response);
// 優化使用重定向,自發前端(需要指明專案名)
response.sendRedirect(request.getContextPath() + "/dept/list");
}
}
/**
* 保存部門資訊
*
* @param request
* @param response
*/
private void doSave(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8");
// 獲取到前端的資料,建議 name 使用復制
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
// 連接資料庫,添加資料
Connection connection = null;
PreparedStatement preparedStatement = null;
// 影響資料庫的行數
int count = 0;
try {
// 1. 注冊驅動,連接資料庫
connection = DBUtil.getConnection();
// 2. 獲取操作資料庫物件,預編譯sql陳述句,Sql測驗
String sql = "insert into dept(depton,dname,loc) values(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符, 真正執行sql陳述句,
// 注意: 占位符的填充是從 1 開始的,基本上資料庫相關的起始下標索引都是從 1下標開始的
preparedStatement.setString(1, deptno);
preparedStatement.setString(2, dname);
preparedStatement.setString(3, loc);
// 回傳影響資料庫的行數
count = preparedStatement.executeUpdate();
// 5.釋放資源
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DBUtil.close(connection, preparedStatement, null);
}
// 保存成功,回傳部門串列頁面
if (count == 1) {
// 這里應該使用,重定向
// 這里用的轉發,是服務器內部的,不要加專案名
//request.getRequestDispatcher("/dept/list/").forward(request, response);
// 重定向
response.sendRedirect(request.getContextPath() + "/dept/list");
}
}
/**
* 通過部門洗掉部門
*
* @param request
* @param response
*/
private void doElete(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("UTF-8"); // 設定獲取的的資訊的編碼集
// 獲取到發送資料
String deptno = request.getParameter("deptno");
/*
根據部門編號洗掉資訊,
洗掉成功,跳轉回原來的部門串列頁面
洗掉失敗,跳轉洗掉失敗的頁面
*/
Connection connection = null;
PreparedStatement preparedStatement = null;
// 記錄洗掉資料庫的行數
int count = 0;
// 連接資料庫進行洗掉操作
try {
// 1.注冊驅動,連接資料庫
connection = DBUtil.getConnection();
// 開啟事務(取消自動提交機制),實作可回滾
connection.setAutoCommit(false);
// 2. 預編譯sql陳述句,sql測驗
String sql = "delete from dept where depton = ?"; // ? 占位符
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符,真正的執行sql陳述句
preparedStatement.setString(1, deptno);
// 回傳影響資料庫的行數
count = preparedStatement.executeUpdate();
connection.commit(); // 手動提交資料
} catch (SQLException e) {
// 遇到例外回滾
if (connection != null) {
try {
// 事務的回滾
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
throw new RuntimeException(e);
} finally {
// 4. 釋放資源
// 因為這里是洗掉資料,沒有查詢操作,所以 沒有 ResultSet 可以傳null
DBUtil.close(connection, preparedStatement, null);
}
if (count == 1) {
// 洗掉成功
// 仍然跳轉到部門串列頁面
// 部門串列頁面的顯示需要執行另外一個Servlet,怎么辦,可以使用跳轉,不過這里最后是使用重定向
// 注意:轉發是在服務器間的,所以不要加“專案名” 而是 / + web.xml 映射的路徑即可
//request.getRequestDispatcher("/dept/list/").forward(request,response);
// 優化:使用重定向機制 注意: 重定向是自發到前端的地址欄上的,前端所以需要指明專案名
// 注意: request.getContextPath() 回傳的根路徑是,包含了 "/" 的
response.sendRedirect(request.getContextPath() + "/dept/list");
}
}
/**
* 通過部門編號,查詢部門的詳情
*
* @param request
* @param response
*/
private void doDetail(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8"); // 設定獲取的的資訊的編碼集
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
// 獲取到部門編號
String dno = request.getParameter("dno");
Dept dept = new Dept();
// 獲取到部門編號,獲取部門資訊,將部門資訊收集好,然后跳轉到JSP做頁面展示
try {
// 2. 連接資料庫,根據部門編號查詢資料庫
// 1.注冊驅動,連接資料庫
connection = DBUtil.getConnection();
// 2. 預編譯SQL陳述句,sql要測驗
String sql = "select dname,loc from dept where depton = ?"; // ? 占位符
preparedStatement = connection.prepareStatement(sql);
// 3. 填充占位符,真正執行sql陳述句
preparedStatement.setString(1, dno);
resultSet = preparedStatement.executeQuery();
// 4. 處理查詢結果集
while (resultSet.next()) {
String dname = resultSet.getString("dname");
String loc = resultSet.getString("loc");
// 封裝物件(建議使用咖啡豆,因為只有一個物件)
dept.setDeptno(dno);
dept.setDname(dname);
dept.setLoc(loc);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 5. 釋放資源
DBUtil.close(connection, preparedStatement, resultSet);
}
// 這個咖啡豆只有一個,所以不需要袋子,只需要將這個咖啡豆放到request請求域當中,
// 用于對應的 jsp顯示
request.setAttribute("dept", dept);
//String sign = request.getParameter("f");
/*if("m".equals(sign)) {
// 轉發:多個請求為一個請求(地址欄不會發生改變)
// 注意: 該路徑默認是從 web 開始找的 / 表示 web
// 轉發到修改頁面
request.getRequestDispatcher("/edit.jsp").forward(request,response);
} else if("d".equals(sign)) {
// 跳轉到詳情頁面
request.getRequestDispatcher("/detail.jsp").forward(request,response);
}*/
// 或者優化
// 注意 無論是轉發還是重定向都是從 “/” 開始的
// request.getParameter()拿到的是 f=edit,還是f=detail 就是跳轉到的哪個頁面
//<a href="https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/04/29//dept/detail?f=edit&dno=">修改</a>
//<a href="https://www.cnblogs.com/TheMagicalRainbowSea/archive/2023/04/29//dept/detail?f=detail&dno=">詳情</a>
String forward = "/" + request.getParameter("f") + ".jsp";
request.getRequestDispatcher(forward).forward(request, response);
}
/**
* 連接資料庫,查詢所有的部門資訊,將部門資訊收集好,然后跳轉到JSP頁面展示
*
* @param request
* @param response
*/
private void doList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8"); // 設定獲取的的資訊的編碼集
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
// 創建一個集合List 存盤查詢到的資訊
List<Dept> depts = new ArrayList<Dept>();
try {
// 連接資料庫,查詢所有部門:
// 1. 注冊驅動,獲取連接
connection = DBUtil.getConnection();
// 2. 獲取操作資料庫物件,預編譯sql陳述句
String sql = "select depton as det,dname,loc from dept"; // 在mysql中測驗一下是否正確
preparedStatement = connection.prepareStatement(sql);
// 3. 執行sql陳述句
resultSet = preparedStatement.executeQuery();
// 4. 處理查詢結果集
while (resultSet.next()) {
String det = resultSet.getString("det"); // 有別名要使用別名
String dname = resultSet.getString("dname");
String loc = resultSet.getString("loc");
Dept dept = new Dept(det, dname, loc);
// 將部門物件放到List集合當中
depts.add(dept);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 5. 關閉資源
DBUtil.close(connection, preparedStatement, resultSet);
}
// 查詢到資料,將資料提交給 list.jsp 顯示資料
// 將集合存盤的資料放到請求域當中,用于其他Servlet 使用 jsp 也是Servelt
request.setAttribute("depList", depts);
// 轉發(注意不要重定向),重定向無法共用 request 請求域當中的資料
// 轉發路徑,/ 默認是從 web 目錄開始找的
request.getRequestDispatcher("/list.jsp").forward(request, response);
}
}
用戶界面的優化:顯示 登錄的用戶名:(該用戶名資訊,從 存盤到 session 會話物件當中,獲取到的,)
核心代碼如下:
需要注意的點就是:這里我們使用的是 JSP 內置的 session 物件,所以在這個 JSP頁面當,你不可以把 session 禁用了,
不要設定這個禁用 session 的指令: <%@page session = false %>
優化演示:
8. 總結:
- session 會話用戶場景:在Web應用程式中,我們經常要跟蹤用戶身份,當一個用戶登錄成功后,如果他繼續訪問其他頁面,Web程式如何才能識別出該用戶身份?
- session物件最主要的作用是:保存會話狀態,
- 為什么要保存會話狀態:因為HTTP協議是一種無狀態協議,
- 無狀態:
- 優點:這樣服務器壓力小,
- 缺點:服務器無法知道客戶端的狀態(是關閉的狀態,還是開啟的狀態)
- 無狀態:
- 一個 session 會話當中包含多次請求(一次會話對應N次請求,)
- session 物件是用服務器端生成的,所以這里是通過 request 請求的方式向服務器獲取到一個 session 會話物件
- session 的生成,銷毀,傳遞的原理機制:
- 簡單的來說吧 ,session 就是一個標記,通過標記 JSESSIONID 獲取到同一個 session 物件,保證你對應的操作是同一個用戶,
- Cookie禁用了,session還能找到嗎 ? 可以,使用 URL重寫機制,
- 實作用戶登錄,通過 session 會話機制(保存用戶登錄資訊),實作用戶登錄成功,可以通過 URL 直接訪問資源,沒有登錄/登錄失敗,則無法直接通過 URL 訪問資源,
9. 最后:
限于自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善,謝謝大家,江湖再見,后悔有期
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551493.html
標籤:其他
上一篇:Django筆記三十三之快取操作
下一篇:返回列表