I need to add multiple beans in Spring context manually on bootstrap. Those beans require MongoTemplate bean to work. Also, this code is in starter. First, I've used an approach with BeanDefinitionRegistryPostProcessor, where I try to get MT bean from beanFactory, but soon I've discovered, that this MT has default settings, such as localhost:27017 and so.
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Reflections reflections = new Reflections();
Set<Class<? extends LockableObject>> lockableEntities = reflections.getSubTypesOf(LockableObject.class);
MongoTemplate mongoTemplate = beanFactory.getBean(MongoTemplate.class);
lockableEntities.forEach(clazz -> {
CommonGenericLockRepositoryImpl repository = new CommonGenericLockRepositoryImpl(mongoTemplate, clazz);
MongoLockService mongoLockService = new MongoLockServiceImpl(repository);
beanFactory.registerSingleton(createBeanName(clazz.getSimpleName()), mongoLockService);
});
}
Then I've tried to @Autowire MT into RegistryBPP, but it was null.
private final MongoTemplate mongoTemplate;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Reflections reflections = new Reflections("phoenixit.proto");
Set<Class<? extends LockableObject>> lockableEntities = reflections.getSubTypesOf(LockableObject.class);
MongoTemplate mongoTemplate = beanFactory.getBean(MongoTemplate.class);
lockableEntities.forEach(clazz -> {
CommonGenericLockRepositoryImpl repository = new CommonGenericLockRepositoryImpl(mongoTemplate, clazz);
MongoLockService mongoLockService = new MongoLockServiceImpl(repository);
beanFactory.registerSingleton(createBeanName(clazz.getSimpleName()), mongoLockService);
});
}
Finally, I used @Autowire on method parameter in @Configuration class, which again resulted in default MT configuration
@AutoConfiguration
public class MongoLockConfig {
@Bean
public MongoLockBeanDefinitionRegistryPostProcessor mongoLockBeanDefinitionRegistryPostProcessor(@Autowired MongoTemplate mongoTemplate) {
return new MongoLockBeanDefinitionRegistryPostProcessor(mongoTemplate);
}
}
After that, I removed every piece of code that accessed MT bean, and restarted the project. And now my properties from .yml file was used. I've tried @DependsOn and @ConditionalOnBean, but it seems to me that if I want to use MongoTemplate bean earlier, that it is configured by itself, Spring just configures it ignoring .yml properties.
Is it bugged, or I'm doing something wrong? Maybe you can suggest another approach of adding beans?
You should be registering BeanDefinition
instances and not actual beans. The BeanDefinitionRegistryPostProcessor
operates very very early in the process, no configuration has been applied yet nor have any beans been constructed. Trying to access beans in that phase will not work.
Which is the same reason autowiring won't work in these kind of classes. What you should do instead is register BeanDefinition
instances and let Spring do the heave lifting of constructing it and managing its lifecycle.
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Reflections reflections = new Reflections("phoenixit.proto");
Set<Class<? extends LockableObject>> lockableEntities = reflections.getSubTypesOf(LockableObject.class);
lockableEntities.forEach(clazz -> {
var lockRepositoryDefinition = BeanDefinitionBuilder.rootBeanDefinition(CommonGenericLockRepositoryImpl.class).addConstructorArgReference("mongoTemplate").addConstructorArgValue(clazzz).getBeanDefinition();
var name = createBeanName(clazz.getSimpleName()) + "Generic";
registry.registerBeanDefinition(name, lockRepositoryDefinition);
var mongoLockServiceDef = BeanDefinitionBuilder.rootBeanDefinition(MongoLockService.class)
.addConstructorArgReference(name).getBeanDefinition();
var name2 = createBeanName(clazz.getSimpleName());
registry.registerBeanDefinition(name, mongoLockServiceDef);
});
}
Something along those line.
Another option is to write an ApplicationContextInitializer
and use a more functional approach.
public class MyApplicationContextInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
public void initialize(GenericApplicationContext ctx) {
Reflections reflections = new Reflections("phoenixit.proto");
Set<Class<? extends LockableObject>> lockableEntities = reflections.getSubTypesOf(LockableObject.class);
lockableEntities.forEach(clazz -> {
var name = createBeanName(clazz.getSimpleName());
ctx.registerBean(name + "Generic", CommonGenericLockRepository.class, () -> {
MongoTemplate mongo = ctx.getBean(MongoTemplate.class);
return new CommonGenericLockRepositoryImpl.class(mongo, class);
});
ctx.registerBean(name, MongoLockService.class, () -> {
CommonGenericLockRepository repo = ctx.getBean(name + "Generic", CommonGenericLockRepository.class);
return new MongoLockService(repo);
});
});
}
}
You now also need a spring.factories
in META-INF/spring
of your project which registers this MyApplicationContextInitializer
.
org.springframework.context.ApplicationContextInitializer=<your.package.here>.MyApplicationContextInitializer