SpringCloud整合Zuul源碼分析

回顧

  1. Zuul是通過(guò)ZuulServletFilter或者 ZuulServlet接管我們的請(qǐng)求

  2. Zuul整個(gè)流程如下:

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

目標(biāo)

明確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)
// 注入觸發(fā)Zuul配置類的標(biāo)記Bean
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}

ZuulProxyAutoConfiguration - Zuul自動(dòng)配置Bean

// 此配置類不會(huì)被代理
@Configuration(proxyBeanMethods = false)
// 引入Ribbon相關(guān)配置
@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自動(dòng)配置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的一個(gè)實(shí)現(xiàn),負(fù)責(zé)將攔截的請(qǐng)求交給ZuulServlet處理
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

  // ZuulHandlerMapping負(fù)責(zé)路由匹配
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes,
            ZuulController zuulController) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController);
        mapping.setErrorController(this.errorController);
        mapping.setCorsConfigurations(getCorsConfigurations());
        return mapping;
    }

  // 默認(rèn)加載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;
    }

  // 當(dāng)配置zuul.use-filter=true,加載zuulServletFilter, 表示用filter來(lái)攔截請(qǐng)求
    @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處理過(guò)程中捕獲異常颁独,SendErrorFilter會(huì)forward "/error" 
    @Bean
    public SendErrorFilter sendErrorFilter() {
        return new SendErrorFilter();
    }

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

    // 注入Spring容器中的ZuulFilter類型所有的實(shí)現(xiàn)類,包括內(nèi)置和自定義的Filter伪冰,內(nèi)置的有10個(gè)
        @Autowired
        private Map<String, ZuulFilter> filters;

    // 注冊(cè)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);
        }
    }
}

以上兩個(gè)類誓酒,加載了Zuul的相關(guān)配置類:

  • 攔截請(qǐng)求:

    • 和SpringMVC結(jié)合的Bean:ZuulControllerZuulHandlerMapping
    • 通過(guò)Web Filter攔截請(qǐng)求Bean:ZuulServletFilter
  • Zuul流程需要的Bean:

    • 自帶的ZuulFilter糜值,有10個(gè)下面會(huì)一一介紹

    • 監(jiān)控相關(guān)

    • ZuulFilter的容器:FilterRegistry

默認(rèn)的ZuulFilters

Pre Filter

  1. ServletDetectionFilter order = -3

    作用:判斷請(qǐng)求是否是由DispatcherServlet or ZuulServlet傳來(lái)的丰捷,并把判斷結(jié)果以鍵值對(duì)的形式放在RequestContext

  2. Servlet30WrapperFilter order = -2

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

  3. FormBodyWrapperFilter order = -1

    作用:包裝表單數(shù)據(jù)并為下游服務(wù)重新編碼

  4. DebugFilter order = 1

    作用:如果debug請(qǐng)求寂汇,那么會(huì)在RequestContext中標(biāo)記為debug請(qǐng)求和routing

  5. PreDecorationFilter = 5

    作用:請(qǐng)求路由和zuul路由配置進(jìn)行匹配病往,并設(shè)置與代理相關(guān)的頭部信息

Route Filter

  1. RibbonRoutingFilter order = 10

    作用:使用Ribbon、Hytrix和可插拔的httpClient發(fā)送請(qǐng)求骄瓣,serviceId停巷、是否重試以及負(fù)載均衡策略在相關(guān)聯(lián)的RequestContext獲取

  2. SimpleHostRoutingFilter order = 100

    作用:用HttpClient發(fā)送請(qǐng)求到預(yù)定的URLs,URLs通過(guò)RequestContext#getRouteHost()獲取

  3. SendForwardFilter order = 500

    作用:用RequestDispatcherforwards請(qǐng)求,轉(zhuǎn)發(fā)的地址是RequestContextFilterConstants#FORWARD_TO_KEY對(duì)應(yīng)value

Post Filter

  1. SendResponseFilter order = 1000

    作用:寫 代理的請(qǐng)求得到的響應(yīng) 到 當(dāng)前響應(yīng)

Error Filter

  1. SendErrorFilter order = 0

    作用:如果RequestContext#getThrowable() 不為空畔勤,默認(rèn)將請(qǐng)求轉(zhuǎn)發(fā)到 /error

SpringMVC怎么把請(qǐng)求轉(zhuǎn)發(fā)給Zuul蕾各?

從配置類分析

從上述配置可以看下幾個(gè)重要的配置類源碼:

ZuulController

public class ZuulController extends ServletWrappingController {

    public ZuulController() {
    // 設(shè)置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;
            }
      // 通過(guò)反射 初始化servlet
            this.servletInstance = ReflectionUtils.accessibleConstructor(this.servletClass).newInstance();
            this.servletInstance.init(new DelegatingServletConfig());
        }

        // 通過(guò)servlet實(shí)例處理請(qǐng)求
        @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;

  // 根據(jù)尋找路由處理器
    @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默認(rèn)為true,第一次會(huì)觸發(fā)注冊(cè)處理器到Spring容器中
      // 或者發(fā)送zuul路由刷新事件缸榛,設(shè)置dirty為true吝羞,見ZuulRefreshListener
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
    // 交給Spring查找路由對(duì)應(yīng)的handler
        return super.lookupHandler(urlPath, request);
    }

  // 注冊(cè)配置路由對(duì)應(yīng)的處理器
    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容器中注冊(cè)zuul路由配置對(duì)應(yīng)ZuulController處理器
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }

}

以上配置類:

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

回顧SpingMVC對(duì)于請(qǐng)求的處理流程

  1. 客戶端請(qǐng)求交給SpringMVC的DispatcherServlet統(tǒng)一處理
  2. 通過(guò)已經(jīng)注冊(cè)的HandlerMapping, 根據(jù)請(qǐng)求路由找到處理器執(zhí)行鏈HandlerExecutionChain钧排,包括請(qǐng)求各個(gè)攔截器HandlerInterceptor和請(qǐng)求處理器handler
  3. 找到請(qǐng)求處理器對(duì)應(yīng)的適配器HandlerAdapter
  4. 執(zhí)行已注冊(cè)的各攔截器的preHandle方法
  5. 調(diào)用處理器處理請(qǐng)求,返回模型數(shù)據(jù)以及視圖ModelAndView
  6. 執(zhí)行已注冊(cè)的各攔截器的postHandle方法
  7. 根據(jù)給定的ModelAndView進(jìn)行渲染
  8. 響應(yīng)客戶端

結(jié)合SpingMVC對(duì)于請(qǐng)求的處理流程可以猜到均澳,當(dāng)請(qǐng)求給到SpringMVC的DispatcherServlet后恨溜,如果該路由是需要Zuul攔截的請(qǐng)求,那么會(huì)匹配到ZuulHandlerMapping找前,從而找到處理器ZuulController糟袁,之后在處理的時(shí)候,會(huì)交給ZuulServlet纸厉,后面的流程見上一篇文章系吭。

Debug驗(yàn)證

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

請(qǐng)求:curl -v http://127.0.0.1:8080/test

圖示過(guò)程:

匹配到ZuulController
執(zhí)行ZuulServlet

結(jié)果顯示:猜想是正確的五嫂。
大致流程:DispatcherServlet -> ZuulController -> ZuulServlet -> 執(zhí)行各階段ZuulFilters

ZuulServlet接管流程圖.png

ZuulServletFilter - 另一種攔截請(qǐng)求流程

配置

    // 在類ZuulServerAutoConfiguration中加載

    @Bean
    @ConditionalOnMissingBean(name = "zuulServletFilter")
    @ConditionalOnProperty(name = "zuul.use-filter", havingValue = "true",
            matchIfMissing = false)
    public FilterRegistrationBean zuulServletFilter() {
        final FilterRegistrationBean<ZuulServletFilter> filterRegistration = new FilterRegistrationBean<>();
    // URL匹配規(guī)則: /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匹配規(guī)則是/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);
    }
}

源碼很簡(jiǎn)單躯枢,在請(qǐng)求上下文添加了一個(gè)標(biāo)志位zuulEngineRan為true。并執(zhí)行父類com.netflix.zuul.filters.ZuulServletFilterdoFilter方法槐臀,進(jìn)而進(jìn)入了Zuul的核心流程當(dāng)中锄蹂,后面的流程我們已經(jīng)熟悉了。

其中要要注意下水慨,com.netflix.zuul.filters.ZuulServletFilter雖然是Filter得糜,但是并沒(méi)有在其doFilter方法中調(diào)用FilterChaindoFilter方法,我們可以回想下晰洒,如果是我們自己寫FIlter朝抖,一定會(huì)調(diào)用。之所以ZuulServletFilte沒(méi)有這么做谍珊,是因?yàn)樗庸苷?qǐng)求治宣,并不要Servlet來(lái)處理。

ZuulServletFilter

大致流程如圖:

ZuulServletFilter接管流程圖

總結(jié)

Zuul和SpringMVC結(jié)合并接管請(qǐng)求的方式主要有兩種:

  • 在Spring容器中通過(guò)注冊(cè)請(qǐng)求處理器ZuulController和路由處理器的映射ZuulHandlerMapping,做到請(qǐng)求的攔截侮邀,并內(nèi)置了一些ZuulFIlter保證請(qǐng)求的處理坏怪。
  • 通過(guò)注冊(cè)ZuulServletFilter,使用Filter方式接管請(qǐng)求,注意默認(rèn)的路徑匹配及生效配置
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末绊茧,一起剝皮案震驚了整個(gè)濱河市铝宵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌华畏,老刑警劉巖捉超,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異唯绍,居然都是意外死亡拼岳,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門况芒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惜纸,“玉大人,你說(shuō)我怎么就攤上這事绝骚∧桶妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵压汪,是天一觀的道長(zhǎng)粪牲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)止剖,這世上最難降的妖魔是什么腺阳? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮穿香,結(jié)果婚禮上亭引,老公的妹妹穿的比我還像新娘。我一直安慰自己皮获,他們只是感情好焙蚓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著洒宝,像睡著了一般固惯。 火紅的嫁衣襯著肌膚如雪溜在。 梳的紋絲不亂的頭發(fā)上数苫,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天柱查,我揣著相機(jī)與錄音,去河邊找鬼将宪。 笑死绘闷,一個(gè)胖子當(dāng)著我的面吹牛橡庞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播印蔗,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扒最,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了华嘹?” 一聲冷哼從身側(cè)響起吧趣,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耙厚,沒(méi)想到半個(gè)月后强挫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薛躬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年俯渤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片型宝。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡八匠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趴酣,到底是詐尸還是另有隱情梨树,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布岖寞,位于F島的核電站抡四,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仗谆。R本人自食惡果不足惜指巡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胸私。 院中可真熱鬧厌处,春花似錦、人聲如沸岁疼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捷绒。三九已至,卻和暖如春贯要,著一層夾襖步出監(jiān)牢的瞬間暖侨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工崇渗, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留字逗,地道東北人京郑。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像葫掉,于是被迫代替她去往敵國(guó)和親些举。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355