Could not extract response: no suitable HttpMessageConverter
版本:spring-cloud-openfeign-core-2.1.1.RELEASE.jar,spring-webmvc-5.1.14.RELEASE.jar,jetty-server-9.4.41.v20210516.jar,tomcat-embed-core-9.0.48.jar
问题背景
生产服务请求下游服务时偶发抛出下面的异常,下游服务已经很久没有人发布并且没有修改任何配置,而且是偶发,这个问题很奇怪,服务使用的Spring cloud openfeign,由于不熟悉Spring cloud与openfeign,先梳理学习Spring cloud openfeign bean的初始化与定义
feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.....master.models.APIResponse] and content type [application/xhtml+xml;charset=UTF-8]at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:180)at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:140)at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:78)at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)at com.sun.proxy.$Proxy401.sendEmail(Unknown Source)at ......Caused by: org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.......APIResponse] and content type [application/xhtml+xml;charset=UTF-8]at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:121)at org.springframework.cloud.openfeign.support.SpringDecoder.decode(SpringDecoder.java:59)at org.springframework.cloud.openfeign.support.ResponseEntityDecoder.decode(ResponseEntityDecoder.java:62)at feign.optionals.OptionalDecoder.decode(OptionalDecoder.java:36)at feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:176)... 106 more
Spring Cloud openfeign
初始化过程
应用启动SpringBootApplication,引入自动配置sdk,自动配置:org.springframework.cloud.openfeign.FeignAutoConfiguration
FeignAutoConfiguration
- 构建feignContext:org.springframework.cloud.openfeign.FeignContext,FeignContext是Spring应用上下文与feignClient配置org.springframework.cloud.openfeign.FeignClientSpecification的组合
- 按需(@ConditionalOnClass(name = “feign.hystrix.HystrixFeign”))构建feignTargeter:HystrixTargeter,默认(@ConditionalOnMissingClass(“feign.hystrix.HystrixFeign”)):DefaultTargeter
- 按需构建HttpClientFeignConfiguration,条件见:下方代码
- 按需构建OkHttpFeignConfiguration,条件见:下方代码
@ConditionalOnClass(ApacheHttpClient.class)@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")@ConditionalOnMissingBean(CloseableHttpClient.class)@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)protected static class HttpClientFeignConfiguration {...}@Configuration@ConditionalOnClass(OkHttpClient.class)@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)@ConditionalOnProperty("feign.okhttp.enabled")protected static class OkHttpFeignConfiguration {...}
查看注解:@EnableFeignClients(basePackages = {“…”}),客户端注入过程切入点,该注解会通过Spring Import注解导入bean定义,注解指定导入bean定义类:org.springframework.cloud.openfeign.FeignClientsRegistrar
注册BeanDefinitions流程
- registerDefaultConfiguration
- registerFeignClients
registerDefaultConfiguration
- 获取EnableFeignClients注解默认属性配置
- 如果默认配置包含(defaultConfiguration),则将默认配置注册为bean(FeignClientSpecification)。beanName:default…notification.Application
registerFeignClients
- 使用ClassPathScanningCandidateComponentProvider扫描器按照EnableFeignClients配置的basePackages扫描当前资源resourceLoader,扫描过滤器AnnotationTypeFilter筛选出FeignClient注解的AnnotatedBeanDefinition
- 根据FeignClient注解的属性(configuration)定义构建FeignClientSpecification的BeanDefinition,注册客户端配置方法代码如下
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());}
registerFeignClient
- 构建客户端bean:FeignClientFactoryBean
- 将bean封装为:BeanDefinitionHolder注册至工厂
- bean名称为className
- bean别名读取注解属性(qualifier),如果qualifier不存在则兜底使用contextId+FeignClient
代码如下
private void registerFeignClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {String className = annotationMetadata.getClassName();BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);validate(attributes);definition.addPropertyValue("url", getUrl(attributes));definition.addPropertyValue("path", getPath(attributes));String name = getName(attributes);definition.addPropertyValue("name", name);String contextId = getContextId(attributes);definition.addPropertyValue("contextId", contextId);definition.addPropertyValue("type", className);definition.addPropertyValue("decode404", attributes.get("decode404"));definition.addPropertyValue("fallback", attributes.get("fallback"));definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);String alias = contextId + "FeignClient";AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be// nullbeanDefinition.setPrimary(primary);String qualifier = getQualifier(attributes);if (StringUtils.hasText(qualifier)) {alias = qualifier;}BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,new String[] { alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}
FeignClientFactoryBean
- 应用上下文中获取FeignContext
- 创建Feign.Builder:org.springframework.cloud.openfeign.FeignClientFactoryBean#feign
- 从FeignContext上下文获取构造者feign.Feign.Builder
- 为构建者设置encoder,从FeignContext上下文获取feign.codec.Encoder:org.springframework.cloud.openfeign.support.PageableSpringEncoder->SpringEncoder->SpringFormEncoder->feign.codec.Encoder.Default
- 为构建者设置decoder,从FeignContext上下文获取feign.codec.Decoder:feign.optionals.OptionalDecoder->org.springframework.cloud.openfeign.support.ResponseEntityDecoder->SpringDecoder
- 为构建者设置contract,从FeignContext上下文获取feign.Contract:使用feign.hystrix.HystrixDelegatingContract#HystrixDelegatingContract代理包装自定义实现OpenFeignSpringMvcContract(继承自feign.OpenFeignBaseContract)
- 如果不存在url配置:org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
- 否则
- 获取Client:org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional。如果client不为空并且是LoadBalancerFeignClient类型则获取其代理的实际Client
- 获取Targeter:org.springframework.cloud.openfeign.FeignClientFactoryBean#get
- 构建目标bean:org.springframework.cloud.openfeign.Targeter#target(feign.Target.HardCodedTarget)
当前案例默认无url,即步骤3:org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
- 获取Client:org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional。常用实现:ApacheHttpClient,OkHttpClient,feign.Client.Default。当前案例使用自定义封装ReWriteHeaderFeignClient客户端
- 获取Targeter:org.springframework.cloud.openfeign.FeignClientFactoryBean#get
- 构建目标bean:org.springframework.cloud.openfeign.Targeter#target(feign.Target.HardCodedTarget)
构建目标bean
构建Feign:feign.hystrix.HystrixFeign.Builder#build(feign.hystrix.FallbackFactory<?>)
- 实现InvocationHandler:feign.hystrix.HystrixInvocationHandler
- 包装contract:feign.hystrix.HystrixDelegatingContract#HystrixDelegatingContract
- 调用父类build方法构建Feign:feign.Feign.Builder#build
public Feign build() {SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName =new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,errorDecoder, synchronousMethodHandlerFactory);return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
SynchronousMethodHandler.Factory工厂相关属性
- client:常用实现:ApacheHttpClient,OkHttpClient,feign.Client.Default。当前案例使用自定义封装ReWriteHeaderFeignClient客户端
- retryer:feign.Retryer.Default#Default()
- requestInterceptors
- 自定义:OpenFeignRequestInterceptor
- 自定义:GrayPatternMatcherRequestInterceptor
- 自定义:GzipFeignAcceptGzipEncodingInterceptor继承FeignAcceptGzipEncodingInterceptor,如果配置feign.compression.response.enabled=true,则启用该拦截器,追加header Accept-Encoding -> gzip
- 自定义:FeignEnvFlagInterceptor
- decode404:false
- closeAfterDecode:true
- propagationPolicy:feign.Feign.Builder#propagationPolicy(NONE)
ParseHandlersByName相关属性
- options:feign.Request.Options#Options(),connectTimeoutMillis默认10s,readTimeoutMillis默认60s
- encoder,decoder,contract:同Feign.Builder
- queryMapEncoder:feign.QueryMapEncoder.Default
- errorDecoder:自定义实现new ErrorDecoder()
ReflectiveFeign相关属性
- invocationHandlerFactory:feign.InvocationHandlerFactory.Default
- queryMapEncoder:同上
- handlersByName:ParseHandlersByName
创建目标实例
feign.ReflectiveFeign#newInstance
- 获取nameToHandler映射:feign.ReflectiveFeign.ParseHandlersByName#apply
- 解析与校验元数据:feign.Contract#parseAndValidatateMetadata,当前案例:自定义实现重写父类方法feign.OpenFeignBaseContract#parseAndValidateMetadata
- processAnnotationOnClass
- processAnnotationOnMethod
- processAnnotationsOnParameter
- checkState
- checkMapString:HeaderMap
- checkMapKeys:QueryMap
- 回调handler:feign.FeignClientMethodMetadataParseHandler#parsed
- 根据default与nameToHandler获取methodToHandler映射
- 创建InvocationHandler:feign.InvocationHandlerFactory.Default#create
- 创建目标实例代理:java.lang.reflect.Proxy#newProxyInstance(java.lang.ClassLoader, java.lang.Class<?>[], java.lang.reflect.InvocationHandler)
- 如果是default方法:feign.Util#isDefault,绑定feign.DefaultMethodHandler至动态代理
- 返回代理对象
isDefault默认方法定义
// Default methods are public non-abstract, non-synthetic, and non-static instance methods// declared in an interface.// method.isDefault() is not sufficient for our usage as it does not check// for synthetic methods. As a result, it picks up overridden methods as well as actual default// methods.
动态代理InvocationHandler
static final class Default implements InvocationHandlerFactory {@Overridepublic InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {return new ReflectiveFeign.FeignInvocationHandler(target, dispatch);}}
请求过程
关键对象属性
FeignInvocationHandler
- target:HardCodedTarget
- type:目标对象class,例如:MyTestAPI
- name:my-feign-client
- url:http://my-feign-client
- dispatch:key代理对象方法,例如:MyTestAPI.helloWorld(),value:feign.SynchronousMethodHandler
SynchronousMethodHandler
- 与SynchronousMethodHandler.Factory相同部分不再重复,搜索上方关键字:SynchronousMethodHandler.Factory
- target:HardCodedTarget
- decoder,options,errorDecoder:同ParseHandlersByName.decoder
- metadata:feign.MethodMetadata
- buildTemplateFromArgs:feign.ReflectiveFeign.BuildEncodedTemplateFromArgs#BuildEncodedTemplateFromArgs/BuildTemplateByResolvingArgs/BuildFormEncodedTemplateFromArgs
请求过程
- 动态代理调用:feign.ReflectiveFeign.FeignInvocationHandler#invoke
- 调用代理方法:feign.SynchronousMethodHandler#invoke
- 创建RequestTemplate:feign.ReflectiveFeign.BuildTemplateByResolvingArgs#create
- 解析参数构建EncodedTemplate对象:feign.ReflectiveFeign.BuildEncodedTemplateFromArgs#resolve
- encode request body:org.springframework.cloud.openfeign.support.PageableSpringEncoder#encode->org.springframework.cloud.openfeign.support.SpringEncoder#encode
- org.springframework.http.converter.json.MappingJackson2HttpMessageConverter->org.springframework.http.converter.AbstractHttpMessageConverter#write将request body写入org.springframework.cloud.openfeign.support.SpringEncoder.FeignOutputMessage
- 如果请求没有Content-Type header默认值设置为:application/json;charset=UTF-8
- 写入encoded body,并追加Content-Length header
- 克隆Retryer:feign.Retryer.Default#clone
- 发起请求并解析响应结果:feign.SynchronousMethodHandler#executeAndDecode
- 构建请求:feign.SynchronousMethodHandler#targetRequest
- 回调拦截器处理请求:feign.RequestInterceptor
- 创建request请求:feign.Target.HardCodedTarget#apply-》feign.RequestTemplate#request
- 客户端执行请求:feign.SynchronousMethodHandler#client#execute(request, options):org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
- 负载均衡,重构Server请求URI:com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)-》com.netflix.loadbalancer.LoadBalancerContext#reconstructURIWithServer
- 完成请求返回响应结果feign.Response
- decode response
- org.springframework.web.client.HttpMessageConverterExtractor.extractData
- 如果转换器类型为GenericHttpMessageConverter,根据responseType+contentType获取转换器GenericHttpMessageConverter
- 否则根据responseClass+contentType获取转换器
- HttpMessageConverter将response转换为目标方法返回值类型
问题分析
客户端
ReWriteHeaderFeignClient自定义封装调用链路
- feign.SynchronousMethodHandler#invoke->executeAndDecode->自定义实现ReWriteHeaderFeignClient#execute ->org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute->org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#lbClient->自定义实现EncodeHeaderCachingSpringLoadBalancerFactory#create->FeignLoadBalancer(如果存在重试工厂loadBalancedRetryFactory则使用RetryableFeignLoadBalancer)->自定义实现EncodeHeaderCachingSpringLoadBalancerFactory.EncodeFeignLoadBalancer#executeWithLoadBalancer->LoadBalancerCommand.submit->异步调用org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute-》org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonRequest.client()->自定义实现ResetTimeoutFeignClient#execute-》自定义实现JettyHttpClient#execute-〉org.eclipse.jetty.client.HttpRequest#send(org.eclipse.jetty.client.api.Response.CompleteListener)-》请求完成回调org.eclipse.jetty.client.util.FutureResponseListener#onComplete-》将org.eclipse.jetty.client.HttpResponse封装为org.eclipse.jetty.client.HttpContentResponse#HttpContentResponse->自定义实现JettyHttpClient#toFeignResponse(代码见下方)-》org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute将Response封装为RibbonResponse->feign.SynchronousMethodHandler#decode
return Response.builder().status(status).reason(reason).headers(headers).request(feignRequest).body(body).build();
查看所有自定义实现的类没有重写Content-Type header的类,故而排除
服务端
处理流程
- 接收派发请求:org.springframework.web.servlet.DispatcherServlet#doDispatch
- 根据请求查找处理句柄:org.springframework.web.servlet.DispatcherServlet#getHandler
- 查找Handler适配器(即目标方法适配器):org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
- 预处理句柄回调:org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle-》org.springframework.web.servlet.HandlerInterceptor#preHandle
- 实际执行处理句柄:org.springframework.web.servlet.HandlerAdapter#handle
- 回处理完成后置调拦截器:org.springframework.web.servlet.HandlerInterceptor#postHandle
- 处理派发请求处理结果:org.springframework.web.servlet.DispatcherServlet#processDispatchResult
- 如果存在异常则处理:org.springframework.web.servlet.DispatcherServlet#processHandlerException
- 如果存在ModelAndView,则渲染mv:org.springframework.web.servlet.DispatcherServlet#render
- 如果是并发异步处理则返回
- 否则回调拦截器:org.springframework.web.servlet.HandlerInterceptor#afterCompletion
- 如果是并发异步处理则回调拦截器:org.springframework.web.servlet.AsyncHandlerInterceptor#afterConcurrentHandlingStarted
- 如果是multipart请求清理multipart:org.springframework.web.servlet.DispatcherServlet#cleanupMultipart
- 发布事件:org.springframework.web.servlet.FrameworkServlet#publishRequestHandledEvent
- javax.servlet.FilterChain
实际执行处理句柄
org.springframework.web.servlet.DispatcherServlet#doDispatch->org.springframework.web.servlet.HandlerAdapter#handle
- org.springframework.web.servlet.HandlerAdapter#handle,当前案例实现类:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter-》org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDataBinderFactory
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelFactory
- org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#createInvocableHandlerMethod
- 设置argumentResolvers(见图1)
- 设置returnValueHandlers(见图2)
- 设置parameterNameDiscoverer:org.springframework.core.DefaultParameterNameDiscoverer
- 设置ignoreDefaultModelOnRedirect,默认true
- 设置asyncRequestTimeout,默认null
- 设置TaskExecutor:new SimpleAsyncTaskExecutor(“MvcAsync”)
- 注册callableInterceptors,默认空数组:CallableProcessingInterceptor
- 注册deferredResultInterceptors,默认空数组:DeferredResultProcessingInterceptor
- 执行目标方法:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
- 反射调用目标方法,得到返回值
- 如果返回值为空或者responseStatusReason不为空设置setRequestHandled后返回
- 选择支持返回值类型的处理句柄:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler,当前案例:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
- 回调返回值拦截器:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue,当前案例:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
- 创建输入输出消息对象:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#createInputMessage,org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#createOutputMessage
- 写入数据:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
- 如果是isResourceType,写入header:Accept-Ranges,存在异常则写入header:Content-Range,当前案例:否
- 如果outputMessage存在Content-Type则使用,当前案例:不存在
- 否则获取request请求的Accept类型org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes
- 获取请求producible类型(对应注解produces属性:@PostMapping(value = “/hello”, produces = “application/json;charset=utf8”))rg.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getProducibleMediaTypes(javax.servlet.http.HttpServletRequest, java.lang.Class<?>, java.lang.reflect.Type):当前案例请求中org.springframework.web.servlet.HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE参数不存在,走兜底:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#allSupportedMediaTypes
- 根据请求方acceptableTypes获取可兼容的producibleTypes作为可使用的Content-Type MediaType列表:mediaTypesToUse,当前案例:Content-Type列表见:注释1
- 排序可兼容的mediaTypes:org.springframework.http.MediaType#sortBySpecificityAndQuality,排序结果见:注释2
- 选择第一个具体的MediaType:org.springframework.util.MimeType#isConcrete
- 获取匹配的转换器:org.springframework.http.converter.HttpMessageConverter,当前案例:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
- 写入body前置通知回调:org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite
- 如果存在则写入Response header:Content-Disposition
- 写入body数据:MappingJackson2HttpMessageConverter-》org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
- 添加默认header:org.springframework.http.converter.AbstractHttpMessageConverter#addDefaultHeaders,Content-Type,Content-Length(当前案例均为null不写入:org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#getContentLength)
- 向outputMessage写入body数据:org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
- 构建ModelAndView:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView
- 请求完成后置处理:org.springframework.web.context.request.AbstractRequestAttributes#requestCompleted
- 如果Response不包含header(Cache-Control),并且org.springframework.web.method.annotation.SessionAttributesHandler#hasSessionAttributes,处理cache:org.springframework.web.servlet.support.WebContentGenerator#applyCacheSeconds(javax.servlet.http.HttpServletResponse, int)
- 否则准备Response:org.springframework.web.servlet.support.WebContentGenerator#prepareResponse
- cacheControl不为空,则设置header(Cache-Control),或header(Pragma),或header(Expires)
- 否则设置header(Cache-Control),或header(Pragma),或header(Expires)为指定值,例如:no-cache,no-store,1L
- 如果varyByRequestHeaders不为空,设置header(Vary)
问题原因
响应体如果没有指定Content-Type,那么就会从兼容请求体Accept的Content-Type中选择一个进行响应,也就是说请求方使用了非json格式的Accept引起,为了验证问题,我们查看线上pinpoint(开源的链路监控平台)监控,出现问题的请求的Accept header如下,与报错原因也就对应上了
accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
解决方法
- 请求方使用适配的Accept类型,例如:application/json;charset=utf8或*/*
- 接口PostMapping/RequestMapping等注解增加produces配置:org.springframework.web.bind.annotation.PostMapping#produces
- 实现接口org.springframework.http.converter.HttpMessageConverter#write写入OutMessage Content-Type,例如:open-feign在编译时写入header:org.springframework.cloud.openfeign.support.SpringEncoder#encode-》write,服务端是在将return type写入outputMessage时处理(Writes the given return type to the given output message):org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
- 为OutputMessage设置适配的Content-Type header org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#createOutputMessage
- 增加自定义ServletHandler,例如:xxl-job中的方法,代码见附件3
- 实现预处理handle拦截器处理:org.springframework.web.servlet.HandlerInterceptor#preHandle
注意:方法2-4要根据实际业务场景是否可以使用,客户端期望xhtml,但是实际响应json,是否有潜在的风险?
Q&A
- 接口PostMapping/RequestMapping等注解增加headers配置是否可以?org.springframework.web.bind.annotation.PostMapping#headers
- 不可以,因为该header是添加再Request请求中,需要添加在Response中的header才可以
注释
注释1
0 = {MediaType@22077} "application/octet-stream"
1 = {MediaType@22078} "text/plain"
2 = {MediaType@22079} "application/xml"
3 = {MediaType@22080} "text/xml"
4 = {MediaType@22081} "application/x-www-form-urlencoded"
5 = {MediaType@22082} "application/cbor"
6 = {MediaType@22083} "application/*+xml"
7 = {MediaType@22084} "multipart/form-data"
8 = {MediaType@22085} "application/json"
9 = {MediaType@22086} "application/*+json"
10 = {MediaType@22057} "*/*"
注释2
0 = {MediaType@22085} "application/json"
1 = {MediaType@22085} "application/json"
2 = {MediaType@22082} "application/cbor"
3 = {MediaType@22293} "application/xml"
4 = {MediaType@22296} "application/xml"
5 = {MediaType@22086} "application/*+json"
6 = {MediaType@22292} "application/*+json"
7 = {MediaType@22294} "text/xml"
8 = {MediaType@22295} "application/*+xml"
9 = {MediaType@22297} "text/xml"
10 = {MediaType@22298} "application/*+xml"
附件
图1
图2
附件3
相关文章:

Could not extract response: no suitable HttpMessageConverter
版本:spring-cloud-openfeign-core-2.1.1.RELEASE.jar,spring-webmvc-5.1.14.RELEASE.jar,jetty-server-9.4.41.v20210516.jar,tomcat-embed-core-9.0.48.jar 问题背景 生产服务请求下游服务时偶发抛出下面的异常,下…...

文献计量三大定律之一---洛特卡定律及普赖斯定律
科学生产率是洛特卡定律的基础,科学生产率”(Scientific Productivity))是指科学家(科研人员)在科学上所表现出的能力和工作效率,通常用其生产的科学文献的数量来衡量。 1926年,洛特卡在一篇论文中提出了科…...

2023年软考高级网络规划设计师
网络规划设计师是软考高级考试科目之一,也是比较难的科目,据官方数据统计网规每年的通过率很低,而且每年只有下半年11月份考一次,如果是直接裸考,估计很悬哦~ 但是你参加考试获得证书的过程就是一个学习网络规划系统知…...

数据治理驱动因素 -报考题
数据治理并不是到此为止,而是需要直接与企业战略保持一致。数据治理越显著地帮助解决组织问题,人们越有可能改变行为、接受数据治理实践。数据治理的驱动因素大多聚焦于减少风险或者改进流程。(1)减少风险1)一般性风险…...

2023淘宝天猫38节红包满减优惠活动时间是从几月几号什么时候开始?
2023年淘宝天猫38节活动将于2023年3月2日中午12点正式开始,活动将持续至2023年3月8日晚上23点59分。届时,淘宝天猫将推出一系列的优惠活动和红包福利,为广大女性用户送上节日的祝福和福利。在这个特别的节日里,淘宝天猫为女性用户…...

Hive表优化、表设计优化、Hive表数据优化(ORC)、数据压缩、存储优化
文章目录Hive表优化Hive表设计优化分区表结构 - 分区设计思想分桶表结构 - Join问题Hive中的索引Hive表数据优化常见文件格式TextFileSequenceFileParquetORC数据压缩存储优化 - 避免小文件生成存储优化 - 合并输入的小文件存储优化 - ORC文件索引Row Group IndexBloom Filter …...

LearnOpenGL-入门-着色器
本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正 我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject LearnOpenGL中文官网:https://learnopengl-cn.github.io/ 文章目录着色器GLSL数据类型输入与输…...

【谷粒学院】vue、axios、element-ui、node.js(44~58)
44.前端技术-vue入门 🧨Vue.js 是什么 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。 Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具…...

【一些回忆】2022.02.26-2023.02.26 一个普通男孩的365天
💃🏼 本人简介:男 👶🏼 年龄:18 🤞 作者:那就叫我亮亮叭 📕 专栏:一些回忆 为什么选择在这个时间节点回忆一下呢? 一是因为今天距离2023高考仅剩1…...

OSPF的多区域特性 (电子科技大学TCP/IP实验三)
一.实验目的 1、掌握OSPF 协议中区域的类型、特征和作用 2、掌握OSPF 路由器的类型、特征和作用 3、掌握OSPF LSA 分组的类型、特征和作用 4、理解OSPF 区域类型、路由器类型和OSPF LSA 分组类型间的相互关系 二.预备知识 1、静态路由选择和动态路…...

(四十四)多个事务更新同一行数据时,是如何加锁避免脏写的?
之前我们已经用很多篇幅给大家讲解了多个事务并发运行的时候,如果同时要读写一批数据,此时读和写时间的关系是如何协调的,毕竟要是你不协调好的话,可能就会有脏读、不可重复读、幻读等一系列的问题。 简单来说,脏读、…...

【数据库】第十二章 数据库管理
第12章 数据库管理 数据库的物理存储 关于内存、外存、磁盘、硬盘、软盘、光盘的区别_Allenzyg的博客-CSDN博客_磁盘和硬盘的区别 数据库记录在磁盘上的存储 定长,变长跨块,非跨快 文件的组织方方法: 无序记录文件(堆文件heap或pile file…...

Redis源码---整体架构
目录 前言 Redis目录结构 前言 deps目录 src 目录 tests 目录 utils 目录 重要的配置文件 Redis 功能模块与源码对应 前言 服务器实例 数据库数据类型与操作 高可靠性和高可扩展性 辅助功能 前言 以先面后点的方法推进无特殊说明,都是基于 Redis 5.0.…...

基于springboot+vue的校园招聘系统
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…...

SAP MM学习笔记1-SAP中扩张的概念,如何将一个物料从工厂A扩张到工厂B
MM中在创建物料的时候,最低也得创建如下5个view。 基本数据1 基本数据2 购买管理 会计1 会计2 1,扩张是什么 有时候,你想增加其他的View,比如保管场所 等,你不能用MM02来做编辑,要用MM01来做扩张。这就是扩…...

【Python】Numpy数组的切片、索引详解:取数组的特定行列
【Python】Numpy数组的切片、索引详解:取数组的特定行列 文章目录【Python】Numpy数组的切片、索引详解:取数组的特定行列1. 介绍2. 切片索引2.1 切片索引先验知识2.1 一维数组的切片索引2.3 多维数组的切片索引3. 数组索引(副本)…...

2023年全国最新交安安全员精选真题及答案6
百分百题库提供交安安全员考试试题、交安安全员考试预测题、交安安全员考试真题、交安安全员证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 51.安全生产资金保障制度建立后关键在于落实,各施工企业在落实安全生…...

JavaScript 闭包【自留】
闭包的概念理解 闭包的定义 ✅ 这里先来看一下闭包的定义,分成两个:在计算机科学中和在JavaScript中。 ✅ 在计算机科学中对闭包的定义(维基百科): 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures);是在支持头等函数…...

【MySQL】什么是意向锁 IS IX 及值得学习的思想
文章目录前言行锁和表锁使用意向锁意向锁的算法意向锁的思想JDK 中相似的思想前言 之前看 MySQL 都刻意忽略掉了 IS 和 IX 锁,今天看 《MySQL 是怎样运行的》,把意向锁讲的很通透,本篇博文提炼一下思想。 I: Intention Lock(意向…...

python多线程实现
用于线程实现的Python模块 Python线程有时称为轻量级进程,因为线程比进程占用的内存少得多。 线程允许一次执行多个任务。 在Python中,以下两个模块在一个程序中实现线程 - _thread模块threading模块 这两个模块之间的主要区别在于_thread模块将线程视…...

macOS使用CodeRunner快速配置fortran环境
个人网站:xzajyjs.cn 由于一些项目的缘故,需要有fortran的需求,但由于是M1 mac的缘故,不能像windows那样直接使用vsivf这种经典配置。搜了一下网上主流的跨平台方案,主要是gfortran,最近用Coderunner(主要…...

【云原生】k8s 离线部署讲解和实战操作
文章目录一、概述二、前期准备1)节点信息2)修改主机名和配置hosts3)配置ssh互信4)时间同步5)关闭防火墙6)关闭 swap7)禁用SELinux8)允许 iptables 检查桥接流量三、开始部署1&#x…...

【Kubernetes】第十一篇 - 滚动发布的介绍与实现
一,前言 上一篇,介绍了灰度发布和流量切分的集中方式,以及如何实现 k8s 的灰度发布; 本篇,介绍滚动发布的实现; 二,滚动发布简介 滚动发布 滚动发布,则是我们一般所说的无宕机发…...

【尊享版】如何系统构建你的思维认知模型?
超友们,早上好,国庆节快乐~ 今天为你带来的分享是《如何系统构建你的思维认知模型?》,主要分为三个部分: 第一部分:【实现爆发式成长的 10 个思维模型】 第二部分:【6 个不可不知的…...

urho3D编码约定
缩进样式类似于Allman(BSD),即在控制语句的下一行使用大括号,在同一级别缩进。在switch-case语句中,case与switch语句处于相同的缩进级别。 缩进使用4个空格而不是制表符。不应保留空行上的缩进。 类和结构名称以大写…...

Overleaf推广奖励:增加合作者的数量、解锁Dropbox同步和项目修改历史
Overleaf推广奖励 Overleaf是一个LaTeX\LaTeXLATEX在线编译器,它可以让你与合作者共同在线编辑文档。但是默认的免费账号仅能邀请一个合作者。那么如何增加合作者的数量呢? Overleaf推出了一个奖励计划,你邀请其他人注册Overleaf…...

ChatGPT的互补工具Perplexity的详细使用方法(持续更新)
大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,科大讯飞比赛第三名,CCF比赛第四名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…...

【Linux驱动开发100问】如何编译Linux内核?
🥇今日学习目标:如何编译Linux内核? 🤵♂️ 创作者:JamesBin ⏰预计时间:10分钟 🎉个人主页:嵌入式悦翔园个人主页 🍁专栏介绍:Linux驱动开发100问 如何编译…...

15、条件概率、全概率公式、贝叶斯公式、马尔科夫链
条件概率定义:设A、B是两个事件,且,P(A) > 0 则称 为事件A发生的条件下事件B的条件概率对这个式子进行变形,即可得到概率的乘法公式:P(A) > 0 时,则P(B) > 0 时,则乍一看,…...

Eureka服务注册与发现
注册中心是分布式开发的核心组件之一,而Eureka是spring cloud推荐的注册中心实现。简单分析一下Eureka的原理。Eureka基础概念与流程1、服务注册在微服务架构中,一个服务提供者本质上也是一个Eureka客户端。启动时,会调用Eureka所提供的服务注…...