Cookie&Session(會話技術)
@
目錄會話技術
今日目標
理解什麼是會話跟蹤技術
掌握Cookie的使用
掌握Session的使用
完善使用者登入註冊案例的功能
1,會話跟蹤技術的概述
對於會話跟蹤
這四個詞,我們需要拆開來進行解釋,首先要理解什麼是會話
,然後再去理解什麼是會話跟蹤
:
-
會話:使用者開啟瀏覽器,訪問web伺服器的資源,會話建立,直到有一方斷開連線,會話結束。在一次會話中可以包含多次請求和響應。
- 從瀏覽器發出請求到服務端響應資料給前端之後,一次會話(在瀏覽器和伺服器之間)就被建立了
- 會話被建立後,如果瀏覽器或服務端都沒有被關閉,則會話就會持續建立著
- 瀏覽器和伺服器就可以繼續使用該會話進行請求傳送和響應,上述的整個過程就被稱之為會話。
用實際場景來理解下會話,比如在我們訪問京東的時候,當開啟瀏覽器進入京東首頁後,瀏覽器和京東的伺服器之間就建立了一次會話,後面的搜尋商品,檢視商品的詳情,加入購物車等都是在這一次會話中完成。
思考:下圖中總共建立了幾個會話?
每個瀏覽器都會與服務端建立了一個會話,加起來總共是3個會話。
-
會話跟蹤:一種維護瀏覽器狀態的方法,伺服器需要識別多次請求是否來自於同一瀏覽器,以便在同一次會話的多次請求間共享資料。
- 伺服器會收到多個請求,這多個請求可能來自多個瀏覽器,如上圖中的6個請求來自3個瀏覽器
- 伺服器需要用來識別請求是否來自同一個瀏覽器
- 伺服器用來識別瀏覽器的過程,這個過程就是會話跟蹤
- 伺服器識別瀏覽器後就可以在同一個會話中多次請求之間來共享資料
那麼我們又有一個問題需要思考,一個會話中的多次請求為什麼要共享資料呢?有了這個資料共享功能後能實現哪些功能呢?
-
購物車:
加入購物車
去購物車結算
是兩次請求,但是後面這次請求要想展示前一次請求所新增的商品,就需要用到資料共享。 -
頁面展示使用者登入資訊:很多網站,登入後訪問多個功能傳送多次請求後,瀏覽器上都會有當前登入使用者的資訊[使用者名稱],比如百度、京東、碼雲等。
-
網站登入頁面的
記住我
功能:當用戶登入成功後,勾選記住我
按鈕後下次再登入的時候,網站就會自動填充使用者名稱和密碼,簡化使用者的登入操作,多次登入就會有多次請求,他們之間也涉及到共享資料 -
登入頁面的驗證碼功能:生成驗證碼和輸入驗證碼點選註冊這也是兩次請求,這兩次請求的資料之間要進行對比,相同則允許註冊,不同則拒絕註冊,該功能的實現也需要在同一次會話中共享資料。
通過這幾個例子的講解,相信大家對會話追蹤
技術已經有了一定的理解,該技術在實際開發中也非常重要。那麼接下來我們就需要去學習下會話跟蹤
技術,在學習這些技術之前,我們需要思考:為什麼現在瀏覽器和伺服器不支援資料共享呢?
- 瀏覽器和伺服器之間使用的是HTTP請求來進行資料傳輸
- HTTP協議是無狀態的,每次瀏覽器向伺服器請求時,伺服器都會將該請求視為新的請求
- HTTP協議設計成無狀態的目的是讓每次請求之間相互獨立,互不影響
- 請求與請求之間獨立後,就無法實現多次請求之間的資料共享
分析完具體的原因後,那麼該如何實現會話跟蹤技術呢? 具體的實現方式有:
(1)客戶端會話跟蹤技術:Cookie
(2)服務端會話跟蹤技術:Session
這兩個技術都可以實現會話跟蹤,它們之間最大的區別:Cookie是儲存在瀏覽器端而Session是儲存在伺服器端
具體的學習思路為:
- CooKie的基本使用、原理、使用細節
- Session的基本使用、原理、使用細節
- Cookie和Session的綜合案例
小結
在這節中,我們主要介紹了下什麼是會話和會話跟蹤技術,需要注意的是:
- HTTP協議是無狀態的,靠HTTP協議是無法實現會話跟蹤
- 想要實現會話跟蹤,就需要用到Cookie和Session
這個Cookie和Session具體該如何使用,接下來就先從Cookie來學起。
2,Cookie
學習Cookie,我們主要解決下面幾個問題:
- 什麼是Cookie?
- Cookie如何來使用?
- Cookie是如何實現的?
- Cookie的使用注意事項有哪些?
2.1 Cookie的基本使用
1.概念
Cookie:客戶端會話技術,將資料儲存到客戶端,以後每次請求都攜帶Cookie資料進行訪問。
2.Cookie的工作流程
- 服務端提供了兩個Servlet,分別是ServletA和ServletB
- 瀏覽器傳送HTTP請求1給服務端,服務端ServletA接收請求並進行業務處理
- 服務端ServletA在處理的過程中可以建立一個Cookie物件並將
name=zs
的資料存入Cookie - 服務端ServletA在響應資料的時候,會把Cookie物件響應給瀏覽器
- 瀏覽器接收到響應資料,會把Cookie物件中的資料儲存在瀏覽器記憶體中,此時瀏覽器和服務端就建立了一次會話
- 在同一次會話中瀏覽器再次傳送HTTP請求2給服務端ServletB,瀏覽器會攜帶Cookie物件中的所有資料
- ServletB接收到請求和資料後,就可以獲取到儲存在Cookie物件中的資料,這樣同一個會話中的多次請求之間就實現了資料共享
3.Cookie的基本使用
對於Cookie的使用,我們更關注的應該是後臺程式碼如何操作Cookie,對於Cookie的操作主要分兩大類,本別是傳送Cookie和獲取Cookie,對於上面這兩塊內容,分別該如何實現呢?
3.1 傳送Cookie
- 建立Cookie物件,並設定資料
Cookie cookie = new Cookie("key","value");
- 傳送Cookie到客戶端:使用response物件
response.addCookie(cookie);
介紹完傳送Cookie對應的步驟後,接下面通過一個案例來完成Cookie的傳送,具體實現步驟為:
需求:在Servlet中生成Cookie物件並存入資料,然後將資料傳送給瀏覽器
1.建立Maven專案,專案名稱為cookie-demo,並在pom.xml新增依賴
2.編寫Servlet類,名稱為AServlet
3.在AServlet中建立Cookie物件,存入資料,傳送給前端
4.啟動測試,在瀏覽器檢視Cookie物件中的值
(1)建立Maven專案cookie-demo,並在pom.xml新增依賴
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--servlet-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--jsp-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<!--jstl-->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</build>
(2)編寫Servlet類,名稱為AServlet
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3)在Servlet中建立Cookie物件,存入資料,傳送給前端
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//傳送Cookie
//1. 建立Cookie物件
Cookie cookie = new Cookie("username","zs");
//2. 傳送Cookie,response
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(4)啟動測試,在瀏覽器檢視Cookie物件中的值
訪問http://localhost:8080/cookie-demo/aServlet
chrome瀏覽器檢視Cookie的值,有兩種方式,分散式:
方式一:
方式二:選中開啟開發者工具或者 使用快捷鍵F12 或者 Ctrl+Shift+I
3.2 獲取Cookie
- 獲取客戶端攜帶的所有Cookie,使用request物件
Cookie[] cookies = request.getCookies();
- 遍歷陣列,獲取每一個Cookie物件:for
- 使用Cookie物件方法獲取資料
cookie.getName();
cookie.getValue();
介紹完獲取Cookie對應的步驟後,接下面再通過一個案例來完成Cookie的獲取,具體實現步驟為:
需求:在Servlet中獲取前一個案例存入在Cookie物件中的資料
1.編寫一個新Servlet類,名稱為BServlet
2.在BServlet中使用request物件獲取Cookie陣列,遍歷陣列,從資料中獲取指定名稱對應的值
3.啟動測試,在控制檯打印出獲取的值
(1)編寫一個新Servlet類,名稱為BServlet
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(2)在BServlet中使用request物件獲取Cookie陣列,遍歷陣列,從資料中獲取指定名稱對應的值
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取Cookie
//1. 獲取Cookie陣列
Cookie[] cookies = request.getCookies();
//2. 遍歷陣列
for (Cookie cookie : cookies) {
//3. 獲取資料
String name = cookie.getName();
if("username".equals(name)){
String value = cookie.getValue();
System.out.println(name+":"+value);
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3)啟動測試,在控制檯打印出獲取的值
訪問http://localhost:8080/cookie-demo/bServlet
在IDEA控制檯就能看到輸出的結果:
思考:測試的時候
- 在訪問AServlet和BServlet的中間把關閉瀏覽器,重啟瀏覽器後訪問BServlet能否獲取到Cookie中的資料?
這個問題,我們會在Cookie的使用細節中講,大家可以動手先試下。
小結
在這節中,我們主要講解了Cookie的基本使用,包含兩部分內容
- 傳送Cookie:
- 建立Cookie物件,並設定值:Cookie cookie = new Cookie("key","value");
- 傳送Cookie到客戶端使用的是Reponse物件:response.addCookie(cookie);
- 獲取Cookie:
- 使用Request物件獲取Cookie陣列:Cookie[] cookies = request.getCookies();
- 遍歷陣列
- 獲取陣列中每個Cookie物件的值:cookie.getName()和cookie.getValue()
介紹完Cookie的基本使用之後,那麼Cookie的底層到底是如何實現一次會話兩次請求之間的資料共享呢?
2.2 Cookie的原理分析
對於Cookie的實現原理是基於HTTP協議的,其中設計到HTTP協議中的兩個請求頭資訊:
- 響應頭:set-cookie
- 請求頭: cookie
- 前面的案例中已經能夠實現,AServlet給前端傳送Cookie,BServlet從request中獲取Cookie的功能
- 對於AServlet響應資料的時候,Tomcat伺服器都是基於HTTP協議來響應資料
- 當Tomcat發現後端要返回的是一個Cookie物件之後,Tomcat就會在響應頭中新增一行資料
Set-Cookie:username=zs
- 瀏覽器獲取到響應結果後,從響應頭中就可以獲取到
Set-Cookie
對應值username=zs
,並將資料儲存在瀏覽器的記憶體中 - 瀏覽器再次傳送請求給BServlet的時候,瀏覽器會自動在請求頭中新增
Cookie: username=zs
傳送給服務端BServlet - Request物件會把請求頭中cookie對應的值封裝成一個個Cookie物件,最終形成一個數組
- BServlet通過Request物件獲取到Cookie[]後,就可以從中獲取自己需要的資料
接下來,使用剛才的案例,把上述結論驗證下:
(1)訪問AServlet對應的地址http://localhost:8080/cookie-demo/aServlet
使用Chrom瀏覽器開啟開發者工具(F12或Crtl+Shift+I)進行檢視響應頭中的資料
(2)訪問BServlet對應的地址`http://localhost:8080/cookie-demo/bServlet
使用Chrom瀏覽器開啟開發者工具(F12或Crtl+Shift+I)進行檢視請求頭中的資料
2.3 Cookie的使用細節
在這節我們主要講解兩個知識,第一個是Cookie的存活時間,第二個是Cookie如何儲存中文,首先來學習下Cookie的存活時間。
2.3.1 Cookie的存活時間
前面讓大家思考過一個問題:
(1)瀏覽器傳送請求給AServlet,AServlet會響應一個存有usernanme=zs
的Cookie物件給瀏覽器
(2)瀏覽器接收到響應資料將cookie存入到瀏覽器記憶體中
(3)當瀏覽器再次傳送請求給BServlet,BServlet就可以使用Request物件獲取到Cookie資料
(4)在傳送請求到BServlet之前,如果把瀏覽器關閉再開啟進行訪問,BServlet能否獲取到Cookie資料?
注意:瀏覽器關閉再開啟不是指開啟一個新的選顯示卡,而且必須是先關閉再開啟,順序不能變。
針對上面這個問題,通過演示,會發現,BServlet中無法再獲取到Cookie資料,這是為什麼呢?
- 預設情況下,Cookie儲存在瀏覽器記憶體中,當瀏覽器關閉,記憶體釋放,則Cookie被銷燬
這個結論就印證了上面的演示效果,但是如果使用這種預設情況下的Cookie,有些需求就無法實現,比如:
上面這個網站的登入頁面上有一個記住我
的功能,這個功能大家都比較熟悉
- 第一次輸入使用者名稱和密碼並勾選
記住我
然後進行登入 - 下次再登陸的時候,使用者名稱和密碼就會被自動填充,不需要再重新輸入登入
- 比如
記住我
這個功能需要記住使用者名稱和密碼一個星期,那麼使用預設情況下的Cookie就會出現問題 - 因為預設情況,瀏覽器一關,Cookie就會從瀏覽器記憶體中刪除,對於
記住我
功能就無法實現
所以我們現在就遇到一個難題是如何將Cookie持久化儲存?
Cookie其實已經為我們提供好了對應的API來完成這件事,這個API就是setMaxAge,
- 設定Cookie存活時間
setMaxAge(int seconds)
引數值為:
1.正數:將Cookie寫入瀏覽器所在電腦的硬碟,持久化儲存。到時間自動刪除
2.負數:預設值,Cookie在當前瀏覽器記憶體中,當瀏覽器關閉,則Cookie被銷燬
3.零:刪除對應Cookie
接下來,咱們就在AServlet中去設定Cookie的存活時間。
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//傳送Cookie
//1. 建立Cookie物件
Cookie cookie = new Cookie("username","zs");
//設定存活時間 ,1周 7天
cookie.setMaxAge(60*60*24*7); //易閱讀,需程式計算
//cookie.setMaxAge(604800); //不易閱讀(可以使用註解彌補),程式少進行一次計算
//2. 傳送Cookie,response
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
修改完程式碼後,啟動測試,訪問http://localhost:8080/cookie-demo/aServlet
- 訪問一個AServlet後,把瀏覽器關閉重啟後,再去訪問
http://localhost:8080/cookie-demo/bServet
,能在控制檯打印出username:zs
,說明Cookie沒有隨著瀏覽器關閉而被銷燬 - 通過瀏覽器檢視Cookie的內容,會發現Cookie的相關資訊
2.3.2 Cookie儲存中文
首先,先來演示一個效果,將之前username=zs
的值改成username=張三
,把漢字張三
存入到Cookie中,看是什麼效果:
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//傳送Cookie
String value = "張三";
Cookie cookie = new Cookie("username",value);
//設定存活時間 ,1周 7天
cookie.setMaxAge(60*60*24*7);
//2. 傳送Cookie,response
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
啟動訪問測試,訪問http://localhost:8080/cookie-demo/aServlet
會發現瀏覽器會提示錯誤資訊
通過上面的案例演示,我們得到一個結論:
- Cookie不能直接儲存中文
Cookie不能儲存中文,但是如果有這方面的需求,這個時候該如何解決呢?
這個時候,我們可以使用之前學過的一個知識點叫URL編碼
,所以如果需要儲存中文,就需要進行轉碼,具體的實現思路為:
1.在AServlet中對中文進行URL編碼,採用URLEncoder.encode(),將編碼後的值存入Cookie中
2.在BServlet中獲取Cookie中的值,獲取的值為URL編碼後的值
3.將獲取的值在進行URL解碼,採用URLDecoder.decode(),就可以獲取到對應的中文值
(1)在AServlet中對中文進行URL編碼
@WebServlet("/aServlet")
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//傳送Cookie
String value = "張三";
//對中文進行URL編碼
value = URLEncoder.encode(value, "UTF-8");
System.out.println("儲存資料:"+value);
//將編碼後的值存入Cookie中
Cookie cookie = new Cookie("username",value);
//設定存活時間 ,1周 7天
cookie.setMaxAge(60*60*24*7);
//2. 傳送Cookie,response
response.addCookie(cookie);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(2)在BServlet中獲取值,並對值進行解碼
@WebServlet("/bServlet")
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取Cookie
//1. 獲取Cookie陣列
Cookie[] cookies = request.getCookies();
//2. 遍歷陣列
for (Cookie cookie : cookies) {
//3. 獲取資料
String name = cookie.getName();
if("username".equals(name)){
String value = cookie.getValue();//獲取的是URL編碼後的值 %E5%BC%A0%E4%B8%89
//URL解碼
value = URLDecoder.decode(value,"UTF-8");
System.out.println(name+":"+value);//value解碼後為 張三
break;
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
至此,我們就可以將中文存入Cookie中進行使用。
小結
Cookie的使用細節中,我們講了Cookie的存活時間
和儲存中文
:
-
存活時間,需要掌握setMaxAage()API的使用
-
儲存中文,需要掌握URL編碼和解碼的使用
3,Session
Cookie已經能完成一次會話多次請求之間的資料共享,之前我們還提到過Session也可以實現,那麼:
- 什麼是Session?
- Session如何來使用?
- Session是如何實現的?
- Session的使用注意事項有哪些?
3.1 Session的基本使用
1.概念
Session:服務端會話跟蹤技術:將資料儲存到服務端。
- Session是儲存在服務端而Cookie是儲存在客戶端
- 儲存在客戶端的資料容易被竊取和截獲,存在很多不安全的因素
- 儲存在服務端的資料相比於客戶端來說就更安全
2.Session的工作流程
- 在服務端的AServlet獲取一個Session物件,把資料存入其中
- 在服務端的BServlet獲取到相同的Session物件,從中取出資料
- 就可以實現一次會話中多次請求之間的資料共享了
- 現在最大的問題是如何保證AServlet和BServlet使用的是同一個Session物件(在原理分析會講解)?
3.Session的基本使用
在JavaEE中提供了HttpSession介面,來實現一次會話的多次請求之間資料共享功能。
具體的使用步驟為:
- 獲取Session物件,使用的是request物件
HttpSession session = request.getSession();
-
Session物件提供的功能:
-
儲存資料到 session 域中
void setAttribute(String name, Object o)
-
根據 key,獲取值
Object getAttribute(String name)
-
根據 key,刪除該鍵值對
void removeAttribute(String name)
-
介紹完Session相關的API後,接下來通過一個案例來完成對Session的使用,具體實現步驟為:
需求:在一個Servlet中往Session中存入資料,在另一個Servlet中獲取Session中存入的資料
1.建立名為SessionDemo1的Servlet類
2.建立名為SessionDemo2的Servlet類
3.在SessionDemo1的方法中:獲取Session物件、儲存資料
4.在SessionDemo2的方法中:獲取Session物件、獲取資料
5.啟動測試
(1)建立名為SessionDemo1的Servlet類
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(2)建立名為SessionDemo2的Servlet類
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3)SessionDemo1:獲取Session物件、儲存資料
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//儲存到Session中
//1. 獲取Session物件
HttpSession session = request.getSession();
//2. 儲存資料
session.setAttribute("username","zs");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(4)SessionDemo2:獲取Session物件、獲取資料
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取資料,從session中
//1. 獲取Session物件
HttpSession session = request.getSession();
//2. 獲取資料
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(5)啟動測試,
- 先訪問
http://localhost:8080/cookie-demo/demo1
,將資料存入Session - 在訪問
http://localhost:8080/cookie-demo/demo2
,從Session中獲取資料 - 檢視控制檯
通過案例的效果,能看到Session是能夠在一次會話中兩次請求之間共享資料。
小結
至此Session的基本使用就已經完成了,重點要掌握的是:
-
Session的獲取
HttpSession session = request.getSession();
-
Session常用方法的使用
void setAttribute(String name, Object o) Object getAttribute(String name)
注意:Session中可以儲存的是一個Object型別的資料,也就是說Session中可以儲存任意資料型別。
介紹完Session的基本使用之後,那麼Session的底層到底是如何實現一次會話兩次請求之間的資料共享呢?
3.2 Session的原理分析
- Session是基於Cookie實現的
這句話其實不太能詳細的說明Session的底層實現,接下來,咱們一步步來分析下Session的具體實現原理:
(1)前提條件
Session要想實現一次會話多次請求之間的資料共享,就必須要保證多次請求獲取Session的物件是同一個。
那麼它們是一個物件麼?要驗證這個結論也很簡單,只需要在上面案例中的兩個Servlet中分別列印下Session物件
SessionDemo1
@WebServlet("/demo1")
public class SessionDemo1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//儲存到Session中
//1. 獲取Session物件
HttpSession session = request.getSession();
System.out.println(session);
//2. 儲存資料
session.setAttribute("username","zs");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
SessionDemo2
@WebServlet("/demo2")
public class SessionDemo2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取資料,從session中
//1. 獲取Session物件
HttpSession session = request.getSession();
System.out.println(session);
//2. 獲取資料
Object username = session.getAttribute("username");
System.out.println(username);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
啟動測試,分別訪問
http://localhost:8080/cookie-demo/demo1
http://localhost:8080/cookie-demo/demo2
通過列印可以得到如下結論:
- 兩個Servlet類中獲取的Session物件是同一個
- 把demo1和demo2請求重新整理多次,控制檯最終列印的結果都是同一個
那麼問題又來了,如果新開一個瀏覽器,訪問demo1或者demo2,列印在控制檯的Session還是同一個物件麼?
注意:在一臺電腦上演示的時候,如果是相同的瀏覽器必須要把瀏覽器全部關掉重新開啟,才算新開的一個瀏覽器。
當然也可以使用不同的瀏覽器進行測試,就不需要把之前的瀏覽器全部關閉。
測試的結果:如果是不同瀏覽器或者重新開啟瀏覽器後,列印的Session就不一樣了。
所以Session實現的也是一次會話中的多次請求之間的資料共享。
那麼最主要的問題就來了,Session是如何保證在一次會話中獲取的Session物件是同一個呢?
(1)demo1在第一次獲取session物件的時候,session物件會有一個唯一的標識,假如是id:10
(2)demo1在session中存入其他資料並處理完成所有業務後,需要通過Tomcat伺服器響應結果給瀏覽器
(3)Tomcat伺服器發現業務處理中使用了session物件,就會把session的唯一標識id:10
當做一個cookie,新增Set-Cookie:JESSIONID=10
到響應頭中,並響應給瀏覽器
(4)瀏覽器接收到響應結果後,會把響應頭中的coookie資料儲存到瀏覽器的記憶體中
(5)瀏覽器在同一會話中訪問demo2的時候,會把cookie中的資料按照cookie: JESSIONID=10
的格式新增到請求頭中併發送給伺服器Tomcat
(6)demo2獲取到請求後,從請求頭中就讀取cookie中的JSESSIONID值為10,然後就會到伺服器記憶體中尋找id:10
的session物件,如果找到了,就直接返回該物件,如果沒有則新建立一個session物件
(7)關閉開啟瀏覽器後,因為瀏覽器的cookie已被銷燬,所以就沒有JESSIONID的資料,服務端獲取到的session就是一個全新的session物件
至此,Session是基於Cookie來實現的
這就話,我們就解釋完了,接下來通過例項來演示下:
(1)使用chrome瀏覽器訪問http://localhost:8080/cookie-demo/demo1
,開啟開發者模式(F12或Ctrl+Shift+I),檢視響應頭(Response Headers)資料:
(2)使用chrome瀏覽器再次訪問http://localhost:8080/cookie-demo/demo2
,檢視請求頭(Request Headers)資料:
小結
介紹完Session的原理,我們只需要記住
- Session是基於Cookie來實現的
3.3 Session的使用細節
這節我們會主要講解兩個知識,第一個是Session的鈍化和活化,第二個是Session的銷燬,首先來學習什麼是Session的鈍化和活化?
3.3.1 Session鈍化與活化
首先需要大家思考的問題是:
- 伺服器重啟後,Session中的資料是否還在?
要想回答這個問題,我們可以先看下下面這幅圖,
(1)伺服器端AServlet和BServlet共用的session物件應該是儲存在伺服器的記憶體中
(2)伺服器重新啟動後,記憶體中的資料應該是已經被釋放,物件也應該都銷燬了
所以session資料應該也已經不存在了。但是如果session不存在會引發什麼問題呢?
舉個例子說明下,
(1)使用者把需要購買的商品新增到購物車,因為要實現同一個會話多次請求資料共享,所以假設把資料存入Session物件中
(2)使用者正要付錢的時候接到一個電話,付錢的動作就擱淺了
(3)正在使用者打電話的時候,購物網站因為某些原因需要重啟
(4)重啟後session資料被銷燬,購物車中的商品資訊也就會隨之而消失
(5)使用者想再次發起支付,就會出為問題
所以說對於session的資料,我們應該做到就算伺服器重啟了,也應該能把資料儲存下來才對。
分析了這麼多,那麼Tomcat伺服器在重啟的時候,session資料到底會不會儲存以及是如何儲存的,我們可以通過實際案例來演示下:
注意:這裡所說的關閉和啟動應該要確保是正常的關閉和啟動。
那如何才是正常關閉Tomcat伺服器呢?
需要使用命令列的方式來啟動和停止Tomcat伺服器:
啟動:進入到專案pom.xml所在目錄,執行tomcat7:run
停止:在啟動的命令列介面,輸入ctrl+c
有了上述兩個正常啟動和關閉的方式後,接下來的測試流程是:
(1)先啟動Tomcat伺服器
(2)訪問http://localhost:8080/cookie-demo/demo1
將資料存入session中
(3)正確停止Tomcat伺服器
(4)再次重新啟動Tomcat伺服器
(5)訪問http://localhost:8080/cookie-demo/demo2
檢視是否能獲取到session中的資料
經過測試,會發現只要伺服器是正常關閉和啟動,session中的資料是可以被儲存下來的。
那麼Tomcat伺服器到底是如何做到的呢?
具體的原因就是:Session的鈍化和活化:
-
鈍化:在伺服器正常關閉後,Tomcat會自動將Session資料寫入硬碟的檔案中
-
鈍化的資料路徑為:
專案目錄\target\tomcat\work\Tomcat\localhost\專案名稱\SESSIONS.ser
-
-
活化:再次啟動伺服器後,從檔案中載入資料到Session中
- 資料載入到Session中後,路徑中的
SESSIONS.ser
檔案會被刪除掉
- 資料載入到Session中後,路徑中的
對於上述的整個過程,大家只需要瞭解下即可。因為所有的過程都是Tomcat自己完成的,不需要我們參與。
小結
Session的鈍化和活化介紹完後,需要我們注意的是:
-
session資料儲存在服務端,伺服器重啟後,session資料會被儲存
-
瀏覽器被關閉啟動後,重新建立的連線就已經是一個全新的會話,獲取的session資料也是一個新的物件
-
session的資料要想共享,瀏覽器不能關閉,所以session資料不能長期儲存資料
-
cookie是儲存在客戶端,是可以長期儲存
3.3.2 Session銷燬
session的銷燬會有兩種方式:
-
預設情況下,無操作,30分鐘自動銷燬
-
對於這個失效時間,是可以通過配置進行修改的
-
在專案的web.xml中配置
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <session-config> <session-timeout>100</session-timeout> </session-config> </web-app>
-
如果沒有配置,預設是30分鐘,預設值是在Tomcat的web.xml配置檔案中寫死的
-
-
-
呼叫Session物件的invalidate()進行銷燬
-
在SessionDemo2類中新增session銷燬的方法
@WebServlet("/demo2") public class SessionDemo2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲取資料,從session中 //1. 獲取Session物件 HttpSession session = request.getSession(); System.out.println(session); // 銷燬 session.invalidate(); //2. 獲取資料 Object username = session.getAttribute("username"); System.out.println(username); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
-
啟動訪問測試,先訪問demo1將資料存入到session,再次訪問demo2從session中獲取資料
-
該銷燬方法一般會在使用者退出的時候,需要將session銷燬掉。
-
Cookie和Session小結
- Cookie 和 Session 都是來完成一次會話內多次請求間資料共享的。
所需兩個物件放在一塊,就需要思考:
Cookie和Session的區別是什麼?
Cookie和Session的應用場景分別是什麼?
- 區別:
- 儲存位置:Cookie 是將資料儲存在客戶端,Session 將資料儲存在服務端
- 安全性:Cookie不安全,Session安全
- 資料大小:Cookie最大3KB,Session無大小限制
- 儲存時間:Cookie可以通過setMaxAge()長期儲存,Session預設30分鐘
- 伺服器效能:Cookie不佔伺服器資源,Session佔用伺服器資源
- 應用場景:
- 購物車:使用Cookie來儲存
- 以登入使用者的名稱展示:使用Session來儲存
- 記住我功能:使用Cookie來儲存
- 驗證碼:使用session來儲存
- 結論
- Cookie是用來保證使用者在未登入情況下的身份識別
- Session是用來儲存使用者登入後的資料
介紹完Cookie和Session以後,具體用哪個還是需要根據具體的業務進行具體分析。
4,使用者登入註冊案例
4.1 需求分析
需求說明:
-
完成使用者登入功能,如果使用者勾選“記住使用者” ,則下次訪問登入頁面自動填充使用者名稱密碼
-
完成註冊功能,並實現驗證碼功能
4.2 使用者登入功能
- 需求:
- 使用者登入成功後,跳轉到列表頁面,並在頁面上展示當前登入的使用者名稱稱
- 使用者登入失敗後,跳轉回登入頁面,並在頁面上展示對應的錯誤資訊
- 實現流程分析
(1)前端通過表單傳送請求和資料給Web層的LoginServlet
(2)在LoginServlet中接收請求和資料[使用者名稱和密碼]
(3)LoginServlet接收到請求和資料後,呼叫Service層完成根據使用者名稱和密碼查詢使用者物件
(4)在Service層需要編寫UserService類,在類中實現login方法,方法中呼叫Dao層的UserMapper
(5)在UserMapper介面中,宣告一個根據使用者名稱和密碼查詢使用者資訊的方法
(6)Dao層把資料查詢出來以後,將返回資料封裝到User物件,將物件交給Service層
(7)Service層將資料返回給Web層
(8)Web層獲取到User物件後,判斷User物件,如果為Null,則將錯誤資訊響應給登入頁面,如果不為Null,則跳轉到列表頁面,並把當前登入使用者的資訊存入Session攜帶到列表頁面。
- 具體實現
(1)完成Dao層的程式碼編寫
(1.1)將04-資料\1. 登入註冊案例\2. MyBatis環境\UserMapper.java
放到com.itheima.mapper`包下:
public interface UserMapper {
/**
* 根據使用者名稱和密碼查詢使用者物件
* @param username
* @param password
* @return
*/
@Select("select * from tb_user where username = #{username} and password = #{password}")
User select(@Param("username") String username,@Param("password") String password);
/**
* 根據使用者名稱查詢使用者物件
* @param username
* @return
*/
@Select("select * from tb_user where username = #{username}")
User selectByUsername(String username);
/**
* 新增使用者
* @param user
*/
@Select("insert into tb_user values(null,#{username},#{password})")
void add(User user);
}
(1.2)將04-資料\1. 登入註冊案例\2. MyBatis環境\User.java
放到com.itheima.pojo
包下:
public class User {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
(1.3)將04-資料\1. 登入註冊案例\2. MyBatis環境\UserMapper.xml
放入到resources/com/itheima/mapper`目錄下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.UserMapper">
</mapper>
(2)完成Service層的程式碼編寫
(2.1)在com.itheima.service
包下,建立UserService類
public class UserService {
//1.使用工具類獲取SqlSessionFactory
SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 登入方法
* @param username
* @param password
* @return
*/
public User login(String username,String password){
//2. 獲取SqlSession
SqlSession sqlSession = factory.openSession();
//3. 獲取UserMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//4. 呼叫方法
User user = mapper.select(username, password);
//釋放資源
sqlSession.close();
return user;
}
}
(3)完成頁面和Web層的程式碼編寫
(3.1)將04-資料\1. 登入註冊案例\1. 靜態頁面
拷貝到專案的webapp
目錄下:
(3.2)將login.html內容修改成login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand-demo/loginServlet" method="post" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">使用者名稱或密碼不正確</div>
<p>Username:<input id="username" name="username" type="text"></p>
<p>Password:<input id="password" name="password" type="password"></p>
<p>Remember:<input id="remember" name="remember" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">沒有賬號?</a>
</div>
</form>
</div>
</body>
</html>
(3.3)建立LoginServlet類
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 獲取使用者名稱和密碼
String username = request.getParameter("username");
String password = request.getParameter("password");
//2. 呼叫service查詢
User user = service.login(username, password);
//3. 判斷
if(user != null){
//登入成功,跳轉到查詢所有的BrandServlet
//將登陸成功後的user物件,儲存到session
HttpSession session = request.getSession();
session.setAttribute("user",user);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/selectAllServlet");
}else {
// 登入失敗,
// 儲存錯誤資訊到request
request.setAttribute("login_msg","使用者名稱或密碼錯誤");
// 跳轉到login.jsp
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3.4)在brand.jsp中標籤下新增歡迎當前使用者的提示資訊:
<h1>${user.username},歡迎您</h1>
(3.5) 修改login.jsp,將錯誤資訊使用EL表示式來獲取
修改前內容:<div id="errorMsg">使用者名稱或密碼不正確</div>
修改後內容: <div id="errorMsg">${login_msg}</div>
(4)啟動,訪問測試
(4.1) 進入登入頁面,輸入錯誤的使用者名稱或密碼
(4.2)輸入正確的使用者和密碼資訊
小結
- 在LoginServlet中,將登入成功的使用者資料存入session中,方法在列表頁面中獲取當前登入使用者資訊進行展示
- 在LoginServlet中,將登入失敗的錯誤資訊存入到request中,如果存入到session中就會出現這次會話的所有請求都有登入失敗的錯誤資訊,這個是不需要的,所以不用存入到session中
4.3 記住我-設定Cookie
- 需求:
如果使用者勾選“記住使用者” ,則下次訪問登陸頁面自動填充使用者名稱密碼。這樣可以提升使用者的體驗。
對應上面這個需求,最大的問題就是: 如何自動填充使用者名稱和密碼?
- 實現流程分析
因為記住我
功能要實現的效果是,就算使用者把瀏覽器關閉過幾天再來訪問也能自動填充,所以需要將登陸資訊存入一個可以長久儲存,並且能夠在瀏覽器關閉重新啟動後依然有效的地方,就是我們前面講的Cookie,所以:
-
將使用者名稱和密碼寫入Cookie中,並且持久化儲存Cookie,下次訪問瀏覽器會自動攜帶Cookie
-
在頁面獲取Cookie資料後,設定到使用者名稱和密碼框中
-
何時寫入Cookie?
- 使用者必須登陸成功後才需要寫
- 使用者必須在登入頁面勾選了
記住我
的複選框
(1)前端需要在傳送請求和資料的時候,多攜帶一個使用者是否勾選Remember
的資料
(2)LoginServlet獲取到資料後,呼叫Service完成使用者名稱和密碼的判定
(3)登入成功,並且使用者在前端勾選了記住我
,需要往Cookie中寫入使用者名稱和密碼的資料,並設定Cookie存活時間
(4)設定成功後,將資料響應給前端
- 具體實現
(1)在login.jsp為複選框設定值
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand-demo/loginServlet" method="post" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
<p>Username:<input id="username" name="username" type="text"></p>
<p>Password:<input id="password" name="password" type="password"></p>
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">沒有賬號?</a>
</div>
</form>
</div>
</body>
</html>
(2)在LoginServlet獲取複選框的值並在登入成功後進行設定Cookie
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 獲取使用者名稱和密碼
String username = request.getParameter("username");
String password = request.getParameter("password");
//獲取複選框資料
String remember = request.getParameter("remember");
//2. 呼叫service查詢
User user = service.login(username, password);
//3. 判斷
if(user != null){
//登入成功,跳轉到查詢所有的BrandServlet
//判斷使用者是否勾選記住我,字串寫前面是為了避免出現空指標異常
if("1".equals(remember)){
//勾選了,傳送Cookie
//1. 建立Cookie物件
Cookie c_username = new Cookie("username",username);
Cookie c_password = new Cookie("password",password);
// 設定Cookie的存活時間
c_username.setMaxAge( 60 * 60 * 24 * 7);
c_password.setMaxAge( 60 * 60 * 24 * 7);
//2. 傳送
response.addCookie(c_username);
response.addCookie(c_password);
}
//將登陸成功後的user物件,儲存到session
HttpSession session = request.getSession();
session.setAttribute("user",user);
String contextPath = request.getContextPath();
response.sendRedirect(contextPath+"/selectAllServlet");
}else {
// 登入失敗,
// 儲存錯誤資訊到request
request.setAttribute("login_msg","使用者名稱或密碼錯誤");
// 跳轉到login.jsp
request.getRequestDispatcher("/login.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3)啟動訪問測試,
只有當前使用者名稱和密碼輸入正確,並且勾選了Remeber的複選框,在響應頭中才可以看得cookie的相關資料
4.4 記住我-獲取Cookie
- 需求
登入成功並勾選了Remeber後,後端返回給前端的Cookie資料就已經儲存好了,接下來就需要在頁面獲取Cookie中的資料,並把資料設定到登入頁面的使用者名稱和密碼框中。
如何在頁面直接獲取Cookie中的值呢?
- 實現流程分析
在頁面可以使用EL表示式,${cookie.key.value}
key:指的是儲存在cookie中的鍵名稱
(1)在login.jsp使用者名稱的表單輸入框使用value值給表單元素新增預設值,value可以使用${cookie.username.value}
(2)在login.jsp密碼的表單輸入框使用value值給表單元素新增預設值,value可以使用${cookie.password.value}
- 具體實現
(1)修改login.jsp頁面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
<link href="css/login.css" rel="stylesheet">
</head>
<body>
<div id="loginDiv" style="height: 350px">
<form action="/brand-demo/loginServlet" method="post" id="form">
<h1 id="loginMsg">LOGIN IN</h1>
<div id="errorMsg">${login_msg}</div>
<p>Username:<input id="username" name="username" value="${cookie.username.value}" type="text"></p>
<p>Password:<input id="password" name="password" value="${cookie.password.value}" type="password"></p>
<p>Remember:<input id="remember" name="remember" value="1" type="checkbox"></p>
<div id="subDiv">
<input type="submit" class="button" value="login up">
<input type="reset" class="button" value="reset">
<a href="register.html">沒有賬號?</a>
</div>
</form>
</div>
</body>
</html>
- 訪問測試,重新訪問登入頁面,就可以看得使用者和密碼已經被填充。
4.5 使用者註冊功能
- 需求
- 註冊功能:儲存使用者資訊到資料庫
- 驗證碼功能
- 展示驗證碼:展示驗證碼圖片,並可以點選切換
- 校驗驗證碼:驗證碼填寫不正確,則註冊失敗
- 實現流程分析
(1)前端通過表單傳送請求和資料給Web層的RegisterServlet
(2)在RegisterServlet中接收請求和資料[使用者名稱和密碼]
(3)RegisterServlet接收到請求和資料後,呼叫Service層完成使用者資訊的儲存
(4)在Service層需要編寫UserService類,在類中實現register方法,需要判斷使用者是否已經存在,如果不存在,則完成使用者資料的儲存
(5)在UserMapper介面中,宣告兩個方法,一個是根據使用者名稱查詢使用者資訊方法,另一個是儲存使用者資訊方法
(6)在UserService類中儲存成功則返回true,失敗則返回false,將資料返回給Web層
(7)Web層獲取到結果後,如果返回的是true,則提示註冊成功
,並轉發到登入頁面,如果返回false則提示使用者名稱已存在
並轉發到註冊頁面
- 具體實現
(1)Dao層程式碼參考資料中的內容完成
(2)編寫Service層程式碼
public class UserService {
//1.使用工具類獲取SqlSessionFactory
SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
/**
* 註冊方法
* @return
*/
public boolean register(User user){
//2. 獲取SqlSession
SqlSession sqlSession = factory.openSession();
//3. 獲取UserMapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//4. 判斷使用者名稱是否存在
User u = mapper.selectByUsername(user.getUsername());
if(u == null){
// 使用者名稱不存在,註冊
mapper.add(user);
sqlSession.commit();
}
sqlSession.close();
return u == null;
}
}
(3)完成頁面和Web層的程式碼編寫
(3.1)將register.html內容修改成register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>歡迎註冊</title>
<link href="css/register.css" rel="stylesheet">
</head>
<body>
<div class="form-div">
<div class="reg-content">
<h1>歡迎註冊</h1>
<span>已有帳號?</span> <a href="login.html">登入</a>
</div>
<form id="reg-form" action="/brand-demo/registerServlet" method="post">
<table>
<tr>
<td>使用者名稱</td>
<td class="inputs">
<input name="username" type="text" id="username">
<br>
<span id="username_err" class="err_msg" style="display:none">使用者名稱不太受歡迎</span>
</td>
</tr>
<tr>
<td>密碼</td>
<td class="inputs">
<input name="password" type="password" id="password">
<br>
<span id="password_err" class="err_msg" style="display: none">密碼格式有誤</span>
</td>
</tr>
<tr>
<td>驗證碼</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img src="imgs/a.jpg">
<a href="#" id="changeImg" >看不清?</a>
</td>
</tr>
</table>
<div class="buttons">
<input value="注 冊" type="submit" id="reg_btn">
</div>
<br class="clear">
</form>
</div>
</body>
</html>
(3.2)編寫RegisterServlet
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 獲取使用者名稱和密碼資料
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = new User();
user.setUsername(username);
user.setPassword(password);
//2. 呼叫service 註冊
boolean flag = service.register(user);
//3. 判斷註冊成功與否
if(flag){
//註冊功能,跳轉登陸頁面
request.setAttribute("register_msg","註冊成功,請登入");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}else {
//註冊失敗,跳轉到註冊頁面
request.setAttribute("register_msg","使用者名稱已存在");
request.getRequestDispatcher("/register.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(3.3)需要在頁面上展示後臺返回的錯誤資訊,需要修改register.jsp
修改前:<span id="username_err" class="err_msg" style="display:none">使用者名稱不太受歡迎</span>
修改後:<span id="username_err" class="err_msg">${register_msg}</span>
(3.4)如果註冊成功,需要把成功資訊展示在登入頁面,所以也需要修改login.jsp
修改前:<div id="errorMsg">${login_msg}</div>
修改後:<div id="errorMsg">${login_msg} ${register_msg}</div>
(3.5)修改login.jsp,將註冊跳轉地址修改為register.jsp
修改前:<a href="register.html">沒有賬號?</a>
修改後: <a href="register.jsp">沒有賬號?</a>
(3.6)啟動測試,
如果是註冊的使用者資訊已經存在:
如果註冊的使用者資訊不存在,註冊成功:
4.6 驗證碼-展示
- 需求分析
展示驗證碼:展示驗證碼圖片,並可以點選切換
驗證碼的生成是通過工具類來實現的,具體的工具類參考
04-資料\1. 登入註冊案例\CheckCodeUtil.java
在該工具類中編寫main方法進行測試:
public static void main(String[] args) throws IOException {
//生成驗證碼的圖片位置
OutputStream fos = new FileOutputStream("d://a.jpg");
//checkCode為最終驗證碼的資料
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4);
System.out.println(checkCode);
}
生成完驗證碼以後,我們就可以知曉:
- 驗證碼就是使用Java程式碼生成的一張圖片
- 驗證碼的作用:防止機器自動註冊,攻擊伺服器
- 實現流程分析
(1)前端傳送請求給CheckCodeServlet
(2)CheckCodeServlet接收到請求後,生成驗證碼圖片,將圖片用Reponse物件的輸出流寫回到前端
思考:如何將圖片寫回到前端瀏覽器呢?
(1)Java中已經有工具類生成驗證碼圖片,測試類中只是把圖片生成到磁碟上
(2)生成磁碟的過程中使用的是OutputStream流,如何把這個圖片生成在頁面呢?
(3)前面在將Reponse物件的時候,它有一個方法可以獲取其位元組輸出流,getOutputStream()
(4)綜上所述,我們可以把寫往磁碟的流物件更好成Response的位元組流,即可完成圖片響應給前端
- 具體實現
(1)修改Register.jsp頁面,將驗證碼的圖片從後臺獲取
<tr>
<td>驗證碼</td>
<td class="inputs">
<input name="checkCode" type="text" id="checkCode">
<img id="checkCodeImg" src="/brand-demo/checkCodeServlet">
<a href="#" id="changeImg" >看不清?</a>
</td>
</tr>
<script>
document.getElementById("changeImg").onclick = function () {
//路徑後面新增時間戳的目的是避免瀏覽器進行快取靜態資源
document.getElementById("checkCodeImg").src = "/brand-demo/checkCodeServlet?"+new Date().getMilliseconds();
}
</script>
(2)編寫CheckCodeServlet類,用來接收請求生成驗證碼
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成驗證碼
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
4.7驗證碼-校驗
- 需求
- 判斷程式生成的驗證碼 和 使用者輸入的驗證碼 是否一樣,如果不一樣,則阻止註冊
- 驗證碼圖片訪問和提交登錄檔單是兩次請求,所以要將程式生成的驗證碼存入Session中
思考:為什麼要把驗證碼資料存入到Session中呢?
- 生成驗證碼和校驗驗證碼是兩次請求,此處就需要在一個會話的兩次請求之間共享資料
- 驗證碼屬於安全資料類的,所以我們選中Session來儲存驗證碼資料。
- 實現流程分析
(1)在CheckCodeServlet中生成驗證碼的時候,將驗證碼資料存入Session物件
(2)前端將驗證碼和註冊資料提交到後臺,交給RegisterServlet類
(3)RegisterServlet類接收到請求和資料後,其中就有驗證碼,和Session中的驗證碼進行對比
(4)如果一致,則完成註冊,如果不一致,則提示錯誤資訊
- 具體實現
(1)修改CheckCodeServlet類,將驗證碼存入Session物件
@WebServlet("/checkCodeServlet")
public class CheckCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 生成驗證碼
ServletOutputStream os = response.getOutputStream();
String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
// 存入Session
HttpSession session = request.getSession();
session.setAttribute("checkCodeGen",checkCode);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
(2)在RegisterServlet中,獲取頁面的和session物件中的驗證碼,進行對比
package com.itheima.web;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
@WebServlet("/registerServlet")
public class RegisterServlet extends HttpServlet {
private UserService service = new UserService();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1. 獲取使用者名稱和密碼資料
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = new User();
user.setUsername(username);
user.setPassword(password);
// 獲取使用者輸入的驗證碼
String checkCode = request.getParameter("checkCode");
// 程式生成的驗證碼,從Session獲取
HttpSession session = request.getSession();
String checkCodeGen = (String) session.getAttribute("checkCodeGen");
// 比對
if(!checkCodeGen.equalsIgnoreCase(checkCode)){
request.setAttribute("register_msg","驗證碼錯誤");
request.getRequestDispatcher("/register.jsp").forward(request,response);
// 不允許註冊
return;
}
//2. 呼叫service 註冊
boolean flag = service.register(user);
//3. 判斷註冊成功與否
if(flag){
//註冊功能,跳轉登陸頁面
request.setAttribute("register_msg","註冊成功,請登入");
request.getRequestDispatcher("/login.jsp").forward(request,response);
}else {
//註冊失敗,跳轉到註冊頁面
request.setAttribute("register_msg","使用者名稱已存在");
request.getRequestDispatcher("/register.jsp").forward(request,response);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
}
至此,使用者的註冊登入功能就已經完成了。
(8)