> 文章列表 > Zuul源码解析(二)

Zuul源码解析(二)

Zuul源码解析(二)

Zuul 的自动配置

ZuulProxyAutoConfiguration 如何触发

image-20230419183851752

如上图,在 spring.factory 中配置 ZuulProxyAutoConfiguration 自动配置了,直接点进去

image-20230419184121617

如上图所示,发现这有个条件注解,需要有 org.springframework.cloud.netflix.zuul.ZuulProxyMarkerConfiguration.Marker 这样一个bean,那这个条件是如何触发的呢?

答案时 @EnableZuulProxy 注解

image-20230419184527553

再进去 ZuulProxyMarkerConfiguration

image-20230419184654581

我们发现是通过注册一个 Marker bean 来触发 ZuulProxyAutoConfiguration,这个思想套路可以学习下。

ZuulProxyAutoConfiguration 主要自动配置了那些东西组件?

ZuulProxyAutoConfiguration 继承了 ZuulServerAutoConfiguration。这两个类中有分别有所负责自动配置的内容

ZuulServerAutoConfiguration

主要配置了 CompositeRouteLocator,SimpleRouteLocator,ZuulController,ZuulHandlerMapping 以及一些默认 Filter 等 Zuul 服务组件。

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {@Autowiredprotected ZuulProperties zuulProperties;@Autowiredprotected ServerProperties server;@Autowired(required = false)private ErrorController errorController;// ...// 这有点类似于 WebMvcConfigurerComposite// CompositeRouteLocator 就是一个集成了所有 RouteLocator 实现的一个组合类@Bean@Primarypublic CompositeRouteLocator primaryRouteLocator(Collection<RouteLocator> routeLocators) {return new CompositeRouteLocator(routeLocators);}// ConditionalOnMissingBean 表示我们可自定义@Bean@ConditionalOnMissingBean(SimpleRouteLocator.class)public SimpleRouteLocator simpleRouteLocator() {return new SimpleRouteLocator(this.server.getServletPrefix(),this.zuulProperties);}// a handler,这个挺重要,后面会说到@Beanpublic ZuulController zuulController() {return new ZuulController();}// 新注册一个 zuul 请求的 handlerMapping,后面会详细说@Beanpublic ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());mapping.setErrorController(this.errorController);return mapping;}// 路由刷新 Listener@Beanpublic ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {return new ZuulRefreshListener();}@Bean@ConditionalOnMissingBean(name = "zuulServlet")public ServletRegistrationBean zuulServlet() {ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),this.zuulProperties.getServletPattern());servlet.addInitParameter("buffer-requests", "false");return servlet;}// 下面时注册了前面介绍的几个默认的 Filter// pre filters@Beanpublic ServletDetectionFilter servletDetectionFilter() {return new ServletDetectionFilter();}@Beanpublic FormBodyWrapperFilter formBodyWrapperFilter() {return new FormBodyWrapperFilter();}@Beanpublic DebugFilter debugFilter() {return new DebugFilter();}@Beanpublic Servlet30WrapperFilter servlet30WrapperFilter() {return new Servlet30WrapperFilter();}// post filters@Beanpublic SendResponseFilter sendResponseFilter() {return new SendResponseFilter();}@Beanpublic SendErrorFilter sendErrorFilter() {return new SendErrorFilter();}@Beanpublic SendForwardFilter sendForwardFilter() {return new SendForwardFilter();}private static class ZuulRefreshListenerimplements ApplicationListener<ApplicationEvent> {@Autowiredprivate ZuulHandlerMapping zuulHandlerMapping;private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ContextRefreshedEvent|| event instanceof RefreshScopeRefreshedEvent|| event instanceof RoutesRefreshedEvent) {this.zuulHandlerMapping.setDirty(true);}else if (event instanceof HeartbeatEvent) {if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {this.zuulHandlerMapping.setDirty(true);}}}}
}

ZuulProxyAutoConfiguration

主要配置了 DiscoveryClientRouteLocator,pre filters,route filters,ZuulDiscoveryRefreshListener 路由监听刷新等 Zuul 代理相关的组件

@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {@SuppressWarnings("rawtypes")@Autowired(required = false)private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();@Autowired(required = false)private Registration registration;@Autowiredprivate DiscoveryClient discovery;@Autowiredprivate ServiceRouteMapper serviceRouteMapper;@Bean@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)public DiscoveryClientRouteLocator discoveryRouteLocator() {return new DiscoveryClientRouteLocator(this.server.getServletPrefix(),this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration);}// pre filters@Beanpublic PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,ProxyRequestHelper proxyRequestHelper) {return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),this.zuulProperties, proxyRequestHelper);}// route filters@Beanpublic RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,RibbonCommandFactory<?> ribbonCommandFactory) {RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,this.requestCustomizers);return filter;}@Bean@ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,ZuulProperties zuulProperties,ApacheHttpClientConnectionManagerFactory connectionManagerFactory,ApacheHttpClientFactory httpClientFactory) {return new SimpleHostRoutingFilter(helper, zuulProperties,connectionManagerFactory, httpClientFactory);}@Bean@ConditionalOnMissingBean({SimpleHostRoutingFilter.class})public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,ZuulProperties zuulProperties,CloseableHttpClient httpClient) {return new SimpleHostRoutingFilter(helper, zuulProperties,httpClient);}@Beanpublic ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {return new ZuulDiscoveryRefreshListener();}private static class ZuulDiscoveryRefreshListenerimplements ApplicationListener<ApplicationEvent> {private HeartbeatMonitor monitor = new HeartbeatMonitor();@Autowiredprivate ZuulHandlerMapping zuulHandlerMapping;@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof InstanceRegisteredEvent) {reset();}else if (event instanceof ParentHeartbeatEvent) {ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;resetIfNeeded(e.getValue());}else if (event instanceof HeartbeatEvent) {HeartbeatEvent e = (HeartbeatEvent) event;resetIfNeeded(e.getValue());}}private void resetIfNeeded(Object value) {if (this.monitor.update(value)) {reset();}}private void reset() {this.zuulHandlerMapping.setDirty(true);}}

Zuul 又是怎么和 MVC 中的 DisPatcherServlet 联系起来的

ps:这部分的理解需要读者简单了解 Spring MVC 原理和组件

在文章的最开头部分,我们看到打印的错误日志是从 DispatcherServlet 进来的。当时由于我对 Zuul 的实现原理不了解,以为他是独立于普通请求的 DispatcherServlet。据我了解 DispatcherServlet 的默认匹配路径是 /,而 ZuulServlet 的默认匹配路径是 /zuul/**,所以我们项目中,一般的 https://网关域名/usercenter/user/detail/1 等的路径都是走的 DispatcherServlet 。

我们知道,ZuulServlet#service() 方法逻辑程序才是 Zuul 核心流程代码,那么它又是如何在从 DispatcherServlet.service() 方法中进入 ZuulServlet 的呢?

因为 Zuul 自动配置中配置了一个 ZuulHandlerMapping。

接着我带着你们去一探究竟~

首先入口还是回到大家熟悉的 DispatcherServlet#doDispatch()

getHandler() 这个方法很重要,这个方法返回的 mappedhandler 中的 hanlder 是 Zuul 自动配置的 ZuulController 的实例。

那么现在你也许有两个疑问:

  1. getHandler() 是怎么根据请i去 urlPath 就匹配到了 ZuulController
  2. ZuulController 又是如何与 ZuulServlet 联系在一起的,是如何进入ZuulServlet 的 service() 方法

getHandler() 是怎么根据请求 urlPath 就匹配到了 ZuulController

进入 getHandler() 方法,发现它是遍历了所有的 HandlerMapping,这其中就包括前面讲 Zuul 自动配置的 ZuulHandlermapping。

根据请求 urlPath,实际上只有 ZuulHandlermapping#getHandler() 方法会返回 handler。

接着进入 ZuulHandlermapping#getHandler(),看他是如何匹配的。

如下图,它继续调用了 ZuulHandlermapping 父类的 AbstractUrlhandlerMapping 的 getHandlerInternal()

如下图,AbstractUrlhandlerMapping 的 getHandlerInternal() 中在调用了子类 ZuulHandlermapping 的 lookupHandler。

在这个方法中,主要做了两件事:

  1. 如果 dirty 为 true,则会注册 handler 到一个 map 中。(一般容器启动时或者新部署应用服务时,dirty 会被改为 true,目的就是实时刷新 这个 map,前面在讲 RefreshableRouteLocator 时有介绍过)
  2. 紧接着再在这个 map 中查找 handler

不理解不要紧,先往下看,我详细说下这两个步骤。

我们进入 registerHandlers() 这个方法,看他如何注册 handler

上图中,首先通过路由定位器调用 getRoutes() 方法获取所有的路由(这个方法前面已经介绍过了)。然后遍历每个 route,每个 routefullPathkey,ZuulController 为 value,put 到一个 map 中去。(比如 /usercenter/** map to ZuulController,/ordercenter/** map to ZuulController,这里的ZuulControler 都是同一个。)

再来看看第二个问题,如何在 map 中匹配到 handler 的,如下图:

比如 urlPath=/usercenter/user/detail/1,那么就会跟 handlerMap 中的 key 为 /usercenter/** 匹配上,然后就返回 handlerMap 的值 ZuulController 了

ZuulController 又是如何于 ZuulServlet 联系在一起的,或者说是如何进入ZuulServlet 的 service() 方法

再回到 DisPatcherServlet,getHandlerAdapter() 获取到 SimpleControllerHandlerAdapter,紧接着调用SimpleControllerHandlerAdapter 的 handle() 方法。如下图:

点击进入 handler() 方法,如下图。

再点击上图中的 handleRequest() 方法,则进入了 ``ZuulController#handleRequest() 中,ZuulController#handleRequest() 没做任何处理,直接调用了父类 ServletWrappingController#handleRequestInternal()` 方法,如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mr8ZYYgi-1682254631363)(null)]

这里可能又有个疑问了,上图中的 servletInstance 是个什么东西,是 ZuulServlet?没错,就是它。

那怎么确定就是 ZuulServlet 呢,如下图,可以看到,ZuulController 在初始化时是指定了 ZuulServlet.class

那么,全文到这里就结束了。

由于全文篇幅太长,我把 Zuul 的源码解析分成了两篇文章,感兴趣可前往 Zuul源码解析(一)
如果你还有其他疑问,可以联系我,一起学习。