Session概述(java)
JavaWeb Session
轉載於:https://www.cnblogs.com/aaron911/p/7889353.html
1. Session概述
1.1. 什麽是Session
Session一般譯為會話,是解決Http協議的無狀態問題的方案,可以將一次會話中的數據存儲在服務器端的內存中,保證在下一次的會話中可以使用。
在客戶端瀏覽器第一次向服務器端發送請求時,服務器端會為這個客戶端創建獨有的Session,並具有唯一的Session ID,存儲在服務器端的內存中。在客戶端第二次訪問服務器端時,會攜帶Session ID在請求中,服務器端會根據Session ID查找對應的Session信息,進行進一步地操作。
在JavaEE中提供了javax.servlet.http.HttpSession接口,通過該接口可以將共享的數據內容存儲在HttpSession對象中,從而解決Http協議的無狀態問題。
1.2. 百度百科
Session直接翻譯成中文比較困難,一般都譯成時域。在計算機專業術語中,Session是指一個終端用戶與交互系統進行通信的時間間隔,通常指從註冊進入系統到註銷退出系統之間所經過的時間。以及如果需要的話,可能還有一定的操作空間。
具體到Web中的Session指的就是用戶在瀏覽某個網站時,從進入網站到關閉這個網站所經過的這段時間,也就是用戶瀏覽這個網站所花費的時間。因此從上述的定義中我們可以看到,Session實際上是一個特定的時間概念。
需要註意的是,一個Session的概念需要包括特定的客戶端,特定的服務器端以及不中斷的操作時間。A用戶和C服務器建立連接時所處的Session同B用戶和C服務器建立連接時所處的Session是兩個不同的Session。
1.3. 維基百科
會話(session)是一種持久網絡協議,在用戶(或用戶代理)端和服務器端之間創建關聯,從而起到交換數據包的作用機制,session在網絡協議(例如telnet或FTP)中是非常重要的部分。
在不包含會話層(例如UDP)或者是無法長時間駐留會話層(例如HTTP)的傳輸協議中,會話的維持需要依靠在傳輸數據中的高級別程序。例如,在瀏覽器和遠程主機之間的HTTP傳輸中,HTTP cookie就會被用來包含一些相關的信息,例如session ID,參數和權限信息等。
1.4. Session與Cookie的區別
Session與Cookie都是解決Http協議的無狀態問題,但是兩者之間還是存在一定區別的:
- Cookie數據存儲在客戶端的瀏覽器內存中或本地緩存文件中,Session數據存儲在服務器端的內存中。
- Cookie數據存儲安全性較低,Session數據存儲安全性較高。
- Session數據存儲在服務器端內存中,訪問增多時,降低服務器端性能。而Cookie則不會對服務器端性能造成影響。
- 單個Cookie存儲的數據最大是4KB,一個網站只能存儲20個Cookie。Session則沒有這個問題。
- Session在關閉瀏覽器時失效,而持久Cookie則可以存儲更長有效時間。
總的來說,Session與Cookie各有優勢,不能簡單來說誰更優。具體用法要考慮具體案例情況而定。
2. Session入門
2.1. Session常用API
在JavaEE提供的javax.servlet.http.HttpSession接口,是Web應用程序開發使用Session的接口,該接口提供了很多API方法,而常用的方法有以下幾個:
Method Summary |
|
Object |
getAttribute(String name) |
Enumeration |
getAttributeNames() |
void |
removeAttribute(String name) |
void |
setAttribute(String name, Object value) |
- 通過Request對象獲得HttpSession對象。
HttpSession session = request.getSession();
- 通過HttpSession對象設置和獲取共享數據內容。
session.setAttribute("name", "longestory"); String name = (String)session.getAttribute("name");
2.2. 第一個Session
掌握了如何獲取Session對象及向Session對象中設置及獲取共享數據內容,下面我們就來利用HttpSession對象實現數據內容共享。
- 創建一個Servlet用於向HttpSession對象中存儲共享數據內容。
public class FirstServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpSession session = request.getSession(); session.setAttribute("name", "longestory"); System.out.println("已經成功向HttpSession對象中存儲了共享數據內容name=longestory..."); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 創建另一個Servlet用於從HttpSession對象中獲取儲存的共享數據內容。
public class SecondServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); HttpSession session = request.getSession(); String name = (String)session.getAttribute("name"); out.println("<h1>你存儲的共享數據內容為name="+name+"</h1>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <servlet> <servlet-name>FirstServlet</servlet-name> <servlet-class>app.java.session.FirstServlet</servlet-class> </servlet> <servlet> <servlet-name>SecondServlet</servlet-name> <servlet-class>app.java.session.SecondServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>FirstServlet</servlet-name> <url-pattern>/first</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>SecondServlet</servlet-name> <url-pattern>/second</url-pattern> </servlet-mapping> </web-app>
2.3. Session其他API
在JavaEE的javax.servlet.http.HttpSession接口提供了除常用方法外,還有很多其他方法可供使用:
Method Summary |
|
long |
getCreationTime() |
String |
getId() |
long |
getLastAccessedTime() |
int |
getMaxInactiveInterval() |
ServletContext |
getServletContext() |
void |
invalidate() |
boolean |
isNew() |
void |
setMaxInactiveInterval(int interval) |
- String getId():獲取sessionId;
- long getCreationTime():返回session的創建時間,返回值為當前時間的毫秒值;
- long getLastAccessedTime():返回session的最後活動時間,返回值為當前時間的毫秒值;
- boolean isNew():查看session是否為新。當客戶端第一次請求時,服務器為客戶端創建session,但這時服務器還沒有響應客戶端,也就是還沒有把sessionId響應給客戶端時,這時session的狀態為新;
- int getMaxInactiveInterval():獲取session可以的最大不活動時間(秒),默認為30分鐘。當session在30分鐘內沒有使用,那麽Tomcat會在session池中移除這個session;
- void setMaxInactiveInterval(int interval):設置session允許的最大不活動時間(秒),如果設置為1秒,那麽只要session在1秒內不被使用,那麽session就會被移除;
- void invalidate():讓session失效!調用這個方法會被session失效,當session失效後,客戶端再次請求,服務器會給客戶端創建一個新的session,並在響應中給客戶端新session的sessionId。
下面我們通過一個Servlet將上述中的一些方法進行測試,這裏理解性記憶就好,後面案例中用到哪個方式再具體掌握。
- 創建一個Servlet用於測試HttpSession對象的方法。
public class ThreeServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Calendar calendar = Calendar.getInstance(); HttpSession session = request.getSession(); System.out.println("Session的ID為"+session.getId()); calendar.setTimeInMillis(session.getCreationTime()); System.out.println("Session的創建時間為"+formatter.format(calendar.getTime())); calendar.setTimeInMillis(session.getLastAccessedTime()); System.out.println("Session的最後活動時間為"+formatter.format(calendar.getTime())); System.out.println("當前Session是否為新的?"+session.isNew()); System.out.println("Session的默認活動時間為"+session.getMaxInactiveInterval()/60+"分鐘"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <servlet> <servlet-name>ThreeServlet</servlet-name> <servlet-class>app.java.session.ThreeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ThreeServlet</servlet-name> <url-pattern>/three</url-pattern> </servlet-mapping> </web-app>
2.4. Servlet三大域對象
現在掌握了HttpSession對象的基本使用方法,到目前為止,Servlet的三大域對象都已經掌握。Servlet的三大域對象分別為HttpServletRequest對象、ServletContext對象及HttpSession對象:
- HttpServletRequest:一個請求創建一個request對象,所以在同一個請求中可以共享request,例如一個請求從AServlet轉發到BServlet,那麽AServlet和BServlet可以共享request域中的數據;
- ServletContext:一個應用只創建一個ServletContext對象,所以在ServletContext中的數據可以在整個應用中共享,只要不啟動服務器,那麽ServletContext中的數據就可以共享;
- HttpSession:一個會話創建一個HttpSession對象,同一會話中的多個請求中可以共享session中的數據。
3. Session詳解
3.1. Session的實現原理
通過HttpSession對象實現了在多次會話中共享數據內容,HttpSession對象底層到底是如何實現這樣的效果?下面我們來討論一下。
- 利用MyEclipse工具的Debug模式進行調試代碼,在request.getSession()處打上斷點。
- Debug模式啟動Tomcat服務器,並訪問對應的Servlet,抓取獲取的Session內容。
會發現實際得到的是org.apache.catalina.session.StandardSession對象,該對象是由Tomcat服務器的Session池創建,並為該對象創建唯一的ID值。
- 通過IE瀏覽器的HttpWatch工具抓取請求響應協議內容。
會發現服務器端向客戶端進行響應時,向客戶端發送了一個Cookie信息,具體內容如下:
Set-Cookie: JSESSIONID=0BD17B07E383FA86703B370560E823F2; Path=/11_session/; HttpOnly
- 客戶端再次訪問對應Servlet時,通過IE瀏覽器的HttpWatch工具抓取請求響應協議內容。
會發現客戶端向服務器端發送請求時,向服務器端發送了一個Cookie信息,具體內容如下:
Cookie: JSESSIONID=0BD17B07E383FA86703B370560E823F2
通過上述操作,我們會發現在第一次請求時,服務器端向客戶端響應Cookie信息,在第二次請求時,客戶端向服務器端發送了Cookie信息。進而可以總結出Session的實現原理如下:
因為Session使用的是會話Cookie,所以當瀏覽器關閉後,Session會失效。重新打開瀏覽器訪問對應Servlet時,服務器端會重新創建Session對象。可以使用持久Cookie來延長Session的有效時間。
3.2. 禁用Cookie後的Session
通過Session的實現原理可以知道,Session的實現是基於Cookie的。如果瀏覽器禁用Cookie的話,Session是否還是有效的呢?下面我們具體操作來看一看。
- 打開IE瀏覽器的“工具”->“Internet選項”->“隱私”選項,阻止所有Cookie。
- 這時重新啟動Tomcat服務器,訪問對應的Servlet。
這個問題是否可以解決呢?答案是可以的,可以利用URL重寫方式來解決,具體操作如下:
- 通過Response對象的encodeURL()方法將URL進行重寫。
String url = request.getRequestURI(); url = response.encodeURL(url); response.getWriter().println("<a href=‘" + url + "‘>second</a>");
禁用Cookie解決Session的問題,這種解決方案是具有理論意義的,但不具備實際意義。因為大部分的網站都是基於Cookie完成數據共享的,例如京東網站或淘寶網站等。如果瀏覽器禁用Cookie,網站會提示相關信息。
3.3. Session的生命周期
關於Sessioin的生命周期,在之前的內容都有學習到,只是沒有專門歸納總結。下面總結一下Session的生命周期,主要是Session的創建和銷毀。
- Session的創建:在客戶端第一次向服務器端發送請求,並執行request.getSession()方法時。
- Session的銷毀:
- 不正常關閉瀏覽器時。(正常關閉瀏覽器,Session信息被序列化到硬盤中,保存在Tomcat服務器安裝目錄/work目錄)
- Session信息過期時(Session默認的有效時間為30分鐘)。
可以利用setMaxInactiveInterval(int interval)方法設置Session的有效時間。
-
- 在服務器端程序中執行session.invalidate()方法,手動銷毀Session對象。
4. Session案例
4.1. 商品購物車案例
利用Session實現商品購物車的邏輯功能,具體操作如下:
- 創建一個JSP頁面用於顯示商品信息。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ page import="java.util.Map"%> <%@ page import="java.util.Set"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP ‘show.jsp‘ starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> </head> <body> <!-- 商品列表 --> <h1>商品列表</h1> Java編程思想<a href="/session/show?id=1">購買</a><br> Java設計模式<a href="/session/show?id=2">購買</a><br> Java語言入門<a href="/session/show?id=3">購買</a><br> Oracle數據庫<a href="/session/show?id=4">購買</a><br> MySQL數據庫<a href="/session/show?id=5">購買</a><br> <!-- 購物車記錄 --> <h1>購物車記錄</h1> <h2><a href="/session/clear">清空購物車</a></h2> <% Map<String,Integer> map = (Map<String,Integer>)request.getSession().getAttribute("cart"); if(map == null){ // 購物車對象不存在 out.println("<h2>購物車無任何商品信息!</h2>"); }else{ // 購物車對象已經存在 Set<String> keySet = map.keySet(); for(String productName: keySet){ int number = map.get(productName);// 購買數量 out.println("商品名稱: " + productName +", 購買數量:" + number + "<br/>"); } } %> </body> </html>
- 創建一個Servlet用於處理添加購物車的邏輯。
public class ShowServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲得購買商品 id String id = request.getParameter("id"); // 獲得id 對應商品名稱 String[] names = { "Java編程思想", "Java設計模式", "Java語言入門", "Oracle數據庫", "MySQL數據庫" }; String productName = names[Integer.parseInt(id) - 1]; // 判斷Session中購物車是否存在 HttpSession session = request.getSession(); Map<String, Integer> cart = (Map<String, Integer>) session.getAttribute("cart"); if (cart == null) { // 購物車不存在 cart = new HashMap<String, Integer>(); } // 判斷購買商品是否存在於購物車中,商品名稱就是map的key if (cart.containsKey(productName)) { // 商品已經在購物車中 int number = cart.get(productName);// 取出原來數量 cart.put(productName, number + 1);// 數量+1 放回購物車 } else { // 商品不在購物車中 cart.put(productName, 1);// 保存商品到購物車,數量為1 } // 將購物車對象保存Session session.setAttribute("cart", cart); // 給用戶提示 response.setContentType("text/html;charset=utf-8"); response.getWriter().println("商品已經添加到購物車!<a href=‘/session/cart/show.jsp‘>返回</a>"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 創建一個Servlet用於處理清空購物車記錄的邏輯。
public class ClearServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲取購物車Session對象 HttpSession session = request.getSession(); // 刪除購物車cart對象 session.removeAttribute("cart"); // 跳轉show.jsp response.sendRedirect("/session/cart/show.jsp"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 配置Web工程的web.xml文件。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name></display-name> <servlet> <servlet-name>ShowServlet</servlet-name> <servlet-class>app.java.session.ShowServlet</servlet-class> </servlet> <servlet> <servlet-name>ClearServlet</servlet-name> <servlet-class>app.java.session.ClearServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ShowServlet</servlet-name> <url-pattern>/show</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ClearServlet</servlet-name> <url-pattern>/clear</url-pattern> </servlet-mapping> </web-app>
4.2. 一次驗證碼登錄案例
所謂一次驗證碼,就是驗證碼生成後,只能使用一次,不管成功或者失敗,驗證碼都將失效。具體實現步驟如下:
- 創建一個JSP頁面用於顯示用戶登錄信息。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP ‘login.jsp‘ starting page</title> </head> <script type="text/javascript"> function change(){ document.getElementById("myimg").src = "/session/checkimg?timeStamp="+new Date().getTime(); } </script> <body> <h1>登陸頁面</h1> <h2 style="color:red;">${requestScope.msg }</h2> <form action="/11_session/login" method="post"> <table> <tr> <td>用戶名</td> <td><input type="text" name="username" /></td> </tr> <tr> <td>密碼</td> <td><input type="password" name="password"/> </td> </tr> <tr> <td>驗證碼</td> <td><input type="text" name="checkcode" /> <img src="/session/checkimg" onclick="change();" id="myimg" style="cursor: pointer;"/></td> </tr> <tr> <td colspan="2"><input type="submit" value="登陸" /></td> </tr> </table> </form> </body> </html>
- 創建一個Servlet用於生成驗證碼圖片。
public class CheckImgServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int width = 120; int height = 30; // 步驟一 繪制一張內存中圖片 BufferedImage bufferedImage = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB); // 步驟二 圖片繪制背景顏色 ---通過繪圖對象 Graphics graphics = bufferedImage.getGraphics();//得到畫圖對象 - 畫筆 // 繪制任何圖形之前 都必須指定一個顏色 graphics.setColor(getRandColor(200, 250)); graphics.fillRect(0, 0, width, height); // 步驟三 繪制邊框 graphics.setColor(Color.WHITE); graphics.drawRect(0, 0, width - 1, height - 1); // 步驟四 四個隨機數字 Graphics2D graphics2d = (Graphics2D) graphics; // 設置輸出字體 graphics2d.setFont(new Font("宋體", Font.BOLD, 18)); String words = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; Random random = new Random();// 生成隨機數 // 為了將驗證碼保存Session StringBuffer buffer = new StringBuffer(); // 定義x坐標 int x = 10; for (int i = 0; i < 4; i++) { // 隨機顏色 graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); // 旋轉 -30 --- 30度 int jiaodu = random.nextInt(60) - 30; // 換算弧度 double theta = jiaodu * Math.PI / 180; // 生成一個隨機數字 int index = random.nextInt(words.length());//生成隨機數0到 length-1 // 獲得字母數字 char c = words.charAt(index); // 將生成漢字 加入buffer buffer.append(c); // 將c 輸出到圖片 graphics2d.rotate(theta, x, 20); graphics2d.drawString(String.valueOf(c), x, 20); graphics2d.rotate(-theta, x, 20); x += 30; } // 將驗證碼內容保存session request.getSession().setAttribute("checkcode_session",buffer.toString()); // 步驟五 繪制幹擾線 graphics.setColor(getRandColor(160, 200)); int x1; int x2; int y1; int y2; for (int i = 0; i < 30; i++) { x1 = random.nextInt(width); x2 = random.nextInt(12); y1 = random.nextInt(height); y2 = random.nextInt(12); graphics.drawLine(x1, y1, x1 + x2, x2 + y2); } // 將上面圖片輸出到瀏覽器 ImageIO graphics.dispose();// 釋放資源 ImageIO.write(bufferedImage, "jpg", response.getOutputStream()); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } /** * 取其某一範圍的color * * @param fc * int 範圍參數1 * @param bc * int 範圍參數2 * @return Color */ private Color getRandColor(int fc, int bc) { // 取其隨機顏色 Random random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } }
- 創建一個Servlet用於處理登錄驗證邏輯。
public class LoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲得用戶名、密碼和 驗證碼 request.setCharacterEncoding("utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String checkcode = request.getParameter("checkcode"); // 判斷驗證碼是否正確 String checkcode_session = (String) request.getSession().getAttribute("checkcode_session"); request.getSession().removeAttribute("checkcode_session"); if (checkcode_session == null || !checkcode_session.equals(checkcode)) { // 驗證碼輸入錯誤 request.setAttribute("msg", "驗證碼輸入錯誤!"); request.getRequestDispatcher("/login/login.jsp").forward(request,response); return; } // 判斷用戶名和密碼是否正確 ,假設用戶名和密碼都是admin/admin if ("admin".equals(username) && "admin".equals(password)) { // 登陸成功 // 將登陸信息保存session request.getSession().setAttribute("username", username); response.sendRedirect("/session/login/welcome.jsp"); } else { // 登陸失敗 request.setAttribute("msg", "用戶名或者密碼錯誤!"); request.getRequestDispatcher("/login/login.jsp").forward(request,response); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
- 創建一個JSP頁面用於顯示歡迎信息。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>My JSP ‘welcome.jsp‘ starting page</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> </head> <body> <h1>登陸後的歡迎頁面</h1> <% String username = (String)request.getSession().getAttribute("username"); if(username == null){ // 未登陸 out.println("您還未登陸,<a href=‘/session/login/login.jsp‘>去登陸</a>"); }else{ // 已經登陸 out.println("歡迎您,"+username); } %> </body> </html>
Session概述(java)