閱讀前請注意:本文章只是在學習過程中的記錄,如果有什麼錯誤的描述的,還請大佬們多多包涵,指點一下小白,多謝多謝。
閱讀前請注意:本文章只是在學習過程中的記錄,如果有什麼錯誤的描述的,還請大佬們多多包涵,指點一下小白,多謝多謝。
閱讀前請注意:本文章只是在學習過程中的記錄,如果有什麼錯誤的描述的,還請大佬們多多包涵,指點一下小白,多謝多謝。
一、定義
裝飾者模式 在不改動對象的情況下,動態地將功能附加到對象上。若要擴展功能,應該提供一個包裝器,把要擴展的對象包裝起來,以提供更加強大的功能。
二、結構
- Component 被包裝的組件,可單獨使用。
- Decorator 包裝器,擴展包裝的對象。
三、使用場景
- 擴展一個類的功能(這個繼承也可以做到)。
- 動態增加功能,動態撤銷功能。
四、實例
開發javaweb的時候我們經常會獲取 ServletRequest 這個對象,這個對象裏面有一個方法叫 getInputStream(),他是用來獲取用戶提交在body裏面的數據流,這個數據流一旦讀取就沒了。
如果我們在過濾器或者攔截器之類的地方直接或者間接調用了getInputStream()這個方法,那麼在controller控制層就會獲取不到數據。
現在假設我們有一個過濾器就是要過濾body中的數據,這該怎麼處理?基於現在的 ServletRequest 的對象明顯是做不了這件事的。
這時我們就可以使用 裝飾者模式 動態的擴展 ServletRequest 對象,以實現我們現在的需求。
擴展步驟:
我們創建一個類 RepeatedlyRequestWrapper 繼承 HttpServletRequestWrapper
(HttpServletRequestWrapper 是一個實現 ServletRequest 接口的裝飾者,我們也可以直接實現ServletRequest 接口,但是那樣我們要實現的方法太多了)。
- 我們在 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過濾器前:
在註冊了Repeatable過濾器後:
肥腸抱歉,這些源碼是在實際項目中的,不能提供源碼了,主要的代碼已經貼上了,如果有什麼問題可以私信或者加我微信。