diff --git a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java index 2655f130d77..b89d725d35d 100644 --- a/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/SimpleAliasRegistry.java @@ -59,6 +59,11 @@ public void registerAlias(String name, String alias) { Assert.hasText(alias, "'alias' must not be empty"); synchronized (this.aliasMap) { if (alias.equals(name)) { + String registeredName = this.aliasMap.get(alias); + if (registeredName != null && !registeredName.equals(name) && !allowAliasOverriding()) { + throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + + name + "': It is already registered for name '" + registeredName + "'."); + } this.aliasMap.remove(alias); this.aliasNames.remove(alias); if (logger.isDebugEnabled()) { diff --git a/spring-core/src/test/java/org/springframework/core/SimpleAliasRegistryTests.java b/spring-core/src/test/java/org/springframework/core/SimpleAliasRegistryTests.java index 4c9f909295c..7ad6ca1aaeb 100644 --- a/spring-core/src/test/java/org/springframework/core/SimpleAliasRegistryTests.java +++ b/spring-core/src/test/java/org/springframework/core/SimpleAliasRegistryTests.java @@ -18,6 +18,7 @@ import java.util.Map; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -53,7 +54,7 @@ class SimpleAliasRegistryTests { private static final String ALIAS5 = "alias5"; - private final SimpleAliasRegistry registry = new SimpleAliasRegistry(); + private final SimpleAliasRegistryForTest registry = new SimpleAliasRegistryForTest(); @Test @@ -89,6 +90,89 @@ void aliasChainingWithMultipleAliases() { assertHasAlias(REAL_NAME, ALIAS3); } + @Test + void registerSameAliasTwice_DoesNothing() { + registerAlias(NAME1, ALIAS1); + assertHasAlias(NAME1, ALIAS1); + + registerAlias(NAME1, ALIAS1); + assertHasAlias(NAME1, ALIAS1); + } + + @Test + void registerAliasWithSameName_GivenAliasOverridingAllowed_DoesNothing() { + registry.setAllowAliasOverriding(true); + + registerAlias(NAME1, NAME1); + assertDoesNotHaveAlias(NAME1, NAME1); + } + + @Test + void registerAliasWithSameName_GivenAliasOverridingNotAllowed_DoesNothing() { + registry.setAllowAliasOverriding(false); + + registerAlias(NAME1, NAME1); + assertDoesNotHaveAlias(NAME1, NAME1); + } + + @Test + void registerAliasWithSameName_GivenAliasOverridingAllowed_ThenReplacesExistingAlias() { + // GIVEN + registry.setAllowAliasOverriding(true); + registerAlias(NAME1, ALIAS1); + assertHasAlias(NAME1, ALIAS1); + + // WHEN + registerAlias(ALIAS1, ALIAS1); + + // THEN + assertDoesNotHaveAlias(NAME1, ALIAS1); + assertDoesNotHaveAlias(ALIAS1, ALIAS1); + } + + @Test + void registerAliasWithSameName_GivenAliasOverridingNotAllowed_ThenThrowsException() { + // GIVEN + registry.setAllowAliasOverriding(false); + registerAlias(NAME1, ALIAS1); + assertHasAlias(NAME1, ALIAS1); + + // WHEN/THEN + assertThatIllegalStateException() + .isThrownBy(() -> registerAlias(ALIAS1, ALIAS1)) + .withMessage("Cannot define alias '%s' for name '%s': It is already " + + "registered for name '%s'.", ALIAS1, ALIAS1, NAME1); + } + + @Test + void overrideAlreadyExistingAlias_GivenAliasOverridingAllowed_ThenReplacesExistingAlias() { + // GIVEN + registry.setAllowAliasOverriding(true); + registerAlias(NAME1, ALIAS1); + assertHasAlias(NAME1, ALIAS1); + + // WHEN + registerAlias(NAME2, ALIAS1); + + // THEN + assertDoesNotHaveAlias(NAME1, ALIAS1); + assertHasAlias(NAME2, ALIAS1); + } + + @Test + void overrideAlreadyExistingAlias_GivenAliasOverridingNotAllowed_ThenThrowsException() { + // GIVEN + registry.setAllowAliasOverriding(false); + registerAlias(NAME1, ALIAS1); + assertHasAlias(NAME1, ALIAS1); + + // WHEN/THEN + assertThatIllegalStateException() + .isThrownBy(() -> registerAlias(NAME2, ALIAS1)) + .withMessage("Cannot define alias '%s' for name '%s': It is already " + + "registered for name '%s'.", ALIAS1, NAME2, NAME1); + } + @Test void removeNullAlias() { assertThatNullPointerException().isThrownBy(() -> registry.removeAlias(null)); @@ -343,4 +427,29 @@ public String resolveStringValue(String str) { } } + /** + * An implementation of {@link SimpleAliasRegistry} that allows configuring the alias overriding behaviour. + * By default, {@link SimpleAliasRegistry}'s default behaviour will be used. + */ + private static class SimpleAliasRegistryForTest extends SimpleAliasRegistry { + + private boolean allowAliasOverriding = super.allowAliasOverriding(); + + /** + * Sets the alias overriding behaviour. + * + * @param allowAliasOverriding {@code true} or {@code false} to force a specific behaviour. {@code null} + * to fall back to {@link SimpleAliasRegistry}'s default. + */ + public void setAllowAliasOverriding(@Nullable Boolean allowAliasOverriding) { + this.allowAliasOverriding = allowAliasOverriding != null ? allowAliasOverriding : super.allowAliasOverriding(); + } + + @Override + protected boolean allowAliasOverriding() { + return allowAliasOverriding; + } + + } + }