SpringCloud整合Zuul源码分析

回顾

  1. Zuul是通过ZuulServletFilter或者 ZuulServlet接管我们的请求

  2. Zuul整个流程如下:

    ZuulServletFilter(ZuulServlet) -> ZuulRunner -> FilterProcessor -> ZuulFilter

目标

明确SpringMVC和Zuul框架是怎么配合的

引入Zuul的版本信息

<properties>
    <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>

<dependencyManagement>
    <dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
    
<dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
   </dependency>
</dependencies>

Zuul功能启用及配置的加载

Zuul的启用 - @EnableZuulProxy

// 引入断路器功能
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
// 注入触发Zuul配置类的标记Bean
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

ZuulProxyAutoConfiguration - Zuul自动配置Bean

// 此配置类不会被代理
@Configuration(proxyBeanMethods = false)
// 引入Ribbon相关配置
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
        HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

    // 省略部分代码。。。

    // 加载pre filters bean
    @Bean
    @ConditionalOnMissingBean(PreDecorationFilter.class)
    public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
            ProxyRequestHelper proxyRequestHelper) {
        return new PreDecorationFilter(routeLocator,
                this.server.getServlet().getContextPath(), this.zuulProperties,
                proxyRequestHelper);
    }

    // 加载route filters bean
    @Bean
    @ConditionalOnMissingBean(RibbonRoutingFilter.class)
    public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
            RibbonCommandFactory<?> ribbonCommandFactory) {
        RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                this.requestCustomizers);
        return filter;
    }

  // 加载route filters bean
    @Bean
    @ConditionalOnMissingBean({ SimpleHostRoutingFilter.class,
            CloseableHttpClient.class })
    public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
            ZuulProperties zuulProperties,
            ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
            ApacheHttpClientFactory httpClientFactory) {
        return new SimpleHostRoutingFilter(helper, zuulProperties,
                connectionManagerFactory, httpClientFactory);
    }
}

ZuulServerAutoConfiguration - Zuul自动配置Bean

@Configuration(proxyBeanMethods = false)
// 加载zuul的自定义properties配置
@EnableConfigurationProperties({ ZuulProperties.class })
// 加载前提:classpath下有类ZuulServlet和ZuulServletFilter
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
public class ZuulServerAutoConfiguration {

    // 省略部分代码。。。

  // ZuulController是Controller的一个实现,负责将拦截的请求交给ZuulServlet处理
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

  // ZuulHandlerMapping负责路由匹配
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
            ZuulController zuulController) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
        mapping.setErrorController(this.errorController);
        mapping.setCorsConfigurations(getCorsConfigurations());
        return mapping;
    }

  // 默认加载ZuulServlet
    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "false",
            matchIfMissing = true)
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean<ZuulServlet> 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;
    }

  // 当配置zuul.use-filter=true,加载zuulServletFilter, 表示用filter来拦截请求
    @Bean
    @ConditionalOnMissingBean(name = "zuulServletFilter")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
            matchIfMissing = false)
    public FilterRegistrationBean zuulServletFilter() {
        final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
        filterRegistration.setUrlPatterns(
                Collections.singleton(this.zuulProperties.getServletPattern()));
        filterRegistration.setFilter(new ZuulServletFilter());
        filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
        // The whole point of exposing this servlet is to provide a route that doesn't
        // buffer requests.
        filterRegistration.addInitParameter("buffer-requests", "false");
        return filterRegistration;
    }

    // 在Zuul各阶段filter处理过程中捕获异常,SendErrorFilter会forward "/error" 
    @Bean
    public SendErrorFilter sendErrorFilter() {
        return new SendErrorFilter();
    }

    @Configuration(proxyBeanMethods = false)
    protected static class ZuulFilterConfiguration {

    // 注入Spring容器中的ZuulFilter类型所有的实现类,包括内置和自定义的Filter,内置的有10个
        @Autowired
        private Map<String, ZuulFilter> filters;

    // 注册ZuulFilter到FilterRegistry中
        @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);
        }
    }
}

以上两个类,加载了Zuul的相关配置类:

  • 拦截请求:

    • 和SpringMVC结合的Bean:ZuulController、ZuulHandlerMapping
    • 通过Web Filter拦截请求Bean:ZuulServletFilter
  • Zuul流程需要的Bean:

    • 自带的ZuulFilter,有10个下面会一一介绍

    • 监控相关

    • ZuulFilter的容器:FilterRegistry

默认的ZuulFilters

Pre Filter

  1. ServletDetectionFilter order = -3

    作用:判断请求是否是由DispatcherServlet or ZuulServlet传来的,并把判断结果以键值对的形式放在RequestContext

  2. Servlet30WrapperFilter order = -2

    作用:包装request,兼容servlet3.0

  3. FormBodyWrapperFilter order = -1

    作用:包装表单数据并为下游服务重新编码

  4. DebugFilter order = 1

    作用:如果debug请求,那么会在RequestContext中标记为debug请求和routing

  5. PreDecorationFilter = 5

    作用:请求路由和zuul路由配置进行匹配,并设置与代理相关的头部信息

Route Filter

  1. RibbonRoutingFilter order = 10

    作用:使用Ribbon、Hytrix和可插拔的httpClient发送请求,serviceId、是否重试以及负载均衡策略在相关联的RequestContext获取

  2. SimpleHostRoutingFilter order = 100

    作用:用HttpClient发送请求到预定的URLs,URLs通过RequestContext#getRouteHost()获取

  3. SendForwardFilter order = 500

    作用:用RequestDispatcherforwards请求,转发的地址是RequestContextFilterConstants#FORWARD_TO_KEY对应value

Post Filter

  1. SendResponseFilter order = 1000

    作用:写 代理的请求得到的响应 到 当前响应

Error Filter

  1. SendErrorFilter order = 0

    作用:如果RequestContext#getThrowable() 不为空,默认将请求转发到 /error

SpringMVC怎么把请求转发给Zuul?

从配置类分析

从上述配置可以看下几个重要的配置类源码:

ZuulController

public class ZuulController extends ServletWrappingController {

    public ZuulController() {
    // 设置Servlet的类型
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // Allow all
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        try {
            // We don't care about the other features of the base class, just want to
            // handle the request
            return super.handleRequestInternal(request, response);
        }
        finally {
            // @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
            RequestContext.getCurrentContext().unset();
        }
    }

}

ServletWrappingController

public class ServletWrappingController extends AbstractController implements BeanNameAware, InitializingBean, DisposableBean {
  // 省略代码。。
  
    @Override
        public void afterPropertiesSet() throws Exception {
            if (this.servletClass == null) {
                throw new IllegalArgumentException("'servletClass' is required");
            }
            if (this.servletName == null) {
                this.servletName = this.beanName;
            }
      // 通过反射 初始化servlet
            this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
            this.servletInstance.init(new DelegatingServletConfig());
        }

        // 通过servlet实例处理请求
        @Override
        protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
        throws Exception {
            Assert.state(this.servletInstance != null, "No Servlet instance");
            this.servletInstance.service(request, response);
            return null;
        }
}

ZuulHandlerMapping

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
    private final ZuulController zuul;
    private volatile boolean dirty = true;

  // 根据寻找路由处理器
    @Override
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
            return null;
        }
    // 如果属于配置的忽视路由,则返回null
        if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        }
        if (this.dirty) {
      // dirty默认为true,第一次会触发注册处理器到Spring容器中
      // 或者发送zuul路由刷新事件,设置dirty为true,见ZuulRefreshListener
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
    // 交给Spring查找路由对应的handler
        return super.lookupHandler(urlPath, request);
    }

  // 注册配置路由对应的处理器
    private void registerHandlers() {
        Collection<Route> routes = this.routeLocator.getRoutes();
        if (routes.isEmpty()) {
            this.logger.warn("No routes found from RouteLocator");
        }
        else {
            for (Route route : routes) {
        // 在Spring容器中注册zuul路由配置对应ZuulController处理器
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }

}

以上配置类:

  • ZuulController:它是ServletWrappingController的 子类,其属性包含了ZuulServlet,
  • ZuulHandlerMapping:它是AbstractUrlHandlerMapping的子类
  • ZuulServlet:由上一篇知道它是Zuul流程的入口之一

回顾SpingMVC对于请求的处理流程

  1. 客户端请求交给SpringMVC的DispatcherServlet统一处理
  2. 通过已经注册的HandlerMapping, 根据请求路由找到处理器执行链HandlerExecutionChain,包括请求各个拦截器HandlerInterceptor和请求处理器handler
  3. 找到请求处理器对应的适配器HandlerAdapter
  4. 执行已注册的各拦截器的preHandle方法
  5. 调用处理器处理请求,返回模型数据以及视图ModelAndView
  6. 执行已注册的各拦截器的postHandle方法
  7. 根据给定的ModelAndView进行渲染
  8. 响应客户端

结合SpingMVC对于请求的处理流程可以猜到,当请求给到SpringMVC的DispatcherServlet后,如果该路由是需要Zuul拦截的请求,那么会匹配到ZuulHandlerMapping,从而找到处理器ZuulController,之后在处理的时候,会交给ZuulServlet,后面的流程见上一篇文章。

Debug验证

zuul拦截配置:

# zuul
# 是否启用ZuulServletFilter
# zuul.use-filter=true
ribbon.ConnectTimeout = 30000
ribbon.ReadTimeout = 30000
ribbon.eureka.enabled = false

management.endpoints.web.exposure.include = *
zuul.routes.test.path = /test/**
zuul.routes.test.stripPrefix = false
test.ribbon.listOfServers = ${service.test}
service.test=http://127.0.0.1:8081/t/test

请求:curl -v http://127.0.0.1:8080/test

图示过程:

匹配到ZuulController
执行ZuulServlet

结果显示:猜想是正确的。
大致流程:DispatcherServlet -> ZuulController -> ZuulServlet -> 执行各阶段ZuulFilters

ZuulServlet接管流程图.png

ZuulServletFilter - 另一种拦截请求流程

配置

    // 在类ZuulServerAutoConfiguration中加载

    @Bean
    @ConditionalOnMissingBean(name = "zuulServletFilter")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
            matchIfMissing = false)
    public FilterRegistrationBean zuulServletFilter() {
        final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
    // URL匹配规则: /zuul
        filterRegistration.setUrlPatterns(
                Collections.singleton(this.zuulProperties.getServletPattern()));
        filterRegistration.setFilter(new ZuulServletFilter());
        filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE);
        filterRegistration.addInitParameter("buffer-requests", "false");
        return filterRegistration;
    }

ZuulServletFilter的URL匹配规则是/zuul, 而且如果要是使得ZuulServletFilterBean加载,必须在配置文件中,添加:zuul.use-filter=true,如图:

# 是否启用filter拦截
zuul.use-filter=true
zuul.routes.test.path = /zuul/test/**
zuul.routes.test.stripPrefix = false
test.ribbon.listOfServers = ${service.test}
service.test=http://127.0.0.1:8081

ZuulServletFilter源码

public class ZuulServletFilter extends com.netflix.zuul.filters.ZuulServletFilter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
            FilterChain filterChain) throws IOException, ServletException {
        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();
        super.doFilter(servletRequest, servletResponse, filterChain);
    }
}

源码很简单,在请求上下文添加了一个标志位zuulEngineRan为true。并执行父类com.netflix.zuul.filters.ZuulServletFilterdoFilter方法,进而进入了Zuul的核心流程当中,后面的流程我们已经熟悉了。

其中要要注意下,com.netflix.zuul.filters.ZuulServletFilter虽然是Filter,但是并没有在其doFilter方法中调用FilterChaindoFilter方法,我们可以回想下,如果是我们自己写FIlter,一定会调用。之所以ZuulServletFilte没有这么做,是因为它要接管请求,并不要Servlet来处理。

ZuulServletFilter

大致流程如图:

ZuulServletFilter接管流程图

总结

Zuul和SpringMVC结合并接管请求的方式主要有两种:

  • 在Spring容器中通过注册请求处理器ZuulController和路由处理器的映射ZuulHandlerMapping,做到请求的拦截,并内置了一些ZuulFIlter保证请求的处理。
  • 通过注册ZuulServletFilter,使用Filter方式接管请求,注意默认的路径匹配及生效配置
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352