错误:
Error creating bean with name ‘entity’: Injection of autowired
dependencies failed; nested exception is java.lang.IllegalArgumentException: key can’t be empty
现在我们在本地环境试试会不会出现同样的问题,代码如下:
相关配置:
apollo-client版本 1.5
本篇文章比较水,会慢慢补充完善,希望各位能留下宝贵建议与意见,或者提出一些问题共同讨论。
bootstrap.properties配置文件
app.id=prometheus
apollo.meta=http://127.0.0.1:8080
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces=application,AlertConfig,AlertRules
env=dev
获取apollo 配置的bean
@Component
public class Entity implements Serializable {
@Value("${rulesPath}")
private String rulesPath;
public String getRulesPath() {
return rulesPath;
}
public void setRulesPath(String rulesPath) {
this.rulesPath = rulesPath;
}
}
@Autowired
private Entity entity;
@PostConstruct
private void printLog(){
System.out.println(entity.getRulesPath());
}
结果:
我们试着换一下namespace的顺序
apollo.bootstrap.namespaces=AlertConfig,application,AlertRules
再换一下:
apollo.bootstrap.namespaces=AlertRules,AlertConfig,application
我们试了一下并没有出现同样的问题,那么问题到底出在了哪里?
从实验结果中发现,客户端获取到的值和Apollo 的 namespace有关,获取配置中第一个namespace中配置的数据,
那么问题到底出在哪?
我们尝试将@Value("${}")设置成这样
@Component
@EnableApolloConfig("AlertConfig")
public class Entity implements Serializable {
// @Value("${rulesPath}")
@Value("${}")
private String rulesPath;
public String getRulesPath() {
return rulesPath;
}
public void setRulesPath(String rulesPath) {
this.rulesPath = rulesPath;
}
似乎我们遗漏了一个细节:测试环境中我们的配置 value中有个特殊的东西 ${}
终于发现答案了,原来是@Value对 SpEL表达式进行了解析,先获取到了apollo配置中 rulesPath 对应的值,然后又对值里边的SpEL表达式进行了解析,所以出现了key can’t be empty的错误。
事情到这里就结束了吗?
现在我知道是哪里出了问题,我只需要将数据中的${}删除掉就好了,但是这个锅到底由谁来背呢?
#app.id=prometheus
## set apollo meta server address, adjust to actual address if necessary
#apollo.meta=http://192.168.11.11:8080
#apollo.bootstrap.enabled=true
#apollo.bootstrap.namespaces=application,AlertRules,AlertConfig
#env=dev
@Slf4j
@SpringBootApplication
public class DemoApplication /*implements CommandLineRunner*/ {
@Component
public class Entity implements Serializable {
@Component
@ConfigurationProperties(value = "test")
public class config {
@Value("${key:123}")
private String key;
我们不用apollo了,直接在application.yml 配置文件里写个简单的参数行不行?
然后启动一下:
那么 如果我们在表达式里再写一些key还可以解析吗?
前方高能警告!!!!
O(∩_∩)O 发生了什么!它还可以解析SpEL里的key 但是如果是空的也是没有关系的
距离真相越来越近了
**20201028更新:**那么我们来看看是哪些骚操作导致这个错误
#app.id=prometheus #appid
## set apollo meta server address, adjust to actual address if necessary
#apollo.meta=http://192.168.11.11:8080 #ip
#apollo.bootstrap.enabled=true
#apollo.bootstrap.namespaces=application,AlertRules,AlertConfig
#env=dev #指定环境
看一下获取el 表达式的过程 Spring 源码如下:
org.springframework.util.PropertyPlaceholderHelper#parseStringValue
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
while 循环里会解析获取到的值是否包含 ${} ,一直到不包含为止。
!!!所以这个问题是由于自己对Spring 的了解不够深刻造成的,因此,这个锅我背了
实际上就是@value使用的spel表达式会解析获取到的的内容里的特殊符号,之前是自己想多了,这是spel表达式的问题,如果apollo 配置的value 中包含有特殊的符号之类还是建议用 @ApolloConfig(“namespace”) 获取比较安全
从上边的测试中我们发现同样是使用@value注解,但是由于数据源的不同,导致产生不同的结果.
实验结果:
项目中引入配置文件并且配置文件中配置正确时,项目能够加载,项目中并未使用@EnableApolloConfig注解,直接使用@ApolloConfig注解也可获取。
加载顺序问题:
apollo.bootstrap.enabled:在应用启动阶段,向Spring容器注入被托管的application.properties文件的配置信息。
apollo.bootstrap.enabled=true时打印的结果为配置中心的值
apollo.bootstrap.enabled=false时打印的结果为本地配置文件中的值
20201103更新:这个问题还是记录下来吧;
在另一台电脑开的虚拟机,使用vm网络配置做的转发,在实际使用的过程中会遇到开启Apollo客户端无法连接的情况,但这个原因是由于Apollo的工作模式决定的,因此使用nginx做了反向代理并修改Apollo服务端配置;