Demand causes
Recently, when implementing the configuration hot update of the configuration center [Nacos in this example], we encountered the configuration bean marked with @ ConfigurationProperties, which involves the refresh problem. Here is a simple implementation method.
thinking
Follow the following questions to solve the problem of configuration refresh
Let's first verify how the configuration bean marked with @ ConfigurationProperties initializes and loads the configuration?
Because this process has the solution of combining configuration data with bean s
Relying on this scheme, we can deduce that the hot update of configuration is nothing more than the combination of new configuration data and bean s, so most solutions can be reused.
analysis
@Loading of ConfigurationProperties
If a bean can be used, the first step is to register it in spring.
Step 1: how to register the configuration bean marked with @ configurationproperties?
According to the way of use
@EnableConfigurationProperties(GovernanceProperties.class)
You can see that the bean is imported through the @ EnableConfigurationProperties annotation.
Further parse @ EnableConfigurationProperties
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(EnableConfigurationPropertiesRegistrar.class) public @interface EnableConfigurationProperties { //Other codes are omitted }
You can see that EnableConfigurationPropertiesRegistrar is further imported through @ Import
The registration of configuration bean s is implemented in EnableConfigurationPropertiesRegistrar
//EnableConfigurationPropertiesRegistrar.java public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerInfrastructureBeans(registry); ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry); //The most critical code in step 1, where the configuration bean is imported getTypes(metadata).forEach(beanRegistrar::register); } //ConfigurationPropertiesBeanRegistrar.java void register(Class<?> type) { MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations .from(type,SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class); //Extract @ ConfigurationProperties annotation metadata before registration register(type, annotation); } void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) { String name = getName(type, annotation); if (!containsBeanDefinition(name)) { //Register bean registerBeanDefinition(name, type, annotation); } }
The registration of the configuration bean is completed
After a bean is registered, to use it, you also need to instantiate the bean and fill [inject] its related properties for the bean. After all these work are completed, it is an "available" bean.
Step 2: how do configuration bean s instantiate and inject configuration data?
Spring's life cycle events provide pre and post events such as bean instantiation and initialization. If you want to inject configuration data into specific fields, you must use these events. The qualified spring events are as follows:
//It is mainly responsible for pre and post events of bean instantiation public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {} //It is mainly responsible for pre and post events of bean initialization public interface BeanPostProcessor {}
After some searching, because there are too many specific implementation classes.
We found ConfigurationPropertiesBindingPostProcessor.java, which completed the configuration data injection into the configuration bean.
The configuration properties binding postprocessor uses bean s to initialize pre and post events
public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor, PriorityOrdered, ApplicationContextAware, InitializingBean { //Other methods are omitted @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { //The core step of step 2 is to complete the injection of configuration data bind(ConfigurationPropertiesBean.get(this.applicationContext, bean, beanName)); return bean; } }
Step 3: the ConfigurationPropertiesBindingPostProcessor is responsible for injecting configuration data. When will the ConfigurationPropertiesBindingPostProcessor be registered?
Review step 1
//EnableConfigurationPropertiesRegistrar.java public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { //The injection of ConfigurationPropertiesBindingPostProcessor is completed registerInfrastructureBeans(registry); ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry); getTypes(metadata).forEach(beanRegistrar::register); } static void registerInfrastructureBeans(BeanDefinitionRegistry registry) { //Register ConfigurationPropertiesBindingPostProcessor ConfigurationPropertiesBindingPostProcessor.register(registry); //Register configuration data injection tool bean BoundConfigurationProperties.register(registry); //Also register a bean, but it's not too important ConfigurationBeanFactoryMetadata.register(registry); }
Therefore, the ConfigurationPropertiesBindingPostProcessor is imported with the @ EnableConfigurationProperties annotation...
step4: specific details of configuring data injection?
//ConfigurationPropertiesBindingPostProcessor.java //Inject in step 1 private ConfigurationPropertiesBinder binder; private void bind(ConfigurationPropertiesBean bean) { if (bean == null || hasBoundValueObject(bean.getName())) { return; } Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '" + bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean"); try { //Use the tool bean injected in step 1 to complete the configuration data binding //The method is not expanded internally. this.binder.bind(bean); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(bean, ex); } }
Source code analysis so far, continue to post
After this.binder.bind(bean)
The source code is meaningless. Here are only a few core parts
Where does the configuration data come from? The configuration data is obtained from the spring environment
Does the injection of configuration data ultimately use reflection? yes
Practice to dynamically refresh @ ConfigurationProperties
By loading @ ConfigurationProperties, we already know how configuration data and bean s are combined. Therefore, we only need to maintain the spring environment when the configuration data is updated, and then call the method of configuration binding again
//Pseudo code private void flushConfigurationProperties(){ String bindBeanName = "org.springframework.boot.context.internalConfigurationPropertiesBinder"; //Get the configuration data and inject the bean. Why not inject it directly? Because it is an internal class and cannot be directly introduced as a class member variable Object bean = applicationContext.getBean(bindBeanName); //beanName of the configuration bean String ConfigurationPropertiesBeanName = "Fill in according to your actual situation"; //Get configuration bean Object ConfigurationPropertiesBean = applicationContext.getBean(ConfigurationPropertiesBeanName); //Get the bound method and execute reflection ReflectionUtils.doWithLocalMethods(bean.getClass(), m->{ if(m.getName().equals("bind")){ m.setAccessible(true); //Packaging metadata ConfigurationPropertiesBean configurationPropertiesBean = ConfigurationPropertiesBean.get(applicationContext,ConfigurationPropertiesBean ,ConfigurationPropertiesBeanName); try { //Refresh complete m.invoke(bean,configurationPropertiesBean); }catch (Exception e){ } } }); }