You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud OpenFeign如何发送含单个字符串的JSON请求

解决Spring Cloud OpenFeign 4.0.1中String类型请求体序列化为JSON字符串的问题

针对你遇到的FeignClient传递String时无法序列化为带引号的合法JSON字符串的问题,这里提供几个更优雅的替代方案:

方案1:自定义String类型专属消息转换器

通过扩展Spring的MappingJackson2HttpMessageConverter,仅针对String类型添加特殊序列化逻辑,既不影响其他类型的原有序列化行为,也无需替换全局Encoder:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import java.io.IOException;
import java.util.List;

public class StringJsonHttpMessageConverter extends MappingJackson2HttpMessageConverter {
    private final ObjectMapper objectMapper;

    public StringJsonHttpMessageConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
        setSupportedMediaTypes(List.of(MediaType.APPLICATION_JSON));
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return String.class.isAssignableFrom(clazz) && super.canWrite(clazz, mediaType);
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, JsonProcessingException {
        String jsonStr = objectMapper.writeValueAsString(object);
        outputMessage.getBody().write(jsonStr.getBytes(getDefaultCharset()));
    }

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        return String.class.isAssignableFrom(clazz) && super.canRead(clazz, mediaType);
    }

    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, JsonProcessingException {
        String jsonStr = new String(inputMessage.getBody().readAllBytes(), getDefaultCharset());
        return objectMapper.readValue(jsonStr, String.class);
    }
}

接着在FeignClient的配置类中注册该转换器:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

@Configuration
public class FeignClientConfig {
    @Bean
    public HttpMessageConverter<String> stringJsonHttpMessageConverter(MappingJackson2HttpMessageConverter defaultConverter) {
        return new StringJsonHttpMessageConverter(defaultConverter.getObjectMapper());
    }
}

最后在FeignClient上指定这个配置即可:

@FeignClient(value = "client", 
             url = "https://${example.host}:${example.port}/api/2/", 
             dismiss404 = true,
             configuration = FeignClientConfig.class)
public interface ExampleClient {
    @GetMapping(value = "/path/to/resource", produces = MediaType.APPLICATION_JSON_VALUE)
    String getResourceValue();

    @PutMapping(value = "/path/to/resource", consumes = MediaType.APPLICATION_JSON_VALUE)
    void setResourceValue(@RequestBody String hostname);
}

方案2:包装类配合@JsonValue注解

如果不想自定义转换器,可以创建一个轻量包装类,通过@JsonValue让Jackson直接将内部String序列化为带引号的JSON:

import com.fasterxml.jackson.annotation.JsonValue;

public class JsonStringWrapper {
    private final String value;

    public JsonStringWrapper(String value) {
        this.value = value;
    }

    @JsonValue
    public String getValue() {
        return value;
    }
}

修改FeignClient的方法:

@PutMapping(value = "/path/to/resource", consumes = MediaType.APPLICATION_JSON_VALUE)
void setResourceValue(@RequestBody JsonStringWrapper hostname);

调用时只需传入new JsonStringWrapper("your-hostname"),反序列化时带引号的JSON字符串也能自动转为普通String类型。

方案3:局部自定义Feign Encoder

仅针对单个FeignClient修改Encoder逻辑,不影响全局:

import feign.RequestTemplate;
import feign.codec.Encoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.http.MediaType;

import java.lang.reflect.Type;

public class StringJsonEncoder implements Encoder {
    private final SpringEncoder delegate;

    @Autowired
    public StringJsonEncoder(SpringEncoder delegate) {
        this.delegate = delegate;
    }

    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (object instanceof String) {
            template.header("Content-Type", MediaType.APPLICATION_JSON_VALUE.toString());
            template.body("\"" + object + "\"");
        } else {
            delegate.encode(object, bodyType, template);
        }
    }
}

在配置类中注册该Encoder:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.openfeign.support.SpringEncoder;

@Configuration
public class FeignClientConfig {
    @Bean
    public Encoder stringJsonEncoder(SpringEncoder springEncoder) {
        return new StringJsonEncoder(springEncoder);
    }
}

内容的提问来源于stack exchange,提问作者kyberpunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 07:05:23