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;
即可。