今天同事遇到一個(gè)問題,swagger-ui界面每個(gè)接口的response的model schema 都沒有生成,如下圖所示:

用的swagger版本:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
本地跑了下,發(fā)現(xiàn)啟動(dòng)就有告警信息:
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了下發(fā)現(xiàn)OptimizedModelPropertiesProvider的objectMapper為空導(dǎo)致的NPE.詳細(xì)堆棧信息記錄如下:
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上已有相關(guān)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中解釋的還是很清楚的,結(jié)合堆棧信息:
當(dāng)引入Feign客戶端時(shí),SpringCloud創(chuàng)建了一個(gè)子應(yīng)用程序上下文FeignContext;創(chuàng)建過程中會(huì)調(diào)用refresh方法,這個(gè)方法里會(huì)派發(fā)ContextRefreshedEvent事件;而DocumentationPluginsBootstrapper會(huì)監(jiān)聽這個(gè)事件,然后開始掃描API構(gòu)建Documentation;這里的NPE正是在構(gòu)建Model的過程中報(bào)錯(cuò)的;
NPE是因?yàn)椋捍藭r(shí)主應(yīng)用程序上下文初始化仍未完成,主應(yīng)用程序上下文在配置類WebMvcAutoConfiguration中會(huì)創(chuàng)建RequestMappingHandlerAdapter實(shí)例;
配置類SpringfoxWebMvcConfiguration中會(huì)創(chuàng)建ObjectMapperConfigurer實(shí)例,這個(gè)類實(shí)現(xiàn)了BeanPostProcessor接口,會(huì)在任何bean初始化之前調(diào)用postProcessBeforeInitialization方法,這個(gè)方法內(nèi)部判斷了如果bean是RequestMappingHandlerAdapter,那么會(huì)派發(fā)一個(gè)ObjectMapperConfigured事件;
OptimizedModelPropertiesProvider監(jiān)聽了這個(gè)事件,從而設(shè)置objectMapper;
如果我們沒有使用FeignClient,那么一切還是很完美的;但是引入FeignClient之后,DocumentationPluginsBootstrapper過早的監(jiān)聽到了springcloud派發(fā)的事件;實(shí)際上應(yīng)該監(jiān)聽的是主應(yīng)用程序上下文派發(fā)的事件。
issue中給出了解決方法:讓DocumentationPluginsBootstrapper實(shí)現(xiàn)SmartLifecycle接口,這個(gè)是在所有bean加載并初始化完成之后才執(zhí)行的,其實(shí)就是讓DocumentationPluginsBootstrapper延遲執(zhí)行構(gòu)建Documentation的功能。
這個(gè)在后面的版本2.5.0中得到了解決,遂升級(jí)了版本...