1. Zuul フィルターの読み込みプロセス
Zuul ゲートウェイのフィルターは、リクエストで役割を果たす前に、初期化して Spring コンテナーにロードする必要があります。
前回の記事「Spring Cloud マイクロサービス ゲートウェイ Zuul のアノテーションで@EnableZuulProxy または @EnableZuulServer が行うことは何ですか?」では、 Zuul でフィルターを初期化するためのエントリ ポイントであるZuulServerAutoConfiguration
内部構成クラスがあります。ZuulFilterConfiguration
@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 のシングルトン プールと同様に、Spring コンテナー内のすべてが管理のためにZuulFilter
引き渡されることがわかりますFilterRegistry
。これらのシングルトンを保持するためにFilterRegistry
内部的に使用されます。ConcurrentHashMap
ZuulFilter
リクエストが到着すると、初期化されているかどうかをZuulServlet
確認します。ZuulRunner
初期化されていない場合は init メソッドの initialization を実行し、ZuulRunner
初期化されている場合は service メソッドに入り、フィルタ チェーンのロジックを実行します。
ZuulRunner
それは主にRequestContext
ペアの初期化であり、要求コンテキストをステートメント サイクルに入れ、取得されたFilterProcessor
シングルトン オブジェクトであり、Zuul フィルター チェーンのエグゼキューターと言えます。最後に、対応する Filter をFilterProcessor
取得して初期化します。FilterLoader
2. フィルターチェーンの実現
Zuul フィルターは、主にプレフィルター (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
クラスを結合するためのものであり、 Spring コンテナーに注入されると、アノテーションCompositeRouteLocator
が使用されます。デフォルトは、@Primary
Spring コンテナーでフェッチする場合です。このクラスに実質的な変更はなく、すべての Spring コンテナーに導入されます。次に、それらを 1 つずつ実行します。RouteLocator
CompositeRouteLocator
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();
}
}
}
}
复制代码