Skip to main content

Spring Interface Client 与 OpenFeign REST 服务调用

前面我们一直使用的是 RestTemplate 来调用服务,但是 RestTemplate 太麻烦了。因此有了Spring Interface Client。正如名字所示,这个组件是可以看成一个 Controller 的接口,然后可以通过这个接口的 Client 来调用服务。

虽然现在 OpenFeign 是最主流的,但很可惜,已经被 Spring Cloud 官方弃用了。因此我们这里首先介绍的是 Spring Interface Client。现在已经集成在了 Spring Framework 中,因此不需要额外引入依赖。

Spring Framework REST Clients​

在介绍 Spring Interface Client 之前,我们先来看看 Spring Framework 中所有的 REST Clients,一共有 4 类接口。

Client接口阻塞API 风格例子
RestTemplate同步面向资源javaRestTemplate.getForObject("http://example.com/hotels/{hotel}", Hotel.class, "42");
WebClient非阻塞函数式(流式)Mono<Hotel> hotel = webClient.get().uri("/hotels/{hotel}", "42").retrieve().bodyToMono(Hotel.class);
RestClient同步函数式(流式)String result = restClient.get().uri("https://example.com").retrieve().body(String.class);
HTTP Interface取决于代理的实现函数式(声明式)Hotel hotel = service.getHotel("42");

从上面的表格不难看出,HTTP Interface 是写起来最爽的,但是它本质上代理了一个 Client,在 Spring 提供的两个 Client 中,性能上更好的是 WebClient。

但是,每个Client 接口不只有一个实现。比如,RestTemplate默认使用SimpleClientHttpRequestFactory,但是也可以使用HttpComponentsClientHttpRequestFactory。而WebClient默认使用ReactorNetty,但是也可以使用Jetty,还可以用 Apache 的HttpClient,Apache 的 Client 性能更好,因此我们将使用它。支持的列表见文档。

注意,WebClient 不在 Spring Web,而是在 Spring WebFlux 中。因此需要引入 WebFlux。我们后面会讲到 WebFlux,不过现在先用着就好。

implementation 'org.springframework.boot:spring-boot-starter-webflux'

Spring Interface Client 的声明​

首先要声明一个接口,这个接口描述了服务应当如何发送并处理 Http 请求。这里我们要使用注解来完成。这里的声明方法和 Controller 异曲同工,只是变成了 Exchange。

对于我们,这样就可以,

package io.github.fingerbone;

import io.github.fingerbone.record.PaymentRecord;
import io.github.fingerbone.wrapper.ResponseWrapper;
import org.springframework.web.service.annotation.HttpExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.DeleteExchange;
import org.springframework.web.service.annotation.PutExchange;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.List;

@HttpExchange("http://payment/payment")
public interface PaymentAPIIf {

@PostExchange
ResponseWrapper<PaymentRecord> createPayment(@RequestBody PaymentRecord paymentRecord);

@GetExchange
ResponseWrapper<List<PaymentRecord>> getAllPayments();

@GetExchange("/{id}")
ResponseWrapper<PaymentRecord> getPayment(@PathVariable Long id);

@DeleteExchange("/{id}")
ResponseWrapper<Void> deletePayment(@PathVariable Long id);

@PutExchange("/{id}")
ResponseWrapper<PaymentRecord> updatePayment(@PathVariable Long id, @RequestBody PaymentRecord paymentRecord);
}

不过,可以把HttpExchange省掉,然后为每个方法改路径,这样也是可以的。

Spring Interface Client 的创建​

要创建一个 Spring Interface Client,有 4 步。

创建 WebClient​

注意,我们依然要使用 Load Balancer。其它 Client 使用 Load Balancer 方法见文档。

@Bean
@LoadBalanced
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}

@Bean
WebClient webClient(@Autowired WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}

注意,@LoadBalanced只能加在WebClient和RestClient的Builder上

这样子创建的是默认的ReactorNetty。我们要使用Apache的,因此要添加依赖。因为WebClient使用的是Reactor,因此要用reactive的 http core。

implementation 'org.apache.httpcomponents.client5:httpclient5:5.3'
implementation 'org.apache.httpcomponents.core5:httpcore5-reactive:5.2.5'
@Bean
@LoadBalanced
WebClient.Builder webClientBuilder() {
HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom();
clientBuilder.setDefaultRequestConfig(RequestConfig.custom().build());
CloseableHttpAsyncClient client = clientBuilder.build();
ClientHttpConnector connector = new HttpComponentsClientHttpConnector(client);
return WebClient.builder().clientConnector(connector);
}

@Bean
WebClient webClient(@Autowired WebClient.Builder webClientBuilder) {
return webClientBuilder.build();
}

创建 Adapter​

@Bean
WebClientAdapter webClientAdapter(@Autowired WebClient webClient) {
return WebClientAdapter.create(webClient);
}

创建 HttpServiceProxyFactory​

@Bean
HttpServiceProxyFactory httpServiceProxyFactory(@Autowired WebClientAdapter webClientAdapter) {
return HttpServiceProxyFactory.builderFor(webClientAdapter).build();
}

创建 Interface Client 对象​

@Bean
PaymentAPIIf paymentAPIIf(@Autowired HttpServiceProxyFactory httpServiceProxyFactory) {
return httpServiceProxyFactory.createClient(PaymentAPIIf.class);
}

这样,这个对象就可以直接使用了。

OpenFeign 的使用​

OpenFeign 和 Spring Interface Client 的使用方法基本一致,只是声明方法不同。

首先引入依赖,

implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.apache.httpcomponents.client5:httpclient5:5.3'
implementation 'io.github.openfeign:feign-hc5'

后面两个是用来开启 Apache 的 HttpClient 的。只要在application.yml中配置,

spring:
cloud:
openfeign:
httpclient:
hc5:
enabled: true

即可。如果使用默认 Client,可以不配置。

然后,需要在启动类上加上@EnableFeignClients,

@EnableFeignClients
@SpringBootApplication
public class PaymentServiceApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentServiceApplication.class, args);
}
}

现在就可以创建接口了,

package io.github.fingerbone.api;

import io.github.fingerbone.record.PaymentRecord;
import io.github.fingerbone.wrapper.ResponseWrapper;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@FeignClient(name = "payment-service", path = "/payment")
public interface PaymentApi {

@PostMapping
ResponseWrapper<PaymentRecord> createPayment(@RequestBody PaymentRecord paymentRecord);

@GetMapping
ResponseWrapper<List<PaymentRecord>> getAllPayments();

@GetMapping("/{id}")
ResponseWrapper<PaymentRecord> getPayment(@PathVariable("id") Long id);

@DeleteMapping("/{id}")
ResponseWrapper<Void> deletePayment(@PathVariable("id") Long id);

@PutMapping("/{id}")
ResponseWrapper<PaymentRecord> updatePayment(@PathVariable("id") Long id, @RequestBody PaymentRecord paymentRecord);
}

与 Spring Interface Client 不同的是,不需要手动创建 Client 对象,而是直接注入即可。

@Autowired
PaymentApi paymentApi;

即可。