Spring Cloud Zuul 分析(一)之ZuulConfiguration入口配置

隨著業(yè)務(wù)范圍越來越大阻星,我們的微服務(wù)項目越來越多,這么多微服務(wù)如何管理已添、服務(wù)之間的鑒權(quán)等等成為重中之重妥箕,所以隨著服務(wù)模塊的增加,我們就必須引入網(wǎng)關(guān)服務(wù)更舞,將流量統(tǒng)一轉(zhuǎn)發(fā)到網(wǎng)關(guān)服務(wù)畦幢,在網(wǎng)關(guān)服務(wù)中進(jìn)行鑒權(quán)操作、限流操作缆蝉、請求追蹤等等宇葱,所以本節(jié)我們重點分析基于Zuul框架的網(wǎng)關(guān)瘦真,Spring Cloud GateWay網(wǎng)關(guān)我們后續(xù)會進(jìn)行分析!


Zuul請求流程圖/過濾器

開局一張圖黍瞧,這里我們還是貼上Zuul的官方圖吗氏,這里的Zuul流程圖版本為1.x版本的,基于Servlet實現(xiàn)的雷逆,是BIO同步阻塞I/O模式,Zuul2.x版本則是基于Netty這種NIO同步非阻塞的I/O模型污尉,本節(jié)我們還是基于Zuul1.x版本分析膀哲,針對上圖,其實我們也可以簡單理解為Servlet Filter攔截器被碗,對請求前某宪、請求中、請求后執(zhí)行不同的攔截策略锐朴!


ZuulConfiguration配置類中主要包含一些基礎(chǔ)的配置兴喂,比如路由器RouteLocator、pre filter&post filter攔截器焚志、Application事件監(jiān)聽器衣迷、Url映射處理實現(xiàn)、Servlet攔截類酱酬。由于ZuulConfiguration比較長壶谒,所以只總結(jié)了重要的部分


ZuulConfiguration

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {
    @Autowired
    protected ZuulProperties zuulProperties;
    @Autowired
    protected ServerProperties server;
    @Autowired(required = false)
    private ErrorController errorController;
    //注冊Zuul特性描述
    @Bean
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Simple)", ZuulConfiguration.class);
    }
    //組合路由器,內(nèi)部其實只有DiscoveryClientRouteLocator這個具備路由刷新的路由器
    @Bean
    @Primary
    public CompositeRouteLocator primaryRouteLocator(
            Collection<RouteLocator> routeLocators) {
        return new CompositeRouteLocator(routeLocators);
    }
    //簡單的路由器膳沽,不具備路由刷新功能汗菜,只有靜態(tài)路由功能
    @Bean
    @ConditionalOnMissingBean(SimpleRouteLocator.class)
    public SimpleRouteLocator simpleRouteLocator() {
        return new SimpleRouteLocator(this.server.getServletPrefix(),
                this.zuulProperties);
    }
    //Zuul的核心處理類
    //讓ZuulServlet這個Servlet直接包裝為一個Controller(這個是servlet.mvc下面的,不是我們的經(jīng)常使用的@Controller注解挑社,不要搞混了)
    //將請求交給ZuulServlet處理陨界,默認(rèn)處理的路徑為 "/" 這個根目錄,默認(rèn)由DispatcherServlet分發(fā)的請求
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }
    //注冊zuul.routes.*.path的路由處理類為ZuulController(內(nèi)部其實是ZuulServlet在處理邏輯)
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }
    //監(jiān)聽一些事件痛阻,處理路由刷新功能
    @Bean
    public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
        return new ZuulRefreshListener();
    }
    //注冊一個ZuulServlet, 處理的路徑/zuul 開頭的路徑
    @Bean
    @ConditionalOnMissingBean(name = "zuulServlet")
    public ServletRegistrationBean zuulServlet() {
        ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
                this.zuulProperties.getServletPattern());
        servlet.addInitParameter("buffer-requests", "false");
        return servlet;
    }
    ......
}

代碼片段中我們已經(jīng)基本注釋了每個Bean實例處理的邏輯菌瘪,上面有一個地方可能有些疑惑,那就是ZuulHandlerMapping內(nèi)部已經(jīng)把Zuul配置的路由Url信息全部都映射給了ZuulController(內(nèi)部也是ZuulServlet處理)录平,為何還需要單獨注冊一個ZuulServlet麻车?所以筆者這里也只是猜測可能單獨注冊這個也是為了更明確知道哪些Url路徑是可以直接使用ZuulServlet進(jìn)行處理的,明確知道這種Url信息就是需要Zuul轉(zhuǎn)發(fā)的,而不需要經(jīng)過DispatcherServlet層層處理斗这,總之為了提高效率吧动猬!
其中ZuulServlet可以說是Zuul的核心,外部訪問之后都會經(jīng)由ZuulServlet來做最終的轉(zhuǎn)發(fā)處理表箭,下面我們就單獨分析下代碼片段中非常重要的幾個Bean實例(CompositeRouteLocator赁咙、ZuulHandlerMapping、ZuulController)


CompositeRouteLocator組合路由器

public class CompositeRouteLocator implements RefreshableRouteLocator {
    //此集合只有一個DiscoveryClientRouteLocator路由
    private final Collection<? extends RouteLocator> routeLocators;
    private ArrayList<RouteLocator> rl;

    public CompositeRouteLocator(Collection<? extends RouteLocator> routeLocators) {
        Assert.notNull(routeLocators, "'routeLocators' must not be null");
        rl = new ArrayList<>(routeLocators);
        AnnotationAwareOrderComparator.sort(rl);
        this.routeLocators = rl;
    }
    //獲取忽略的路徑url,也就是讓部分url對應(yīng)的Service不暴露給外面
    @Override
    public Collection<String> getIgnoredPaths() {
        List<String> ignoredPaths = new ArrayList<>();
        for (RouteLocator locator : routeLocators) {
            ignoredPaths.addAll(locator.getIgnoredPaths());
        }
        return ignoredPaths;
    }
    //獲取路由映射集合,url等等信息
    @Override
    public List<Route> getRoutes() {
        List<Route> route = new ArrayList<>();
        for (RouteLocator locator : routeLocators) {
            route.addAll(locator.getRoutes());
        }
        return route;
    }
    //根據(jù)實際匹配的路徑返回一個Route
    @Override
    public Route getMatchingRoute(String path) {......}
    //刷新路由器
    @Override
    public void refresh() {......}
}

組合路由器主要就是用于分發(fā)作用,獲取每個RouteLocator對應(yīng)的操作彼水!


ZuulController

public class ZuulController extends ServletWrappingController {
    public ZuulController() {
        //設(shè)置ZuulServlet作為處理類
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // 支持所有方法
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            //調(diào)用ZuulServlet.service方法
            return super.handleRequestInternal(request, response);
        }
        finally {
            //釋放當(dāng)前請求上下文
            RequestContext.getCurrentContext().unset();
        }
    }
}

ZuulController作用就是將ZuulServlet包裝為一個Servlet Controller崔拥,讓進(jìn)入到DispatcherServlet的請求通過分發(fā),最終到ZuulController的請求都交由ZuulServlet這個Servlet來處理凤覆。


ZuulHandlerMapping

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
    ......
    //查找urlPath對應(yīng)的處理實例
    @Override
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        //urlPath與errorController匹配則忽略
        if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
            return null;
        }
        //若匹配到配置的忽略地址則忽略
        String[] ignored = this.routeLocator.getIgnoredPaths().toArray(new String[0]);
        if (PatternMatchUtils.simpleMatch(ignored, urlPath)) {
            return null;
        }
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        }
        //重點链瓦,注冊Zuul配置的路由url對應(yīng)的處理實例為ZuulController
        if (this.dirty) {
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
        //返回當(dāng)前urlPath對應(yīng)的處理實例
        return super.lookupHandler(urlPath, request);
    }
    //注冊路由對應(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) {
                //注冊zuul.routes.*.path的處理實例(ZuulController)
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }
}

ZuulHandlerMapping繼承了AbstractUrlHandlerMapping之后通過重寫lookupHandler查找實例方法,注冊Zuul的路由處理實例為ZuulController盯桦,最后在返回當(dāng)前urlPath對應(yīng)的處理實例慈俯,請求下游服務(wù)的一次完整的請求流程(除開/zuul的路徑,這個路徑直接由ZuulServlet處理)大致為HttpServlet->FrameworkServlet->DispatcherServlet->DispatcherServlet#getHandler()->ZuulHandlerMapping->ZuulController->ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter拥峦,DispatcherServlet#getHandler()這個步驟里面就會查找這個urlPath對應(yīng)的處理實例贴膘!


ZuulServlet

上面我們總結(jié)和分析了各種Bean實例的作用,其實都是在圍繞著ZuulServlet這個Servlet略号,ZuulServlet作為Zuul的一個非常核心的功能刑峡,作為Zuul的請求入口處理類,那都做了什么事情呢玄柠,我們接著往下看突梦!

public class ZuulServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
        zuulRunner = new ZuulRunner(bufferReqs);
    }
    //執(zhí)行Zuul的filter階段
    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            //設(shè)置zuul的RequestContext請求上下文參數(shù)
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                //執(zhí)行pre filters階段
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //執(zhí)行routing filters階段
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //執(zhí)行post filters階段
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            //釋放當(dāng)前請求上下文
            RequestContext.getCurrentContext().unset();
        }
    }
    //請求完后的攔截器
    void postRoute() throws ZuulException {......}
    //請求中的攔截器,請求下游服務(wù)在這個攔截器中
    void route() throws ZuulException {......}
    //請求前的攔截器
    void preRoute() throws ZuulException {......}
    //初始化階段,設(shè)置請求上下文參數(shù)
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {......}
    //失敗時候的攔截器
    void error(ZuulException e) {......}
    ......
}

至此我們大致分析了ZuulConfiguration配置類中比較重要的一部分羽利,其中ZuulConfiguration#ZuulFilterConfiguration配置我們放到下一節(jié)一起分析和總結(jié)阳似,下一節(jié)我們將分析ZuulFilter(pre filters、routing filters铐伴、post filters...)的初始化以及調(diào)用過程撮奏,即調(diào)用鏈中的ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter部分!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末当宴,一起剝皮案震驚了整個濱河市畜吊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌户矢,老刑警劉巖玲献,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梯浪,居然都是意外死亡捌年,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門挂洛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來礼预,“玉大人,你說我怎么就攤上這事虏劲⊥兴幔” “怎么了褒颈?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長励堡。 經(jīng)常有香客問我谷丸,道長,這世上最難降的妖魔是什么应结? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任刨疼,我火速辦了婚禮,結(jié)果婚禮上鹅龄,老公的妹妹穿的比我還像新娘币狠。我一直安慰自己,他們只是感情好砾层,可當(dāng)我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贱案,像睡著了一般肛炮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宝踪,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天侨糟,我揣著相機(jī)與錄音,去河邊找鬼瘩燥。 笑死秕重,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厉膀。 我是一名探鬼主播溶耘,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼服鹅!你這毒婦竟也來了凳兵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤企软,失蹤者是張志新(化名)和其女友劉穎庐扫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仗哨,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡形庭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了厌漂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片萨醒。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苇倡,靈堂內(nèi)的尸體忽然破棺而出验靡,到底是詐尸還是另有隱情倍宾,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布胜嗓,位于F島的核電站高职,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辞州。R本人自食惡果不足惜怔锌,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望变过。 院中可真熱鬧埃元,春花似錦、人聲如沸媚狰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崭孤。三九已至类嗤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辨宠,已是汗流浹背遗锣。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嗤形,地道東北人精偿。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像赋兵,于是被迫代替她去往敵國和親笔咽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容