在 Feign RequestInterceptor / RequestTemplate 中访问 URITemplate 或 RequestLine 值

Access URITemplate or RequestLine value in Feign RequestInterceptor / RequestTemplate

我正在开发一个针对具有严格 api 速率限制的云应用程序的应用程序。为了让我的团队了解我们离这些限制有多近,我想以一种有意义的方式计算从我们的应用发出的所有 API 调用。

我们使用 Feign 作为访问层,我希望能够使用 RequestInterceptor 来计算我们调用的不同 API 端点:

RequestInterceptor ri = rq -> addStatistics(rq.url());

现在这不起作用,因为生成的 URL 之后几乎总是计数为“1”,因为它们已经包含所有已解析的路径变量,所以我得到了

的计数
1 - /something/id1valueverycryptic/get
1 - /something/anothercrypticidkey/get

等等。

我希望以某种方式访问​​ @ResuqestLine 映射值 (GET /something/{id}/get) 或至少访问 uri 模板预解析 (/somethine/{id}/get)

有办法吗?

谢谢!

也许您可以尝试使用自定义 feign InvocationHandlerFactory。

我已经使用如下代码成功记录了 RequestInterceptor:

  • 更改 EnableFeignClients 并添加默认配置

    @EnableFeignClients(defaultConfiguration = FeignConfig.class)
    
  • 添加默认伪装配置

    @Configuration
    public class FeignConfig {
    
    @Bean
    @ConditionalOnMissingBean
    public Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }
    
    @Bean
    @Scope("prototype")
    @ConditionalOnMissingBean
    public Feign.Builder feignBuilder(Retryer retryer) {
        return Feign.builder()
                .retryer(retryer)
                .invocationHandlerFactory((target, dispatch) -> new CountingFeignInvocationHandler(target, dispatch));
    }
    
    }
    
  • 创建您的调用处理程序(代码基于 feign.ReflectiveFeign.FeignInvocationHandler)

    public class CountingFeignInvocationHandler implements InvocationHandler {
    
        private final Target target;
        private final Map<Method, MethodHandler> dispatch;
    
        public CountingFeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
            this.target = checkNotNull(target, "target");
            this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("equals".equals(method.getName())) {
                try {
                    Object otherHandler =
                            args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
                    return equals(otherHandler);
                } catch (IllegalArgumentException e) {
                    return false;
                }
            } else if ("hashCode".equals(method.getName())) {
                return hashCode();
            } else if ("toString".equals(method.getName())) {
                return toString();
            }
    
            RequestLine requestLine = method.getAnnotation(RequestLine.class);
            addStatistics(requestLine.value());
    
            return dispatch.get(method).invoke(args);
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof CountingFeignInvocationHandler) {
                CountingFeignInvocationHandler other = (CountingFeignInvocationHandler) obj;
                return target.equals(other.target);
            }
            return false;
        }
    
        @Override
        public int hashCode() {
            return target.hashCode();
        }
    
        @Override
        public String toString() {
            return target.toString();
        }
    }
    

小心并检查你是否假装配置不更复杂,在这种情况下根据需要扩展 类。

    If you are using spring-cloud-starter-openfeign ,  You could do something like below 
    
    add the a primary contract bean 
    @Bean("YourContract")
    @Primary
        public Contract springpringContract() {
            return (targetType) -> {
    
                List<MethodMetadata> parseAndValidatateMetadata = new SpringMvcContract().parseAndValidatateMetadata(targetType);
                parseAndValidatateMetadata.forEach(metadata -> {
                    RequestTemplate template = metadata.template();
                    template.header("unresolved_uri", template.path().replace("{", "[").replace("}", "]"));
    
                });
                return parseAndValidatateMetadata;
            };
        }
    
    Add the contract to the feign client builder 
    @Bean
     public <T> T feignBuilder(Class<T> feignInterface, String targetURL) {
            return Feign.builder().client(getClient())
                    .contract(contract)
                    .
                    .
    }
    
    Once you are done with the above you should be able to access the unresolved path in the RequestTemplate

@component
public class FeignRequestFilter  implements RequestInterceptor {
    @Override
        public void apply(RequestTemplate template) {
            String unresolvedUri = template.headers().getOrDefault("unresolved_uri", Collections.singleton(template.path()))
                    .iterator().next();
    }
}

也许你可以尝试覆盖 feign Logger。

假设我们有一个假客户,

@FeignClient(name = "demo-client", url = "http://localhost:8080/api", configuration = FeignConfig.class)
public interface DemoClient {

    @GetMapping(value = "/test/{id}")
    void test(@PathVariable(name = "id") Integer id) {
    }
}


import feign.Logger;
import feign.Request;
import feign.Response;

import java.io.IOException;

public class CustomFeignRequestLogging extends Logger {
    @Override
    protected void logRequest(String configKey, Level logLevel, Request request) {
        super.logRequest(configKey, logLevel, request);
        // targetUrl = http://localhost:8080/api
        String targetUrl = request.requestTemplate().feignTarget().url();
        // path = /test/{id}
        String path = request.requestTemplate().methodMetadata().template().path();
    }
}