This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new a7d80b7880 refactor(registry): decouple core from registry
implementations (#7578)
a7d80b7880 is described below
commit a7d80b7880000a89bf15aa968db9c6ad07d96734
Author: redyyang <[email protected]>
AuthorDate: Tue Oct 13 11:32:01 2026 +0800
refactor(registry): decouple core from registry implementations (#7578)
Co-authored-by: redyyang <[email protected]>
---
.github/scripts/check-registry-dependencies.py | 99 +++++++++++
.github/scripts/test-registry-dependencies.py | 73 ++++++++
.github/workflows/ci.yml | 19 ++
shenyu-admin/pom.xml | 32 ++++
.../discovery/AbstractDiscoveryProcessor.java | 4 +-
.../discovery/DefaultDiscoveryProcessorTest.java | 31 ++++
.../pom.xml | 13 +-
.../shenyu-examples-sdk-feign/pom.xml | 7 +
.../shenyu-examples-sdk-grpc-consumer/pom.xml | 11 --
.../shenyu-examples-sdk-http/pom.xml | 13 +-
.../pom.xml | 25 +++
.../shenyu-integrated-test-sdk-http/pom.xml | 19 ++
.../pom.xml | 6 -
shenyu-registry/README.md | 132 ++++++++++++++
.../shenyu/registry/apollo/RegistrySpiTest.java | 33 ++++
shenyu-registry/shenyu-registry-core/pom.xml | 24 +--
.../ShenyuInstanceRegisterRepositoryFactory.java | 39 +++-
...henyuInstanceRegisterRepositoryFactoryTest.java | 196 +++++++++++++++++++--
.../shenyu/registry/etcd/RegistrySpiTest.java | 33 ++++
.../shenyu/registry/eureka/RegistrySpiTest.java | 33 ++++
.../registry/kubernetes/RegistrySpiTest.java | 33 ++++
.../shenyu/registry/nacos/RegistrySpiTest.java | 33 ++++
.../shenyu/registry/zookeeper/RegistrySpiTest.java | 33 ++++
shenyu-sdk/shenyu-sdk-core/pom.xml | 2 +-
.../apache/shenyu/sdk/core/ShenyuResponseTest.java | 3 +-
shenyu-sdk/shenyu-sdk-feign/pom.xml | 2 +-
.../shenyu-spring-boot-starter-registry/pom.xml | 27 +++
.../starter/registry/RegistryClasspathTest.java | 122 +++++++++++++
...u.registry.api.ShenyuInstanceRegisterRepository | 18 ++
.../shenyu-spring-boot-starter-sdk-feign/pom.xml | 11 ++
.../starter/sdk/feign/RegistryClasspathTest.java | 82 +++++++++
...u.registry.api.ShenyuInstanceRegisterRepository | 17 ++
.../shenyu-spring-boot-starter-sdk/pom.xml | 5 +
.../starter/sdk/RegistryClasspathTest.java | 82 +++++++++
...u.registry.api.ShenyuInstanceRegisterRepository | 17 ++
35 files changed, 1251 insertions(+), 78 deletions(-)
diff --git a/.github/scripts/check-registry-dependencies.py
b/.github/scripts/check-registry-dependencies.py
new file mode 100644
index 0000000000..ff233ff0c3
--- /dev/null
+++ b/.github/scripts/check-registry-dependencies.py
@@ -0,0 +1,99 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Assert registry boundaries against Maven's resolved runtime dependency
trees."""
+
+import sys
+from pathlib import Path
+
+PROVIDERS = ("zookeeper", "etcd", "nacos", "apollo", "eureka", "kubernetes")
+COMPATIBILITY_PROVIDERS = set(PROVIDERS) - {"apollo"}
+LEAN_MODULES = (
+ "shenyu-registry/shenyu-registry-api",
+ "shenyu-registry/shenyu-registry-core",
+ "shenyu-sdk/shenyu-sdk-core",
+ "shenyu-sdk/shenyu-sdk-feign",
+ "shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk",
+ "shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign",
+)
+IMPLEMENTATION_MODULES = tuple("shenyu-registry/shenyu-registry-" + name for
name in PROVIDERS)
+COMPATIBILITY_MODULE =
"shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry"
+MODULES = LEAN_MODULES + IMPLEMENTATION_MODULES + (COMPATIBILITY_MODULE,)
+CLIENT_GROUPS = (
+ "org.apache.zookeeper", "org.apache.curator", "io.etcd",
"com.alibaba.nacos",
+ "com.ecwid.consul", "com.orbitz.consul", "com.tencent.polaris",
+ "com.netflix.eureka", "io.kubernetes",
+)
+TREE_FILE = "target/registry-dependencies.tgf"
+
+
+def read_tree(path):
+ """Read node coordinates, excluding the project node and the edge
section."""
+ nodes = path.read_text(encoding="utf-8").split("#", 1)[0].splitlines()
+ if not nodes:
+ raise ValueError("empty dependency tree: " + str(path))
+ artifacts = set()
+ for node in nodes[1:]:
+ fields = node.split(" ", 1)
+ if len(fields) != 2 or len(fields[1].split(":")) < 4:
+ raise ValueError("invalid dependency coordinate: " + node)
+ artifacts.add(tuple(fields[1].split(":")[:2]))
+ return artifacts
+
+
+def check_module(module, artifacts):
+ errors = []
+ implementations = {artifact.removeprefix("shenyu-registry-")
+ for group, artifact in artifacts
+ if group == "org.apache.shenyu" and
artifact.startswith("shenyu-registry-")
+ and artifact not in {"shenyu-registry-api",
"shenyu-registry-core"}}
+ if module in LEAN_MODULES:
+ if implementations:
+ errors.append("registry implementations leaked into " + module +
": " + str(sorted(implementations)))
+ clients = {group + ":" + artifact for group, artifact in artifacts
+ if any(group == prefix or group.startswith(prefix + ".")
for prefix in CLIENT_GROUPS)}
+ if clients:
+ errors.append("registry clients leaked into " + module + ": " +
str(sorted(clients)))
+ if module in IMPLEMENTATION_MODULES and ("org.apache.shenyu",
"shenyu-registry-core") in artifacts:
+ errors.append(module + " depends on registry-core instead of
registry-api")
+ if module == COMPATIBILITY_MODULE:
+ missing = COMPATIBILITY_PROVIDERS - implementations
+ if missing:
+ errors.append("compatibility starter lost providers: " +
str(sorted(missing)))
+ return errors
+
+
+def main():
+ if sys.argv[1:] == ["--modules"]:
+ print(",".join(MODULES))
+ return 0
+ root = Path(__file__).resolve().parents[2]
+ errors = []
+ for module in MODULES:
+ path = root / module / TREE_FILE
+ try:
+ artifacts = read_tree(path)
+ errors.extend(check_module(module, artifacts))
+ except (OSError, ValueError) as error:
+ errors.append(str(error))
+ if errors:
+ print("\n".join(errors), file=sys.stderr)
+ return 1
+ print("Registry dependency boundaries passed for " + str(len(MODULES)) + "
runtime trees.")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/.github/scripts/test-registry-dependencies.py
b/.github/scripts/test-registry-dependencies.py
new file mode 100644
index 0000000000..2ceb0a8f49
--- /dev/null
+++ b/.github/scripts/test-registry-dependencies.py
@@ -0,0 +1,73 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Regression checks for the resolved dependency boundary assertions."""
+
+import importlib.util
+import sys
+import tempfile
+import unittest
+from pathlib import Path
+
+sys.dont_write_bytecode = True
+
+SPEC = importlib.util.spec_from_file_location("registry_dependencies",
Path(__file__).with_name("check-registry-dependencies.py"))
+CHECK = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(CHECK)
+
+
+class RegistryDependencyTest(unittest.TestCase):
+ def test_transitive_clients_are_rejected_for_every_lean_consumer(self):
+ for module in CHECK.LEAN_MODULES:
+ for group in CHECK.CLIENT_GROUPS:
+ with self.subTest(module=module, group=group):
+ self.assertTrue(CHECK.check_module(module, {(group,
"client")}))
+
+ def test_any_registry_implementation_is_rejected(self):
+ self.assertTrue(CHECK.check_module(CHECK.LEAN_MODULES[1],
{("org.apache.shenyu", "shenyu-registry-external")}))
+
+ def test_api_and_spi_are_allowed(self):
+ artifacts = {("org.apache.shenyu", "shenyu-registry-api"),
("org.apache.shenyu", "shenyu-spi")}
+ self.assertFalse(CHECK.check_module(CHECK.LEAN_MODULES[1], artifacts))
+
+ def test_implementation_cannot_depend_on_core(self):
+ self.assertTrue(CHECK.check_module(CHECK.IMPLEMENTATION_MODULES[0],
{("org.apache.shenyu", "shenyu-registry-core")}))
+
+ def test_compatibility_starter_must_retain_all_original_providers(self):
+ artifacts = {("org.apache.shenyu", "shenyu-registry-" + provider) for
provider in CHECK.COMPATIBILITY_PROVIDERS}
+ self.assertFalse(CHECK.check_module(CHECK.COMPATIBILITY_MODULE,
artifacts))
+ artifacts.pop()
+ self.assertTrue(CHECK.check_module(CHECK.COMPATIBILITY_MODULE,
artifacts))
+
+ def
test_tgf_nodes_include_transitive_artifacts_but_not_root_or_edges(self):
+ with tempfile.TemporaryDirectory() as directory:
+ tree = Path(directory) / "tree.tgf"
+ tree.write_text("1
org.apache.shenyu:shenyu-registry-core:jar:2.7.2-SNAPSHOT\n"
+ "2
org.apache.shenyu:shenyu-registry-api:jar:2.7.2-SNAPSHOT:compile\n"
+ "3
org.apache.curator:curator-client:jar:5.7.1:runtime\n#\n1 2\n2 3\n",
encoding="utf-8")
+ artifacts = CHECK.read_tree(tree)
+ self.assertEqual({("org.apache.shenyu", "shenyu-registry-api"),
("org.apache.curator", "curator-client")}, artifacts)
+ self.assertTrue(CHECK.check_module(CHECK.LEAN_MODULES[1],
artifacts))
+
+ def test_empty_tree_fails_closed(self):
+ with tempfile.TemporaryDirectory() as directory:
+ tree = Path(directory) / "tree.tgf"
+ tree.write_text("", encoding="utf-8")
+ with self.assertRaises(ValueError):
+ CHECK.read_tree(tree)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4594b7abb6..256ea81de8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -112,6 +112,25 @@ jobs:
- name: Test GHCR publish routing
run: bash .github/scripts/resolve-ghcr-publish-test.sh
+ registry_dependencies:
+ needs: changes
+ runs-on: ubuntu-latest
+ if: github.repository == 'apache/shenyu' && needs.changes.outputs.code ==
'true'
+ steps:
+ - uses: actions/checkout@v3
+ - uses: ./actions/setup-java-with-retry
+ with:
+ java-version: "17"
+ distribution: "temurin"
+ - name: Check resolved registry dependency boundaries
+ run: |
+ python3 .github/scripts/test-registry-dependencies.py
+ REGISTRY_MODULES="$(python3
.github/scripts/check-registry-dependencies.py --modules)"
+ ./mvnw -B -ntp -pl "$REGISTRY_MODULES" -am package dependency:tree \
+ -DskipTests -DskipRemoteResources=true -Djacoco.skip=true
-Dmaven.javadoc.skip=true \
+ -Dscope=runtime -DoutputType=tgf
-DoutputFile=target/registry-dependencies.tgf
+ python3 .github/scripts/check-registry-dependencies.py
+
rat_check:
needs: changes
runs-on: ubuntu-latest
diff --git a/shenyu-admin/pom.xml b/shenyu-admin/pom.xml
index 4b5be49852..3da4f9fd0c 100644
--- a/shenyu-admin/pom.xml
+++ b/shenyu-admin/pom.xml
@@ -347,6 +347,38 @@
<version>${project.version}</version>
</dependency>
+ <!-- Explicit providers preserve the existing distribution/classpath.
-->
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-etcd</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-nacos</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-eureka</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-kubernetes</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-client-mcp-common</artifactId>
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/discovery/AbstractDiscoveryProcessor.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/discovery/AbstractDiscoveryProcessor.java
index 578bcb85d7..212ee8865b 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/discovery/AbstractDiscoveryProcessor.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/discovery/AbstractDiscoveryProcessor.java
@@ -37,7 +37,7 @@ import org.apache.shenyu.common.utils.UUIDUtils;
import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
import org.apache.shenyu.registry.api.config.RegisterConfig;
import org.apache.shenyu.registry.api.entity.InstanceEntity;
-import org.apache.shenyu.spi.ExtensionLoader;
+import org.apache.shenyu.registry.core.ShenyuInstanceRegisterRepositoryFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
@@ -97,7 +97,7 @@ public abstract class AbstractDiscoveryProcessor implements
DiscoveryProcessor,
discoveryConfig.setRegisterType(type);
discoveryConfig.setProps(properties);
discoveryConfig.setServerLists(discoveryDO.getServerList());
- ShenyuInstanceRegisterRepository discoveryService =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin(type);
+ ShenyuInstanceRegisterRepository discoveryService =
ShenyuInstanceRegisterRepositoryFactory.loadRepository(type);
discoveryService.init(discoveryConfig);
discoveryServiceCache.put(discoveryDO.getId(), discoveryService);
dataChangedEventListenerCache.put(discoveryDO.getId(), new
HashSet<>());
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/DefaultDiscoveryProcessorTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/DefaultDiscoveryProcessorTest.java
index 778dff6b74..fa6204c989 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/DefaultDiscoveryProcessorTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/discovery/DefaultDiscoveryProcessorTest.java
@@ -107,6 +107,7 @@ public class DefaultDiscoveryProcessorTest {
MockedStatic<ExtensionLoader> mocked =
mockStatic(ExtensionLoader.class);
ExtensionLoader extensionLoader = mock(ExtensionLoader.class);
mocked.when(() ->
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class)).thenReturn(extensionLoader);
+
when(extensionLoader.getExtensionClasses1()).thenReturn(Collections.singletonMap(type,
ShenyuInstanceRegisterRepository.class));
when(extensionLoader.getJoin(anyString())).thenReturn(shenyuDiscoveryService);
doNothing().when(shenyuDiscoveryService).init(any(RegisterConfig.class));
defaultDiscoveryProcessor.createDiscovery(discoveryDO);
@@ -138,6 +139,36 @@ public class DefaultDiscoveryProcessorTest {
mocked.close();
}
+ @Test
+ public void testCreateSeparateDiscoveriesOfSameType() throws Exception {
+ ShenyuInstanceRegisterRepository first =
mock(ShenyuInstanceRegisterRepository.class);
+ ShenyuInstanceRegisterRepository second =
mock(ShenyuInstanceRegisterRepository.class);
+ try (MockedStatic<ExtensionLoader> mocked =
mockStatic(ExtensionLoader.class)) {
+ ExtensionLoader<ShenyuInstanceRegisterRepository> loader =
mock(ExtensionLoader.class);
+ mocked.when(() ->
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class)).thenReturn(loader);
+
when(loader.getExtensionClasses1()).thenReturn(Collections.singletonMap("zookeeper",
ShenyuInstanceRegisterRepository.class));
+ when(loader.getJoin("zookeeper")).thenReturn(first, second);
+ DiscoveryDO firstDiscovery = new DiscoveryDO();
+ firstDiscovery.setId("first-discovery");
+ firstDiscovery.setDiscoveryType("zookeeper");
+ firstDiscovery.setProps("{}");
+ firstDiscovery.setServerList("first:2181");
+ DiscoveryDO secondDiscovery = new DiscoveryDO();
+ secondDiscovery.setId("second-discovery");
+ secondDiscovery.setDiscoveryType("zookeeper");
+ secondDiscovery.setProps("{}");
+ secondDiscovery.setServerList("second:2181");
+ defaultDiscoveryProcessor.createDiscovery(firstDiscovery);
+ defaultDiscoveryProcessor.createDiscovery(secondDiscovery);
+ verify(first).init(new RegisterConfig("zookeeper", "first:2181",
new Properties()));
+ verify(second).init(new RegisterConfig("zookeeper", "second:2181",
new Properties()));
+ defaultDiscoveryProcessor.removeDiscovery(firstDiscovery);
+ verify(first).close();
+ defaultDiscoveryProcessor.removeDiscovery(secondDiscovery);
+ verify(second).close();
+ }
+ }
+
@Test
public void testCreateProxySelector() {
DiscoveryHandlerDTO discoveryHandlerDTO = new DiscoveryHandlerDTO();
diff --git
a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-dubbo/shenyu-examples-sdk-apache-dubbo-consumer/pom.xml
b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-dubbo/shenyu-examples-sdk-apache-dubbo-consumer/pom.xml
index f8edde7f7e..e4f058e478 100644
---
a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-dubbo/shenyu-examples-sdk-apache-dubbo-consumer/pom.xml
+++
b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-dubbo/shenyu-examples-sdk-apache-dubbo-consumer/pom.xml
@@ -28,15 +28,16 @@
<artifactId>shenyu-examples-sdk-apache-dubbo-consumer</artifactId>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-sdk-core</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-eureka</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
diff --git
a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-feign/pom.xml
b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-feign/pom.xml
index d581451e15..5a8c3d196d 100644
--- a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-feign/pom.xml
+++ b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-feign/pom.xml
@@ -33,6 +33,13 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
diff --git
a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-grpc/shenyu-examples-sdk-grpc-consumer/pom.xml
b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-grpc/shenyu-examples-sdk-grpc-consumer/pom.xml
index d0dc390fe2..63a42f7c5c 100644
---
a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-grpc/shenyu-examples-sdk-grpc-consumer/pom.xml
+++
b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-grpc/shenyu-examples-sdk-grpc-consumer/pom.xml
@@ -68,17 +68,6 @@
<artifactId>shenyu-sdk-core</artifactId>
</dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-eureka</artifactId>
- <version>${project.version}</version>
- <exclusions>
- <exclusion>
- <artifactId>servlet-api</artifactId>
- <groupId>javax.servlet</groupId>
- </exclusion>
- </exclusions>
- </dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
diff --git
a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-http/pom.xml
b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-http/pom.xml
index 91a70f3d97..dbd3ac8fa9 100644
--- a/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-http/pom.xml
+++ b/shenyu-examples/shenyu-examples-sdk/shenyu-examples-sdk-http/pom.xml
@@ -28,15 +28,16 @@
<artifactId>shenyu-examples-sdk-http</artifactId>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-sdk-core</artifactId>
- <exclusions>
- <exclusion>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-eureka</artifactId>
- </exclusion>
- </exclusions>
</dependency>
<dependency>
diff --git
a/shenyu-integrated-test/shenyu-integrated-test-sdk-apache-dubbo/pom.xml
b/shenyu-integrated-test/shenyu-integrated-test-sdk-apache-dubbo/pom.xml
index 3b7874c38c..9097565017 100644
--- a/shenyu-integrated-test/shenyu-integrated-test-sdk-apache-dubbo/pom.xml
+++ b/shenyu-integrated-test/shenyu-integrated-test-sdk-apache-dubbo/pom.xml
@@ -36,6 +36,13 @@
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-integrated-test-common</artifactId>
@@ -46,6 +53,24 @@
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-registry</artifactId>
<version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-etcd</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-nacos</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-eureka</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-kubernetes</artifactId>
+ </exclusion>
+ </exclusions>
</dependency>
<dependency>
diff --git a/shenyu-integrated-test/shenyu-integrated-test-sdk-http/pom.xml
b/shenyu-integrated-test/shenyu-integrated-test-sdk-http/pom.xml
index 769a9f4164..5ce537d271 100644
--- a/shenyu-integrated-test/shenyu-integrated-test-sdk-http/pom.xml
+++ b/shenyu-integrated-test/shenyu-integrated-test-sdk-http/pom.xml
@@ -28,6 +28,13 @@
<artifactId>shenyu-integrated-test-sdk-http</artifactId>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-integrated-test-common</artifactId>
@@ -39,10 +46,22 @@
<artifactId>shenyu-spring-boot-starter-registry</artifactId>
<version>${project.version}</version>
<exclusions>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-etcd</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-nacos</artifactId>
+ </exclusion>
<exclusion>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-registry-eureka</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-kubernetes</artifactId>
+ </exclusion>
</exclusions>
</dependency>
diff --git
a/shenyu-integrated-test/shenyu-integrated-test-upload-plugin/shenyu-integrated-test-upload-plugin-case/pom.xml
b/shenyu-integrated-test/shenyu-integrated-test-upload-plugin/shenyu-integrated-test-upload-plugin-case/pom.xml
index 9ad4393e46..66e5948ca5 100644
---
a/shenyu-integrated-test/shenyu-integrated-test-upload-plugin/shenyu-integrated-test-upload-plugin-case/pom.xml
+++
b/shenyu-integrated-test/shenyu-integrated-test-upload-plugin/shenyu-integrated-test-upload-plugin-case/pom.xml
@@ -42,12 +42,6 @@
<version>${project.version}</version>
</dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-spring-boot-starter-registry</artifactId>
- <version>${project.version}</version>
- </dependency>
-
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-gateway</artifactId>
diff --git a/shenyu-registry/README.md b/shenyu-registry/README.md
new file mode 100644
index 0000000000..d91f0f8ef0
--- /dev/null
+++ b/shenyu-registry/README.md
@@ -0,0 +1,132 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Registry dependency selection
+
+`shenyu-registry-core` supplies the SPI-backed factory and its existing
per-type cache.
+It depends only on `shenyu-registry-api` and `shenyu-spi`; implementation
modules depend
+on the API. Loading a factory no longer loads every registry client.
+
+## Select providers explicitly
+
+Add the implementation matching `RegisterConfig.registerType` to your
application's
+runtime classpath, using the same ShenYu version as the application:
+
+```xml
+<dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${shenyu.version}</version>
+ <scope>runtime</scope>
+</dependency>
+```
+
+| SPI key | Implementation artifact |
+| --- | --- |
+| `zookeeper` | `shenyu-registry-zookeeper` |
+| `etcd` | `shenyu-registry-etcd` |
+| `nacos` | `shenyu-registry-nacos` |
+| `apollo` | `shenyu-registry-apollo` |
+| `eureka` | `shenyu-registry-eureka` |
+| `kubernetes` | `shenyu-registry-kubernetes` |
+
+Multiple implementation dependencies may coexist. The configured key selects
the
+provider; the factory never calls `init` on unrelated providers. Missing or
unknown keys
+fail with the requested artifact, runtime classpath guidance, and available
SPI keys.
+Provider construction and initialization errors still propagate without being
reported
+as missing dependencies.
+
+SDK libraries depend on the registry API. The SDK and Feign SDK starters
additionally
+include the factory, so add the selected implementation next to the SDK
starter.
+Applications calling the factory directly should declare
`shenyu-registry-core` explicitly.
+`shenyu.sdk.register-type=local` uses configured gateway addresses and needs
no registry
+implementation. The HTTP, Dubbo and Feign SDK examples select ZooKeeper, the
Spring Cloud
+example selects Eureka, and the gRPC example selects no registry
implementation.
+
+## Compatibility and migration
+
+For the 2.7.2 transition, `shenyu-spring-boot-starter-registry` retains the
five providers
+previously supplied by core: ZooKeeper, etcd, Nacos, Eureka and Kubernetes.
Existing users
+of that starter keep the same classpath and configuration keys. Apollo was not
part of
+the previous aggregate and remains an explicit choice. Removing the
compatibility
+aggregate is deferred to a separately announced change.
+
+Applications depending directly on core, SDK libraries, or SDK starters should
add
+their configured provider before upgrading. For gateway registration with a
smaller
+classpath, retain the registry starter, exclude the unused implementation
dependencies,
+and declare the selected implementation explicitly. The SDK integration tests
show
+this arrangement for ZooKeeper. Admin declares its five supported discovery
providers
+explicitly and preserves independent connections for separate discovery
configurations.
+
+To roll back a dependency selection, restore the application's previous
explicit
+provider dependencies, or keep the compatibility starter if already used.
Rolling back
+the ShenYu version restores the old core aggregation. No stored configuration
or SPI
+key migration is required. The factory's cache and reinitialization behavior
is unchanged.
+
+## External implementations
+
+External providers, including Consul and Polaris extensions, implement
+`org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository`, carry
`@Join`, and
+publish this resource in their JAR:
+
+```text
+META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
+```
+
+For example, `consul=com.example.ConsulInstanceRegisterRepository` preserves
the existing
+registration key. Add the extension JAR and its client dependencies to the
runtime
+classpath; no core POM change is needed. Existing ShenYu SPI classloader rules
continue
+to apply. Factory tests load external `consul` and `polaris` keys from
isolated resources
+without any built-in provider dependency.
+
+## Dependency and release inventory
+
+| Consumer | Registry implementation inventory |
+| --- | --- |
+| Registry API/core, SDK libraries and SDK starters | None |
+| Compatibility registry starter | ZooKeeper, etcd, Nacos, Eureka, Kubernetes |
+| Admin distribution | ZooKeeper, etcd, Nacos, Eureka, Kubernetes, explicitly
declared |
+| Bootstrap distribution | Registry API only; client libraries also arrive
through sync/register/plugin modules |
+| SDK HTTP/Dubbo integration gateways | ZooKeeper |
+| Upload-plugin integration gateway | None; registry registration is not
enabled |
+
+The packaged Admin client inventory includes Curator 5.7.0, ZooKeeper 3.9.5,
jetcd
+0.7.3, Nacos 2.2.4, Eureka 2.0.2, and Kubernetes Java client 17.0.2. Bootstrap
still
+packages Curator, ZooKeeper, jetcd and Nacos through its other selected
modules. Both
+release archives include their LICENSE/NOTICE files. No registry client
license entry
+is removed by this change.
+
+Admin retains the same client libraries, so its existing distribution
LICENSE/NOTICE
+entries still apply. Bootstrap distribution client libraries also come from
selected
+plugin, registration and synchronization starters; removing an edge from
registry-core
+alone does not remove those libraries. Keep the existing license and notice
entries
+for libraries still shipped. Custom distributions must inventory the actual
packaged
+`lib/` JARs and include their notices when selecting or removing providers.
+
+CI checks resolved runtime trees for the API/core, both SDK
libraries/starters, all six
+in-repository implementations and the compatibility starter. The checks reject
registry
+implementations and client libraries in lean consumers, reverse
implementation-to-core
+dependencies, and loss of a compatibility provider. Run locally:
+
+```bash
+python3 .github/scripts/test-registry-dependencies.py
+REGISTRY_MODULES="$(python3 .github/scripts/check-registry-dependencies.py
--modules)"
+./mvnw -B -ntp -pl "$REGISTRY_MODULES" -am package dependency:tree \
+ -DskipTests -DskipRemoteResources=true -Djacoco.skip=true
-Dmaven.javadoc.skip=true \
+ -Dscope=runtime -DoutputType=tgf
-DoutputFile=target/registry-dependencies.tgf
+python3 .github/scripts/check-registry-dependencies.py
+```
diff --git
a/shenyu-registry/shenyu-registry-apollo/src/test/java/org/apache/shenyu/registry/apollo/RegistrySpiTest.java
b/shenyu-registry/shenyu-registry-apollo/src/test/java/org/apache/shenyu/registry/apollo/RegistrySpiTest.java
new file mode 100644
index 0000000000..af130fd279
--- /dev/null
+++
b/shenyu-registry/shenyu-registry-apollo/src/test/java/org/apache/shenyu/registry/apollo/RegistrySpiTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.registry.apollo;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RegistrySpiTest {
+
+ @Test
+ void discoversPublishedRegistryKey() {
+ ShenyuInstanceRegisterRepository repository =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin("apollo");
+ assertEquals(ApolloInstanceRegisterRepository.class,
repository.getClass());
+ }
+}
diff --git a/shenyu-registry/shenyu-registry-core/pom.xml
b/shenyu-registry/shenyu-registry-core/pom.xml
index 68f73d3aa7..4349509aca 100644
--- a/shenyu-registry/shenyu-registry-core/pom.xml
+++ b/shenyu-registry/shenyu-registry-core/pom.xml
@@ -28,32 +28,12 @@
<dependencies>
<dependency>
<groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-spi</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-zookeeper</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-etcd</artifactId>
+ <artifactId>shenyu-registry-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-nacos</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-eureka</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-kubernetes</artifactId>
+ <artifactId>shenyu-spi</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
diff --git
a/shenyu-registry/shenyu-registry-core/src/main/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactory.java
b/shenyu-registry/shenyu-registry-core/src/main/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactory.java
index 4af9d17449..4388a39752 100644
---
a/shenyu-registry/shenyu-registry-core/src/main/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactory.java
+++
b/shenyu-registry/shenyu-registry-core/src/main/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactory.java
@@ -17,11 +17,14 @@
package org.apache.shenyu.registry.core;
+import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
import org.apache.shenyu.registry.api.config.RegisterConfig;
import org.apache.shenyu.spi.ExtensionLoader;
import java.util.Map;
+import java.util.Objects;
+import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -38,7 +41,28 @@ public final class ShenyuInstanceRegisterRepositoryFactory {
* @return the shenyu instance register repository
*/
public static ShenyuInstanceRegisterRepository newInstance(final String
registerType) {
- return REPOSITORY_MAP.computeIfAbsent(registerType,
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class)::getJoin);
+ checkRegisterType(registerType);
+ return REPOSITORY_MAP.computeIfAbsent(registerType,
ShenyuInstanceRegisterRepositoryFactory::loadRepository);
+ }
+
+ /**
+ * Load a repository through SPI without using the factory's per-type
cache.
+ * The provider's {@code @Join} singleton setting still applies.
+ *
+ * @param registerType the configured registry type
+ * @return the repository
+ */
+ public static ShenyuInstanceRegisterRepository loadRepository(final String
registerType) {
+ checkRegisterType(registerType);
+ ExtensionLoader<ShenyuInstanceRegisterRepository> loader =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class);
+ Map<String, Class<?>> providers = loader.getExtensionClasses1();
+ if (!providers.containsKey(registerType)) {
+ throw new IllegalArgumentException("No registry implementation
found for registerType '" + registerType
+ + "'. Add the matching shenyu-registry-" + registerType
+ + " dependency (or an external registry SPI provider) to
the runtime classpath. Available types: "
+ + new TreeSet<>(providers.keySet()));
+ }
+ return loader.getJoin(registerType);
}
/**
@@ -48,8 +72,10 @@ public final class ShenyuInstanceRegisterRepositoryFactory {
* @return the shenyu instance register repository
*/
public static ShenyuInstanceRegisterRepository newAndInitInstance(final
RegisterConfig config) {
+ Objects.requireNonNull(config, "register config is required");
+ checkRegisterType(config.getRegisterType());
return REPOSITORY_MAP.computeIfAbsent(config.getRegisterType(),
registerType -> {
- ShenyuInstanceRegisterRepository result =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin(registerType);
+ ShenyuInstanceRegisterRepository result =
loadRepository(registerType);
result.init(config);
return result;
});
@@ -62,9 +88,16 @@ public final class ShenyuInstanceRegisterRepositoryFactory {
* @return the shenyu instance register repository
*/
public static ShenyuInstanceRegisterRepository reNewAndInitInstance(final
RegisterConfig config) {
- ShenyuInstanceRegisterRepository result =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin(config.getRegisterType());
+ Objects.requireNonNull(config, "register config is required");
+ ShenyuInstanceRegisterRepository result =
loadRepository(config.getRegisterType());
result.init(config);
REPOSITORY_MAP.put(config.getRegisterType(), result);
return result;
}
+
+ private static void checkRegisterType(final String registerType) {
+ if (StringUtils.isBlank(registerType)) {
+ throw new IllegalArgumentException("registerType is required;
configure a registry type and add its SPI provider to the runtime classpath");
+ }
+ }
}
diff --git
a/shenyu-registry/shenyu-registry-core/src/test/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactoryTest.java
b/shenyu-registry/shenyu-registry-core/src/test/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactoryTest.java
index d7c40e9fe6..d42e57ba3f 100644
---
a/shenyu-registry/shenyu-registry-core/src/test/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactoryTest.java
+++
b/shenyu-registry/shenyu-registry-core/src/test/java/org/apache/shenyu/registry/core/ShenyuInstanceRegisterRepositoryFactoryTest.java
@@ -18,37 +18,197 @@
package org.apache.shenyu.registry.core;
import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.registry.api.config.RegisterConfig;
+import org.apache.shenyu.registry.api.entity.InstanceEntity;
import org.apache.shenyu.spi.ExtensionLoader;
+import org.apache.shenyu.spi.Join;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
+import org.junit.jupiter.api.io.TempDir;
+import java.io.IOException;
import java.lang.reflect.Field;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Enumeration;
import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
-public final class ShenyuInstanceRegisterRepositoryFactoryTest {
+class ShenyuInstanceRegisterRepositoryFactoryTest {
+
+ private static final String SPI_RESOURCE = "META-INF/shenyu/" +
ShenyuInstanceRegisterRepository.class.getName();
+
+ @TempDir
+ private Path directory;
+
+ private URLClassLoader classLoader;
@BeforeEach
- public void setup() throws Exception {
- final Field field =
ShenyuInstanceRegisterRepositoryFactory.class.getDeclaredField("REPOSITORY_MAP");
- field.setAccessible(true);
- final Map map = (Map) field.get(null);
- map.put("zookeeper", mock(ShenyuInstanceRegisterRepository.class));
+ void setUp() throws Exception {
+ clearCaches();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ clearCaches();
+ if (Objects.nonNull(classLoader)) {
+ classLoader.close();
+ }
+ }
+
+ @Test
+ void noProviderGivesDependencyHint() throws Exception {
+ useProviders("");
+ IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class,
+ () ->
ShenyuInstanceRegisterRepositoryFactory.newInstance("zookeeper"));
+
assertTrue(exception.getMessage().contains("shenyu-registry-zookeeper"));
+ assertTrue(exception.getMessage().contains("runtime classpath"));
+ assertTrue(exception.getMessage().contains("Available types: []"));
+ }
+
+ @Test
+ void oneProviderIsLoadedAndCached() throws Exception {
+ useProviders(provider("external"));
+ ShenyuInstanceRegisterRepository repository =
ShenyuInstanceRegisterRepositoryFactory.newInstance("external");
+ assertEquals(TestRepository.class, repository.getClass());
+ assertSame(repository,
ShenyuInstanceRegisterRepositoryFactory.newInstance("external"));
+ assertNotSame(repository,
ShenyuInstanceRegisterRepositoryFactory.loadRepository("external"));
+ }
+
+ @Test
+ void multipleExternalProvidersKeepTheirSpiKeys() throws Exception {
+ useProviders(provider("consul") + provider("polaris"));
+ ShenyuInstanceRegisterRepository consul =
ShenyuInstanceRegisterRepositoryFactory.newInstance("consul");
+ ShenyuInstanceRegisterRepository polaris =
ShenyuInstanceRegisterRepositoryFactory.newInstance("polaris");
+ assertNotSame(consul, polaris);
+ assertSame(consul,
ShenyuInstanceRegisterRepositoryFactory.newInstance("consul"));
+ assertSame(polaris,
ShenyuInstanceRegisterRepositoryFactory.newInstance("polaris"));
+ IllegalArgumentException exception =
assertThrows(IllegalArgumentException.class,
+ () ->
ShenyuInstanceRegisterRepositoryFactory.newInstance("unknown"));
+ assertTrue(exception.getMessage().contains("Available types: [consul,
polaris]"));
+ }
+
+ @Test
+ void missingProviderFailsFromEveryFactoryEntryPoint() throws Exception {
+ useProviders(provider("external"));
+ RegisterConfig config = config("missing");
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.loadRepository("missing"));
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config));
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.reNewAndInitInstance(config));
+ }
+
+ @Test
+ void blankTypeFailsBeforeCacheAccess() {
+ for (String type : new String[] {null, "", " "}) {
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.newInstance(type));
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.loadRepository(type));
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config(type)));
+ assertThrows(IllegalArgumentException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.reNewAndInitInstance(config(type)));
+ }
}
@Test
- public void testNewInstance() {
-
assertNotNull(ShenyuInstanceRegisterRepositoryFactory.newInstance("zookeeper"));
- try (MockedStatic<ExtensionLoader> extensionLoaderMockedStatic =
mockStatic(ExtensionLoader.class)) {
- ExtensionLoader extensionLoader = mock(ExtensionLoader.class);
- extensionLoaderMockedStatic.when(() ->
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class)).thenReturn(extensionLoader);
-
when(extensionLoader.getJoin("zs")).thenReturn(mock(ShenyuInstanceRegisterRepository.class));
-
assertNotNull(ShenyuInstanceRegisterRepositoryFactory.newInstance("zs"));
+ void initializationAndRenewalPreserveCacheContract() throws Exception {
+ useProviders(provider("external"));
+ RegisterConfig config = config("external");
+ TestRepository repository = (TestRepository)
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config);
+ assertSame(config, repository.config);
+ assertEquals(1, repository.initCount);
+ assertSame(repository,
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config));
+ assertEquals(1, repository.initCount);
+ RegisterConfig replacement = config("external");
+ replacement.setServerLists("replacement");
+ TestRepository renewed = (TestRepository)
ShenyuInstanceRegisterRepositoryFactory.reNewAndInitInstance(replacement);
+ assertNotSame(repository, renewed);
+ assertSame(replacement, renewed.config);
+ assertSame(renewed,
ShenyuInstanceRegisterRepositoryFactory.newInstance("external"));
+ }
+
+ @Test
+ void uninitializedCachedInstanceKeepsExistingBehavior() throws Exception {
+ useProviders(provider("external"));
+ TestRepository repository = (TestRepository)
ShenyuInstanceRegisterRepositoryFactory.newInstance("external");
+ assertSame(repository,
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config("external")));
+ assertEquals(0, repository.initCount);
+ }
+
+ @Test
+ void
failedInitializationIsNotCachedAndFailedRenewalKeepsPreviousInstance() throws
Exception {
+ useProviders(provider("external"));
+ RegisterConfig failing = config("external");
+ failing.setServerLists("fail");
+ assertThrows(IllegalStateException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(failing));
+ TestRepository repository = (TestRepository)
ShenyuInstanceRegisterRepositoryFactory.newAndInitInstance(config("external"));
+ assertEquals(1, repository.initCount);
+ assertThrows(IllegalStateException.class, () ->
ShenyuInstanceRegisterRepositoryFactory.reNewAndInitInstance(failing));
+ assertSame(repository,
ShenyuInstanceRegisterRepositoryFactory.newInstance("external"));
+ }
+
+ private void useProviders(final String definitions) throws IOException {
+ Path resource = directory.resolve(SPI_RESOURCE);
+ Files.createDirectories(resource.getParent());
+ Files.write(resource, definitions.getBytes(StandardCharsets.UTF_8));
+ classLoader = new URLClassLoader(new URL[]
{directory.toUri().toURL()}, getClass().getClassLoader()) {
+ @Override
+ public Enumeration<URL> getResources(final String name) throws
IOException {
+ if (SPI_RESOURCE.equals(name)) {
+ return findResources(name);
+ }
+ return super.getResources(name);
+ }
+ };
+
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class,
classLoader);
+ }
+
+ private String provider(final String type) {
+ return type + "=" + TestRepository.class.getName() + "\n";
+ }
+
+ private RegisterConfig config(final String type) {
+ return new RegisterConfig(type, "server", new Properties());
+ }
+
+ private void clearCaches() throws Exception {
+ Field repositories =
ShenyuInstanceRegisterRepositoryFactory.class.getDeclaredField("REPOSITORY_MAP");
+ repositories.setAccessible(true);
+ ((Map<?, ?>) repositories.get(null)).clear();
+ Field loaders = ExtensionLoader.class.getDeclaredField("LOADERS");
+ loaders.setAccessible(true);
+ ((Map<?, ?>)
loaders.get(null)).remove(ShenyuInstanceRegisterRepository.class);
+ }
+
+ /**
+ * A provider loaded from an external SPI resource, without registry
clients.
+ */
+ @Join(isSingleton = false)
+ public static class TestRepository implements
ShenyuInstanceRegisterRepository {
+
+ private RegisterConfig config;
+
+ private int initCount;
+
+ @Override
+ public void init(final RegisterConfig registerConfig) {
+ if ("fail".equals(registerConfig.getServerLists())) {
+ throw new IllegalStateException("initialization failed");
+ }
+ config = registerConfig;
+ initCount++;
+ }
+
+ @Override
+ public void persistInstance(final InstanceEntity instance) {
}
}
}
diff --git
a/shenyu-registry/shenyu-registry-etcd/src/test/java/org/apache/shenyu/registry/etcd/RegistrySpiTest.java
b/shenyu-registry/shenyu-registry-etcd/src/test/java/org/apache/shenyu/registry/etcd/RegistrySpiTest.java
new file mode 100644
index 0000000000..b1ae3d7c67
--- /dev/null
+++
b/shenyu-registry/shenyu-registry-etcd/src/test/java/org/apache/shenyu/registry/etcd/RegistrySpiTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.registry.etcd;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RegistrySpiTest {
+
+ @Test
+ void discoversPublishedRegistryKey() {
+ ShenyuInstanceRegisterRepository repository =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin("etcd");
+ assertEquals(EtcdInstanceRegisterRepository.class,
repository.getClass());
+ }
+}
diff --git
a/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/RegistrySpiTest.java
b/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/RegistrySpiTest.java
new file mode 100644
index 0000000000..ea1f39c636
--- /dev/null
+++
b/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/RegistrySpiTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.registry.eureka;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RegistrySpiTest {
+
+ @Test
+ void discoversPublishedRegistryKey() {
+ ShenyuInstanceRegisterRepository repository =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin("eureka");
+ assertEquals(EurekaInstanceRegisterRepository.class,
repository.getClass());
+ }
+}
diff --git
a/shenyu-registry/shenyu-registry-kubernetes/src/test/java/org/apache/shenyu/registry/kubernetes/RegistrySpiTest.java
b/shenyu-registry/shenyu-registry-kubernetes/src/test/java/org/apache/shenyu/registry/kubernetes/RegistrySpiTest.java
new file mode 100644
index 0000000000..fd2ad0a320
--- /dev/null
+++
b/shenyu-registry/shenyu-registry-kubernetes/src/test/java/org/apache/shenyu/registry/kubernetes/RegistrySpiTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.registry.kubernetes;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RegistrySpiTest {
+
+ @Test
+ void discoversPublishedRegistryKey() {
+ ShenyuInstanceRegisterRepository repository =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin("kubernetes");
+ assertEquals(KubernetesInstanceRegisterRepository.class,
repository.getClass());
+ }
+}
diff --git
a/shenyu-registry/shenyu-registry-nacos/src/test/java/org/apache/shenyu/registry/nacos/RegistrySpiTest.java
b/shenyu-registry/shenyu-registry-nacos/src/test/java/org/apache/shenyu/registry/nacos/RegistrySpiTest.java
new file mode 100644
index 0000000000..163c29778c
--- /dev/null
+++
b/shenyu-registry/shenyu-registry-nacos/src/test/java/org/apache/shenyu/registry/nacos/RegistrySpiTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.registry.nacos;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RegistrySpiTest {
+
+ @Test
+ void discoversPublishedRegistryKey() {
+ ShenyuInstanceRegisterRepository repository =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin("nacos");
+ assertEquals(NacosInstanceRegisterRepository.class,
repository.getClass());
+ }
+}
diff --git
a/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/RegistrySpiTest.java
b/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/RegistrySpiTest.java
new file mode 100644
index 0000000000..276224cc07
--- /dev/null
+++
b/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/RegistrySpiTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.registry.zookeeper;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class RegistrySpiTest {
+
+ @Test
+ void discoversPublishedRegistryKey() {
+ ShenyuInstanceRegisterRepository repository =
ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getJoin("zookeeper");
+ assertEquals(ZookeeperInstanceRegisterRepository.class,
repository.getClass());
+ }
+}
diff --git a/shenyu-sdk/shenyu-sdk-core/pom.xml
b/shenyu-sdk/shenyu-sdk-core/pom.xml
index f1f1edf178..eb5b8c8eb4 100644
--- a/shenyu-sdk/shenyu-sdk-core/pom.xml
+++ b/shenyu-sdk/shenyu-sdk-core/pom.xml
@@ -37,7 +37,7 @@
</dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-core</artifactId>
+ <artifactId>shenyu-registry-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
diff --git
a/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/ShenyuResponseTest.java
b/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/ShenyuResponseTest.java
index f05be79555..2919e5d9bc 100644
---
a/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/ShenyuResponseTest.java
+++
b/shenyu-sdk/shenyu-sdk-core/src/test/java/org/apache/shenyu/sdk/core/ShenyuResponseTest.java
@@ -17,7 +17,6 @@
package org.apache.shenyu.sdk.core;
-import org.apache.http.HttpStatus;
import org.junit.Assert;
import org.junit.Test;
@@ -40,7 +39,7 @@ public class ShenyuResponseTest {
headerMap, null, null, null);
ShenyuResponse response = new ShenyuResponse(
- HttpStatus.SC_OK,
+ 200,
"success",
headerMap,
body,
diff --git a/shenyu-sdk/shenyu-sdk-feign/pom.xml
b/shenyu-sdk/shenyu-sdk-feign/pom.xml
index b8e16b0d0f..ac50c5da29 100644
--- a/shenyu-sdk/shenyu-sdk-feign/pom.xml
+++ b/shenyu-sdk/shenyu-sdk-feign/pom.xml
@@ -32,7 +32,7 @@
</dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
- <artifactId>shenyu-registry-core</artifactId>
+ <artifactId>shenyu-registry-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/pom.xml
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/pom.xml
index 5b22fbc564..715b81f388 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/pom.xml
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/pom.xml
@@ -31,5 +31,32 @@
<artifactId>shenyu-registry-core</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <!-- Explicit providers preserve the existing distribution/classpath.
-->
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-zookeeper</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-etcd</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-nacos</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-eureka</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-kubernetes</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/src/test/java/org/apache/shenyu/springboot/starter/registry/RegistryClasspathTest.java
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/src/test/java/org/apache/shenyu/springboot/starter/registry/RegistryClasspathTest.java
new file mode 100644
index 0000000000..5207687513
--- /dev/null
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/src/test/java/org/apache/shenyu/springboot/starter/registry/RegistryClasspathTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.springboot.starter.registry;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.registry.api.config.RegisterConfig;
+import org.apache.shenyu.registry.api.entity.InstanceEntity;
+import org.apache.shenyu.registry.core.ShenyuInstanceRegisterRepositoryFactory;
+import org.apache.shenyu.spi.ExtensionLoader;
+import org.apache.shenyu.spi.Join;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.boot.web.context.WebServerInitializedEvent;
+import org.springframework.boot.web.server.WebServer;
+
+import java.util.Properties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class RegistryClasspathTest {
+
+ private final ApplicationContextRunner runner = new
ApplicationContextRunner()
+
.withConfiguration(AutoConfigurations.of(ConfigurationPropertiesAutoConfiguration.class,
ShenyuRegistryConfiguration.class))
+ .withPropertyValues("shenyu.register.enabled=true",
"shenyu.register.server-lists=localhost:9195");
+
+ @Test
+ void disabledRegistrationStartsWithoutARegistryConnection() {
+ runner.withPropertyValues("shenyu.register.enabled=false").run(context
-> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).doesNotHaveBean(RegistryListener.class);
+ });
+ }
+
+ @Test
+ void compatibilityStarterStillDiscoversItsOriginalProviders() {
+
assertThat(ExtensionLoader.getExtensionLoader(ShenyuInstanceRegisterRepository.class).getExtensionClasses1())
+ .containsKeys("zookeeper", "etcd", "nacos", "eureka",
"kubernetes");
+ }
+
+ @Test
+ void missingConfiguredProviderFailsWithDependencyHint() {
+
runner.withPropertyValues("shenyu.register.register-type=missing").run(context
-> {
+ assertThat(context).hasFailed();
+
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(IllegalArgumentException.class)
+
.hasStackTraceContaining("shenyu-registry-missing").hasStackTraceContaining("runtime
classpath");
+ });
+ }
+
+ @Test
+ void explicitlyProvidedSpiStartsAndReceivesBoundConfiguration() {
+
runner.withPropertyValues("shenyu.register.register-type=test-registry").run(context
-> {
+ assertThat(context).hasNotFailed();
+ assertThat(context).hasSingleBean(RegistryListener.class);
+ TestRepository repository = (TestRepository)
ShenyuInstanceRegisterRepositoryFactory.newInstance("test-registry");
+
assertThat(repository.config.getRegisterType()).isEqualTo("test-registry");
+
assertThat(repository.config.getServerLists()).isEqualTo("localhost:9195");
+ });
+ }
+
+ @Test
+ void selectedProviderRegistersGatewayOnce() {
+ Properties props = new Properties();
+ props.setProperty("host", "127.0.0.1");
+ props.setProperty("name", "test-gateway");
+ RegisterConfig config = new RegisterConfig("test-registry-event",
"localhost:9195", props);
+ RegistryListener listener = new RegistryListener(config);
+ WebServer server = mock(WebServer.class);
+ when(server.getPort()).thenReturn(9195);
+ WebServerInitializedEvent event =
mock(WebServerInitializedEvent.class);
+ when(event.getWebServer()).thenReturn(server);
+ listener.onApplicationEvent(event);
+ listener.onApplicationEvent(event);
+ TestRepository repository = (TestRepository)
ShenyuInstanceRegisterRepositoryFactory.newInstance("test-registry-event");
+ assertThat(repository.persistCount).isEqualTo(1);
+ assertThat(repository.instance.getAppName()).isEqualTo("test-gateway");
+ assertThat(repository.instance.getHost()).isEqualTo("127.0.0.1");
+ assertThat(repository.instance.getPort()).isEqualTo(9195);
+ }
+
+ /**
+ * External implementation supplied only on the test runtime classpath.
+ */
+ @Join(isSingleton = false)
+ public static class TestRepository implements
ShenyuInstanceRegisterRepository {
+
+ private RegisterConfig config;
+
+ private InstanceEntity instance;
+
+ private int persistCount;
+
+ @Override
+ public void init(final RegisterConfig registerConfig) {
+ config = registerConfig;
+ }
+
+ @Override
+ public void persistInstance(final InstanceEntity instanceEntity) {
+ instance = instanceEntity;
+ persistCount++;
+ }
+ }
+}
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
new file mode 100644
index 0000000000..a4093a18cf
--- /dev/null
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-registry/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+test-registry=org.apache.shenyu.springboot.starter.registry.RegistryClasspathTest$TestRepository
+test-registry-event=org.apache.shenyu.springboot.starter.registry.RegistryClasspathTest$TestRepository
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/pom.xml
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/pom.xml
index 7a6139623d..f40ebdc095 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/pom.xml
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/pom.xml
@@ -25,10 +25,21 @@
<artifactId>shenyu-spring-boot-starter-sdk-feign</artifactId>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-sdk-feign</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-openfeign</artifactId>
+ <version>4.1.3</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/src/test/java/org/apache/shenyu/springboot/starter/sdk/feign/RegistryClasspathTest.java
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/src/test/java/org/apache/shenyu/springboot/starter/sdk/feign/RegistryClasspathTest.java
new file mode 100644
index 0000000000..1ff57e4798
--- /dev/null
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/src/test/java/org/apache/shenyu/springboot/starter/sdk/feign/RegistryClasspathTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.springboot.starter.sdk.feign;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.registry.api.config.RegisterConfig;
+import org.apache.shenyu.registry.api.entity.InstanceEntity;
+import org.apache.shenyu.spi.Join;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RegistryClasspathTest {
+
+ private final ApplicationContextRunner runner = new
ApplicationContextRunner()
+
.withConfiguration(AutoConfigurations.of(ConfigurationPropertiesAutoConfiguration.class,
ShenyuSdkAutoConfiguration.class))
+ .withPropertyValues("shenyu.sdk.enabled=true",
"shenyu.sdk.server-lists=localhost:9195");
+
+ @Test
+ void localModeStartsWithoutARegistryImplementation() {
+
runner.withPropertyValues("shenyu.sdk.register-type=local").run(context -> {
+ assertThat(context).hasNotFailed();
+
assertThat(context.getBeanProvider(ShenyuInstanceRegisterRepository.class).getIfAvailable()).isNull();
+ });
+ }
+
+ @Test
+ void missingConfiguredProviderFailsWithDependencyHint() {
+
runner.withPropertyValues("shenyu.sdk.register-type=missing").run(context -> {
+ assertThat(context).hasFailed();
+
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(IllegalArgumentException.class)
+
.hasStackTraceContaining("shenyu-registry-missing").hasStackTraceContaining("runtime
classpath");
+ });
+ }
+
+ @Test
+ void explicitlyProvidedSpiStartsAndReceivesBoundConfiguration() {
+
runner.withPropertyValues("shenyu.sdk.register-type=test-sdk-feign").run(context
-> {
+ assertThat(context).hasNotFailed();
+
assertThat(context).hasSingleBean(ShenyuInstanceRegisterRepository.class);
+ TestRepository repository = (TestRepository)
context.getBean(ShenyuInstanceRegisterRepository.class);
+
assertThat(repository.config.getRegisterType()).isEqualTo("test-sdk-feign");
+
assertThat(repository.config.getServerLists()).isEqualTo("localhost:9195");
+ });
+ }
+
+ /**
+ * External implementation supplied only on the test runtime classpath.
+ */
+ @Join(isSingleton = false)
+ public static class TestRepository implements
ShenyuInstanceRegisterRepository {
+
+ private RegisterConfig config;
+
+ @Override
+ public void init(final RegisterConfig registerConfig) {
+ config = registerConfig;
+ }
+
+ @Override
+ public void persistInstance(final InstanceEntity instance) {
+ }
+ }
+}
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
new file mode 100644
index 0000000000..30cc62ccff
--- /dev/null
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk-feign/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+test-sdk-feign=org.apache.shenyu.springboot.starter.sdk.feign.RegistryClasspathTest$TestRepository
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/pom.xml
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/pom.xml
index a741dc7d8d..5807acca3e 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/pom.xml
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/pom.xml
@@ -25,6 +25,11 @@
<artifactId>shenyu-spring-boot-starter-sdk</artifactId>
<dependencies>
+ <dependency>
+ <groupId>org.apache.shenyu</groupId>
+ <artifactId>shenyu-registry-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-sdk-spring</artifactId>
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/src/test/java/org/apache/shenyu/springboot/starter/sdk/RegistryClasspathTest.java
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/src/test/java/org/apache/shenyu/springboot/starter/sdk/RegistryClasspathTest.java
new file mode 100644
index 0000000000..a863540cdc
--- /dev/null
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/src/test/java/org/apache/shenyu/springboot/starter/sdk/RegistryClasspathTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.springboot.starter.sdk;
+
+import org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository;
+import org.apache.shenyu.registry.api.config.RegisterConfig;
+import org.apache.shenyu.registry.api.entity.InstanceEntity;
+import org.apache.shenyu.spi.Join;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RegistryClasspathTest {
+
+ private final ApplicationContextRunner runner = new
ApplicationContextRunner()
+
.withConfiguration(AutoConfigurations.of(ConfigurationPropertiesAutoConfiguration.class,
ShenyuSdkAutoConfiguration.class))
+ .withPropertyValues("shenyu.sdk.enabled=true",
"shenyu.sdk.server-lists=localhost:9195");
+
+ @Test
+ void localModeStartsWithoutARegistryImplementation() {
+
runner.withPropertyValues("shenyu.sdk.register-type=local").run(context -> {
+ assertThat(context).hasNotFailed();
+
assertThat(context.getBeanProvider(ShenyuInstanceRegisterRepository.class).getIfAvailable()).isNull();
+ });
+ }
+
+ @Test
+ void missingConfiguredProviderFailsWithDependencyHint() {
+
runner.withPropertyValues("shenyu.sdk.register-type=missing").run(context -> {
+ assertThat(context).hasFailed();
+
assertThat(context.getStartupFailure()).hasRootCauseInstanceOf(IllegalArgumentException.class)
+
.hasStackTraceContaining("shenyu-registry-missing").hasStackTraceContaining("runtime
classpath");
+ });
+ }
+
+ @Test
+ void explicitlyProvidedSpiStartsAndReceivesBoundConfiguration() {
+
runner.withPropertyValues("shenyu.sdk.register-type=test-sdk").run(context -> {
+ assertThat(context).hasNotFailed();
+
assertThat(context).hasSingleBean(ShenyuInstanceRegisterRepository.class);
+ TestRepository repository = (TestRepository)
context.getBean(ShenyuInstanceRegisterRepository.class);
+
assertThat(repository.config.getRegisterType()).isEqualTo("test-sdk");
+
assertThat(repository.config.getServerLists()).isEqualTo("localhost:9195");
+ });
+ }
+
+ /**
+ * External implementation supplied only on the test runtime classpath.
+ */
+ @Join(isSingleton = false)
+ public static class TestRepository implements
ShenyuInstanceRegisterRepository {
+
+ private RegisterConfig config;
+
+ @Override
+ public void init(final RegisterConfig registerConfig) {
+ config = registerConfig;
+ }
+
+ @Override
+ public void persistInstance(final InstanceEntity instance) {
+ }
+ }
+}
diff --git
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
new file mode 100644
index 0000000000..709c5b58df
--- /dev/null
+++
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-sdk/src/test/resources/META-INF/shenyu/org.apache.shenyu.registry.api.ShenyuInstanceRegisterRepository
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+test-sdk=org.apache.shenyu.springboot.starter.sdk.RegistryClasspathTest$TestRepository