設計模式之 裝飾者模式

閱讀前請注意:本文章只是在學習過程中的記錄,如果有什麼錯誤的描述的,還請大佬們多多包涵,指點一下小白,多謝多謝。
閱讀前請注意:本文章只是在學習過程中的記錄,如果有什麼錯誤的描述的,還請大佬們多多包涵,指點一下小白,多謝多謝。
閱讀前請注意:本文章只是在學習過程中的記錄,如果有什麼錯誤的描述的,還請大佬們多多包涵,指點一下小白,多謝多謝。

一、定義

裝飾者模式 在不改動對象的情況下,動態地將功能附加到對象上。若要擴展功能,應該提供一個包裝器,把要擴展的對象包裝起來,以提供更加強大的功能。

二、結構

  1. Component 被包裝的組件,可單獨使用。
  2. Decorator 包裝器,擴展包裝的對象。

類圖

三、使用場景

  1. 擴展一個類的功能(這個繼承也可以做到)。
  2. 動態增加功能,動態撤銷功能。

四、實例

開發javaweb的時候我們經常會獲取 ServletRequest 這個對象,這個對象裏面有一個方法叫 getInputStream(),他是用來獲取用戶提交在body裏面的數據流,這個數據流一旦讀取就沒了。

image-20201103224025967

如果我們在過濾器或者攔截器之類的地方直接或者間接調用了getInputStream()這個方法,那麼在controller控制層就會獲取不到數據。

現在假設我們有一個過濾器就是要過濾body中的數據,這該怎麼處理?基於現在的 ServletRequest 的對象明顯是做不了這件事的。

這時我們就可以使用 裝飾者模式 動態的擴展 ServletRequest 對象,以實現我們現在的需求。

擴展步驟:

  1. 我們創建一個類 RepeatedlyRequestWrapper 繼承 HttpServletRequestWrapper

    (HttpServletRequestWrapper 是一個實現 ServletRequest 接口的裝飾者,我們也可以直接實現ServletRequest 接口,但是那樣我們要實現的方法太多了)。

image-20201103230148776

  1. 我們在 RepeatedlyRequestWrapper 中把結果保存下來,讓getInputStream()方法去獲取我們保存下來的內容,而不是去調用原始的方法,這樣getInputStream()就可以一直獲取到內容了。
import cn.hutool.extra.servlet.ServletUtil;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 構建可重複讀取inputStream的request
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    RepeatedlyRequestWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
        super(request);
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        body = ServletUtil.getBodyBytes(request);
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() {
        ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }
}

現在我們只需要在需要獲取 body 裏面的內容的時候,創建一個 RepeatedlyRequestWrapper ,傳入HttpServletRequest,然後調用調用getInputStream()就可以獲取到body數據流了。

當然傳入的HttpServletRequest對象必須要還存在body數據流,如果之前被獲取了,再包裝也不會有數據,所以我們應該在數據進來就立刻把HttpServletRequest進行包裝,以確保數據能準確的包裝。

這時我們定義一個權重最大的過濾器,在裏面包裝一下,讓後面調用getInputStream()能一值獲取到值。

import cn.hutool.core.util.StrUtil;
import org.springframework.http.MediaType;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Repeatable 過濾器
 */
public class RepeatableFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest && StrUtil.equalsAnyIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE, MediaType.TEXT_PLAIN_VALUE)) {
            requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, (HttpServletResponse) response);
        }
        if (null == requestWrapper) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }
}

我們現在註冊這個過濾器,把權重調到最大

@Bean
public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
    FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
    registration.setFilter(new RepeatableFilter());
    registration.addUrlPatterns("/*");
    registration.setName("repeatableFilter");
    // 權重調到最大值
    registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
    return registration;
}

五、測試

創建一個測試接口,在接口中獲取2次body中的內容。

@PostMapping("/get/body")
public void testRepeatedlyRequest(HttpServletRequest request) {
    System.out.println("第1次:" + ServletUtil.getBody(request));
    System.out.println("第2次:" + ServletUtil.getBody(request));
}

在沒有註冊Repeatable過濾器前:

image-20201103235638650

image-20201103235828397

在註冊了Repeatable過濾器後:

image-20201103235930654

image-20201104000059876

肥腸抱歉,這些源碼是在實際項目中的,不能提供源碼了,主要的代碼已經貼上了,如果有什麼問題可以私信或者加我微信。

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