Debugging Spring Boot + Nacos Config Center: Why @NacosConfigurationProperties Fails to Load Local Bean Properties
The article investigates why a Spring Boot application using Nacos as a configuration center does not inject bean properties defined locally when the Nacos console lacks those entries, by walking through the integration steps and analyzing the relevant source code.
First, the author describes the problem: when a Spring Boot project integrates Nacos as a configuration center and uses the @NacosConfigurationProperties annotation to inject bean properties, the properties are not injected if the Nacos console does not contain the corresponding configuration, even though the same properties are defined in the local application.yml file.
1. Integrating Spring Boot with Nacos
The integration requires only three steps:
Add dependency :
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.10</version>
</dependency>Add configuration to application.yml (example shows Nacos server address, namespace, data‑id, and a local user1 property block).
Usage example with a bean annotated by @NacosConfigurationProperties:
@Data
@Component
@NacosConfigurationProperties(prefix = "user1", autoRefreshed = true, dataId = "example.properties")
public class User {
private String name;
private Integer age;
private Long id;
}Running the application shows that the bean fields remain empty because the Nacos console lacks the configuration, even though the local file defines them.
2. Source Code Analysis
The author follows Spring Boot’s auto‑configuration mechanism. The nacos-config-spring-boot-starter JAR contains a spring.factories file that should register NacosConfigApplicationContextInitializer, but in version 0.2.10 this entry is commented out, so the initializer is not loaded during the early auto‑configuration phase.
During SpringApplication.run(), the prepareEnvironment() method invokes NacosConfigEnvironmentProcessor.postProcessEnvironment(), which manually adds NacosConfigApplicationContextInitializer to the list of initializers.
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
application.addInitializers(new NacosConfigApplicationContextInitializer(this));
// ... load config if enabled
}The applyInitializers() method of SpringApplication then calls initialize() on each ApplicationContextInitializer. The Nacos initializer registers several beans, including NacosBootConfigurationPropertiesBinder via NacosConfigBootBeanDefinitionRegistrar:
@Configuration
public class NacosConfigBootBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// register NacosBootConfigurationPropertiesBinder bean
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// no additional definitions
}
}The binder is later used by NacosConfigurationPropertiesBindingPostProcessor, which implements BeanPostProcessor. In its postProcessBeforeInitialization method it detects beans annotated with @NacosConfigurationProperties and calls bind():
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
NacosConfigurationProperties anno = findAnnotation(bean.getClass(), NacosConfigurationProperties.class);
if (anno != null) {
bind(bean, beanName, anno);
}
return bean;
}The actual binding happens in NacosBootConfigurationPropertiesBinder.doBind(). It creates a new StandardEnvironment instance, loads the property source from Nacos, adds it to the environment, and then uses Binder to bind the properties to the bean:
protected void doBind(Object bean, String beanName, String dataId, String groupId,
String configType, NacosConfigurationProperties properties, String content,
ConfigService configService) {
synchronized (this) {
String name = "nacos-bootstrap-" + beanName;
NacosPropertySource propertySource = new NacosPropertySource(name, dataId, groupId, content, configType);
environment.getPropertySources().addLast(propertySource);
Binder binder = Binder.get(environment);
ResolvableType type = getBeanType(bean, beanName);
Bindable<?> target = Bindable.of(type).withExistingValue(bean);
binder.bind(properties.prefix(), target);
// publish events and clean up
environment.getPropertySources().remove(name);
}
}Crucially, the environment used here is a freshly created StandardEnvironment (shown in the source as
private StandardEnvironment environment = new StandardEnvironment();), not the one built from the Spring application’s application.yml. Therefore, the local configuration file is never consulted during the binding process, which explains why the bean properties remain unset when the Nacos console lacks them.
Conclusion
The missing injection is caused by the Nacos auto‑configuration using an isolated StandardEnvironment that only contains properties fetched from the Nacos server. Local application.yml entries are ignored, so when the remote configuration is absent the bean receives no values. To make local defaults work, one would need to modify the binder to merge the application’s environment or ensure the initializer is loaded early enough to incorporate the existing environment.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
