在上篇中介紹了SpringCloud Zuul路由網關的基本使用版本,本篇則介紹基於SpringCloud(基於SpringBoot2.x,.SpringCloud Finchley版)中的路由網關的過濾器Filter以及異常處理的教程。html
過濾器概述git
Zuul的中心是一系列過濾器,可以在HTTP請求和響應的路由過程當中執行一系列操做。github
如下是Zuul過濾器的主要特徵:spring
過濾器目前用Groovy編寫,儘管Zuul支持任何基於JVM的語言。每一個Filter的源代碼都寫入Zuul服務器上的一組指定目錄,這些目錄會按期輪詢更改。更新的過濾器從磁盤讀取,動態編譯到正在運行的服務器中,並由Zuul爲每一個後續請求調用。json
過濾器類型與請求生命週期瀏覽器
Zuul大部分功能都是經過過濾器來實現的。Zuul中定義了四種標準過濾器類型,這些過濾器類型對應於請求的典型生命週期。springboot
官網Wiki 提供的四種過濾器的生命週期圖。服務器
注:此段來之Netflix / zuul的官網Wiki,地址:https://github.com/Netflix/zuul/wiki/How-it-Works。app
開發環境負載均衡
注:不必定非要用上述的版本,能夠根據狀況進行相應的調整。須要注意的是SpringBoot2.x之後,jdk的版本必須是1.8以上!
因爲在上一篇中咱們已經完成了Zuul路由網關的基本功能實現,因此服務端這塊咱們能夠直接把以前的項目拿來直接使用,而後更改相應的名稱以及相關代碼便可。
這裏咱們來編寫自定義一個Filter實現類,看看該類是如何工做的。
在編寫該類的時候,發現自定義的Filter類須要繼承ZuulFilter
這個類,咱們查看該類的源碼,發現了該類定義了兩個抽象的方法,而且該類實現了IZuulFilter
該接口,該接口也定義了兩個方法,咱們就來看看這幾個方法究竟是幹嗎的吧。
ZuulFilter源碼:
filterType
這個方法表示按類型對過濾器進行分類,分別是pre、post、route和error,在FilterConstants
這個常量類中已經進行定義了,其意義在上述的Filter請求的典型生命週期已經進行過說明了。
filterOrder
這個方法表示Filter執行的順序,數值越小優先級越高。
IZuulFilter
shouldFilter
該方法表示是否執行該過濾器,也能夠說是該過濾器的一個開關。
run
過濾器的具體邏輯。在該函數中,咱們能夠實現自定義的過濾邏輯,來肯定是否要攔截當前的請求,不對其進行後續的路由,或是在請求路由返回結果以後,對處理結果作一些加工等。
看完上述的源碼以後,這裏咱們再來編寫自定的Filter代碼。
首先繼承ZuulFilter
該類,而後實現裏面的方法。
首先是shouldFilter
方法,這裏咱們就直接返回true;
而後是filterType
,這裏咱們就設置爲前置過濾器,在請求被路由以前調用。
繼而是filterOrder
,這裏咱們就設置0;
最後是run
,這是過濾器的核心業務代碼,這裏咱們就簡單一點,獲取請求的url,若是該url攜帶了token,咱們就讓他經過,不然直接攔截。
固然,咱們須要將該過濾類使用Bean註解使其生效。
那麼代碼以下:
自定義的Filter代碼:
@Component public class MyZuulFilter extends ZuulFilter{ @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); ctx.addZuulResponseHeader("Content-type", "text/json;charset=UTF-8"); ctx.getResponse().setCharacterEncoding("UTF-8"); System.out.println("請求地址:"+request.getRequestURI()); String token = request.getParameter("token"); String msg="請求成功!"; if(token==null) { ctx.setSendZuulResponse(false); msg="請求失敗!"; ctx.setResponseBody(msg); ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return msg; } @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 0; } @Bean public MyZuulFilter zuulFilter() { return new MyZuulFilter(); } }
Zuul除了能夠自定義過濾器以外,也能夠對異常結果進行處理,以保持返回值一致。在進行Zuul使用的時候發現了在發生了異常以後,會調用SendErrorFilter
異常過濾器,對異常常常處理,同時重定向至/error這個路徑中。因此若是咱們須要自定義對異常處理的話,繼承SendErrorFilter
該類就能夠實現了。咱們查看SendErrorFilter
源碼,其實也是繼承ZuulFilter
該類並實現裏面的一些方法,作的自定義異常封裝,其實也能夠把SendErrorFilter
該類當作一個自定義的過濾器。
因爲SendErrorFilter
是對ZuulFilter
類進行了二次封裝,因此咱們自定義的Error代碼只需繼承SendErrorFilter
改爲,而後實現其中的run方法便可。
自定義的Error代碼:
@Component public class MyErrorFilter extends SendErrorFilter{ @Override public Object run() { String msg="請求失敗!"; try{ RequestContext ctx = RequestContext.getCurrentContext(); ExceptionHolder exception = findZuulException(ctx.getThrowable()); System.out.println("錯誤信息:"+exception.getErrorCause()); msg+="error:"+exception.getErrorCause(); HttpServletResponse response = ctx.getResponse(); response.setCharacterEncoding("UTF-8"); response.getOutputStream().write(msg.getBytes()); }catch (Exception ex) { ex.printStackTrace(); ReflectionUtils.rethrowRuntimeException(ex); } return msg; } @Bean public MyErrorFilter errorFilter() { return new MyErrorFilter(); } }
這裏咱們還須要禁用SendErrorFilter
過濾器,否則是不會使用咱們自定的異常過濾器的。
在application.properties
添加以下配置:
zuul.SendErrorFilter.error.disable=true
這裏順便說下禁用過濾器的規則。組件實現的過濾器,知足執行條件時都是會執行的,若咱們想禁用某個過濾器時,能夠在配置文件中配置。
規則:
zuul.<SimpleClassName>.<filterType>.disable=true
說明:
SimpleClassName爲類名,filterType過濾器類型
固然,若是以爲上述的異常處理仍是不夠優雅的話,可使用ControllerAdvice
註解進行全局異常處理,該註解的使用示例能夠從我的的springboot項目中進行找到,地址:https://github.com/xuwujing/springBoot-study
在以前的關於springcloud中SpringCloud學習系列之三----- 斷路器(Hystrix)和斷路器監控(Dashboard)這篇文章中講解過服務的降級處理,其實這裏的處理也是相似,也就是某個服務沒法進行訪問的時候,進行回退處理。
這裏咱們自定義異常回退處理的代碼相對而已也比較簡單,只需實現FallbackProvider
該接口的方法既可。
該類的源碼以下:
getRoute
該方法主要是指定須要回退服務的名稱。
fallbackResponse
該方法提供基於執行失敗緣由並進行回退響應。
瞭解以後該源碼以後,咱們再來編寫一個自定義異常回退處理的類。
自定義的Fallback代碼:
@Component public class MyFallback implements FallbackProvider { private static final String SERVER_NAME="springcloud-zuul-filter-server2"; @Override public String getRoute() { return SERVER_NAME; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { //標記不一樣的異常爲不一樣的http狀態值 if (cause instanceof HystrixTimeoutException) { return response(HttpStatus.GATEWAY_TIMEOUT); } else { //可繼續添加自定義異常類 return response(HttpStatus.INTERNAL_SERVER_ERROR); } } //處理 private ClientHttpResponse response(final HttpStatus status) { String msg="該"+SERVER_NAME+"服務暫時不可用!"; return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return status; } @Override public int getRawStatusCode() throws IOException { return status.value(); } @Override public String getStatusText() throws IOException { return status.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream(msg.getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } @Bean public MyFallback eurekaClientFallback() { return new MyFallback(); } }
客戶端這邊,咱們能夠把以前springcloud-zuul項目中的springcloud-zuul-server1
和springcloud-zuul-server2
拿來進行使用既可。
完成上述的代碼開發後,咱們來進行springcloud-zuul
的一系列自定義過濾測試。
首先依次啓動springcloud-zuul-filter-eureka
、springcloud-zuul-filter-gateway
、springcloud-zuul-filter-server1
和springcloud-zuul-filter-server2
這四個項目。其中9009是服務端springcloud-zuul-filter-gatewayr
的端口,9010是第一個客戶端springcloud-zuul-filter-server1
的端口,9011是第二個客戶端springcloud-zuul-filter-server2
的端口。
這裏順便說下路由網關的默認規則:http://ZUUL_HOST:ZUUL_PORT/微服務實例名(serverId)/**
,轉發至serviceId對應的微服務。好比在瀏覽器輸入:http://localhost:9009/springcloud-zuul-filter-server1/hello
地址, 它就會跳轉訪問到:http://localhost:9010/hello/
這個地址上。使用這個方式進行測試能夠幫助咱們更好的瞭解本篇文章的實現目的。
完成上述的項目啓動成功以後。
咱們首先在瀏覽器上輸入:
http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm
界面返回:
請求失敗!
這裏看到直接進行攔截了,並返回了相應的信息、
加上token以後在進行訪問
http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm&token=123
界面返回:
pancm,Hello World!
咱們按照咱們自定的規則進行訪問以後,發現能夠直接訪問到咱們想要訪問的服務上,所以該次測試也符合咱們的預期,達成了自定義過濾器的處理。
上述的功能測試ok以後,這裏咱們中止掉springcloud-zuul-filter-server1
服務,而後在瀏覽器上輸入:
http://localhost:9009/springcloud-zuul-filter-server1/hello?name=pancm&token=123
界面返回:
請求失敗!error:GENERAL請求失敗!error:GENERAL
注: 這裏實際是調用了兩次。
能夠看到此次測試也符合咱們的預期,達成了自定義異常的處理。
這裏咱們再來中止掉springcloud-zuul-filter-server2
服務,而後在瀏覽器上輸入:
http://localhost:9009/springcloud-zuul-filter-server2/hi?name=pancm&token=123
界面返回:
該springcloud-zuul-filter-server2服務暫時不可用!
能夠看到此次測試也符合咱們的預期,達成了 自定義異常回退處理的處理。這裏也順便說下,自定義該服務的異常和自定義異常回退處理最好不要在同一個服務同時使用,若是同時使用,會優先進行自定義異常回退處理的處理。
參考:
https://github.com/Netflix/zuul/wiki/How-it-Works
https://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#_router_and_filter_zuul
http://www.itmuch.com/spring-cloud/zuul/spring-cloud-zuul-filter/
https://blog.lqdev.cn/2018/10/17/SpringCloud/chapter-ten/#參考資料
基於SpringBoot2.x、SpringCloud的Finchley版本開發的地址:https://github.com/xuwujing/springcloud-study
若是感受項目不錯,但願能給個star,謝謝!
原創不易,若是感受不錯,但願留言推薦!您的支持是我寫做的最大動力! 版權聲明: 做者:虛無境 博客園出處:http://www.cnblogs.com/xuwujing CSDN出處:http://blog.csdn.net/qazwsxpcm 我的博客出處:http://www.panchengming.com