Overview
As discussed in commit 5d309d5724, alias resolution in SimpleAliasRegistry.resolveAliases()
currently depends on the iteration order of map entries in the aliasMap
which is a ConcurrentHashMap
.
In other words, the order in which aliases are processed depends on their hash code values.
This results in different behavior for the same set of logical alias pairs if the names of some of the aliases are changed in such a way that their hash codes result in a different iteration order for the aliasMap
.
For example, given an existing, working application that relies on aliases and placeholder replacement for such aliases, simply changing the name of one of those aliases may result in failure to start the application.
Possible Solutions
Using a LinkedHashMap
for aliasMap
and aliasCopy
ensures alias processing in the order in which aliases were registered.
However, we currently use a ConcurrentHashMap
for aliasMap
, so we would need to wrap that in Collections.synchronizedMap()
. That works, but we may not want to use a synchronized LinkedHashMap
for the aliasMap
in general.
Thus, another possibility proposed by @jhoeller is to track the names of registered aliases separately:
Along the lines of
this.beanDefinitionNames
vs.this.beanDefinitionMap
inDefaultListableBeanFactory
, we could preserve the iteration order for the keys separately and just use it for theforEach
loop inresolveAliases()
. That would still be a separate data structure, increasing the footprint, but only for the keys then.
Related Issues
-
31353
Comment From: sbrannen
Current proposal for a fix can be viewed in the following feature branch.
https://github.com/spring-projects/spring-framework/compare/main...sbrannen:spring-framework:issues/gh-32024-SimpleAliasRegistry-consistent-alias-processing-order
Comment From: sbrannen
For example, given an existing, working application that relies on aliases and placeholder replacement for such aliases, simply changing the name of one of those aliases may result in failure to start the application.
For anyone reading this issue and wondering how that can happen, the simplest analogy I can come up with is the standard swap
algorithm in computer science.
@Test
void swap() {
String aliasX = "bean1";
String aliasY = "bean2";
String temp = aliasX;
aliasX = aliasY;
aliasY = temp;
assertThat(aliasX).isEqualTo("bean2");
assertThat(aliasY).isEqualTo("bean1");
}
The swap works if aliasX
is processed before aliasY
, but if we change the order of the aliasX = aliasY;
and aliasY = temp;
statements, then aliasX
and aliasY
will both have the value of bean1
.
And that's effectively what can happen currently in SimpleAliasRegistry
.