會話技術之 Session
阿新 • • 發佈:2020-09-07
# 會話技術之 Session
不多廢話,先來一個 HelloWorld。
Session 有 get 肯定要先有 set 。
~~~java
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設定編碼
resp.setContentType("text/html;charset=UTF-8");
// get 到session
HttpSession session = req.getSession();
//有中文,別忘了編碼再傳進去
session.setAttribute("HelloWorld", URLEncoder.encode("Hello--World!向世界宣告我的到來!","UTF-8"));
}
~~~
==這裡分兩個Servlet編寫==
~~~java
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//設定編碼
resp.setContentType("text/html;charset=UTF-8");
// get 到 session
HttpSession session = req.getSession();
// 獲取到值,別忘了解碼
String helloWorld = URLDecoder.decode((String) session.getAttribute("HelloWorld"),"UTF-8");
//輸出一下
resp.getWriter().write(helloWorld);
}
~~~
## Session API
- long getCreationTime();【獲取Session被建立時間】
- String getId();【獲取Session的id】
- long getLastAccessedTime();【返回Session上一次訪問的時間】
- ServletContext getServletContext();【獲取ServletContext物件】
- void setMaxInactiveInterval(int var1);【設定Session超時時間】
- int getMaxInactiveInterval();【獲取Session超時時間】
- Object getAttribute(String var1);【獲取Session屬性】
- Enumeration getAttributeNames();【獲取Session所有的屬性名】
- void setAttribute(String var1, Object var2);【設定Session屬性】
- void removeAttribute(String var1);【移除Session屬性,==**注意,不是銷燬該Session**==】
- void invalidate();【銷燬該Session】
- boolean isNew();【該Session是否為新的】
**還有些過時的方法就不一一列舉了**
## Session 有效期
在此之前先說一下 Session 的物件,是一個域物件, 只要Session 還沒有被銷燬(或者瀏覽器沒有關閉),Servlet 就可以通過 Session 物件進行資料傳遞。
Session 的生命週期可以通過方法去控制的,直接殺死銷燬掉,當然關閉瀏覽器也是可以直接殺死掉的。
~~~java
//銷燬該 session
session.invalidate();
~~~
還有可以通過時間的預先設定,在此時間段內目標Session 沒有被訪問,就會進行銷燬。這是伺服器出於防止記憶體溢位的考慮,將最久未訪問的銷燬。不知你是否嗅到了 least recently used (LRU)演算法的味道。
這種方式又有三種途徑可以去實現,同時作用範圍也不盡相同。
### 在 Tomcat 的 web.xml 配置檔案中配置(**對部署在該伺服器上的應用有效**)
可以看到預設的配置:
![image-20200906215627123](https://i.loli.net/2020/09/06/1MXTHbOle4pnDE7.png)
以分鐘為單位,預設是 30 分鐘。
接下來照葫蘆畫瓢,修改為 20 分鐘:
~~~xml
20
~~~
儲存即可生效,==再次強調作用域,是部署在該 Tomcat 伺服器上的所有專案==。如果只是為了練手,驗證結果後,別忘了修改回來,或者記住你曾經修改過。
### 在工程的 web.xml 檔案中配置(**對 web.xml 檔案下的 web 應用有效**)
配置內容是一樣滴。
~~~xml
20
~~~
這裡提一個醒,web.xml 在 Servlet 2.3 中,配置的元素必須按照順序配置。而Servlet 2.4 中是不需要的。那麼如何知道自己用的是哪個版本呢?
很簡單,你先不按順序配,如下圖所示報紅了,那就說明你是 2.3 ,得乖乖按順序配,提示也會貼心提醒你配置順序。
![image-20200906220234436](https://i.loli.net/2020/09/06/2qW9ikgPDBNc3Up.png)
### 在單個Servlet 中通過 setMaxInactiveInterval(int var1) 方法設定 (**對單個 Session 有效**)
既然知道配置方法,那就來看看原始碼:
![image-20200906221831812](https://i.loli.net/2020/09/06/LQrRGuvEcSCo9Up.png)
從原始碼可知,是以秒為單位配置的。我們還是來個 20 分鐘。
~~~java
//配置有效期
session.setMaxInactiveInterval(60*20);
~~~
既然三個作用範圍,那就會有優先順序的問題,這裡由小見大,==**優先順序為 3 > 2 > 1**==。
## Session 原理
### 依賴 Cookie
http://127.0.0.1:8080/ling/session/putSession 首先由訪問地址來看,可知使用的是 Http 協議。Http 協議是一種無狀態協議。伺服器端並不能通過 Http 協議感知到瀏覽器中的是哪一個使用者。
在瀏覽器中我們可以看到訪問時,會為我們存下名為 JSESSIONID 的 Cookie ,它的值就是 Session的 ID。根本上,瀏覽器就是根據這一 Cookie 的值來判斷是否是否為同一使用者,該用哪一個 Session 進行通訊。注意他的生命週期,為 Session 。正合 Session 意。
![image-20200907152132373](https://i.loli.net/2020/09/07/cuRlfkYNVnMxG1L.png)
刪除此 Cookie 後,訪問一個從 Session中取值的 Servlet ,會報空指標錯誤,如上所述,因為伺服器是根據瀏覽器傳送來名為 JSESSIONID 的 Cookie 來判斷使用哪一個 Session,沒有 Cookie 提供的 ID ,也就無從去找對應的 Session 了,自然會是伺服器端報空指標的錯。
![image-20200907151233026](https://i.loli.net/2020/09/07/P7SIxV5eWonGzDK.png)
------
那麼問題來了,我禁用了 Cookie 的話,那 Session 不就不能用了嗎?
是的,正常渠道下是不能用了。
### URL 地址重寫
==注意這兩種方法都需要禁用Cookie==
關於對目標web 應用禁用 Cookie方法如下:
Java Web規範⽀持通過配置禁⽤Cookie,禁⽤⾃⼰項⽬的Cookie,在META-INF⽂件夾下的context.xml⽂件中修改(沒有則建立),並編寫如下內容:
~~~xml
~~~
這裡有由HttpServletResponse 提供兩種方法進行 URL 地址重寫:
- encodeURL(String url) ;
看原始碼,我們再決定如何使用它:
![image-20200907160427467](https://i.loli.net/2020/09/07/4lwTcComZyvReri.png)
這裡需要注意原始碼中也說了,如果瀏覽器支援 Cookie,或者Session 被關閉(通常是Session是否執行了 invalidate() 方法),這時候是不會進行地址重寫的。==注意,該方法不能在不支援Cookie的瀏覽器中生效。==
```java
// 目標url
String url = "/ling/session/testSession";
// 重寫url,並重定向到目標url
resp.sendRedirect(resp.encodeURL(url));
```
- encodeRedirectURL(String url);
原始碼裡,坑爹的來了:
![image-20200907182340737](https://i.loli.net/2020/09/07/eY79lrPuIgjQMo5.png)
大概就是,有不同,你們用的時候小心,但是什麼不同我就不具體說了。。。這就一個介面,我哪找原始碼去.....
~~~java
// 目標url
String url = "/ling/session/testSession";
// 重寫url,並重定向到目標url
resp.sendRedirect(resp.encodeRedirectURL(url));
~~~
以上寫法下,至少同一站點下,兩者的效果並無差別:
![image-20200907153817655](https://i.loli.net/2020/09/07/zuTasDKM9CWxvfe.png)
要問有什麼不同,原始碼上也看不出來什麼。關於==**不同點**==,只在網路找到以下說法:
> ---------來自CSDN。 原文連結:https://blog.csdn.net/SpbDev/article/details/37879549
>
> encodeURL在附加jsessionid之前還對url做了判斷處理:如果url為空字串(長度為0的字串),則將url轉換為完整的URL(http或https開頭的);如果url是完整的URL,但不含任何路徑(即只包含協議、主機名、埠,例如http://127.0.0.1),則在末尾加上根路徑符號/。
> 也就是encodeURL如果進行了編碼,則返回的URL一定是完整URL而不是相對路徑;而encodeRedirectURL則不對URL本身進行處理,只專注於新增jsessionid引數(如果需要)。
> --------來自 STACK OVERFLOW 。問答連結:https://stackoverflow.com/questions/4944778/whats-the-difference-between-encodeurl-and-encoderedirecturl
>
> The main difference between two is, the implementation of encodeRedirectURL method includes the logic to determine whether the session ID needs to be encoded in the URL in the case when you are redirecting the URL to different context where the session information is not required or invalid. The encodeURL method do not appent the seesion id if the cookies are enabled. In addition to this encodeRedirectURL do not append the session information if the URL is redirected to the different context (web application). Because the rules for making this determination can differ from those used to decide whether to encode a normal link, this method is separete from the encodeURL method.
>
> ==**主要是這句 “如果 URL 重定向到不同的上下文(Web 應用程式),encodeRedirectURL 不追加會話資訊。”,然而無從驗證,筆者通過兩種方法,得到的是同一個 Session 的 ID 值。**==
## Cookie和Session共同使用
現在有一個這樣的需求,在瀏覽器瀏覽資料時,不小心關閉了瀏覽器,現在需要設計重新開啟瀏覽器還可以看到之前的記錄(==此記錄不是單純的字元型資料,指的是複雜型別資料==)。
結合 [會話技術之Cookie](https://www.cnblogs.com/l1ng14/p/13616238.html) ,可以知道,單一地使用 Cookie 或者 Session 都是無法達到在安全性和便捷性上合理的效果的。這時候需要兩者結合使用才可以發揮兩者的優勢,規避兩者的劣勢。
這時候可以結合上面所述,用 Cookie 儲存 Session 的 ID 到 JSESSIONID ,在Session的有效期內,再次打開了瀏覽器,根據 JSESSIONID 的值去伺服器找到目標 Session,將內容呈現帶瀏覽器展示。
~~~java
// new 一個名為 JSESSIONID 的 Cookie
Cookie cookie = new Cookie("JSESSIONID",session.getId());
//設定該Cookie 有效期,注意並非Session的有效期,但是會影響Session 的取值
cookie.setMaxAge(30*60);
//設定訪問路徑
cookie.setPath("/ling/");
//加入
response.addCookie(cookie);
~~~
這樣就可以滿足上面提到的需求,同時利用了 Session 的儲存方式以及對使用者的不可見 和 Cookie 在瀏覽器(硬碟)持久化的特點