Affects: 5.3.22
Issue Details
According to the below attached code,
My expectation is that spring framework creates one single instance of ObjectMapper
class,
But actual result is that it creates 2 instances of it.
Run details
- By running the below attached class as it is, the following result appears:
Setting applied: class SettingOne
Setting applied: class SettingTwo
Created the [com.fasterxml.jackson.databind.ObjectMapper@592e843a] instance of object mapper with [2] total applied settings.
Removed the [settingOne] -> [SettingOne] setting bean.
Removed the [settingTwo] -> [SettingTwo] setting bean.
Created the [com.fasterxml.jackson.databind.ObjectMapper@64bc21ac] instance of object mapper with [0] total applied settings.
objectMapperReference1: com.fasterxml.jackson.databind.ObjectMapper@592e843a
objectMapperReference2: com.fasterxml.jackson.databind.ObjectMapper@592e843a
objectMapperReference3: com.fasterxml.jackson.databind.ObjectMapper@64bc21ac
objectMapperReference4: com.fasterxml.jackson.databind.ObjectMapper@64bc21ac
- By running it with the commented
<-- focus line
line, the following result appears:
Setting applied: class SettingOne
Setting applied: class SettingTwo
Created the [com.fasterxml.jackson.databind.ObjectMapper@592e843a] instance of object mapper with [2] total applied settings.
objectMapperReference1: com.fasterxml.jackson.databind.ObjectMapper@592e843a
objectMapperReference2: com.fasterxml.jackson.databind.ObjectMapper@592e843a
objectMapperReference3: com.fasterxml.jackson.databind.ObjectMapper@592e843a
objectMapperReference4: com.fasterxml.jackson.databind.ObjectMapper@592e843a
Demo code (ready for copy paste, apart of maven details)
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
public class Demo {
public static void main(final String... args) {
final AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(ApplicationConfiguration.class);
applicationContext.refresh();
final ObjectMapper objectMapperReference1 = applicationContext.getBean(ObjectMapper.class);
final ObjectMapper objectMapperReference2 = applicationContext.getBean(ObjectMapper.class);
removeConsumedSettings(applicationContext); // <-- focus line
final ObjectMapper objectMapperReference3 = applicationContext.getBean(ObjectMapper.class);
final ObjectMapper objectMapperReference4 = applicationContext.getBean(ObjectMapper.class);
System.out.println("objectMapperReference1: " + objectMapperReference1);
System.out.println("objectMapperReference2: " + objectMapperReference2);
System.out.println("objectMapperReference3: " + objectMapperReference3);
System.out.println("objectMapperReference4: " + objectMapperReference4);
}
private static void removeConsumedSettings(final ApplicationContext applicationContext) {
final BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
applicationContext.getBeansOfType(Setting.class).forEach((name, bean) -> {
beanDefinitionRegistry.removeBeanDefinition(applicationContext.getBeanNamesForType(bean.getClass())[0]);
System.out.printf("Removed the [%s] -> [%s] setting bean.%n", name, bean.getClass().getName());
});
}
}
interface Setting {
void apply(ObjectMapper objectMapper);
}
class SettingOne implements Setting {
@Override
public void apply(final ObjectMapper objectMapper) {
System.out.println("Setting applied: " + this.getClass());
}
}
class SettingTwo implements Setting {
@Override
public void apply(final ObjectMapper objectMapper) {
System.out.println("Setting applied: " + this.getClass());
}
}
@Configuration
class ApplicationConfiguration {
@Bean
public Setting settingOne() {
return new SettingOne();
}
@Bean
public Setting settingTwo() {
return new SettingTwo();
}
@Bean
public ObjectMapper objectMapper(final List<Setting> settings) {
final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
settings.forEach(setting -> setting.apply(objectMapper));
System.out.printf("Created the [%s] instance of object mapper with [%d] total applied settings.%n", objectMapper, settings.size());
return objectMapper;
}
}
Comment From: snicoll
final BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
You can't cast a BeanFactory
to a BeanRegistry
like this and/or removing bean definitions manually as you do when the context is running.
Comment From: me0x847206
Well, then a question appears: is there any way to correctly remove a bean definition from spring context after a successful bootstrapping process?
Comment From: bclozel
I'm afraid there is not. What would be the expected behavior in that case? Should all the injected singletons be removed as well? How can we ensure that the global state of the system is not unstable or plain invalid?
As Stephane explained, casting the bean factory like this is not supported. We might even make some methods throw exceptions once the context has been refreshed.
Comment From: me0x847206
I see. Thanks for answer.
The cast part may look strange but, as Internet is full with such incorrect solutions then, most probably it is something needed or asked.
What would be the expected behavior in that case? Should all the injected singletons be removed as well? How can we ensure that the global state of the system is not unstable or plain invalid?
Is there a chance then, for spring framework, to contain one more layer of abstraction - somewhere around configuration - with a kind of consumable-scoped
instances of beans which are instantiated and used
during the application bootstrapping process and immediately removed
once used? A kind of short-living instances similar to request scoped but at configuration level only.
The reason I'm asking is that currently some kind of spring based applications become over-populated with beans which are not needed for entire application life but at configuration phase only.