0


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(UnknownSource)
    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)protectedstaticclassHttpClientFeignConfiguration{...}@Configuration@ConditionalOnClass(OkHttpClient.class)@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)@ConditionalOnProperty("feign.okhttp.enabled")protectedstaticclassOkHttpFeignConfiguration{...}

查看注解:@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,注册客户端配置方法代码如下
privatevoidregisterClientConfiguration(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名称为className2. feign client bean别名使用注解属性(FeignClient):qualifier,兜底使用:contextId+FeignClient,contextId读取注解属性优先级:contextId>name,name注解属性优先级:serviceId>name>value

代码如下

privatevoidregisterFeignClient(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// null
        beanDefinition.setPrimary(primary);String qualifier =getQualifier(attributes);if(StringUtils.hasText(qualifier)){
            alias = qualifier;}BeanDefinitionHolder holder =newBeanDefinitionHolder(beanDefinition, className,newString[]{ alias });BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);}

FeignClientFactoryBean

  1. 获取FeignClient bean:org.springframework.cloud.openfeign.FeignClientFactoryBean#getObject
  2. 应用上下文中获取FeignContext
  3. 创建Feign.Builder:org.springframework.cloud.openfeign.FeignClientFactoryBean#feign 1. 从FeignContext上下文获取构造者feign.Feign.Builder2. 为构建者设置encoder,从FeignContext上下文获取feign.codec.Encoder:org.springframework.cloud.openfeign.support.PageableSpringEncoder->SpringEncoder->SpringFormEncoder->feign.codec.Encoder.Default3. 为构建者设置decoder,从FeignContext上下文获取feign.codec.Decoder:feign.optionals.OptionalDecoder->org.springframework.cloud.openfeign.support.ResponseEntityDecoder->SpringDecoder4. 为构建者设置contract,从FeignContext上下文获取feign.Contract:使用feign.hystrix.HystrixDelegatingContract#HystrixDelegatingContract代理包装自定义实现OpenFeignSpringMvcContract(继承自feign.OpenFeignBaseContract)5. 配置feign(configureFeign):如果存在FeignClientProperties配置,则按照配置设置:loggerLevel/connectTimeout/readTimeout/Retryer/ErrorDecoder/RequestInterceptor/decode404/encoder/decoder/contract。注意配置优先级,如果spring上下文中存在配置bean,则:如果当前配置是默认配置,则当前配置<default配置<contextId对应的配置。否则:default配置<contextId对应的配置<当前配置。如果spring上下文中不存在配置bean,则仅按照当前配置设置feign
  4. 如果不存在url配置:org.springframework.cloud.openfeign.FeignClientFactoryBean#loadBalance
  5. 否则 1. 获取Client:org.springframework.cloud.openfeign.FeignClientFactoryBean#getOptional。如果client不为空并且是LoadBalancerFeignClient类型则获取其代理的实际Client2. 获取Targeter:org.springframework.cloud.openfeign.FeignClientFactoryBean#get3. 构建目标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。AnnotationConfigApplicationContext上下文(每一个应用都有一个独立的上下文,拥有同一个parent Spring应用上下文)仅管理FeignClient注解的bean。从中获取对应的bean(org.springframework.cloud.context.named.NamedContextFactory#getInstance(java.lang.String, java.lang.Class))。当前案例使用自定义封装ReWriteHeaderFeignClient客户端,继承自:org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient
  2. 获取Targeter:org.springframework.cloud.openfeign.HystrixTargeter
  3. 构建目标bean:org.springframework.cloud.openfeign.Targeter#target(feign.Target.HardCodedTarget)

Client

配置入口:org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration-》org.springframework.cloud.openfeign.ribbon.HttpClientFeignLoadBalancedConfiguration#feignClient
默认实现:org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#LoadBalancerFeignClient

  1. lbClientFactory:配置入口org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration#cachingLBClientFactory
  2. clientFactory:配置入口org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration#springClientFactory

构建目标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
publicFeignbuild(){SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =newSynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
          logLevel, decode404, closeAfterDecode, propagationPolicy);ParseHandlersByName handlersByName =newParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
          errorDecoder, synchronousMethodHandlerFactory);returnnewReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);}

SynchronousMethodHandler.Factory工厂相关属性

  1. client:常用实现:ApacheHttpClient,OkHttpClient,feign.Client.Default。当前案例使用自定义封装ReWriteHeaderFeignClient客户端
  2. retryer:feign.Retryer.Default#Default()
  3. requestInterceptors 1. 自定义:OpenFeignRequestInterceptor2. 自定义:GrayPatternMatcherRequestInterceptor3. 自定义:GzipFeignAcceptGzipEncodingInterceptor继承FeignAcceptGzipEncodingInterceptor,如果配置feign.compression.response.enabled=true,则启用该拦截器,追加header Accept-Encoding -> gzip4. 自定义: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()
  5. contract:HystrixDelegatingContract

ReflectiveFeign相关属性

  1. invocationHandlerFactory:feign.InvocationHandlerFactory.Default。当前案例为内部实现类,工厂创建的handler为:HystrixInvocationHandler
  2. queryMapEncoder:同上
  3. handlersByName:ParseHandlersByName

创建目标实例
feign.ReflectiveFeign#newInstance

  1. 获取nameToHandler映射:feign.ReflectiveFeign.ParseHandlersByName#apply 1. 解析与校验元数据:feign.Contract#parseAndValidatateMetadata,当前案例:自定义实现重写父类方法feign.OpenFeignBaseContract#parseAndValidateMetadata2. processAnnotationOnClass3. processAnnotationOnMethod4. processAnnotationsOnParameter5. checkState6. checkMapString:HeaderMap7. checkMapKeys:QueryMap8. 回调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

staticfinalclassDefaultimplementsInvocationHandlerFactory{@OverridepublicInvocationHandlercreate(Target target,Map<Method,MethodHandler> dispatch){returnnewReflectiveFeign.FeignInvocationHandler(target, dispatch);}}

请求过程

关键对象属性

FeignInvocationHandler

  1. target:HardCodedTarget 1. type:目标对象class,例如:MyTestAPI2. name:my-feign-client3. 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
  6. client: 同工厂(SynchronousMethodHandler.Factory),当前案例为自定义实现:ReWriteHeaderFeignClient

FeignLoadBalancer

  1. lb:ZoneAwareLoadBalancer->com.netflix.loadbalancer.DynamicServerListLoadBalancer,定期更新server列表updateListOfServers
  2. config:com.netflix.client.config.DefaultClientConfigImpl

ZoneAwareLoadBalancer

  1. serverListImpl:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList

DynamicServerListLoadBalancer

  1. list:com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList

DiscoveryEnabledNIWSServerList

  1. eurekaClientProvider:com.netflix.niws.loadbalancer.LegacyEurekaClientProvider,get获取返回EurekaClient:com.netflix.discovery.DiscoveryManager#getDiscoveryClient/getEurekaClient
  2. obtainServersViaDiscovery:刷新服务实例列表方法,按照服务名称(vipAddresses,例如:MyEurekaService1,MyEurekaService2)获取实例列表:com.netflix.discovery.EurekaClient#getInstancesByVipAddress(java.lang.String, boolean, java.lang.String)->com.netflix.discovery.DiscoveryClient#getInstancesByVipAddress(java.lang.String, boolean, java.lang.String)

请求过程

  1. 动态代理调用:feign.ReflectiveFeign.FeignInvocationHandler#invoke
  2. 调用代理方法:feign.SynchronousMethodHandler#invoke
  3. 创建RequestTemplate:feign.ReflectiveFeign.BuildTemplateByResolvingArgs#create 1. 解析参数构建EncodedTemplate对象:feign.ReflectiveFeign.BuildEncodedTemplateFromArgs#resolve2. encode request body:org.springframework.cloud.openfeign.support.PageableSpringEncoder#encode->org.springframework.cloud.openfeign.support.SpringEncoder#encode3. org.springframework.http.converter.json.MappingJackson2HttpMessageConverter->org.springframework.http.converter.AbstractHttpMessageConverter#write将request body写入org.springframework.cloud.openfeign.support.SpringEncoder.FeignOutputMessage4. 如果请求没有Content-Type header默认值设置为:application/json;charset=UTF-85. 写入encoded body,并追加Content-Length header
  4. 克隆Retryer:feign.Retryer.Default#clone
  5. 发起请求并解析响应结果:feign.SynchronousMethodHandler#executeAndDecode
  6. 构建请求:feign.SynchronousMethodHandler#targetRequest 1. 回调拦截器处理请求:feign.RequestInterceptor2. 创建request请求:feign.Target.HardCodedTarget#apply-》feign.RequestTemplate#request
  7. 客户端执行请求:feign.SynchronousMethodHandler#client#execute(request, options):org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
  8. 按照客户端名称创建负载均衡客户端:org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#lbClient-》org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory#create,从Spring client Factory中获取ILoadBalancer+IClientConfig,构建FeignLoadBalancer或RetryableFeignLoadBalancer
  9. 负载均衡,构建LB命令:com.netflix.client.AbstractLoadBalancerAwareClient#buildLoadBalancerCommand,如果LoadBalancerCommand.server为null则选择server:com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer-》com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer-》com.netflix.loadbalancer.ZoneAwareLoadBalancer#chooseServer-》自定义实现LB规则…platform.ribbon.wrapper.ZoneAvoidanceAndGrayAndLoadBasedRule#choose(请求eureka刷新服务器列表com.netflix.discovery.DiscoveryClient#refreshRegistry,按照LB规则选择服务器)
  10. 提交命令执行请求。自定义实现com.netflix.loadbalancer.reactive.ServerOperation:重构Server请求URI:com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig)-》com.netflix.loadbalancer.LoadBalancerContext#reconstructURIWithServer
  11. 完成请求返回响应结果feign.Response
  12. decode response
  13. org.springframework.web.client.HttpMessageConverterExtractor.extractData
  14. 如果转换器类型为GenericHttpMessageConverter,根据responseType+contentType获取转换器GenericHttpMessageConverter
  15. 否则根据responseClass+contentType获取转换器
  16. 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
returnResponse.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#processHandlerException2. 如果存在ModelAndView,则渲染mv:org.springframework.web.servlet.DispatcherServlet#render3. 如果是并发异步处理则返回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#getDataBinderFactory2. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelFactory3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#createInvocableHandlerMethod4. 设置argumentResolvers(见图1)5. 设置returnValueHandlers(见图2)6. 设置parameterNameDiscoverer:org.springframework.core.DefaultParameterNameDiscoverer7. 设置ignoreDefaultModelOnRedirect,默认true8. 设置asyncRequestTimeout,默认null9. 设置TaskExecutor:new SimpleAsyncTaskExecutor(“MvcAsync”)10. 注册callableInterceptors,默认空数组:CallableProcessingInterceptor11. 注册deferredResultInterceptors,默认空数组:DeferredResultProcessingInterceptor12. 执行目标方法:org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle13. 反射调用目标方法,得到返回值14. 如果返回值为空或者responseStatusReason不为空设置setRequestHandled后返回15. 选择支持返回值类型的处理句柄:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler,当前案例:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor16. 回调返回值拦截器:org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue,当前案例:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue17. 创建输入输出消息对象:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#createInputMessage,org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#createOutputMessage18. 写入数据: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#getAcceptableMediaTypes22. 获取请求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#allSupportedMediaTypes23. 根据请求方acceptableTypes获取可兼容的producibleTypes作为可使用的Content-Type MediaType列表:mediaTypesToUse,当前案例:Content-Type列表见:注释124. 排序可兼容的mediaTypes:org.springframework.http.MediaType#sortBySpecificityAndQuality,排序结果见:注释225. 选择第一个具体的MediaType:org.springframework.util.MimeType#isConcrete26. 获取匹配的转换器:org.springframework.http.converter.HttpMessageConverter,当前案例:org.springframework.http.converter.json.MappingJackson2HttpMessageConverter27. 写入body前置通知回调:org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice#beforeBodyWrite28. 如果存在则写入Response header:Content-Disposition29. 写入body数据:MappingJackson2HttpMessageConverter-》org.springframework.http.converter.AbstractGenericHttpMessageConverter#write30. 添加默认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#writeInternal32. 构建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,1L3. 如果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等注解增加配置 a. 下游接口可以增加produces配置响应内容为json格式,即content-type header:org.springframework.web.bind.annotation.PostMapping#produces b. 上游接口可以增加consumes配置请求内容为json格式,即accept header:org.springframework.web.bind.annotation.PostMapping#consumes
  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中的方法,代码见附件32. 实现预处理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
在这里插入图片描述

标签: java spring spring boot

本文转载自: https://blog.csdn.net/u010597819/article/details/129227501
版权归原作者 太阳伞下的阿呆 所有, 如有侵权,请联系我们删除。

“Could not extract response: no suitable HttpMessageConverter”的评论:

还没有评论