Spring Cloud对Netflix Zuul做了封装集成, 使得在Spring Cloud环境中使用Zuul更方便. Netflix Zuul相关分析请看上一篇.

实现

@EnableZuulProxy 与 @EnableZuulServer 二者的区别在于前者使用了服务发现作为路由寻址, 并使用Ribbon做客户端的负载均衡; 后者没有使用. Zuul server的路由都通过ZuulProperties进行配置.

具体实现:

  1. 使用ZuulController(ServletWrappingController的子类)封装ZuulServlet实例, 处理从DispatcherServlet进来的请求.
  2. ZuulHandlerMapping负责注册handler mapping, 将RoutefullPath的请求交由ZuulController处理.
  3. 同时使用ServletRegistrationBean注册ZuulServlet, 默认使用/zuul作为urlMapping. 所有来自以/zuul开头的path的请求都会直接进入ZuulServlet, 不会进入DispatcherServlet.

使用注解

  • @EnableZuulProxy引入了ZuulProxyMarkerConfiguration, ZuulProxyMarkerConfiguration只做了一件事, 实例化了内部类Marker.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    @Configuration
    public class ZuulProxyMarkerConfiguration {
        @Bean
        public Marker zuulProxyMarkerBean() {
            return new Marker();
        }
    
        class Marker {
        }
    }
  • @EnableZuulServer引入了ZuulServerMarkerConfiguration, ZuulServerMarkerConfiguration也只做了一件事: 实例化了内部类Marker

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    @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-corespring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration实现中我们可以找到org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfigurationorg.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration

  • ZuulServerAutoConfiguration 它的初始化条件有两个:

    • @ConditionalOnClass(ZuulServlet.class)指定classpath中需要有ZuulServlet.class. 这个servlet负责对所有进入Zuul server的请求以及配置应用指定的preRoute, route, postRouteerror.
    • @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等的初始化.

    1
    2
    3
    4
    5
    6
    7
    8
    
    @Configuration
    @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
    @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
    public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
        ...
    }

ZuulServerAutoConfiguration 详解
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//声明配置
@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 详解
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
​```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. ZuulControllerZuulHandlerMapping默认把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属性控制, 默认启用. 执行时将上下文中的debugRoutingdebugRequest设置为true

Servlet30WrapperFilter

使用Servlet30RequestWrapper封装请求, 强制启用.

SendResponseFilter

后执行的过滤器, 负责将代理请求的响应写入当前的请求的响应中.

ZuulProxyAutoConfiguration

PreDecorationFilter

Pre类型的过滤器, 通过提供的RouteLocator决定将如何请求路由到哪里和如何路由. 同时为下游请求添加多个与代理相关的头信息. 当RequestContext中不存在FORWARD_TO_KEYSERVICE_ID_KEY信息时生效.

将路由判断结果写入routeHost, FORWARD_TO_KEY或者SERVICE_ID_KEY.

RibbonRoutingFilter

Route类型的过滤器, 当RequestContextrouteHost为空, 且有serviceId值时生效.

使用RequestContext构建RibbonCommandContext, 通过RibbonCommandFactory进而创建RibbonCommand并执行. 最后通过ProxyRequestHelper将响应结果记录到RequestContext中.

SimpleHostRoutingFilter

Route类型的过滤器, 当RequestContext中的routeHost不为空时生效. 使用Apache的HttpClient发送请求

监听器

ZuulRefreshListener

通过监听应用程序事件(ContextRefreshedEvent, RefreshScopeRefreshedEvent, RoutesRefreshedEventRoutesRefreshedEvent)更新handler mapping的注册信息. 前两个事件在ContextRefresh时发出; 第三个是通过JMX重置路由时发出(参考RoutesMvcEndpoint); 最后一个是DiscoveryClient每次拉取服务注册信息后发出.

收到事件后, 将ZuulHandlerMappingdirty变量置为true, 当下次请求进来时, 检查到dirtytrue, 就会重新注册url mapping.

ZuulDiscoveryRefreshListener

监听应用程序事件(InstanceRegisteredEvent, ParentHeartbeatEventHeartbeatEvent)更新handler mapping的注册信息.

InstanceRegisteredEvent当前路由服务实例完成服务注册后发出的事件. ParentHeartbeatEventDiscoveryClient定位到*Config Server*服务的时候有bootstrapContext发给应用程序上下文的事件. HeartbeatEventDiscoveryClient每次拉取服务注册信息后发出.