使用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;
    }
posted @   漠孤烟  阅读(1597)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示