我最新最全的文章都在 南瓜慢說 www.pkslow.com ,歡迎大家來喝茶!
1 不一樣的世界
在常規的Spring Web
項目中,我們要獲取Request
對象是非常方便的,不少庫都提供了靜態方法來獲取。獲取代碼如下:
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
// get the request
HttpServletRequest request = requestAttributes.getRequest();
在類RequestContextHolder
提供了靜態方法,也就意味着你可以在任何地方調用。而它使用了ThreadLocal
來保存Request
對象,也就是不同線程是可以獲取各自的Request對象。
但在響應式WebFlux
的世界裏,並沒有提供類似的Holder
類,而WebFlux
是無法感知線程的,任何一個線程可以在任何時候處理任何請求,如果它覺得切換當前線程更有效率,它就會這麼做。但在Servlet Based
的應用裏,它會爲某個請求安排一個線程去處理完整個過程。
這個巨大的差別,意味着不能簡單地通過ThreadLocal
來保存和獲取Request
了。
2 先保存,再獲取
爲了在後面可以方便獲得Request
對象,我們就需要在開始的時候把它存在一個可以使用、並且是相同scope
的容器裏。這裏需要解決兩個關鍵問題:
(1)Request
對象從何而來;
(2)存在哪裏?
針對問題(1), 我們可以回想什麼時候會出現Request
對象,最容易想得到的就是WebFilter
了,它的方法簽名如下:
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain);
我們可以通過ServerWebExchange
直接獲取到Request
對象:
ServerHttpRequest request = exchange.getRequest();
而因爲Filter
是可以先於應用邏輯執行的,所以滿足要求,問題(1)解決。
針對問題(2),需要一個與Reavtive
請求相同範圍的容器,reactor.util.context.Context
可以滿足需求。查看reactor
的官方文檔(https://projectreactor.io/docs/core/release/reference/#context )可見下面這段話:
Since version
3.1.0
, Reactor comes with an advanced feature that is somewhat comparable toThreadLocal
but can be applied to aFlux
or aMono
instead of aThread
. This feature is calledContext
.
並且官網也給出了爲何ThreadLocal
在某些場景不適用的解釋,有興趣可以看看。
3 代碼實現
3.1 WebFilter獲取並保存
首先,在WebFilter
中獲取Request
對象並保存,代碼如下:
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class ReactiveRequestContextFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
return chain.filter(exchange)
.subscriberContext(ctx -> ctx.put(ReactiveRequestContextHolder.CONTEXT_KEY, request));
}
}
從ServerWebExchange
中獲取到ServerHttpRequest
對象,再通過put
方法把它放進Context
裏。
3.2 工具類Holder
實現一個工具類來提供靜態方法,在Filter
後的任何場景都可以使用:
public class ReactiveRequestContextHolder {
public static final Class<ServerHttpRequest> CONTEXT_KEY = ServerHttpRequest.class;
public static Mono<ServerHttpRequest> getRequest() {
return Mono.subscriberContext()
.map(ctx -> ctx.get(CONTEXT_KEY));
}
}
3.3 在Controller中使用
我們嘗試在Controller
中使用ReactiveRequestContextHolder
來獲取Request
:
@RestController
public class GetRequestController {
@RequestMapping("/request")
public Mono<String> getRequest() {
return ReactiveRequestContextHolder.getRequest()
.map(request -> request.getHeaders().getFirst("user"));
}
}
上面方法獲取了Request
對象,然後再獲取了Request
中的Header
。
啓動應用,測試如下:
$ curl http://localhost:8088/request -H 'user: pkslow'
pkslow
$ curl http://localhost:8088/request -H 'user: larry'
larry
$ curl http://localhost:8088/request -H 'user: www.pkslow.com'
www.pkslow.com
可以成功獲取請求頭user
。
4 總結
代碼請查看:https://github.com/LarryDpk/pkslow-samples
歡迎關注微信公衆號<南瓜慢說>,將持續爲你更新...
多讀書,多分享;多寫作,多整理。