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 {
@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()
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源码解析(一)
如果你还有其他疑问,可以联系我,一起学习。