給大家分享我收藏的幾個(gè)不錯(cuò)的 github 項(xiàng)目,內(nèi)容都還是不錯(cuò)的,如果覺(jué)得有幫助,可以順便給個(gè) star。
- 計(jì)算機(jī)專業(yè)學(xué)生必須要啃的書籍推薦: https://github.com/hello-go-maker/cs-books
- Java 實(shí)戰(zhàn)項(xiàng)目推薦: https://github.com/hello-go-maker/Java-project
- 推薦一些很不錯(cuò)的計(jì)算機(jī)學(xué)習(xí)教程,包括:數(shù)據(jù)結(jié)構(gòu)、算法、計(jì)算機(jī)網(wǎng)絡(luò)、操作系統(tǒng)、Java(spring、springmvc、springboot、springcloud),也包括多個(gè)企業(yè)級(jí)實(shí)戰(zhàn)項(xiàng)目: https://github.com/hello-go-maker/cs-learn-source
- 【Java面試+Java后端技術(shù)學(xué)習(xí)指南】:一份通向理想互聯(lián)網(wǎng)公司的面試指南,包括 Java,技術(shù)面試必備基礎(chǔ)知識(shí)、Leetcode、計(jì)算機(jī)操作系統(tǒng)、計(jì)算機(jī)網(wǎng)絡(luò)、系統(tǒng)設(shè)計(jì)、分布式、數(shù)據(jù)庫(kù)(MySQL、Redis)、Java 項(xiàng)目實(shí)戰(zhàn)等: https://github.com/hello-java-maker/JavaInterview
一、會(huì)話的概念
會(huì)話可簡(jiǎn)單理解為:用戶開(kāi)一個(gè)瀏覽器,點(diǎn)擊多個(gè)超鏈接,訪問(wèn)服務(wù)器多個(gè)web資源,然后關(guān)閉瀏覽器,整個(gè)過(guò)程稱之為一個(gè)會(huì)話。
二、會(huì)話過(guò)程中要解決的一些問(wèn)題
每個(gè)用戶在使用瀏覽器與服務(wù)器進(jìn)行會(huì)話的過(guò)程中,不可避免各自會(huì)產(chǎn)生一些數(shù)據(jù),程序要想辦法為每個(gè)用戶保存這些數(shù)據(jù)。
三、保存會(huì)話數(shù)據(jù)的兩種技術(shù)
1、Cookie
Cookie意為"甜餅",是由W3C組織提出,最早由Netscape社區(qū)發(fā)展的一種機(jī)制。目前Cookie已經(jīng)成為標(biāo)準(zhǔn),所有的主流瀏覽器如IE、Netscape、Firefox、Opera等都支持Cookie。
由于HTTP是一種無(wú)狀態(tài)的協(xié)議,服務(wù)器單從網(wǎng)絡(luò)連接上無(wú)從知道客戶身份。怎么辦呢?就給客戶端們頒發(fā)一個(gè)通行證吧,每人一個(gè),無(wú)論誰(shuí)訪問(wèn)都必須攜帶自己通行證。這樣服務(wù)器就能從通行證上確認(rèn)客戶身份了。這就是Cookie的工作原理。
Cookie實(shí)際上是一小段的文本信息??蛻舳苏?qǐng)求服務(wù)器,如果服務(wù)器需要記錄該用戶狀態(tài),就使用response向客戶端瀏覽器頒發(fā)一個(gè)Cookie??蛻舳藶g覽器會(huì)把Cookie保存起來(lái)。當(dāng)瀏覽器再請(qǐng)求該網(wǎng)站時(shí),瀏覽器把請(qǐng)求的網(wǎng)址連同該Cookie一同提交給服務(wù)器。服務(wù)器檢查該Cookie,以此來(lái)辨認(rèn)用戶狀態(tài)。服務(wù)器還可以根據(jù)需要修改Cookie的內(nèi)容。
2、Session
Session是服務(wù)器端技術(shù),利用這個(gè)技術(shù),服務(wù)器在運(yùn)行時(shí)可以為每一個(gè)用戶的瀏覽器創(chuàng)建一個(gè)其獨(dú)享的session對(duì)象,由于session為用戶瀏覽器獨(dú)享,所以用戶在訪問(wèn)服務(wù)器的web資源時(shí),可以把各自的數(shù)據(jù)放在各自的session中,當(dāng)用戶再去訪問(wèn)服務(wù)器中的其它web資源時(shí),其它web資源再?gòu)挠脩舾髯缘膕ession中取出數(shù)據(jù)為用戶服務(wù)。
四、Cookie類的主要方法
| int getMaxAge() | 返回Cookie過(guò)期之前的最大時(shí)間,以秒計(jì)算。 |
|---|---|
| void setMaxAge(intexpiry) | 以秒計(jì)算,設(shè)置Cookie過(guò)期時(shí)間。 |
| String getName() | 返回Cookie的名字。名字和值是我們始終關(guān)心的兩個(gè)部分,筆者會(huì)在后面詳細(xì)介紹 getName/setName。 |
| void setValue(String newValue) | Cookie創(chuàng)建后設(shè)置一個(gè)新的值。 |
| String getValue() | 返回Cookie的值。筆者也將在后面詳細(xì)介紹getValue/setValue。 |
| void setDomain(String pattern) | 設(shè)置cookie中Cookie適用的域名 |
| String getDomain() | 返回Cookie中Cookie適用的域名. 使用getDomain() 方法可以指示瀏覽器把Cookie返回給同 一域內(nèi)的其他服務(wù)器,而通常Cookie只返回給與發(fā)送它的服務(wù)器名字完全相同的服務(wù)器。注意域名必須以點(diǎn)開(kāi)始(例如.yesky.com) |
| void setPath(String uri) | 指定Cookie適用的路徑。 |
| String getPath() | 返回Cookie適用的路徑。如果不指定路徑,Cookie將返回給當(dāng)前頁(yè)面所在目錄及其子目錄下 的所有頁(yè)面。 |
| void setSecure(boolean flag) | 指出瀏覽器使用的安全協(xié)議,例如HTTPS或SSL。 |
| boolean getSecure() | 如果瀏覽器通過(guò)安全協(xié)議發(fā)送cookies將返回true值,如果瀏覽器使用標(biāo)準(zhǔn)協(xié)議則返回false值。 |
| void setVersion(int v) | Cookie所遵從的協(xié)議版本。 |
| int getVersion() | 返回Cookie所遵從的協(xié)議版本。 |
| void setComment(String purpose) | 設(shè)置cookie中注釋。 |
| String getComment() | 返回Cookie中注釋,如果沒(méi)有注釋的話將返回空值。 |
| Cookie(String name, String value) | 實(shí)例化Cookie對(duì)象,傳入cooke名稱和cookie的值。 |
response接口也中定義了一個(gè)addCookie方法,它用于在其響應(yīng)頭中增加一個(gè)相應(yīng)的Set-Cookie頭字段。 同樣,request接口中也定義了一個(gè)getCookies方法,它用于獲取客戶端提交的Cookie。
五、Cookie使用
1、使用cookie記錄用戶上一次訪問(wèn)的時(shí)間
public class CookieDemo extends HttpServlet{
private static final long serialVersionUID = 5757885987685925915L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設(shè)置服務(wù)器以UTF-8編碼輸出
resp.setCharacterEncoding("UTF-8");
//設(shè)置瀏覽器以UTF-8編碼進(jìn)行接收
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
//獲取Cookie數(shù)組
Cookie[] cookie = req.getCookies();
if(cookie == null){
out.write("這是你的第一次訪問(wèn)!");
} else {
for (Cookie ck : cookie) {
if(ck.getName().equals("cookieName")){
//獲取Cookie里面保存的數(shù)據(jù)
Long time = Long.parseLong(ck.getValue());
Date date = new Date(time);
out.write("上次訪問(wèn)時(shí)間:" + date.toLocaleString());
}
}
}
//創(chuàng)建一個(gè)cookie,cookie的名字是cookieName
Cookie cookies = new Cookie("cookieName", System.currentTimeMillis()+"");
//設(shè)置Cookie的有效期為1天,這樣即使關(guān)閉了瀏覽器,下次再訪問(wèn)時(shí),也依然可以通過(guò)cookie獲取用戶上一次訪問(wèn)的時(shí)間。
cookies.setMaxAge(24*60*60);
//將cookie對(duì)象添加到response對(duì)象中
resp.addCookie(cookies);
}
}
第一次訪問(wèn)時(shí),如下所示:

再次訪問(wèn):

2、刪除Cookie
public class CookieDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
//創(chuàng)建一個(gè)名字為lastAccessTime的cookie
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");
//將cookie的有效期設(shè)置為0,命令瀏覽器刪除該cookie
cookie.setMaxAge(0);
req.addCookie(cookie);
}
}
3、cookie中存/取中文
public class CookieDemo2 extends HttpServlet{
private static final long serialVersionUID = 5757885987685925915L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設(shè)置服務(wù)器以UTF-8編碼輸出
resp.setCharacterEncoding("UTF-8");
//設(shè)置瀏覽器以UTF-8編碼進(jìn)行接收
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
//創(chuàng)建一個(gè)cookie,cookie的名字是cookieName
//存儲(chǔ)中文時(shí),使用URLEncoder類里面的encode(String s, String enc)方法進(jìn)行中文轉(zhuǎn)碼
Cookie cookies = new Cookie("cookieName", URLEncoder.encode("哎喲!不錯(cuò)喲", "UTF-8"));
//將cookie對(duì)象添加到response對(duì)象中
resp.addCookie(cookies);
//獲取Cookie數(shù)組
Cookie[] cookie = req.getCookies();
if(cookie != null){
for (Cookie ck : cookie) {
if(ck.getName().equals("cookieName")){
//獲取Cookie里面保存的數(shù)據(jù)
String text = ck.getValue();
//使用URLDecoder類里面的decode(String s, String enc)進(jìn)行解碼
out.write("存儲(chǔ)的中文數(shù)據(jù):" + URLDecoder.decode(text, "UTF-8"));
}
}
}
}
}
結(jié)果如下:

Cookie注意細(xì)節(jié)
1,一個(gè)Cookie只能標(biāo)識(shí)一種信息,它至少含有一個(gè)標(biāo)識(shí)該信息的名稱(NAME)和設(shè)置值(VALUE)。
2,一個(gè)WEB站點(diǎn)可以給一個(gè)WEB瀏覽器發(fā)送多個(gè)Cookie,一個(gè)WEB瀏覽器也可以存儲(chǔ)多個(gè)WEB站點(diǎn)提供的Cookie。
3,瀏覽器一般只允許存放300個(gè)Cookie,每個(gè)站點(diǎn)最多存放20個(gè)Cookie,每個(gè)Cookie的大小限制為4KB。
4,如果創(chuàng)建了一個(gè)cookie,并將他發(fā)送到瀏覽器,默認(rèn)情況下它是一個(gè)會(huì)話級(jí)別的cookie(即存儲(chǔ)在瀏覽器的內(nèi)存中),用戶退出瀏覽器之后即被刪除。若希望瀏覽器將該cookie存儲(chǔ)在磁盤上,則需要使用maxAge,并給出一個(gè)以秒為單位的時(shí)間。將最大時(shí)效設(shè)為0則是命令瀏覽器刪除該cookie。
六、Session簡(jiǎn)單介紹
在WEB開(kāi)發(fā)中,服務(wù)器可以為每個(gè)用戶瀏覽器創(chuàng)建一個(gè)會(huì)話對(duì)象(session對(duì)象),注意:一個(gè)瀏覽器獨(dú)占一個(gè)session對(duì)象(默認(rèn)情況下)。因此,在需要保存用戶數(shù)據(jù)時(shí),服務(wù)器程序可以把用戶數(shù)據(jù)寫到用戶瀏覽器獨(dú)占的session中,當(dāng)用戶使用瀏覽器訪問(wèn)其它程序時(shí),其它程序可以從用戶的session中取出該用戶的數(shù)據(jù),為用戶服務(wù)。
Session和Cookie的主要區(qū)別
1,Cookie是把用戶的數(shù)據(jù)寫給用戶的瀏覽器。
2,Session技術(shù)把用戶的數(shù)據(jù)寫到用戶獨(dú)占的session中。
3,Session對(duì)象由服務(wù)器創(chuàng)建,開(kāi)發(fā)人員可以調(diào)用request對(duì)象的getSession方法得到session對(duì)象。
七、Session基礎(chǔ)知識(shí)
Session是服務(wù)器端技術(shù),利用這個(gè)技術(shù),服務(wù)器在運(yùn)行時(shí)可以為每一個(gè)用戶的瀏覽器創(chuàng)建一個(gè)其獨(dú)享的session對(duì)象,由于session為用戶瀏覽器獨(dú)享,所以用戶在訪問(wèn)服務(wù)器的web資源時(shí),可以把各自的數(shù)據(jù)放在各自的session中,當(dāng)用戶再去訪問(wèn)服務(wù)器中的其它web資源時(shí),其它web資源再?gòu)挠脩舾髯缘膕ession中取出數(shù)據(jù)為用戶服務(wù)。
當(dāng)用戶打開(kāi)瀏覽器,訪問(wèn)某個(gè)網(wǎng)站操作session時(shí),服務(wù)器就會(huì)在服務(wù)器的內(nèi)存為該瀏覽器分配一個(gè)session對(duì)象,該session對(duì)象被這個(gè)瀏覽器獨(dú)占。
這個(gè)session對(duì)象也可以看做是一個(gè)容器,session默認(rèn)存在時(shí)間為30min,你可以修改。
1、Session可以用來(lái)做什么
1、網(wǎng)上商城中的購(gòu)物車
2、保存登錄用戶的信息
3、將某些數(shù)據(jù)放入到Session中,供同一用戶的各個(gè)頁(yè)面使用
4、防止用戶非法登錄到某個(gè)頁(yè)面。
2、Session基本使用
| request.getSession() | 返回這個(gè)request綁定的session對(duì)象,如果沒(méi)有,則創(chuàng)建一個(gè) |
|---|---|
| request.getSession(boolean create) | 返回這個(gè)request綁定的session對(duì)象,如果沒(méi)有,則根據(jù)create的值決定是否創(chuàng)建一個(gè) |
| session.setAttribute(String name,Object val) | 向session中添加屬性 |
| session.getAttribute(String name) | 從session中得到某個(gè)屬性 |
| session.removeAttribute(String name) | 從session中刪除某個(gè)屬性 |
| session.setMaxInactiveInterval() | 設(shè)置Session的生命周期(單位秒),Session的生命周期默認(rèn)是30min |
| session.invalidate() | 清除所有session |
| session.removeAttribute(String name) | 刪除指定名稱的session |
Servlet1:
public class Servlet1 extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取session
HttpSession session = req.getSession();
//將數(shù)據(jù)存儲(chǔ)到session中
session.setAttribute("name", "Zender");
}
}
Servlet2:
public class Servlet2 extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取session
HttpSession session = req.getSession();
//獲取name
String name = (String) session.getAttribute("name");
PrintWriter out = resp.getWriter();
out.print("name:" + name);
}
}
同一瀏覽器訪問(wèn)Servlet1,再訪問(wèn)Servlet2,結(jié)果如下:

不同瀏覽器訪問(wèn)Servlet1,再訪問(wèn)Servlet2,結(jié)果如下:

可以看到這時(shí)候name是null,也就是沒(méi)有從session對(duì)象中取出值,因?yàn)?60瀏覽器并沒(méi)有運(yùn)行Servlet1來(lái)創(chuàng)建Session對(duì)象,上面的session對(duì)象是Chrome瀏覽器獨(dú)占的。
3、Session生命周期
Session中的屬性的默認(rèn)生命周期是30min,這個(gè)默認(rèn)時(shí)間可以通過(guò)修改web.xml文件來(lái)修改
1,在Tomcat根目錄\conf\web.xml文件中修改
<session-config>
<session-timeout>30</session-timeout>
</session-config>
2,如果只需要對(duì)某一個(gè)web應(yīng)用設(shè)置,則只需要修改對(duì)應(yīng)web應(yīng)用的web.xml文件。在這個(gè)web.xml文件中添加如上的代碼:
<session-config>
<session-timeout>10</session-timeout>
</session-config>
除了設(shè)置默認(rèn)生命周期之外,最重要的是在程序中設(shè)置,調(diào)用setMaxInacttiveInterval(int interval),這里的interval是以秒為單位的,而且這個(gè)方法設(shè)置的是發(fā)呆時(shí)間,比如你設(shè)置的是60秒,那么在這60秒之內(nèi)如果你沒(méi)有操作過(guò)session,它就會(huì)自動(dòng)刪除,如果你操作過(guò),不管是設(shè)置屬性還是讀取屬性,它都會(huì)從頭開(kāi)始計(jì)時(shí)。
session.setMaxInactiveInterval(60);
八、Session實(shí)現(xiàn)原理
服務(wù)器是如何實(shí)現(xiàn)一個(gè)session為一個(gè)用戶瀏覽器服務(wù)的?

1,瀏覽器A先訪問(wèn)Servlet1,這時(shí)候它創(chuàng)建了一個(gè)Session,ID號(hào)為110,然后Servlet1將這個(gè)ID號(hào)以Cookie的方式返回給瀏覽器A。
2,瀏覽器A繼續(xù)訪問(wèn)Servlet2,那么這個(gè)請(qǐng)求會(huì)帶上Cookie值: JSESSIONID=110,然后服務(wù)器根據(jù)瀏覽器A傳遞過(guò)來(lái)的ID號(hào)找到內(nèi)存中的這個(gè)Session。
3,瀏覽器B來(lái)訪問(wèn)Servlet1了,它的請(qǐng)求并沒(méi)有帶上 JSESSIONID這個(gè)Cookie值,由于它也要使用Session,所以服務(wù)器會(huì)新創(chuàng)建一個(gè)Session,ID號(hào)為111。
4,瀏覽器B繼續(xù)訪問(wèn)Servlet2,那么這個(gè)請(qǐng)求會(huì)帶上Cookie值: JSESSIONID=111,然后服務(wù)器根據(jù)瀏覽器B傳遞過(guò)來(lái)的ID號(hào)找到內(nèi)存中的這個(gè)Session。
例如:
Servlet1:
public class Servlet1 extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取session
HttpSession session = req.getSession();
//將數(shù)據(jù)存儲(chǔ)到session中
session.setAttribute("name", "Zender");
PrintWriter out = resp.getWriter();
out.print("create Session OK");
}
}
Servlet2:
public class Servlet2 extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取session
HttpSession session = req.getSession();
//獲取name
String name = (String) session.getAttribute("name");
PrintWriter out = resp.getWriter();
out.println("Session ID:" + session.getId());
out.print("name:" + name);
}
}
第一次訪問(wèn)Servlet1時(shí),服務(wù)器會(huì)創(chuàng)建一個(gè)新的sesion,并且把session的Id以cookie的形式發(fā)送給客戶端瀏覽器,如下圖所示:

可以看到,Request Headers中并沒(méi)有Cookie的信息,而Response Headers中有這么一句話:
Set-Cookie:
JSESSIONID=05A94199DDC64311563740CC2C78D656; Path=/CookieAndSession/; HttpOnly
說(shuō)明這個(gè)時(shí)候服務(wù)器向客戶端通過(guò)Cookie傳遞回了 JSESSIONID這個(gè)屬性。
然后訪問(wèn)Servlet2,如下圖所示:

可以看到Response Headers中沒(méi)有出現(xiàn)Set-Cookie這個(gè)頭,而Request Headers中帶上了Cookie這個(gè)頭:
Cookie:
JSESSIONID=05A94199DDC64311563740CC2C78D656
而這個(gè)頭中正包含 JSESSIONID,并且它的值也就是我們之前Set-Cookie中 JSESSIONID的值。
這就證明了我們之前圖解的Session的原理,也就是服務(wù)器能夠?yàn)椴煌臑g覽器區(qū)分不同的Session的機(jī)制。
九、Session的簡(jiǎn)單應(yīng)用
1,用戶登錄時(shí)候驗(yàn)證驗(yàn)證碼
Index.jsp:
<head>
<base href="<%=basePath%>" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>表單提交</title>
<link href="css/bootstrap.css" rel="stylesheet">
<script src="js/jquery-3.2.1.js"></script>
<script src="js/bootstrap.js"></script>
</head>
<body>
<form class="form-horizontal" action="<%=request.getContextPath()%>/CodeServlet.html" role="form" method="post">
<div class="form-group">
<label for="firstname" class="col-sm-1 control-label">驗(yàn)證碼</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="idCodeNum"
placeholder="請(qǐng)輸入驗(yàn)證碼">
</div>
<img src="idCode" onclick="this.src+=''" style="cursor:pointer;" width="115" height="30" title="看不清?換一個(gè)">
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-3">
<button type="submit" class="btn btn-default">提交</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</form>
</body>
</html>
CodeServlet:
public class CodeServlet extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//獲取session
HttpSession session = req.getSession();
//獲取存儲(chǔ)的驗(yàn)證碼
String idCodeText = (String) session.getAttribute("idCode");
System.out.println("服務(wù)端驗(yàn)證碼:" + idCodeText);
String idCodeNum = req.getParameter("idCodeNum");
System.out.println("輸入的驗(yàn)證碼:" + idCodeNum);
//設(shè)置瀏覽器以UTF-8編碼進(jìn)行接收
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
if(idCodeText.equalsIgnoreCase(idCodeNum)){
out.println("驗(yàn)證成功!");
} else {
out.println("驗(yàn)證碼錯(cuò)誤!");
}
}
}
Web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>CookieAndSession</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>CodeServlet</servlet-name>
<servlet-class>com.zender.session.CodeServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CodeServlet</servlet-name>
<url-pattern>/CodeServlet.html</url-pattern>
</servlet-mapping>
<!-- 生成驗(yàn)證碼的Servlet -->
<servlet>
<servlet-name>ValidateCode</servlet-name>
<servlet-class>org.jelly.image.ValidateCode</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ValidateCode</servlet-name>
<url-pattern>/idCode</url-pattern>
</servlet-mapping>
</web-app>
這里使用了jelly-core-1.7.0.GA.jar來(lái)生成了驗(yàn)證碼,具體使用方式請(qǐng)點(diǎn)擊:jelly-core-1.7.0.GA.jar(http://www.blogjava.net/fancydeepin/archive/2014/08/03/jelly_image.html)
訪問(wèn)http://localhost:8081/CookieAndSession/index.jsp輸入驗(yàn)證碼,點(diǎn)擊提交:

輸入正確驗(yàn)證碼,頁(yè)面響應(yīng)結(jié)果:

輸入錯(cuò)誤驗(yàn)證碼,頁(yè)面響應(yīng)結(jié)果:

輸入正確驗(yàn)證碼,后臺(tái)結(jié)果:

輸入錯(cuò)誤驗(yàn)證碼,后臺(tái)結(jié)果:

2,實(shí)現(xiàn)簡(jiǎn)易購(gòu)物車
模擬一個(gè)數(shù)據(jù)庫(kù):
public class DB {
private static HashMap<String, Book> hm = null;
private DB(){
}
static{
hm = new LinkedHashMap<String, Book>();
Book book1 = new Book(1, "Java基礎(chǔ)");
Book book2 = new Book(2, "Oracle數(shù)據(jù)庫(kù)");
Book book3 = new Book(3, "C語(yǔ)言");
Book book4 = new Book(4, "Python核心教程");
Book book5 = new Book(5, "Web技術(shù)");
hm.put(book1.getId() + "" , book1);
hm.put(book2.getId() + "" , book2);
hm.put(book3.getId() + "" , book3);
hm.put(book4.getId() + "" , book4);
hm.put(book5.getId() + "" , book5);
}
/**
* 得到數(shù)據(jù)庫(kù)中所有的書
* @return
*/
public static HashMap<String, Book> getBooks(){
return hm;
}
/**
* 根據(jù)ID得到書
* @param id
* @return
*/
public static Book getBookById(String id){
if(hm.containsKey(id)){
return hm.get(id);
}
return null;
}
}
BuyBookServlet這個(gè)Servlet用于購(gòu)買圖書:
//顯示購(gòu)買的圖書
public class ShowMyCartServlet extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
List<Book> list = (ArrayList<Book>) session.getAttribute("books");
if(list==null || list.size()==0){
out.write("對(duì)不起,您還沒(méi)有購(gòu)買任何商品!!");
return;
}
//顯示用戶買過(guò)的商品
out.write("您買過(guò)如下商品:<br/>");
for(Book book : list){
out.write(book.getName() + "<br/>");
}
}
}
運(yùn)行結(jié)果:

3,防止用戶非法登錄到某個(gè)頁(yè)面
比如我們的用戶管理系統(tǒng),必須要登錄成功后才能跳轉(zhuǎn)到主頁(yè)面,而不能直接繞過(guò)登錄頁(yè)面直接到主頁(yè)面,這個(gè)應(yīng)用是一個(gè)非常常見(jiàn)的應(yīng)用。
當(dāng)在驗(yàn)證用戶的控制器LoginClServlet.java驗(yàn)證用戶成功后,將當(dāng)前的用戶信息保存在Session對(duì)象中:
// 把user對(duì)象保存在session
HttpSession session = req.getSession();
session.setAttribute("loginUser", user);
然后在主頁(yè)面Main.java最開(kāi)始的地方,取出Session中的登錄用戶信息,如果信息為空,則為非法訪問(wèn),直接跳轉(zhuǎn)到登錄頁(yè)面,并提示相關(guān)信息:
// 取出login-user這個(gè)session
User loginUser = (User)req.getSession().getAttribute("loginUser");
if(loginUser == null){
// 說(shuō)明用戶沒(méi)有登錄,讓他跳轉(zhuǎn)到登錄頁(yè)面
req.setAttribute("error", "請(qǐng)登錄!");
req.getRequestDispatcher("/LoginServlet").forward(req,res);
return;
}
那么這里就存在一個(gè)問(wèn)題,一個(gè)網(wǎng)站會(huì)有很多個(gè)需要防止非法訪問(wèn)的頁(yè)面,如果都是用這種方法豈不是很麻煩?
兩種解決辦法:
第一種:將這段驗(yàn)證用戶的代碼封裝成函數(shù),每次調(diào)用
第二種:使用過(guò)濾器
4,利用Session防止表單重復(fù)提交
具體的做法:
在服務(wù)器端生成一個(gè)唯一的隨機(jī)標(biāo)識(shí)號(hào),專業(yè)術(shù)語(yǔ)稱為Token(令牌),同時(shí)在當(dāng)前用戶的Session域中保存這個(gè)Token。然后將Token發(fā)送到客戶端的Form表單中,在Form表單中使用隱藏域來(lái)存儲(chǔ)這個(gè)Token,表單提交的時(shí)候連同這個(gè)Token一起提交到服務(wù)器端,然后在服務(wù)器端判斷客戶端提交上來(lái)的Token與服務(wù)器端生成的Token是否一致,如果不一致,那就是重復(fù)提交了,此時(shí)服務(wù)器端就可以不處理重復(fù)提交的表單。如果相同則處理表單提交,處理完后清除當(dāng)前用戶的Session域中存儲(chǔ)的標(biāo)識(shí)號(hào)。
在下列情況下,服務(wù)器程序?qū)⒕芙^處理用戶提交的表單請(qǐng)求:
1,存儲(chǔ)Session域中的Token(令牌)與表單提交的Token(令牌)不同。
2,當(dāng)前用戶的Session中不存在Token(令牌)。
3,用戶提交的表單數(shù)據(jù)中沒(méi)有Token(令牌)。
例如:
創(chuàng)建FormTokenServlet,用于生成Token和跳轉(zhuǎn)到token.jsp頁(yè)面:
public class FormTokenServlet extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 創(chuàng)建令牌
String token = TokenProccessor.getInstance().makeToken();
System.out.println("在FormServlet中生成的token:" + token);
// 在服務(wù)器使用session保存token(令牌)
req.getSession().setAttribute("token", token);
// 跳轉(zhuǎn)到token.jsp頁(yè)面
req.getRequestDispatcher("/token.jsp").forward(req, resp);
}
}
在token.jsp中使用隱藏域來(lái)存儲(chǔ)Token(令牌),提交Token(令牌)到服務(wù)器:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<html>
<head>
<base href="<%=basePath%>" />
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>表單提交</title>
<link href="css/bootstrap.css" rel="stylesheet">
<script src="js/jquery-3.2.1.js"></script>
<script src="js/bootstrap.js"></script>
</head>
<body>
<form class="form-horizontal" action="<%=request.getContextPath()%>/TokenServlet.html" role="form" method="post">
<input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
<div class="form-group">
<label for="firstname" class="col-sm-1 control-label">用戶名</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="idCodeNum"
placeholder="請(qǐng)輸入用戶名">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-3">
<button type="submit" class="btn btn-default">提交</button>
<button type="reset" class="btn btn-default">重置</button>
</div>
</div>
</form>
</body>
</html>
TokenServlet處理表單提交:
public class TokenServlet extends HttpServlet {
private static final long serialVersionUID = -8236507185410764108L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
boolean b = false;
String client_token = req.getParameter("token");
// 如果用戶提交的表單數(shù)據(jù)中沒(méi)有token,則用戶是重復(fù)提交了表單
if (client_token == null) {
b = true;
}
// 取出存儲(chǔ)在Session中的token
String serverToken = (String) req.getSession().getAttribute("token");
// 如果當(dāng)前用戶的Session中不存在Token,則用戶是重復(fù)提交了表單
if (serverToken == null) {
b = true;
}
// 存儲(chǔ)在Session中的Token與表單提交的Token不同,則用戶是重復(fù)提交了表單
if (!client_token.equals(serverToken)) {
b = true;
}
if (b == true) {
System.out.println("請(qǐng)不要重復(fù)提交!");
return;
}
// 移除session中的token
req.getSession().removeAttribute("token");
System.out.println("正在處理用戶提交請(qǐng)求!!");
}
}
運(yùn)行結(jié)果如下:

十、用戶禁用Cookie后的Session處理
這里存在一種情況,假如用戶瀏覽器禁用了Cookie怎么辦?比如我把Chrome的Cookie禁用,如下:

解決方法:URL重寫
Servlet中的response提供了對(duì)URL重寫的方法:
| response.encodeRedirectURL(String url) | 用于對(duì)sendRedirect方法后的url地址進(jìn)行重寫 |
|---|---|
| response.encodeURL(String url) | 用于對(duì)表單action和超鏈接的url地址進(jìn)行重寫 |
那么URL重寫是什么意思呢?其實(shí)就是人為地把JSESSIONID附在了url的后面,比如我們修改之前寫的簡(jiǎn)易購(gòu)物車,ShowBook中所有的點(diǎn)擊購(gòu)買超鏈接都要重寫。
之前我們是這么寫的:
out.println("<tr><td>"+book.getName()+"</td><td><a href='"+ req.getContextPath() +"/BuyBookServlet.html?id="+book.getId()+"'>點(diǎn)擊購(gòu)買</a></td></tr>");
現(xiàn)在進(jìn)行URL重寫:
req.getSession();
String url = "/MyCart/BuyBookCl?id="+book.getId();
url = resp.encodeURL(url);
out.println("<tr><td>"+book.getName()+"</td><td><a href='"+url+"'>點(diǎn)擊購(gòu)買</a></td></tr>");
需要注意的是,重寫之前一定要調(diào)用或者確保使用過(guò)request.getSession()這個(gè)方法。
重寫之前,訪問(wèn)ShowBookServlet的源代碼是這樣的:

重寫之后呢:

可以看到URL重寫之后,jsessionid這個(gè)參數(shù)自動(dòng)附在了url后面,由此,得以確保我們的Session在Cookie被禁用的情況下繼續(xù)正常使用。這時(shí)候我們查看購(gòu)物車的地址欄如下,可以明顯看到j(luò)sessionid這個(gè)參數(shù):

最后,給大家分享我收藏的幾個(gè)不錯(cuò)的 github 項(xiàng)目,內(nèi)容都還是不錯(cuò)的,如果覺(jué)得有幫助,可以順便給個(gè) star。
- 計(jì)算機(jī)專業(yè)學(xué)生必須要啃的書籍推薦: https://github.com/hello-go-maker/cs-books
- Java 實(shí)戰(zhàn)項(xiàng)目推薦: https://github.com/hello-go-maker/Java-project
- 推薦一些很不錯(cuò)的計(jì)算機(jī)學(xué)習(xí)教程,包括:數(shù)據(jù)結(jié)構(gòu)、算法、計(jì)算機(jī)網(wǎng)絡(luò)、操作系統(tǒng)、Java(spring、springmvc、springboot、springcloud),也包括多個(gè)企業(yè)級(jí)實(shí)戰(zhàn)項(xiàng)目: https://github.com/hello-go-maker/cs-learn-source
- Java面試+Java后端技術(shù)學(xué)習(xí)指南】:一份通向理想互聯(lián)網(wǎng)公司的面試指南,包括 Java,技術(shù)面試必備基礎(chǔ)知識(shí)、Leetcode、計(jì)算機(jī)操作系統(tǒng)、計(jì)算機(jī)網(wǎng)絡(luò)、系統(tǒng)設(shè)計(jì)、分布式、數(shù)據(jù)庫(kù)(MySQL、Redis)、Java 項(xiàng)目實(shí)戰(zhàn)等: https://github.com/hello-java-maker/JavaInterview