Zuul源码解读

最近这段时间在使用Zuul,顺便简单阅读了一下源码。本文旨在对自己阅读到的源码做一点小结,以后日后回顾。不追求面面俱到,细致入微,看到哪里写到哪里吧

关于Zuul的介绍及基本使用就不在此赘述了,网上有很多这方面的文章

入口:

系统启动时处理@EnableZuulProxy,重点是其中的ZuulProxyConfiguration.class


底层http库:

ZuulProxyConfiguration.class又额外引入下面3个class,说明zuul支持以下3种方式作为底层http库


1. RestClientRibbonConfiguration.class,底层是Jersey client,具体没研究,并且已经标记为过时


2. OkHttpRibbonConfiguration.class,底层是OkHttp,安卓上用的较多,服务端使用情况未知

3. HttpClientRibbonConfiguration.class,底层是HttpClient,都比较熟了不多说

那么具体使用哪一种方式?我们以HttpClient举例,看下图源码,通过ribbon.httpclient.enabled激活,并且matchIfMissing=true,说明不配置的时候作为默认激活项;其他两种方式配置也是类似,但是不作为默认激活


从源码看,没有RestTemplate方式,细想一下确实也没必要,因为RestTemplate也只是一种门面,以统一的方式调用底层http库,优点就是对于使用者API更简单和统一,既然不需要直接调用,也就不需要RestTemplate了

RouteLocator:

我们继续来看ZuulProxyConfiguration.class,发现个DiscoveryClientRouteLocator.class


RouteLocator作用是什么?很可惜在RouteLocator.class源码中并没有一个整体的说明,不过从其中定义的3个方法中可以看出些端倪,其实就是维护路由信息


知道了大致的地位,我们再来看一下RouteLocator这个家族的势力


1. RouteLocator是其鼻祖,其中定义了三个与路由相关的基本方法:获取忽略的path集合、获取路由列表、根据path获取路由信息

2. SimpleRouteLocator实现了RouteLocator及Ordered,看其描述,主要负责维护配置文件中的路由配置,配置文件中的路由配置会被转化后存储在一个map中,其中key为配置的path


3. RefreshableRouteLocator继承自RouteLocator,额外提供了对于路由刷新方面的定义,refresh方法会结合spring的ApplicationEvent,实现基于事件的路由刷新机制,具体可以参看ZuulDiscoveryRefreshListener源码


4. DiscoveryClientRouteLocator继承自SimpleRouteLocator,并且实现了RefreshableRouteLocator,从类定义上可以看出他具备了基本的路由功能及路由刷新功能。查看源码描述也证实了我们的猜测,另外从描述中可以看出,该类会将配置文件中的路由配置(称为静态)以及服务发现(比如eureka)中的路由信息进行合并。需要注意描述中的一点细节,“The discovery client takes precedence”,说明会以服务发现中的路由信息为主


关于细节,我们可以从locateRoutes方法中获悉。从这份代码中也就可以解答我在一开始开发时遇到的一个问题,就是我仅在配置文件中配置了xxx的路由配置,但是实际访问时为什么yyy下的接口也可以被路由?原因就在于zuul会以服务发现中的路由信息为主,虽然自己在配置文件中没有配置,但是eureka中却已经存在了40+个服务路由



5. 让我们来到RouteLocator家族中的最后一个成员CompositeRouteLocator,他虽然仅仅实现了RefreshableRouteLocator,但是却是能力最强的一个,因为他能够整合众多RouteLocator的功能于一身。注意其构造方法,它会将传入的routeLocators排序,还记得SimpleRouteLocator是实现了Ordered接口吧?


其他几个方法就很好理解了,就是遍历排序后的routeLocators并调用相关的方法


实际上,CompositeRouteLocator也被标记为@Primary,作为主要的RouteLocator被使用,CompositeRouteLocator构造过程中被传入的routeLocators其实仅包括一个DiscoveryClientRouteLocator


ZuulFilter:

看完RouteLocator家族,让我们再来拜访下另一家族ZuulFilter,从家谱上看显然没有RouteLocator家族人丁兴旺,虽然也是爷孙三代,但是却比人家少一口(5:4)


我们先来看一下IZuulFilter,很简单明了,shouldFilter表示该filter是否应该被运行,run方法只有在shouldFilter方法返回true时候才会被运行


ZuulFilter是一个抽象类,实现了IZuulFilter及Comparable,其中定义了几个重要的方法:

filterType返回filter的类型,zuul内置了几个类型,包括pre、route、post、error,顾名思义就知道其用途及触发时机。不同于java web的filter,zuul filter并非嵌套调用,而是顺序调用


filterOrder返回filter的排序值,越小越靠前


介绍完这两位,我想说的是,这个只是冰山一角,实际上spring针对zuul内置扩展了许多核心的filter

pre:


route:


post:


看完这些,你还会觉得ZuulFilter家族势单力薄吗?另外在实际开发中,我们在实现各种功能时绝大多数时候都是在自定义各种ZuulFilter。下面针对这些filter进行介绍

pre过滤器:

1. ServletDetectionFilter:

该过滤器order值为-3,是pre阶段第一个过滤器,并且总是会运行。主要用途是判断该请求是被spring的DispatcherServlet处理还是被zuul的ZuulServlet处理,并且将判断结果设置到context中,后续处理中可以依照此结果进行个性化处理


2. Servlet30WrapperFilter:

该过滤器order值为-2,是pre阶段第二个过滤器,并且总是会运行。Zuul默认仅对servlet2.5兼容,该过滤器可以将request包装成3.0兼容的形式(Servlet30RequestWrapper)


3. FormBodyWrapperFilter:

该过滤器order值为-1,是pre阶段第三个过滤器,仅针对两类请求生效,第一种是Context-Type为application/x-www-form-urlencoded,第二种是由spring的DispatcherServlet处理的Context-Type为multipart/form-data的请求。该过滤器的主要目的是将上述两种请求包装成FormBodyRequestWrapper



4. TracePreZuulFilter:

该过滤器order值为0,是pre阶段第四个过滤器,并且总是会运行。结合Sleuth实现链路追踪相关功能


5. DebugFilter:

该过滤器order值为1,是pre阶段第五个过滤器,仅在请求参数中出现debug=true(参数名称可设置)时执行。具体执行逻辑就是在context中设置debugRouting=true及debugRequest=true。在后续执行中可以通过这两个值来预埋一些debug信息,用于出现问题时提供排查所需的信息


6. PreDecorationFilter:

该过滤器order值为5,是pre阶段第六个过滤器(最后一个),仅在forward.to和serviceId都没有出现在context中的时候才执行。具体来说就是对请求做一些预处理,包括使用前面介绍过的RouteLocator获取路由信息,在context中设置一些后续处理需要的信息,还有就是在请求头中添加一些代理信息,比如X-Forwarded-For



route过滤器:

1. RibbonRoutingFilter:

该过滤器order值为10,是route阶段第一个过滤器,仅在context中存在serviceId的情况下运行。存在serviceId,就是说需要面向服务进行路由,服务的路由信息就是我们上面讲过的两种方式,配置文件(静态)及服务注册。具体就是创建RibbonCommandContext,然后交由ribbon和hystrix向下游服务进行请求


2. SimpleHostRoutingFilter:

该过滤器order值为100,是route阶段第二个过滤器,仅在context中存在routeHost的情况下运行。存在routeHost,就是说我们配置了具体的http或者https url的请求信息。具体逻辑就是通过HttpClient直接向目标url发起请求,不再经过ribbon及hystrix,所以也就没有负载均衡以及熔断


3. SendForwardFilter:

该过滤器order值为500,是route阶段第三个(最后一个)过滤器,仅在context中存在forward.to的情况下运行。存在forward.to,就是说我们配置了类似forward:/index的请求信息。具体就是通过RequestDispatcher进行forward


post过滤器:

1. TracePostZuulFilter:

该过滤器order值为0,是post阶段第一个过滤器,结合pre阶段的TracePreZuulFilter通过Sleuth实现链路追踪的功能


2. SendResponseFilter:

该过滤器order值为1000,是post阶段第二个(最后一个)过滤器,仅在context中存在zuulResponseHeaders、responseDataStream、responseBody(三者是或的关系)的情况下运行,简单来说,就是在有响应数据的时候运行。我们以responseBody举例,来看下responseBody是什么时候被设置到context中的。还记得RibbonRoutingFilter吧,在他的run方法中会调用一个setResponse方法,responseBody就是在这个方法中被设置到context中



再回到SendResponseFilter,具体逻辑就是添加相关的响应头,然后将响应数据回写到response


error过滤器:

1. SendErrorFilter:

该过滤器order值为0,是error阶段唯一一个过滤器,仅在context中存在throwable的情况下运行,也就是说有异常产生的情况下运行。那么这个throwable是何时被设置到context中的呢?我们不妨看一下ZuulServlet,service方法中,preRoute、route、postRoute分别负责上述三种过滤器的运行,他们外层被try catch,产生异常的时候会执行error方法,throwable就是在error方法中被设置到context



继续回到SendErrorFilter,他的执行逻辑也比较简单,就是将错误状态码、错误信息、异常对象设置到request中,然后forward到/error(默认,可配置)。之后我们可以自己定义一个/error端口对错误进行响应


了解完上面这些重要的组件后,让我们再来看一下,当一个请求到来时,zuul是如何利用这些组件,将一切串起来的

ZuulContoller:

ZuulContoller继承自spring的ServletWrappingController,能够将请求交由ZuulServlet处理


ZuulServlet:

ZuulServlet接收到请求,初始化RequestContext,之后分别执行pre、route和post阶段的filter,最后销毁context




ZuulRunner:

可以看到,ZuulServlet实际只是组织整个流程,具体工作还是交给ZuulRunner来完成,看一下ZuulRunner的描述:初始化context,并将request、response设置其中,并且通过FilterProcessor调用pre、route、post及error




FilterProcessor:

Filter的核心执行类,根据不同的filter类型运行filter




你可能感兴趣的:(Spring,Boot,Spring,Spring,Cloud)