在微服务项目中,一个服务需要调用另一个服务来获取相关数据,feign组件能够很方便进行调用,添加相关依赖和注解,就能快速进行使用。
在实际应用中,调用完之后需要解析返回的数据,这时候就有一个问题, 如果是多层封装的数据
,如何进行获取?第一时间想到的是直接强转,这样做如果只是简单的返回对象是没有问题的,但需要的数据是对象中集合,而直接强转获取到的集合并不能直接进行操作,这时候就不得不换一种方式。可以把feign调用想象成http调用,而http返回的数据格式是json,那么能不能用json去解析feign返回数据?如果可以就能直接使用FastJson转换成我们需要的集合再进行操作,然鹅使用FastJson解析过程中出现了问题,debug发现返回的数据根本就不是json数据,FastJson直接抛出解析异常,问题在哪就解决问题,返回的数据不是json,那么就让它返回json数据不就能继续往下操作吗?那么如何让feign返回我们需要的json数据呢!!!
查阅相关资料发现(找度娘),因为Feign并不共用Spring MVC的消息转换器链,默认使用的是Jackson Json解析库。只要我们把服务端和客户端都替换成FastJson,理论上就能够返回json数据。
所以来进行配置吧
服务端配置,也就是提供服务的一方
@Configuration
public class FastJsonConverterConfig extends WebMvcConfigurationSupport {
/**
* 使用fastjson代替jackson
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
//先把JackSon的消息转换器删除.
//(1)源码分析可知,返回json的过程为:
// Controller调用结束后返回一个数据对象,for循环遍历conventers,找到支持application/json的HttpMessageConverter,然后将返回的数据序列化成json。
// 具体参考org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor的writeWithMessageConverters方法
//(2)由于是list结构,我们添加的fastjson在最后。因此必须要将jackson的转换器删除,不然会先匹配上jackson,导致没使用fastjson
for (int i = converters.size() - 1; i >= 0; i--) {
if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) {
converters.remove(i);
}
}
//自定义fastjson配置
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(
// 是否输出值为null的字段,默认为false,我们将它打开
SerializerFeature.WriteMapNullValue,
// 将Collection类型字段的字段空值输出为[]
SerializerFeature.WriteNullListAsEmpty,
// 将字符串类型字段的空值输出为空字符串
SerializerFeature.WriteNullStringAsEmpty,
// 将数值类型字段的空值输出为0
SerializerFeature.WriteNullNumberAsZero,
SerializerFeature.WriteDateUseDateFormat,
// 禁用循环引用
SerializerFeature.DisableCircularReferenceDetect
);
// 添加支持的MediaTypes;不添加时默认为*/*,也就是默认支持全部
// 但是MappingJackson2HttpMessageConverter里面支持的MediaTypes为application/json
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON);
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
fastJsonHttpMessageConverter.setFastJsonConfig(config);
fastJsonHttpMessageConverter.setSupportedMediaTypes(fastMediaTypes);
converters.add(fastJsonHttpMessageConverter);
//支持XML格式的请求
converters.add(new StringHttpMessageConverter());
}
/**
* 发现如果继承了WebMvcConfigurationSupport,则在yml中配置的相关内容会失效。 需要重新指定静态资源
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(
"classpath:/static/");
registry.addResourceHandler("swagger-ui.html").addResourceLocations(
"classpath:/META-INF/resources/");
registry.addResourceHandler("doc.html").addResourceLocations(
"classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations(
"classpath:/META-INF/resources/webjars/");
super.addResourceHandlers(registry);
}
}
客户端配置,也就是调用的一方
@Configuration
public class FeignSimpleEncoderConfig {
@Bean
Logger.Level feignLoggerLevel() {
//这里记录所有,根据实际情况选择合适的日志level
return Logger.Level.FULL;
}
@Bean
public Encoder feignEncoder() {
return new SpringEncoder(feignHttpMessageConverter());
}
@Bean
public Decoder feignDecoder() {
return new SpringDecoder(feignHttpMessageConverter());
}
/**
* 设置解码器为fastjson
*
* @return
*/
private ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(this.getFastJsonConverter());
return () -> httpMessageConverters;
}
private FastJsonHttpMessageConverter getFastJsonConverter() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
List<MediaType> supportedMediaTypes = new ArrayList<>();
MediaType mediaTypeJson = MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE);
supportedMediaTypes.add(mediaTypeJson);
converter.setSupportedMediaTypes(supportedMediaTypes);
FastJsonConfig config = new FastJsonConfig();
config.getSerializeConfig().put(JSON.class, new SwaggerJsonSerializer());
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
converter.setFastJsonConfig(config);
return converter;
}
}
feign调用类指定配置
//远程调用
@FeignClient(value = "nakadai",fallback = NakadaiClientImpl.class,configuration = FeignSimpleEncoderConfig.class)
@Component
@Headers("Accept: application/json")
public interface NakadaiClient {
/**
* 查询数据
*/
@Headers({"Content-Type: application/json","Accept: application/json"})
@GetMapping(value = "/nakadai/nakadai/order/getCustomerOrder",consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
R getCustomerOrder(@RequestParam("id") Integer id);
}
再重新启动,debug可以发现返回的数据符合json格式,接下来就可以对json数据进行操作了
例:
R result= nakadaiClient.getCustomerOrder(id);
Object orderList = customerOrder.get("orderList");
String jsonString = JSON.toJSONString(orderList);
List<Order> orders = JSON.parseArray(jsonString, Order.class);
这样转换虽然较为麻烦,但操作集合时就没有直接强转所导致的问题
在实际运用中,又发现了一个问题,在服务端配置完之后,发现返回格式全都乱码了,导致无法正确获取接口返回数据,然鹅将这个配置类删除,feign返回数据还是json格式,这说明服务端并不需要去进行配置,配置反而会有问题,发现乱码之后直接将配置类删除即可