Skip to content

Commit ad4aa96

Browse files
committed
fix: deduplicate config listeners by identity
Fixes #88
1 parent df7aef2 commit ad4aa96

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Apollo Java 2.5.0
1010
* [Feature Added a new feature to get instance count by namespace.](https://github.com/apolloconfig/apollo-java/pull/103)
1111
* [Feature Support retry in open api client.](https://github.com/apolloconfig/apollo-java/pull/105)
1212
* [Support Spring Boot 4.0 bootstrap context package relocation for apollo-client-config-data](https://github.com/apolloconfig/apollo-java/pull/115)
13+
* [Fix change listener de-duplication by identity to avoid stale property names cache in Spring Cloud bootstrap dual-context initialization](https://github.com/apolloconfig/apollo-java/pull/121)
1314

1415
------------------
1516
All issues and pull requests are [here](https://github.com/apolloconfig/apollo-java/milestone/5?closed=1)

apollo-client/src/main/java/com/ctrip/framework/apollo/internals/AbstractConfig.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void addChangeListener(ConfigChangeListener listener, Set<String> interes
9999

100100
@Override
101101
public void addChangeListener(ConfigChangeListener listener, Set<String> interestedKeys, Set<String> interestedKeyPrefixes) {
102-
if (!m_listeners.contains(listener)) {
102+
if (!containsListenerInstance(listener)) {
103103
m_listeners.add(listener);
104104
if (interestedKeys != null && !interestedKeys.isEmpty()) {
105105
m_interestedKeys.put(listener, Sets.newHashSet(interestedKeys));
@@ -114,7 +114,12 @@ public void addChangeListener(ConfigChangeListener listener, Set<String> interes
114114
public boolean removeChangeListener(ConfigChangeListener listener) {
115115
m_interestedKeys.remove(listener);
116116
m_interestedKeyPrefixes.remove(listener);
117-
return m_listeners.remove(listener);
117+
for (ConfigChangeListener addedListener : m_listeners) {
118+
if (addedListener == listener) {
119+
return m_listeners.remove(addedListener);
120+
}
121+
}
122+
return false;
118123
}
119124

120125
@Override
@@ -608,4 +613,13 @@ List<ConfigChange> calcPropertyChanges(String appId, String namespace, Propertie
608613

609614
return changes;
610615
}
616+
617+
private boolean containsListenerInstance(ConfigChangeListener listener) {
618+
for (ConfigChangeListener configChangeListener : m_listeners) {
619+
if (configChangeListener == listener) {
620+
return true;
621+
}
622+
}
623+
return false;
624+
}
611625
}

apollo-client/src/test/java/com/ctrip/framework/apollo/internals/AbstractConfigTest.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.ctrip.framework.apollo.internals;
1818

1919
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertNotSame;
21+
import static org.junit.Assert.assertTrue;
2022
import static org.mockito.ArgumentMatchers.any;
2123
import static org.mockito.ArgumentMatchers.eq;
2224
import static org.mockito.Mockito.spy;
@@ -28,6 +30,7 @@
2830
import com.ctrip.framework.apollo.enums.PropertyChangeType;
2931
import com.ctrip.framework.apollo.model.ConfigChange;
3032
import com.ctrip.framework.apollo.model.ConfigChangeEvent;
33+
import com.ctrip.framework.apollo.spring.config.CachedCompositePropertySource;
3134
import com.google.common.util.concurrent.SettableFuture;
3235
import java.util.Collections;
3336
import java.util.HashMap;
@@ -122,6 +125,36 @@ public void onChange(ConfigChangeEvent changeEvent) {
122125
verify(configChangeListener2, times(1)).onChange(eq(configChangeEvent));
123126
}
124127

128+
@Test
129+
public void testFireConfigChange_twoCachedCompositePropertySourcesWithSameName_shouldBothBeNotified()
130+
throws InterruptedException {
131+
AbstractConfig abstractConfig = new ErrorConfig();
132+
final String namespace = "app-namespace-listener-equals";
133+
final String key = "great-key";
134+
135+
final CountingCachedCompositePropertySource listener1 =
136+
new CountingCachedCompositePropertySource("ApolloBootstrapPropertySources");
137+
final CountingCachedCompositePropertySource listener2 =
138+
new CountingCachedCompositePropertySource("ApolloBootstrapPropertySources");
139+
140+
assertNotSame(listener1, listener2);
141+
assertTrue(listener1.equals(listener2));
142+
143+
abstractConfig.addChangeListener(listener1, Collections.singleton(key));
144+
abstractConfig.addChangeListener(listener2, Collections.singleton(key));
145+
146+
Map<String, ConfigChange> changes = new HashMap<>();
147+
changes.put(key,
148+
new ConfigChange(someAppId, namespace, key, "old-value", "new-value", PropertyChangeType.MODIFIED));
149+
150+
abstractConfig.fireConfigChange(someAppId, namespace, changes);
151+
152+
Thread.sleep(100);
153+
154+
assertEquals(1, listener1.changeCount.get());
155+
assertEquals(1, listener2.changeCount.get());
156+
}
157+
125158
@Test
126159
public void testFireConfigChange_changes_notify_once()
127160
throws ExecutionException, InterruptedException, TimeoutException {
@@ -188,4 +221,18 @@ public ConfigSourceType getSourceType() {
188221
throw new UnsupportedOperationException();
189222
}
190223
}
191-
}
224+
225+
private static class CountingCachedCompositePropertySource extends CachedCompositePropertySource {
226+
private final AtomicInteger changeCount = new AtomicInteger();
227+
228+
private CountingCachedCompositePropertySource(String name) {
229+
super(name);
230+
}
231+
232+
@Override
233+
public void onChange(ConfigChangeEvent changeEvent) {
234+
changeCount.incrementAndGet();
235+
super.onChange(changeEvent);
236+
}
237+
}
238+
}

0 commit comments

Comments
 (0)