我该如何在运行时更改Feign的URL?

57

@FeignClient(name = "test", url="http://xxxx")

我如何在运行时更改feign的URL(url="http://xxxx")?因为URL只能在运行时确定。

10个回答

70

您可以添加一个未注释的URI参数(可能在运行时确定),并且该参数将作为请求的基本路径。例如:

您可以添加一个未注释的 URI 参数(可能在运行时确定),并且该参数将成为用于请求的基本路径。例如:

    @FeignClient(name = "dummy-name", url = "https://this-is-a-placeholder.com")
    public interface MyClient {
        @PostMapping(path = "/create")
        UserDto createUser(URI baseUrl, @RequestBody UserDto userDto);
    }

然后使用方法如下:

    @Autowired 
    private MyClient myClient;
    ...
    URI determinedBasePathUri = URI.create("https://my-determined-host.com");
    myClient.createUser(determinedBasePathUri, userDto);

这将向https://my-determined-host.com/create发送一个POST请求(来源)。


在你的例子中,path (例如 /create)是否也可以是动态的? - lily
1
@lily 是的,你应该使用 @PathVariable。更多信息请参见这里 - Robert Jakubowski

31

Feign有一种方法,在运行时提供动态的URL和端点。

必须按照以下步骤进行:

  1. FeignClient接口中,我们必须删除URL参数。 我们必须使用@RequestLine注释来指定REST方法(GET、PUT、POST等):

    @FeignClient(name="customerProfileAdapter")
    public interface CustomerProfileAdaptor {

        // @RequestMapping(method=RequestMethod.GET, value="/get_all")
        @RequestLine("GET")
        public List<Customer> getAllCustomers(URI baseUri); 
     
        // @RequestMapping(method=RequestMethod.POST, value="/add")
        @RequestLine("POST")
        public ResponseEntity<CustomerProfileResponse> addCustomer(URI baseUri, Customer customer);
     
        @RequestLine("DELETE")
        public ResponseEntity<CustomerProfileResponse> deleteCustomer(URI baseUri, String mobile);
    }
    
  1. 在 RestController 中,您需要导入 FeignClientConfiguration。
  2. 您需要编写一个 RestController 构造函数,并将编码器和解码器作为参数。
  3. 您需要使用编码器和解码器构建 FeignClient。
  4. 在调用 FeignClient 方法时,如果有任何 rest 调用参数,请提供 URI(BaserUrl + endpoint)。

    @RestController
    @Import(FeignClientsConfiguration.class)
    public class FeignDemoController {
    
        CustomerProfileAdaptor customerProfileAdaptor;
    
        @Autowired
        public FeignDemoController(Decoder decoder, Encoder encoder) {
            customerProfileAdaptor = Feign.builder().encoder(encoder).decoder(decoder) 
               .target(Target.EmptyTarget.create(CustomerProfileAdaptor.class));
        }
    
        @RequestMapping(value = "/get_all", method = RequestMethod.GET)
        public List<Customer> getAllCustomers() throws URISyntaxException {
            return customerProfileAdaptor
                .getAllCustomers(new URI("http://localhost:8090/customer-profile/get_all"));
        }
    
        @RequestMapping(value = "/add", method = RequestMethod.POST)
        public ResponseEntity<CustomerProfileResponse> addCustomer(@RequestBody Customer customer) 
                throws URISyntaxException {
            return customerProfileAdaptor
                .addCustomer(new URI("http://localhost:8090/customer-profile/add"), customer);
        }
    
        @RequestMapping(value = "/delete", method = RequestMethod.POST)
        public ResponseEntity<CustomerProfileResponse> deleteCustomer(@RequestBody String mobile)
                throws URISyntaxException {
            return customerProfileAdaptor
                .deleteCustomer(new URI("http://localhost:8090/customer-profile/delete"), mobile);
        }
    }

谢谢。但是我能从属性/ YAML 文件中读取 URL,使用 @Value@ConfigurationProperties 进行注入吗?我找不到如何做到这一点。 - WesternGun
3
这种方法改变了方法的签名,因此不是一个好的解决方案。请参见下面@Forest10的答案,使用Spring有更好的解决方案。 - Simon Zambrovski

10

我不知道你是否根据多个配置文件使用Spring,例如像(dev、beta、prod等)。如果你依赖不同的yml或者properties文件,你可以这样定义FeignClient:(@FeignClient(url = "${feign.client.url.TestUrl}", configuration = FeignConf.class))。

然后,你就可以定义:

...(以下内容需要补充)

feign:
  client:
    url:
      TestUrl: http://dev:dev

在你的应用程序-dev.yml文件中

定义

feign:
  client:
    url:
      TestUrl: http://beta:beta

在你的应用程序-beta.yml文件中(我更喜欢yml格式)。

......

感谢上帝。享受吧。


这是我认为最好的答案,特别是对于使用多个环境的大型项目。 - Omar YAYA
2
这个问题是关于运行时的,但是这个答案只能在启动应用程序时使用,而不能在运行时使用。 - Pavel Krizhanovskiy
4
这只是一个初创公司的解决方案,原始问题是有关运行时的,所以这是无关的。 - Luís Cunha

9

使用 feign.Target.EmptyTarget

@Bean
public BotRemoteClient botRemoteClient(){
    return Feign.builder().target(Target.EmptyTarget.create(BotRemoteClient.class));
}

public interface BotRemoteClient {

    @RequestLine("POST /message")
    @Headers("Content-Type: application/json")
    BotMessageRs sendMessage(URI url, BotMessageRq message);
}

botRemoteClient.sendMessage(new URI("http://google.com"), rq)

8
您可以手动创建客户端:
@Import(FeignClientsConfiguration.class)
class FooController {

    private FooClient fooClient;

    private FooClient adminClient;

    @Autowired
    public FooController(ResponseEntityDecoder decoder, SpringEncoder encoder, Client client) {
        this.fooClient = Feign.builder().client(client)
            .encoder(encoder)
            .decoder(decoder)
            .requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
            .target(FooClient.class, "http://PROD-SVC");
        this.adminClient = Feign.builder().client(client)
            .encoder(encoder)
            .decoder(decoder)
            .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
            .target(FooClient.class, "http://PROD-SVC");
     }
}

来自文档:https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#_creating_feign_clients_manually

根据文档,可以手动创建Feign客户端。

4

在接口中,您可以通过Spring注释更改URL。基本URI在yml Spring配置中进行配置。

   @FeignClient(
            name = "some.client",
            url = "${some.serviceUrl:}",
            configuration = FeignClientConfiguration.class
    )

public interface SomeClient {

    @GetMapping("/metadata/search")
    String search(@RequestBody SearchCriteria criteria);

    @GetMapping("/files/{id}")
    StreamingResponseBody downloadFileById(@PathVariable("id") UUID id);

}

1
一个简单的方法是使用拦截器:RequestInterceptor 如果在拦截器中设置了目标主机,feign将替换目标URL。
  // source code of feign
  Request targetRequest(RequestTemplate template) {
    for (RequestInterceptor interceptor : requestInterceptors) {
      interceptor.apply(template);
    }
    return target.apply(template);
  }

  public Request apply(RequestTemplate input) {
    if (input.url().indexOf("http") != 0) {
      input.target(url());
    }
    return input.request();
  }

自定义你的拦截器:
public class DynamicFeignUrlInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        if (isNotDynamicPath(template)) {
            return;
        }
        template.target(getHost());
    }

    private boolean isNotDynamicPath(RequestTemplate template) {
        // TODO Determine whether it is dynamic according to your logic
        return false;
    }

    private String getHost() {
        // use any host you want, host must be contained key word of 'http' 
        return "http://example.com";
    }

}

这样做的好处是,如果已经有大量的伪客户端代码,可以在不修改代码的情况下实施。

0

像这样使用@PathVariable:

@Service    
@FeignClient(name = "otherservicename", decode404 = true)    
public interface myService {

@RequestMapping(method = RequestMethod.POST, value = "/basepath/{request-path}")
ResponseEntity<String> getResult(@RequestHeader("Authorization") String token,
                                                      @RequestBody HashMap<String, String> reqBody,
                                                      @PathVariable(value = "request-path") String requestPath);
}

然后从服务中构建动态URL路径并发送请求:

String requestPath = "approve-req";

ResponseEntity<String> responseEntity = myService.getResult(
                token, reqBody, requestPath);

您的请求URL将为:"/basepath/approve-req"


1
作者询问的是动态URL,而不是动态请求路径。你的回答与此无关。 - Igor Bljahhin

-2

我更喜欢通过配置构建Feign客户端,在运行时传递URL(在我的情况下,我从Consul发现服务中获取服务名称)

因此,我将扩展Feign目标类如下:

public class DynamicTarget<T> implements Target<T> {
private final CustomLoadBalancer loadBalancer;
private final String serviceId;
private final Class<T> type;

public DynamicTarget(String serviceId, Class<T> type, CustomLoadBalancer loadBalancer) {
    this.loadBalancer = loadBalancer;
    this.serviceId = serviceId;
    this.type = type;
}

@Override
public Class<T> type() {
    return type;
}

@Override
public String name() {
    return serviceId;
}

@Override
public String url() {
    return loadBalancer.getServiceUrl(name());
}

@Override
public Request apply(RequestTemplate requestTemplate) {
    requestTemplate.target(url());
    return requestTemplate.request();
}
}


 var target = new DynamicTarget<>(Services.service_id, ExamsAdapter.class, loadBalancer);

-3
                package commxx;

                import java.net.URI;
                import java.net.URISyntaxException;

                import feign.Client;
                import feign.Feign;
                import feign.RequestLine;
                import feign.Retryer;
                import feign.Target;
                import feign.codec.Encoder;
                import feign.codec.Encoder.Default;
                import feign.codec.StringDecoder;

                public class FeignTest {

                    public interface someItfs {

                 
                        @RequestLine("GET")
                        String getx(URI baseUri);
                    }

                    public static void main(String[] args) throws URISyntaxException {
                        String url = "http://www.baidu.com/s?wd=ddd";  //ok..
                        someItfs someItfs1 = Feign.builder()
                                // .logger(new FeignInfoLogger()) // 自定义日志类,继承 feign.Logger
                                // .logLevel(Logger.Level.BASIC)// 日志级别
                                // Default(long period, long maxPeriod, int maxAttempts)
                                .client(new Client.Default(null, null))// 默认 http
                                .retryer(new Retryer.Default(5000, 5000, 1))// 5s超时,仅1次重试
                            //  .encoder(Encoder)
                            //  .decoder(new StringDecoder())
                                .target(Target.EmptyTarget.create(someItfs.class));

                //       String url = "http://localhost:9104/";
                //         
                        System.out.println(someItfs1.getx(new URI(url)));
                    }

                }

你能够在回答中加入一些上下文信息吗?同时请清理代码,删除对百度的引用以及无关的注释。 - Marcel Dumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接