nacos-spring使用时@PropertySource注解不生效问题
问题
写了一个nacos整合sprig的demo,依赖
<dependencies>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-spring-context</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
代码
package com.zby;
import java.util.concurrent.TimeUnit;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import com.alibaba.nacos.api.annotation.NacosProperties;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.alibaba.nacos.spring.context.annotation.config.EnableNacosConfig;
import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource;
@Configuration
@PropertySource("application.properties")
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "${nacos.server.addr}"))
@NacosPropertySource(dataId = "${nacos.server.dataId}", groupId = "${nacos.server.groupId}", autoRefreshed = true, properties = @NacosProperties(namespace = "${nacos.server.namespace}"))
public class Application {
@NacosValue(value = "${key}", autoRefreshed = true)
public String value;
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Application.class);
Application bean = applicationContext.getBean(Application.class);
while (true) {
TimeUnit.SECONDS.sleep(1);
System.out.println(bean.value);
}
}
}
本地配置application.properties
nacos.server.addr=127.0.0.1:8848
nacos.server.namespace=dev
nacos.server.groupId=zby
nacos.server.dataId=base.properties
nacos创建dev名称空间,创建配置
此时,运行main函数,报错:
二月 02, 2021 7:59:33 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6433a2: startup date [Tue Feb 02 19:59:33 CST 2021]; root of context hierarchy
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
二月 02, 2021 7:59:34 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@39fb3ab6: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,application,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,nacosApplicationContextHolder,annotationNacosInjectedBeanPostProcessor,propertySourcesPlaceholderConfigurer,nacosConfigurationPropertiesBindingPostProcessor,nacosConfigListenerMethodProcessor,nacosPropertySourcePostProcessor,annotationNacosPropertySourceBuilder,nacosValueAnnotationBeanPostProcessor,configServiceBeanBuilder,loggingNacosConfigMetadataEventListener]; root of factory hierarchy
二月 02, 2021 7:59:34 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'application': Injection of @NacosValue dependencies is failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'key' in string value "${key}"
二月 02, 2021 7:59:34 下午 org.springframework.beans.factory.support.DefaultListableBeanFactory destroySingletons
信息: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@39fb3ab6: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,application,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor,nacosApplicationContextHolder,annotationNacosInjectedBeanPostProcessor,propertySourcesPlaceholderConfigurer,nacosConfigurationPropertiesBindingPostProcessor,nacosConfigListenerMethodProcessor,nacosPropertySourcePostProcessor,annotationNacosPropertySourceBuilder,nacosValueAnnotationBeanPostProcessor,configServiceBeanBuilder,loggingNacosConfigMetadataEventListener]; root of factory hierarchy
二月 02, 2021 7:59:34 下午 com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor destroy
信息: class com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor was destroying!
二月 02, 2021 7:59:34 下午 com.alibaba.nacos.spring.beans.factory.annotation.AnnotationNacosInjectedBeanPostProcessor destroy
信息: class com.alibaba.nacos.spring.beans.factory.annotation.AnnotationNacosInjectedBeanPostProcessor was destroying!
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'application': Injection of @NacosValue dependencies is failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'key' in string value "${key}"
at com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor.postProcessPropertyValues(AbstractAnnotationBeanPostProcessor.java:183)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1148)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:191)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:638)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:942)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:73)
at com.zby.Application.main(Application.java:26)
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'key' in string value "${key}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:173)
at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:125)
at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:190)
at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:164)
at org.springframework.context.support.PropertySourcesPlaceholderConfigurer$2.resolveStringValue(PropertySourcesPlaceholderConfigurer.java:167)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:764)
at com.alibaba.nacos.spring.context.annotation.config.NacosValueAnnotationBeanPostProcessor.doGetInjectedBean(NacosValueAnnotationBeanPostProcessor.java:108)
at com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor.getInjectedObject(AbstractAnnotationBeanPostProcessor.java:409)
at com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor$AnnotatedFieldElement.inject(AbstractAnnotationBeanPostProcessor.java:626)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at com.alibaba.spring.beans.factory.annotation.AbstractAnnotationBeanPostProcessor.postProcessPropertyValues(AbstractAnnotationBeanPostProcessor.java:179)
... 12 more
原因nacos-spring-contex:1.0.0依赖的spring是3.x的,3.x时Spring处理@PropertySource注解是等其他注解处理完了,才会把@PropertySource的配置放进enviroment
org.springframework.context.annotation.ConfigurationClassParser.processPropertySource(AnnotationAttributes)
/**
* Process the given <code>@PropertySource</code> annotation metadata.
* @param propertySource metadata for the <code>@PropertySource</code> annotation found
* @throws IOException if loading a property source failed
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
String[] locations = propertySource.getStringArray("value");
int locationCount = locations.length;
if (locationCount == 0) {
throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
}
for (int i = 0; i < locationCount; i++) {
locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
}
ClassLoader classLoader = this.resourceLoader.getClassLoader();
if (!StringUtils.hasText(name)) {
for (String location : locations) {
this.propertySources.push(new ResourcePropertySource(location, classLoader));
}
}
else {
if (locationCount == 1) {
this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
}
else {
CompositePropertySource ps = new CompositePropertySource(name);
for (int i = locations.length - 1; i >= 0; i--) {
ps.addPropertySource(new ResourcePropertySource(locations[i], classLoader));
}
this.propertySources.push(ps);
}
}
}
org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry)
// Handle any @PropertySource annotations
Stack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
if (!parsedPropertySources.isEmpty()) {
if (!(this.environment instanceof ConfigurableEnvironment)) {
logger.warn("Ignoring @PropertySource annotations. " +
"Reason: Environment must implement ConfigurableEnvironment");
}
else {
MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();
while (!parsedPropertySources.isEmpty()) {
envPropertySources.addLast(parsedPropertySources.pop());
}
}
}
解决:spring使用5.x,因为5.x会直接一次性处理好@PropertySource
org.springframework.context.annotation.ConfigurationClassParser.processPropertySource(AnnotationAttributes)
/**
* Process the given <code>@PropertySource</code> annotation metadata.
* @param propertySource metadata for the <code>@PropertySource</code> annotation found
* @throws IOException if loading a property source failed
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
for (String location : locations) {
try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation);
//!!!!!!这里就加入到Environment了
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
// Placeholders not resolvable or resource not found when trying to open it
if (ignoreResourceNotFound) {
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
throw ex;
}
}
}
}
然后,试试自动修改配置,发现没有自动刷新!
原因
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "${nacos.server.addr}"))
@NacosPropertySource(dataId = "${nacos.server.dataId}", groupId = "${nacos.server.groupId}", autoRefreshed = true, properties = @NacosProperties(namespace = "${nacos.server.namespace}"))
名称空间配置在NacosPropertySource注解里,读配置没问题,自动刷新就不能用了
解决
@EnableNacosConfig(globalProperties = @NacosProperties(serverAddr = "${nacos.server.addr}", namespace = "${nacos.server.namespace}"))
@NacosPropertySource(dataId = "${nacos.server.dataId}", groupId = "${nacos.server.groupId}", autoRefreshed = true)
至于为啥会有这么多坑