当前位置: 首页 > news >正文

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

  1. 构建feignContext:org.springframework.cloud.openfeign.FeignContext,FeignContext是Spring应用上下文与feignClient配置org.springframework.cloud.openfeign.FeignClientSpecification的组合
  2. 按需(@ConditionalOnClass(name = “feign.hystrix.HystrixFeign”))构建feignTargeter:HystrixTargeter,默认(@ConditionalOnMissingClass(“feign.hystrix.HystrixFeign”)):DefaultTargeter
  3. 按需构建HttpClientFeignConfiguration,条件见:下方代码
  4. 按需构建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流程

  1. registerDefaultConfiguration
  2. registerFeignClients

registerDefaultConfiguration

  1. 获取EnableFeignClients注解默认属性配置
  2. 如果默认配置包含(defaultConfiguration),则将默认配置注册为bean(FeignClientSpecification)。beanName:default…notification.Application

registerFeignClients

  1. 使用ClassPathScanningCandidateComponentProvider扫描器按照EnableFeignClients配置的basePackages扫描当前资源resourceLoader,扫描过滤器AnnotationTypeFilter筛选出FeignClient注解的AnnotatedBeanDefinition
  2. 根据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

  1. 构建客户端bean:FeignClientFactoryBean
  2. 将bean封装为:BeanDefinitionHolder注册至工厂
    1. bean名称为className
    2. 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

  1. 应用上下文中获取FeignContext
  2. 创建Feign.Builder:org.springframework.cloud.openfeign.FeignClientFactoryBean#feign
    1. 从FeignContext上下文获取构造者feign.Feign.Builder
    2. 为构建者设置encoder,从FeignContext上下文获取feign.codec.Encoder:org.springframework.cloud.openfeign.support.PageableSpringEncoder->SpringEncoder->SpringFormEncoder->feign.codec.Encoder.Default
    3. 为构建者设置decoder,从FeignContext上下文获取feign.codec.Decoder:feign.optionals.OptionalDecoder->org.springframework.cloud.openfeign.support.ResponseEntityDecoder->SpringDecoder
    4. 为构建者设置contract,从FeignContext上下文获取feign.Contract:使用feign.hystrix.HystrixDelegatingContract#HystrixDelegatingContract代理包装自定义实现OpenFeignSpringMvcContract(继承自feign.OpenFeignBaseContract)
  3. 如果不存在url配置:org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
  4. 否则
    1. 获取Client:org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional。如果client不为空并且是LoadBalancerFeignClient类型则获取其代理的实际Client
    2. 获取Targeter:org.springframework.cloud.openfeign.FeignClientFactoryBean#get
    3. 构建目标bean:org.springframework.cloud.openfeign.Targeter#target(feign.Target.HardCodedTarget)

当前案例默认无url,即步骤3:org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance

  1. 获取Client:org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional。常用实现:ApacheHttpClient,OkHttpClient,feign.Client.Default。当前案例使用自定义封装ReWriteHeaderFeignClient客户端
  2. 获取Targeter:org.springframework.cloud.openfeign.FeignClientFactoryBean#get
  3. 构建目标bean:org.springframework.cloud.openfeign.Targeter#target(feign.Target.HardCodedTarget)

构建目标bean

构建Feign:feign.hystrix.HystrixFeign.Builder#build(feign.hystrix.FallbackFactory<?>)

  1. 实现InvocationHandler:feign.hystrix.HystrixInvocationHandler
  2. 包装contract:feign.hystrix.HystrixDelegatingContract#HystrixDelegatingContract
  3. 调用父类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工厂相关属性

  1. client:常用实现:ApacheHttpClient,OkHttpClient,feign.Client.Default。当前案例使用自定义封装ReWriteHeaderFeignClient客户端
  2. retryer:feign.Retryer.Default#Default()
  3. requestInterceptors
    1. 自定义:OpenFeignRequestInterceptor
    2. 自定义:GrayPatternMatcherRequestInterceptor
    3. 自定义:GzipFeignAcceptGzipEncodingInterceptor继承FeignAcceptGzipEncodingInterceptor,如果配置feign.compression.response.enabled=true,则启用该拦截器,追加header Accept-Encoding -> gzip
    4. 自定义:FeignEnvFlagInterceptor
  4. decode404:false
  5. closeAfterDecode:true
  6. propagationPolicy:feign.Feign.Builder#propagationPolicy(NONE)

ParseHandlersByName相关属性

  1. options:feign.Request.Options#Options(),connectTimeoutMillis默认10s,readTimeoutMillis默认60s
  2. encoder,decoder,contract:同Feign.Builder
  3. queryMapEncoder:feign.QueryMapEncoder.Default
  4. errorDecoder:自定义实现new ErrorDecoder()

ReflectiveFeign相关属性

  1. invocationHandlerFactory:feign.InvocationHandlerFactory.Default
  2. queryMapEncoder:同上
  3. handlersByName:ParseHandlersByName

创建目标实例
feign.ReflectiveFeign#newInstance

  1. 获取nameToHandler映射:feign.ReflectiveFeign.ParseHandlersByName#apply
    1. 解析与校验元数据:feign.Contract#parseAndValidatateMetadata,当前案例:自定义实现重写父类方法feign.OpenFeignBaseContract#parseAndValidateMetadata
    2. processAnnotationOnClass
    3. processAnnotationOnMethod
    4. processAnnotationsOnParameter
    5. checkState
    6. checkMapString:HeaderMap
    7. checkMapKeys:QueryMap
    8. 回调handler:feign.FeignClientMethodMetadataParseHandler#parsed
  2. 根据default与nameToHandler获取methodToHandler映射
  3. 创建InvocationHandler:feign.InvocationHandlerFactory.Default#create
  4. 创建目标实例代理:java.lang.reflect.Proxy#newProxyInstance(java.lang.ClassLoader, java.lang.Class<?>[], java.lang.reflect.InvocationHandler)
  5. 如果是default方法:feign.Util#isDefault,绑定feign.DefaultMethodHandler至动态代理
  6. 返回代理对象

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

  1. target:HardCodedTarget
    1. type:目标对象class,例如:MyTestAPI
    2. name:my-feign-client
    3. url:http://my-feign-client
  2. dispatch:key代理对象方法,例如:MyTestAPI.helloWorld(),value:feign.SynchronousMethodHandler

SynchronousMethodHandler

  1. 与SynchronousMethodHandler.Factory相同部分不再重复,搜索上方关键字:SynchronousMethodHandler.Factory
  2. target:HardCodedTarget
  3. decoder,options,errorDecoder:同ParseHandlersByName.decoder
  4. metadata:feign.MethodMetadata
  5. buildTemplateFromArgs:feign.ReflectiveFeign.BuildEncodedTemplateFromArgs#BuildEncodedTemplateFromArgs/BuildTemplateByResolvingArgs/BuildFormEncodedTemplateFromArgs

请求过程

  1. 动态代理调用:feign.ReflectiveFeign.FeignInvocationHandler#invoke
  2. 调用代理方法:feign.SynchronousMethodHandler#invoke
  3. 创建RequestTemplate:feign.ReflectiveFeign.BuildTemplateByResolvingArgs#create
    1. 解析参数构建EncodedTemplate对象:feign.ReflectiveFeign.BuildEncodedTemplateFromArgs#resolve
    2. encode request body:org.springframework.cloud.openfeign.support.PageableSpringEncoder#encode->org.springframework.cloud.openfeign.support.SpringEncoder#encode
    3. org.springframework.http.converter.json.MappingJackson2HttpMessageConverter->org.springframework.http.converter.AbstractHttpMessageConverter#write将request body写入org.springframework.cloud.openfeign.support.SpringEncoder.FeignOutputMessage
    4. 如果请求没有Content-Type header默认值设置为:application/json;charset=UTF-8
    5. 写入encoded body,并追加Content-Length header
  4. 克隆Retryer:feign.Retryer.Default#clone
  5. 发起请求并解析响应结果:feign.SynchronousMethodHandler#executeAndDecode
  6. 构建请求:feign.SynchronousMethodHandler#targetRequest
    1. 回调拦截器处理请求:feign.RequestInterceptor
    2. 创建request请求:feign.Target.HardCodedTarget#apply-》feign.RequestTemplate#request
  7. 客户端执行请求:feign.SynchronousMethodHandler#client#execute(request, options):org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
  8. 负载均衡,重构Server请求URI:com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)-》com.netflix.loadbalancer.LoadBalancerContext#reconstructURIWithServer
  9. 完成请求返回响应结果feign.Response
  10. decode response
  11. org.springframework.web.client.HttpMessageConverterExtractor.extractData
  12. 如果转换器类型为GenericHttpMessageConverter,根据responseType+contentType获取转换器GenericHttpMessageConverter
  13. 否则根据responseClass+contentType获取转换器
  14. HttpMessageConverter将response转换为目标方法返回值类型

问题分析

客户端

ReWriteHeaderFeignClient自定义封装调用链路

  1. 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的类,故而排除

服务端

处理流程

  1. 接收派发请求:org.springframework.web.servlet.DispatcherServlet#doDispatch
  2. 根据请求查找处理句柄:org.springframework.web.servlet.DispatcherServlet#getHandler
  3. 查找Handler适配器(即目标方法适配器):org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
  4. 预处理句柄回调:org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle-》org.springframework.web.servlet.HandlerInterceptor#preHandle
  5. 实际执行处理句柄:org.springframework.web.servlet.HandlerAdapter#handle
  6. 回处理完成后置调拦截器:org.springframework.web.servlet.HandlerInterceptor#postHandle
  7. 处理派发请求处理结果:org.springframework.web.servlet.DispatcherServlet#processDispatchResult
    1. 如果存在异常则处理:org.springframework.web.servlet.DispatcherServlet#processHandlerException
    2. 如果存在ModelAndView,则渲染mv:org.springframework.web.servlet.DispatcherServlet#render
    3. 如果是并发异步处理则返回
    4. 否则回调拦截器:org.springframework.web.servlet.HandlerInterceptor#afterCompletion
  8. 如果是并发异步处理则回调拦截器:org.springframework.web.servlet.AsyncHandlerInterceptor#afterConcurrentHandlingStarted
  9. 如果是multipart请求清理multipart:org.springframework.web.servlet.DispatcherServlet#cleanupMultipart
  10. 发布事件:org.springframework.web.servlet.FrameworkServlet#publishRequestHandledEvent
  11. javax.servlet.FilterChain

实际执行处理句柄

org.springframework.web.servlet.DispatcherServlet#doDispatch->org.springframework.web.servlet.HandlerAdapter#handle

  1. org.springframework.web.servlet.HandlerAdapter#handle,当前案例实现类:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter-》org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
  2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
  3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
    1. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDataBinderFactory
    2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelFactory
    3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#createInvocableHandlerMethod
    4. 设置argumentResolvers(见图1)
    5. 设置returnValueHandlers(见图2)
    6. 设置parameterNameDiscoverer:org.springframework.core.DefaultParameterNameDiscoverer
    7. 设置ignoreDefaultModelOnRedirect,默认true
    8. 设置asyncRequestTimeout,默认null
    9. 设置TaskExecutor:new SimpleAsyncTaskExecutor(“MvcAsync”)
    10. 注册callableInterceptors,默认空数组:CallableProcessingInterceptor
    11. 注册deferredResultInterceptors,默认空数组:DeferredResultProcessingInterceptor
    12. 执行目标方法:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
    13. 反射调用目标方法,得到返回值
    14. 如果返回值为空或者responseStatusReason不为空设置setRequestHandled后返回
    15. 选择支持返回值类型的处理句柄:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler,当前案例:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
    16. 回调返回值拦截器:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue,当前案例:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
    17. 创建输入输出消息对象:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#createInputMessage,org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#createOutputMessage
    18. 写入数据:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
    19. 如果是isResourceType,写入header:Accept-Ranges,存在异常则写入header:Content-Range,当前案例:否
    20. 如果outputMessage存在Content-Type则使用,当前案例:不存在
    21. 否则获取request请求的Accept类型org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#getAcceptableMediaTypes
    22. 获取请求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
    23. 根据请求方acceptableTypes获取可兼容的producibleTypes作为可使用的Content-Type MediaType列表:mediaTypesToUse,当前案例:Content-Type列表见:注释1
    24. 排序可兼容的mediaTypes:org.springframework.http.MediaType#sortBySpecificityAndQuality,排序结果见:注释2
    25. 选择第一个具体的MediaType:org.springframework.util.MimeType#isConcrete
    26. 获取匹配的转换器:org.springframework.http.converter.HttpMessageConverter,当前案例:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
    27. 写入body前置通知回调:org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite
    28. 如果存在则写入Response header:Content-Disposition
    29. 写入body数据:MappingJackson2HttpMessageConverter-》org.springframework.http.converter.AbstractGenericHttpMessageConverter#write
    30. 添加默认header:org.springframework.http.converter.AbstractHttpMessageConverter#addDefaultHeaders,Content-Type,Content-Length(当前案例均为null不写入:org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#getContentLength)
    31. 向outputMessage写入body数据:org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
    32. 构建ModelAndView:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView
  4. 请求完成后置处理:org.springframework.web.context.request.AbstractRequestAttributes#requestCompleted
  5. 如果Response不包含header(Cache-Control),并且org.springframework.web.method.annotation.SessionAttributesHandler#hasSessionAttributes,处理cache:org.springframework.web.servlet.support.WebContentGenerator#applyCacheSeconds(javax.servlet.http.HttpServletResponse, int)
  6. 否则准备Response:org.springframework.web.servlet.support.WebContentGenerator#prepareResponse
    1. cacheControl不为空,则设置header(Cache-Control),或header(Pragma),或header(Expires)
    2. 否则设置header(Cache-Control),或header(Pragma),或header(Expires)为指定值,例如:no-cache,no-store,1L
    3. 如果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

解决方法

  1. 请求方使用适配的Accept类型,例如:application/json;charset=utf8或*/*
  2. 接口PostMapping/RequestMapping等注解增加produces配置:org.springframework.web.bind.annotation.PostMapping#produces
  3. 实现接口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)
  4. 为OutputMessage设置适配的Content-Type header org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#createOutputMessage
    1. 增加自定义ServletHandler,例如:xxl-job中的方法,代码见附件3
    2. 实现预处理handle拦截器处理:org.springframework.web.servlet.HandlerInterceptor#preHandle

注意:方法2-4要根据实际业务场景是否可以使用,客户端期望xhtml,但是实际响应json,是否有潜在的风险?
Q&A

  1. 接口PostMapping/RequestMapping等注解增加headers配置是否可以?org.springframework.web.bind.annotation.PostMapping#headers
    1. 不可以,因为该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
argumentResolvers.png
图2
returnValueHandlers.png
附件3
在这里插入图片描述

相关文章:

Could not extract response: no suitable HttpMessageConverter

版本&#xff1a;spring-cloud-openfeign-core-2.1.1.RELEASE.jar&#xff0c;spring-webmvc-5.1.14.RELEASE.jar&#xff0c;jetty-server-9.4.41.v20210516.jar&#xff0c;tomcat-embed-core-9.0.48.jar 问题背景 生产服务请求下游服务时偶发抛出下面的异常&#xff0c;下…...

文献计量三大定律之一---洛特卡定律及普赖斯定律

科学生产率是洛特卡定律的基础&#xff0c;科学生产率”(Scientific Productivity)&#xff09;是指科学家&#xff08;科研人员&#xff09;在科学上所表现出的能力和工作效率&#xff0c;通常用其生产的科学文献的数量来衡量。 1926年&#xff0c;洛特卡在一篇论文中提出了科…...

2023年软考高级网络规划设计师

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

数据治理驱动因素 -报考题

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

2023淘宝天猫38节红包满减优惠活动时间是从几月几号什么时候开始?

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

Hive表优化、表设计优化、Hive表数据优化(ORC)、数据压缩、存储优化

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

LearnOpenGL-入门-着色器

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

【谷粒学院】vue、axios、element-ui、node.js(44~58)

44.前端技术-vue入门 &#x1f9e8;Vue.js 是什么 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式框架。 Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。另一方面&#xff0c;当与现代化的工具…...

【一些回忆】2022.02.26-2023.02.26 一个普通男孩的365天

&#x1f483;&#x1f3fc; 本人简介&#xff1a;男 &#x1f476;&#x1f3fc; 年龄&#xff1a;18 &#x1f91e; 作者&#xff1a;那就叫我亮亮叭 &#x1f4d5; 专栏&#xff1a;一些回忆 为什么选择在这个时间节点回忆一下呢&#xff1f; 一是因为今天距离2023高考仅剩1…...

OSPF的多区域特性 (电子科技大学TCP/IP实验三)

一&#xff0e;实验目的 1、掌握OSPF 协议中区域的类型、特征和作用 2、掌握OSPF 路由器的类型、特征和作用 3、掌握OSPF LSA 分组的类型、特征和作用 4、理解OSPF 区域类型、路由器类型和OSPF LSA 分组类型间的相互关系 二&#xff0e;预备知识 1、静态路由选择和动态路…...

(四十四)多个事务更新同一行数据时,是如何加锁避免脏写的?

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

【数据库】第十二章 数据库管理

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

Redis源码---整体架构

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

基于springboot+vue的校园招聘系统

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

SAP MM学习笔记1-SAP中扩张的概念,如何将一个物料从工厂A扩张到工厂B

MM中在创建物料的时候&#xff0c;最低也得创建如下5个view。 基本数据1 基本数据2 购买管理 会计1 会计2 1&#xff0c;扩张是什么 有时候&#xff0c;你想增加其他的View&#xff0c;比如保管场所 等&#xff0c;你不能用MM02来做编辑&#xff0c;要用MM01来做扩张。这就是扩…...

【Python】Numpy数组的切片、索引详解:取数组的特定行列

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

2023年全国最新交安安全员精选真题及答案6

百分百题库提供交安安全员考试试题、交安安全员考试预测题、交安安全员考试真题、交安安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 51.安全生产资金保障制度建立后关键在于落实&#xff0c;各施工企业在落实安全生…...

JavaScript 闭包【自留】

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

【MySQL】什么是意向锁 IS IX 及值得学习的思想

文章目录前言行锁和表锁使用意向锁意向锁的算法意向锁的思想JDK 中相似的思想前言 之前看 MySQL 都刻意忽略掉了 IS 和 IX 锁&#xff0c;今天看 《MySQL 是怎样运行的》&#xff0c;把意向锁讲的很通透&#xff0c;本篇博文提炼一下思想。 I: Intention Lock&#xff08;意向…...

python多线程实现

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

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

高等数学(下)题型笔记(八)空间解析几何与向量代数

目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

理想汽车5月交付40856辆,同比增长16.7%

6月1日&#xff0c;理想汽车官方宣布&#xff0c;5月交付新车40856辆&#xff0c;同比增长16.7%。截至2025年5月31日&#xff0c;理想汽车历史累计交付量为1301531辆。 官方表示&#xff0c;理想L系列智能焕新版在5月正式发布&#xff0c;全系产品力有显著的提升&#xff0c;每…...

20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题

20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题 2025/6/9 20:54 缘起&#xff0c;为了跨网段推流&#xff0c;千辛万苦配置好了网络参数。 但是命令iptables -t filter -F tetherctrl_FORWARD可以在调试串口/DEBUG口正确执行。…...

day51 python CBAM注意力

目录 一、CBAM 模块简介 二、CBAM 模块的实现 &#xff08;一&#xff09;通道注意力模块 &#xff08;二&#xff09;空间注意力模块 &#xff08;三&#xff09;CBAM 模块的组合 三、CBAM 模块的特性 四、CBAM 模块在 CNN 中的应用 一、CBAM 模块简介 在之前的探索中…...