B/S結構系統的會話機制(session) 每博一文案 你跑得快,22歲有個家,身邊全是贊嘆,你跑得慢,30歲還在路上追求夢想。有的人為了車,房拼了一輩子, 有的人買輛摩托車走遍了大好江山。你想成為怎樣的人,過怎樣的生活,只要你不後悔就行。 並不是所有人都能在早上七點鐘起床的,也別拿一碗飯來衡量一個 ...
B/S結構系統的會話機制(session)
目錄每博一文案
你跑得快,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.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="<%=request.getContextPath()%>/dept/detail?f=edit&dno=<%=dept.getDeptno()%>">修改</a>
//<a href="<%=request.getContextPath()%>/dept/detail?f=detail&dno=<%=dept.getDeptno()%>">詳情</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. 最後:
限於自身水平,其中存在的錯誤,希望大家給予指教,韓信點兵——多多益善,謝謝大家,江湖再見,後悔有期