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

随着业务范围越来越大,我们的微服务项目越来越多,这么多微服务如何管理、服务之间的鉴权等等成为重中之重,所以随着服务模块的增加,我们就必须引入网关服务,将流量统一转发到网关服务,在网关服务中进行鉴权操作、限流操作、请求追踪等等,所以本节我们重点分析基于Zuul框架的网关,Spring Cloud GateWay网关我们后续会进行分析!


Zuul请求流程图/过滤器

开局一张图,这里我们还是贴上Zuul的官方图,这里的Zuul流程图版本为1.x版本的,基于Servlet实现的,是BIO同步阻塞I/O模式,Zuul2.x版本则是基于Netty这种NIO同步非阻塞的I/O模型,本节我们还是基于Zuul1.x版本分析,针对上图,其实我们也可以简单理解为Servlet Filter拦截器,对请求前、请求中、请求后执行不同的拦截策略!


ZuulConfiguration配置类中主要包含一些基础的配置,比如路由器RouteLocator、pre filter&post filter拦截器、Application事件监听器、Url映射处理实现、Servlet拦截类。由于ZuulConfiguration比较长,所以只总结了重要的部分


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);
    }
    //组合路由器,内部其实只有DiscoveryClientRouteLocator这个具备路由刷新的路由器
    @Bean
    @Primary
    public CompositeRouteLocator primaryRouteLocator(
            Collection<RouteLocator> routeLocators) {
        return new CompositeRouteLocator(routeLocators);
    }
    //简单的路由器,不具备路由刷新功能,只有静态路由功能
    @Bean
    @ConditionalOnMissingBean(SimpleRouteLocator.class)
    public SimpleRouteLocator simpleRouteLocator() {
        return new SimpleRouteLocator(this.server.getServletPrefix(),
                this.zuulProperties);
    }
    //Zuul的核心处理类
    //让ZuulServlet这个Servlet直接包装为一个Controller(这个是servlet.mvc下面的,不是我们的经常使用的@Controller注解,不要搞混了)
    //将请求交给ZuulServlet处理,默认处理的路径为 "/" 这个根目录,默认由DispatcherServlet分发的请求
    @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }
    //注册zuul.routes.*.path的路由处理类为ZuulController(内部其实是ZuulServlet在处理逻辑)
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }
    //监听一些事件,处理路由刷新功能
    @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;
    }
    ......
}

代码片段中我们已经基本注释了每个Bean实例处理的逻辑,上面有一个地方可能有些疑惑,那就是ZuulHandlerMapping内部已经把Zuul配置的路由Url信息全部都映射给了ZuulController(内部也是ZuulServlet处理),为何还需要单独注册一个ZuulServlet?所以笔者这里也只是猜测可能单独注册这个也是为了更明确知道哪些Url路径是可以直接使用ZuulServlet进行处理的,明确知道这种Url信息就是需要Zuul转发的,而不需要经过DispatcherServlet层层处理,总之为了提高效率吧!
其中ZuulServlet可以说是Zuul的核心,外部访问之后都会经由ZuulServlet来做最终的转发处理,下面我们就单独分析下代码片段中非常重要的几个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对应的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;
    }
    //根据实际匹配的路径返回一个Route
    @Override
    public Route getMatchingRoute(String path) {......}
    //刷新路由器
    @Override
    public void refresh() {......}
}

组合路由器主要就是用于分发作用,获取每个RouteLocator对应的操作!


ZuulController

public class ZuulController extends ServletWrappingController {
    public ZuulController() {
        //设置ZuulServlet作为处理类
        setServletClass(ZuulServlet.class);
        setServletName("zuul");
        setSupportedMethods((String[]) null); // 支持所有方法
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            //调用ZuulServlet.service方法
            return super.handleRequestInternal(request, response);
        }
        finally {
            //释放当前请求上下文
            RequestContext.getCurrentContext().unset();
        }
    }
}

ZuulController作用就是将ZuulServlet包装为一个Servlet Controller,让进入到DispatcherServlet的请求通过分发,最终到ZuulController的请求都交由ZuulServlet这个Servlet来处理。


ZuulHandlerMapping

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
    ......
    //查找urlPath对应的处理实例
    @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对应的处理实例为ZuulController
        if (this.dirty) {
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
        //返回当前urlPath对应的处理实例
        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) {
                //注册zuul.routes.*.path的处理实例(ZuulController)
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }
}

ZuulHandlerMapping继承了AbstractUrlHandlerMapping之后通过重写lookupHandler查找实例方法,注册Zuul的路由处理实例为ZuulController,最后在返回当前urlPath对应的处理实例,请求下游服务的一次完整的请求流程(除开/zuul的路径,这个路径直接由ZuulServlet处理)大致为HttpServlet->FrameworkServlet->DispatcherServlet->DispatcherServlet#getHandler()->ZuulHandlerMapping->ZuulController->ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter,DispatcherServlet#getHandler()这个步骤里面就会查找这个urlPath对应的处理实例!


ZuulServlet

上面我们总结和分析了各种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);
    }
    //执行Zuul的filter阶段
    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            //设置zuul的RequestContext请求上下文参数
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                //执行pre filters阶段
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //执行routing filters阶段
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                //执行post filters阶段
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            //释放当前请求上下文
            RequestContext.getCurrentContext().unset();
        }
    }
    //请求完后的拦截器
    void postRoute() throws ZuulException {......}
    //请求中的拦截器,请求下游服务在这个拦截器中
    void route() throws ZuulException {......}
    //请求前的拦截器
    void preRoute() throws ZuulException {......}
    //初始化阶段,设置请求上下文参数
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {......}
    //失败时候的拦截器
    void error(ZuulException e) {......}
    ......
}

至此我们大致分析了ZuulConfiguration配置类中比较重要的一部分,其中ZuulConfiguration#ZuulFilterConfiguration配置我们放到下一节一起分析和总结,下一节我们将分析ZuulFilter(pre filters、routing filters、post filters...)的初始化以及调用过程,即调用链中的ZuulServlet->ZuulRunner-> FilterProcessor->ZuulFilter->RibbonRoutingFilter部分!

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

推荐阅读更多精彩内容