session鈍化、序列化

首先明白一個概念,sessio可以存儲在很多位置,並不是固定在某個地方。可能是內存,也可以是硬盤,服務器關閉後,session暫時還不會失效,比如登錄頁面,如果服務器關閉了,session還沒失效,但是開啓服務器後,希望還是看到之前登錄的用戶登錄進去的頁面,這時候需要序列化改pojo對象,這樣pojo對象會跟session一起保存到內存或者硬盤。

而重新開啓服務器後,session和pojo會重新被加載,原來的pojo對象也會重新顯現出來。所以說session對象的序列化是爲了保持對象處於一種狀態。

即序列化的作用就是:爲了保存在內存中的各種對象的狀態(序列化),並且可以把保存的對象狀態再讀出來(反序列化)。

先簡單說下:

應用場景:

1.一般來說,服務器啓動後,就不會再關閉了,但是如果逼不得已需要重啓,而用戶會話還在進行相應的操作,這時就需要使用序列化將session信息保存起來放在硬盤,服務器重啓後,又重新加載。這樣就保證了用戶信息不會丟失,實現永久化保存

2.淘寶每年都會有定時搶購的活動,很多用戶會提前登錄等待,長時間不進行操作,一致保存在內存中,而到達指定時刻,幾十萬用戶併發訪問,就可能會有幾十萬個session,內存可能吃不消,這時就需要進行對象的活化、鈍化,讓其在閒置的時候離開內存,將信息保存至硬盤,等要用的時候,就重新加載進內存。

 

(一理解)

一、session的序列化和反序列化

什麼是序列化?

把對象的狀態信息轉換爲可以存儲或傳輸的形式過程,簡單說就是把對象轉換爲字節形式存儲的過程稱爲對象的序列化

什麼是反序列化?

把字節序列轉化爲對象的過程

 

Tomcat下保存的session序列化文件


 

實現了Serializable接口的User類

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. import java.io.Serializable;  
  2.   
  3. public class User implements Serializable {  
  4.   
  5.     private static final long serialVersionUID = 1L;  
  6.     private String username;  
  7.     private String password;  
  8.     public String getUsername() {  
  9.         return username;  
  10.     }  
  11.     public void setUsername(String username) {  
  12.         this.username = username;  
  13.     }  
  14.     public String getPassword() {  
  15.         return password;  
  16.     }  
  17.     public void setPassword(String password) {  
  18.         this.password = password;  
  19.     }  
  20.     @Override  
  21.     public String toString() {  
  22.         return "User [username=" + username + ", password=" + password + "]";  
  23.     }  
  24.     public User() {  
  25.         super();  
  26.     }  
  27.     public User(String username, String password) {  
  28.         super();  
  29.         this.username = username;  
  30.         this.password = password;  
  31.     }  
  32. }  


向session中存放數據的a.jsp

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <%@page import="cn.cil.domain.User"%>  
  2. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
  3. <%  
  4. String path = request.getContextPath();  
  5. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
  6. %>  
  7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
  8. <html>  
  9.   <head>  
  10.     <base href="<%=basePath%>">  
  11.       
  12.     <title>My JSP 'a.jsp' starting page</title>  
  13.       
  14.     <meta http-equiv="pragma" content="no-cache">  
  15.     <meta http-equiv="cache-control" content="no-cache">  
  16.     <meta http-equiv="expires" content="0">      
  17.     <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">  
  18.     <meta http-equiv="description" content="This is my page">  
  19.   
  20.   </head>  
  21.     
  22.   <body>  
  23.     <h1>向 Session 存放資源</h1>  
  24.     <%  
  25.         User user = new User("123","abc");  
  26.         session.setAttribute("user",user);  
  27.     %>  
  28.   </body>  
  29. </html>  
  30. </strong></span>  

取數據的b.jsp

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>  
  2. <%  
  3. String path = request.getContextPath();  
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";  
  5. %>  
  6.   
  7. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
  8. <html>  
  9.   <head>  
  10.     <base href="<%=basePath%>">  
  11.       
  12.     <title>My JSP 'b.jsp' starting page</title>  
  13.       
  14.     <meta http-equiv="pragma" content="no-cache">  
  15.     <meta http-equiv="cache-control" content="no-cache">  
  16.     <meta http-equiv="expires" content="0">      
  17.     <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">  
  18.     <meta http-equiv="description" content="This is my page">  
  19.   
  20.   </head>  
  21.     
  22.   <body>  
  23.     <h1>從 Seesion 中取資源</h1>  
  24.     <%= session.getAttribute("user") %>  
  25.   </body>  
  26. </html>  
  27. </strong></span>  


 

在向seesion保存資源後(訪問a.jsp後),關閉tomcat,然後迅速打開tomcat的work目錄到指定項目文件中,就會看到生成的Sessions.ser文件,這個文件保存的就是所有session對象序列化後的信息

這時,再重啓Tomcat,會看到SESSIONS.ser文件消失,又被重新加載,再訪問b.jsp,原來的對象信息還在


模擬對象的序列化反序列化

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileOutputStream;  
  4. import java.io.ObjectInputStream;  
  5. import java.io.ObjectOutputStream;  
  6.   
  7. import org.junit.Test;  
  8.   
  9. import cn.cil.domain.User;  
  10.   
  11.   
  12. public class TestSerializable {  
  13.   
  14.     @Test  
  15.     public void test() throws Exception {  
  16.           
  17.         User user = new User("123","abc");  
  18.         SerializeObj(user);  
  19.         User users = ObjDeSerialize();  
  20.         System.out.println(users);  
  21.     }  
  22.       
  23.     private void SerializeObj(User user) throws Exception{  
  24.           
  25.         ObjectOutputStream outObj = new ObjectOutputStream(  
  26.                 new FileOutputStream(new File("G:/SESSIONS.ser")));  
  27.         outObj.writeObject(user);  
  28.         System.out.println("對象序列化完成");  
  29.     }  
  30.       
  31.     private User ObjDeSerialize() throws Exception{  
  32.         ObjectInputStream inObj = new ObjectInputStream(new FileInputStream(  
  33.                 new File("G:/SESSIONS.ser")));  
  34.         User user = (User)inObj.readObject();  
  35.         System.out.println("對象反序列化完成");  
  36.         return user;  
  37.     }  
  38.   
  39. }  



 

特別注意:

1.進行對象的序列化和反序列化的對象,必須實現Serializable接口,否則無法進行序列化和反序列化,當然這僅僅可以進行序列化和反序列化而已,如果序列化完成的對象(已經保存至硬盤),反序列化前又修改了對象,那麼反序列化會失敗,所以進行序列化的對象必須還要添加serialVersionUID

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. public class User implements Serializable{   
  2.   
  3.     //private static final long serialVersionUID = 1L; //不添加ID  
  4.     private String username;  
  5.     private String password;  
  6.     //private int age = 10; 對象序列化後,新增內容  
  7.       
  8.     public String getUsername() {  
  9.         return username;  
  10.     }  
  11.     public void setUsername(String username) {  
  12.         this.username = username;  
  13.     }  
  14.     public String getPassword() {  
  15.         return password;  
  16.     }  
  17.     public void setPassword(String password) {  
  18.         this.password = password;  
  19.     }  
  20.       
  21.   
  22.       
  23.     /*@Override 
  24.     public String toString() { 
  25.         return "User [username=" + username + ", password=" + password 
  26.                 + ", age=" + age + "]"; 
  27.     }*/  
  28.       
  29.     /*@Override 
  30.     public String toString() { 
  31.         return "User [username=" + username + ", password=" + password 
  32.                 + "]"; 
  33.     }*/  
  34.     public User() {  
  35.         super();  
  36.     }  
  37.     public User(String username, String password) {  
  38.         super();  
  39.         this.username = username;  
  40.         this.password = password;  
  41.     }  
  42. }  
  43.   
  44.   
  45. @Test  
  46.     public void test2() throws Exception {  
  47.           
  48.         User user = new User("123","abc");  
  49.           
  50.         SerializeObj(user);  
  51.           
  52.     }  
  53.     @Test  
  54.     public void test3() throws Exception {  
  55.           
  56.         User users = ObjDeSerialize();  
  57.         System.out.println(users);  
  58.           
  59.     }  
  60. </strong></span>  


在反序列化前,打開註釋部分,進行反序列化

 


這也就說明了,文件流中的class和web下的classpath中的class(修改後)不一致了,會拋出異常,所以進行序列化的對象,如果需要在修改後還可以進行反序列化,就必須添加serialVersionUID,如果不指定serialVersionUID,Java編譯器會自動幫我們添加,一個對象只要修改一點點,他們的serialVersionUID就會不一致,序列化前是一個serialVersionUID,反序列化又是一個serialVersionUID,兩個serialVersionUID不一致,肯定會異常。Eclipse如此強大,不填寫serialVersionUID時,它會報警。所以稍稍注意一下。

2.如果一個對象不實現Serializable接口,它是無法進行序列化的,根本就無法轉換爲字節序列

實現Serializable接口,就好比坐飛機的機票,有機票帶你飛,沒票在地上呆着


二、session的活化和鈍化

當一個用戶長時間不進行操作的時,服務器爲減輕內存壓力,可以將其session保存到硬盤中,等待用戶再次操作的時候,再從硬盤中取出來,(保存到硬盤中的信息不會刪除)

 

將下面配置文件放到tomcat\conf\catalina\localhost目錄下!文件名稱爲項目名稱。

[html]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <Context>  
  2.     <Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1"[如果session在1分鐘內沒有使用,那麼Tomcat就會鈍化它]>  
  3.         <Store className="org.apache.catalina.session.FileStore" directory="mysession"[把session序列化到Tomcat\work\Catalina\localhost\listener\mysession目錄下。]/>  
  4.     </Manager>  
  5. </Context>  

當然也可以放到tomcat的config下的context.xml中,這樣就是對Tomcat下所有應用都生效

 

 

 

session數據

(二理解)

序列化是什麼?

簡單說就是爲了保存在內存中的各種對象的狀態,並且可以把保存的對象狀態再讀出來。雖然你可以用你自己的各種各樣的方法來保存Object States,但是Java給你提供一種應該比你自己好的保存對象狀態的機制,那就是序列化。

2、什麼情況下需要序列化 

a)當你想把的內存中的對象保存到一個文件中或者數據庫中時候;
b)當你想用套接字在網絡上傳送對象的時候;
c)當你想通過RMI傳輸對象的時候;

3、當對一個對象實現序列化時,究竟發生了什麼?

在沒有序列化前,每個保存在堆(Heap)中的對象都有相應的狀態(state),即實例變量(instance ariable)比如:

Foo myFoo = new Foo(); 
myFoo .setWidth(37); 
myFoo.setHeight(70); 

  當通過下面的代碼序列化之後,MyFoo對象中的width和Height實例變量的值(37,70)都被保存到foo.ser文件中,這樣以後又可以把它 從文件中讀出來,重新在堆中創建原來的對象。當然保存時候不僅僅是保存對象的實例變量的值,JVM還要保存一些小量信息,比如類的類型等以便恢復原來的對 象。
服務器重啓後,要想得到session的原來對象的的方法:

    衆所周知,session是服務器端的一種會話技術,只要session沒有關閉,一個會話就會保持。這裏先引出一個問題:如果我在訪問某個頁面後,服務器重啓了一下,但是網頁還沒關,那麼原來的session還在麼?答案是很明顯的,你都把服務器關掉了,session肯定不是原來的session了,原來的像登錄信息等一些跟session相關的信息肯定就沒了。但是如果我們想要服務器重啓後,還是原來的session,那跟如何做呢?

我們看下session的存儲方式:

1.1保存在IIS進程中:

  保存在IIS進程中是指把Session數據保存在IIS的運行的進程中,也就是inetinfo.exe這個進程中,這也是默認的Session的存方式,也是最常用的。

  這種方式的優點是簡單,性能最高。但是當重啓IIS服務器時Session丟失。

 

  1.2.保存在StateServer上

  這種存儲模式是指將Session數據存儲在一個稱爲Asp.Net狀態服務進程中,該進程獨立於Asp.Net輔助進程或IIS應用程序池的單獨進程,使用此模式可以確保在重新啓動Web應用程序時保留會話狀態,並使會話狀態可以用於網絡中的多個Web服務器。可以通過序列化到內存中,保存session對象。

序列化是將內存中的二進制取出來,session數據存儲在內存中,如果實現序列化的話,可以讀取出來,即關閉服務器後開了之後還可以獲取到原來的session對象。

 

  1.3.保存在SQL Server數據庫中

  可以配置把Session數據存儲到SQL Server數據庫中,爲了進行這樣的配置,程序員首先需要準備SQL Server數據服務器,然後在運行.NET自帶安裝工具安裝狀態數據庫。

  這種方式在服務器掛掉重啓後都還在,因爲他存儲在內存和磁盤中。

 

        這就涉及到了一個叫序列化(Serializable)的技術。當對象存儲到硬盤的時候,就需要實現序列化接口,序列化的功能就是添加了一個唯一的ID(類主鍵),這樣在反序列化(從硬盤加載到內存)的時候就可以成功找到相應的對象。另外,還要弄清楚一件事情:一般大家都覺得容器關閉後,session就銷燬了,其實不是這樣的,容器的關閉並不會導致session的銷燬。過程是這樣子的,一旦容器關閉後,session就會被持久化到硬盤,並沒有真正銷燬,爲了說明這個問題,來做個試驗:打開tomcat的工作目錄下正在運行的工程目錄:我的是E:\web\apache-tomcat-8.0.26\work\Catalina\localhost\E_shop,裏面只有一個org的文件夾,其他什麼也沒有,現在我們重啓tomcat服務器,注意觀察這裏面的變化,當服務器停掉後,這個該目錄下多了個SESSION.ser文件,服務器重啓成功後,該文件又消失了。如下:

        所以,如果項目中的POJO實現了Serializable接口,當反序列化的時候就能找到剛剛序列化時候的POJO,原來session中的內容就能成功反序列化,session還是原來的session,這樣原來頁面的東西還在,刷新後還是繼續上次的操作。如果POJO沒有被實例化,那麼在session發序列化的時候當然就沒有了這些POJO了。下面看一下我的項目中的部分POJO,如下:

        最後總結一下:

        1. 容器關閉後session並沒有消失,而是被持久化到了硬盤裏;

        2. 如果項目中的POJO實現了Serializable接口,那麼會跟着session一起被持久化到硬盤,在反序列化的時候會成功還原;

        3. 要想服務器重啓後,還是原來的session,還繼續緊接着原來的頁面操作的話,就需要序列化項目中的POJO。



6、相關注意事項

a)當一個父類實現序列化,子類自動實現序列化,不需要顯式實現Serializable接口;
b)當一個對象的實例變量引用其他對象,序列化該對象時也把引用對象進行序列化;
c)並非所有的對象都可以序列化,,至於爲什麼不可以,有很多原因了,比如:

  1.安全方面的原因,比如一個對象擁有private,public等field,對於一個要傳輸的對象,比如寫到文件,或者進行rmi傳輸 等等,在序列化進行傳輸的過程中,這個對象的private等域是不受保護的。
  2. 資源分配方面的原因,比如socket,thread類,如果可以序列化,進行傳輸或者保存,也無法對他們進行重新的資源分 配,而且,也是沒有必要這樣實現。

 

因爲session是用來傳輸各種值和對象的 而對象不能通過網絡直接傳輸 所以必須序列化,

如果不序列化的話,可能導致網絡中傳輸失敗.

序列化的方法很簡單,只要實現Java.io.serialia就可以了,並且加上唯一標識即可。