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
相关产品推荐
相关产品推荐

