本文基于 spring cloud gateway 2.0.1

1、简介

GlobalGilter 全局过滤器接口与 GatewayFilter 网关过滤器接口具有相同的方法定义。全局过滤器是一系列特殊的过滤器,会根据条件应用到所有路由中。网关过滤器是更细粒度的过滤器,作用于指定的路由中。

从类图中可以看到 GlobalFilter 有十一个实现类,包括路由转发、负载均衡、ws 路由、netty 路由等全局过滤器。下面我们就分别介绍一下这些全局路由过滤器的实现。

2、ForwardRoutingFilter 转发路由过滤器

ForwardRoutingFilter 在交换属性 ServerWebExchangeUtils.GATEWAY_ REQUEST_ URL_ ATTR 中 查找 URL, 如果 URL 为转发模式即 forward:/// localendpoint, 它将使用Spring DispatcherHandler 来处 理请求。 未修改的原始 URL 将保存到 GATEWAY_ ORIGINAL_ REQUEST_ URL_ ATTR 属性的列表中。

public class ForwardRoutingFilter implements GlobalFilter, Ordered {

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

	private final ObjectProvider<DispatcherHandler> dispatcherHandler;

	public ForwardRoutingFilter(ObjectProvider<DispatcherHandler> dispatcherHandler) {
this.dispatcherHandler = dispatcherHandler;
} @Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
} @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
//获取请求URI的请求结构
String scheme = requestUrl.getScheme();
//该路由已经被处理或者URI格式不是forward则继续其它过滤器
if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange); //TODO: translate url? if (log.isTraceEnabled()) {
log.trace("Forwarding to URI: "+requestUrl);
}
// 使用dispatcherHandler进行处理
return this.dispatcherHandler.getIfAvailable().handle(exchange);
}
}

转发路由过滤器实现比较简单,构造函数传入请求的分发处理器DispatcherHandler。过滤器执行时,首先获取请求地址的url前缀,然后判断该请求是否已被路由处理或者URL的前缀不是forward,则继续执行过滤器链;否则设置路由处理状态并交由DispatcherHandler进行处理。

请求路由是否被处理的判断如下:

// ServerWebExchangeUtils.java

public static void setAlreadyRouted(ServerWebExchange exchange) {
exchange.getAttributes().put(GATEWAY_ALREADY_ROUTED_ATTR, true);
} public static boolean isAlreadyRouted(ServerWebExchange exchange) {
return exchange.getAttributeOrDefault(GATEWAY_ALREADY_ROUTED_ATTR, false);
}

两个 方法 定义 在 ServerWebExchangeUtils 中, 这 两个 方法 用于 修改 与 查询 ServerWebExchange 中的 Map< String, Object> getAttributes(),# getAttributes 方法 返回 当前 exchange 所请 求 属性 的 可变 映射。

这两个方法定义在 ServerWebExchangeUtils 中,分别用于修改和查询 GATEWAY_ALREADY_ROUTED_ATTR 状态。

3、LoadBalancerClientFilter 负载均衡客户端过滤器

spring:
cloud:
gateway:
routes:
- id: myRoute
uri: lb://service
predicates:
- Path=/service/**

LoadBalancerClientFilter 在交换属性 GATEWAY_ REQUEST_ URL_ ATTR 中查找URL, 如果URL有一个 lb 前缀 ,即 lb:// myservice,将使用 LoadBalancerClient 将名称 解析为实际的主机和端口,如示例中的 myservice。 未修改的原始 URL将保存到 GATEWAY_ ORIGINAL_ REQUEST_ URL_ ATTR 属性的列表中。过滤器还将查看ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR属性以查看它是否等于lb,然后应用相同的规则。

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
return chain.filter(exchange);
}
//保留原始url
addOriginalRequestUrl(exchange, url); log.trace("LoadBalancerClientFilter url before: " + url);
//负载均衡到具体服务实例
final ServiceInstance instance = choose(exchange); if (instance == null) {
throw new NotFoundException("Unable to find instance for " + url.getHost());
} URI uri = exchange.getRequest().getURI(); //如果没有提供前缀的话,则会使用默认的'< scheme>',否则使用' lb:< scheme>' 机制。
String overrideScheme = null;
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
//根据获取的服务实例信息,重新组装请求的 url
URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);
// Routing 相关 的 GatewayFilter 会 通过 GATEWAY_ REQUEST_ URL_ ATTR 属性, 发起 请求。
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}

从过滤器执行方法中可以看出,负载均衡客户端过滤器的实现步骤如下:

1、构造函数传入负载均衡客户端,依赖中添加 Spring Cloud Netflix Ribbon 即可 注入 该 Bean。

2、获取请求的 URL 及其前缀,如果 URL 不为空且前缀为lb或者网关请求的前缀是 lb,则保存原始的URL,负载到具体的服务实例并根据获取的服务实例信息,重新组装请求的URL。

3、最后,添加请求的URL到GATEWAY_ REQUEST_ URL_ ATTR,并提交到过滤器链中继续执行

在组装请求的地址时,如果loadbalancer没有提供前缀的话,则使用默认的,即overrideScheme 为null,否则的话使用 lb:

4、NettyRoutingFilter 和 NettyWriteResponseFilter

如果 ServerWebExchangeUtils.GATEWAY_ REQUEST_ URL_ ATTR 请求属性中的URL 具有http或https前缀,NettyRoutingFilter 路由过滤器将运行,它使用 Netty HttpClient 代理对下游的请求。响应信息放在ServerWebExchangeUtils.CLIENT_ RESPONSE_ ATTR 属性中,在过滤器链中进行传递。

该过滤器实际处理 和客户端负载均衡的实现方式类似:

首先获取请求的URL及前缀,判断前缀是不是http或者https,如果该请求已经被路由或者前缀不合法,则调用过滤器链直接向后传递;否则正常对头部进行过滤操作。

public class NettyRoutingFilter implements GlobalFilter, Ordered {

	private final HttpClient httpClient;
private final ObjectProvider<List<HttpHeadersFilter>> headersFilters;
private final HttpClientProperties properties; public NettyRoutingFilter(HttpClient httpClient,
ObjectProvider<List<HttpHeadersFilter>> headersFilters,
HttpClientProperties properties) {
this.httpClient = httpClient;
this.headersFilters = headersFilters;
this.properties = properties;
} @Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
} @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange); ServerHttpRequest request = exchange.getRequest(); final HttpMethod method = HttpMethod.valueOf(request.getMethod().toString());
final String url = requestUrl.toString(); HttpHeaders filtered = filterRequest(this.headersFilters.getIfAvailable(),
exchange); final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set); String transferEncoding = request.getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding); boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false); Mono<HttpClientResponse> responseMono = this.httpClient.request(method, url, req -> {
final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnEach)
.headers(httpHeaders)
.chunkedTransfer(chunkedTransfer)
.failOnServerError(false)
.failOnClientError(false); if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
proxyRequest.header(HttpHeaders.HOST, host);
} if (properties.getResponseTimeout() != null) {
proxyRequest.context(ctx -> ctx.addHandlerFirst(
new ReadTimeoutHandler(properties.getResponseTimeout().toMillis(), TimeUnit.MILLISECONDS)));
} return proxyRequest.sendHeaders() //I shouldn't need this
.send(request.getBody().map(dataBuffer ->
((NettyDataBuffer) dataBuffer).getNativeBuffer()));
}); return responseMono.doOnNext(res -> {
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders(); res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue())); String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);
} HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE); response.getHeaders().putAll(filteredResponseHeaders);
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
} else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response).setStatusCodeValue(res.status().code());
} else {
throw new IllegalStateException("Unable to set status code on response: " +res.status().code()+", "+response.getClass());
} // Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
})
.onErrorMap(t -> properties.getResponseTimeout() != null && t instanceof ReadTimeoutException,
t -> new TimeoutException("Response took longer than timeout: " +
properties.getResponseTimeout()))
.then(chain.filter(exchange));
}
}

NettyRoutingFilter 过滤器的构造函数有三个参数:

HttpClient httpClient : 基于 Netty 实现的 HttpClient,通过该属性请求后端 的 Http 服务

ObjectProvider<List> headersFilters: ObjectProvider 类型 的 headersFilters,用于头部过滤

HttpClientProperties properties: Netty HttpClient 的配置属性

4.1、NettyRoutingFilter ## HttpHeadersFilter 头部过滤器接口

filterRequest 用于对请求头部的信息进行处理,是定义在接口 HttpHeadersFilter 中的默认方法,该接口有三个实现类,请求头部将会经过这三个头部过滤器,并最终返回修改之后的头部。

public interface HttpHeadersFilter {

	enum Type {
REQUEST, RESPONSE
} /**
* Filters a set of Http Headers
*
* @param input Http Headers
* @param exchange
* @return filtered Http Headers
*/
HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange); static HttpHeaders filterRequest(List<HttpHeadersFilter> filters,
ServerWebExchange exchange) {
HttpHeaders headers = exchange.getRequest().getHeaders();
return filter(filters, headers, exchange, Type.REQUEST);
} static HttpHeaders filter(List<HttpHeadersFilter> filters, HttpHeaders input,
ServerWebExchange exchange, Type type) {
HttpHeaders response = input;
if (filters != null) {
HttpHeaders reduce = filters.stream()
.filter(headersFilter -> headersFilter.supports(type))
.reduce(input,
(headers, filter) -> filter.filter(headers, exchange),
(httpHeaders, httpHeaders2) -> {
httpHeaders.addAll(httpHeaders2);
return httpHeaders;
});
return reduce;
} return response;
} default boolean supports(Type type) {
return type.equals(Type.REQUEST);
}
}

HttpHeadersFilter 接口的三个实现类:

  • ForwardedHeadersFilter:

    增加 Forwarded头部,头部值为协议类型、host和目标地址

  • XForwardedHeadersFilter:

    增加 X- Forwarded- For、 X- Forwarded- Host、 X- Forwarded- Port 和 X- Forwarded- Proto 头部。 代理转发时,用以自定义的头部信息向下游传递。

  • RemoveHopByHopHeadersFilter:

    为了定义缓存和非缓存代理的行为,我们将HTTP头字段分为两类:端到端的头部字段,发送给请求或响应的最终接收人;逐跳头部字段,对单个传输级别连接有意义,并且不被缓存存储或由代理转发。

    所以该头部过滤器会移除逐跳头部字段,包括以下8个字段:

    Proxy- Authenticate

    Proxy- Authorization

    TE

    Trailer

    Transfer- Encoding

    Upgrade

    proxy- connection

    content- length

4.2、NettyWriteResponseFilter

NettyWriteResponseFilter 与 NettyRoutingFilter 成对使用。“ 预” 过滤阶段没有任何内容,因为 CLIENT_ RESPONSE_ ATTR 在 WebHandler 运行之前不会被添加。

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
// until the WebHandler is run
return chain.filter(exchange).then(Mono.defer(() -> {
HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR); if (clientResponse == null) {
return Mono.empty();
}
log.trace("NettyWriteResponseFilter start");
ServerHttpResponse response = exchange.getResponse(); NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
//TODO: what if it's not netty final Flux<NettyDataBuffer> body = clientResponse.receive()
.retain() //TODO: needed?
.map(factory::wrap); MediaType contentType = null;
try {
contentType = response.getHeaders().getContentType();
} catch (Exception e) {
log.trace("invalid media type", e);
}
return (isStreamingMediaType(contentType) ?
response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
}));
}

如果 CLIENT_ RESPONSE_ ATTR 请求 属性 中 存在 Netty HttpClientResponse, 则 会应用 NettyWriteResponseFilter。 它在其他过滤器完成后运行,并将代理响应写回 网关客户端响应。成对出现的 WebClientHttpRoutingFilter 和 WebClientWriteResponseFilter 过滤器,与基于Nettty 的路由和响应过滤器执行相同 的功能,但不需要使用Netty。

5、RouteToRequestUrlFilter 路由到指定url的过滤器

如果 ServerWebExchangeUtils.GATEWAY_ ROUTE_ ATTR 请求属性中有Route对象, 则 会运行 RouteToRequestUrlFilter 过滤器。他会根据请求URI创建一个新的URI。 新的 URI 位于 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 请求属性中。该过滤器会组装成发送到代理服务的URL地址,向后传递到路由转发的过滤器。

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
if (route == null) {
return chain.filter(exchange);
}
log.trace("RouteToRequestUrlFilter start");
URI uri = exchange.getRequest().getURI();
boolean encoded = containsEncodedParts(uri);
URI routeUri = route.getUri(); if (hasAnotherScheme(routeUri)) {
// this is a special url, save scheme to special attribute
// replace routeUri with schemeSpecificPart
exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
routeUri = URI.create(routeUri.getSchemeSpecificPart());
} URI mergedUrl = UriComponentsBuilder.fromUri(uri)
// .uri(routeUri)
.scheme(routeUri.getScheme())
.host(routeUri.getHost())
.port(routeUri.getPort())
.build(encoded)
.toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
return chain.filter(exchange);
}
  • 首先获取请求中的 Route, 如 果为 空 则 直接 提交 过滤器 链; 否则 获取 routeUri, 并 判断 routeUri 是否 特殊, 如果 是 则需 要 处理 URL, 保存 前缀 到 GATEWAY_SCHEME_PREFIX_ATTR, 并将 routeUri 替换

  • 首先获取请求中的Route,如果为空则直接提交给过滤器链

  • 获取routeUri并判断是否特殊,如果是则需要处理URL,保存前缀到GATEWAY_SCHEME_PREFIX_ATTR,并将routeUri 替换为schemeSpecificPart

  • 然后拼接requestUrl,将请求的URI转换为路由定义的routeUri

  • 最后,提交到过滤器链继续执行

6、WebsocketRoutingFilter

如果请求中的ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 属性对应的URL前缀为 ws 或 wss,则启用Websocket 路由过滤器。它使用Spring Web Socket 作为底层通信组件向下游转发 WebSocket 请求。Websocket 可以通过添加前缀 lb来实现负载均衡,如 lb:ws://serviceid

如果您使用SockJS作为普通http的回调,则应配置正常的HTTP路由以及Websocket路由

spring:
cloud:
gateway:
routes:
# SockJS route
- id: websocket_sockjs_route
uri: http://localhost:3001
predicates:
- Path=/websocket/info/**
# Normwal Websocket route
- id: websocket_route
uri: ws://localhost:3001
predicates:
- Path=/websocket/**

Websocket 路由过滤器进行处理时,首先获取请求的URL及其前缀,判断是否满足 Websocket 过滤器启用的条件;对于未被路由处理且请求前缀为ws或wss的请求,设置路由处理状态位,构造过滤后的头部。最后将请求通过代理转发。

// WebsocketRoutingFilter.java

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//检查websocket 是否是 upgrade
changeSchemeIfIsWebSocketUpgrade(exchange); URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
//判断是否满足websocket启用条件
if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange); HttpHeaders headers = exchange.getRequest().getHeaders();
HttpHeaders filtered = filterRequest(getHeadersFilters(),
exchange); List<String> protocols = headers.get(SEC_WEBSOCKET_PROTOCOL);
if (protocols != null) {
protocols = headers.get(SEC_WEBSOCKET_PROTOCOL).stream()
.flatMap(header -> Arrays.stream(commaDelimitedListToStringArray(header)))
.map(String::trim)
.collect(Collectors.toList());
}
//将请求代理转发
return this.webSocketService.handleRequest(exchange,
new ProxyWebSocketHandler(requestUrl, this.webSocketClient,
filtered, protocols));
}

ProxyWebSocketHandler 是 WebSocketHandler 的实现类,处理客户端 WebSocket Session。 下面看一下代理 WebSocket 处理器的具体实现:

// WebsocketRoutingFilter.java

private static class ProxyWebSocketHandler implements WebSocketHandler {

		private final WebSocketClient client;
private final URI url;
private final HttpHeaders headers;
private final List<String> subProtocols; public ProxyWebSocketHandler(URI url, WebSocketClient client, HttpHeaders headers, List<String> protocols) {
this.client = client;
this.url = url;
this.headers = headers;
if (protocols != null) {
this.subProtocols = protocols;
} else {
this.subProtocols = Collections.emptyList();
}
} @Override
public List<String> getSubProtocols() {
return this.subProtocols;
} @Override
public Mono<Void> handle(WebSocketSession session) {
// pass headers along so custom headers can be sent through
return client.execute(url, this.headers, new WebSocketHandler() {
@Override
public Mono<Void> handle(WebSocketSession proxySession) {
// Use retain() for Reactor Netty
Mono<Void> proxySessionSend = proxySession
.send(session.receive().doOnNext(WebSocketMessage::retain));
// .log("proxySessionSend", Level.FINE);
Mono<Void> serverSessionSend = session
.send(proxySession.receive().doOnNext(WebSocketMessage::retain));
// .log("sessionSend", Level.FINE);
return Mono.zip(proxySessionSend, serverSessionSend).then();
} /**
* Copy subProtocols so they are available downstream.
* @return
*/
@Override
public List<String> getSubProtocols() {
return ProxyWebSocketHandler.this.subProtocols;
}
});
}
}
  • WebSocketClient# execute 方法连接后端被代理的 WebSocket 服务。

  • 连接成功后,回调WebSocketHandler实现的内部类的handle( WebSocketSession session)方法

  • WebSocketHandler 实现的内部类实现对消息的转发: 客户端=> 具体业务服务=> 客户 端; 然后合并代理服务的会话信息 proxySessionSend 和业务服务的会话信息serverSessionSend。

7、其它过滤器

AdaptCachedBodyGlobalFilter— 用于缓存请求体的过滤器,在全局过滤器中的优先级较高。

ForwardPathFilter— 请求中的 gatewayRoute 属性对应 Route 对象,当 Route 中的 URI scheme 为 forward 模式 时, 该过滤器用于设置请求的 URI 路径为 Route 对象 中的 URI 路径。

Spring Cloud Gateway(十一):全局过滤器GlobalFilter的更多相关文章

  1. Spring Cloud Alibaba学习笔记(21) - Spring Cloud Gateway 自定义全局过滤器

    在前文中,我们介绍了Spring Cloud Gateway内置了一系列的全局过滤器,本文介绍如何自定义全局过滤器. 自定义全局过滤需要实现GlobalFilter 接口,该接口和 GatewayFi ...

  2. Spring Cloud Gateway之全局过滤器在工作中的使用场景

    一.使用注意事项 1.全局过滤器作用于所有的路由,不需要单独配置. 2.通过@Order来指定执行的顺序,数字越小,优先级越高. 二.默认全局拦截器的整体架构 三.实战场景,例如,校验token.记录 ...

  3. Spring Cloud Gateway的全局异常处理

    Spring Cloud Gateway中的全局异常处理不能直接用@ControllerAdvice来处理,通过跟踪异常信息的抛出,找到对应的源码,自定义一些处理逻辑来符合业务的需求. 网关都是给接口 ...

  4. Spring Cloud gateway 三 自定义过滤器GatewayFilter

    之前zuul 网关介绍.他有过滤器周期是四种,也是四种类型的过滤器.而gateway 只有俩种过滤器:"pre" 和 "post". PRE: 这种过滤器在请求 ...

  5. Spring Cloud Gateway之全局异常拦截器

    /** * @version 2019/8/14 * @description: 异常拦截器 * @modified: */ @Slf4j public class JsonExceptionHand ...

  6. 看完就会的Spring Cloud Gateway

    在前面几节,我给大家介绍了当一个系统拆分成微服务后,会产生的问题与解决方案:服务如何发现与管理(Nacos注册中心实战),服务与服务如何通信(Ribbon, Feign实战) 今天我们就来聊一聊另一个 ...

  7. Spring Cloud Alibaba学习笔记(20) - Spring Cloud Gateway 内置的全局过滤器

    参考:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filter ...

  8. Spring Cloud Alibaba学习笔记(19) - Spring Cloud Gateway 自定义过滤器工厂

    在前文中,我们介绍了Spring Cloud Gateway内置了一系列的内置过滤器工厂,若Spring Cloud Gateway内置的过滤器工厂无法满足我们的业务需求,那么此时就需要自定义自己的过 ...

  9. spring cloud gateway之filter篇

    转载请标明出处: https://www.fangzhipeng.com 本文出自方志朋的博客 在上一篇文章详细的介绍了Gateway的Predict,Predict决定了请求由哪一个路由处理,在路由 ...

  10. Spring Cloud Gateway GatewayFilter的使用

    Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...

随机推荐

  1. Java自学-类和对象 继承

    什么是 Java的 继承 ? 在LOL中,武器是物品的一种,也是有名称和价格的 所以在设计类的时候,可以让武器继承物品,从而继承名称和价格属性 步骤 1 : 物品类Item 物品类Item 有属性 n ...

  2. linux入门—安装linux系统(1)

    一,linux介绍 linux是一套免费使用和自由传播的类Unix操作系统,简单的说就是不要钱,你可以随便使用,也可以分享给其他人. (剩下的详细内容,个人认为百度百科的内容比我瞎讲强的多,网址:ht ...

  3. nodeJS从入门到进阶三(MongoDB数据库)

    一.MongoDB数据库 1.概念 数据库(DataBase)是一个按照数据结构进行数据的组织,管理,存放数据的仓库. 2.关系型数据库 按照关系模型存储的数据库,数据与数据之间的关系非常密切,可以实 ...

  4. 基于 k8s-搭建 Kubernetes 的 web 管理界面

    查看我们的k8s环境是否正常: 使用kubectl get nodes 获取我们的节点的信息: 到此说明我们的kubernetes环境是正常的,接下来就可以实验了 第一步在master上传所需的软件包 ...

  5. appium自动化webview时遇到的chromedriver问题

    安卓app里面的网页,基本上都是使用手机系统上的webview 去显示的. 安卓 webview 可以看成是 手机上的 chrome 浏览器精简版. appium desktop 里面内置了 用于 w ...

  6. 构建nodejs环境

    总想留下点东西,不负年华! 00.download releasehttps://nodejs.org/dist/      //all release example https://nodejs. ...

  7. 04-JavaScript的操作

    本篇主要介绍获取元素的方法.操作元素.数组和字符串的操作方法.定时器和封闭函数.以及贪吃蛇案例: 一.获取元素的方法 1.document.getElementById:可以使用内置对象documen ...

  8. Tomcat+Nginx+Memcached综合案例

    Tomcat+Nginx+Memcached综合案例 说明 通过Nginx解析静态页面并将动态负载均衡调度给后面的多个Tomcat,Tomcat解析java动态程序. 由于http是无状态的协议,你访 ...

  9. MySQL数据的优化方案

    一.选取最使用的字段属性 mysql可以使用的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快,因此在创建表的时候,为了获得更好的性能,我们可以将表中的字段的宽度尽量设 ...

  10. Map遍历效率比较

    1.由来 上次博客提到了Map的四种遍历方法,其中有的只是获取了key值或者是value值,但我们应该在什么时刻选择什么样的遍历方式呢,必须通过实践的比较才能看到效率. 也看了很多文章,大家建议使用e ...