写点什么

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

  • 2023-04-17
    湖南
  • 本文字数:9256 字

    阅读完需:约 30 分钟

一、Zuul 过滤器的加载过程

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

在上篇文章中说到的ZuulServerAutoConfiguration中有一个内部配置类ZuulFilterConfiguration就是 Zuul 中 Filter 初始化的入口:

@Configurationprotected 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)。


同类型的过滤器组成一个过滤连,可以通过指定过滤器的执行顺序。它们的执行顺序在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(); } } }}
复制代码

作者:𝐿𝑜𝑣𝑒𝐿𝑖𝑓𝑒𝑆𝑢𝑝𝑒𝑟

链接:https://juejin.cn/post/7222268370269159480

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
Spring Cloud微服务网关Zuul过滤链实现的源码解读_做梦都在改BUG_InfoQ写作社区