今天同事遇到一个问题,swagger-ui界面每个接口的response的model schema 都没有生成,如下图所示:
用的swagger版本:
io.springfox
springfox-swagger-ui
2.4.0
io.springfox
springfox-swagger2
2.4.0
本地跑了下,发现启动就有告警信息:
18:09:33.596 [main] WARN Exception calculating properties for model(com.xxx.xxx.mobile.client.dto.resp.CarBrandResp) -> ModelContext{type=com.xxx.xxx.mobile.client.dto.resp.CarBrandResp, isReturnType=true}. java.lang.NullPointerException
Debug了下发现OptimizedModelPropertiesProvider
的objectMapper
为空导致的NPE.详细堆栈信息记录如下:
at springfox.documentation.schema.property.OptimizedModelPropertiesProvider.beanDescription(OptimizedModelPropertiesProvider.java:350)
at springfox.documentation.schema.property.OptimizedModelPropertiesProvider.propertiesFor(OptimizedModelPropertiesProvider.java:117)
at springfox.documentation.schema.property.CachingModelPropertiesProvider$1.load(CachingModelPropertiesProvider.java:56)
at springfox.documentation.schema.property.CachingModelPropertiesProvider$1.load(CachingModelPropertiesProvider.java:54)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2319)
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2282)
- locked <0x1a49> (a com.google.common.cache.LocalCache$StrongAccessWriteEntry)
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2197)
at com.google.common.cache.LocalCache.get(LocalCache.java:3937)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3941)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4824)
at springfox.documentation.schema.property.CachingModelPropertiesProvider.propertiesFor(CachingModelPropertiesProvider.java:64)
at springfox.documentation.schema.DefaultModelProvider.properties(DefaultModelProvider.java:151)
at springfox.documentation.schema.DefaultModelProvider.modelFor(DefaultModelProvider.java:84)
at springfox.documentation.schema.CachingModelProvider$1.load(CachingModelProvider.java:51)
at springfox.documentation.schema.CachingModelProvider$1.load(CachingModelProvider.java:49)
at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3527)
at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2319)
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2282)
- locked <0x1a74> (a com.google.common.cache.LocalCache$StrongAccessWriteEntry)
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2197)
at com.google.common.cache.LocalCache.get(LocalCache.java:3937)
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:3941)
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:4824)
at springfox.documentation.schema.CachingModelProvider.modelFor(CachingModelProvider.java:59)
at springfox.documentation.spring.web.scanners.ApiModelReader.read(ApiModelReader.java:67)
at springfox.documentation.spring.web.scanners.ApiListingScanner.scan(ApiListingScanner.java:88)
at springfox.documentation.spring.web.scanners.ApiDocumentationScanner.scan(ApiDocumentationScanner.java:69)
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.scanDocumentation(DocumentationPluginsBootstrapper.java:105)
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.onApplicationEvent(DocumentationPluginsBootstrapper.java:91)
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.onApplicationEvent(DocumentationPluginsBootstrapper.java:53)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:393)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:399)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:347)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:883)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546)
- locked <0x1a75> (a java.lang.Object)
at org.springframework.cloud.context.named.NamedContextFactory.createContext(NamedContextFactory.java:116)
at org.springframework.cloud.context.named.NamedContextFactory.getContext(NamedContextFactory.java:85)
- locked <0x1a76> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.cloud.context.named.NamedContextFactory.getInstance(NamedContextFactory.java:121)
at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.get(FeignClientFactoryBean.java:193)
at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.feign(FeignClientFactoryBean.java:84)
at org.springframework.cloud.netflix.feign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:221)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:103)
- locked <0x1a77> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1634)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:254)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1316)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1282)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1101)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
- locked <0x1a78> (a java.lang.Object)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
at com.xxx.xxx.mobile.MobileApplication.main(MobileApplication.java:21)
github上已有相关issue:
CachingModelPropertiesProvider - NullPointerException
【praveen12bnitt】解释了原因:
What i found was, the NPE is coming for OptimizedModelPropertiesProvider because the objectMapper is null.
When a Feign client is added to the application, spring-cloud creates a child application context for feign beans. Check spring cloud's NamedContextFactory for details on how they create the context. Once this context is refreshed, it signals a ContextRefreshedEvent which is picked up by DocumentationPluginsBootstrapper. The problem is the main application context initialization is still not complete. RequestMappingHandlerAdapter is not even created at this time and so ObjectMapperConfigurer does not fire ObjectMapperConfigured. This is the reason why OptimizedModelPropertiesProvider has a null object mapper.
When RequestMappingHandlerAdapter is created at a later time by the main application context, all the events are fired, but its too late. DocumentationPluginsBootstrapper has already finished its model evaluation.
A possible fix is to wait for the main application's ContextRefreshedEvent, and skip the ContextRefreshedEvent published by spring-cloud. But i am not sure how to do it.
issue中解释的还是很清楚的,结合堆栈信息:
当引入Feign客户端时,SpringCloud创建了一个子应用程序上下文FeignContext;创建过程中会调用refresh方法,这个方法里会派发ContextRefreshedEvent
事件;而DocumentationPluginsBootstrapper
会监听这个事件,然后开始扫描API构建Documentation;这里的NPE正是在构建Model的过程中报错的;
NPE是因为:此时主应用程序上下文初始化仍未完成,主应用程序上下文在配置类WebMvcAutoConfiguration
中会创建RequestMappingHandlerAdapter
实例;
配置类SpringfoxWebMvcConfiguration
中会创建ObjectMapperConfigurer
实例,这个类实现了BeanPostProcessor
接口,会在任何bean初始化之前调用postProcessBeforeInitialization
方法,这个方法内部判断了如果bean是RequestMappingHandlerAdapter,那么会派发一个ObjectMapperConfigured
事件;
OptimizedModelPropertiesProvider
监听了这个事件,从而设置objectMapper
;
如果我们没有使用FeignClient,那么一切还是很完美的;但是引入FeignClient之后,DocumentationPluginsBootstrapper
过早的监听到了springcloud派发的事件;实际上应该监听的是主应用程序上下文派发的事件。
issue中给出了解决方法:让DocumentationPluginsBootstrapper
实现SmartLifecycle
接口,这个是在所有bean加载并初始化完成之后才执行的,其实就是让DocumentationPluginsBootstrapper
延迟执行构建Documentation的功能。
这个在后面的版本2.5.0中得到了解决,遂升级了版本...