HttpExchange声明式客户端
前一篇笔记我们介绍了RestClient的用法,它是一个命令式的HTTP客户端类,对于简单的接口调用还算方便,但如果用于大型微服务工程,服务间通信用RestClient是不合适的,命令式的写法仍会产生大量样板代码,使用不够方便。事实也证明开发者在这类工程中更倾向于声明式客户端,例如OpenFeign,SpringBoot 2.x时代OpenFeign便是Spring Cloud实现服务间通信给出的标准方案。不过SpringBoot 3.x时代情况又发生了变化,SpringWeb 6.x把声明式客户端给“吞”了,框架内置了声明式HTTP客户端方案HttpExchange,它的功能基本与OpenFeign相同,不过由于HttpExchange是内置的,因此也就没什么必要再引入一套OpenFeign了。
使用HttpExchange
定义HTTP服务接口
使用@HttpExchange系列注解,我们可以像写Feign接口一样定义HTTP客户端接口。常用的注解包括@HttpExchange、@GetExchange、@PostExchange、@PutExchange、@DeleteExchange等。
package com.gacfox.demo.client;
import com.gacfox.demo.model.QueryParam;
import com.gacfox.demo.model.User;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.service.annotation.*;
import java.util.List;
@HttpExchange("http://localhost:8080/api/v1")
public interface UserClient {
@GetExchange("/getUserById")
User getUserById(@RequestParam Long userId);
@PostExchange("/postUser")
User postUser(@RequestBody User user);
@PostExchange("/getUserList")
List<User> getUserList(@RequestBody QueryParam queryParam);
@GetExchange("/download")
byte[] downloadFile();
@PostExchange(value = "/upload", contentType = "multipart/form-data")
void upload(@RequestParam Long userId, @RequestPart("image") Resource image);
}
通过在方法上使用@GetExchange、@PostExchange等注解,我们清晰地声明了请求方式、路径、参数绑定方式。相比命令式的RestClient,这种声明式写法更加直观。
配置HttpServiceProxyFactory
要让上面的接口生效,需要通过HttpServiceProxyFactory为其创建动态代理实现。HttpExchange本身并不实现具体的HTTP客户端,而是基于适配器模式设计,它需要RestClient或WebClient两者之一作为底层实现,我们这里使用阻塞式的RestClient,因此你需要完成上一章节中RestClient的配置,有关WebClient可以参考SpringWebFlux中的相关章节。
package com.gacfox.demo.config;
import com.gacfox.demo.client.UserClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
@Configuration
public class HttpClientConfig {
@Bean
public UserClient userClient(RestClient restClient) {
RestClientAdapter adapter = RestClientAdapter.create(restClient);
HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build();
return factory.createClient(UserClient.class);
}
}
代码中我们通过方法参数注入了RestClient,然后通过RestClientAdapter类适配,最后使用HttpServiceProxyFactory生成接口的代理实例。生成后的UserClient可以直接注入到Service层使用。
在Service中使用
配置完成后,我们就可以像使用普通Spring Bean一样注入并调用我们配置的HTTP客户端接口了。
package com.gacfox.demo.service;
import com.gacfox.demo.model.QueryParam;
import com.gacfox.demo.model.User;
import com.gacfox.demo.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DemoService {
private final UserClient userClient;
@Autowired
public DemoService(UserClient userClient) {
this.userClient = userClient;
}
public User getUserById(Long userId) {
return userClient.getUserById(userId);
}
public User createUser(User user) {
return userClient.postUser(user);
}
public List<User> getUserList(QueryParam queryParam) {
return userClient.getUserList(queryParam);
}
}
返回ResponseEntity与泛型支持
如果需要获取完整的响应信息(状态码、响应头等),可以将方法的返回值声明为ResponseEntity<T>。
@GetExchange("/getUserById")
ResponseEntity<User> getUserWithResponse(@RequestParam Long userId);
对于复杂泛型(如List<User>)@HttpExchange也提供了良好的支持,无需额外使用ParameterizedTypeReference。
@PostExchange("/getUserList")
List<User> getUserList(@RequestBody QueryParam queryParam);
文件上传与下载
文件上传可以使用@RequestPart结合Resource或MultipartFile实现。
@PostExchange(value = "/upload", contentType = "multipart/form-data")
void upload(@RequestParam Long userId, @RequestPart("image") Resource image);
文件下载同样简单,直接返回byte[]或Resource即可。
@GetExchange("/download")
byte[] downloadFile();
@GetExchange("/download")
Resource downloadAsResource();
关于全局配置与拦截器
前面我们说过HttpExchange本身是基于RestClient或WebClient的,如果需要统一配置超时、请求头、拦截器等,在构建RestClient或WebClient时进行设置即可,HttpExchange中无需再配置什么了。