Spring Cloud Zuul详解
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
项目中使用@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
注解呼应.
java @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 { ... }
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);
}
}
}
}
}
ZuulProxyAutoConfiguration 详解
```java
//声明配置
@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.servletPath
默认为*/zuul*, 注册ZuulServlet的时候作为urlMapping使用. 即所有来自以*/zuul*开头的path都会由ZuulServlet处理.
zuul.ignoredPatterns
Zuul使用ZuulController
封装了ZuulServlet
. 所有进入Zuul的请求的入口都是ZuulController
.
ZuulController
的ZuulHandlerMapping
默认把zuul.routes.[ITEM].path
的请求交给ZuulServlet
处理. 如果找不到对应的path的route, 则会走其他的DispatcherServlet
zuul.ignoredPatterns
作用就是进入Zuul的请求, 只要match都会直接交由其他的DispatcherServlet
处理, 而不需要先检查是否有对应path的route.
…
过滤器
ZuulServerAutoConfiguration
ServletDetectionFilter
检查请求的入口是DispatcherServlet
还是ZuulServlet
如果是DispatcherServlet
进来的请求, 将RequestContext
中的属性isDispatcherServletRequest
设置为ture.
检查的方法是判断RequestContext
中的请求类型是否为HttpServletRequestWrapper
类型, 因为ZuulServlet
进来的请求会使用HttpServletRequestWrapper
进行再次封装; 同时检查请求中中是否有DispatcherServlet.CONTEXT
属性, 因为DispatcherServlet
进来的请求会带有该属性.
FormBodyWrapperFilter
为下游的服务解析表单数据, 并重新编码. 只针对multipart/form-data和application/x-www-form-urlencoded类型的请求.
DebugFilter
通过设置zuul.debug.parameter
属性控制, 默认启用.
执行时将上下文中的debugRouting
和debugRequest
设置为true
Servlet30WrapperFilter
使用Servlet30RequestWrapper
封装请求, 强制启用.
SendResponseFilter
后执行的过滤器, 负责将代理请求的响应写入当前的请求的响应中.
ZuulProxyAutoConfiguration
PreDecorationFilter
Pre类型的过滤器, 通过提供的RouteLocator决定将如何请求路由到哪里和如何路由. 同时为下游请求添加多个与代理相关的头信息. 当RequestContext
中不存在FORWARD_TO_KEY
和SERVICE_ID_KEY
信息时生效.
将路由判断结果写入routeHost
, FORWARD_TO_KEY
或者SERVICE_ID_KEY
.
RibbonRoutingFilter
Route类型的过滤器, 当RequestContext
中routeHost
为空, 且有serviceId
值时生效.
使用RequestContext
构建RibbonCommandContext
, 通过RibbonCommandFactory
进而创建RibbonCommand
并执行. 最后通过ProxyRequestHelper
将响应结果记录到RequestContext
中.
SimpleHostRoutingFilter
Route类型的过滤器, 当RequestContext
中的routeHost
不为空时生效. 使用Apache的HttpClient发送请求
监听器
ZuulRefreshListener
通过监听应用程序事件(ContextRefreshedEvent
, RefreshScopeRefreshedEvent
, RoutesRefreshedEvent
和RoutesRefreshedEvent
)更新handler mapping的注册信息. 前两个事件在ContextRefresh
时发出; 第三个是通过JMX重置路由时发出(参考RoutesMvcEndpoint
); 最后一个是DiscoveryClient
每次拉取服务注册信息后发出.
收到事件后, 将ZuulHandlerMapping
的dirty
变量置为true
, 当下次请求进来时, 检查到dirty
为true
, 就会重新注册url mapping.
ZuulDiscoveryRefreshListener
监听应用程序事件(InstanceRegisteredEvent
, ParentHeartbeatEvent
和HeartbeatEvent
)更新handler mapping的注册信息.
InstanceRegisteredEvent
当前路由服务实例完成服务注册后发出的事件.
ParentHeartbeatEvent
当DiscoveryClient
定位到Config Server服务的时候有bootstrapContext
发给应用程序上下文的事件.
HeartbeatEvent
由DiscoveryClient
每次拉取服务注册信息后发出.