自定义全局过滤器
相关面试题:统计接口调用耗时,如何落地,谈谈设计思路?
自定义统计接口耗时的全局过滤器
- https://docs.spring.io/spring-cloud-gateway/docs/4.0.9/reference/html/#gateway-combined-global-filter-and-gatewayfilter-ordering
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 自定义全局过滤器,实现GlobalFilter和Ordered接口,统计接口耗时** @author gengduc@qq.com* @since 2024-03-09*/
@Component
@Slf4j
public class CustomGlobalFilter implements GlobalFilter, Ordered {public static final String BEGIN_VISIT_TIME = "begin_visit_time";@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {/* 1、保存请求开始时间 */exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());return chain.filter(exchange).then(Mono.fromRunnable(() -> {/* 2、获取请求开始时间 */Long beginTime = exchange.getAttribute(BEGIN_VISIT_TIME);if (beginTime != null) {/* 3、计算接口耗时 */log.info("==========访问接口耗时统计==========");log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());log.info("访问接口路径: " + exchange.getRequest().getURI().getPath());log.info("访问接口参数: " + exchange.getRequest().getURI().getQuery());log.info("访问接口时间: " + (System.currentTimeMillis() - beginTime) + "ms");log.info("==============E N D==============");}}));}/*** 设置过滤器的优先级,值越小优先级越高** @return 优先级*/@Overridepublic int getOrder() {return 0;}
}
设计思路:
- 实现
GlobalFilter
和Orderd
接口。 - 实现getOrder()方法,方法返回值代表过滤器的优先级,值越小,优先级越高。
- 实现filter()方法。方法接收两个参数:ServerWebExchange和GatewayFilterChain。ServerWebExchange是一个接口,它提供了对HTTP请求-响应交互的全面访问。GatewayFilterChain是一个接口,它提供了对下一个过滤器的访问。
- 首先,方法将当前的系统时间(请求开始的时间)存储在ServerWebExchange的属性中。这是通过调用exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis())实现的。
- 然后,方法调用chain.filter(exchange)来处理请求。这个调用会将请求传递给过滤器链中的下一个过滤器。如果没有更多的过滤器,那么请求将被发送到目标服务。
- 当请求被处理完毕后,then方法会被调用。这个方法接收一个Runnable参数,这个参数是一个无参数的函数,它会在请求被处理完毕后被执行。
- 在这个Runnable函数中,首先从ServerWebExchange的属性中获取请求开始的时间。然后,计算处理请求所花费的时间(当前的系统时间减去请求开始的时间)。最后,将这些信息记录在日志中。
自定义网关过滤器
要自定义网关过滤器,参考Spring Cloud Gateway自带的网关过滤器实现。
自定义网关过滤器的步骤如下:
- 新建类名
CustomGatewayFilterFactory
(类名需要以GatewayFilterFactory结尾),并继承AbstractGatewayFilterFactory
抽象类。 - 新建
CustomGatewayFilterFactory.Config
静态内部类,这个Config类就是我们自定义的过滤器配置 - 重写apply()方法,处理自定义逻辑,返回
GatewayFilter
对象。 - 重写shortcutFieldOrder()方法,使yml配置支持快捷配置
- 空参构造函数调用了父类的构造函数,传入了Config类的Class对象。
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.Arrays;
import java.util.List;/*** @author gengduc@qq.com* @since 2024-03-09*/
@Component
@Slf4j
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {private static final String STATUS_KEY = "status";public CustomGatewayFilterFactory() {super(Config.class);}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList(STATUS_KEY);}@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("请求进入自定义网关过滤器,请求路径为: {}", exchange.getRequest().getURI().getPath());log.info("提示: 合法请求中请求参数必须包含\"custom\"参数, 否则请求将被拦截");ServerHttpRequest request = exchange.getRequest();if (request.getQueryParams().containsKey("custom")) {log.info("请求参数中包含custom参数, 请求合法, 放行");log.info("请求参数中custom参数的值为: {}", request.getQueryParams().get("custom"));return chain.filter(exchange);} else {log.info("请求参数中不包含custom参数, 请求非法, 拦截");exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);return exchange.getResponse().setComplete();}}};}@Setter@Getterpublic static class Config {// 自定义配置属性private String status;}
}
apply()方法是AbstractGatewayFilterFactory的抽象方法,子类需要实现这个方法来创建过滤器。在这个方法中,返回了一个新的GatewayFilter对象。
在返回的GatewayFilter对象中,重写了filter方法。这个方法在每个请求被处理之前都会被调用。在这个方法中,首先打印了一些日志,然后检查请求参数中是否包含"custom"这个键。如果包含,那么就继续处理请求,并打印出"custom"参数的值;如果不包含,那么就设置响应的状态码为BAD_REQUEST,并结束请求的处理。
spring:application:name: gateway-service # 网关服务名称cloud:consul:host: 47.120.52.144 # Consul服务地址port: 8500 # Consul服务端口discovery:prefer-ip-address: true # 服务注册时优先使用IP地址而不是主机名service-name: ${spring.application.name} # 在Consul中注册的服务名称heartbeat:enabled: true # 启用心跳检测,定期检查服务健康状态gateway:routes:- id: order-routeuri: http://localhost:8001predicates:- Path=/pay/get/1filters:- Custom=custom
浏览器访问地址:
- http://localhost:9527/pay/get/1?custom=1✅
- http://localhost:9527/pay/get/1✖️