Zuul 的自动配置
ZuulProxyAutoConfiguration 如何触发
如上图,在 spring.factory 中配置 ZuulProxyAutoConfiguration 自动配置了,直接点进去
如上图所示,发现这有个条件注解,需要有 org.springframework.cloud.netflix.zuul.ZuulProxyMarkerConfiguration.Marker
这样一个bean,那这个条件是如何触发的呢?
答案时 @EnableZuulProxy
注解
再进去 ZuulProxyMarkerConfiguration
我们发现是通过注册一个 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 的实例。
那么现在你也许有两个疑问:
getHandler()
是怎么根据请i去 urlPath 就匹配到了 ZuulController- ZuulController 又是如何与 ZuulServlet 联系在一起的,是如何进入ZuulServlet 的
service()
方法
getHandler() 是怎么根据请求 urlPath 就匹配到了 ZuulController
进入 getHandler()
方法,发现它是遍历了所有的 HandlerMapping,这其中就包括前面讲 Zuul 自动配置的 ZuulHandlermapping。
根据请求 urlPath
,实际上只有 ZuulHandlermapping#getHandler()
方法会返回 handler。
接着进入 ZuulHandlermapping#getHandler()
,看他是如何匹配的。
如下图,它继续调用了 ZuulHandlermapping 父类的 AbstractUrlhandlerMapping 的 getHandlerInternal()
如下图,AbstractUrlhandlerMapping 的 getHandlerInternal()
中在调用了子类 ZuulHandlermapping 的 lookupHandler。
在这个方法中,主要做了两件事:
- 如果
dirty
为 true,则会注册handler
到一个map
中。(一般容器启动时或者新部署应用服务时,dirty
会被改为true
,目的就是实时刷新 这个map
,前面在讲 RefreshableRouteLocator 时有介绍过) - 紧接着再在这个
map
中查找handler
。
不理解不要紧,先往下看,我详细说下这两个步骤。
我们进入 registerHandlers()
这个方法,看他如何注册 handler
。
上图中,首先通过路由定位器调用 getRoutes()
方法获取所有的路由(这个方法前面已经介绍过了)。然后遍历每个 route
,每个 route
的 fullPath
为 key
,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() 没做任何处理,直接调用了父类 S
ervletWrappingController#handleRequestInternal()` 方法,如下图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mr8ZYYgi-1682254631363)(null)]
这里可能又有个疑问了,上图中的 servletInstance
是个什么东西,是 ZuulServlet?没错,就是它。
那怎么确定就是 ZuulServlet 呢,如下图,可以看到,ZuulController 在初始化时是指定了 ZuulServlet.class
那么,全文到这里就结束了。
由于全文篇幅太长,我把 Zuul 的源码解析分成了两篇文章,感兴趣可前往 Zuul源码解析(一)
如果你还有其他疑问,可以联系我,一起学习。