beaninfo详解源码解析 java_Java后端精选技术:源码解析Spring Cloud Zuul
Zuul 架構(gòu)圖
在zuul中, 整個請求的過程是這樣的,首先將請求給zuulservlet處理,zuulservlet中有一個zuulRunner對象,該對象中初始化了RequestContext:作為存儲整個請求的一些數(shù)據(jù),并被所有的zuulfilter共享。zuulRunner中還有 FilterProcessor,FilterProcessor作為執(zhí)行所有的zuulfilter的管理器。FilterProcessor從filterloader 中獲取zuulfilter,而zuulfilter是被filterFileManager所加載,并支持groovy熱加載,采用了輪詢的方式熱加載。有了這些filter之后,zuulservelet首先執(zhí)行的Pre類型的過濾器,再執(zhí)行route類型的過濾器,最后執(zhí)行的是post 類型的過濾器,如果在執(zhí)行這些過濾器有錯誤的時候則會執(zhí)行error類型的過濾器。執(zhí)行完這些過濾器,最終將請求的結(jié)果返回給客戶端。
zuul工作原理源碼分析
在之前已經(jīng)講過,如何使用zuul,其中不可缺少的一個步驟就是在程序的啟動類加上@EnableZuulProxy,該EnableZuulProxy類代碼如下:
其中,引用了ZuulProxyConfiguration,跟蹤ZuulProxyConfiguration,該類注入了DiscoveryClient、RibbonCommandFactoryConfiguration用作負載均衡相關的。注入了一些列的filters,比如PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter,代碼如如下:
它的父類ZuulConfiguration ,引用了一些相關的配置。在缺失zuulServlet bean的情況下注入了ZuulServlet,該類是zuul的核心類。
同時也注入了其他的過濾器,比如ServletDetectionFilter、DebugFilter、Servlet30WrapperFilter,這些過濾器都是pre類型的。
它也注入了post類型的,比如 SendResponseFilter,error類型,比如 SendErrorFilter,route類型比如SendForwardFilter,代碼如下:
初始化ZuulFilterInitializer類,將所有的filter 向FilterRegistry注冊。
而FilterRegistry管理了一個ConcurrentHashMap,用作存儲過濾器的,并有一些基本的CURD過濾器的方法,代碼如下:
FilterLoader類持有FilterRegistry,FilterFileManager類持有FilterLoader,所以最終是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager到開啟了輪詢機制,定時的去加載過濾器,代碼如下:
Zuulservlet作為類似于Spring MVC中的DispatchServlet,起到了前端控制器的作用,所有的請求都由它接管。它的核心代碼如下:
跟蹤init(),可以發(fā)現(xiàn)這個方法為每個請求生成了RequestContext,RequestContext繼承了ConcurrentHashMap,在請求結(jié)束時銷毀掉該RequestContext,RequestContext的生命周期為請求到zuulServlet開始處理,直到請求結(jié)束返回結(jié)果。 RequestContext類在存儲了很多重要的信息,包括HttpServletRequest、HttpServletRespons、ResponseDataStream、ResponseStatusCode等。 RequestContext對象在處理請求的過程中,一直存在,所以這個對象為所有Filter共享。
從ZuulServlet的service()方法可知,它是先處理pre()類型的處理器,然后在處理route()類型的處理器,最后再處理post類型的處理器。
首先來看一看pre()的處理過程,它會進入到ZuulRunner,該類的作用是將請求的HttpServletRequest、HttpServletRespons放在RequestContext類中,并包裝了一個FilterProcessor,代碼如下:
而FilterProcessor類為調(diào)用filters的類,比如調(diào)用pre類型所有的過濾器:
跟蹤runFilters()方法,可以發(fā)現(xiàn),它最終調(diào)用了FilterLoader的getFiltersByType(sType)方法來獲取同一類的過濾器,然后用for循環(huán)遍歷所有的ZuulFilter,執(zhí)行了 processZuulFilter()方法,跟蹤該方法可以發(fā)現(xiàn)最終是執(zhí)行了ZuulFilter的方法,最終返回了該方法返回的Object對象。
route、post類型的過濾器的執(zhí)行過程和pre執(zhí)行過程類似。
Zuul默認過濾器
默認的核心過濾器一覽表
Zuul默認注入的過濾器,它們的執(zhí)行順序在FilterConstants類,我們可以先定位在這個類,然后再看這個類的過濾器的執(zhí)行順序以及相關的注釋,可以很輕松定位到相關的過濾器,也可以直接打開 spring-cloud-netflix-core.jar的 zuul.filters包,可以看到一些列的filter,現(xiàn)在我以表格的形式,列出默認注入的filter.
過濾器的order值越小,就越先執(zhí)行,并且在執(zhí)行過濾器的過程中,它們共享了一個RequestContext對象,該對象的生命周期貫穿于請求,可以看出優(yōu)先執(zhí)行了pre類型的過濾器,并將執(zhí)行后的結(jié)果放在RequestContext中,供后續(xù)的filter使用,比如在執(zhí)行PreDecorationFilter的時候,決定使用哪一個route,它的結(jié)果的是放在RequestContext對象中,后續(xù)會執(zhí)行所有的route的過濾器,如果不滿足條件就不執(zhí)行該過
濾器的run方法。最終達到了就執(zhí)行一個route過濾器的run()方法。
而error類型的過濾器,是在程序發(fā)生異常的時候執(zhí)行的。
post類型的過濾,在默認的情況下,只注入了SendResponseFilter,該類型的過濾器是將最終的請求結(jié)果以流的形式輸出給客戶單。
現(xiàn)在來看SimpleHostRoutingFilter是如何工作?
進入到SimpleHostRoutingFilter類的方法的run()方法,核心代碼如下:
查閱這個類的全部代碼可知,該類創(chuàng)建了一個HttpClient作為請求類,并重構(gòu)了url,請求到了具體的服務,得到的一個CloseableHttpResponse對象,并將CloseableHttpResponse對象的保存到RequestContext對象中。并調(diào)用了ProxyRequestHelper的setResponse方法,將請求狀態(tài)碼,流等信息保存在RequestContext對象中。
現(xiàn)在來看SendResponseFilter是如何工作?
這個過濾器的order為1000,在默認且正常的情況下,是最后一個執(zhí)行的過濾器,該過濾器是最終將得到的數(shù)據(jù)返回給客戶端的請求。
在它的run()方法里,有兩個方法:addResponseHeaders()和writeResponse(),即添加響應頭和寫入響應數(shù)據(jù)流。
其中writeResponse()方法是通過從RequestContext中獲取ResponseBody獲或者ResponseDataStream來寫入到HttpServletResponse中的,但是在默認的情況下ResponseBody為null,而ResponseDataStream在route類型過濾器中已經(jīng)設置進去了。具體代碼如下:
如何在zuul上做日志處理
由于zuul作為api網(wǎng)關,所有的請求都經(jīng)過這里,所以在網(wǎng)關上,可以做請求相關的日志處理。 我的需求是這樣的,需要記錄請求的 url,ip地址,參數(shù),請求發(fā)生的時間,整個請求的耗時,請求的響應狀態(tài),甚至請求響應的結(jié)果等。 很顯然,需要實現(xiàn)這樣的一個功能,需要寫一個ZuulFliter,它應該是在請求發(fā)送給客戶端之前做處理,并且在route過濾器路由之后,在默認的情況下,這個過濾器的order應該為500-1000之間。那么如何獲取這些我需要的日志信息呢?找RequestContext,在請求的生命周期里這個對象里,存儲了整個請求的所有信息。
現(xiàn)在編碼,在代碼的注釋中,做了詳細的說明,代碼如下:
現(xiàn)在讀者也許有疑問,如何得到的statrtTime,即請求開始的時間,其實這需要另外一個過濾器,在網(wǎng)絡請求route之前(大部分耗時都在route這一步),在過濾器中,在RequestContext存儲一個時間即可,另寫一個過濾器,代碼如下:
可能還有這樣的需求,我需要將響應結(jié)果,也要存儲在log中,在之前已經(jīng)分析了,在route結(jié)束后,將從具體服務獲取的響應流存儲在RequestContext中,在SendResponseFilter過濾器寫入在HttpServletResponse中,最終返回給客戶端。那么我只需要在SendResponseFilter寫入響應流之前把響應流寫入到 log日志中即可,那么會引發(fā)另外一個問題,因為響應流寫入到 log后,RequestContext就沒有響應流了,在SendResponseFilter就沒有流輸入到HttpServletResponse中,導致客戶端沒有任何的返回數(shù)據(jù),那么解決的辦法是這樣的:
InputStream inputStream =RequestContext.getCurrentContext().getResponseDataStream();InputStream newInputStream= copy(inputStream);transerferTolog(inputStream);RequestContext.getCurrentContext().setResponseDataStream(newInputStream);從RequestContext獲取到流之后,首先將流 copy一份,將流轉(zhuǎn)化下字符串,存在日志中,再set到RequestContext中, 這樣SendResponseFilter就可以將響應返回給客戶端。這樣的做法有點影響性能,如果不是字符流,可能需要做更多的處理工作。
原文地址:https://dwz.cn/9tKgRhJF作者:方志朋 創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎
總結(jié)
以上是生活随笔為你收集整理的beaninfo详解源码解析 java_Java后端精选技术:源码解析Spring Cloud Zuul的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jdbc 批量insert_JDBC相关
- 下一篇: linux 休眠定时唤醒_Linux重启