使用Spring HttpExchange替代FeignClient进行http远程服务调用
背景
spring boot 3.0使用的spring framework 6.0里有一个全新的http服务调用注解@HttpExchange
,该注解的用途是可以进行申明式http远程服务调用。与Feign作用相同,在spring boot 3.x里,由于本身spring内置,相比Feign可以大幅减少第三方包依赖,且比Feign更轻巧。
依赖:
@HttpExchange
位于spring-web.jar里,因此只要项目是spring boot 3.x且引入了spring-boot-starter-web,即可使用@HttpExchange
进行申明式http远程服务调用。
使用步骤
步骤1:使用@HttpExchange
定义http接口,此类可以打包成独立jar或独立maven module工程,用于给后面的provider和client依赖。
@HttpExchange("/hello")
public interface HelloClient {
@GetExchange("/world")
String world(@RequestParam("world") String world);
@PostExchange("/user")
UserResponse user(@RequestBody UserRequest userRequest);
@Data
class UserRequest {
String userName;
}
@Data
class UserResponse {
String userName;
String remark;
}
}
步骤2:在服务提供工程里(provider)里实现一个以上接口的服务端
@RestController
public class HelloProvider implements HelloClient {
@Override
public String world(String world) {
return "Hello " + world;
}
@Override
public UserResponse user(UserRequest userRequest) {
UserResponse userResponse = new UserResponse();
userResponse.setUserName(userRequest.getUserName());
return userResponse;
}
}
步骤3:在服务调用工程里(client)里配置http远程服务client:
@Configuration
public class HttpClientConfig {
@Bean
RestClient.Builder restClientBuilder() {
return RestClient.builder();
}
@Bean
public HelloClient helloClient(RestClient.Builder restClientBuilder) {
return HttpServiceProxyFactory
.builder()
.exchangeAdapter(
RestClientAdapter.create(
restClientBuilder.baseUrl("http://localhost:8001").build()
)
)
.build().createClient(HelloClient.class);
}
}
步骤4:在服务调用工程里(client)里进行http远程服务调用
@RestController
public class MyClientController {
/**
* 注入远程服务
*/
@Autowired
private HelloClient helloClient;
@GetMapping("/client")
public String client() {
return helloClient.world("this is a client"); // 虽然helloClient的实现不在此工程,但调用时像调本地方法一样简单
}
}
jackson序列化指定时间格式
@Bean
public HttpServiceProxyFactory httpServiceProxyFactory(RestClient.Builder restClientBuilder) {
HttpExchangeAdapter httpExchangeAdapter = RestClientAdapter.create(
restClientBuilder.baseUrl(host)
.messageConverters(httpMessageConverters -> {
httpMessageConverters.stream()
.filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
.map(converter -> (MappingJackson2HttpMessageConverter) converter).forEach(converter -> {
converter.setObjectMapper(customObjectMapper());
converter.setPrettyPrint(true);
List<MediaType> mediaTypes = new ArrayList<>(converter.getSupportedMediaTypes());
mediaTypes.add(MediaType.APPLICATION_JSON);
converter.setSupportedMediaTypes(mediaTypes);
});
})
.build()
);
return HttpServiceProxyFactory
.builder()
.exchangeAdapter(httpExchangeAdapter)
.build();
}
public ObjectMapper customObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 其他配置...
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 匹配不上的字段忽略,宽松模式
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 允许出现特殊字符和转义符
objectMapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
// 允许出现单引号
objectMapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(Date.class, new DateSerializer(false, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(Date.class, new com.fasterxml.jackson.databind.JsonDeserializer<Date>() {
@Override
public Date deserialize(com.fasterxml.jackson.core.JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(p.getText());
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
});
objectMapper.registerModule(javaTimeModule);
objectMapper.enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);
return objectMapper;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)