1. 程式人生 > >spring-cloud 原始碼 zuul 路由定位(三)

spring-cloud 原始碼 zuul 路由定位(三)

zuul 路由定位過程

 

1 RouteLocator 路由器

這裡寫圖片描述
從上面圖可以知道,RouteLocator 有三個實現類,在使用@EnableZuulServer 使用到
SimpleRouteLocator,CompositeRouteLocator 作為簡單的路由定位,如開啟的是@EnableZuulProxy 則使用到DiscoveryClientRouteLocator 及CompositeRouteLocator(該路由器只是組合)

2 路由器如作初始化

結合zuul的第一編原始碼分析
@EnableZuulServer 生效的配置 ZuulServerAutoConfiguration ,非代理方式轉發請求

    @Bean
    @Primary
    public CompositeRouteLocator primaryRouteLocator(
            Collection<RouteLocator> routeLocators) {
        return new CompositeRouteLocator(routeLocators);
    }

    //當SimpleRouteLocator 例項不存在時建立其例項
    @Bean
    @ConditionalOnMissingBean(SimpleRouteLocator.class)
    public
SimpleRouteLocator simpleRouteLocator() { return new SimpleRouteLocator(this.server.getServletPrefix(), this.zuulProperties); } //當前建立的RouteLocator 被裝入ZuulHandlerMapping(spring-mvc的內容) @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new
ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; }

@EnableZuulProxy生效的配置 ZuulProxyAutoConfiguration,動態代理路由轉發請求
當DiscoveryClientRouteLocator例項不存在時建立其例項,因為DiscoveryClientRouteLocator是SimpleRouteLocator的子類,所有當該例項建立後,SimpleRouteLocator 不會再被建立,而簡接裝入ZuulHandlerMapping 是DiscoveryClientRouteLocator

    @Bean
    @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
    public DiscoveryClientRouteLocator discoveryRouteLocator() {
        return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties,
                this.serviceRouteMapper);
    }

3 RooteLocator執行過程

1.從Dispatcher->ZuulHandlerMapping->ZuulController->ZuulServlet->ZuulRunner->FilterProcessor->ZuulFilter->PreDecorationFilter/CompositeRouteLocator(SimpleRouteLocator)
前面三個類呼叫過程是spring-mvc的內容不再分析,這裡重點看ZuulServerlet後面的原始碼
2.靜態路由將會把url及handler controler註冊到ZuulHandlerMapping
ZuulHandlerMapping
動態更新路由資訊

監聽服務資訊變化,以更可以動態變更路由資訊

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);
        }

    }

標記由路資訊是否髒了,如果髒了,則重新整理路由資訊
這裡寫圖片描述

    @Override
    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
        if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
            return null;
        }
        if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        }
        if (this.dirty) {
            synchronized (this) {
                //如果路由資訊髒了,則重新註冊新的處理器
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
        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) {
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }
    protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;

        // Eagerly resolve handler if referencing singleton via name.
        if (!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String) handler;
            if (getApplicationContext().isSingleton(handlerName)) {
                resolvedHandler = getApplicationContext().getBean(handlerName);
            }
        }
        //靜態路由url處理器是否為傳入的(ZuulController)
        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                        "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
            }
        }
        else {
            //配置根路徑
            if (urlPath.equals("/")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Root mapping to " + getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            }
            //匹配*萬用字元
            else if (urlPath.equals("/*")) {
                if (logger.isInfoEnabled()) {
                    logger.info("Default mapping to " + getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            }
            else {
                //儲存其它的url對應的handler
                this.handlerMap.put(urlPath, resolvedHandler);
                if (logger.isInfoEnabled()) {
                    logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
                }
            }
        }
    }

4 SimpleRouteLocator

該類主要是處理靜態路由

開始獲取靜態路由資訊

    @Override
    public List<Route> getRoutes() {
        List<Route> values = new ArrayList<>();
        for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
            ZuulRoute route = entry.getValue();
            String path = route.getPath();
            values.add(getRoute(route, path));
        }
        return values;
    }
    protected Map<String, ZuulRoute> getRoutesMap() {
        if (this.routes.get() == null) {
            this.routes.set(locateRoutes());
        }
        return this.routes.get();
    }

獲取靜態配置路由資訊

    /**
     * Compute a map of path pattern to route. The default is just a static map from the
     * {@link ZuulProperties}, but subclasses can add dynamic calculations.
     */
    protected Map<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
        for (ZuulRoute route : this.properties.getRoutes().values()) {
            routesMap.put(route.getPath(), route);
        }
        return routesMap;
    }

5 DiscoveryClientRouteLocator

繼承 SimpleRouteLocator 主要是通過eureka client發現服務資訊,以便動態更新路由資訊

    @Override
    protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
        //呼叫父類方法,獲取靜態路由資訊
        routesMap.putAll(super.locateRoutes());
        if (this.discovery != null) {

            Map<String, ZuulRoute> staticServices = new LinkedHashMap<String, ZuulRoute>();
            for (ZuulRoute route : routesMap.values()) {
                String serviceId = route.getServiceId();
                if (serviceId == null) {
                    serviceId = route.getId();
                }
                if (serviceId != null) {
                    staticServices.put(serviceId, route);
                }
            }
            // Add routes for discovery services by default
            //通過discovery發現服務,並動態建立服務路由資訊
            List<String> services = this.discovery.getServices();
            String[] ignored = this.properties.getIgnoredServices()
                    .toArray(new String[0]);
            for (String serviceId : services) {
                // Ignore specifically ignored services and those that were manually
                // configured
                String key = "/" + mapRouteToService(serviceId) + "/**";
                if (staticServices.containsKey(serviceId)
                        && staticServices.get(serviceId).getUrl() == null) {
                    // Explicitly configured with no URL, cannot be ignored
                    // all static routes are already in routesMap
                    // Update location using serviceId if location is null
                    ZuulRoute staticRoute = staticServices.get(serviceId);
                    if (!StringUtils.hasText(staticRoute.getLocation())) {
                        staticRoute.setLocation(serviceId);
                    }
                }
                if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
                        && !routesMap.containsKey(key)) {
                    // Not ignored
                    routesMap.put(key, new ZuulRoute(key, serviceId));
                }
            }
        }
        if (routesMap.get(DEFAULT_ROUTE) != null) {
            ZuulRoute defaultRoute = routesMap.get(DEFAULT_ROUTE);
            // Move the defaultServiceId to the end
            routesMap.remove(DEFAULT_ROUTE);
            routesMap.put(DEFAULT_ROUTE, defaultRoute);
        }
        LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
        for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            // Prepend with slash if not already present.
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.hasText(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getValue());
        }
        return values;
    }