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

Posted

技术标签:

【中文标题】在 Feign RequestInterceptor / RequestTemplate 中访问 URITemplate 或 RequestLine 值【英文标题】:Access URITemplate or RequestLine value in Feign RequestInterceptor / RequestTemplate 【发布时间】:2019-08-27 03:12:58 【问题描述】:

我正在针对具有硬 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)

有没有办法做到这一点?

谢谢!

【问题讨论】:

它是 Spring Boot 应用程序吗? 是的 - 添加了标签来表明这一点。 【参考方案1】:

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

我已经设法使用如下代码记录 RequestInterceptor:

更改 EnableFeignClients 并添加 defaultConfiguration

@EnableFeignClients(defaultConfiguration = FeignConfig.class)

添加默认的 feign 配置

@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();
    

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

【讨论】:

【参考方案2】:
    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 RequestInterceptor / RequestTemplate 中访问 URITemplate 或 RequestLine 值的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud- 第九篇 Feign

Feign 最佳实现方案探究

Feign

SpringCloud-声明式Rest调用Feign

使用nacos无法正确引入feign

08 在Spring Cloud中使用Feign