Spring Cloud对Netflix Zuul做了封装集成, 使得在Spring Cloud环境中使用Zuul更方便. Netflix Zuul相关分析请看上一篇.
@EnableZuulProxy 与 @EnableZuulServer
二者的区别在于前者使用了服务发现作为路由寻址, 并使用Ribbon做客户端的负载均衡; 后者没有使用.
Zuul server的路由都通过 ZuulProperties
进行配置.
ZuulController
( ServletWrappingController
的子类)封装 ZuulServlet
实例, 处理从 DispatcherServlet
进来的请求. ZuulHandlerMapping
负责注册handler mapping, 将 Route
的 fullPath
的请求交由 ZuulController
处理. ServletRegistrationBean
注册 ZuulServlet
, 默认使用 /zuul
作为urlMapping. 所有来自以 /zuul
开头的path的请求都会直接进入 ZuulServlet
, 不会进入 DispatcherServlet
. @EnableZuulProxy
引入了 ZuulProxyMarkerConfiguration
, ZuulProxyMarkerConfiguration
只做了一件事, 实例化了内部类 Marker
.
@Configuration public class ZuulProxyMarkerConfiguration { @Bean public Marker zuulProxyMarkerBean() { return new Marker(); } class Marker { } }
@EnableZuulServer
引入了 ZuulServerMarkerConfiguration
, ZuulServerMarkerConfiguration
也只做了一件事: 实例化了内部类 Marker
@Configuration public class ZuulServerMarkerConfiguration { @Bean public Marker zuulServerMarkerBean() { return new Marker(); } class Marker { } }
项目中使用 @EnableAutoConfiguration
注解, 开启Spring上下文对象的自动配置功能, 尝试去猜测和实例化你 可能需要的 bean.
这个功能是基于classPath来完成的. 比如: 项目中引用了 tomcat-embedded.jar
, 你可能需要一个 TomcatEmbeddedServletContainerFactory
实例, 除非定义了自己的 EmbeddedServletContainerFactory
实例.
我们来接着看, 在 spring-cloud-netflix-core
的 spring.factories
中的 org.springframework.boot.autoconfigure.EnableAutoConfiguration
实现中我们可以找到 org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
和 org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration
ZuulServerAutoConfiguration
它的初始化条件有两个:
@ConditionalOnClass(ZuulServlet.class)
指定classpath中需要有 ZuulServlet.class
. 这个servlet负责对所有进入Zuul server的请求以及配置应用指定的 preRoute
, route
, postRoute
和 error
. @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
与 @EnableZuulServer
注解呼应.
@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 { ... }
ZuulProxyAutoConfiguration
它有一个初始化的条件 @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
, 就是上下文中需要有 ZuulProxyMarkerConfiguration.Marker
这个内部类的bean. 与 @EnableZuulProxy
注解呼应.
初始化包括内置的filter, 以及Discovery, Ribbon等的初始化.
@Configuration @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class, RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class, RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class }) @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class) public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration { ... }
//声明配置 @Configuration //配置ZuulProperties实例 @EnableConfigurationProperties({ ZuulProperties.class }) //条件1 存在ZuulServlet.class @ConditionalOnClass(ZuulServlet.class) //条件2 存在ZuulServerMarkerConfiguration.Marker.class bean, 即应用使用@EnableZuulServer注解 @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class) //配置ServerProperties实例 // Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) public class ZuulServerAutoConfiguration { @Autowired protected ZuulProperties zuulProperties; @Autowired protected ServerProperties server; @Autowired(required = false) private ErrorController errorController; @Bean public HasFeatures zuulFeature() { return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class); } //复合结构的RouteLocator @Bean @Primary public CompositeRouteLocator primaryRouteLocator( Collection<RouteLocator> routeLocators) { return new CompositeRouteLocator(routeLocators); } //没有SimpleRouteLocator.class的bean时, 使用zuulProperties实例化一个SimpleRouteLocator实例. @Bean @ConditionalOnMissingBean(SimpleRouteLocator.class) public SimpleRouteLocator simpleRouteLocator() { return new SimpleRouteLocator(this.server.getServletPrefix(), this.zuulProperties); } //zuulController, 包装了一个ZuulServlet类型的servlet, 实现对ZuulServlet类型的servlet的初始化. @Bean public ZuulController zuulController() { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; } @Bean public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() { return new ZuulRefreshListener(); } @Bean @ConditionalOnMissingBean(name = "zuulServlet") public ServletRegistrationBean zuulServlet() { ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(), this.zuulProperties.getServletPattern()); // The whole point of exposing this servlet is to provide a route that doesn't // buffer requests. servlet.addInitParameter("buffer-requests", "false"); return servlet; } // pre filters @Bean public ServletDetectionFilter servletDetectionFilter() { return new ServletDetectionFilter(); } @Bean public FormBodyWrapperFilter formBodyWrapperFilter() { return new FormBodyWrapperFilter(); } @Bean public DebugFilter debugFilter() { return new DebugFilter(); } @Bean public Servlet30WrapperFilter servlet30WrapperFilter() { return new Servlet30WrapperFilter(); } // post filters @Bean public SendResponseFilter sendResponseFilter() { return new SendResponseFilter(); } @Bean public SendErrorFilter sendErrorFilter() { return new SendErrorFilter(); } @Bean public SendForwardFilter sendForwardFilter() { return new SendForwardFilter(); } @Bean @ConditionalOnProperty(value = "zuul.ribbon.eager-load.enabled", matchIfMissing = false) public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer( SpringClientFactory springClientFactory) { return new ZuulRouteApplicationContextInitializer(springClientFactory, zuulProperties); } @Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters; @Bean public ZuulFilterInitializer zuulFilterInitializer( CounterFactory counterFactory, TracerFactory tracerFactory) { FilterLoader filterLoader = FilterLoader.getInstance(); FilterRegistry filterRegistry = FilterRegistry.instance(); return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry); } } @Configuration @ConditionalOnClass(CounterService.class) protected static class ZuulCounterFactoryConfiguration { @Bean @ConditionalOnBean(CounterService.class) public CounterFactory counterFactory(CounterService counterService) { return new DefaultCounterFactory(counterService); } } @Configuration protected static class ZuulMetricsConfiguration { @Bean @ConditionalOnMissingBean(CounterFactory.class) public CounterFactory counterFactory() { return new EmptyCounterFactory(); } @ConditionalOnMissingBean(TracerFactory.class) @Bean public TracerFactory tracerFactory() { return new EmptyTracerFactory(); } } private static class ZuulRefreshListener implements ApplicationListener<ApplicationEvent> { @Autowired private ZuulHandlerMapping zuulHandlerMapping; private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor(); @Override public 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); } } } } }
//声明配置 @Configuration //引入RibbonCommandFactory配置 @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; //服务发现客户端 @Autowired private DiscoveryClient discovery; //serviceId和路由的映射逻辑, 默认为相同 @Autowired private ServiceRouteMapper serviceRouteMapper; @Override public HasFeatures zuulFeature() { return HasFeatures.namedFeature("Zuul (Discovery)", ZuulProxyAutoConfiguration.class); } //静态和动态路由寻址: 静态从配置文件获取, 动态通过服务发现客户端完成. 后者优先级更高 @Bean @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class) public DiscoveryClientRouteLocator discoveryRouteLocator() { return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration); } //装饰过滤器 // pre filters @Bean public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) { return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties, proxyRequestHelper); } //基于Ribbon路由过滤器 // route filters @Bean public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) { RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers); return filter; } //基于host的路由过滤器 @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); } //服务发现寻址刷新监听器 @Bean public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() { return new ZuulDiscoveryRefreshListener(); } @Bean @ConditionalOnMissingBean(ServiceRouteMapper.class) public ServiceRouteMapper serviceRouteMapper() { return new SimpleServiceRouteMapper(); } @Configuration @ConditionalOnMissingClass("org.springframework.boot.actuate.endpoint.Endpoint") protected static class NoActuatorConfiguration { @Bean public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) { ProxyRequestHelper helper = new ProxyRequestHelper(); helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders()); helper.setTraceRequestBody(zuulProperties.isTraceRequestBody()); return helper; } } @Configuration @ConditionalOnClass(Endpoint.class) protected static class EndpointConfiguration { @Autowired(required = false) private TraceRepository traces; @ConditionalOnEnabledEndpoint("routes") @Bean public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) { return new RoutesEndpoint(routeLocator); } @ConditionalOnEnabledEndpoint("routes") @Bean public RoutesMvcEndpoint routesMvcEndpoint(RouteLocator routeLocator, RoutesEndpoint endpoint) { return new RoutesMvcEndpoint(endpoint, routeLocator); } @ConditionalOnEnabledEndpoint("filters") @Bean public FiltersEndpoint filtersEndpoint() { FilterRegistry filterRegistry = FilterRegistry.instance(); return new FiltersEndpoint(filterRegistry); } @Bean public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) { TraceProxyRequestHelper helper = new TraceProxyRequestHelper(); if (this.traces != null) { helper.setTraces(this.traces); } helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders()); helper.setTraceRequestBody(zuulProperties.isTraceRequestBody()); return helper; } } private static class ZuulDiscoveryRefreshListener implements ApplicationListener<ApplicationEvent> { private HeartbeatMonitor monitor = new HeartbeatMonitor(); @Autowired private ZuulHandlerMapping zuulHandlerMapping; @Override public 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 , 注册ZuulServlet的时候作为urlMapping使用. 即所有来自以 /zuul 开头的path都会由ZuulServlet处理.
Zuul使用 ZuulController
封装了 ZuulServlet
. 所有进入Zuul的请求的入口都是 ZuulController
.
ZuulController
的 ZuulHandlerMapping
默认把 zuul.routes.[ITEM].path
的请求交给 ZuulServlet
处理. 如果找不到对应的path的route, 则会走其他的 DispatcherServlet
zuul.ignoredPatterns
作用就是进入Zuul的请求, 只要match都会直接交由其他的 DispatcherServlet
处理, 而不需要先检查是否有对应path的route.
…
检查请求的入口是 DispatcherServlet
还是 ZuulServlet
如果是 DispatcherServlet
进来的请求, 将 RequestContext
中的属性 isDispatcherServletRequest
设置为ture.
检查的方法是判断 RequestContext
中的请求类型是否为 HttpServletRequestWrapper
类型, 因为 ZuulServlet
进来的请求会使用 HttpServletRequestWrapper
进行再次封装; 同时检查请求中中是否有 DispatcherServlet.CONTEXT
属性, 因为 DispatcherServlet
进来的请求会带有该属性.
为下游的服务解析表单数据, 并重新编码. 只针对multipart/form-data和application/x-www-form-urlencoded类型的请求.
通过设置 zuul.debug.parameter
属性控制, 默认启用.
执行时将上下文中的 debugRouting
和 debugRequest
设置为 true
使用 Servlet30RequestWrapper
封装请求, 强制启用.
后执行的过滤器, 负责将代理请求的响应写入当前的请求的响应中.
Pre类型的过滤器, 通过提供的RouteLocator决定将如何请求路由到哪里和如何路由. 同时为下游请求添加多个与代理相关的头信息. 当 RequestContext
中不存在 FORWARD_TO_KEY
和 SERVICE_ID_KEY
信息时生效.
将路由判断结果写入 routeHost
, FORWARD_TO_KEY
或者 SERVICE_ID_KEY
.
Route类型的过滤器, 当 RequestContext
中 routeHost
为空, 且有 serviceId
值时生效.
使用 RequestContext
构建 RibbonCommandContext
, 通过 RibbonCommandFactory
进而创建 RibbonCommand
并执行. 最后通过 ProxyRequestHelper
将响应结果记录到 RequestContext
中.
Route类型的过滤器, 当 RequestContext
中的 routeHost
不为空时生效. 使用Apache的HttpClient发送请求
通过监听应用程序事件( ContextRefreshedEvent
, RefreshScopeRefreshedEvent
, RoutesRefreshedEvent
和 RoutesRefreshedEvent
)更新handler mapping的注册信息. 前两个事件在 ContextRefresh
时发出; 第三个是通过JMX重置路由时发出(参考 RoutesMvcEndpoint
); 最后一个是 DiscoveryClient
每次拉取服务注册信息后发出.
收到事件后, 将 ZuulHandlerMapping
的 dirty
变量置为 true
, 当下次请求进来时, 检查到 dirty
为 true
, 就会重新注册url mapping.
监听应用程序事件( InstanceRegisteredEvent
, ParentHeartbeatEvent
和 HeartbeatEvent
)更新handler mapping的注册信息.
InstanceRegisteredEvent
当前路由服务实例完成服务注册后发出的事件.
ParentHeartbeatEvent
当 DiscoveryClient
定位到 Config Server 服务的时候有 bootstrapContext
发给应用程序上下文的事件.
HeartbeatEvent
由 DiscoveryClient
每次拉取服务注册信息后发出.