hengyuss commented on issue #6366:
URL: https://github.com/apache/shenyu/issues/6366#issuecomment-4766908358

   # ShenYu Client-Java 独立仓库端到端验证报告 / Split Repository End-to-End Verification 
Report
   
   ---
   
   ## 中文版
   
   > 我在本地已经用 Java 8 编译 shenyu-client-java,跑通了主仓库 shenyu-examples-springmvc 
需要验证的流程。
   
   ### 基本信息
   
   | 项目 | 值 |
   |------|-----|
   | Main Repo Commit | `7b5e23375d216711506933dc33f4bd4191d32058` 
(apache/shenyu master) |
   | Client Repo Commit | `7f3c5724403dcbb3b278ca7647d157684bb0951e` 
(shenyu-client-java) |
   | Client Version | `2.7.0.1-jdk8-SNAPSHOT` |
   | JDK (Admin/Bootstrap) | OpenJDK 17.0.19 |
   | JDK (Client Example) | OpenJDK 21.0.11 |
   | Admin Profile | `h2` (standalone) |
   | Sync Strategy | WebSocket |
   | Register Strategy | HTTP |
   
   ---
   
   ### 集成适配修改及原因
   
   为使 `shenyu-examples-springmvc` 使用独立客户端产物,需做以下适配:
   
   **1. pom.xml — 替换依赖版本**
   
   ```xml
   <!-- 原 -->
   <version>${project.version}</version>
   <!-- 改为 -->
   <version>2.7.0.1-jdk8-SNAPSHOT</version>
   ```
   
   **原因**:原 example 依赖的 `shenyu-client-springmvc` 
来自主仓库,版本与主仓库一致(`2.7.1-SNAPSHOT`)。替换为独立客户端仓库后,其版本号为 
`2.7.0.1-jdk8-SNAPSHOT`,必须显式指定才能从本地 Maven 仓库拉取到正确的 artifact。
   
   **2. context/shenyu.xml — 类包名迁移**
   
   | 类 | 原 FQN (shenyu main) | 新 FQN (shenyu-client-java) |
   |----|----------------------|-----------------------------|
   | `ShenyuRegisterCenterConfig` | 
`org.apache.shenyu.register.common.config.ShenyuRegisterCenterConfig` | 
`org.apache.shenyu.client.core.register.config.ShenyuRegisterCenterConfig` |
   | `ShenyuClientConfig` | 
`org.apache.shenyu.register.common.config.ShenyuClientConfig` | 
`org.apache.shenyu.client.core.register.config.ShenyuClientConfig` |
   | `ClientPropertiesConfig` | 
`org.apache.shenyu.register.common.config.ShenyuClientConfig.ClientPropertiesConfig`
 | 
`org.apache.shenyu.client.core.register.config.ShenyuClientConfig.ClientPropertiesConfig`
 |
   | `HttpClientRegisterRepository` | 
`org.apache.shenyu.register.client.http.HttpClientRegisterRepository` | 
`org.apache.shenyu.client.core.register.HttpClientRegisterRepository` |
   
   
**原因**:这是最核心的适配工作。主仓库中注册相关类分散在多个模块(`shenyu-register-common`、`shenyu-register-client-http`
 等),客户端拆分为独立仓库后,这些模块被合并进 `shenyu-client-core`,包名随之收敛。独立仓库不再依赖主仓库的 
`shenyu-register-center` 模块,因此注册中心相关的类被重新组织到客户端自己的包结构下。XML 
配置是硬编码类全限定名的,所以必须手动更新。
   
   **3. context/shenyu.xml — 新增 `discoveryLocalMode` 属性**
   
   ```xml
   <entry key="discoveryLocalMode" value="true"/>
   ```
   
   **原因**:`AbstractContextRefreshedEventListener.onApplicationEvent()` 中,只有当 
`isDiscoveryLocalMode=true` 时才会调用 `publishEvent(buildURIRegisterDTO(...))` 发布 
URI 注册事件,进而由 `ShenyuClientURIExecutorSubscriber` 消费并完成 Discovery 实例注册。主仓库的 
Spring Boot Starter(`ShenyuSpringMvcClientConfiguration`)会根据 
`shenyu.discovery.type` 配置(默认 `"local"`)自动设置 `discoveryLocalMode=true`,因此使用 
Starter 的用户无需手动配置。但本 example 使用 XML 配置方式(`@ImportResource`),不经过 Starter 
自动配置,所以必须在 XML 中手动添加此属性。
   
   ---
   
   ### 验证结果
   
   | 验证项 | 结果 | 说明 |
   |--------|------|------|
   | Spring MVC 注册 | **PASS** | Admin 日志收到 `appName=springmvc` 的 META_DATA 
注册,路径包括 
`/mvc/order/save`、`/mvc/order/findById`、`/mvc/test/**`、`/mvc/upload/upload` 等 |
   | Beat 心跳 | **PASS** | Bootstrap 日志持续收到 `DISCOVER_UPSTREAM` REFRESH 事件(每 
30s),上游 `172.19.0.1:8289` 状态正常 |
   | Discovery 实例注册 | **PASS(需手动补设 instanceInfo 后方可通过)** | Admin 创建了 `/mvc` 
选择器,Discovery 上游注册成功。但原始客户端代码中 `URIRegisterDTO` 缺少 `instanceInfo` 字段且 
`sendHeartbeat` 未设置该值,需手动补加后实例才能正常注册 |
   | Admin 可见路由数据 | **PASS** | WebSocket 同步正常,Bootstrap 成功订阅所有 
PLUGIN/SELECTOR/RULE/META_DATA/DISCOVER_UPSTREAM 数据 |
   | Bootstrap 消费路由 | **PASS** | `divide` 插件加载 `/mvc` 选择器数据,上游代理目标可达 |
   | ShutdownHook 接管 | **FAIL** | 详见下方不兼容问题 |
   
   ---
   
   ### 不兼容问题
   
   **[ISSUE-1] `InaccessibleObjectException` — ShutdownHook 反射访问 JPMS 限制**
   
   - **严重程度**: 低(不影响核心注册/路由功能,仅影响优雅关机时的 hook 接管)
   - **位置**: `ShenyuClientShutdownHook.java:89`
   - **异常栈**:
     ```
     java.lang.reflect.InaccessibleObjectException: Unable to make field 
private static
      java.util.IdentityHashMap java.lang.ApplicationShutdownHooks.hooks 
accessible:
      module java.base does not "opens java.lang" to unnamed module
     ```
   - **原因**: 代码通过反射访问 `java.lang.ApplicationShutdownHooks.hooks` 私有字段。自 JDK 16 
起,`--illegal-access` 默认为 deny,`java.base` 模块不再对 unnamed module 
开放内部包。**注意:客户端编译目标为 JDK 8,但 JPMS 强封装是运行时 JVM 行为,与 class 文件编译版本无关。此问题仅在运行时 JVM 为 
JDK 16+ 时触发,在 JDK 8-15 运行环境下不受影响。本次测试环境 example 运行于 JDK 21,故触发了此异常。**主仓库同样存在此问题。
   - **变通方案**: 启动参数加 `--add-opens java.base/java.lang=ALL-UNNAMED`
   - **建议修复**: 在 `TakeoverOtherHooksThread.run()` 中,当 `hooks` 为 
null(反射获取失败)时应跳过 hook 接管逻辑而非抛 NPE(`Objects.requireNonNull(hooks)` 在第 96 行会抛 NPE)
   
   **[ISSUE-2] `instanceInfo` 缺失 — `URIRegisterDTO` 缺少字段 + `sendHeartbeat` 
缺少赋值,导致实例无法注册**
   
   - **严重程度**: 高(导致实例注册失败,影响核心功能,阻塞 release-ready 判断)
   - **位置**: `URIRegisterDTO.java` 及 `ShenyuClientURIExecutorSubscriber.java` 的 
`sendHeartbeat` 方法
   - **主仓库代码**:
     - 
`URIRegisterDTO`(`org.apache.shenyu.register.common.dto.URIRegisterDTO`)包含 
`instanceInfo` 字段
     - `ShenyuClientURIExecutorSubscriber.sendHeartbeat()` 中调用 
`uriRegisterDTO.setInstanceInfo(SystemInfoUtils.getSystemInfo())` 动态设置系统信息
   - **客户端仓库原始代码**:
     - 
`URIRegisterDTO`(`org.apache.shenyu.client.core.dto.URIRegisterDTO`)**完全没有 
`instanceInfo` 字段**
     - `ShenyuClientURIExecutorSubscriber.sendHeartbeat()` 中也**没有设置 
`instanceInfo` 的逻辑**,仅直接调用 
`shenyuClientRegisterRepository.sendHeartbeat(uriRegisterDTO)`
   - **原因**: 独立客户端仓库不依赖主仓库的 `shenyu-common` 模块,而 
`SystemInfoUtils`(`org.apache.shenyu.common.utils.SystemInfoUtils`)位于该模块中,依赖 
`oshi` 库来动态获取系统架构、操作系统、内存、处理器等信息。由于去除了 `shenyu-common` 依赖,`instanceInfo` 
字段及其赋值逻辑在拆分时被一同遗漏,导致 Admin 端因缺少实例信息而无法完成实例注册。
   - **验证时变通**: 手动在 `URIRegisterDTO` 中添加 `instanceInfo` 字段及 getter/setter,并在 
`sendHeartbeat` 中硬编码 `instanceInfo` 为固定 JSON 字符串后,实例注册成功。
   - **建议修复**: 需要同时补全两层缺失:
     1. 在 `URIRegisterDTO` 中添加 `instanceInfo` 字段及其 getter/setter/Builder 方法
     2. 在 `shenyu-client-core` 中自行实现一个轻量版的 `SystemInfoUtils`(可仅使用 JDK 自带的 
`ManagementFactory` + `com.sun.management.OperatingSystemMXBean` 获取 
arch、availableProcessors、totalMemorySize 等基本信息,无需引入 `oshi` 依赖),并在 
`sendHeartbeat` 中调用其填充 `instanceInfo`
     3. 或提供一个可选的 `oshi` 依赖模块供用户选择引入
   
   ---
   
   ### 结论
   
   独立客户端仓库 `shenyu-client-java` 版本 `2.7.0.1-jdk8-SNAPSHOT` 与主仓库 Admin/Bootstrap 
基本兼容,Spring MVC 注册、心跳、Discovery 注册(需手动补设 `instanceInfo` 后)均验证通过。
   
   主要集成成本在于 **包名迁移**(`org.apache.shenyu.register.*` → 
`org.apache.shenyu.client.core.register.*`),使用 XML 配置的用户需手动更新类引用;使用 Spring Boot 
Starter 自动配置的用户不受影响。
   
   发现两个运行时问题:
   1. **ShutdownHook 在 JDK 16+ 运行时下的反射限制**(低严重度),属于已有问题(主仓库也存在),不影响发布判断;
   2. **`URIRegisterDTO` 缺少 `instanceInfo` 字段 + `sendHeartbeat` 
缺少赋值逻辑,导致实例无法注册**(高严重度),根本原因是独立仓库不再依赖 `shenyu-common` 中的 `SystemInfoUtils`(依赖 
`oshi`),拆分时字段和赋值逻辑被一同遗漏。必须在客户端仓库中补全 `instanceInfo` 字段并自行实现轻量版系统信息采集工具后方可认为 
release-ready。
   
   ---
   
   ---
   
   ## English Version
   
   > I have compiled shenyu-client-java with Java 8 and successfully run the 
verification flow for shenyu-examples-springmvc in the main repository.
   
   ### Basic Information
   
   | Item | Value |
   |------|-------|
   | Main Repo Commit | `7b5e23375d216711506933dc33f4bd4191d32058` 
(apache/shenyu master) |
   | Client Repo Commit | `7f3c5724403dcbb3b278ca7647d157684bb0951e` 
(shenyu-client-java) |
   | Client Version | `2.7.0.1-jdk8-SNAPSHOT` |
   | JDK (Admin/Bootstrap) | OpenJDK 17.0.19 |
   | JDK (Client Example) | OpenJDK 21.0.11 |
   | Admin Profile | `h2` (standalone) |
   | Sync Strategy | WebSocket |
   | Register Strategy | HTTP |
   
   ---
   
   ### Integration Adaptations & Rationale
   
   The following adaptations are required for `shenyu-examples-springmvc` to 
use the split client artifacts:
   
   **1. pom.xml — Replace Dependency Version**
   
   ```xml
   <!-- Original -->
   <version>${project.version}</version>
   <!-- Changed to -->
   <version>2.7.0.1-jdk8-SNAPSHOT</version>
   ```
   
   **Reason**: The original example depends on `shenyu-client-springmvc` from 
the main repository, whose version matches the main repo (`2.7.1-SNAPSHOT`). 
After switching to the split client repository, the version is 
`2.7.0.1-jdk8-SNAPSHOT`, which must be explicitly specified to pull the correct 
artifact from the local Maven repository.
   
   **2. context/shenyu.xml — Class Package Migration**
   
   | Class | Original FQN (shenyu main) | New FQN (shenyu-client-java) |
   |-------|---------------------------|------------------------------|
   | `ShenyuRegisterCenterConfig` | 
`org.apache.shenyu.register.common.config.ShenyuRegisterCenterConfig` | 
`org.apache.shenyu.client.core.register.config.ShenyuRegisterCenterConfig` |
   | `ShenyuClientConfig` | 
`org.apache.shenyu.register.common.config.ShenyuClientConfig` | 
`org.apache.shenyu.client.core.register.config.ShenyuClientConfig` |
   | `ClientPropertiesConfig` | 
`org.apache.shenyu.register.common.config.ShenyuClientConfig.ClientPropertiesConfig`
 | 
`org.apache.shenyu.client.core.register.config.ShenyuClientConfig.ClientPropertiesConfig`
 |
   | `HttpClientRegisterRepository` | 
`org.apache.shenyu.register.client.http.HttpClientRegisterRepository` | 
`org.apache.shenyu.client.core.register.HttpClientRegisterRepository` |
   
   **Reason**: This is the most critical adaptation. In the main repository, 
registration-related classes are spread across multiple modules 
(`shenyu-register-common`, `shenyu-register-client-http`, etc.). After the 
client is split into an independent repository, these modules are merged into 
`shenyu-client-core`, and package names are consolidated. The split repository 
no longer depends on the main repository's `shenyu-register-center` module, so 
registration-center classes are reorganized under the client's own package 
structure. XML configuration uses hard-coded fully qualified class names, so 
manual updates are required.
   
   **3. context/shenyu.xml — Add `discoveryLocalMode` Property**
   
   ```xml
   <entry key="discoveryLocalMode" value="true"/>
   ```
   
   **Reason**: In `AbstractContextRefreshedEventListener.onApplicationEvent()`, 
the `publishEvent(buildURIRegisterDTO(...))` call (which publishes the URI 
registration event consumed by `ShenyuClientURIExecutorSubscriber` to complete 
Discovery instance registration) is only executed when 
`isDiscoveryLocalMode=true`. The main repository's Spring Boot Starter 
(`ShenyuSpringMvcClientConfiguration`) automatically sets 
`discoveryLocalMode=true` based on the `shenyu.discovery.type` config (default 
`"local"`), so Starter users do not need to configure it manually. However, 
this example uses XML configuration (`@ImportResource`), which bypasses the 
Starter auto-configuration, so this property must be added manually in the XML.
   
   ---
   
   ### Verification Results
   
   | Verification Item | Result | Description |
   |-------------------|--------|-------------|
   | Spring MVC Registration | **PASS** | Admin logs received META_DATA 
registration with `appName=springmvc`, paths include `/mvc/order/save`, 
`/mvc/order/findById`, `/mvc/test/**`, `/mvc/upload/upload`, etc. |
   | Beat Heartbeat | **PASS** | Bootstrap logs continuously received 
`DISCOVER_UPSTREAM` REFRESH events (every 30s), upstream `172.19.0.1:8289` 
status normal |
   | Discovery Instance Registration | **PASS (requires manual instanceInfo 
patching)** | Admin created `/mvc` selector, Discovery upstream registered 
successfully. However, the original client code's `URIRegisterDTO` is missing 
the `instanceInfo` field and `sendHeartbeat` does not set it; manual patching 
is required for instance registration to work |
   | Admin Route Data Visible | **PASS** | WebSocket sync normal, Bootstrap 
successfully subscribed to all PLUGIN/SELECTOR/RULE/META_DATA/DISCOVER_UPSTREAM 
data |
   | Bootstrap Consumes Route Data | **PASS** | `divide` plugin loaded `/mvc` 
selector data, upstream proxy target reachable |
   | ShutdownHook Takeover | **FAIL** | See incompatibility issues below |
   
   ---
   
   ### Incompatibility Issues
   
   **[ISSUE-1] `InaccessibleObjectException` — ShutdownHook Reflection Blocked 
by JPMS**
   
   - **Severity**: Low (does not affect core registration/routing; only affects 
hook takeover during graceful shutdown)
   - **Location**: `ShenyuClientShutdownHook.java:89`
   - **Stack Trace**:
     ```
     java.lang.reflect.InaccessibleObjectException: Unable to make field 
private static
      java.util.IdentityHashMap java.lang.ApplicationShutdownHooks.hooks 
accessible:
      module java.base does not "opens java.lang" to unnamed module
     ```
   - **Cause**: The code uses reflection to access the private field 
`java.lang.ApplicationShutdownHooks.hooks`. Since JDK 16, `--illegal-access` 
defaults to deny, and the `java.base` module no longer opens its internal 
packages to unnamed modules. **Note: The client's compile target is JDK 8, but 
JPMS strong encapsulation is a runtime JVM behavior, independent of the class 
file's compile version. This issue only triggers when the runtime JVM is JDK 
16+; it does not occur on JDK 8-15. The test environment ran the example on JDK 
21, so this exception was triggered.** The main repository has the same issue.
   - **Workaround**: Add `--add-opens java.base/java.lang=ALL-UNNAMED` to JVM 
startup arguments
   - **Suggested Fix**: In `TakeoverOtherHooksThread.run()`, when `hooks` is 
null (reflection failed), skip the hook takeover logic instead of throwing NPE 
(`Objects.requireNonNull(hooks)` at line 96 throws NPE)
   
   **[ISSUE-2] Missing `instanceInfo` — `URIRegisterDTO` Missing Field + 
`sendHeartbeat` Missing Assignment, Causing Instance Registration Failure**
   
   - **Severity**: High (causes instance registration failure, affects core 
functionality, blocks release-ready judgment)
   - **Location**: `URIRegisterDTO.java` and 
`ShenyuClientURIExecutorSubscriber.java` `sendHeartbeat` method
   - **Main Repository Code**:
     - `URIRegisterDTO` 
(`org.apache.shenyu.register.common.dto.URIRegisterDTO`) includes the 
`instanceInfo` field
     - `ShenyuClientURIExecutorSubscriber.sendHeartbeat()` calls 
`uriRegisterDTO.setInstanceInfo(SystemInfoUtils.getSystemInfo())` to 
dynamically set system info
   - **Client Repository Original Code**:
     - `URIRegisterDTO` (`org.apache.shenyu.client.core.dto.URIRegisterDTO`) 
**completely lacks the `instanceInfo` field**
     - `ShenyuClientURIExecutorSubscriber.sendHeartbeat()` **has no logic to 
set `instanceInfo`**; it only directly calls 
`shenyuClientRegisterRepository.sendHeartbeat(uriRegisterDTO)`
   - **Cause**: The split client repository does not depend on the main 
repository's `shenyu-common` module, where `SystemInfoUtils` 
(`org.apache.shenyu.common.utils.SystemInfoUtils`) resides, depending on the 
`oshi` library to dynamically retrieve system architecture, OS, memory, and 
processor info. Due to the removal of the `shenyu-common` dependency, the 
`instanceInfo` field and its assignment logic were both omitted during the 
split, causing the Admin to fail to complete instance registration due to 
missing instance info.
   - **Verification Workaround**: Manually added `instanceInfo` field and 
getter/setter to `URIRegisterDTO`, and hardcoded `instanceInfo` as a fixed JSON 
string in `sendHeartbeat`; instance registration then succeeded.
   - **Suggested Fix**: Both layers of missing need to be addressed:
     1. Add the `instanceInfo` field with getter/setter/Builder methods to 
`URIRegisterDTO`
     2. Implement a lightweight `SystemInfoUtils` in `shenyu-client-core` (can 
use only JDK built-in `ManagementFactory` + 
`com.sun.management.OperatingSystemMXBean` to get arch, availableProcessors, 
totalMemorySize, etc., without introducing `oshi` dependency), and call it in 
`sendHeartbeat` to populate `instanceInfo`
     3. Or provide an optional `oshi` dependency module for users to choose to 
include
   
   ---
   
   ### Conclusion
   
   The split client repository `shenyu-client-java` version 
`2.7.0.1-jdk8-SNAPSHOT` is fundamentally compatible with the main repository's 
Admin/Bootstrap. Spring MVC registration, heartbeat, and Discovery registration 
(after manually patching `instanceInfo`) all passed verification.
   
   The main integration cost is **package name migration** 
(`org.apache.shenyu.register.*` → `org.apache.shenyu.client.core.register.*`). 
Users with XML configuration need to manually update class references; users 
with Spring Boot Starter auto-configuration are unaffected.
   
   Two runtime issues were found:
   1. **ShutdownHook reflection restriction on JDK 16+ runtime** (low severity) 
— an existing issue also present in the main repository, does not affect 
release judgment;
   2. **`URIRegisterDTO` missing `instanceInfo` field + `sendHeartbeat` missing 
assignment logic, causing instance registration failure** (high severity) — 
root cause is the removal of `shenyu-common` dependency which includes 
`SystemInfoUtils` (depends on `oshi`); the field and assignment logic were both 
omitted during the split. The `instanceInfo` field must be added back and a 
lightweight system info collection utility must be implemented in the client 
repository before it can be considered release-ready.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to