Spring-Session 基礎知識點 和 源碼分析(下)

一、Spring-Session的使用場景:

 

場景一 、相同域名下相同項目實現session共享。

  1. 集羣部署之後我們需要使用spring-session。

原因: 我們tomcat 服務器集羣部署(處理動態資源)和nginx集羣部署(處理靜態資源)之後,配合我們的 主nginx負載均衡。我們的主nginx 根據 服務器的壓力或者某種規則來進行分發請求。一個用戶在一次會話當中,發出很多次請求,這些請求可能被分發到了 不同的 tomcat服務器進行處理,所以這時候需要 我們 spring-session 框架 將我們的session對象實現共享。

我們 9100 和 9200 部署 p2p項目模塊,在9300中部署dataservice項目模塊。

Nginx.conf 中配置的負載均衡:

我們的 127.0.0.1 是 windows 和 linux 通用的 地址,我們的 windows可以使用它,我們的linux也可以使用它,這個127.0.0.1 都代表本機的意思,代表windows本機或者linux本機。我們也可以在上面的配置中直接寫linux的地址:192.168.242.128 。

 

知識點補充:nginx重啓的命令。

 

  1. 向p2p項目中 添加 spring-Session框架所以來的jar包。不需要修改我們的pom文件,直接將jar導入到複製粘貼到 lib包中就可以了,因爲我們的項目已經上線了,pom文件中所有的jar都下載完成了。
  2. 向9100和9200端口的tomcat中的p2p項目的web.xml中添加spring-session全局過濾器。
  3. 向配置文件包中,添加 appliction-session配置文件,還有redis.properties配置文件。
  4. 不需要使用監聽器listener來加載我們的spring容器,因爲我們的springMVC已經將spring容器加載了。 就是在 配置中央調度器的時候加載了我們的 spring總配置文件從而加載了 spring容器了。

 

上面的步驟就實現了:

配置多個tomcat(集羣),然後使用nginx負載均衡。然後使用spring-session來實現我們的 session共享。因爲是將我們的session對象存放進我們的redis中,這個redis是一個公共的部分,所以所有的tomcat都可以有需要,自己去redis中獲取,但是我們存放的session最大空閒時間爲 30 分鐘。

 

 

場景二、同域名下不同項目實現session共享。 實質上和相同項目是一樣的。

假如 域名爲 www.myweb.com,項目都爲 p2p,那麼生成的session的sessionid存入cookie之後,這個cookie存放的位置是: myweb/com/www/ p2p/cookie對象   

如果 兩個不同的項目,一個是 pay一個是shop,我們第一次訪問pay,生成的session將sessionId存放進cookie中,然後這個cookie被存放在  myweb/com/www/pay/下面。

然後同一個用戶再發出一次請求訪問,但是我們的這次的請求是請求 訪問我們的shop項目,所以瀏覽器會從   myweb/com/www/shop/下面獲取 cookie ,但是不會從 myweb/com/www/pay/下面獲取cookie。

  Domain + Path = cookie的存放路徑。

意思是這樣的:

  1. Localhost:9200/pay/setsession 這個請求的時候,我們的瀏覽器是從 myweb/com/www/pay/這個包結構下面獲取cookie中的sessionId。
  2. Localhost:9200/shop/getsession 這個請求的時候,我們的瀏覽器是從Localhost:9200/shop/ 這個包結構下面獲取cookie中的sessionId。
  3. 存放在不同包結構下面的cookie,cookie保存的sessionId也肯定不一樣,從而導致從redis獲取的session也肯定不一樣。從而沒有達到session共享的效果。
  4. 所以我們 需要讓 每次發出請求後,返回的sessionid存放在 相同包結構下的cookie中,如:我們的cookie都放在根目錄下,也就是myweb/com/www/下面,這樣在一次會話中,發出所有的請求 瀏覽器都會去相同的包結構中獲取cookie中的sessionId,這樣就保證session的共享了。

 

這種情況造成的原因是:因爲我們的項目不同,項目不同所以項目名不同,項目名不同從而導致了 tomcat的上下文根 不同,從而導致cookie的存放位置不同。存放位置不同所以cookie就不同,裏面存放的sessionI就不同,從而session也不同,所以服務端就認爲這是兩個不同的請求。

 

********怎麼設置我們cookie的存放位置呢?

我們的 cookie的存放位置 與Domain 和 Path有關。

在 spring-session 核心類 中 添加 property 屬性。

這個核心類中有一個屬性:

意思是 cookie的序列化方式,這個屬性就是用來設置cookie的存放方式的,注意了:

核心類中的這個 cookie序列化屬性 只是用來 設置 cookie的存放的方式的。

這個類是用來設置 cookie的默認序列化方式,也就是專門用來設置cookie默認存放路徑的,注意了 是用來設置 cookie存放路徑的。

核心類的屬性和DefaultCookieSerializer這個類的屬性配合起來就可以 設置我們的 cookie默認存放路徑了 。

底層源碼:

private String getDomainName(HttpServletRequest request) {
    if (this.domainName != null) {
        return this.domainName;
    } else {
        if (this.domainNamePattern != null) {
            Matcher matcher = this.domainNamePattern.matcher(request.getServerName());
            if (matcher.matches()) {
                return matcher.group(1);
            }
        }

        return null;
    }
}

private String getCookiePath(HttpServletRequest request) {
    return this.cookiePath == null ? request.getContextPath() + "/" : this.cookiePath;
}

場景三、同根域名,不同二級子域名下的項目實現session共享。

 

www.p2p.com 域名中 p2p.com 就是頂級域名,也叫做根域名,也可以叫做一級域名。

 

www不是二級域名。當www被替換了,那麼替換的就是二級域名,如下:

Beijing.p2p.com  、 nanjing.p2p.com 、 tianjing.p2p.com

這三個域名中 beijing、tianjing、nanjing 是 二級域名 。

 

知識點補充;

在我們的 redis.conf中:

有一個 daemonize 屬性,屬性值爲 默認爲 no,意思是 不後臺啓動,我們如果想讓redis默認後臺啓動,那麼我們就 將我們的 redis.conf文件中的 daemonize屬性值修改爲 yes 。

然後就可以直接使用

來啓動redis了,就不用 再 從最後 加一個 & 號 。

 

這是提供給我們測試使用的三個域名,我們在本地啓動對應的tomcat,測試 根域名相同,但是二級域名不同,如何實現session的共享。

每個域名 對應 一個網站,每個網站 部署到兩個不同的tomcat服務器上,然後使用nginx進行負載均衡。

 

測試前的準備: 

我們需要修改 hosts文件,讓我們的域名 指向到 我們的本地來,也就是 這三個域名對應的ip地址爲127.0.0.1 。

 

根域名相同 ,二級域名不同,項目名不同:

      項目名不同 導致的session不共享,我們的解決方案是 將 所有的cookie存放進 /目錄下,也就是  根域名/二級域名/ cookie對象  這樣子存放我們的cookie 。

   當我們的 二級域名也不一樣的時候,只是單單的修改cookie存放位置是不行的,還要修改cookie的存放路徑,二級域名不同,根域名相同,那麼爲了保證每次請求保存的cookie相同,那麼需要將cookie存放在公共相同的區域,所以 存放路徑爲:  根域名

 

存放路徑 和 存放位置 相結合:  根域名/   直接將我們的cookie存放在根域名下。

 

 

 

總之,要保證 我們的 session共享,就需要保證cookie是同一個,保證從一個cookie就需要 每次 存 和 每次 取 cookie 都要從 同一個地方 獲取和存儲,這樣保證了cookie的位置。

如:

Beiijing.p2p.com:9100/pay/setsession 創建的session,而且在session中存放了xxx。

如何讓我們的 nanjing.p2p.com:9200/pay/getSession 獲取到 上面請求創建session,從而取到裏面xxx。 這裏就需要session共享,單單將session存放進redis是遠遠不夠的,雖然我們的session 被存放進了 一個 共享的位置。 但是每次請求的推送過來的 sessionId被存放進不同位置上的cookie,如果讓我們的  cookie也能放在一個共享的區域,每次存和取都能在同一個空間,這樣就能保證同一個用戶 對應 同一個cookie 對應 同一個session 。

 

 

***如何找到這個 公共的 共享區域呢?

分析我們的 兩個域名:

Beijing . p2p . com  對應瀏覽器中的包:  p2p/com/beijing/

Nanjing . p2p . com  對應瀏覽器中的包: p2p/com/nanjing/

包結構的公共區域爲: P2p/com/

(1)P2p/com 就是 domainName。

(2)/ 就是 cookiePath 。

補充的知識點:

我們的 瀏覽器有 【 cookie存儲 安全保護機制 】。

意思是: 我們不能自己給 上面這兩個域名 自定義命名一個 自己命名的共享空間,如:我如果設定 所有的 cookie存放進 abc/com/ 這裏。這樣子我們的 cookie不會被我們的瀏覽器存儲進去的,因爲 在域名-1 下創建出來 cookie對象,最後存放的時候 也要 認祖歸宗 落葉歸根 到 域名-1 的 【根域名/】 來進行存儲。 舉個例子:你總不能 向京東 發送一個訪問請求,然後將產生的cookie保存到 淘寶的域名根域名下面。

你訪問京東產生的cookie只能存放在 京東的域名下,不能存放在淘寶的域名下。這就是cookie存放安全保護機制 。

 

思路:

我們二級域名不一樣,那我們就存放在 相同的頂級域名下唄,哪裏相同我們就存在哪裏唄。

只要 domain+path 保持一致,兩次請求cookie的存取位置都是相同的,那麼就session共享了。

 *************項目名不相同,二級域名不相同,頂級域名相同,如何實現session共享,步驟如下:

場景四、不同根域名下的項目實現session共享。

如:

這種情況 我們的spring-session框架就不支持了,因爲 這種情況:存放 cookie的包 從 根部就已經不相同了,那麼根底下的子包就更不相同了,所以domain找不到 相同和共享的包空間了, 所以 我們在 spring-session的核心類中 定義 cookie 存放路徑屬性的時候,就沒辦法指定 屬性 domainName屬性的值,這個值不能指定 我們的session就沒有辦法共享,所以這種情況 如果 域名從根域名就開始不同了,那麼我們的 spring-session就不支持了。

 

我們 通過 www.p2p.com訪問我們的服務器 。那麼我們的生成的cookie只能往 p2p.com根域名下面去寫。

我們通過 www.web.com訪問我們的服務器,那麼生成的cookie只能往web.com根域名中去寫。

以此類推.......

(1)這種情況 我們 可以 使用 【單點登錄SSO】來解決我們的session共享問題。

(2)【單點登錄SSO】是什麼意思呢?

 多個系統應用中(如:天貓,淘寶,支付寶),用戶只需要登錄一次,其他與這個應用相互信任的應用都會自動登錄了。

就好像 我們 登錄了 支付寶,那麼我們的再登錄我們的 淘寶和天貓就可以點擊直接登錄了。

【單點登錄】的實現,我們必須開發一套單點登錄系統。一般很大的公司纔會有這樣的單點登錄系統,像阿里巴巴這樣纔有多個網站和應用。

 

 

Spring - Session框架 的 處理流程:

Spring-session框架需要運行必須擁有而且必不可少的東西:

  1. 全局過濾器:
  2. spring容器::使用 Listener監聽器 來 讀取我們的 spring 總配置文件 ,然後創建對應的 spring容器。
  3.  我們 用戶發出的請求 首先會被我們的 過濾器接收到 。

    過濾器DelegatingFilterProxy 繼承了 GenericFilterBean 這個類,然後GenericFilterBean這個類又實現了Filter接口,所以我們的DelegatingFilterProxy 這個類也是一個Filter類,也就是一個過濾器 。

    只要是過濾器就有一下三種方法: init()初始化方法 , doFilter()過濾方法 和 destory()關閉時銷燬用的方法 。(1)GenericFilterBean  類的 init初始化方法中:核心代碼:

    但是這個方法在我們的GenericFilterBean  類中,方法體中沒有添加任何的代碼。所以推測出我們的 子類spring-session全局過濾器DelegatingFilterProxy  應該是重寫了這個方法initFilterBean()。

    我們子類中 重寫這個方法的代碼如下:

    當我們的全局過濾器在初始化的時候就會調用這個這個方法。

    全局過濾器剛被加載的時候,會調用init方法,所以也會調用initFilterBean這個方法,這個方法中涉及這段代碼:

    剛開始 我們的 delegate對象和targetBeanName對象 肯定都爲空。

    這時候 是調用 getFilterName來獲取當前這個過濾器的名稱,然後將名稱賦值爲targetBeanName對象。

                          TargetBeanName = filter名稱                      ***到這一步我們的 獲取到了 Filter的名稱了 。

  4. 這段代碼是來找 web容器,也就是spring容器,通過FindWebApplicationContext方法。 ****到這一步我們 獲取到了spring容器了.方法initDelegate的源碼:

    使用 spring容器wac,來獲取到一個id爲TargetBeanName ,類型爲Filter過濾器的 bean對象。

    ***到這一步 我們的獲取到了 全局過濾器的 bean對象 。

  5. 初始化都做了什麼?

    也就是 我們的 全局過濾器初始化的時候:先獲取了 本過濾器的名稱,然後有獲取到了spring容器,然後根據名稱和類型獲取到了 過濾器對應的 bean對象 。

    上面的步驟初始化已經完成了

    下面是 發送請求的時候,過濾器會調用 doFilter方法:全局過濾器的父類是一個抽象類,內部沒有重寫doFilter方法。 那麼這個doFilter方法肯定是有 我們的全局過濾器 重寫的 。

  6. 全局過濾器中 重寫的 doFilter方法,如下:這個dofilter方法中有一個 代理類方法:

  7. 這個代理類方法的內部是:這個代理類方法的內部是:

    意思是:我們調用DelegatingProxy過濾器中的dofilter方法實質上是調用delegate中的dofilter方法。

    其實這個 invokeDelegate方法是一個代理。

    Delegate 就是 我們上面 從init初始化方法中獲取到的 目標過濾器,也就是web.xml中配置的過濾器。 也就是最終調用的是 spring容器中bean對象對應的filter中的dofilter方法。 這個代理類 代理的是名字叫做:filter過濾器。 這個過濾器是在我們的spring容器中的一個bean對象。所以真正工作的就是我們的是這個過濾器。

  8. 所以我們開始分析:springsessionrespositoryFilter過濾器: 這個類是我們 Spring-session框架   最核心  的 類 。

  9. 我們在 核心類的父類中找到了這個 真正工作的 過濾器:

  10. @Bean 註解 : Bean的id是方法名,class是方法的返回值類型。

  11. 由上面的源碼我們可以看出:

     

  12. 下面我們開始分析中的代碼內容: 這個過濾器中不存在 init方法,所以我們的 這個過濾器 不存在 初始化的問題,它繼承了父類中的dofilter方法,dofiler中的內容:

  13. 父類dofilter方法中的核心邏輯是:但是這個方法是一個抽象方法:,所以這個方法 應該在我們的子類 中被重寫。所以說子類調用父類的dofilter方法實質上是調用了doFilterInternal這個方法。

  14. 子類重寫的內容如下:

  15. 繼承關係:

  16. 這個是構造自己的 request 和response,將原來的  request 和response覆蓋掉。將 request 和response中的方法也給替換掉了,替換成Spring-session框架中的了。

  17. 這個方法就是用來將我們的session存放在redis中。

    這個方法中真正保存session的方法是save()方法:

  18. Save方法中使用的 saveDelta這個方法進行session保存的:

  19. 這個方法中最核心的代碼是:

  20. 這裏保存的是一個 hash結構的session。因爲是使用 putAll進行保存的:

    PutAll這個方法是在 spring-data-redis jar包中。

    然後這個方法中又調用了 jedis 的jar中的方法 hMset()方法,以爲操作redis都是用的我麼你的jedis進行間接操作的。

  21. 所以保存的session是hash結構:如下圖

  22. Spring-Session的執行流程:上面就是 Spring-Session的 全部操作過程。

  23. 最後需要注意的是:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章