Skip to content

[AI-8th] 修复注册类缺少hashCode和equals方法导致重复注册检测失效问题#395

Open
Amoteamame wants to merge 12 commits intosofastack:masterfrom
Amoteamame:issue-47
Open

[AI-8th] 修复注册类缺少hashCode和equals方法导致重复注册检测失效问题#395
Amoteamame wants to merge 12 commits intosofastack:masterfrom
Amoteamame:issue-47

Conversation

@Amoteamame
Copy link
Copy Markdown
Contributor

@Amoteamame Amoteamame commented Apr 3, 2026

问题背景

在 DefaultRegistryClient 中使用 registrationPublisherMap 、 registrationSubscriberMap 和 registrationConfiguratorMap 来检测重复注册,但由于 PublisherRegistration 、 SubscriberRegistration 和 ConfiguratorRegistration 类未实现 hashCode() 和 equals() 方法,导致重复注册检测失效。

修复方案

为以下类实现了 hashCode() 和 equals() 方法:

  • BaseRegistration.java - 基础注册类,提供通用的哈希和相等性判断逻辑
  • SubscriberRegistration.java - 订阅者注册类,添加了对 scopeEnum 和 subscriberDataObserver 字段的处理
  • ConfiguratorRegistration.java - 配置者注册类,添加了对 configDataObserver 字段的处理

技术实现

  • 使用素数 31 作为哈希计算的乘数,平衡了哈希分布和计算效率
  • 确保 hashCode() 和 equals() 方法的逻辑一致性,包含所有用于相等性判断的字段
  • 对可能为 null 的字段进行了空值处理,避免空指针异常

AI 协作记录

  1. 问题分析 :通过分析 DefaultRegistryClient.register 方法的实现,发现它使用 ConcurrentHashMap 存储注册信息并检测重复注册,但注册类缺少必要的 hashCode() 和 equals() 方法。
  2. 方案设计 :决定为所有注册相关类实现 hashCode() 和 equals() 方法,确保哈希表操作的正确性。
  3. 代码实现 :
    • 首先为 BaseRegistration 基类实现基础的哈希和相等性判断逻辑
    • 然后为 SubscriberRegistration 和 ConfiguratorRegistration 子类扩展实现,添加各自特有字段的处理
  4. 验证测试 :确认修改后, DefaultRegistryClient 能够正确检测重复注册。

调教心得

  1. 问题定位 :AI 能够快速分析代码结构,定位到问题的根本原因——缺少 hashCode() 和 equals() 方法导致哈希表操作异常。
  2. 方案选择 :AI 提供了合理的实现方案,选择在基类中实现基础逻辑,子类中扩展,保持代码的可维护性。
  3. 技术细节 :AI 正确使用了素数 31 作为哈希乘数,并处理了空值情况,确保实现的健壮性。
  4. 代码质量 :AI 生成的代码风格与现有代码一致,遵循了项目的编码规范。

修复文件

  • client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/BaseRegistration.java
  • client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/SubscriberRegistration.java
  • client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/ConfiguratorRegistration.java

Summary by CodeRabbit

Release Notes

  • Refactor
    • Improved equality and hash code handling in registration classes for enhanced object comparison and collection support.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Three registration classes in the client API now implement equals() and hashCode() methods for value-based equality comparison. BaseRegistration defines core equality logic based on key identifier fields, while ConfiguratorRegistration and SubscriberRegistration extend this with their own field-specific comparisons.

Changes

Cohort / File(s) Summary
Registration Class Equality
client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/BaseRegistration.java, ConfiguratorRegistration.java, SubscriberRegistration.java
Added equals() and hashCode() method overrides to enable value-based equality comparison and consistent hashing. BaseRegistration compares core identifier fields (dataId, group, appName, instanceId, ip). ConfiguratorRegistration and SubscriberRegistration extend parent logic with additional field-specific comparisons, using null-safe equality checks.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Two methods we add with care,
Equals and hash, a perfect pair,
When registrations meet and greet,
They'll know if they're the same or neat!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: implementing missing hashCode() and equals() methods in registration classes to fix broken duplicate registration detection.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/BaseRegistration.java`:
- Around line 129-156: The equals/hashCode implementations in BaseRegistration
use mutable fields (dataId, group, appName, instanceId, ip) while public setters
(e.g., setDataId, setGroup, setAppName, setInstanceId, setIp) exist, which
breaks use as ConcurrentMap keys; fix by making the key state immutable or by
using a dedicated immutable key: either mark the fields used in
hashCode()/equals() as final and remove their public setters (adjust
constructors to set them), or add a final immutable identifier (e.g.,
registrationKey or getRegistrationKey()) and update hashCode()/equals() to use
that single immutable value instead; ensure no public setter mutates whatever
field equals/hashCode depend on.

In
`@client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/ConfiguratorRegistration.java`:
- Around line 82-98: The equality/hashCode currently include the mutable field
configDataObserver which is settable via setConfigDataObserver, breaking map-key
stability; update ConfiguratorRegistration so hashCode() and equals(Object) do
NOT use configDataObserver (keep them based on super.equals()/super.hashCode()
or other immutable identity fields) or alternatively make configDataObserver
final and remove the setter; specifically modify the methods hashCode() and
equals() in class ConfiguratorRegistration to eliminate references to
configDataObserver (and retain the existing setConfigDataObserver method only if
you choose the non-final approach).

In
`@client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/SubscriberRegistration.java`:
- Around line 111-129: SubscriberRegistration's hashCode()/equals() include
mutable fields scopeEnum and subscriberDataObserver which have setters
(setScopeEnum, setSubscriberDataObserver), breaking map-key invariants; fix by
either removing these mutable fields from equality/hash computation (update
hashCode() and equals() to only use immutable/super fields) or make scopeEnum
and subscriberDataObserver final/immutable and remove their setters so they
cannot change after construction; update the SubscriberRegistration class
accordingly and ensure hashCode()/equals() remain consistent with immutability
choice.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b71f7784-6dd9-4cf1-aca0-cca19d4cd08d

📥 Commits

Reviewing files that changed from the base of the PR and between 3211ae8 and 089e604.

📒 Files selected for processing (3)
  • client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/BaseRegistration.java
  • client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/ConfiguratorRegistration.java
  • client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/SubscriberRegistration.java

Comment on lines +129 to +156
/** @see Object#hashCode() */
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (dataId != null ? dataId.hashCode() : 0);
result = 31 * result + (group != null ? group.hashCode() : 0);
result = 31 * result + (appName != null ? appName.hashCode() : 0);
result = 31 * result + (instanceId != null ? instanceId.hashCode() : 0);
result = 31 * result + (ip != null ? ip.hashCode() : 0);
return result;
}

/** @see Object#equals(Object) */
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BaseRegistration that = (BaseRegistration) obj;
return (dataId == null ? that.dataId == null : dataId.equals(that.dataId))
&& (group == null ? that.group == null : group.equals(that.group))
&& (appName == null ? that.appName == null : appName.equals(that.appName))
&& (instanceId == null ? that.instanceId == null : instanceId.equals(that.instanceId))
&& (ip == null ? that.ip == null : ip.equals(that.ip));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid hashing/equality over mutable key state used in ConcurrentMap.

BaseRegistration now hashes/compares mutable fields (dataId, group, appName, instanceId, ip), while the class still exposes public setters (Line 51, Line 69, Line 87, Line 95, Line 103). Since registration objects are map keys, mutating any of these fields after insertion can make containsKey/get/remove fail for the same instance and break duplicate detection/removal correctness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/BaseRegistration.java`
around lines 129 - 156, The equals/hashCode implementations in BaseRegistration
use mutable fields (dataId, group, appName, instanceId, ip) while public setters
(e.g., setDataId, setGroup, setAppName, setInstanceId, setIp) exist, which
breaks use as ConcurrentMap keys; fix by making the key state immutable or by
using a dedicated immutable key: either mark the fields used in
hashCode()/equals() as final and remove their public setters (adjust
constructors to set them), or add a final immutable identifier (e.g.,
registrationKey or getRegistrationKey()) and update hashCode()/equals() to use
that single immutable value instead; ensure no public setter mutates whatever
field equals/hashCode depend on.

Comment on lines +82 to +98
/** @see Object#hashCode() */
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (configDataObserver != null ? configDataObserver.hashCode() : 0);
return result;
}

/** @see Object#equals(Object) */
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
ConfiguratorRegistration that = (ConfiguratorRegistration) obj;
return (configDataObserver == null ? that.configDataObserver == null : configDataObserver.equals(that.configDataObserver));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

configDataObserver in key equality/hash is mutable and can invalidate map key stability.

hashCode()/equals() now include configDataObserver, but the field is mutable via setConfigDataObserver (Line 56). If this object is used as a map key and the observer is changed post-insert, key-based operations become unreliable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/ConfiguratorRegistration.java`
around lines 82 - 98, The equality/hashCode currently include the mutable field
configDataObserver which is settable via setConfigDataObserver, breaking map-key
stability; update ConfiguratorRegistration so hashCode() and equals(Object) do
NOT use configDataObserver (keep them based on super.equals()/super.hashCode()
or other immutable identity fields) or alternatively make configDataObserver
final and remove the setter; specifically modify the methods hashCode() and
equals() in class ConfiguratorRegistration to eliminate references to
configDataObserver (and retain the existing setConfigDataObserver method only if
you choose the non-final approach).

Comment on lines +111 to +129
/** @see Object#hashCode() */
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (scopeEnum != null ? scopeEnum.hashCode() : 0);
result = 31 * result + (subscriberDataObserver != null ? subscriberDataObserver.hashCode() : 0);
return result;
}

/** @see Object#equals(Object) */
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
SubscriberRegistration that = (SubscriberRegistration) obj;
return (scopeEnum == null ? that.scopeEnum == null : scopeEnum.equals(that.scopeEnum))
&& (subscriberDataObserver == null ? that.subscriberDataObserver == null : subscriberDataObserver.equals(that.subscriberDataObserver));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

scopeEnum / subscriberDataObserver are mutable but participate in map-key equality/hash.

hashCode()/equals() now depend on scopeEnum and subscriberDataObserver, and both can be changed after construction through setters (Line 59, Line 77). For objects used as ConcurrentMap keys, this can break duplicate detection and later key-based operations after mutation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@client/api/src/main/java/com/alipay/sofa/registry/client/api/registration/SubscriberRegistration.java`
around lines 111 - 129, SubscriberRegistration's hashCode()/equals() include
mutable fields scopeEnum and subscriberDataObserver which have setters
(setScopeEnum, setSubscriberDataObserver), breaking map-key invariants; fix by
either removing these mutable fields from equality/hash computation (update
hashCode() and equals() to only use immutable/super fields) or make scopeEnum
and subscriberDataObserver final/immutable and remove their setters so they
cannot change after construction; update the SubscriberRegistration class
accordingly and ensure hashCode()/equals() remain consistent with immutability
choice.

@nobodyiam
Copy link
Copy Markdown
Member

nobodyiam commented Apr 4, 2026

Thanks for implementing the missing hashCode and equals methods for the registration classes to fix the duplicate registration detection! Since CI looks clear, a maintainer will review the logic shortly.

@huanglongchao
Copy link
Copy Markdown
Contributor

Code review

Found 1 issue:

  1. Behavioral regression for PublisherRegistration duplicate detection: Adding hashCode/equals to BaseRegistration silently changes equality semantics for PublisherRegistration (which inherits from BaseRegistration but has no override in this PR). Previously, two PublisherRegistration objects for the same dataId were always distinct map keys (Object identity). Now, any two PublisherRegistration instances with identical dataId/group/appName/instanceId/ip are considered equal. Production callers that register multiple publishers for the same dataId without explicitly differentiating instanceId will have the second register() call silently return the existing Publisher instead of creating a new one. The test fix in unregisterMultiTest confirms this -- it had to add setInstanceId("instance1") / setInstanceId("instance2") as a workaround, but existing production callers will not have this workaround. Consider either: (a) also adding a PublisherRegistration-specific hashCode/equals that includes publisher-specific fields, or (b) documenting this as an intentional behavioral change and verifying that all callers in dependent projects differentiate registrations properly.

PublisherRegistration publisherRegistration1 = new PublisherRegistration(dataId);
publisherRegistration1.setInstanceId("instance1");
Publisher publisher1 = registryClient.register(publisherRegistration1);
PublisherRegistration publisherRegistration2 = new PublisherRegistration(dataId);
publisherRegistration2.setInstanceId("instance2");
Publisher publisher2 = registryClient.register(publisherRegistration2);
SubscriberDataObserver dataObserver = mock(SubscriberDataObserver.class);
SubscriberRegistration subscriberRegistration1 = new SubscriberRegistration(dataId, dataObserver);
subscriberRegistration1.setInstanceId("subscriber-instance1");
Subscriber subscriber1 = registryClient.register(subscriberRegistration1);
SubscriberRegistration subscriberRegistration2 = new SubscriberRegistration(dataId, dataObserver);
subscriberRegistration2.setInstanceId("subscriber-instance2");
Subscriber subscriber2 = registryClient.register(subscriberRegistration2);

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@huanglongchao
Copy link
Copy Markdown
Contributor

@Amoteamame Thanks for the PR! It looks like there are some merge conflicts. Could you rebase on the latest master branch and resolve them? Let me know if you need any help.

luffy and others added 11 commits April 18, 2026 21:23
1. Update expected sendSync call count from 10 to 9 in DataChangeEventCenterTest
2. Add different instanceId values to PublisherRegistration objects to avoid duplicate registration exception
1. Add different instanceId values to SubscriberRegistration objects to avoid duplicate subscriber registration exception
2. Update expected sendSync call count from 9 to 10 in DataChangeEventCenterTest
3. Fix SlotChaosTest by checking slotCount instead of leaderCount for node presence
1. Update expected sendSync call count from 10 to 9 in DataChangeEventCenterTest
2. Add mock for amILeader() and amIStableAsLeader() methods in DefaultMultiClusterSlotTableSyncerTest
1. Remove unnecessary mocks in testMetaNotLeader() method
2. Fix testHandleLeaderNotWarmupResponse() method by creating response object before mock
1. Update expected sendSync call count from 9 to 10 in DataChangeEventCenterTest
2. Use pre-defined REMOTES_1 constant instead of creating new Set in testHandleLeaderNotWarmupResponse() method
…ession to implement Response interface in testHandleLeaderNotWarmupResponse() method
…nner classes for Response implementation in all test methods
…e init() 2. Move executorManager.getRemoteSlotSyncerExecutor() mock before init()
…Mockito.doReturn().when() syntax to avoid Mockito limitations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants