Spring Cloud 微服务网关 Zuul 过滤链实现的源码解读
- 2023-04-17 湖南
本文字数:9256 字
阅读完需:约 30 分钟
一、Zuul 过滤器的加载过程
Zuul 网关的 Filter 需要经过初始化加载到 Spring 容器后,才能在请求中发挥作用:
在上篇文章中说到的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();
}
}
源码上可以看出,FilterLoader
和FilterRegistry
都是单例对象,然后把 Spring 容器中的所有ZuulFilter
都交给FilterRegistry
来管理,像 Spring 的单例池那样。FilterRegistry
内部使用ConcurrentHashMap
保存这些单例ZuulFilter
。
在请求走到ZuulServlet
的时候,会检查ZuulRunner
是否初始化。如果没初始化,就去执行 init 方法初始化ZuulRunner
;如果已经初始化,就进入 service 方法执行过滤链的逻辑。
ZuulRunner
主要是对RequestContext
的初始化,将请求上下文放入声明周期,以及获取FilterProcessor
的单例对象,可以说是 Zuul 过滤链的执行器。最后在FilterProcessor
中通过FilterLoader
获取相应的 Filter 并初始化。
二、过滤链实现
Zuul Filter 主要分为前置过滤器(pre)、路由过滤器(route)和后置过滤器(post)。
同类型的过滤器组成一个过滤连,可以通过指定过滤器的执行顺序。它们的执行顺序在FilterLoader
和FilterProcessor
中被定义和执行。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
来源:稀土掘金
做梦都在改BUG
还未添加个人签名 2021-07-28 加入
公众号:该用户快成仙了
评论