Now create a bean date inside the configuration class
@Configuration
public class DateConfig {
@Bean("date")
public Date date(){
return new Date();
}
}
The time is not static, I want to get the current time, how should I override the bean already in the container. At first I thought of using org..cloud.context.scope.refresh.RefreshScope
, but the Spring boot project did not use the Spring Cloud package. This didn't work, so I tried registerBean
to dynamically register a bean with the same name, wondering if it could be overwritten Beans in the container, after all, the so-called container is just a Map, as long as the value on the Map is overwritten by the mechanism, dynamic refresh can be achieved.
private ApplicationContext applicationContext;
@GetMapping("setting/now")
public void dkd(){
GenericApplicationContext gac = (GenericApplicationContext)applicationContext;
gac.registerBean("date",Date.class);
}
When this request is executed, an error is reported directly, and a BeanDefinitionOverrideException is thrown, and the bean cannot be overridden. You can see the reason in DefaultListableBeanFactory.registerBeanDefinition
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
// 省略多余代码
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) { //对于已经存在bean
if (!isAllowBeanDefinitionOverriding()) { //如果allowBeanDefinitionOverriding 这个值为false 这里就会抛出异常
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) { //这里是BeanDefinition
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
//省略。。
Then I found that this allowBeanDefinitionOverriding will initialize this value in SpringApplication when SpringBoot is just initialized, in SpringApplication.prepareContext
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); //设置到DefaultListableBeanFactory中
}
}
if (this.lazyInitialization) { //开启懒加载配置
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
Then see how the configuration file value is set to SpringApplication.allowBeanDefinitionOverriding
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment); //将配置环境bind到属性中
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
//将配置文件绑定到当前属性上
//看起来就有ConfigurationProperties 那味了
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
}
catch (Exception ex) {//略}
}
Add the following configuration in application.properties
spring.main.allow-bean-definition-overriding=true
Re-execute the HTTP request after restarting, no error is reported, and the date bean is re-fetched, and the time becomes the latest value.
experience
It is estimated that there may be some bean conflicts between different components in order to add this configuration, and the later initialization of bean components can override the components already created in Spring. If bean A has been initialized in Spring now, and it is successfully added to the container, then loading and then loading the Spring component also has a Class that inherits bean A, which needs to be added to the container. If there is no mechanism for the same override of beanName, the component will fail at initialization.
It is also worth noting that the registerBean method only deletes the bean cache in the container. How to inject the bean into the object property, this value will not change at this time, you need to manually call beanFactory.getBean("beanName"), Because the initialization will only be performed if the bean does not exist. If there is such a bean refresh scenario you can use @Lookup to generate a proxy method.
@Lookup
public Date initDate() { //这里会将容器内Date类型注入,每次调用方法,重新从容器获取一次
return null;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。