Java源碼解析之Gateway請(qǐng)求轉(zhuǎn)發(fā)
Gateway請(qǐng)求轉(zhuǎn)發(fā)
本期我們主要還是講解一下Gateway,上一期我們講解了一下Gateway中進(jìn)行路由轉(zhuǎn)發(fā)的關(guān)鍵角色,過(guò)濾器和斷言是如何被加載的,上期鏈接://www.dhdzp.com/article/211824.htm
好了我們廢話不多說(shuō),開(kāi)始今天的Gateway請(qǐng)求轉(zhuǎn)發(fā)流程講解,為了在講解源碼的時(shí)候,以防止大家可能會(huì)迷糊,博主專門(mén)畫(huà)了一下源碼流程圖,鏈接地址://www.dhdzp.com/article/211824.htm
上一期我們已經(jīng)知道了相關(guān)類(lèi)的加載,今天直接從源碼開(kāi)始,大家可能不太了解webflux和reactor這種響應(yīng)式編程,畢竟不是主流,我們一直用的都是spring MVC,沒(méi)事,我們主要講解流程,不做過(guò)多的講解。
大家先看下面的代碼,我們今天主要的代碼入口就是這里:
public Mono<Void> handle(ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
ServerHttpRequest request = exchange.getRequest();
logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
}
if (this.handlerMappings == null) {
return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
第一步,我們先來(lái)看一看幾個(gè)主要的類(lèi)及其方法,F(xiàn)lux 表示的是包含 0 到 N 個(gè)元素的異步序列,Mono 表示的是包含 0 或者 1 個(gè)元素的異步序列,記住Flux是多個(gè)元素集合,Mono 是單個(gè)元素集合就很好理解以后的源碼了,以下方法注釋是博主為了大家好理解而寫(xiě)的,具體實(shí)際的意義還是需要大家自行Google學(xué)習(xí)了。
Mono.empty();創(chuàng)建一個(gè)空Mono對(duì)象;
Mono.just(**);創(chuàng)建一個(gè)**元素的對(duì)象;
Mono.then(**);在最后執(zhí)行,相當(dāng)于spring的aop后置通知一樣
開(kāi)始我們的第一步解析:mapping.getHandler(exchange);本方法主要做的是獲取路由,我們繼續(xù)看一看底層源碼:

getHandler

getHandlerInternal
//這里返回的是單個(gè)對(duì)象
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
return this.routeLocator
//我們一會(huì)主要看一下這個(gè)方法
.getRoutes()
//individually filter routes so that filterWhen error delaying is not a problem
.concatMap(route -> Mono
.just(route)
.filterWhen(r -> {
// add the current route we are testing
exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
//只返回一個(gè)符合斷言的路由配置,所以整個(gè)流程先匹配斷言
return r.getPredicate().apply(exchange);
})
//instead of immediately stopping main flux due to error, log and swallow it
.doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
.onErrorResume(e -> Mono.empty())
)
// .defaultIfEmpty() put a static Route not found
// or .switchIfEmpty()
// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
.next()
//TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("Route matched: " + route.getId());
}
validateRoute(route, exchange);
return route;
});
}
我們現(xiàn)在看看Route對(duì)象是怎么在getRoutes()創(chuàng)建的。
public Flux<Route> getRoutes() {
return this.routeDefinitionLocator.getRouteDefinitions() //這一步是從配置文件中讀取我們配置的路由定義
.map(this::convertToRoute)//這一步會(huì)加載我們配置給路由的斷言與過(guò)濾器形成路由對(duì)象
//TODO: error handling
.map(route -> {
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition matched: " + route.getId());
}
return route;
});
}
//關(guān)鍵的代碼在這里
private Route convertToRoute(RouteDefinition routeDefinition) {
//這兩步才會(huì)跟上一章節(jié)講解的如何加載斷言與過(guò)濾器有關(guān)聯(lián),大家可以自行查看底層源碼是如何查出來(lái)的對(duì)象的
AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
List<GatewayFilter> gatewayFilters = getFilters(routeDefinition);
//終于生成了路由對(duì)象
return Route.async(routeDefinition)
.asyncPredicate(predicate)
.replaceFilters(gatewayFilters)
.build();
}
這里大家要記住getHandlerInternal方法,生成了Mono.just(webHandler),仔細(xì)看webHandler是FilteringWebHandler對(duì)象,以后用到這個(gè)WebHandler,好了路由生成也選擇完畢了,我們應(yīng)該知道改請(qǐng)求是否符合我們配置的過(guò)濾器了,因?yàn)檫^(guò)濾器還沒(méi)用上,斷言只負(fù)責(zé)了選擇哪一個(gè)路由生效。
//我們看下一個(gè)主流程的方法
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
if (this.handlerAdapters != null) {
for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
if (handlerAdapter.supports(handler)) {
//這里走的是SimpleHandlerAdapter,可以自己debug發(fā)現(xiàn),也可以去找自動(dòng)配置類(lèi)找,這里就不講解了
return handlerAdapter.handle(exchange, handler);
}
}
}
return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
WebHandler webHandler = (WebHandler) handler;
//讓大家記住的那個(gè)FilteringWebHandler類(lèi),終于在這里起作用了。我們這回可以看看過(guò)濾器是如何起作用的
Mono<Void> mono = webHandler.handle(exchange);
return mono.then(Mono.empty());//過(guò)濾器處理完后,開(kāi)始處理mono.then方法
}
public Mono<Void> handle(ServerWebExchange exchange) {
Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
List<GatewayFilter> gatewayFilters = route.getFilters();//我們路由自己配置的過(guò)濾器
//加載全局過(guò)濾器
List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
combined.addAll(gatewayFilters);
//TODO: needed or cached?
AnnotationAwareOrderComparator.sort(combined);
//排序
if (logger.isDebugEnabled()) {
logger.debug("Sorted gatewayFilterFactories: "+ combined);
}
//形成過(guò)濾器鏈,開(kāi)始調(diào)用filter進(jìn)行過(guò)濾。這里剩下的我們就不講解,跟spring配置的過(guò)濾器鏈調(diào)用流程是一樣的
return new DefaultGatewayFilterChain(combined).filter(exchange);
}
至此,我們的請(qǐng)求流程基本完事了,我們?cè)賮?lái)看看幾個(gè)主要的全局過(guò)濾器配置。LoadBalancerClientFilter:負(fù)責(zé)獲取服務(wù)器ip的過(guò)濾器,NettyRoutingFilter:負(fù)責(zé)轉(zhuǎn)發(fā)我們請(qǐng)求的過(guò)濾器。
這里主要講解Gateway流程,關(guān)于Ribbon的代碼我們就不做主要講解了
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
//所以要加上lb前綴,才會(huì)走該過(guò)濾器
if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
//preserve the original url
addOriginalRequestUrl(exchange, url);
log.trace("LoadBalancerClientFilter url before: " + url);
//選擇實(shí)例
final ServiceInstance instance = choose(exchange);
......
return chain.filter(exchange);
}
看主要代碼即可,非必要的看了也暈。
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
.......
//通過(guò)httpClient發(fā)送請(qǐng)求獲取響應(yīng)
Mono<HttpClientResponse> responseMono = this.httpClient.request(method, url, req -> {
final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnEach)
.headers(httpHeaders)
.chunkedTransfer(chunkedTransfer)
.failOnServerError(false)
.failOnClientError(false);
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
proxyRequest.header(HttpHeaders.HOST, host);
}
if (properties.getResponseTimeout() != null) {
proxyRequest.context(ctx -> ctx.addHandlerFirst(
new ReadTimeoutHandler(properties.getResponseTimeout().toMillis(), TimeUnit.MILLISECONDS)));
}
return proxyRequest.sendHeaders() //I shouldn't need this
.send(request.getBody().map(dataBuffer ->
((NettyDataBuffer) dataBuffer).getNativeBuffer()));
});
return responseMono.doOnNext(res -> {
...
}
}
我們今天主要看的是Gateway的主要請(qǐng)求轉(zhuǎn)發(fā)的流程,像webflux這種我們沒(méi)有精力學(xué)習(xí)的,可以暫時(shí)略過(guò),畢竟也不是主流。我們今天最后總結(jié)一下。首先在Gateway這兩章的點(diǎn),項(xiàng)目啟動(dòng)時(shí)加載斷言與過(guò)濾器->接收請(qǐng)求時(shí)添加配置文件中的路由配置并生成路由對(duì)象->找到符合斷言的路由->除了個(gè)人配置的過(guò)濾器聯(lián)合全局過(guò)濾器生成過(guò)濾器鏈,并逐步過(guò)濾知道所有調(diào)用完成。
其中我們主要分析了兩個(gè)主要的全局過(guò)濾器:LoadBalancerClientFilter:負(fù)責(zé)獲取服務(wù)器ip的過(guò)濾器,NettyRoutingFilter:負(fù)責(zé)轉(zhuǎn)發(fā)我們請(qǐng)求的過(guò)濾器。
到此這篇關(guān)于Java源碼解析之Gateway請(qǐng)求轉(zhuǎn)發(fā)的文章就介紹到這了,更多相關(guān)Gateway請(qǐng)求轉(zhuǎn)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)RedisTemplate操作哈希數(shù)據(jù)
RedisTemplate是Spring Data Redis提供的一個(gè)用于操作Redis的模板類(lèi),本文主要介紹了java實(shí)現(xiàn)RedisTemplate操作哈希數(shù)據(jù),具有一定的參考價(jià)值,感興趣的可以了解一下2024-09-09
JAVA將中文轉(zhuǎn)換為拼音簡(jiǎn)單實(shí)現(xiàn)方法
拼音轉(zhuǎn)換是中文處理的常見(jiàn)需求,TinyPinyin、HanLP、pinyin4j是常用的本地拼音轉(zhuǎn)換庫(kù),各有特點(diǎn),開(kāi)發(fā)者可根據(jù)具體需求選擇合適的拼音轉(zhuǎn)換工具,需要的朋友可以參考下2024-10-10
JPA @Basic單表查詢?nèi)绾螌?shí)現(xiàn)大字段懶加載
這篇文章主要介紹了JPA @Basic單表查詢?nèi)绾螌?shí)現(xiàn)大字段懶加載的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Mybatis-Plus實(shí)現(xiàn)SQL攔截器的示例
這篇文章主要介紹了Mybatis-Plus實(shí)現(xiàn)一個(gè)SQL攔截器,通過(guò)使用SQL攔截器,開(kāi)發(fā)人員可以在執(zhí)行SQL語(yǔ)句之前或之后對(duì)其進(jìn)行修改或記錄,從而更好地控制和優(yōu)化數(shù)據(jù)庫(kù)操作,對(duì)Mybatis-Plus?SQL攔截器相關(guān)知識(shí)感興趣的朋友一起看看吧2023-05-05
20秒教你學(xué)會(huì)java?List函數(shù)排序操作示例
這篇文章主要為大家介紹了20秒教你學(xué)會(huì)List函數(shù)排序操作示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
Java棧和基礎(chǔ)隊(duì)列的實(shí)現(xiàn)詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)中的棧與隊(duì)列,在Java的時(shí)候,對(duì)于棧與隊(duì)列的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時(shí)候能夠有扎實(shí)的基礎(chǔ)能力。本文小編就來(lái)詳細(xì)說(shuō)說(shuō)Java中的棧與隊(duì)列,需要的朋友可以參考一下2022-02-02
Java集合Iterator迭代的實(shí)現(xiàn)方法
這篇文章主要介紹了Java集合Iterator迭代接口的實(shí)現(xiàn)方法,非常不錯(cuò),具有參考借鑒家,對(duì)Java 結(jié)合iterator知識(shí)感興趣的朋友一起看看吧2016-08-08
使用IDEA創(chuàng)建maven父子工程項(xiàng)目 (圖文)
本文主要介紹了使用IDEA創(chuàng)建maven父子工程項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04

