Zuul源码解析(二)

Zuul 的自动配置

ZuulProxyAutoConfiguration 如何触发

Zuul源码解析(二)_第1张图片

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

Zuul源码解析(二)_第2张图片

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

答案时 @EnableZuulProxy 注解

Zuul源码解析(二)_第3张图片

再进去 ZuulProxyMarkerConfiguration

Zuul源码解析(二)_第4张图片

我们发现是通过注册一个 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 {

	@Autowired
	protected ZuulProperties zuulProperties;

	@Autowired
	protected ServerProperties server;

	@Autowired(required = false)
	private ErrorController errorController;
    // ...
    // 这有点类似于 WebMvcConfigurerComposite
    // CompositeRouteLocator 就是一个集成了所有 RouteLocator 实现的一个组合类
	@Bean
	@Primary
	public 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,这个挺重要,后面会说到
	@Bean
	public ZuulController zuulController() {
		return new ZuulController();
	}

    // 新注册一个 zuul 请求的 handlerMapping,后面会详细说
	@Bean
	public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
		ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
		mapping.setErrorController(this.errorController);
		return mapping;
	}

    // 路由刷新 Listener
	@Bean
	public 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
	@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();
	}

	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

主要配置了 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;

	@Autowired
	private DiscoveryClient discovery;

	@Autowired
	private 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
	@Bean
	public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
			ProxyRequestHelper proxyRequestHelper) {
		return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
				this.zuulProperties, proxyRequestHelper);
	}

	// route filters
	@Bean
	public 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);
	}

	@Bean
	public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {
		return new ZuulDiscoveryRefreshListener();
	}

	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 又是怎么和 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()

Zuul源码解析(二)_第5张图片

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。

Zuul源码解析(二)_第6张图片

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

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

Zuul源码解析(二)_第7张图片

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

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

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

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

Zuul源码解析(二)_第8张图片

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

Zuul源码解析(二)_第9张图片

上图中,首先通过路由定位器调用 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 了

Zuul源码解析(二)_第10张图片

Zuul源码解析(二)_第11张图片

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

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

Zuul源码解析(二)_第12张图片

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

Zuul源码解析(二)_第13张图片

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

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

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

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

Zuul源码解析(二)_第14张图片

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

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

你可能感兴趣的:(源码系列,Spring,Cloud,spring,cloud)