天天看點

設計模式之裝飾模式,session共享的底層原理

  還記得當初寫spring-session實作分布式叢集session的共享的時候,裡面有說到利用filter和HttpServletRequestWrapper可以定制自己的getSession方法,實作對session的控制,進而将session存放到統一的位置進行存儲,達到session共享的目的。但是具體是如何實作的沒有提及,今天我們就自己實作一個簡單的session共享。

  進入正題之前我們先來看看另外一個内容,放松下心情。盡管目前房價依舊很高,但還是阻止不了大家對新房的渴望和買房的熱情。如果大家買的是毛坯房,無疑還有一項艱巨的任務要面對,那就是裝修。對新房進行裝修并沒有改變房屋用于居住的本質,但它可以讓房子變得更漂亮、更溫馨、更實用、更能滿足居家的需求。在軟體設計中,我們也有一種類似新房裝修的技術可以對已有對象(新房)的功能進行擴充(裝修),以獲得更加符合使用者需求的對象,使得對象具有更加強大的功能。這種技術對應于一種被稱之為裝飾模式的設計模式。

  裝飾者模式又名包裝模式,以對用戶端透明的方式拓展對象的功能,能夠讓我們在不修改底層代碼的情況下,給我們的對象賦予新的職責。是繼承關系的一個替代方案。

設計模式之裝飾模式,session共享的底層原理

  裝飾模式中的角色:    

    抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。

    具體構件(ConcreteComponent)角色:定義一個将要接收附加責任的類。

    裝飾(Decorator)角色:持有一個構件(Component)對象的執行個體,并定義一個與抽象構件接口一緻的接口。

    具體裝飾(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任

    Component.java

設計模式之裝飾模式,session共享的底層原理
設計模式之裝飾模式,session共享的底層原理

View Code

    ConcreteComponent.java

設計模式之裝飾模式,session共享的底層原理
設計模式之裝飾模式,session共享的底層原理

    Decorator.java

設計模式之裝飾模式,session共享的底層原理
設計模式之裝飾模式,session共享的底層原理

    ConcreteDecorator.java

設計模式之裝飾模式,session共享的底層原理
設計模式之裝飾模式,session共享的底層原理

    更多詳情在spring-boot-test下的com.lee.decorator包下

    裝飾模式在Java語言中的最著名的應用莫過于Java I/O标準庫的設計了。由于Java I/O庫需要很多性能的各種組合,如果這些性能都是用繼承的方法實作的,那麼每一種組合都需要一個類,這樣就會造成大量性能重複的類出現。而如果采用裝飾模式,那麼類的數目就會大大減少,性能的重複也可以減至最少,是以裝飾模式是Java I/O庫的基本模式。

    由于Java I/O的對象衆多,這裡隻畫出InputStream的一部分

設計模式之裝飾模式,session共享的底層原理

    我們來捋一捋這個類圖在裝飾模式中角色的對應

      抽象構件(Component)角色:InputStream,這是一個抽象類,為各種子類型提供統一的接口

      具體構件(ConcreteComponent)角色:FileInputStream,實作了抽象構件角色所規定的接口

      裝飾(Decorator)角色:FilterInputStream,它實作了InputStream所規定的接口

      具體裝飾(ConcreteDecorator)角色:BufferedInputStream

  我們先來看看一個請求的發起到響應的時序圖

設計模式之裝飾模式,session共享的底層原理

  Interceptor依賴具體的架構(當然我們也可以自己實作),不是Servlet的内容,暫且先将其抛開,那麼相當于請求先經過Filter鍊,再到Servlet,然後servlet處理完之後,再經過Filter鍊傳回給浏覽器。

  此時我們要對session的擷取進行定制,我們能怎麼處理?兩種選擇,一是從Servlet入手,二是從Filter入手。那我們想一想,從Servlet入手可行嗎?可行,隻是可行性非常低,因為我們需要定制的東西就太多了,容器的那套Servlet規範實作我們都需要自己來實作了。如果從Filter入手,我們可以繼續沿用容器的那套實作,并從中插入我們的定制内容,那麼改動的内容就很少了。具體如何實作,我們一起往下看

      在實作我們自己的session管理之前,我們先來看看session在servlet容器中的建立。

      用戶端第一次請求request.getSession()時,也就是說用戶端的請求中服務端第一次調用request.getSession()時,伺服器會建立了Session對象并儲存在servlet容器的session集合中,同時生成一個Session id,并通過響應頭的Set-Cookie指令,向用戶端發送要求設定cookie的響應(cookie中設定Session id資訊),用戶端收到響應後,在用戶端設定了一個JSESSIONID=XXXXXXX的cookie資訊;接下來用戶端每次向伺服器發送請求時,請求頭都會帶上該cookie資訊(包含Session id),那麼之後的每次請求都能從servlet容器的session集合中找到用戶端對應的session了,這樣也就相當于保持了使用者與伺服器的互動狀态。     

      注意:

        第一次請求request.getSession()時,請求頭沒帶session id的資訊,響應頭中包括設定session id的cookie設定指令;之後用戶端的請求(不管服務端時候調用request.getSession()),請求頭都有session id資訊,而響應頭再也不會有設定session id的cookie設定指令

        session以及session id是在第一次調用request.getSession()時建立的(session過期另說,不是本文内容)

        不同容器的session id名稱可能不一樣,JSESSIONID是tomcat中session id的預設名

      不依賴任何架構,就用Filter + HttpServletRequestWrapper實作我們自己的簡單session管理。自定義Filter的作用是在請求到達Servlet之前,我們将HttpServletRequest封裝成我們自己的HttpServletRequestWrapper實作類:CustomizeSessionHttpServletRequest,那麼到達Servlet的HttpServletRequest對象實際上是CustomizeSessionHttpServletRequest;我們重寫CustomizeSessionHttpServletRequest的getSession方法,使其從我們自己的session容器中擷取,進而實作session的自定義管理。為了實作同一會話的效果,在建立session的時候,需要往response中添加cookie,儲存session id,下次請求的時候,浏覽器會将cookie資訊傳過來,我們去cookie中擷取session id,根據session id取session容器擷取session,這樣就能保證同一會話效果了。

      具體代碼這裡就不貼了,大家去檢視customize-session,效果如下

設計模式之裝飾模式,session共享的底層原理

      先通路http://localhost:8083/customize-session/test,此時是沒有産生session的,http://localhost:8083/customize-session/請求的是index.jsp,jsp請求了内置對象session,此時産生session,并讓浏覽器設定緩存,那麼之後的每次請求都會帶上包含session id的緩存。

      

設計模式之裝飾模式,session共享的底層原理

      ServletRequestWrapper中有成員變量ServletRequest request;

      不是嚴格意義上的裝飾模式

      抽象構件(Component)角色:ServletRequest

      具體構件(ConcreteComponent)角色:無

      裝飾(Decorator)角色:ServletRequestWrapper

      具體裝飾(ConcreteDecorator)角色:CustomizeHttpServletRequest

  1、裝飾模式

    文中裝飾模式講的不是很細,大家如果有什麼不懂的地方可以去我參考的兩本的兩本書中尋找更詳細的資訊。

    jdk源碼中,I/O标準庫大量用到了裝飾模式和擴充卡模式,有興趣的小夥伴可以去詳細的看看。

  2、自定義session管理

    Filter攔截請求,将HttpServletRequest封裝成我們自己的CustomizeSessionHttpServletRequest,進而插入我們的session建立與擷取邏輯,因為session的擷取方式往往是:request.getSession();

    往response中添加cookie,需要在response送出之前,否則添加無效;

    另外我們自定義了HttpSession:CustomizeSession,目的是為了更好地控制session

  3、不足

    首先強調一點:方向與思路是沒錯的!

    目前隻是實作了session的建立與擷取,實作的還比較一般,提升空間比較大;session管理還包括:session過期、session重新整理等;另外session的存儲在本文中寫死了,沒有對外送出接口實作多方式存儲,好的方式應該是對外提供接口并提供預設實作。

  4、目的

    寫本文的目的隻是讓大家對自定義session的管理有個簡單的認知,如果直接從shiro的session管理,或者spring-session的session管理入口,我們可能不知道如何去閱讀,畢竟這兩者是個成熟的體系,涉及的内容很多,我們可能會望而卻步了;但不管怎樣,實作方式都是一樣的,隻是shiro、spring-session在此基礎上進行各種内容豐富,使得體系愈發成熟。

    為我的shiro源碼篇 - shiro的session共享,你值得擁有做準備

  《Head First 設計模式》

  《Java與模式》

繼續閱讀