Defer `MappingContext` lookup in `PersistentEntities` to allow bean creation while `MappingContext` is being initialized
Closed this issue · 6 comments
Hello,
I have working on project using spring-data-rest and spring-data-mongodb and all work fine. when i have only add spring-cloud-stream spring-data api doesn't work any more.
Error
I have null pointer in this method (org.springframework.data.rest.webmvc.config.ResourceMetadataHandlerMethodArgumentResolver#resolveArgument) mappings.getMetadataFor(domainType) return null for mongo domaintype.
cause
The bean org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration#persistentEntities does't found MongoMappingContext when calling BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MappingContext.class).values()), this is because persistentEntities is created when creating MongoMappingContext.
This is the chain of bean creation:
- when creating bean MongoMappingContext it publish event
publishEvent:387, AbstractApplicationContext (org.springframework.context.support)
addPersistentEntity:434, AbstractMappingContext (org.springframework.data.mapping.context)
-
publishing event need all listner to be loaded so bean org.springframework.cloud.stream.function.FunctionConfiguration#streamBridgeUtils should be loaded.
-
streamBridgeUtils need bean org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration#functionCatalog
-
functionCatalog need all GenericConverter
@Bean
public FunctionRegistry functionCatalog(...r) {
....
Map<String, GenericConverter> converters = context.getBeansOfType(GenericConverter.class);- bean jsonSchemaConverter org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration#jsonSchemaConverter is loaded because it's a GenericConverter
- persistentEntities is loaded because jsonSchemaConverter
so persistentEntities is loaded during MongoMappingContext creation and no way to change order.
version:
Spring-boot 3.4.5 same problem on 3.5.0
spring-cloud-starter-stream-kafka: 4.2.1
persistentEntities:328, RepositoryRestMvcConfiguration (org.springframework.data.rest.webmvc.config)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
lambda$instantiate$0:171, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
get:-1, SimpleInstantiationStrategy$$Lambda$714/0x00000008011d2998 (org.springframework.beans.factory.support)
instantiateWithFactoryMethod:88, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiate:168, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiate:653, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:489, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1367, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1197, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:569, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:529, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:339, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$474/0x0000000800f96360 (org.springframework.beans.factory.support)
getSingleton:371, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:337, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
doResolveDependency:1681, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1627, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:913, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:791, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:546, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1367, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1197, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:569, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:529, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:339, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$474/0x0000000800f96360 (org.springframework.beans.factory.support)
getSingleton:371, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:337, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:747, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:735, DefaultListableBeanFactory (org.springframework.beans.factory.support)
getBeansOfType:1418, AbstractApplicationContext (org.springframework.context.support)
functionCatalog:113, ContextFunctionCatalogAutoConfiguration (org.springframework.cloud.function.context.config)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:77, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:568, Method (java.lang.reflect)
lambda$instantiate$0:171, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
get:-1, SimpleInstantiationStrategy$$Lambda$714/0x00000008011d2998 (org.springframework.beans.factory.support)
instantiateWithFactoryMethod:88, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiate:168, SimpleInstantiationStrategy (org.springframework.beans.factory.support)
instantiate:653, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:645, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1367, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1197, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:569, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:529, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:339, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$474/0x0000000800f96360 (org.springframework.beans.factory.support)
getSingleton:371, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:337, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
doResolveDependency:1681, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1627, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:913, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:791, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:546, ConstructorResolver (org.springframework.beans.factory.support)
instantiateUsingFactoryMethod:1367, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1197, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:569, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:529, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:339, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$474/0x0000000800f96360 (org.springframework.beans.factory.support)
getSingleton:371, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:337, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:207, AbstractBeanFactory (org.springframework.beans.factory.support)
retrieveApplicationListeners:267, AbstractApplicationEventMulticaster (org.springframework.context.event)
getApplicationListeners:223, AbstractApplicationEventMulticaster (org.springframework.context.event)
multicastEvent:145, SimpleApplicationEventMulticaster (org.springframework.context.event)
publishEvent:454, AbstractApplicationContext (org.springframework.context.support)
publishEvent:387, AbstractApplicationContext (org.springframework.context.support)
addPersistentEntity:434, AbstractMappingContext (org.springframework.data.mapping.context)
lambda$createAndRegisterProperty$3:671, AbstractMappingContext$PersistentPropertyCreator (org.springframework.data.mapping.context)
accept:-1, AbstractMappingContext$PersistentPropertyCreator$$Lambda$1748/0x00000008018f0250 (org.springframework.data.mapping.context)
forEach:75, Iterable (java.lang)
createAndRegisterProperty:668, AbstractMappingContext$PersistentPropertyCreator (org.springframework.data.mapping.context)
doWith:622, AbstractMappingContext$PersistentPropertyCreator (org.springframework.data.mapping.context)
doWithFields:727, ReflectionUtils (org.springframework.util)
doAddPersistentEntity:471, AbstractMappingContext (org.springframework.data.mapping.context)
addPersistentEntity:424, AbstractMappingContext (org.springframework.data.mapping.context)
lambda$createAndRegisterProperty$3:671, AbstractMappingContext$PersistentPropertyCreator (org.springframework.data.mapping.context)
accept:-1, AbstractMappingContext$PersistentPropertyCreator$$Lambda$1748/0x00000008018f0250 (org.springframework.data.mapping.context)
forEach:75, Iterable (java.lang)
createAndRegisterProperty:668, AbstractMappingContext$PersistentPropertyCreator (org.springframework.data.mapping.context)
doWith:622, AbstractMappingContext$PersistentPropertyCreator (org.springframework.data.mapping.context)
doWithFields:727, ReflectionUtils (org.springframework.util)
doAddPersistentEntity:471, AbstractMappingContext (org.springframework.data.mapping.context)
addPersistentEntity:424, AbstractMappingContext (org.springframework.data.mapping.context)
getPersistentEntity:320, AbstractMappingContext (org.springframework.data.mapping.context)
getPersistentEntity:246, AbstractMappingContext (org.springframework.data.mapping.context)
getPersistentEntity:97, AbstractMappingContext (org.springframework.data.mapping.context)
getRequiredPersistentEntity:74, MappingContext (org.springframework.data.mapping.context)
getEntityInformation:160, MongoRepositoryFactory (org.springframework.data.mongodb.repository.support)
getTargetRepository:136, MongoRepositoryFactory (org.springframework.data.mongodb.repository.support)
getRepository:377, RepositoryFactorySupport (org.springframework.data.repository.core.support)
lambda$afterPropertiesSet$4:350, RepositoryFactoryBeanSupport (org.springframework.data.repository.core.support)
get:-1, RepositoryFactoryBeanSupport$$Lambda$1662/0x00000008018d9e70 (org.springframework.data.repository.core.support)
getNullable:135, Lazy (org.springframework.data.util)
get:113, Lazy (org.springframework.data.util)
afterPropertiesSet:356, RepositoryFactoryBeanSupport (org.springframework.data.repository.core.support)
afterPropertiesSet:101, MongoRepositoryFactoryBean (org.springframework.data.mongodb.repository.support)
invokeInitMethods:1865, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
initializeBean:1814, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:607, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:529, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:339, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$474/0x0000000800f96360 (org.springframework.beans.factory.support)
getSingleton:371, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:337, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
resolveCandidate:254, DependencyDescriptor (org.springframework.beans.factory.config)
doResolveDependency:1739, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveDependency:1627, DefaultListableBeanFactory (org.springframework.beans.factory.support)
resolveAutowiredArgument:913, ConstructorResolver (org.springframework.beans.factory.support)
createArgumentArray:791, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:240, ConstructorResolver (org.springframework.beans.factory.support)
autowireConstructor:1387, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBeanInstance:1224, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:569, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:529, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:339, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, AbstractBeanFactory$$Lambda$474/0x0000000800f96360 (org.springframework.beans.factory.support)
getSingleton:371, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:337, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:202, AbstractBeanFactory (org.springframework.beans.factory.support)
instantiateSingleton:1221, DefaultListableBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingleton:1187, DefaultListableBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:1122, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:987, AbstractApplicationContext (org.springframework.context.support)
refresh:627, AbstractApplicationContext (org.springframework.context.support)
refresh:146, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:753, SpringApplication (org.springframework.boot)
refreshContext:439, SpringApplication (org.springframework.boot)
run:318, SpringApplication (org.springframework.boot)
run:1362, SpringApplication (org.springframework.boot)
run:1351, SpringApplication (org.springframework.boot)
Would you mind attaching a minimal reproducer? Looking at the stack trace, initialization is rooted in MongoRepositoryFactoryBean that is creating a Repository instance. From there on, the wired MappingContext is publishing an event. That being said, MongoMappingContext is fully initialized at that time.
I would like to investigate the said NullPointerException in detail to see what causes it.
hello @mp911de ,
Thanks to take time to check my issue, I was working on big project so i create a minimal project to reproduce the problem.
you can see on github actions that the build does'nt work any more only by adding spring-stream-cloud dependency
https://github.com/anasoid/rest-data-stream-issue/actions
The branch with issue is https://github.com/anasoid/rest-data-stream-issue/tree/issue
the main branch work perfectly.
The test is PersonControllerTest.
the PR for issue is anasoid/rest-data-stream-issue#1
MongoMappingContext is not fully initailized for spring, during initailisation of MongoMappingContext if any bean get the list of MappingContext it will not found MongoMappingContext.
I fix temproaly the issue by this code as after MongoMappingContext is loaded it will be available.
I create branch with this fix and it work also (https://github.com/anasoid/rest-data-stream-issue/actions/runs/15495044138)
@Configuration
@Slf4j
public class SpringDataFixIssueStreamFunction {
@Autowired private ApplicationContext applicationContext;
@Autowired private PersistentEntities persistentEntities;
/**
* @see
* org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration#persistentEntities()
*/
@EventListener(ApplicationReadyEvent.class)
public void fixPersistentEntities() {
log.warn("Fixing PersistentEntities contexts via reflection");
List<MappingContext<?, ?>> arrayList = new ArrayList<>();
for (MappingContext<?, ?> context :
BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MappingContext.class)
.values()) {
arrayList.add(context);
}
setContexts(persistentEntities, arrayList);
}
private void setContexts(
PersistentEntities persistentEntities,
Collection<? extends MappingContext<?, ? extends PersistentProperty<?>>> newContexts) {
try {
Field contextsField = PersistentEntities.class.getDeclaredField("contexts");
contextsField.setAccessible(true);
contextsField.set(persistentEntities, newContexts);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Failed to set contexts field via reflection", e);
}
}
}Thanks a lot for the reproducer. The issue is triggered initially by eager domain model scanning. If the domain model was added later, then all beans would initialize cleanly and with the proper wiring.
Summary of contributing factors (paging @olegz and @odrotbohm):
- Eager entity scan (there should be no reason to disable eager scanning)
- Event publication leading to instantiation of
StreamBridgeand causingFunctionCatalogto obtainGenericConverterbeans - Initialization of
PersistentEntityToJsonSchemaConverterrequiringPersistentEntities PersistentEntitiesobtaining the mapping contexts that are about to be initialized
Looking at BeanFactoryAwareFunctionRegistry, it would qualify as good spot for deferring its lookups because its name already suggests being aware of BeanFactory. Also, its design doesn't require eager presence of constructor inputs as these are merely held in the class without further interaction.
PersistentEntities is similar in design, it holds its input in the class for later usage. I think we could have also a variant of PersistentEntities that could accept a Supplier<? extends Collection<? extends MappingContext>>.
The the amount of involved components contributes to the complexity where to resolve the issue.
I wonder if it makes sense to lazify the dependency from PersistentEntityToJsonSchemaConverter as it seems to be a very low-level abstraction (a Converter) depending on rather high-level ones such as PersistentEntities. I'd suggest we start with PE and see whether this resolves the issue.
PersistentEntities now internally uses Lazy and accesses its Iterable.iterator() lazily allowing to simplify the config in Spring Data REST.
