Spring Cloud微服务网关Zuul过滤链实现的源码解读

1,716 阅读6分钟

一、Zuul过滤器的加载过程

Zuul网关的Filter需要经过初始化加载到Spring容器后,才能在请求中发挥作用:

zuul-filter-config.jpg

在上篇文章:《Spring Cloud微服务网关Zuul的注解@EnableZuulProxy或@EnableZuulServer做了什么事情》 中说到的ZuulServerAutoConfiguration中有一个内部配置类ZuulFilterConfiguration就是Zuul中Filter初始化的入口:

@Configuration
protected static class ZuulFilterConfiguration {
   @Autowired
   private Map<String, ZuulFilter> filters;
   @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);
   }
}

ZuulFilterInitializer

public class ZuulFilterInitializer {

   private static final Log log = LogFactory.getLog(ZuulFilterInitializer.class);

   private final Map<String, ZuulFilter> filters;
   private final CounterFactory counterFactory;
   private final TracerFactory tracerFactory;
   private final FilterLoader filterLoader;
   private final FilterRegistry filterRegistry;

   public ZuulFilterInitializer(Map<String, ZuulFilter> filters,
                         CounterFactory counterFactory,
                         TracerFactory tracerFactory,
                         FilterLoader filterLoader,
                         FilterRegistry filterRegistry) {
      this.filters = filters;
      this.counterFactory = counterFactory;
      this.tracerFactory = tracerFactory;
      this.filterLoader = filterLoader;
      this.filterRegistry = filterRegistry;
   }

   @PostConstruct
   public void contextInitialized() {
      log.info("Starting filter initializer");

      TracerFactory.initialize(tracerFactory);
      CounterFactory.initialize(counterFactory);
      // 将ZuulFilter放到FilterRegistry中
      for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
         filterRegistry.put(entry.getKey(), entry.getValue());
      }
   }

   @PreDestroy
   public void contextDestroyed() {
      log.info("Stopping filter initializer");
      for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
         filterRegistry.remove(entry.getKey());
      }
      clearLoaderCache();

      TracerFactory.initialize(null);
      CounterFactory.initialize(null);
   }

   private void clearLoaderCache() {
      Field field = ReflectionUtils.findField(FilterLoader.class, "hashFiltersByType");
      ReflectionUtils.makeAccessible(field);
      @SuppressWarnings("rawtypes")
      Map cache = (Map) ReflectionUtils.getField(field, filterLoader);
      cache.clear();
   }

}

源码上可以看出,FilterLoaderFilterRegistry都是单例对象,然后把Spring容器中的所有ZuulFilter都交给FilterRegistry来管理,像Spring的单例池那样。FilterRegistry内部使用ConcurrentHashMap保存这些单例ZuulFilter

在请求走到ZuulServlet的时候,会检查ZuulRunner是否初始化。如果没初始化,就去执行init方法初始化ZuulRunner;如果已经初始化,就进入service方法执行过滤链的逻辑。

ZuulRunner主要是对RequestContext的初始化,将请求上下文放入声明周期,以及获取FilterProcessor的单例对象,可以说是Zuul过滤链的执行器。最后在FilterProcessor中通过FilterLoader获取相应的Filter并初始化。

二、过滤链实现

Zuul Filter主要分为前置过滤器(pre)、路由过滤器(route)和后置过滤器(post)。

zuul-filter-time-line.jpg

同类型的过滤器组成一个过滤连,可以通过指定过滤器的执行顺序。它们的执行顺序在FilterLoaderFilterProcessor中被定义和执行。FilterProcessor会通过FilterLoader获取同一个类型的过滤器集合,然后遍历这些过滤器按照排序执行。

public class ZuulServlet extends HttpServlet {

    private static final long serialVersionUID = -3374242278843351500L;
    private ZuulRunner zuulRunner;


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

    @Override
    public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        try {
            // 初始化ZuulRUnner
            init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

            // Marks this request as having passed through the "Zuul engine", as opposed to servlets
            // explicitly bound in web.xml, for which requests will not have the same data attached
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                // 前置过滤器链
                preRoute();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // 路由
                route();
            } catch (ZuulException e) {
                error(e);
                postRoute();
                return;
            }
            try {
                // 后置过滤器链
                postRoute();
            } catch (ZuulException e) {
                error(e);
                return;
            }

        } catch (Throwable e) {
            error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }

    /**
     * executes "post" ZuulFilters
     *
     * @throws ZuulException
     */
    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }

    /**
     * executes "route" filters
     *
     * @throws ZuulException
     */
    void route() throws ZuulException {
        zuulRunner.route();
    }

    /**
     * executes "pre" filters
     *
     * @throws ZuulException
     */
    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }

    /**
     * initializes request
     *
     * @param servletRequest
     * @param servletResponse
     */
    void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
        zuulRunner.init(servletRequest, servletResponse);
    }

    /**
     * sets error context info and executes "error" filters
     *
     * @param e
     */
    void error(ZuulException e) {
        RequestContext.getCurrentContext().setThrowable(e);
        zuulRunner.error();
    }
    ...
}

三、核心路由实现源码解析

在之前动态路由配置的文章中有介绍到:Spring Cloud微服务网关Zuul动态路由配置Spring Cloud微服务网关Zuul动态路由配置优化和手动触发路由刷新。那么在本章节将深入源码解析这个类的作用和功能,以及如何去扩展。

其组成的类图如下:

3.1 两个接口RouteLocator和RefreshableRouteLocator

核心的顶层接口RouteLocator:

public interface RouteLocator {
    // 获取忽略的路径
    Collection<String> getIgnoredPaths();
    // 获取所有的路由信息
    List<Route> getRoutes();
    // 根据请求路径获取路由信息
    Route getMatchingRoute(String path);
}

**RefreshableRouteLocator:**这个也是个接口,它是继承了接口RouteLocator。如果要实现路由刷新功能必须要实现该接口。

public interface RefreshableRouteLocator extends RouteLocator {
   // 刷新内存中路由信息
   void refresh();
}

3.2 SimpleRouteLocator源码解析

这个类是真正持有并管理路由信息的类,但是这个类只能加载配置文件中的路由信息,最终这些路由信息被封装到一个Map中:routes。同时它也实现了Ordered接口,可以对定位器优先级进行设置。Spring大量使用了策略模式,通过Ordered接口来实现优先级。

源码如下:

public class SimpleRouteLocator implements RouteLocator, Ordered {

	private static final Log log = LogFactory.getLog(SimpleRouteLocator.class);
	private static final int DEFAULT_ORDER = 0;
    // 读取配置文件中zuul的配置
	private ZuulProperties properties;
	private PathMatcher pathMatcher = new AntPathMatcher();
	private String dispatcherServletPath = "/";
	private String zuulServletPath;
    // 所有的路由配置最终都会保存到这里
	private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();
	private int order = DEFAULT_ORDER;

	public SimpleRouteLocator(String servletPath, ZuulProperties properties) {
        this.properties = properties;
		if (StringUtils.hasText(servletPath)) {
			this.dispatcherServletPath = servletPath;
		}
        // 每一个servlet path 对应一个路由
		this.zuulServletPath = properties.getServletPath();
	}

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

	@Override
	public Collection<String> getIgnoredPaths() {
        // 获取配置文件中的忽略路径
		return this.properties.getIgnoredPatterns();
	}

	@Override
	public Route getMatchingRoute(final String path) {
        // 根据请求路径获取对应的路由配置
		return getSimpleMatchingRoute(path);
	}

	protected Map<String, ZuulRoute> getRoutesMap() {
        // 如果路由信息为空,会把路由配置信息加载到内存
		if (this.routes.get() == null) {
            // 这里的locateRoutes方法这个类的方法,读取的是配置文件中的路由配置
			this.routes.set(locateRoutes());
		}
		return this.routes.get();
	}

	protected Route getSimpleMatchingRoute(final String path) {
        // 根据请求路径来获取路由配置信息
		if (log.isDebugEnabled()) {
			log.debug("Finding route for path: " + path);
		}
		// 调用以下,确保保存路由的map路由配置加载完成
		getRoutesMap();
		if (log.isDebugEnabled()) {
			log.debug("servletPath=" + this.dispatcherServletPath);
			log.debug("zuulServletPath=" + this.zuulServletPath);
			log.debug("RequestUtils.isDispatcherServletRequest()="
					+ RequestUtils.isDispatcherServletRequest());
			log.debug("RequestUtils.isZuulServletRequest()="
					+ RequestUtils.isZuulServletRequest());
		}
        // 调整请求路径
		String adjustedPath = adjustPath(path);
        // 根据路径获取路由表
		ZuulRoute route = getZuulRoute(adjustedPath);
        // 把ZuulRoute封装成Route
		return getRoute(route, adjustedPath);
	}

	protected ZuulRoute getZuulRoute(String adjustedPath) {
		if (!matchesIgnoredPatterns(adjustedPath)) {
			for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
				String pattern = entry.getKey();
				log.debug("Matching pattern:" + pattern);
				if (this.pathMatcher.match(pattern, adjustedPath)) {
					return entry.getValue();
				}
			}
		}
		return null;
	}

	protected Route getRoute(ZuulRoute route, String path) {
		if (route == null) {
			return null;
		}
		if (log.isDebugEnabled()) {
			log.debug("route matched=" + route);
		}
		String targetPath = path;
		String prefix = this.properties.getPrefix();
		if(prefix.endsWith("/")) {
			prefix = prefix.substring(0, prefix.length() - 1);
		}
		if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
			targetPath = path.substring(prefix.length());
		}
		if (route.isStripPrefix()) {
			int index = route.getPath().indexOf("*") - 1;
			if (index > 0) {
				String routePrefix = route.getPath().substring(0, index);
				targetPath = targetPath.replaceFirst(routePrefix, "");
				prefix = prefix + routePrefix;
			}
		}
		Boolean retryable = this.properties.getRetryable();
		if (route.getRetryable() != null) {
			retryable = route.getRetryable();
		}
		return new Route(route.getId(), targetPath, route.getLocation(), prefix,
				retryable,
				route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, 
				route.isStripPrefix());
	}

	protected void doRefresh() {
        // 刷新路由信息,这里刷新操作实质是重新加载配置文件中的路由配置
		this.routes.set(locateRoutes());
	}

	protected Map<String, ZuulRoute> locateRoutes() {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
		for (ZuulRoute route : this.properties.getRoutes().values()) {
			routesMap.put(route.getPath(), route);
		}
		return routesMap;
	}

	protected boolean matchesIgnoredPatterns(String path) {
		for (String pattern : this.properties.getIgnoredPatterns()) {
			log.debug("Matching ignored pattern:" + pattern);
			if (this.pathMatcher.match(pattern, path)) {
				log.debug("Path " + path + " matches ignored pattern " + pattern);
				return true;
			}
		}
		return false;
	}

	private String adjustPath(final String path) {
		String adjustedPath = path;

		if (RequestUtils.isDispatcherServletRequest()
				&& StringUtils.hasText(this.dispatcherServletPath)) {
			if (!this.dispatcherServletPath.equals("/")) {
				adjustedPath = path.substring(this.dispatcherServletPath.length());
				log.debug("Stripped dispatcherServletPath");
			}
		}
		else if (RequestUtils.isZuulServletRequest()) {
			if (StringUtils.hasText(this.zuulServletPath)
					&& !this.zuulServletPath.equals("/")) {
				adjustedPath = path.substring(this.zuulServletPath.length());
				log.debug("Stripped zuulServletPath");
			}
		}
		else {
			// do nothing
		}

		log.debug("adjustedPath=" + adjustedPath);
		return adjustedPath;
	}

	@Override
	public int getOrder() {
		return order;
	}
	
	public void setOrder(int order) {
		this.order = order;
	}
}

3.3 DiscoveryClientRouteLocator

这个类是继承了SimpleRouteLocator,并实现了RefreshableRouteLocator。重写了的locateRoutes()是核心,这个路由定位器的功能就是可以读取注册中心中的服务然后加载其路由配置信息,从源码里面可以看到依赖了DiscoveryClient

public class DiscoveryClientRouteLocator extends SimpleRouteLocator
		implements RefreshableRouteLocator {

	private static final Log log = LogFactory.getLog(DiscoveryClientRouteLocator.class);
	public static final String DEFAULT_ROUTE = "/**";
	private DiscoveryClient discovery;
	private ZuulProperties properties;
	private ServiceRouteMapper serviceRouteMapper;

	...

	public void addRoute(String path, String location) {
		this.properties.getRoutes().put(path, new ZuulRoute(path, location));
		refresh();
	}

	public void addRoute(ZuulRoute route) {
		this.properties.getRoutes().put(route.getPath(), route);
		refresh();
	}

	@Override
	protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
		LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
		routesMap.putAll(super.locateRoutes());
		if (this.discovery != null) {
			Map<String, ZuulRoute> staticServices = new LinkedHashMap<>();
			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
			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;
	}

	@Override
	public void refresh() {
		doRefresh();
	}

	protected String mapRouteToService(String serviceId) {
		return this.serviceRouteMapper.apply(serviceId);
	}

	protected void addConfiguredRoutes(Map<String, ZuulRoute> routes) {
		Map<String, ZuulRoute> routeEntries = this.properties.getRoutes();
		for (ZuulRoute entry : routeEntries.values()) {
			String route = entry.getPath();
			if (routes.containsKey(route)) {
				log.warn("Overwriting route " + route + ": already defined by "
						+ routes.get(route));
			}
			routes.put(route, entry);
		}
	}
}

3.4 CompositeRouteLocator

这是组合所有的RouteLocator的类,并且CompositeRouteLocator注入到Spring容器时,是使用了@Primary注解,也就是说。当你想要在Spring容器中获取RouteLocator时,默认是CompositeRouteLocator。该类并没有什么实质性的修改,它是会引入Spring容器中所有的RouteLocator。然后逐个执行RouteLocator

public class CompositeRouteLocator implements RefreshableRouteLocator {
	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;
	}

	@Override
	public Collection<String> getIgnoredPaths() {
		List<String> ignoredPaths = new ArrayList<>();
		for (RouteLocator locator : routeLocators) {
			ignoredPaths.addAll(locator.getIgnoredPaths());
		}
		return ignoredPaths;
	}

	@Override
	public List<Route> getRoutes() {
		List<Route> route = new ArrayList<>();
		for (RouteLocator locator : routeLocators) {
			route.addAll(locator.getRoutes());
		}
		return route;
	}

	@Override
	public Route getMatchingRoute(String path) {
		for (RouteLocator locator : routeLocators) {
			Route route = locator.getMatchingRoute(path);
			if (route != null) {
				return route;
			}
		}
		return null;
	}

	@Override
	public void refresh() {
		for (RouteLocator locator : routeLocators) {
			if (locator instanceof RefreshableRouteLocator) {
				((RefreshableRouteLocator) locator).refresh();
			}
		}
	}
}

3.5 自定义路由定位器