1. 程式人生 > >spring-boot-2.0.3不一樣系列之番外篇

spring-boot-2.0.3不一樣系列之番外篇

前言

  還記得當初寫spring-session實現分散式叢集session的共享的時候,裡面有說到利用filter和HttpServletRequestWrapper可以定製自己的getSession方法,實現對session的控制,從而將session存放到統一的位置進行儲存,達到session共享的目的。但是具體是如何實現的沒有提及,今天我們就從原始碼的角度來看看shiro的session共享實現。

  路漫漫其修遠兮,吾將上下而求索!

裝飾模式

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

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

  裝飾模式類圖

  裝飾模式中的角色:    

    抽象構件(Component)角色:給出一個抽象介面,以規範準備接收附加責任的物件。    具體構件(ConcreteComponent)角色:定義一個將要接收附加責任的類。    裝飾(Decorator)角色:持有一個構件(Component)物件的例項,並定義一個與抽象構件介面一致的介面。    具體裝飾(ConcreteDecorator)角色:負責給構件物件“貼上”附加的責任

  原始碼實現

    Component.java

public interface Component {

    void sampleOperation();
}
View Code

    ConcreteComponent.java

public class ConcreteComponent implements Component {

    @Override
    public void sampleOperation() {
        // 寫具體業務程式碼
        System.out.println("我是ConcreteComponent");
    }
}
View Code

    Decorator.java

public class Decorator implements Component {

    private Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public void sampleOperation() {

        // 委派給具體的構建
        component.sampleOperation();
    }
}
View Code

    ConcreteDecorator.java

public class ConcreteDecorator extends Decorator{

    public ConcreteDecorator(Component component) {
        super(component);
    }

    @Override
    public void sampleOperation() {
        // 寫相關的業務程式碼
        System.out.println("呼叫component方法之前業務處理");

        super.sampleOperation();

        // 寫相關的業務程式碼
        System.out.println("呼叫component方法之後業務處理");
    }
}
View Code

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

  jdk中的案例

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

    由於Java I/O的物件眾多,這裡只畫出InputStream的一部分

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

      抽象構件(Component)角色:InputStream,這是一個抽象類,為各種子型別提供統一的介面      具體構件(ConcreteComponent)角色:FileInputStream,實現了抽象構件角色所規定的介面      裝飾(Decorator)角色:FilterInputStream,它實現了InputStream所規定的介面      具體裝飾(ConcreteDecorator)角色:BufferedInputStream

自定義session管理

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

  Interceptor依賴具體的框架(當然我們也可以自己實現),不是Servlet的內容,暫且先將其拋開,那麼相當於請求先經過Filter鏈,再到Servlet,然後servlet處理完之後,再經過Filter鏈返回給瀏覽器。

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

  定製session管理

    servlet容器的session建立

      在實現我們自己的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的預設名

    自定義session的建立與獲取

      不依賴任何框架,就用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,這樣就能保證同一會話效果了。

      先訪問http://localhost:8083/customize-session/test,此時是沒有產生session的,http://localhost:8083/customize-session/請求的是index.jsp,jsp請求了內建物件session,此時產生session,並讓瀏覽器設定快取,那麼之後的每次請求都會帶上包含session id的快取。

    關鍵部分類圖

      

      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在此基礎上進行各種內容豐富,使得體系愈發成熟。

    為我的另外一篇博文做準備

參考

  《Head First 設計模式》

  《Java與模式》