Spring+OpenFeign调用WebLogic上JEE服务报400:JSON格式不匹配
问题描述
使用OpenFeign发送携带对象列表参数的Post请求调用Web服务时,部署在WebLogic上的目标JEE服务无法识别发送的JSON格式——服务期望的JSON外层多一个tEbourse键。该问题仅在WebLogic环境出现,WildFly环境下正常。
相关代码及格式如下:
Feign客户端接口
@FeignClient(name = "clientfeign",url = "http://192.168.x.x:8080") public interface SRestClient { @PostMapping(path = "/api/method-adresse") List<TEbourse> checkFonctionnaire(@RequestBody List<TEbourse> demandes); }
实体类TEbourse
@Data @AllArgsConstructor @NoArgsConstructor public class TEbourse { private int id; private String nom; private String prenom; private String sexe; private String date_naissance; private String lieu_naissance; private String matricule; private String statut; }
当前发送的JSON格式
[ { "ID": "12", "date_naissance": "08/01/84", "lieu_naissance": "", "matricule": "", "nom": "XXXX", "prenom": "XXXX XX", "sexe": "M", "statut": "" }, { "ID": "20", "date_naissance": "02/10/98", "lieu_naissance": "", "matricule": "", "nom": "XXXX", "prenom": "XXXX XX", "sexe": "M", "statut": "" }, { "ID": "152", "date_naissance": "27/03/94", "lieu_naissance": "", "matricule": "", "nom": "XXXX", "prenom": "XXXX XX", "sexe": "M", "statut": "" } ]
目标服务期望的JSON格式
{ "tEbourse":[ { "date_naissance": "08/01/84", "ID": "12", "lieu_naissance": "", "matricule": "", "nom": "XXXX", "prenom": "XXXX XX", "sexe": "M", "statut": "" }, { "date_naissance": "02/10/98", "ID": "20", "lieu_naissance": "", "matricule": "", "nom": "XXXX", "prenom": "XXXX XX", "sexe": "M", "statut": "" }, { "date_naissance": "27/03/94", "ID": "152", "lieu_naissance": "", "matricule": "", "nom": "XXXX", "prenom": "XXXX XX", "sexe": "M", "statut": "" } ]}
问题原因
WildFly和WebLogic对JAX-RS请求参数的解析策略存在差异:
- WildFly默认支持直接接收JSON数组作为请求体,无需外层包装键;
- WebLogic的JAX-RS实现(多为Oracle的Jersey版本)更严格,要求请求体必须是JSON对象,数组需要被包裹在指定键下才能与服务端参数正确绑定。
核心原因还是服务端在WebLogic上的接口定义,大概率是将列表参数封装在含tEbourse属性的对象中,而非直接接收List类型。
解决方案
提供两种可选择的实现方式:
方式一:创建包装类封装列表
这是最直接且易维护的方案:
- 新增包装类,属性名与服务端期望的
tEbourse完全匹配:
@Data @AllArgsConstructor @NoArgsConstructor public class TEbourseWrapper { private List<TEbourse> tEbourse; }
- 修改Feign客户端接口,将请求参数替换为包装类:
@FeignClient(name = "clientfeign",url = "http://192.168.x.x:8080") public interface SRestClient { @PostMapping(path = "/api/method-adresse") List<TEbourse> checkFonctionnaire(@RequestBody TEbourseWrapper demandesWrapper); }
- 调用时将原列表封装到包装类中:
List<TEbourse> demandes = ...; // 原有列表数据 TEbourseWrapper wrapper = new TEbourseWrapper(demandes); List<TEbourse> result = sRestClient.checkFonctionnaire(wrapper);
方式二:自定义Feign消息转换器(无需新增类场景)
如果不想新增包装类,可通过自定义序列化逻辑实现:
- 自定义消息转换器,序列化List
时自动添加外层键:
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.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import java.io.IOException; import java.util.List; public class TEbourseListConverter implements HttpMessageConverter<List<TEbourse>> { private final ObjectMapper objectMapper; public TEbourseListConverter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public boolean canRead(Class<?> clazz, MediaType mediaType) { return false; // 仅处理序列化 } @Override public boolean canWrite(Class<?> clazz, MediaType mediaType) { return List.class.isAssignableFrom(clazz) && TEbourse.class.isAssignableFrom(((List<?>) clazz).getClass().getComponentType()); } @Override public List<MediaType> getSupportedMediaTypes() { return List.of(MediaType.APPLICATION_JSON); } @Override public List<TEbourse> read(Class<? extends List<TEbourse>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { throw new UnsupportedOperationException("不支持反序列化"); } @Override public void write(List<TEbourse> tEbourseList, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { // 将列表封装到含tEbourse键的对象中序列化 objectMapper.writeValue(outputMessage.getBody(), new TEbourseWrapper(tEbourseList)); } }
- 配置Feign使用该转换器:
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; import feign.codec.Encoder; import org.springframework.cloud.openfeign.support.SpringEncoder; import java.util.ArrayList; import java.util.List; @Configuration public class FeignConfig { @Bean public Encoder feignEncoder(ObjectMapper objectMapper) { List<HttpMessageConverter<?>> converters = new ArrayList<>(); converters.add(new TEbourseListConverter(objectMapper)); converters.add(new MappingJackson2HttpMessageConverter(objectMapper)); return new SpringEncoder(() -> converters); } }
- 在Feign接口上指定配置类:
@FeignClient(name = "clientfeign",url = "http://192.168.x.x:8080", configuration = FeignConfig.class) public interface SRestClient { @PostMapping(path = "/api/method-adresse") List<TEbourse> checkFonctionnaire(@RequestBody List<TEbourse> demandes); }
注意:自定义转换器需确保仅对List<TEbourse>生效,避免影响其他接口的序列化逻辑。
内容的提问来源于stack exchange,提问作者fady okana
相关产品推荐
相关产品推荐

