怎么才能像gateway一样在yml文件中配置自定义的filter?

595 阅读2分钟

效果类似于

spring:
  cloud:
    gateway:
      routes:
        - id: my-service-path
          uri: lb://my-service
          filters:
           - RedirectTo=302, https://acme.org

在yml文件中配置自定义的filter

步骤1:寻找线索

通过IDEA的CTRL+左键单击 发现spring. cloud. gateway.routes指向一个类

@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties

然后,继续CTRL+左键单击发现GatewayProperties被一个类所依赖,即

public class RouteDefinitionRouteLocator
      implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware

然后稍微粗糙的浏览一下里面的方法,属性。发现loadGatewayFilters方法非常符合我们要找的线索:

List<GatewayFilter> loadGatewayFilters(String id,List<FilterDefinition> filterDefinitions) {
        ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size());
        for (int i = 0; i < filterDefinitions.size(); i++) {
            FilterDefinition definition = filterDefinitions.get(i);
            GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());
            ...
            Map<String, Object> properties = factory.shortcutType().normalize(args,factory, this.parser, this.beanFactory);
            Object configuration = factory.newConfig();
            ConfigurationUtils.bind(configuration, properties,factory.shortcutFieldPrefix(), definition.getName(), validator);
            GatewayFilter gatewayFilter = factory.apply(configuration);
            ...
        }
        return ordered;
}

@Validated
public class FilterDefinition {
   @NotNull
   private String name;
   private Map<String, String> args = new LinkedHashMap<>();
   ......
}

仔细观察这个方法,发现有一句代码容易引起注意

GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName());

属性是一个HashMap:

private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();

可以大胆的猜测,这个属性是过滤器名字和过滤器工厂的映射。

步骤二:通过线索,寻找已有实现

通过类似的类进行分析,首先随便找一个观察。在官方文档中找到一个合适的类,例如

public class RedirectToGatewayFilterFactory
      extends AbstractGatewayFilterFactory<RedirectToGatewayFilterFactory.Config>

含有一个内部类

public static class Config {
   String status;
   String url;
   public String getStatus() {
      return status;
   }
   public void setStatus(String status) {
      this.status = status;
   }
   public String getUrl() {
      return url;
   }
   public void setUrl(String url) {
      this.url = url;
   }
}

根据官网的示例,大胆猜测:

spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: https://example.org
        filters:
        - RedirectTo=302, https://acme.org

对于示例中的过滤器RedirectToGatewayFilter,是通过某种方式得到的name=RedirectTo,args=302, acme.org

默认内部类Config定义属性的顺序就是args字符串按,分割后的数组顺序,也可以重写AbstractGatewayFilterFactory的

@Override
public List<String> shortcutFieldOrder()

步骤三:FilterDefinition的Name是怎么来的?

根据线索回溯到属性

private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();

看它是怎么put进去就知道了。利用CTRL+左键寻找代码调用该属性,最终发现

public final class NameUtils {

  public static String normalizeFilterFactoryName(
        Class<? extends GatewayFilterFactory> clazz) {
     return removeGarbage(clazz.getSimpleName()
           .replace(GatewayFilterFactory.class.getSimpleName(), ""));
  }
}

答案就是,通过类名进行字符串切割,所以我们起名字的时候要起成XXXGatewayFilterFactory

步骤四:自定义GatewayFilterFactory

@Component
@Slf4j
public class LogHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory {

    public LogHeaderGatewayFilterFactory() {
        super();
    }
    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            HttpHeaders headers = request.getHeaders();
            headers.entrySet().forEach(entry -> {
                log.info("request header name:{},values{}",entry.getKey(),entry.getValue().toString());
            });
            return chain.filter(exchange);
        };
    }
}

自定义GatewayFilterFactory报类型转换异常,无法将Object转换成内部类?

步骤一:分析异常堆栈

分析异常堆栈,把控制台打印的堆栈顶开始分析,定位问题。发现:

GatewayFilter gatewayFilter = factory.apply(configuration);

是这个对象configuration出现了类型转换异常。溯源,找到创建对象的代码,

Object configuration = factory.newConfig();

断点打上,发现初始化时调用

BeanUtils.instantiateClass(this.configClass);

而debugger中限制this.configClass确实是Object类型,当然就不能强转

步骤二:寻找错误代码

一直CTRL+左键单击溯源configClass属性是怎么初始化的,最终发现是

public AbstractGatewayFilterFactory() {
   super((Class<C>) Object.class);
}

发现这个就是我们自定义GatewayFilterFactory的父类,重写这个方法,类似于

public RedirectToGatewayFilterFactory() {
   super(Config.class);
}