\s*\{@code\s*(.*?)\}', re.DOTALL)
+CODE_EXAMPLE_ALT_RE = re.compile(r'\s*(.*?)', re.DOTALL) + +# HTML tags used in Javadoc +LINK_TAG_RE = re.compile(r'\{@link\s+([^}]+)\}') +CODE_TAG_RE = re.compile(r'\{@code\s+([^}]+)\}') +LINKPLAIN_TAG_RE = re.compile(r'\{@linkplain\s+([^}]+)\}') +VALUE_TAG_RE = re.compile(r'\{@value\s+([^}]+)\}') + +# Method/field signatures +PUBLIC_METHOD_RE = re.compile( + r'(?:/\*\*(.*?)\*/\s*)?' + r'(?:@\w+(?:\([^)]*\))?\s*)*' + r'(public\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?' + r'(?:<[^>]+>\s+)?' + r'\S+\s+' # return type + r'(\w+)\s*' # method name + r'\([^)]*\))', # parameters + re.DOTALL, +) + + +# ────────────────────────────────────────────── +# Javadoc Parsing Utilities +# ────────────────────────────────────────────── + +def clean_javadoc_line(line): + """Remove leading whitespace, asterisks, and extra spaces from a Javadoc line.""" + line = line.strip() + if line.startswith('*'): + line = line[1:] + if line.startswith(' '): + line = line[1:] + return line + + +def parse_javadoc(javadoc_text): + """Parse a Javadoc comment block into structured components.""" + if not javadoc_text: + return { + "description": "", + "since": "", + "author": "", + "see": [], + "params": [], + "examples": [], + } + + lines = javadoc_text.split('\n') + cleaned_lines = [clean_javadoc_line(line) for line in lines] + full_text = '\n'.join(cleaned_lines) + + # Extract tags + since_match = SINCE_TAG_RE.search(full_text) + since = since_match.group(1).strip() if since_match else "" + + author_match = AUTHOR_TAG_RE.search(full_text) + author = author_match.group(1).strip() if author_match else "" + # Clean HTML from author + author = re.sub(r'<[^>]+>', '', author).strip() + + see_matches = SEE_TAG_RE.findall(full_text) + see_refs = [s.strip() for s in see_matches] + + # Extract description (text before any @tag) + desc_lines = [] + for line in cleaned_lines: + stripped = line.strip() + if stripped.startswith('@'): + break + desc_lines.append(line) + description = '\n'.join(desc_lines).strip() + + # Extract code examples + examples = [] + for match in CODE_EXAMPLE_RE.finditer(javadoc_text): + code = match.group(1).strip() + # Clean Javadoc asterisks from code lines + code_lines = code.split('\n') + cleaned_code = '\n'.join(clean_javadoc_line(l) for l in code_lines) + examples.append(cleaned_code.strip()) + + if not examples: + for match in CODE_EXAMPLE_ALT_RE.finditer(javadoc_text): + code = match.group(1).strip() + if '{@code' not in code and len(code) > 10: + code_lines = code.split('\n') + cleaned_code = '\n'.join(clean_javadoc_line(l) for l in code_lines) + examples.append(cleaned_code.strip()) + + return { + "description": description, + "since": since, + "author": author, + "see": see_refs, + "params": [], + "examples": examples, + } + + +def convert_javadoc_to_markdown(text): + """Convert Javadoc HTML/tags to Markdown.""" + if not text: + return "" + + # Convert {@link ...} to `...` + text = LINK_TAG_RE.sub(r'`\1`', text) + text = LINKPLAIN_TAG_RE.sub(r'`\1`', text) + text = CODE_TAG_RE.sub(r'`\1`', text) + text = VALUE_TAG_RE.sub(r'`\1`', text) + + # Convert basic HTML + text = re.sub(r'
', '\n\n', text) + text = re.sub(r'
', '', text) + text = re.sub(r'(.*?)', r'`\1`', text)
+ text = re.sub(r')
+ text = re.sub(r'<(?!pre|/pre)[^>]+>', '', text)
+
+ return text.strip()
+
+
+# ──────────────────────────────────────────────
+# Java Source File Parser
+# ──────────────────────────────────────────────
+
+class JavaComponent:
+ """Represents a parsed Java component (class, interface, enum, annotation)."""
+
+ def __init__(self):
+ self.name = ""
+ self.package = ""
+ self.module = ""
+ self.component_type = "" # class, interface, enum, annotation
+ self.description = ""
+ self.since_version = ""
+ self.author = ""
+ self.see_refs = []
+ self.examples = []
+ self.extends_class = ""
+ self.implements_interfaces = []
+ self.declaration_line = ""
+ self.public_methods = []
+ self.source_path = ""
+
+ @property
+ def fully_qualified_name(self):
+ if self.package:
+ return f"{self.package}.{self.name}"
+ return self.name
+
+ @property
+ def wiki_page_name(self):
+ """Generate a wiki-friendly page name."""
+ return self.fully_qualified_name.replace('.', '-')
+
+
+class JavaMethod:
+ """Represents a parsed public method."""
+
+ def __init__(self):
+ self.name = ""
+ self.signature = ""
+ self.description = ""
+ self.since_version = ""
+ self.examples = []
+ self.params = []
+
+
+def parse_java_file(filepath, module_name):
+ """Parse a Java source file and extract component information."""
+ try:
+ with open(filepath, 'r', encoding='utf-8') as f:
+ content = f.read()
+ except (IOError, UnicodeDecodeError):
+ return None
+
+ # Extract package
+ pkg_match = PACKAGE_RE.search(content)
+ package_name = pkg_match.group(1) if pkg_match else ""
+
+ # Check for package-info.java
+ if os.path.basename(filepath) == 'package-info.java':
+ return None
+
+ # Find the class/interface/enum/annotation declaration
+ # First, find the Javadoc that precedes the class declaration
+ class_javadoc = None
+ class_decl_match = None
+
+ # Strategy: find all Javadoc blocks and the class declaration
+ javadoc_blocks = list(JAVADOC_BLOCK_RE.finditer(content))
+
+ # Find the main type declaration
+ for line in content.split('\n'):
+ stripped = line.strip()
+ if re.match(r'(?:public\s+)?(?:abstract\s+)?(?:final\s+)?(?:class|interface|enum|@interface)\s+', stripped):
+ class_decl_match = stripped
+ break
+
+ if not class_decl_match:
+ return None
+
+ # Determine component type (check @interface before interface)
+ comp_type = "class"
+ if re.search(r'@interface\s+', class_decl_match):
+ comp_type = "annotation"
+ elif re.search(r'\binterface\s+', class_decl_match):
+ comp_type = "interface"
+ elif re.search(r'\benum\s+', class_decl_match):
+ comp_type = "enum"
+
+ # Extract class name
+ name_match = re.search(
+ r'(?:class|interface|enum|@interface)\s+(\w+)', class_decl_match
+ )
+ if not name_match:
+ return None
+
+ class_name = name_match.group(1)
+
+ # Skip non-public classes, inner classes, and module-info
+ if 'public' not in class_decl_match and comp_type != "annotation":
+ # Check if the file name matches the class name (top-level class)
+ file_basename = os.path.splitext(os.path.basename(filepath))[0]
+ if file_basename != class_name:
+ return None
+
+ if class_name in ('module-info', 'package-info'):
+ return None
+
+ # Find the class-level Javadoc (the last Javadoc before the class declaration)
+ class_decl_pos = content.find(class_decl_match)
+ for jd_block in reversed(javadoc_blocks):
+ if jd_block.end() <= class_decl_pos:
+ # Verify there's no other declaration between this Javadoc and the class
+ between = content[jd_block.end():class_decl_pos].strip()
+ # Remove annotations between Javadoc and class decl
+ between_cleaned = re.sub(r'@\w+(?:\([^)]*\))?', '', between).strip()
+ if not between_cleaned or between_cleaned.startswith('@'):
+ class_javadoc = jd_block.group(1)
+ break
+
+ # Parse the class Javadoc
+ javadoc_info = parse_javadoc(class_javadoc)
+
+ # Check for @Since annotation on the class (limit search to nearby context)
+ search_start = max(0, class_decl_pos - 2000)
+ since_annotation = SINCE_ANNOTATION_RE.search(content[search_start:class_decl_pos + len(class_decl_match)])
+ annotation_since = since_annotation.group(1) if since_annotation else ""
+
+ # Extract extends/implements
+ extends_match = re.search(r'\bextends\s+([\w.<>, ]+?)(?:\s+implements|\s*\{)', class_decl_match)
+ extends_class = extends_match.group(1).strip() if extends_match else ""
+
+ implements_match = re.search(r'\bimplements\s+([\w.<>, ]+?)(?:\s*\{|$)', class_decl_match)
+ implements_interfaces = []
+ if implements_match:
+ impl_str = implements_match.group(1).strip().rstrip('{').strip()
+ implements_interfaces = [i.strip() for i in impl_str.split(',') if i.strip()]
+
+ # Parse public methods
+ public_methods = extract_public_methods(content, class_decl_pos)
+
+ # Build the component
+ component = JavaComponent()
+ component.name = class_name
+ component.package = package_name
+ component.module = module_name
+ component.component_type = comp_type
+ component.description = javadoc_info["description"]
+ component.since_version = javadoc_info["since"] or annotation_since
+ component.author = javadoc_info["author"]
+ component.see_refs = javadoc_info["see"]
+ component.examples = javadoc_info["examples"]
+ component.extends_class = extends_class
+ component.implements_interfaces = implements_interfaces
+ component.declaration_line = class_decl_match.rstrip('{').strip()
+ component.public_methods = public_methods
+ component.source_path = os.path.relpath(filepath, PROJECT_ROOT)
+
+ return component
+
+
+def extract_public_methods(content, class_start_pos):
+ """Extract public methods from the class body."""
+ methods = []
+ # Only look at content after class declaration
+ body = content[class_start_pos:]
+
+ for match in PUBLIC_METHOD_RE.finditer(body):
+ javadoc_text = match.group(1)
+ full_signature = match.group(2)
+ method_name = match.group(3)
+
+ # Skip constructors, getters/setters that are trivial
+ if method_name in ('toString', 'hashCode', 'equals', 'clone'):
+ continue
+
+ method = JavaMethod()
+ method.name = method_name
+ method.signature = full_signature.strip()
+
+ if javadoc_text:
+ method_jd = parse_javadoc(javadoc_text)
+ method.description = method_jd["description"]
+ method.since_version = method_jd["since"]
+ method.examples = method_jd["examples"]
+
+ methods.append(method)
+
+ return methods[:20] # Limit to 20 methods per class to keep docs manageable
+
+
+# ──────────────────────────────────────────────
+# Wiki Page Generator
+# ──────────────────────────────────────────────
+
+def generate_wiki_page(component):
+ """Generate a Markdown wiki page for a Java component."""
+ lines = []
+
+ # Title
+ type_label = component.component_type.capitalize()
+ lines.append(f"# {component.name}")
+ lines.append("")
+
+ # Metadata badge line
+ badges = []
+ badges.append(f"**Type:** `{type_label}`")
+ badges.append(f"**Module:** `{component.module}`")
+ badges.append(f"**Package:** `{component.package}`")
+ if component.since_version:
+ badges.append(f"**Since:** `{component.since_version}`")
+ lines.append(" | ".join(badges))
+ lines.append("")
+
+ # Source link
+ lines.append(f"> **Source:** [`{component.source_path}`]"
+ f"(https://github.com/microsphere-projects/{ARTIFACT_ID}/blob/main/{component.source_path})")
+ lines.append("")
+
+ # ── Overview ──
+ lines.append("## Overview")
+ lines.append("")
+ if component.description:
+ desc_md = convert_javadoc_to_markdown(component.description)
+ lines.append(desc_md)
+ else:
+ lines.append(f"`{component.name}` is a {type_label.lower()} in the "
+ f"`{component.package}` package of the `{component.module}` module.")
+ lines.append("")
+
+ # Declaration
+ lines.append("### Declaration")
+ lines.append("")
+ lines.append("```java")
+ lines.append(component.declaration_line)
+ lines.append("```")
+ lines.append("")
+
+ # ── Author ──
+ if component.author:
+ lines.append(f"**Author:** {component.author}")
+ lines.append("")
+
+ # ── Since / Version Info ──
+ lines.append("## Version Information")
+ lines.append("")
+ if component.since_version:
+ lines.append(f"- **Introduced in:** `{component.since_version}`")
+ else:
+ lines.append(f"- **Introduced in:** `{PROJECT_VERSION}` (current)")
+ lines.append(f"- **Current Project Version:** `{PROJECT_VERSION}`")
+ lines.append("")
+
+ # ── Version Compatibility ──
+ lines.append("## Version Compatibility")
+ lines.append("")
+ lines.append("This component is tested and compatible with the following Java versions:")
+ lines.append("")
+ lines.append("| Java Version | Status |")
+ lines.append("|:---:|:---:|")
+ for v in JAVA_VERSIONS:
+ lines.append(f"| Java {v} | ✅ Compatible |")
+ lines.append("")
+
+ # ── Examples ──
+ has_examples = bool(component.examples)
+ if not has_examples:
+ # Check methods for examples
+ for method in component.public_methods:
+ if method.examples:
+ has_examples = True
+ break
+
+ if has_examples:
+ lines.append("## Examples")
+ lines.append("")
+
+ if component.examples:
+ for i, example in enumerate(component.examples, 1):
+ if len(component.examples) > 1:
+ lines.append(f"### Example {i}")
+ lines.append("")
+ lines.append("```java")
+ lines.append(example)
+ lines.append("```")
+ lines.append("")
+
+ # Method-level examples
+ method_examples_added = False
+ for method in component.public_methods:
+ if method.examples:
+ if not method_examples_added:
+ lines.append(f"### Method Examples")
+ lines.append("")
+ method_examples_added = True
+ lines.append(f"#### `{method.name}`")
+ lines.append("")
+ for example in method.examples:
+ lines.append("```java")
+ lines.append(example)
+ lines.append("```")
+ lines.append("")
+
+ # ── Usage Guide ──
+ lines.append("## Usage")
+ lines.append("")
+ lines.append("### Maven Dependency")
+ lines.append("")
+ lines.append("Add the following dependency to your `pom.xml`:")
+ lines.append("")
+ lines.append("```xml")
+ lines.append("")
+ lines.append(" io.github.microsphere-projects ")
+ lines.append(f" {component.module} ")
+ lines.append(f" ${{{ARTIFACT_ID}.version}} ")
+ lines.append(" ")
+ lines.append("```")
+ lines.append("")
+ lines.append(f"> **Tip:** Use the BOM (`{ARTIFACT_ID}-dependencies`) for consistent version management. "
+ f"See the [Getting Started](https://github.com/microsphere-projects/{ARTIFACT_ID}#getting-started) guide.")
+ lines.append("")
+
+ # ── Import ──
+ lines.append("### Import")
+ lines.append("")
+ lines.append("```java")
+ lines.append(f"import {component.fully_qualified_name};")
+ lines.append("```")
+ lines.append("")
+
+ # ── Public API ──
+ if component.public_methods:
+ lines.append("## API Reference")
+ lines.append("")
+ lines.append("### Public Methods")
+ lines.append("")
+ lines.append("| Method | Description |")
+ lines.append("|--------|-------------|")
+ for method in component.public_methods:
+ desc = method.description.split('\n')[0] if method.description else ""
+ desc = convert_javadoc_to_markdown(desc)
+ # Truncate long descriptions for the table
+ if len(desc) > 120:
+ desc = desc[:117] + "..."
+ sig = method.signature.replace('|', '\\|')
+ lines.append(f"| `{method.name}` | {desc} |")
+ lines.append("")
+
+ # Detailed method descriptions
+ has_detailed_methods = any(
+ m.description and len(m.description) > 50 for m in component.public_methods
+ )
+ if has_detailed_methods:
+ lines.append("### Method Details")
+ lines.append("")
+ for method in component.public_methods:
+ if method.description and len(method.description) > 50:
+ lines.append(f"#### `{method.name}`")
+ lines.append("")
+ lines.append(f"```java")
+ lines.append(method.signature)
+ lines.append("```")
+ lines.append("")
+ desc_md = convert_javadoc_to_markdown(method.description)
+ lines.append(desc_md)
+ lines.append("")
+ if method.since_version:
+ lines.append(f"*Since: {method.since_version}*")
+ lines.append("")
+
+ # ── See Also ──
+ if component.see_refs:
+ lines.append("## See Also")
+ lines.append("")
+ for ref in component.see_refs:
+ ref_clean = ref.strip()
+ if ref_clean:
+ lines.append(f"- `{ref_clean}`")
+ lines.append("")
+
+ # ── Footer ──
+ lines.append("---")
+ lines.append("")
+ lines.append(f"*This documentation was auto-generated from the source code of "
+ f"[{ARTIFACT_ID}](https://github.com/microsphere-projects/{ARTIFACT_ID}).*")
+ lines.append("")
+
+ return '\n'.join(lines)
+
+
+def generate_home_page(components_by_module):
+ """Generate the Home (index) wiki page."""
+ lines = []
+ lines.append(f"# {PROJECT_TITLE} - API Documentation")
+ lines.append("")
+ lines.append(f"Welcome to the **{PROJECT_TITLE}** wiki! This documentation is auto-generated "
+ f"from the project source code and provides detailed information about each Java component.")
+ lines.append("")
+ lines.append("## Project Information")
+ lines.append("")
+ lines.append(f"- **Current Version:** `{PROJECT_VERSION}`")
+ lines.append(f"- **Java Compatibility:** {', '.join('Java ' + v for v in JAVA_VERSIONS)}")
+ lines.append("- **License:** Apache License 2.0")
+ lines.append(f"- **Repository:** [microsphere-projects/{ARTIFACT_ID}]"
+ f"(https://github.com/microsphere-projects/{ARTIFACT_ID})")
+ lines.append("")
+
+ # Table of Contents by module
+ lines.append("## Modules")
+ lines.append("")
+
+ for module_name, components in components_by_module.items():
+ lines.append(f"### {module_name}")
+ lines.append("")
+
+ # Group by package
+ by_package = OrderedDict()
+ for comp in components:
+ pkg = comp.package or "(default)"
+ if pkg not in by_package:
+ by_package[pkg] = []
+ by_package[pkg].append(comp)
+
+ for pkg, comps in by_package.items():
+ lines.append(f"**`{pkg}`**")
+ lines.append("")
+ for comp in sorted(comps, key=lambda c: c.name):
+ type_icon = {
+ "class": "📦",
+ "interface": "🔌",
+ "enum": "🔢",
+ "annotation": "🏷️",
+ }.get(comp.component_type, "📄")
+ wiki_link = comp.wiki_page_name
+ lines.append(f"- {type_icon} [{comp.name}]({wiki_link}) - "
+ f"{comp.component_type.capitalize()}"
+ f"{' - Since ' + comp.since_version if comp.since_version else ''}")
+ lines.append("")
+
+ # Quick links
+ lines.append("## Quick Links")
+ lines.append("")
+ lines.append(f"- [Getting Started](https://github.com/microsphere-projects/{ARTIFACT_ID}#getting-started)")
+ lines.append(f"- [Building from Source](https://github.com/microsphere-projects/{ARTIFACT_ID}#building-from-source)")
+ lines.append(f"- [Contributing](https://github.com/microsphere-projects/{ARTIFACT_ID}#contributing)")
+ lines.append("- [JavaDoc](https://javadoc.io/doc/io.github.microsphere-projects)")
+ lines.append("")
+ lines.append("---")
+ lines.append("")
+ lines.append(f"*This wiki is auto-generated from the source code of "
+ f"[{ARTIFACT_ID}](https://github.com/microsphere-projects/{ARTIFACT_ID}). "
+ f"To update, trigger the `wiki-publish` workflow.*")
+ lines.append("")
+
+ return '\n'.join(lines)
+
+
+def generate_sidebar(components_by_module):
+ """Generate the _Sidebar wiki page for navigation."""
+ lines = []
+ lines.append("**[Home](Home)**")
+ lines.append("")
+
+ for module_name, components in components_by_module.items():
+ # Shorten module name for sidebar
+ short_name = module_name.replace("microsphere-", "")
+ lines.append(f"**{short_name}**")
+ lines.append("")
+ for comp in sorted(components, key=lambda c: c.name):
+ wiki_link = comp.wiki_page_name
+ lines.append(f"- [{comp.name}]({wiki_link})")
+ lines.append("")
+
+ return '\n'.join(lines)
+
+
+# ──────────────────────────────────────────────
+# Main
+# ──────────────────────────────────────────────
+
+def discover_java_files(project_root, modules):
+ """Discover all main Java source files in the given modules."""
+ java_files = []
+ for module in modules:
+ src_dir = os.path.join(project_root, module, SRC_MAIN_JAVA)
+ if not os.path.isdir(src_dir):
+ continue
+ for root, _dirs, files in os.walk(src_dir):
+ for fname in files:
+ if fname.endswith('.java') and fname != 'package-info.java' and fname != 'module-info.java':
+ java_files.append((os.path.join(root, fname), module))
+ return java_files
+
+
+def main():
+ parser = argparse.ArgumentParser(description=f"Generate wiki documentation for {ARTIFACT_ID}")
+ parser.add_argument(
+ "--output", "-o",
+ default=os.path.join(PROJECT_ROOT, "wiki"),
+ help="Output directory for generated wiki pages (default: /wiki)",
+ )
+ parser.add_argument(
+ "--project-root",
+ default=PROJECT_ROOT,
+ help=f"Root directory of the {ARTIFACT_ID} project",
+ )
+ args = parser.parse_args()
+
+ project_root = args.project_root
+ output_dir = args.output
+
+ print(f"{PROJECT_TITLE} Wiki Documentation Generator")
+ print(f" Project root: {project_root}")
+ print(f" Output dir: {output_dir}")
+ print()
+
+ # Discover Java files
+ java_files = discover_java_files(project_root, MODULES)
+ print(f"Found {len(java_files)} Java source files across {len(MODULES)} modules")
+ print()
+
+ # Parse all files
+ components = []
+ for filepath, module_name in java_files:
+ component = parse_java_file(filepath, module_name)
+ if component:
+ components.append(component)
+
+ print(f"Parsed {len(components)} Java components")
+ print()
+
+ # Group by module
+ components_by_module = OrderedDict()
+ for module in MODULES:
+ module_components = [c for c in components if c.module == module]
+ if module_components:
+ components_by_module[module] = sorted(module_components, key=lambda c: (c.package, c.name))
+
+ # Create output directory
+ os.makedirs(output_dir, exist_ok=True)
+
+ # Generate individual wiki pages
+ page_count = 0
+ for module_name, module_components in components_by_module.items():
+ for comp in module_components:
+ page_content = generate_wiki_page(comp)
+ page_filename = f"{comp.wiki_page_name}.md"
+ page_path = os.path.join(output_dir, page_filename)
+ with open(page_path, 'w', encoding='utf-8') as f:
+ f.write(page_content)
+ page_count += 1
+
+ print(f"Generated {page_count} wiki pages")
+
+ # Generate Home page
+ home_content = generate_home_page(components_by_module)
+ with open(os.path.join(output_dir, "Home.md"), 'w', encoding='utf-8') as f:
+ f.write(home_content)
+ print("Generated Home.md")
+
+ # Generate Sidebar
+ sidebar_content = generate_sidebar(components_by_module)
+ with open(os.path.join(output_dir, "_Sidebar.md"), 'w', encoding='utf-8') as f:
+ f.write(sidebar_content)
+ print("Generated _Sidebar.md")
+
+ print()
+ print(f"Wiki documentation generated successfully in: {output_dir}")
+ print(f"Total pages: {page_count + 2} ({page_count} components + Home + Sidebar)")
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml
index df4fe003..366b2dc0 100644
--- a/.github/workflows/maven-build.yml
+++ b/.github/workflows/maven-build.yml
@@ -10,39 +10,43 @@ name: Maven Build
on:
push:
- branches: [ 'dev-1.x' ]
+ branches: [ 'main', 'dev' ]
pull_request:
- branches: [ 'dev-1.x' , 'release-1.x' ]
+ branches: [ 'main', 'dev' , 'release' ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [ '8', '11' , '17' , '21' ]
- maven-profile-spring-cloud: [ 'spring-cloud-hoxton' , 'spring-cloud-2020' , 'spring-cloud-2021' ]
+ java: [ '17' , '21' , '25' ]
+ maven-profile-spring-cloud: [ 'spring-cloud-2022' , 'spring-cloud-2023' , 'spring-cloud-2024' , 'spring-cloud-2025' ]
steps:
- name: Checkout Source
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
+
+ - name: Setup Testcontainers Cloud Client
+ uses: atomicjar/testcontainers-cloud-setup-action@v1
+ with:
+ token: ${{ secrets.TC_CLOUD_TOKEN }}
- name: Setup JDK ${{ matrix.Java }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
- cache: maven
- name: Build with Maven
- run: mvn
+ run: ./mvnw
--batch-mode
--update-snapshots
--file pom.xml
-Drevision=0.0.1-SNAPSHOT
test
- --activate-profiles test,coverage,${{ matrix.maven-profile-spring-cloud }}
+ --activate-profiles test,coverage,testcontainers,${{ matrix.maven-profile-spring-cloud }}
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- slug: microsphere-projects/microsphere-spring-boot
\ No newline at end of file
+ slug: microsphere-projects/microsphere-spring-cloud
\ No newline at end of file
diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml
index 241727bb..7b1b44ed 100644
--- a/.github/workflows/maven-publish.yml
+++ b/.github/workflows/maven-publish.yml
@@ -10,26 +10,33 @@ name: Maven Publish
on:
push:
- branches: [ 'release-1.x' ]
+ branches: [ 'release' ]
workflow_dispatch:
inputs:
revision:
- description: 'The version to publish'
+ description: 'The version to publish for Spring Cloud 2022+ and JDK 17+'
required: true
- default: '0.0.1-SNAPSHOT'
+ default: '${major}.${minor}.${patch}'
jobs:
build:
runs-on: ubuntu-latest
- if: ${{ inputs.revision }}
+ if: ${{ inputs.revision }}
steps:
+ - name: Validate version format
+ run: |
+ if ! echo "${{ inputs.revision }}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
+ echo "Error: version '${{ inputs.revision }}' does not match the required pattern major.minor.patch"
+ exit 1
+ fi
+
- name: Checkout Source
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Setup Maven Central Repository
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
- java-version: '11'
+ java-version: '17'
distribution: 'temurin'
server-id: ossrh
server-username: MAVEN_USERNAME
@@ -37,10 +44,72 @@ jobs:
cache: maven
- name: Publish package
- run: mvn --batch-mode --update-snapshots -Drevision=${{ inputs.revision }} -Dgpg.skip=true -Prelease,ci clean deploy
+ run: mvn
+ --batch-mode
+ --update-snapshots
+ --file pom.xml
+ -Drevision=${{ inputs.revision }}
+ -Dgpg.skip=true
+ deploy
+ --activate-profiles publish,ci
env:
MAVEN_USERNAME: ${{ secrets.OSS_SONATYPE_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSS_SONATYPE_PASSWORD }}
- SIGN_KEY_ID: ${{ secrets.OSS_SIGNING_KEY_ID_LONG }}
+ SIGN_KEY_ID: ${{ secrets.OSS_SIGNING_KEY_ID_LONG }}
SIGN_KEY: ${{ secrets.OSS_SIGNING_KEY }}
SIGN_KEY_PASS: ${{ secrets.OSS_SIGNING_PASSWORD }}
+
+
+ release:
+ runs-on: ubuntu-latest
+ needs: build
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout Source
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+
+ - name: Create Tag
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ if git rev-parse "${{ inputs.revision }}" >/dev/null 2>&1; then
+ echo "Tag ${{ inputs.revision }} already exists, skipping."
+ else
+ git tag ${{ inputs.revision }}
+ git push origin ${{ inputs.revision }}
+ fi
+
+ - name: Create Release
+ run: |
+ if gh release view "v${{ inputs.revision }}" >/dev/null 2>&1; then
+ echo "Release v${{ inputs.revision }} already exists, skipping."
+ else
+ gh release create v${{ inputs.revision }} \
+ --title "v${{ inputs.revision }}" \
+ --generate-notes \
+ --latest
+ fi
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Increment patch version
+ run: |
+ CURRENT="${{ inputs.revision }}"
+ MAJOR=$(echo "$CURRENT" | cut -d. -f1)
+ MINOR=$(echo "$CURRENT" | cut -d. -f2)
+ PATCH=$(echo "$CURRENT" | cut -d. -f3)
+ NEXT_PATCH=$((PATCH + 1))
+ NEXT_VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}-SNAPSHOT"
+ sed -i "s|^\( *\)[^<]* |\1${NEXT_VERSION} |" pom.xml
+ echo "Bumped version from ${CURRENT} to ${NEXT_VERSION}"
+
+ - name: Commit and push next version
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add pom.xml
+ git diff --cached --quiet && echo "No changes to commit" || \
+ git commit -m "chore: bump version to next patch after publishing ${{ inputs.revision }}" && git push
\ No newline at end of file
diff --git a/.github/workflows/merge-main-to-branches.yml b/.github/workflows/merge-main-to-branches.yml
new file mode 100644
index 00000000..f4715238
--- /dev/null
+++ b/.github/workflows/merge-main-to-branches.yml
@@ -0,0 +1,66 @@
+# This workflow automatically merges the 'main' branch into 'dev' and 'release' branches
+# whenever changes are pushed to 'main', without requiring manual confirmation.
+
+name: Merge Main to Dev and Release
+
+on:
+ push:
+ branches:
+ - main
+
+concurrency:
+ group: merge-main-to-branches
+ cancel-in-progress: false
+
+jobs:
+ merge-to-dev:
+ name: Merge main → dev
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Merge main into dev
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ if ! git checkout dev; then
+ echo "::error::Branch 'dev' does not exist. Skipping merge."
+ exit 1
+ fi
+ if ! git merge --no-ff origin/main -m "chore: merge main into dev [skip ci]"; then
+ echo "::error::Merge conflict detected when merging main into dev. Manual intervention required."
+ exit 1
+ fi
+ git push origin dev
+
+ merge-to-release:
+ name: Merge main → release
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Merge main into release
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ if ! git checkout release; then
+ echo "::error::Branch 'release' does not exist. Skipping merge."
+ exit 1
+ fi
+ if ! git merge --no-ff origin/main -m "chore: merge main into release [skip ci]"; then
+ echo "::error::Merge conflict detected when merging main into release. Manual intervention required."
+ exit 1
+ fi
+ git push origin release
diff --git a/.github/workflows/wiki-publish.yml b/.github/workflows/wiki-publish.yml
new file mode 100644
index 00000000..53f78fb6
--- /dev/null
+++ b/.github/workflows/wiki-publish.yml
@@ -0,0 +1,79 @@
+name: Generate and Publish Wiki Documentation
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - '*/src/main/java/**/*.java'
+ - '.github/scripts/generate-wiki-docs.py'
+ - '.github/workflows/wiki-publish.yml'
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ generate-wiki:
+ name: Generate Wiki Documentation
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source repository
+ uses: actions/checkout@v5
+ with:
+ fetch-depth: 1
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Generate wiki pages
+ run: |
+ python .github/scripts/generate-wiki-docs.py --output wiki-output
+ echo "Generated wiki pages:"
+ ls -la wiki-output/ | head -20
+ echo "Total pages: $(ls wiki-output/*.md | wc -l)"
+
+ - name: Checkout wiki repository
+ uses: actions/checkout@v5
+ with:
+ repository: ${{ github.repository }}.wiki
+ path: wiki-repo
+ token: ${{ secrets.GITHUB_TOKEN }}
+ continue-on-error: true
+
+ - name: Initialize wiki repository if needed
+ run: |
+ if [ ! -d "wiki-repo/.git" ]; then
+ echo "Wiki repository not found. Creating initial wiki structure..."
+ mkdir -p wiki-repo
+ cd wiki-repo
+ git init
+ git remote add origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.wiki.git"
+ fi
+
+ - name: Copy generated pages to wiki
+ run: |
+ # Remove all existing markdown files from wiki repo to ensure
+ # pages for renamed or deleted classes are properly cleaned up
+ find wiki-repo -maxdepth 1 -name '*.md' -type f -delete 2>/dev/null || true
+ # Copy all generated markdown files to the wiki repo
+ cp wiki-output/*.md wiki-repo/
+ echo "Copied wiki pages to wiki repository"
+ ls -la wiki-repo/*.md | head -20
+
+ - name: Push to wiki
+ run: |
+ cd wiki-repo
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add -A
+ if git diff --cached --quiet; then
+ echo "No changes to wiki documentation"
+ else
+ TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M:%S UTC")
+ git commit -m "Update wiki documentation - ${TIMESTAMP}"
+ git push origin HEAD:master || git push origin HEAD:main
+ echo "Wiki documentation updated successfully"
+ fi
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
deleted file mode 100755
index bf82ff01..00000000
Binary files a/.mvn/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index d83cf136..423c23e5 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,16 +1,3 @@
-# Copyright 2013-2023 the original author or authors.
-#
-# Licensed 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
-#
-# https://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.
-
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
\ No newline at end of file
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://maven.aliyun.com/repository/public/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..d57aa809
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,48 @@
+# Contributor Code of Conduct
+
+As contributors and maintainers of this project, and in the interest of
+fostering an open and welcoming community, we pledge to respect all people who
+contribute through reporting issues, posting feature requests, updating
+documentation, submitting pull requests or patches, and other activities.
+
+We are committed to making participation in this project a harassment-free
+experience for everyone, regardless of level of experience, gender, gender
+identity and expression, sexual orientation, disability, personal appearance,
+body size, race, ethnicity, age, religion, or nationality.
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery
+* Personal attacks
+* Trolling or insulting/derogatory comments
+* Public or private harassment
+* Publishing other's private information, such as physical or electronic
+ addresses, without explicit permission
+* Other unethical or unprofessional conduct
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+By adopting this Code of Conduct, project maintainers commit themselves to
+fairly and consistently applying these principles to every aspect of managing
+this project. Project maintainers who do not follow or enforce the Code of
+Conduct may be permanently removed from the project team.
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community.
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting a project maintainer at [mercyblitz@gmail.com](mailto:mercyblitz@gmail.com). All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. Maintainers are
+obligated to maintain confidentiality with regard to the reporter of an
+incident.
+
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.3.0, available at https://www.contributor-covenant.org/version/1/3/0/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
\ No newline at end of file
diff --git a/README.md b/README.md
index e2791caa..eae413cb 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,143 @@
-# microsphere-spring-cloud
-Microsphere Projects for Spring Cloud
+# Microsphere Spring Cloud
+
+> Microsphere Projects for Spring Cloud
+
+[](https://deepwiki.com/microsphere-projects/microsphere-spring-cloud)
+[](https://zread.ai/microsphere-projects/microsphere-spring-cloud)
+[](https://github.com/microsphere-projects/microsphere-spring-cloud/actions/workflows/maven-build.yml)
+[](https://app.codecov.io/gh/microsphere-projects/microsphere-spring-cloud)
+
+
+
+
+Microsphere Spring Cloud is an extension library for Spring Cloud that enhances and optimizes its capabilities,
+particularly focused on providing dynamic runtime configuration changes without application restarts. It's designed to
+solve common pain points when working with distributed systems in Spring Cloud.
+
+## Purpose and Scope
+
+Microsphere Spring Cloud is a comprehensive extension framework that enhances Spring Cloud applications with advanced
+service registration capabilities, dynamic OpenFeign client configuration, and fault tolerance features. This project
+provides production-ready enhancements to the core Spring Cloud ecosystem, focusing on operational reliability and
+dynamic configuration management.
+
+The framework supports multiple Spring Cloud versions (2022.x, 2023.x, 2024.x and 2025.x) and integrates seamlessly with
+various
+service discovery systems including Nacos, Eureka, Consul, and Zookeeper. For detailed information about specific
+subsystems, see Project Structure, Service Registration System, OpenFeign Auto-Refresh System, and Fault Tolerance.
+
+## Modules
+
+| **Module** | **Purpose** |
+|-------------------------------------------|-------------------------------------------------------------------------------------|
+| **microsphere-spring-cloud-parent** | Defines the parent POM with dependency management and Spring Cloud version profiles |
+| **microsphere-spring-cloud-dependencies** | Centralizes dependency management for all project modules |
+| **microsphere-spring-cloud-commons** | Common utilities for service discovery, registry, and fault tolerance |
+| **microsphere-spring-cloud-openfeign** | Extensions for Spring Cloud OpenFeign with auto-refresh capabilities |
+
+## Getting Started
+
+The easiest way to get started is by adding the Microsphere Spring Cloud BOM (Bill of Materials) to your project's
+pom.xml:
+
+```xml
+
+
+ ...
+
+
+ io.github.microsphere-projects
+ microsphere-spring-cloud-dependencies
+ ${microsphere-spring-cloud.version}
+ pom
+ import
+
+ ...
+
+
+```
+
+`${microsphere-spring-boot.version}` has two branches:
+
+| **Branches** | **Purpose** | **Latest Version** |
+|--------------|--------------------------------------------------|--------------------|
+| **0.2.x** | Compatible with Spring Cloud 2022.0.x - 2025.0.x | 0.2.5 |
+| **0.1.x** | Compatible with Spring Cloud Hoxton - 2021.0.x | 0.1.5 |
+
+Then add the specific modules you need:
+
+```xml
+
+
+
+ io.github.microsphere-projects
+ microsphere-spring-cloud-commons
+
+
+
+
+ io.github.microsphere-projects
+ microsphere-spring-cloud-openfeign
+
+
+```
+
+## Building from Source
+
+You don't need to build from source unless you want to try out the latest code or contribute to the project.
+
+To build the project, follow these steps:
+
+1. Clone the repository:
+
+```bash
+git clone https://github.com/microsphere-projects/microsphere-spring-cloud.git
+```
+
+2. Build the source:
+
+- Linux/MacOS:
+
+```bash
+./mvnw package
+```
+
+- Windows:
+
+```powershell
+mvnw.cmd package
+```
+
+## Contributing
+
+We welcome your contributions! Please read [Code of Conduct](./CODE_OF_CONDUCT.md) before submitting a pull request.
+
+## Reporting Issues
+
+* Before you log a bug, please search
+ the [issues](https://github.com/microsphere-projects/microsphere-spring-cloud/issues)
+ to see if someone has already reported the problem.
+* If the issue doesn't already
+ exist, [create a new issue](https://github.com/microsphere-projects/microsphere-spring-cloud/issues/new).
+* Please provide as much information as possible with the issue report.
+
+## Documentation
+
+### User Guide
+
+[DeepWiki Host](https://deepwiki.com/microsphere-projects/microsphere-spring-cloud)
+
+[ZRead Host](https://zread.ai/microsphere-projects/microsphere-spring-cloud)
+
+### Wiki
+
+[Github Host](https://github.com/microsphere-projects/microsphere-spring-cloud/wiki)
+
+### JavaDoc
+
+- [microsphere-spring-cloud-commons](https://javadoc.io/doc/io.github.microsphere-projects/microsphere-spring-cloud-commons)
+- [microsphere-spring-cloud-openfeign](https://javadoc.io/doc/io.github.microsphere-projects/microsphere-spring-cloud-openfeign)
+
+## License
+
+The Microsphere Spring is released under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/microsphere-spring-cloud-commons/pom.xml b/microsphere-spring-cloud-commons/pom.xml
index bcc2a392..be5392c8 100644
--- a/microsphere-spring-cloud-commons/pom.xml
+++ b/microsphere-spring-cloud-commons/pom.xml
@@ -67,12 +67,57 @@
true
+
+ org.springframework.cloud
+ spring-cloud-loadbalancer
+ true
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-netflix-eureka-client
+ true
+
+
+
+ com.netflix.eureka
+ eureka-client
+ true
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+ true
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-zookeeper-discovery
+ true
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-consul-discovery
+ true
+
+
io.github.microsphere-projects
microsphere-spring-boot-core
+
+ io.github.microsphere-projects
+ microsphere-spring-boot-actuator
+
+
io.github.microsphere-projects
microsphere-spring-webmvc
@@ -92,31 +137,24 @@
test
-
-
- org.testcontainers
- junit-jupiter
- test
-
-
-
-
- org.springframework.cloud
- spring-cloud-netflix-eureka-client
- test
-
-
+
- com.netflix.eureka
- eureka-client
+ io.github.microsphere-projects
+ microsphere-spring-test
test
-
+
- com.alibaba.cloud
- spring-cloud-starter-alibaba-nacos-discovery
+ org.testcontainers
+ testcontainers-junit-jupiter
test
+
+
+ junit
+ junit
+
+
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabled.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabled.java
index 2509dd8a..d7058b9b 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabled.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabled.java
@@ -21,15 +21,15 @@
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
import org.springframework.cloud.client.actuator.FeaturesEndpoint;
import org.springframework.cloud.client.actuator.HasFeatures;
-import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.FEATURES_ENABLED_PROPERTY_NAME;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* The conditional annotation meta-annotates {@link ConditionalOnProperty @ConditionalOnProperty} for
@@ -43,18 +43,9 @@
* @see ConditionalOnProperty
* @since 1.0.0
*/
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
@Documented
-@ConditionalOnProperty(name = FEATURES_ENABLED_PROPERTY_NAME)
+@ConditionalOnProperty(name = FEATURES_ENABLED_PROPERTY_NAME, matchIfMissing = true)
public @interface ConditionalOnFeaturesEnabled {
-
- /**
- * Specify if the condition should match if the property is not set. Defaults to
- * {@code true}.
- *
- * @return if the condition should match if the property is missing
- */
- @AliasFor(annotation = ConditionalOnProperty.class, attribute = "matchIfMissing")
- boolean matchIfMissing() default true;
}
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/ReactiveDiscoveryClientAdapter.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/ReactiveDiscoveryClientAdapter.java
new file mode 100644
index 00000000..73ba2fe2
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/ReactiveDiscoveryClientAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery;
+
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+
+import static io.microsphere.lang.function.ThrowableSupplier.execute;
+import static reactor.core.scheduler.Schedulers.isInNonBlockingThread;
+
+/**
+ * An adapter {@link DiscoveryClient} class based on {@link ReactiveDiscoveryClient}
+ *
+ * @author Mercy
+ * @see DiscoveryClient
+ * @since 1.0.0
+ */
+public class ReactiveDiscoveryClientAdapter implements DiscoveryClient {
+
+ private final ReactiveDiscoveryClient reactiveDiscoveryClient;
+
+ /**
+ * Create a new {@link ReactiveDiscoveryClientAdapter} that wraps the given
+ * {@link ReactiveDiscoveryClient} as a blocking {@link DiscoveryClient}.
+ *
+ * Example Usage:
+ *
{@code
+ * ReactiveDiscoveryClient reactiveClient = new SimpleReactiveDiscoveryClient(properties);
+ * DiscoveryClient adapter = new ReactiveDiscoveryClientAdapter(reactiveClient);
+ * List services = adapter.getServices();
+ * }
+ *
+ * @param reactiveDiscoveryClient the {@link ReactiveDiscoveryClient} to adapt, must not be {@code null}
+ */
+ public ReactiveDiscoveryClientAdapter(ReactiveDiscoveryClient reactiveDiscoveryClient) {
+ this.reactiveDiscoveryClient = reactiveDiscoveryClient;
+ }
+
+ /**
+ * {@inheritDoc}
+ * Delegates to the underlying {@link ReactiveDiscoveryClient#description()}.
+ */
+ @Override
+ public String description() {
+ return this.reactiveDiscoveryClient.description();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
Delegates to {@link ReactiveDiscoveryClient#getInstances(String)} and collects the
+ * reactive {@link Flux} result into a blocking {@link List}.
+ */
+ @Override
+ public List getInstances(String serviceId) {
+ Flux flux = this.reactiveDiscoveryClient.getInstances(serviceId);
+ return toList(flux);
+ }
+
+ /**
+ * {@inheritDoc}
+ * Delegates to {@link ReactiveDiscoveryClient#getServices()} and collects the
+ * reactive {@link Flux} result into a blocking {@link List}.
+ */
+ @Override
+ public List getServices() {
+ Flux flux = this.reactiveDiscoveryClient.getServices();
+ return toList(flux);
+ }
+
+ /**
+ * {@inheritDoc}
+ * Delegates to {@link ReactiveDiscoveryClient#probe()}.
+ */
+ @Override
+ public void probe() {
+ this.reactiveDiscoveryClient.probe();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
Delegates to {@link ReactiveDiscoveryClient#getOrder()}.
+ */
+ @Override
+ public int getOrder() {
+ return this.reactiveDiscoveryClient.getOrder();
+ }
+
+ static List toList(Flux flux) {
+ Mono> mono = flux.collectList();
+ if (isInNonBlockingThread()) {
+ return execute(() -> mono.toFuture().get());
+ }
+ return mono.block();
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClient.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClient.java
index bf517416..8b19619c 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClient.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClient.java
@@ -16,19 +16,23 @@
*/
package io.microsphere.spring.cloud.client.discovery;
+import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import static io.microsphere.collection.CollectionUtils.isNotEmpty;
import static io.microsphere.reflect.TypeUtils.getClassName;
+import static io.microsphere.spring.beans.BeanUtils.getSortedBeans;
import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.COMPOSITE_DISCOVERY_CLIENT_CLASS_NAME;
/**
@@ -38,56 +42,78 @@
* @see CompositeDiscoveryClient
* @since 1.0.0
*/
-public final class UnionDiscoveryClient implements DiscoveryClient, SmartInitializingSingleton, DisposableBean {
+public final class UnionDiscoveryClient implements DiscoveryClient, ApplicationContextAware, SmartInitializingSingleton, DisposableBean {
- private final ObjectProvider discoveryClientsProvider;
+ private ApplicationContext context;
private List discoveryClients;
- public UnionDiscoveryClient(ObjectProvider discoveryClientsProvider) {
- this.discoveryClientsProvider = discoveryClientsProvider;
- }
-
+ /**
+ * {@inheritDoc}
+ *
+ * @return the description "Union Discovery Client"
+ */
@Override
public String description() {
return "Union Discovery Client";
}
+ /**
+ * {@inheritDoc}
+ * Aggregates service instances from all underlying {@link DiscoveryClient DiscoveryClients}.
+ */
@Override
public List getInstances(String serviceId) {
List serviceInstances = new LinkedList<>();
List discoveryClients = getDiscoveryClients();
for (DiscoveryClient discoveryClient : discoveryClients) {
List instances = discoveryClient.getInstances(serviceId);
- if (instances != null && !instances.isEmpty()) {
+ if (isNotEmpty(instances)) {
serviceInstances.addAll(instances);
}
}
return serviceInstances;
}
+ /**
+ * {@inheritDoc}
+ * Returns a deduplicated union of service names from all underlying {@link DiscoveryClient DiscoveryClients}.
+ */
@Override
public List getServices() {
LinkedHashSet services = new LinkedHashSet<>();
List discoveryClients = getDiscoveryClients();
for (DiscoveryClient discoveryClient : discoveryClients) {
List serviceForClient = discoveryClient.getServices();
- if (serviceForClient != null) {
+ if (isNotEmpty(serviceForClient)) {
services.addAll(serviceForClient);
}
}
return new ArrayList<>(services);
}
+ /**
+ * Returns the sorted list of underlying {@link DiscoveryClient DiscoveryClients}, excluding
+ * {@link CompositeDiscoveryClient} and this instance itself. The list is lazily initialized
+ * from the {@link ApplicationContext} on first access and cached for subsequent calls.
+ *
+ * Example Usage:
+ *
{@code
+ * UnionDiscoveryClient unionClient = applicationContext.getBean(UnionDiscoveryClient.class);
+ * List clients = unionClient.getDiscoveryClients();
+ * clients.forEach(c -> System.out.println(c.description()));
+ * }
+ *
+ * @return an unmodifiable list of {@link DiscoveryClient} instances
+ */
public List getDiscoveryClients() {
List discoveryClients = this.discoveryClients;
if (discoveryClients != null) {
return discoveryClients;
}
- discoveryClients = new LinkedList<>();
-
- for (DiscoveryClient discoveryClient : discoveryClientsProvider) {
+ discoveryClients = new ArrayList<>();
+ for (DiscoveryClient discoveryClient : getSortedBeans(this.context, DiscoveryClient.class)) {
String className = getClassName(discoveryClient.getClass());
if (COMPOSITE_DISCOVERY_CLIENT_CLASS_NAME.equals(className) || this.equals(discoveryClient)) {
// excludes CompositeDiscoveryClient and self
@@ -95,21 +121,44 @@ public List getDiscoveryClients() {
}
discoveryClients.add(discoveryClient);
}
+ this.discoveryClients = discoveryClients;
return discoveryClients;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@link #HIGHEST_PRECEDENCE} to ensure this client takes priority
+ */
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
+ /**
+ * {@inheritDoc}
+ * Eagerly initializes the list of {@link DiscoveryClient DiscoveryClients} after all singletons are instantiated.
+ */
@Override
public void afterSingletonsInstantiated() {
this.discoveryClients = getDiscoveryClients();
}
+ /**
+ * {@inheritDoc}
+ *
Clears the cached list of {@link DiscoveryClient DiscoveryClients} on bean destruction.
+ */
@Override
public void destroy() throws Exception {
this.discoveryClients.clear();
}
-}
+
+ /**
+ * {@inheritDoc}
+ *
Stores the {@link ApplicationContext} used to look up {@link DiscoveryClient} beans.
+ */
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ this.context = applicationContext;
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfiguration.java
index 25302cba..7fc2c6eb 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfiguration.java
@@ -16,8 +16,8 @@
*/
package io.microsphere.spring.cloud.client.discovery.autoconfigure;
+import io.microsphere.annotation.ConfigurationProperty;
import io.microsphere.spring.cloud.client.discovery.UnionDiscoveryClient;
-import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -28,11 +28,10 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import static io.microsphere.annotation.ConfigurationProperty.APPLICATION_SOURCE;
import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.DISCOVERY_CLIENT_CLASS_NAME;
-import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.DISCOVERY_CLIENT_PROPERTY_PREFIX;
-import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.MODE_PROPERTY_NAME;
-import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.UNION_DISCOVERY_CLIENT_MODE;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX;
/**
* {@link UnionDiscoveryClient} Auto-Configuration Class
@@ -53,13 +52,50 @@
})
public class DiscoveryClientAutoConfiguration {
+ /**
+ * The property prefix of {@link DiscoveryClient} : "microsphere.spring.cloud.client.discovery."
+ */
+ public static final String DISCOVERY_CLIENT_PROPERTY_PREFIX = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "client.discovery.";
+
+ /**
+ * The property name of mode : "mode"
+ */
+ public static final String MODE_PROPERTY_NAME = "mode";
+
+ /**
+ * The {@link DiscoveryClient} "mode" for {@link UnionDiscoveryClient} : "union"
+ */
+ public static final String UNION_DISCOVERY_CLIENT_MODE = "union";
+
+ /**
+ * The property name of DiscoveryClient mode : "microsphere.spring.cloud.client.discovery.mode"
+ */
+ @ConfigurationProperty(
+ source = APPLICATION_SOURCE
+ )
+ public static final String DISCOVERY_CLIENT_MODE_PROPERTY_NAME = DISCOVERY_CLIENT_PROPERTY_PREFIX + MODE_PROPERTY_NAME;
+
+
@Configuration(proxyBeanMethods = false)
- @ConditionalOnProperty(prefix = DISCOVERY_CLIENT_PROPERTY_PREFIX, name = MODE_PROPERTY_NAME, havingValue = UNION_DISCOVERY_CLIENT_MODE)
+ @ConditionalOnProperty(name = DISCOVERY_CLIENT_MODE_PROPERTY_NAME, havingValue = UNION_DISCOVERY_CLIENT_MODE)
public static class UnionConfiguration {
+ /**
+ * Creates a {@link UnionDiscoveryClient} bean that aggregates all {@link DiscoveryClient}
+ * instances in the {@link org.springframework.context.ApplicationContext}.
+ *
+ *
Example Usage:
+ *
{@code
+ * // Activated when microsphere.spring.cloud.client.discovery.mode=union
+ * UnionDiscoveryClient client = applicationContext.getBean(UnionDiscoveryClient.class);
+ * List services = client.getServices();
+ * }
+ *
+ * @return a new {@link UnionDiscoveryClient} instance
+ */
@Bean
- public UnionDiscoveryClient unionDiscoveryClient(ObjectProvider discoveryClientsProvider) {
- return new UnionDiscoveryClient(discoveryClientsProvider);
+ public UnionDiscoveryClient unionDiscoveryClient() {
+ return new UnionDiscoveryClient();
}
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/ReactiveDiscoveryClientAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/ReactiveDiscoveryClientAutoConfiguration.java
new file mode 100644
index 00000000..8cb3a906
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/ReactiveDiscoveryClientAutoConfiguration.java
@@ -0,0 +1,84 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery.autoconfigure;
+
+import io.microsphere.spring.cloud.client.discovery.ReactiveDiscoveryClientAdapter;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.cloud.client.ConditionalOnBlockingDiscoveryEnabled;
+import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled;
+import org.springframework.cloud.client.ConditionalOnReactiveDiscoveryEnabled;
+import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.DISCOVERY_CLIENT_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.REACTIVE_COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.REACTIVE_COMPOSITE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.SIMPLE_REACTIVE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+
+/**
+ * The Auto-Configuration class for {@link ReactiveDiscoveryClient}
+ *
+ * @author Mercy
+ * @see ReactiveDiscoveryClient
+ * @see DiscoveryClientAutoConfiguration
+ * @since 1.0.0
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(name = {
+ DISCOVERY_CLIENT_CLASS_NAME
+})
+@ConditionalOnDiscoveryEnabled
+@ConditionalOnReactiveDiscoveryEnabled
+@AutoConfigureBefore(name = {
+ REACTIVE_COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME
+})
+@AutoConfigureAfter(name = {
+ SIMPLE_REACTIVE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME,
+ REACTIVE_COMPOSITE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME
+})
+public class ReactiveDiscoveryClientAutoConfiguration {
+
+ @Configuration(proxyBeanMethods = false)
+ @ConditionalOnBlockingDiscoveryEnabled
+ public static class BlockingConfiguration {
+
+ /**
+ * Creates a {@link ReactiveDiscoveryClientAdapter} bean that adapts a
+ * {@link ReactiveDiscoveryClient} to the blocking {@link org.springframework.cloud.client.discovery.DiscoveryClient} interface.
+ *
+ * Example Usage:
+ *
{@code
+ * // Auto-configured when both reactive and blocking discovery are enabled
+ * DiscoveryClient client = applicationContext.getBean(ReactiveDiscoveryClientAdapter.class);
+ * List services = client.getServices();
+ * }
+ *
+ * @param reactiveDiscoveryClient the {@link ReactiveDiscoveryClient} to adapt
+ * @return a new {@link ReactiveDiscoveryClientAdapter} instance
+ */
+ @Bean
+ @ConditionalOnBean(ReactiveDiscoveryClient.class)
+ public ReactiveDiscoveryClientAdapter reactiveDiscoveryClientAdapter(ReactiveDiscoveryClient reactiveDiscoveryClient) {
+ return new ReactiveDiscoveryClientAdapter(reactiveDiscoveryClient);
+ }
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstants.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstants.java
index a41950f8..5cce42f7 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstants.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstants.java
@@ -16,12 +16,12 @@
*/
package io.microsphere.spring.cloud.client.discovery.constants;
-import io.microsphere.spring.cloud.client.discovery.UnionDiscoveryClient;
import org.springframework.cloud.client.CommonsClientAutoConfiguration;
+import org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
-
-import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX;
+import org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
/**
* The constants for {@link DiscoveryClient}
@@ -32,33 +32,44 @@
public interface DiscoveryClientConstants {
/**
- * The property prefix of {@link DiscoveryClient}
+ * The class name of {@link DiscoveryClient}
+ *
+ * @see org.springframework.cloud.client.discovery.DiscoveryClient
*/
- String DISCOVERY_CLIENT_PROPERTY_PREFIX = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "client.discovery";
+ String DISCOVERY_CLIENT_CLASS_NAME = "org.springframework.cloud.client.discovery.DiscoveryClient";
/**
- * The property name of "mode"
+ * The class name of {@link CompositeDiscoveryClient}
+ *
+ * @see org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient
*/
- String MODE_PROPERTY_NAME = "mode";
+ String COMPOSITE_DISCOVERY_CLIENT_CLASS_NAME = "org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient";
/**
- * The {@link DiscoveryClient} "mode" for {@link UnionDiscoveryClient}
+ * The class name of {@link CommonsClientAutoConfiguration}
+ *
+ * @see org.springframework.cloud.client.CommonsClientAutoConfiguration
*/
- String UNION_DISCOVERY_CLIENT_MODE = "union";
+ String COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.client.CommonsClientAutoConfiguration";
/**
- * The class name of {@link DiscoveryClient}
+ * The class name of {@link ReactiveCommonsClientAutoConfiguration}
+ *
+ * @see org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration
*/
- String DISCOVERY_CLIENT_CLASS_NAME = "org.springframework.cloud.client.discovery.DiscoveryClient";
+ String REACTIVE_COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration";
/**
- * The class name of {@link CompositeDiscoveryClient}
+ * The class name of {@link SimpleReactiveDiscoveryClientAutoConfiguration}
+ *
+ * @see org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration
*/
- String COMPOSITE_DISCOVERY_CLIENT_CLASS_NAME = "org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient";
+ String SIMPLE_REACTIVE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration";
/**
- * The class name of {@link CommonsClientAutoConfiguration}
+ * The class name of {@link ReactiveCompositeDiscoveryClientAutoConfiguration}
+ *
+ * @see org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration
*/
- String COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.client.CommonsClientAutoConfiguration";
-
-}
+ String REACTIVE_COMPOSITE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME = "org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration";
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/util/DiscoveryUtils.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/util/DiscoveryUtils.java
new file mode 100644
index 00000000..db3a521f
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/discovery/util/DiscoveryUtils.java
@@ -0,0 +1,106 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery.util;
+
+import io.microsphere.annotation.Nonnull;
+import io.microsphere.util.Utils;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties;
+
+import java.util.List;
+import java.util.Map;
+
+import static io.microsphere.reflect.MethodUtils.invokeMethod;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.setProperties;
+
+/**
+ * The utilities class for Spring Cloud Discovery
+ *
+ * @author Mercy
+ * @see Utils
+ * @since 1.0.0
+ */
+public abstract class DiscoveryUtils implements Utils {
+
+ /**
+ * Get the instances map from {@link SimpleDiscoveryProperties}
+ *
+ * @param properties {@link SimpleDiscoveryProperties}
+ * @return the instances map
+ */
+ @Nonnull
+ public static Map> getInstancesMap(@Nonnull SimpleDiscoveryProperties properties) {
+ return properties.getInstances();
+ }
+
+ /**
+ * Get the instances map from {@link SimpleReactiveDiscoveryProperties}
+ *
+ * @param properties {@link SimpleReactiveDiscoveryProperties}
+ * @return the instances map
+ */
+ @Nonnull
+ public static Map> getInstancesMap(@Nonnull SimpleReactiveDiscoveryProperties properties) {
+ return invokeMethod(properties, "getInstances");
+ }
+
+ /**
+ * Convert {@link SimpleDiscoveryProperties} to {@link SimpleReactiveDiscoveryProperties}
+ *
+ * @param properties {@link SimpleDiscoveryProperties}
+ * @return {@link SimpleReactiveDiscoveryProperties}
+ */
+ @Nonnull
+ public static SimpleReactiveDiscoveryProperties simpleReactiveDiscoveryProperties(@Nonnull SimpleDiscoveryProperties properties) {
+ SimpleReactiveDiscoveryProperties simpleReactiveDiscoveryProperties = new SimpleReactiveDiscoveryProperties();
+ simpleReactiveDiscoveryProperties.setOrder(properties.getOrder());
+
+ DefaultServiceInstance local = properties.getLocal();
+ DefaultServiceInstance targetLocal = simpleReactiveDiscoveryProperties.getLocal();
+ setProperties(targetLocal, local);
+
+ Map> instances = getInstancesMap(properties);
+ simpleReactiveDiscoveryProperties.setInstances(instances);
+
+ return simpleReactiveDiscoveryProperties;
+ }
+
+ /**
+ * Convert {@link SimpleReactiveDiscoveryProperties} to {@link SimpleDiscoveryProperties}
+ *
+ * @param properties {@link SimpleReactiveDiscoveryProperties}
+ * @return {@link SimpleDiscoveryProperties}
+ */
+ @Nonnull
+ public static SimpleDiscoveryProperties simpleDiscoveryProperties(@Nonnull SimpleReactiveDiscoveryProperties properties) {
+ SimpleDiscoveryProperties simpleDiscoveryProperties = new SimpleDiscoveryProperties();
+ simpleDiscoveryProperties.setOrder(properties.getOrder());
+
+ DefaultServiceInstance local = properties.getLocal();
+ simpleDiscoveryProperties.setInstance(local.getServiceId(), local.getHost(), local.getPort());
+
+ Map> instances = invokeMethod(properties, "getInstances");
+ simpleDiscoveryProperties.setInstances(instances);
+
+ return simpleDiscoveryProperties;
+ }
+
+ private DiscoveryUtils() {
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEvent.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEvent.java
index e13600c2..2515d02b 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEvent.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEvent.java
@@ -16,13 +16,6 @@
*/
package io.microsphere.spring.cloud.client.event;
-/**
- * TODO Comment
- *
- * @author Mercy
- * @since TODO
- */
-
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ApplicationEventMulticaster;
@@ -31,9 +24,10 @@
import java.util.List;
import static java.util.Collections.unmodifiableList;
+import static org.springframework.util.Assert.notEmpty;
/**
- * An event raised after the {@link ServiceInstance instances} of one service has been
+ * An event raised when the {@link ServiceInstance instances} of one service has been
* changed.
*
* @author Mercy
@@ -58,6 +52,7 @@ public class ServiceInstancesChangedEvent extends ApplicationEvent {
public ServiceInstancesChangedEvent(String serviceName,
List serviceInstances) {
super(serviceName);
+ notEmpty(serviceInstances, () -> "The arguments 'serviceInstances' must not be empty!");
this.serviceInstances = unmodifiableList(serviceInstances);
}
@@ -91,4 +86,4 @@ public boolean isProcessed() {
return processed;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistration.java
index d15acf15..bd3eb25b 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistration.java
@@ -27,4 +27,4 @@
* @since 1.0.0
*/
public class DefaultRegistration extends DefaultServiceInstance implements Registration {
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/InMemoryServiceRegistry.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/InMemoryServiceRegistry.java
index b094c8dc..7d1840c3 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/InMemoryServiceRegistry.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/InMemoryServiceRegistry.java
@@ -35,23 +35,73 @@ public class InMemoryServiceRegistry implements ServiceRegistry {
private final ConcurrentMap storage = new ConcurrentHashMap<>(1);
+ /**
+ * Registers the given {@link Registration} instance in the in-memory storage,
+ * keyed by its instance ID.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry registry = new InMemoryServiceRegistry();
+ * Registration registration = createRegistration();
+ * registry.register(registration);
+ * }
+ *
+ * @param registration the {@link Registration} to store
+ */
@Override
public void register(Registration registration) {
String id = registration.getInstanceId();
storage.put(id, registration);
}
+ /**
+ * Removes the given {@link Registration} instance from the in-memory storage.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry registry = new InMemoryServiceRegistry();
+ * Registration registration = createRegistration();
+ * registry.register(registration);
+ * registry.deregister(registration);
+ * }
+ *
+ * @param registration the {@link Registration} to remove
+ */
@Override
public void deregister(Registration registration) {
String id = registration.getInstanceId();
storage.remove(id, registration);
}
+ /**
+ * Closes this registry by clearing all stored {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry registry = new InMemoryServiceRegistry();
+ * registry.register(registration);
+ * registry.close();
+ * }
+ */
@Override
public void close() {
storage.clear();
}
+ /**
+ * Sets the status of the given {@link Registration} by storing it in
+ * the registration's metadata under the {@code _status_} key.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry registry = new InMemoryServiceRegistry();
+ * registry.register(registration);
+ * registry.setStatus(registration, "UP");
+ * }
+ *
+ * @param registration the {@link Registration} whose status is to be set
+ * @param status the status value to set
+ */
@Override
public void setStatus(Registration registration, String status) {
Map metadata = getMetadata(registration);
@@ -60,6 +110,20 @@ public void setStatus(Registration registration, String status) {
}
}
+ /**
+ * Retrieves the status of the given {@link Registration} from its metadata.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry registry = new InMemoryServiceRegistry();
+ * registry.register(registration);
+ * registry.setStatus(registration, "UP");
+ * Object status = registry.getStatus(registration); // "UP"
+ * }
+ *
+ * @param registration the {@link Registration} whose status is to be retrieved
+ * @return the status value, or {@code null} if not set or registration not found
+ */
@Override
public Object getStatus(Registration registration) {
Map metadata = getMetadata(registration);
@@ -69,12 +133,26 @@ public Object getStatus(Registration registration) {
return null;
}
+ /**
+ * Retrieves the metadata {@link Map} for the given {@link Registration}
+ * from the in-memory storage.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry registry = new InMemoryServiceRegistry();
+ * registry.register(registration);
+ * Map metadata = registry.getMetadata(registration);
+ * }
+ *
+ * @param registration the {@link Registration} whose metadata is to be retrieved
+ * @return the metadata map, or {@code null} if the registration is not found
+ */
protected Map getMetadata(Registration registration) {
String id = registration.getInstanceId();
Registration instance = storage.get(id);
- if (storage != null) {
+ if (instance != null) {
return instance.getMetadata();
}
return null;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistration.java
index 3d860b2b..227305f5 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistration.java
@@ -17,8 +17,27 @@
public class MultipleAutoServiceRegistration extends AbstractAutoServiceRegistration {
private final AutoServiceRegistrationProperties autoServiceRegistrationProperties;
+
private final MultipleRegistration multipleRegistration;
+ /**
+ * Constructs a new {@link MultipleAutoServiceRegistration} with the specified
+ * {@link MultipleRegistration}, {@link ServiceRegistry}, and
+ * {@link AutoServiceRegistrationProperties}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration registration = new MultipleRegistration(registrations);
+ * ServiceRegistry serviceRegistry = new InMemoryServiceRegistry();
+ * AutoServiceRegistrationProperties properties = new AutoServiceRegistrationProperties();
+ * MultipleAutoServiceRegistration autoReg =
+ * new MultipleAutoServiceRegistration(registration, serviceRegistry, properties);
+ * }
+ *
+ * @param multipleRegistration the {@link MultipleRegistration} to manage
+ * @param serviceRegistry the {@link ServiceRegistry} to delegate to
+ * @param properties the {@link AutoServiceRegistrationProperties} for configuration
+ */
public MultipleAutoServiceRegistration(MultipleRegistration multipleRegistration,
ServiceRegistry serviceRegistry,
AutoServiceRegistrationProperties properties) {
@@ -27,23 +46,70 @@ public MultipleAutoServiceRegistration(MultipleRegistration multipleRegistration
this.multipleRegistration = multipleRegistration;
}
+ /**
+ * Returns the configuration object for this auto service registration.
+ * This implementation always returns {@code null}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleAutoServiceRegistration autoReg = ...;
+ * Object config = autoReg.getConfiguration(); // null
+ * }
+ *
+ * @return {@code null}
+ */
@Override
protected Object getConfiguration() {
return null;
}
+ /**
+ * Determines whether this auto service registration is enabled based on the
+ * {@link AutoServiceRegistrationProperties}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleAutoServiceRegistration autoReg = ...;
+ * boolean enabled = autoReg.isEnabled();
+ * }
+ *
+ * @return {@code true} if auto service registration is enabled
+ */
@Override
protected boolean isEnabled() {
return this.autoServiceRegistrationProperties.isEnabled();
}
+ /**
+ * Returns the {@link MultipleRegistration} managed by this auto service registration.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleAutoServiceRegistration autoReg = ...;
+ * MultipleRegistration registration = autoReg.getRegistration();
+ * }
+ *
+ * @return the {@link MultipleRegistration} instance
+ */
@Override
protected MultipleRegistration getRegistration() {
return this.multipleRegistration;
}
+ /**
+ * Returns the management {@link MultipleRegistration}, which is the same as
+ * the primary registration in this implementation.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleAutoServiceRegistration autoReg = ...;
+ * MultipleRegistration mgmtRegistration = autoReg.getManagementRegistration();
+ * }
+ *
+ * @return the {@link MultipleRegistration} instance used for management
+ */
@Override
protected MultipleRegistration getManagementRegistration() {
return this.multipleRegistration;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistration.java
index 75d3fd7b..aba1ff63 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistration.java
@@ -1,13 +1,18 @@
package io.microsphere.spring.cloud.client.service.registry;
import org.springframework.cloud.client.serviceregistry.Registration;
-import org.springframework.util.CollectionUtils;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import static io.microsphere.util.Assert.assertNotEmpty;
+import static io.microsphere.util.ClassUtils.findAllClasses;
+import static io.microsphere.util.ClassUtils.isAssignableFrom;
+import static org.springframework.aop.framework.AopProxyUtils.ultimateTargetClass;
+
/**
* The Delegating {@link Registration} for the multiple service registration
*
@@ -24,55 +29,180 @@ public class MultipleRegistration implements Registration {
private final RegistrationMetaData metaData;
+ /**
+ * Constructs a new {@link MultipleRegistration} from the given collection of
+ * {@link Registration} instances. The last registration in the collection becomes
+ * the default registration.
+ *
+ * Example Usage:
+ *
{@code
+ * DefaultRegistration registration = new DefaultRegistration();
+ * registration.setServiceId("test-service");
+ * MultipleRegistration multipleRegistration =
+ * new MultipleRegistration(List.of(registration));
+ * }
+ *
+ * @param registrations the collection of {@link Registration} instances, must not be empty
+ */
public MultipleRegistration(Collection registrations) {
- if (CollectionUtils.isEmpty(registrations))
- throw new IllegalArgumentException("registrations cannot be empty");
+ assertNotEmpty(registrations, () -> "registrations cannot be empty");
//init map
for (Registration registration : registrations) {
- Class extends Registration> clazz = registration.getClass();
- this.registrationMap.put(clazz, registration);
+ Class extends Registration> clazz = (Class) ultimateTargetClass(registration);
+ List> classes = (List) findAllClasses(clazz, type -> isAssignableFrom(Registration.class, type) && !Registration.class.equals(type));
+ classes.forEach(type -> registrationMap.put(type, registration));
this.defaultRegistration = registration;
}
this.metaData = new RegistrationMetaData(registrations);
}
+ /**
+ * Returns the instance ID from the default {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * String instanceId = multipleRegistration.getInstanceId();
+ * }
+ *
+ * @return the instance ID of the default registration
+ */
+ @Override
+ public String getInstanceId() {
+ return getDefaultRegistration().getInstanceId();
+ }
+
+ /**
+ * Returns the service ID from the default {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * String serviceId = multipleRegistration.getServiceId();
+ * }
+ *
+ * @return the service ID of the default registration
+ */
@Override
public String getServiceId() {
return getDefaultRegistration().getServiceId();
}
+ /**
+ * Returns the host from the default {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * String host = multipleRegistration.getHost();
+ * }
+ *
+ * @return the host of the default registration
+ */
@Override
public String getHost() {
return getDefaultRegistration().getHost();
}
+ /**
+ * Returns the port from the default {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * int port = multipleRegistration.getPort();
+ * }
+ *
+ * @return the port of the default registration
+ */
@Override
public int getPort() {
return getDefaultRegistration().getPort();
}
+ /**
+ * Returns whether the default {@link Registration} is secure.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * boolean secure = multipleRegistration.isSecure();
+ * }
+ *
+ * @return {@code true} if the default registration is secure
+ */
@Override
public boolean isSecure() {
return getDefaultRegistration().isSecure();
}
+ /**
+ * Returns the {@link URI} from the default {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * URI uri = multipleRegistration.getUri();
+ * }
+ *
+ * @return the URI of the default registration
+ */
@Override
public URI getUri() {
return getDefaultRegistration().getUri();
}
+ /**
+ * Returns the aggregated {@link RegistrationMetaData} that synchronizes metadata
+ * across all underlying {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * Map metadata = multipleRegistration.getMetadata();
+ * }
+ *
+ * @return the aggregated metadata map
+ */
@Override
public Map getMetadata() {
return metaData;
}
+ /**
+ * Returns the default {@link Registration} instance, which is the last registration
+ * provided during construction.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * Registration defaultReg = multipleRegistration.getDefaultRegistration();
+ * }
+ *
+ * @return the default {@link Registration}
+ */
public Registration getDefaultRegistration() {
return defaultRegistration;
}
+ /**
+ * Retrieves a specific {@link Registration} by its class type. If the specified
+ * class is {@link Registration} itself, returns this {@link MultipleRegistration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleRegistration multipleRegistration = new MultipleRegistration(registrations);
+ * DefaultRegistration specific = multipleRegistration.special(DefaultRegistration.class);
+ * Registration self = multipleRegistration.special(Registration.class);
+ * }
+ *
+ * @param specialClass the specific {@link Registration} subclass to look up
+ * @param the type of the registration
+ * @return the matching registration, or {@code null} if not found
+ */
public T special(Class specialClass) {
- if (specialClass.equals(Registration.class))
+ if (Registration.class.equals(specialClass))
return (T) this;
return (T) this.registrationMap.getOrDefault(specialClass, null);
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistry.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistry.java
index ffaf3f45..d87c3118 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistry.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistry.java
@@ -1,11 +1,7 @@
package io.microsphere.spring.cloud.client.service.registry;
-import org.springframework.aop.framework.AopProxyUtils;
-import org.springframework.aop.support.AopUtils;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
-import org.springframework.core.ResolvableType;
-import org.springframework.util.CollectionUtils;
import java.util.HashMap;
import java.util.List;
@@ -13,6 +9,9 @@
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import static io.microsphere.util.Assert.assertNotEmpty;
+import static org.springframework.aop.framework.AopProxyUtils.ultimateTargetClass;
+import static org.springframework.core.ResolvableType.forClass;
import static org.springframework.core.io.support.SpringFactoriesLoader.loadFactoryNames;
import static org.springframework.util.ClassUtils.resolveClassName;
@@ -35,12 +34,26 @@ public class MultipleServiceRegistry implements ServiceRegistry> beanNameToRegistrationTypesMap;
private ServiceRegistry defaultServiceRegistry;
+
private String defaultRegistrationBeanName;
+ /**
+ * Constructs a new {@link MultipleServiceRegistry} from the given map of bean names
+ * to {@link ServiceRegistry} instances. Each registry is mapped to its corresponding
+ * {@link Registration} type.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceRegistry simpleRegistry = new InMemoryServiceRegistry();
+ * MultipleServiceRegistry registry =
+ * new MultipleServiceRegistry(Map.of("default", simpleRegistry));
+ * }
+ *
+ * @param registriesMap the map of Spring bean names to {@link ServiceRegistry} instances,
+ * must not be empty
+ */
public MultipleServiceRegistry(Map registriesMap) {
- if (CollectionUtils.isEmpty(registriesMap)) {
- throw new IllegalArgumentException("service registry cannot be empty");
- }
+ assertNotEmpty(registriesMap, () -> "registrations cannot be empty");
this.registriesMap = registriesMap;
this.beanNameToRegistrationTypesMap = new HashMap<>(registriesMap.size());
@@ -48,28 +61,78 @@ public MultipleServiceRegistry(Map registriesMap) {
for (Map.Entry entry : registriesMap.entrySet()) {
String beanName = entry.getKey();
ServiceRegistry serviceRegistry = entry.getValue();
- Class extends Registration> registrationClass = getRegistrationClass(serviceRegistry.getClass(), entry.getValue());
+ Class extends Registration> registrationClass = getRegistrationClass(ultimateTargetClass(serviceRegistry));
beanNameToRegistrationTypesMap.put(beanName, registrationClass);
defaultServiceRegistry = serviceRegistry;
defaultRegistrationBeanName = beanName;
}
}
+ /**
+ * Registers the given {@link MultipleRegistration} by delegating to each underlying
+ * {@link ServiceRegistry} with the corresponding specific {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleServiceRegistry registry = new MultipleServiceRegistry(registriesMap);
+ * MultipleRegistration registration = new MultipleRegistration(registrations);
+ * registry.register(registration);
+ * }
+ *
+ * @param registration the {@link MultipleRegistration} to register
+ */
@Override
public void register(MultipleRegistration registration) {
iterate(registration, (reg, registry) -> registry.register(reg));
}
+ /**
+ * Deregisters the given {@link MultipleRegistration} by delegating to each underlying
+ * {@link ServiceRegistry} with the corresponding specific {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleServiceRegistry registry = new MultipleServiceRegistry(registriesMap);
+ * MultipleRegistration registration = new MultipleRegistration(registrations);
+ * registry.register(registration);
+ * registry.deregister(registration);
+ * }
+ *
+ * @param registration the {@link MultipleRegistration} to deregister
+ */
@Override
public void deregister(MultipleRegistration registration) {
iterate(registration, (reg, registry) -> registry.deregister(reg));
}
+ /**
+ * Closes all underlying {@link ServiceRegistry} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleServiceRegistry registry = new MultipleServiceRegistry(registriesMap);
+ * registry.close();
+ * }
+ */
@Override
public void close() {
iterate(ServiceRegistry::close);
}
+ /**
+ * Sets the status of the given {@link MultipleRegistration} by delegating to each
+ * underlying {@link ServiceRegistry} with the corresponding specific {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleServiceRegistry registry = new MultipleServiceRegistry(registriesMap);
+ * registry.register(registration);
+ * registry.setStatus(registration, "UP");
+ * }
+ *
+ * @param registration the {@link MultipleRegistration} whose status is to be set
+ * @param status the status value to set
+ */
@Override
public void setStatus(MultipleRegistration registration, String status) {
iterate(registration, (reg, registry) -> registry.setStatus(reg, status));
@@ -89,6 +152,22 @@ private void iterate(Consumer action) {
registriesMap.values().forEach(action);
}
+ /**
+ * Retrieves the status of the given {@link MultipleRegistration} from the default
+ * {@link ServiceRegistry}.
+ *
+ * Example Usage:
+ *
{@code
+ * MultipleServiceRegistry registry = new MultipleServiceRegistry(registriesMap);
+ * registry.register(registration);
+ * registry.setStatus(registration, "UP");
+ * Object status = registry.getStatus(registration); // "UP"
+ * }
+ *
+ * @param registration the {@link MultipleRegistration} whose status is to be retrieved
+ * @param the type of the status value
+ * @return the status from the default service registry
+ */
@Override
public T getStatus(MultipleRegistration registration) {
Class extends Registration> registrationClass = beanNameToRegistrationTypesMap.get(defaultRegistrationBeanName);
@@ -96,8 +175,23 @@ public T getStatus(MultipleRegistration registration) {
return (T) defaultServiceRegistry.getStatus(targetRegistration);
}
- private static Class extends Registration> getRegistrationClass(Class extends ServiceRegistry> serviceRegistryClass, ServiceRegistry serviceRegistry) {
- Class> registrationClass = ResolvableType.forClass(serviceRegistryClass)
+ /**
+ * Resolves the {@link Registration} class for the given {@link ServiceRegistry} class
+ * using generic type resolution. Falls back to {@code SpringFactoriesLoader} when the
+ * generic type resolves to {@link Registration} itself.
+ *
+ * Example Usage:
+ *
{@code
+ * Class extends Registration> regClass =
+ * MultipleServiceRegistry.getRegistrationClass(NacosServiceRegistry.class);
+ * // returns NacosRegistration.class
+ * }
+ *
+ * @param serviceRegistryClass the {@link ServiceRegistry} implementation class
+ * @return the resolved {@link Registration} subclass
+ */
+ static Class extends Registration> getRegistrationClass(Class> serviceRegistryClass) {
+ Class> registrationClass = forClass(serviceRegistryClass)
.as(ServiceRegistry.class)
.getGeneric(0)
.resolve();
@@ -107,11 +201,7 @@ private static Class extends Registration> getRegistrationClass(Class extend
ClassLoader classLoader = serviceRegistryClass.getClassLoader();
List registrationClassNames;
- if (AopUtils.isAopProxy(serviceRegistry)) {
- registrationClassNames = loadFactoryNames(AopProxyUtils.ultimateTargetClass(serviceRegistry), classLoader);
- } else {
- registrationClassNames = loadFactoryNames(serviceRegistryClass, classLoader);
- }
+ registrationClassNames = loadFactoryNames(serviceRegistryClass, classLoader);
for (String registrationClassName : registrationClassNames) {
registrationClass = resolveClassName(registrationClassName, classLoader);
@@ -119,4 +209,4 @@ private static Class extends Registration> getRegistrationClass(Class extend
}
return (Class extends Registration>) registrationClass;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationCustomizer.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationCustomizer.java
index eccef247..8884b42b 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationCustomizer.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationCustomizer.java
@@ -33,4 +33,4 @@ public interface RegistrationCustomizer {
* @param registration {@link Registration}
*/
void customize(Registration registration);
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaData.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaData.java
index 8a35c2aa..638acce7 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaData.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaData.java
@@ -9,26 +9,58 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import static io.microsphere.reflect.MethodUtils.invokeMethod;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.removeMetadata;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.setMetadata;
+import static io.microsphere.util.Assert.assertNotEmpty;
+import static org.springframework.aop.framework.AopProxyUtils.ultimateTargetClass;
+
/**
* @author 韩超
* @since 1.0.0
*/
public final class RegistrationMetaData implements Map {
+ /**
+ * The class name of {@link org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegistration}
+ */
+ static final String ZOOKEEPER_REGISTRATION_CLASS_NAME = "org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegistration";
+
+ /**
+ * The method name of {@link org.springframework.cloud.zookeeper.serviceregistry.ServiceInstanceRegistration#getServiceInstance()}
+ */
+ static final String GET_SERVICE_INSTANCE_METHOD_NAME = "getServiceInstance";
+
/**
* MetaData information manually added by the application,usually specified by configuration
*/
private final Map applicationMetaData;
+
private final Collection registrations;
+
private final Object lock = new Object();
+ /**
+ * Constructs a new {@link RegistrationMetaData} that aggregates metadata from the given
+ * collection of {@link Registration} instances. Metadata changes are synchronized across
+ * all registrations.
+ *
+ * Example Usage:
+ *
{@code
+ * DefaultRegistration registration = new DefaultRegistration();
+ * registration.getMetadata().put("key1", "value1");
+ * RegistrationMetaData metaData = new RegistrationMetaData(List.of(registration));
+ * }
+ *
+ * @param registrations the collection of {@link Registration} instances, must not be empty
+ */
public RegistrationMetaData(Collection registrations) {
- if (CollectionUtils.isEmpty(registrations))
- throw new IllegalArgumentException("registrations cannot be empty");
-
+ assertNotEmpty(registrations, () -> "registrations cannot be empty");
this.registrations = registrations;
this.applicationMetaData = new ConcurrentHashMap<>();
for (Registration registration : registrations) {
+ initializeIfZookeeperRegistrationAvailable(registration);
+
Map metaData = registration.getMetadata();
if (!CollectionUtils.isEmpty(metaData)) {
//check key and value must not be null
@@ -41,51 +73,153 @@ public RegistrationMetaData(Collection registrations) {
}
}
+ /**
+ * Returns the number of metadata entries.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * int count = metaData.size(); // e.g. 3
+ * }
+ *
+ * @return the number of key-value mappings in this metadata
+ */
@Override
public int size() {
return applicationMetaData.size();
}
+ /**
+ * Returns whether this metadata map is empty.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * boolean empty = metaData.isEmpty(); // false if registrations have metadata
+ * }
+ *
+ * @return {@code true} if this metadata contains no entries
+ */
@Override
public boolean isEmpty() {
return this.applicationMetaData.isEmpty();
}
+ /**
+ * Returns whether this metadata contains the specified key.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * boolean hasKey = metaData.containsKey("key1"); // true
+ * boolean missing = metaData.containsKey("unknown"); // false
+ * }
+ *
+ * @param key the key to check for
+ * @return {@code true} if this metadata contains the specified key
+ */
@Override
public boolean containsKey(Object key) {
return this.applicationMetaData.containsKey(key);
}
+ /**
+ * Returns whether this metadata contains the specified value.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * boolean hasValue = metaData.containsValue("value1"); // true
+ * boolean missing = metaData.containsValue("unknown"); // false
+ * }
+ *
+ * @param value the value to check for
+ * @return {@code true} if this metadata contains the specified value
+ */
@Override
public boolean containsValue(Object value) {
return this.applicationMetaData.containsValue(value);
}
+ /**
+ * Returns the metadata value associated with the specified key.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * String value = metaData.get("key1"); // "value1"
+ * String missing = metaData.get("unknown"); // null
+ * }
+ *
+ * @param key the key whose associated value is to be returned
+ * @return the value associated with the key, or {@code null} if not found
+ */
@Override
public String get(Object key) {
return this.applicationMetaData.get(key);
}
+ /**
+ * Puts a metadata entry and synchronizes it across all underlying {@link Registration}
+ * instances.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * metaData.put("key4", "value4");
+ * String value = metaData.get("key4"); // "value4"
+ * }
+ *
+ * @param key the metadata key
+ * @param value the metadata value
+ * @return the previous value associated with the key, or {@code null}
+ */
@Override
public String put(String key, String value) {
synchronized (lock) {
this.registrations.forEach(registration -> {
- registration.getMetadata().put(key, value);
+ setMetadata(registration, key, value);
});
}
return this.applicationMetaData.put(key, value);
}
+ /**
+ * Removes the metadata entry for the specified key and synchronizes the removal
+ * across all underlying {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * metaData.remove("key1");
+ * String value = metaData.get("key1"); // null
+ * }
+ *
+ * @param key the key whose mapping is to be removed
+ * @return the previous value associated with the key, or {@code null}
+ */
@Override
public String remove(Object key) {
synchronized (lock) {
this.registrations.forEach(registration -> {
- registration.getMetadata().remove(key);
+ removeMetadata(registration, (String) key);
});
}
return this.applicationMetaData.remove(key);
}
+ /**
+ * Copies all entries from the specified map into this metadata and synchronizes
+ * them across all underlying {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * metaData.putAll(Map.of("key4", "value4", "key5", "value5"));
+ * }
+ *
+ * @param m the map of entries to add
+ */
@Override
public void putAll(Map extends String, ? extends String> m) {
synchronized (lock) {
@@ -96,6 +230,17 @@ public void putAll(Map extends String, ? extends String> m) {
this.applicationMetaData.putAll(m);
}
+ /**
+ * Clears all metadata entries and synchronizes the clearing across all underlying
+ * {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * metaData.clear();
+ * int size = metaData.size(); // 0
+ * }
+ */
@Override
public void clear() {
synchronized (lock) {
@@ -104,18 +249,63 @@ public void clear() {
this.applicationMetaData.clear();
}
+ /**
+ * Returns an unmodifiable {@link Set} view of the metadata keys.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * Set keys = metaData.keySet();
+ * boolean hasKey = keys.contains("key1"); // true
+ * }
+ *
+ * @return an unmodifiable set of metadata keys
+ */
@Override
public Set keySet() {
return Collections.unmodifiableSet(this.applicationMetaData.keySet());
}
+ /**
+ * Returns an unmodifiable {@link Collection} view of the metadata values.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * Collection values = metaData.values();
+ * boolean hasValue = values.contains("value1"); // true
+ * }
+ *
+ * @return an unmodifiable collection of metadata values
+ */
@Override
public Collection values() {
return Collections.unmodifiableCollection(this.applicationMetaData.values());
}
+ /**
+ * Returns a modifiable {@link Set} view of the metadata entries. Unlike
+ * {@link #keySet()} and {@link #values()}, the returned set is not wrapped
+ * in an unmodifiable view.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationMetaData metaData = new RegistrationMetaData(registrations);
+ * Set> entries = metaData.entrySet();
+ * }
+ *
+ * @return a set of metadata entries
+ */
@Override
public Set> entrySet() {
return this.applicationMetaData.entrySet();
}
-}
+
+ private void initializeIfZookeeperRegistrationAvailable(Registration registration) {
+ Class> registrationClass = ultimateTargetClass(registration);
+ if (ZOOKEEPER_REGISTRATION_CLASS_NAME.equals(registrationClass.getName())) {
+ // init ServiceInstance
+ invokeMethod(registration, GET_SERVICE_INSTANCE_METHOD_NAME);
+ }
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistration.java
index 30082573..26c3c4d2 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistration.java
@@ -34,30 +34,94 @@ public class SimpleAutoServiceRegistration extends AbstractAutoServiceRegistrati
private final Registration registration;
- protected SimpleAutoServiceRegistration(ServiceRegistry serviceRegistry,
+ /**
+ * Constructs a new {@link SimpleAutoServiceRegistration} with the specified
+ * {@link ServiceRegistry}, {@link AutoServiceRegistrationProperties}, and
+ * {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * InMemoryServiceRegistry serviceRegistry = new InMemoryServiceRegistry();
+ * AutoServiceRegistrationProperties properties = new AutoServiceRegistrationProperties();
+ * Registration registration = createRegistration();
+ * SimpleAutoServiceRegistration autoReg =
+ * new SimpleAutoServiceRegistration(serviceRegistry, properties, registration);
+ * }
+ *
+ * @param serviceRegistry the {@link ServiceRegistry} to delegate to
+ * @param properties the {@link AutoServiceRegistrationProperties} for configuration
+ * @param registration the {@link Registration} to manage
+ */
+ public SimpleAutoServiceRegistration(ServiceRegistry serviceRegistry,
AutoServiceRegistrationProperties properties, Registration registration) {
super(serviceRegistry, properties);
this.properties = properties;
this.registration = registration;
}
+ /**
+ * Returns the {@link AutoServiceRegistrationProperties} as the configuration object.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleAutoServiceRegistration autoReg = ...;
+ * Object config = autoReg.getConfiguration();
+ * }
+ *
+ * @return the {@link AutoServiceRegistrationProperties} instance
+ */
@Override
protected Object getConfiguration() {
return properties;
}
+ /**
+ * Determines whether this auto service registration is enabled based on the
+ * {@link AutoServiceRegistrationProperties}.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleAutoServiceRegistration autoReg = ...;
+ * boolean enabled = autoReg.isEnabled();
+ * }
+ *
+ * @return {@code true} if auto service registration is enabled
+ */
@Override
protected boolean isEnabled() {
return properties.isEnabled();
}
+ /**
+ * Returns the {@link Registration} managed by this auto service registration.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleAutoServiceRegistration autoReg = ...;
+ * Registration registration = autoReg.getRegistration();
+ * }
+ *
+ * @return the {@link Registration} instance
+ */
@Override
protected Registration getRegistration() {
return registration;
}
+ /**
+ * Returns the management {@link Registration}, which is the same as the primary
+ * registration in this implementation.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleAutoServiceRegistration autoReg = ...;
+ * Registration mgmtRegistration = autoReg.getManagementRegistration();
+ * }
+ *
+ * @return the {@link Registration} instance used for management
+ */
@Override
protected Registration getManagementRegistration() {
return registration;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleServiceRegistry.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleServiceRegistry.java
new file mode 100644
index 00000000..386bd33c
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/SimpleServiceRegistry.java
@@ -0,0 +1,222 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static io.microsphere.spring.cloud.client.discovery.util.DiscoveryUtils.getInstancesMap;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.getMetadata;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.setMetadata;
+
+/**
+ * Simple {@link ServiceRegistry} class that is based on {@link SimpleDiscoveryProperties}
+ * or {@link SimpleReactiveDiscoveryProperties} to register
+ * {@link DefaultRegistration}.
+ *
+ * @author Mercy
+ * @see ServiceRegistry
+ * @see DefaultRegistration
+ * @see SimpleDiscoveryProperties#getInstances()
+ * @see SimpleReactiveDiscoveryProperties#getInstances()
+ * @since 1.0.0
+ */
+public class SimpleServiceRegistry implements ServiceRegistry {
+
+ public static final String STATUS_KEY = "_status_";
+
+ private final Map> instancesMap;
+
+ /**
+ * Constructs a new {@link SimpleServiceRegistry} using the given
+ * {@link SimpleDiscoveryProperties} to obtain the instances map.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleDiscoveryProperties properties = new SimpleDiscoveryProperties();
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * }
+ *
+ * @param properties the {@link SimpleDiscoveryProperties} to use
+ */
+ public SimpleServiceRegistry(SimpleDiscoveryProperties properties) {
+ this(getInstancesMap(properties));
+ }
+
+ /**
+ * Constructs a new {@link SimpleServiceRegistry} using the given
+ * {@link SimpleReactiveDiscoveryProperties} to obtain the instances map.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleReactiveDiscoveryProperties reactiveProperties = ...;
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(reactiveProperties);
+ * }
+ *
+ * @param properties the {@link SimpleReactiveDiscoveryProperties} to use
+ */
+ public SimpleServiceRegistry(SimpleReactiveDiscoveryProperties properties) {
+ this(getInstancesMap(properties));
+ }
+
+ /**
+ * Constructs a new {@link SimpleServiceRegistry} with the given instances map.
+ *
+ * Example Usage:
+ *
{@code
+ * Map> instancesMap = new HashMap<>();
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(instancesMap);
+ * }
+ *
+ * @param instancesMap the map of service IDs to {@link DefaultServiceInstance} lists
+ */
+ public SimpleServiceRegistry(Map> instancesMap) {
+ this.instancesMap = instancesMap;
+ }
+
+ /**
+ * Registers the given {@link DefaultRegistration} by adding it to the instances list
+ * for its service ID.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * DefaultRegistration registration = new DefaultRegistration();
+ * registration.setServiceId("test-service");
+ * registry.register(registration);
+ * }
+ *
+ * @param registration the {@link DefaultRegistration} to register
+ */
+ @Override
+ public void register(DefaultRegistration registration) {
+ List instances = getInstances(registration);
+ instances.add(registration);
+ }
+
+ /**
+ * Deregisters the given {@link DefaultRegistration} by removing it from the instances
+ * list for its service ID.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * registry.register(registration);
+ * registry.deregister(registration);
+ * }
+ *
+ * @param registration the {@link DefaultRegistration} to deregister
+ */
+ @Override
+ public void deregister(DefaultRegistration registration) {
+ List instances = getInstances(registration);
+ instances.remove(registration);
+ }
+
+ /**
+ * Closes this registry. This implementation is a no-op.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * registry.close();
+ * }
+ */
+ @Override
+ public void close() {
+ }
+
+ /**
+ * Sets the status of the given {@link DefaultRegistration} by storing it in the
+ * registration's metadata under the {@link #STATUS_KEY} key.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * registry.register(registration);
+ * registry.setStatus(registration, "UP");
+ * }
+ *
+ * @param registration the {@link DefaultRegistration} whose status is to be set
+ * @param status the status value to set
+ */
+ @Override
+ public void setStatus(DefaultRegistration registration, String status) {
+ setMetadata(registration, STATUS_KEY, status);
+ }
+
+ /**
+ * Retrieves the status of the given {@link DefaultRegistration} from its metadata.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * registry.register(registration);
+ * registry.setStatus(registration, "UP");
+ * String status = registry.getStatus(registration); // "UP"
+ * }
+ *
+ * @param registration the {@link DefaultRegistration} whose status is to be retrieved
+ * @return the status value, or {@code null} if not set
+ */
+ @Override
+ public String getStatus(DefaultRegistration registration) {
+ return getMetadata(registration, STATUS_KEY);
+ }
+
+ /**
+ * Returns the list of {@link DefaultServiceInstance} instances for the given
+ * {@link DefaultRegistration}'s service ID.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * registry.register(registration);
+ * List instances = registry.getInstances(registration);
+ * }
+ *
+ * @param registration the {@link DefaultRegistration} to look up instances for
+ * @return the list of instances for the registration's service ID
+ */
+ List getInstances(DefaultRegistration registration) {
+ return getInstances(registration.getServiceId());
+ }
+
+ /**
+ * Returns the list of {@link DefaultServiceInstance} instances for the given service ID,
+ * creating an empty list if none exists.
+ *
+ * Example Usage:
+ *
{@code
+ * SimpleServiceRegistry registry = new SimpleServiceRegistry(properties);
+ * List instances = registry.getInstances("test-service");
+ * }
+ *
+ * @param serviceId the service ID to look up
+ * @return the list of instances for the service ID
+ */
+ List getInstances(String serviceId) {
+ return this.instancesMap.computeIfAbsent(serviceId, k -> new ArrayList<>());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfiguration.java
index c2a73cbb..531fc200 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfiguration.java
@@ -49,6 +49,19 @@
})
public class ServiceRegistrationEndpointAutoConfiguration {
+ /**
+ * Creates a {@link ServiceRegistrationEndpoint} bean for managing service registration via actuator.
+ *
+ * Example Usage:
+ *
{@code
+ * // The endpoint is auto-configured and accessible at /actuator/serviceRegistration
+ * @Autowired
+ * ServiceRegistrationEndpoint endpoint;
+ * Map metadata = endpoint.metadata();
+ * }
+ *
+ * @return a new {@link ServiceRegistrationEndpoint} instance
+ */
@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
@@ -56,10 +69,23 @@ public ServiceRegistrationEndpoint serviceRegistrationEndpoint() {
return new ServiceRegistrationEndpoint();
}
+ /**
+ * Creates a {@link ServiceDeregistrationEndpoint} bean for managing service deregistration via actuator.
+ *
+ * Example Usage:
+ *
{@code
+ * // The endpoint is auto-configured and accessible at /actuator/serviceDeregistration
+ * @Autowired
+ * ServiceDeregistrationEndpoint endpoint;
+ * boolean wasRunning = endpoint.stop();
+ * }
+ *
+ * @return a new {@link ServiceDeregistrationEndpoint} instance
+ */
@Bean
@ConditionalOnMissingBean
@ConditionalOnAvailableEndpoint
public ServiceDeregistrationEndpoint serviceDeregistrationEndpoint() {
return new ServiceDeregistrationEndpoint();
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspect.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspect.java
index 57a45a06..0c99526a 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspect.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspect.java
@@ -59,40 +59,113 @@ public class EventPublishingRegistrationAspect implements ApplicationContextAwar
private ObjectProvider registrationCustomizers;
+ /**
+ * AOP advice executed before {@link ServiceRegistry#register(Registration)}, publishing a
+ * {@link RegistrationPreRegisteredEvent} and applying {@link RegistrationCustomizer customizations}.
+ *
+ * Example Usage:
+ *
{@code
+ * // This advice is triggered automatically when ServiceRegistry.register() is called:
+ * serviceRegistry.register(registration);
+ * // A RegistrationPreRegisteredEvent is published before actual registration
+ * }
+ *
+ * @param registry the target {@link ServiceRegistry}
+ * @param registration the {@link Registration} being registered
+ */
@Before(value = REGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
public void beforeRegister(ServiceRegistry registry, Registration registration) {
- if (registry.getClass().isAssignableFrom(MultipleServiceRegistry.class))
- return;//Remove redundant register
+ if (isIgnored(registry)) {
+ return; // Remove redundant deregister
+ }
context.publishEvent(new RegistrationPreRegisteredEvent(registry, registration));
registrationCustomizers.forEach(customizer -> {
customizer.customize(registration);
});
}
+ /**
+ * AOP advice executed before {@link ServiceRegistry#deregister(Registration)}, publishing a
+ * {@link RegistrationPreDeregisteredEvent}.
+ *
+ * Example Usage:
+ *
{@code
+ * // This advice is triggered automatically when ServiceRegistry.deregister() is called:
+ * serviceRegistry.deregister(registration);
+ * // A RegistrationPreDeregisteredEvent is published before actual deregistration
+ * }
+ *
+ * @param registry the target {@link ServiceRegistry}
+ * @param registration the {@link Registration} being deregistered
+ */
@Before(value = DEREGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
public void beforeDeregister(ServiceRegistry registry, Registration registration) {
- if (registry.getClass().isAssignableFrom(MultipleServiceRegistry.class))
- return;//Remove redundant deregister
+ if (isIgnored(registry)) {
+ return; // Remove redundant deregister
+ }
context.publishEvent(new RegistrationPreDeregisteredEvent(registry, registration));
}
+ /**
+ * AOP advice executed after {@link ServiceRegistry#register(Registration)}, publishing a
+ * {@link RegistrationRegisteredEvent}.
+ *
+ * Example Usage:
+ *
{@code
+ * // This advice is triggered automatically after ServiceRegistry.register() completes:
+ * serviceRegistry.register(registration);
+ * // A RegistrationRegisteredEvent is published after successful registration
+ * }
+ *
+ * @param registry the target {@link ServiceRegistry}
+ * @param registration the {@link Registration} that was registered
+ */
@After(value = REGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
public void afterRegister(ServiceRegistry registry, Registration registration) {
- if (registry.getClass().isAssignableFrom(MultipleServiceRegistry.class))
- return;//Remove redundant register
+ if (isIgnored(registry)) {
+ return; // Remove redundant deregister
+ }
context.publishEvent(new RegistrationRegisteredEvent(registry, registration));
}
+ /**
+ * AOP advice executed after {@link ServiceRegistry#deregister(Registration)}, publishing a
+ * {@link RegistrationDeregisteredEvent}.
+ *
+ * Example Usage:
+ *
{@code
+ * // This advice is triggered automatically after ServiceRegistry.deregister() completes:
+ * serviceRegistry.deregister(registration);
+ * // A RegistrationDeregisteredEvent is published after successful deregistration
+ * }
+ *
+ * @param registry the target {@link ServiceRegistry}
+ * @param registration the {@link Registration} that was deregistered
+ */
@After(value = DEREGISTER_POINTCUT_EXPRESSION, argNames = "registry, registration")
public void afterDeregister(ServiceRegistry registry, Registration registration) {
- if (registry.getClass().isAssignableFrom(MultipleServiceRegistry.class))
- return;//Remove redundant deregister
+ if (isIgnored(registry)) {
+ return; // Remove redundant deregister
+ }
context.publishEvent(new RegistrationDeregisteredEvent(registry, registration));
}
+ boolean isIgnored(ServiceRegistry registry) {
+ return MultipleServiceRegistry.class.isAssignableFrom(registry.getClass());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Example Usage:
+ *
{@code
+ * EventPublishingRegistrationAspect aspect = new EventPublishingRegistrationAspect();
+ * aspect.setApplicationContext(applicationContext);
+ * }
+ */
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
this.registrationCustomizers = applicationContext.getBeanProvider(RegistrationCustomizer.class);
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfiguration.java
index 2fa3b76e..9eb53ecc 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfiguration.java
@@ -55,6 +55,20 @@ public class ServiceRegistryAutoConfiguration {
@ConditionalOnMultipleRegistrationEnabled
static class MultipleConfiguration {
+ /**
+ * Creates a primary {@link MultipleRegistration} bean that aggregates all available
+ * {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * @Autowired
+ * MultipleRegistration multipleRegistration;
+ * // Access individual registrations from the composite
+ * }
+ *
+ * @param registrations the collection of {@link Registration} instances
+ * @return a new {@link MultipleRegistration} aggregating the provided registrations
+ */
@Primary
@Bean
@ConditionalOnMissingBean
@@ -62,6 +76,21 @@ public MultipleRegistration multipleRegistration(Collection regist
return new MultipleRegistration(registrations);
}
+ /**
+ * Creates a primary {@link MultipleServiceRegistry} bean that delegates to all available
+ * {@link ServiceRegistry} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * @Autowired
+ * MultipleServiceRegistry multipleServiceRegistry;
+ * // Register with all service registries at once
+ * multipleServiceRegistry.register(registration);
+ * }
+ *
+ * @param registriesMap a map of bean names to {@link ServiceRegistry} instances
+ * @return a new {@link MultipleServiceRegistry} delegating to all registries
+ */
@Bean
@Primary
@ConditionalOnMissingBean
@@ -69,6 +98,23 @@ public MultipleServiceRegistry multipleServiceRegistry(MapExample Usage:
+ * {@code
+ * @Autowired
+ * MultipleAutoServiceRegistration autoRegistration;
+ * // Auto-registration is managed by the Spring lifecycle
+ * boolean running = autoRegistration.isRunning();
+ * }
+ *
+ * @param multipleRegistration the composite {@link MultipleRegistration}
+ * @param multipleServiceRegistry the composite {@link MultipleServiceRegistry}
+ * @param properties the {@link AutoServiceRegistrationProperties}
+ * @return a new {@link MultipleAutoServiceRegistration} instance
+ */
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@Primary
@Bean
@@ -80,4 +126,4 @@ public MultipleAutoServiceRegistration multipleAutoServiceRegistration(MultipleR
}
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfiguration.java
index 7115ef0a..c3028236 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfiguration.java
@@ -16,6 +16,8 @@
*/
package io.microsphere.spring.cloud.client.service.registry.autoconfigure;
+import io.microsphere.annotation.ConfigurationProperty;
+import io.microsphere.constants.PropertyConstants;
import io.microsphere.spring.cloud.client.service.registry.DefaultRegistration;
import io.microsphere.spring.cloud.client.service.registry.InMemoryServiceRegistry;
import io.microsphere.spring.cloud.client.service.registry.SimpleAutoServiceRegistration;
@@ -26,6 +28,7 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration;
import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration;
import org.springframework.cloud.client.serviceregistry.Registration;
@@ -36,8 +39,8 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
-import static io.microsphere.constants.PropertyConstants.ENABLED_PROPERTY_NAME;
-import static io.microsphere.spring.cloud.client.service.registry.autoconfigure.SimpleAutoServiceRegistrationAutoConfiguration.PROPERTY_NAME_PREFIX;
+import static io.microsphere.annotation.ConfigurationProperty.APPLICATION_SOURCE;
+import static io.microsphere.spring.cloud.client.service.registry.autoconfigure.SimpleAutoServiceRegistrationAutoConfiguration.ENABLED_PROPERTY_NAME;
import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX;
/**
@@ -48,14 +51,15 @@
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
-@ConditionalOnProperty(prefix = PROPERTY_NAME_PREFIX, name = ENABLED_PROPERTY_NAME)
+@ConditionalOnProperty(name = ENABLED_PROPERTY_NAME)
@ConditionalOnAutoServiceRegistrationEnabled
@AutoConfigureBefore(value = {
AutoServiceRegistrationAutoConfiguration.class
})
@AutoConfigureAfter(value = {
UtilAutoConfiguration.class,
- AutoServiceRegistrationConfiguration.class
+ AutoServiceRegistrationConfiguration.class,
+ SimpleDiscoveryClientAutoConfiguration.class
})
@Import(value = {
SimpleAutoServiceRegistration.class
@@ -63,10 +67,35 @@
public class SimpleAutoServiceRegistrationAutoConfiguration {
/**
- * The property name prefix
+ * The property name prefix : "microsphere.spring.cloud.service-registry.auto-registration.simple."
*/
- public static final String PROPERTY_NAME_PREFIX = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "simple.";
+ public static final String PROPERTY_NAME_PREFIX = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "service-registry.auto-registration.simple.";
+ /**
+ * The property name : "microsphere.spring.cloud.service-registry.auto-registration.simple.enabled"
+ */
+ @ConfigurationProperty(
+ type = boolean.class,
+ source = APPLICATION_SOURCE
+ )
+ public static final String ENABLED_PROPERTY_NAME = PROPERTY_NAME_PREFIX + PropertyConstants.ENABLED_PROPERTY_NAME;
+
+ /**
+ * Creates a {@link Registration} bean from the application name, server properties, and network info.
+ *
+ * Example Usage:
+ *
{@code
+ * // Auto-configured via Spring Boot; the bean is available for injection:
+ * @Autowired
+ * Registration registration;
+ * String serviceId = registration.getServiceId();
+ * }
+ *
+ * @param applicationName the Spring application name resolved from {@code spring.application.name}
+ * @param serverProperties the {@link ServerProperties} providing the server port
+ * @param inetUtils the {@link InetUtils} for resolving the host address
+ * @return a new {@link DefaultRegistration} instance
+ */
@Bean
public Registration registration(
@Value("${spring.application.name:default}") String applicationName,
@@ -85,10 +114,22 @@ public Registration registration(
return registration;
}
-
+ /**
+ * Creates an {@link InMemoryServiceRegistry} bean as the default {@link ServiceRegistry} implementation.
+ *
+ * Example Usage:
+ *
{@code
+ * // Auto-configured when no other ServiceRegistry bean is present:
+ * @Autowired
+ * ServiceRegistry serviceRegistry;
+ * serviceRegistry.register(registration);
+ * }
+ *
+ * @return a new {@link InMemoryServiceRegistry} instance
+ */
@Bean
@ConditionalOnMissingBean
public ServiceRegistry serviceRegistry() {
return new InMemoryServiceRegistry();
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfiguration.java
index 0cb30de1..87ede268 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfiguration.java
@@ -16,12 +16,9 @@
*/
package io.microsphere.spring.cloud.client.service.registry.autoconfigure;
-import io.microsphere.spring.cloud.client.service.registry.condition.ConditionalOnAutoServiceRegistrationEnabled;
-import org.springframework.boot.autoconfigure.AutoConfigureAfter;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import io.microsphere.spring.web.metadata.WebEndpointMapping;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
-import org.springframework.context.annotation.Configuration;
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.REACTIVE;
@@ -31,15 +28,42 @@
* @author Mercy
* @since 1.0.0
*/
-@Configuration(proxyBeanMethods = false)
-@ConditionalOnClass(name = {
- "io.microsphere.spring.web.metadata.WebEndpointMapping",
- "io.microsphere.spring.web.event.WebEndpointMappingsReadyEvent"
-})
@ConditionalOnWebApplication(type = REACTIVE)
-@ConditionalOnAutoServiceRegistrationEnabled
-@AutoConfigureAfter(value = {
- ServiceRegistryAutoConfiguration.class
-})
-public class WebFluxServiceRegistryAutoConfiguration {
-}
+public class WebFluxServiceRegistryAutoConfiguration extends WebServiceRegistryAutoConfiguration {
+
+ /**
+ * {@inheritDoc}
+ * Returns an empty string as WebFlux applications do not use a servlet context path.
+ *
+ *
Example Usage:
+ *
{@code
+ * WebFluxServiceRegistryAutoConfiguration config = new WebFluxServiceRegistryAutoConfiguration();
+ * String contextPath = config.getContextPath(); // returns ""
+ * }
+ *
+ * @return an empty string
+ */
+ @Override
+ protected String getContextPath() {
+ return "";
+ }
+
+ /**
+ * {@inheritDoc}
+ * Always returns {@code false} for WebFlux applications, as no mappings are excluded.
+ *
+ *
Example Usage:
+ *
{@code
+ * WebFluxServiceRegistryAutoConfiguration config = new WebFluxServiceRegistryAutoConfiguration();
+ * boolean excluded = config.isExcludedMapping(mapping, patterns); // always false
+ * }
+ *
+ * @param mapping the {@link WebEndpointMapping} to evaluate
+ * @param patterns the URL patterns associated with the mapping
+ * @return always {@code false}
+ */
+ @Override
+ protected boolean isExcludedMapping(WebEndpointMapping mapping, String[] patterns) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfiguration.java
index deb59f3c..b160d563 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfiguration.java
@@ -16,34 +16,23 @@
*/
package io.microsphere.spring.cloud.client.service.registry.autoconfigure;
-import io.microsphere.logging.Logger;
-import io.microsphere.logging.LoggerFactory;
-import io.microsphere.spring.cloud.client.service.registry.condition.ConditionalOnAutoServiceRegistrationEnabled;
-import io.microsphere.spring.web.event.WebEndpointMappingsReadyEvent;
import io.microsphere.spring.web.metadata.WebEndpointMapping;
+import io.microsphere.util.ValueHolder;
+import jakarta.servlet.Filter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.autoconfigure.AutoConfigureAfter;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.event.EventListener;
-import javax.servlet.Filter;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.Objects;
-import java.util.Set;
-import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.attachMetadata;
+import static io.microsphere.util.ArrayUtils.EMPTY_STRING_ARRAY;
+import static io.microsphere.util.ArrayUtils.arrayEquals;
+import static java.lang.Boolean.FALSE;
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
/**
@@ -52,28 +41,11 @@
* @author Mercy
* @since 1.0.0
*/
-@Configuration(proxyBeanMethods = false)
-@ConditionalOnClass(name = {
- "io.microsphere.spring.web.metadata.WebEndpointMapping",
- "io.microsphere.spring.web.event.WebEndpointMappingsReadyEvent"
-})
-@ConditionalOnBean(Registration.class)
@ConditionalOnWebApplication(type = SERVLET)
-@ConditionalOnAutoServiceRegistrationEnabled
-@AutoConfigureAfter(value = {
- ServiceRegistryAutoConfiguration.class
-})
-public class WebMvcServiceRegistryAutoConfiguration {
-
- private static final Logger logger = LoggerFactory.getLogger(WebMvcServiceRegistryAutoConfiguration.class);
+public class WebMvcServiceRegistryAutoConfiguration extends WebServiceRegistryAutoConfiguration {
private static final String[] DEFAULT_URL_MAPPINGS = {"/*"};
- @Autowired
- private Registration registration;
-
- @Value("${management.endpoints.web.base-path:/actuator}")
- private String actuatorBasePath;
@Value("${server.servlet.context-path:}")
private String contextPath;
@@ -83,34 +55,40 @@ public class WebMvcServiceRegistryAutoConfiguration {
@Autowired
private ObjectProvider dispatcherServletRegistrationBeanProvider;
- @EventListener(WebEndpointMappingsReadyEvent.class)
- public void onApplicationEvent(WebEndpointMappingsReadyEvent event) {
- Collection webEndpointMappings = event.getMappings();
- attachWebMappingsMetadata(registration, webEndpointMappings);
+ /**
+ * {@inheritDoc}
+ * Returns the servlet context path configured via {@code server.servlet.context-path}.
+ *
+ *
Example Usage:
+ *
{@code
+ * // With application property: server.servlet.context-path=/api
+ * String contextPath = config.getContextPath(); // returns "/api"
+ * }
+ *
+ * @return the servlet context path
+ */
+ @Override
+ protected String getContextPath() {
+ return this.contextPath;
}
- private void attachWebMappingsMetadata(Registration registration, Collection webEndpointMappings) {
- Set mappings = new HashSet<>(webEndpointMappings);
- excludeMappings(mappings);
- attachMetadata(contextPath, registration, mappings);
- }
-
- private void excludeMappings(Set mappings) {
- Iterator iterator = mappings.iterator();
- while (iterator.hasNext()) {
- WebEndpointMapping mapping = iterator.next();
- String[] patterns = mapping.getPatterns();
- if (isBuiltInFilterMapping(patterns)
- || isDispatcherServletMapping(mapping, patterns)
- || isActuatorWebEndpointMapping(patterns)
- ) {
- if (logger.isTraceEnabled()) {
- logger.trace("The '{}' was removed", mapping);
- }
- iterator.remove();
- }
-
- }
+ /**
+ * {@inheritDoc}
+ * Excludes built-in Spring filter mappings and the default DispatcherServlet mapping.
+ *
+ *
Example Usage:
+ *
{@code
+ * boolean excluded = config.isExcludedMapping(mapping, new String[]{"/*"});
+ * // returns true if the mapping matches a built-in filter or DispatcherServlet
+ * }
+ *
+ * @param mapping the {@link WebEndpointMapping} to evaluate
+ * @param patterns the URL patterns associated with the mapping
+ * @return {@code true} if the mapping is a built-in filter or DispatcherServlet mapping
+ */
+ @Override
+ protected boolean isExcludedMapping(WebEndpointMapping mapping, String[] patterns) {
+ return isBuiltInFilterMapping(patterns) || isDispatcherServletMapping(mapping, patterns);
}
private boolean isBuiltInFilterMapping(String[] patterns) {
@@ -129,26 +107,16 @@ private boolean isBuiltInFilterMapping(String[] patterns) {
}
private boolean isDispatcherServletMapping(WebEndpointMapping mapping, String[] patterns) {
- DispatcherServletRegistrationBean registrationBean = dispatcherServletRegistrationBeanProvider.getIfAvailable();
- if (registrationBean != null) {
+ ValueHolder found = new ValueHolder<>(FALSE);
+ this.dispatcherServletRegistrationBeanProvider.ifAvailable(registrationBean -> {
Object source = mapping.getEndpoint();
String servletName = registrationBean.getServletName();
if (Objects.equals(source, servletName)) {
Collection urlMappings = registrationBean.getUrlMappings();
- return matchUrlPatterns(urlMappings, patterns);
- }
- }
- return false;
- }
-
-
- private boolean isActuatorWebEndpointMapping(String[] patterns) {
- for (String pattern : patterns) {
- if (pattern.startsWith(actuatorBasePath)) {
- return true;
+ found.setValue(matchUrlPatterns(urlMappings, patterns));
}
- }
- return false;
+ });
+ return found.getValue();
}
private boolean matchFilter(FilterRegistrationBean filterRegistrationBean, String[] patterns) {
@@ -157,8 +125,7 @@ private boolean matchFilter(FilterRegistrationBean filterRegistrationBean, Strin
}
private boolean matchUrlPatterns(Collection urlPatterns, String[] patterns) {
- String[] urlPatternsArray = urlPatterns.isEmpty() ? DEFAULT_URL_MAPPINGS : urlPatterns.toArray(new String[0]);
- return Arrays.equals(urlPatternsArray, patterns);
+ String[] urlPatternsArray = urlPatterns.isEmpty() ? DEFAULT_URL_MAPPINGS : urlPatterns.toArray(EMPTY_STRING_ARRAY);
+ return arrayEquals(urlPatternsArray, patterns);
}
-
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebServiceRegistryAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebServiceRegistryAutoConfiguration.java
new file mode 100644
index 00000000..427f88f1
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebServiceRegistryAutoConfiguration.java
@@ -0,0 +1,137 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.autoconfigure;
+
+import io.microsphere.logging.Logger;
+import io.microsphere.spring.cloud.client.service.registry.condition.ConditionalOnAutoServiceRegistrationEnabled;
+import io.microsphere.spring.web.event.WebEndpointMappingsReadyEvent;
+import io.microsphere.spring.web.metadata.WebEndpointMapping;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import static io.microsphere.logging.LoggerFactory.getLogger;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.attachMetadata;
+
+/**
+ * Auto-Configuration class for {@link ServiceRegistry ServiceRegistry} on the Spring WebMVC Application
+ *
+ * @author Mercy
+ * @since 1.0.0
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnClass(name = {
+ "io.microsphere.spring.web.metadata.WebEndpointMapping",
+ "io.microsphere.spring.web.event.WebEndpointMappingsReadyEvent"
+})
+@ConditionalOnBean(Registration.class)
+@ConditionalOnAutoServiceRegistrationEnabled
+@AutoConfigureAfter(value = {
+ ServiceRegistryAutoConfiguration.class
+})
+public abstract class WebServiceRegistryAutoConfiguration implements ApplicationListener {
+
+ protected final Logger logger = getLogger(getClass());
+
+ @Value("${management.endpoints.web.base-path:/actuator}")
+ protected String actuatorBasePath;
+
+ /**
+ * Handles {@link WebEndpointMappingsReadyEvent} by attaching web endpoint mapping metadata
+ * to all available {@link Registration} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * // This listener is invoked automatically by the Spring event system:
+ * // When WebEndpointMappingsReadyEvent is published, metadata is attached
+ * // to each Registration bean in the ApplicationContext.
+ * }
+ *
+ * @param event the {@link WebEndpointMappingsReadyEvent} containing the web endpoint mappings
+ */
+ @Override
+ public final void onApplicationEvent(WebEndpointMappingsReadyEvent event) {
+ ApplicationContext context = event.getApplicationContext();
+ ObjectProvider registrationProvider = context.getBeanProvider(Registration.class);
+ Collection webEndpointMappings = event.getMappings();
+ registrationProvider.forEach(registration -> attachWebMappingsMetadata(registration, webEndpointMappings));
+ }
+
+ private void attachWebMappingsMetadata(Registration registration, Collection webEndpointMappings) {
+ Set mappings = new HashSet<>(webEndpointMappings);
+ excludeMappings(mappings);
+ attachMetadata(getContextPath(), registration, mappings);
+ }
+
+ void excludeMappings(Collection mappings) {
+ Iterator iterator = mappings.iterator();
+ while (iterator.hasNext()) {
+ WebEndpointMapping mapping = iterator.next();
+ String[] patterns = mapping.getPatterns();
+ if (isExcludedMapping(mapping, patterns) || isActuatorWebEndpointMapping(mapping, patterns)) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("The '{}' was excluded", mapping);
+ }
+ iterator.remove();
+ }
+ }
+ }
+
+ /**
+ * Get the context path of the Spring Web Application
+ *
+ * @return context path
+ */
+ protected abstract String getContextPath();
+
+ /**
+ * Is excluded mapping
+ *
+ * @param mapping {@link WebEndpointMapping}
+ * @param patterns patterns
+ * @return if excluded mapping, return true, or false
+ */
+ protected abstract boolean isExcludedMapping(WebEndpointMapping mapping, String[] patterns);
+
+ /**
+ * Is actuator {@link WebEndpointMapping}
+ *
+ * @param mapping {@link WebEndpointMapping}
+ * @param patterns patterns
+ * @return if actuator {@link WebEndpointMapping}, return true, or false
+ */
+ protected boolean isActuatorWebEndpointMapping(WebEndpointMapping mapping, String[] patterns) {
+ for (String pattern : patterns) {
+ if (pattern.startsWith(actuatorBasePath)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnAutoServiceRegistrationEnabled.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnAutoServiceRegistrationEnabled.java
index 9da6d954..720d5f0e 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnAutoServiceRegistrationEnabled.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnAutoServiceRegistrationEnabled.java
@@ -21,12 +21,13 @@
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* The conditional annotation meta-annotates {@link ConditionalOnProperty @ConditionalOnProperty} for
@@ -37,10 +38,10 @@
* @see ConditionalOnProperty
* @since 1.0.0
*/
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
@Documented
-@ConditionalOnProperty(name = SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME)
+@ConditionalOnProperty(name = SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME, matchIfMissing = true)
public @interface ConditionalOnAutoServiceRegistrationEnabled {
/**
@@ -51,4 +52,4 @@
*/
@AliasFor(annotation = ConditionalOnProperty.class, attribute = "matchIfMissing")
boolean matchIfMissing() default true;
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnMultipleRegistrationEnabled.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnMultipleRegistrationEnabled.java
index 9e854728..7051e2ee 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnMultipleRegistrationEnabled.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/condition/ConditionalOnMultipleRegistrationEnabled.java
@@ -4,12 +4,13 @@
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MULTIPLE_REGISTRATION_ENABLED_PROPERTY_NAME;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* {@link Conditional @Conditional} that checks whether the multiple service registry enabled
@@ -18,10 +19,9 @@
* @author Mercy
* @since 1.0
*/
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
@Documented
@ConditionalOnProperty(name = MULTIPLE_REGISTRATION_ENABLED_PROPERTY_NAME)
public @interface ConditionalOnMultipleRegistrationEnabled {
-
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstants.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstants.java
index d27e5587..a6c651e2 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstants.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstants.java
@@ -33,4 +33,14 @@ public interface InstanceConstants {
* The meta-data name of Web Context Path
*/
String WEB_CONTEXT_PATH_METADATA_NAME = "web.context-path";
-}
+
+ /**
+ * The metadata name of management
+ */
+ String MANAGEMENT_PORT_METADATA_NAME = "management-port";
+
+ /**
+ * The metadata name of start time
+ */
+ String START_TIME_METADATA_NAME = "start-time";
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpoint.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpoint.java
index a0869aa0..c9737de9 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpoint.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpoint.java
@@ -1,5 +1,6 @@
package io.microsphere.spring.cloud.client.service.registry.endpoint;
+import io.microsphere.logging.Logger;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
@@ -11,13 +12,17 @@
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
import org.springframework.context.ApplicationListener;
+import static io.microsphere.logging.LoggerFactory.getLogger;
+
/**
* Abstract Endpoint for Service Registration
*
* @author Mercy
* @since 1.0.0
*/
-public class AbstractServiceRegistrationEndpoint implements SmartInitializingSingleton, ApplicationListener {
+public abstract class AbstractServiceRegistrationEndpoint implements SmartInitializingSingleton, ApplicationListener {
+
+ protected final Logger logger = getLogger(getClass());
@Value("${spring.application.name}")
protected String applicationName;
@@ -41,6 +46,17 @@ public class AbstractServiceRegistrationEndpoint implements SmartInitializingSin
protected static boolean running;
+ /**
+ * {@inheritDoc}
+ * Initializes the {@link Registration}, {@link ServiceRegistry}, and
+ * {@link AbstractAutoServiceRegistration} from available bean providers.
+ *
+ *
Example Usage:
+ *
{@code
+ * // Called automatically by the Spring container after all singletons are instantiated.
+ * // Ensures registration, serviceRegistry, and serviceRegistration fields are populated.
+ * }
+ */
@Override
public void afterSingletonsInstantiated() {
this.registration = registrationProvider.getIfAvailable();
@@ -48,18 +64,69 @@ public void afterSingletonsInstantiated() {
this.serviceRegistration = autoServiceRegistrationProvider.getIfAvailable();
}
+ /**
+ * {@inheritDoc}
+ * Captures the web server port and detects the running state of the
+ * {@link AbstractAutoServiceRegistration}.
+ *
+ *
Example Usage:
+ *
{@code
+ * // Called automatically when the embedded web server has been initialized.
+ * // After this event, the port and running state are available.
+ * }
+ *
+ * @param event the {@link WebServerInitializedEvent} carrying the initialized web server
+ */
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
WebServer webServer = event.getWebServer();
this.port = webServer.getPort();
- this.running = serviceRegistration == null ? true : serviceRegistration.isRunning();
+ this.running = detectRunning(serviceRegistration);
+ }
+
+ /**
+ * Detects whether the given {@link AbstractAutoServiceRegistration} is currently running.
+ *
+ * Example Usage:
+ *
{@code
+ * boolean running = AbstractServiceRegistrationEndpoint.detectRunning(serviceRegistration);
+ * }
+ *
+ * @param serviceRegistration the {@link AbstractAutoServiceRegistration} to check, may be {@code null}
+ * @return {@code true} if the service registration is running, {@code false} otherwise
+ */
+ static boolean detectRunning(AbstractAutoServiceRegistration serviceRegistration) {
+ return serviceRegistration == null ? false : serviceRegistration.isRunning();
}
+ /**
+ * Returns whether the service registration is currently running.
+ *
+ * Example Usage:
+ *
{@code
+ * if (endpoint.isRunning()) {
+ * // service is registered and running
+ * }
+ * }
+ *
+ * @return {@code true} if the service registration is running, {@code false} otherwise
+ */
protected boolean isRunning() {
return running;
}
+ /**
+ * Sets the running state of the service registration.
+ *
+ * Example Usage:
+ *
{@code
+ * endpoint.setRunning(true); // mark service as running
+ * endpoint.setRunning(false); // mark service as stopped
+ * }
+ *
+ * @param running {@code true} to mark the service as running, {@code false} otherwise
+ */
public void setRunning(boolean running) {
this.running = running;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpoint.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpoint.java
index f1dbdf72..84b0e481 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpoint.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpoint.java
@@ -1,7 +1,5 @@
package io.microsphere.spring.cloud.client.service.registry.endpoint;
-import io.microsphere.logging.Logger;
-import io.microsphere.logging.LoggerFactory;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
@@ -17,14 +15,25 @@
@Endpoint(id = "serviceDeregistration")
public class ServiceDeregistrationEndpoint extends AbstractServiceRegistrationEndpoint {
- private final Logger logger = LoggerFactory.getLogger(getClass());
-
+ /**
+ * Deregisters the service from the {@link ServiceRegistry} if it is currently running.
+ * This is a write operation exposed via the {@code /actuator/serviceDeregistration} endpoint.
+ *
+ * Example Usage:
+ *
{@code
+ * // Via actuator HTTP POST to /actuator/serviceDeregistration
+ * ServiceDeregistrationEndpoint endpoint = context.getBean(ServiceDeregistrationEndpoint.class);
+ * boolean wasRunning = endpoint.stop();
+ * }
+ *
+ * @return {@code true} if the service was running before deregistration, {@code false} otherwise
+ */
@WriteOperation
public boolean stop() {
boolean isRunning = isRunning();
if (isRunning) {
serviceRegistry.deregister(registration);
- if(logger.isInfoEnabled()) {
+ if (logger.isInfoEnabled()) {
logger.info("Service[name : '{}'] is deregistered!", applicationName);
}
setRunning(false);
@@ -35,4 +44,4 @@ public boolean stop() {
}
return isRunning;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpoint.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpoint.java
index 360f85bb..bd6e78ef 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpoint.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpoint.java
@@ -1,17 +1,16 @@
package io.microsphere.spring.cloud.client.service.registry.endpoint;
-import io.microsphere.logging.Logger;
-import io.microsphere.logging.LoggerFactory;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
import org.springframework.cloud.client.serviceregistry.AbstractAutoServiceRegistration;
-import org.springframework.util.ReflectionUtils;
-import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
+import static io.microsphere.lang.function.ThrowableSupplier.execute;
+import static io.microsphere.reflect.MethodUtils.invokeMethod;
+
/**
* The {@link Endpoint @Endpoint} for Service Registration
*
@@ -23,43 +22,61 @@
@Endpoint(id = "serviceRegistration")
public class ServiceRegistrationEndpoint extends AbstractServiceRegistrationEndpoint {
- private final Logger logger = LoggerFactory.getLogger(getClass());
-
+ /**
+ * Returns metadata about the current service registration, including application name,
+ * registration details, port, status, and running state.
+ * This is a read operation exposed via the {@code /actuator/serviceRegistration} endpoint.
+ *
+ * Example Usage:
+ *
{@code
+ * // Via actuator HTTP GET to /actuator/serviceRegistration
+ * ServiceRegistrationEndpoint endpoint = context.getBean(ServiceRegistrationEndpoint.class);
+ * Map metadata = endpoint.metadata();
+ * String appName = (String) metadata.get("application-name");
+ * }
+ *
+ * @return a {@link Map} containing service registration metadata
+ */
@ReadOperation
public Map metadata() {
-
- Map metadata = new LinkedHashMap<>();
-
+ Map metadata = new LinkedHashMap<>(16);
metadata.put("application-name", applicationName);
metadata.put("registration", registration);
metadata.put("port", port);
metadata.put("status", serviceRegistry.getStatus(registration));
metadata.put("running", isRunning());
-
- if (serviceRegistration != null) {
- metadata.put("enabled", invoke("isEnabled"));
- metadata.put("phase", serviceRegistration.getPhase());
- metadata.put("order", serviceRegistration.getOrder());
- if (Boolean.TRUE.equals(invoke("shouldRegisterManagement"))) {
- metadata.put("managementRegistration", invoke("getManagementRegistration"));
- }
- metadata.put("config", invoke("getConfiguration"));
- }
-
+ metadata.put("enabled", invoke("isEnabled"));
+ metadata.put("phase", serviceRegistration.getPhase());
+ metadata.put("order", serviceRegistration.getOrder());
+ metadata.put("managementRegistration", invoke("getManagementRegistration"));
+ metadata.put("config", invoke("getConfiguration"));
return metadata;
}
+ /**
+ * Registers the service with the {@link ServiceRegistry} if it is not already running.
+ * This is a write operation exposed via the {@code /actuator/serviceRegistration} endpoint.
+ *
+ * Example Usage:
+ *
{@code
+ * // Via actuator HTTP POST to /actuator/serviceRegistration
+ * ServiceRegistrationEndpoint endpoint = context.getBean(ServiceRegistrationEndpoint.class);
+ * boolean wasAlreadyRunning = endpoint.start();
+ * }
+ *
+ * @return {@code true} if the service was already running, {@code false} if it was newly registered
+ */
@WriteOperation
public boolean start() {
boolean isRunning = isRunning();
if (!isRunning) {
serviceRegistry.register(registration);
setRunning(true);
- if(logger.isTraceEnabled()) {
- logger.trace("Service[name : '{}'] is registered!", applicationName);
+ if (logger.isInfoEnabled()) {
+ logger.info("Service[name : '{}'] is registered!", applicationName);
}
} else {
- if(logger.isWarnEnabled()) {
+ if (logger.isWarnEnabled()) {
logger.warn("Service[name : '{}'] was registered!", applicationName);
}
}
@@ -67,15 +84,6 @@ public boolean start() {
}
private Object invoke(String methodName) {
- Object returnValue = null;
- try {
- Class> serviceRegistrationClass = AbstractAutoServiceRegistration.class;
- Method method = serviceRegistrationClass.getDeclaredMethod(methodName);
- ReflectionUtils.makeAccessible(method);
- returnValue = method.invoke(serviceRegistration);
- } catch (Throwable e) {
- logger.error("Invocation on method :" + methodName + "is failed", e);
- }
- return returnValue;
+ return execute(() -> invokeMethod(serviceRegistration, methodName), e -> null);
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationDeregisteredEvent.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationDeregisteredEvent.java
index 294583c2..45ef54ce 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationDeregisteredEvent.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationDeregisteredEvent.java
@@ -19,6 +19,8 @@
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.DEREGISTERED;
+
/**
* The after-{@link ServiceRegistry#deregister(Registration) deregister} event.
*
@@ -28,17 +30,40 @@
*/
public class RegistrationDeregisteredEvent extends RegistrationEvent {
+ /**
+ * Create a new {@link RegistrationDeregisteredEvent} indicating that a
+ * {@link Registration} has been deregistered from the {@link ServiceRegistry}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceRegistry registry = ...;
+ * Registration registration = ...;
+ * RegistrationDeregisteredEvent event = new RegistrationDeregisteredEvent(registry, registration);
+ * applicationContext.publishEvent(event);
+ * }
+ *
+ * @param registry the {@link ServiceRegistry} that performed the deregistration
+ * @param source the {@link Registration} that was deregistered
+ */
public RegistrationDeregisteredEvent(ServiceRegistry registry, Registration source) {
super(registry, source);
}
+ /**
+ * Returns the {@link Type} of this event, which is always {@link Type#DEREGISTERED}.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationDeregisteredEvent event = ...;
+ * RegistrationEvent.Type type = event.getType();
+ * // type == RegistrationEvent.Type.DEREGISTERED
+ * }
+ *
+ * @return {@link Type#DEREGISTERED}
+ */
@Override
- public boolean isRegistered() {
- return false;
+ public Type getType() {
+ return DEREGISTERED;
}
- @Override
- public boolean isDeregistered() {
- return true;
- }
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationEvent.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationEvent.java
index d51d26a6..939afba7 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationEvent.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationEvent.java
@@ -22,6 +22,11 @@
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.DEREGISTERED;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.PRE_DEREGISTERED;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.PRE_REGISTERED;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.REGISTERED;
+
/**
* The Spring event for {@link ServiceRegistry}
*
@@ -37,6 +42,20 @@ public abstract class RegistrationEvent extends ApplicationEvent {
private final ServiceRegistry registry;
+ /**
+ * Create a new {@link RegistrationEvent} with the given {@link ServiceRegistry} and {@link Registration}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceRegistry registry = ...;
+ * Registration registration = ...;
+ * // Typically used through a subclass such as RegistrationRegisteredEvent
+ * RegistrationRegisteredEvent event = new RegistrationRegisteredEvent(registry, registration);
+ * }
+ *
+ * @param registry the {@link ServiceRegistry} that triggered the event
+ * @param source the {@link Registration} associated with the event
+ */
public RegistrationEvent(ServiceRegistry registry, Registration source) {
super(source);
Assert.notNull(registry, "The 'registry' must not be null");
@@ -80,7 +99,7 @@ public ServiceRegistry getRegistry() {
* @return true if pre-registered
*/
public final boolean isPreRegistered() {
- return !isRegistered();
+ return getType() == PRE_REGISTERED;
}
/**
@@ -89,7 +108,9 @@ public final boolean isPreRegistered() {
*
* @return true if registered
*/
- public abstract boolean isRegistered();
+ public final boolean isRegistered() {
+ return getType() == REGISTERED;
+ }
/**
* Current event is raised before the {@link #getRegistration() registration} is
@@ -98,7 +119,7 @@ public final boolean isPreRegistered() {
* @return true if pre-deregistered
*/
public final boolean isPreDeregistered() {
- return !isDeregistered();
+ return getType() == PRE_DEREGISTERED;
}
/**
@@ -107,6 +128,40 @@ public final boolean isPreDeregistered() {
*
* @return true if deregistered
*/
- public abstract boolean isDeregistered();
+ public final boolean isDeregistered() {
+ return getType() == DEREGISTERED;
+ }
-}
+ /**
+ * Get the {@link Type} of the {@link RegistrationEvent}
+ *
+ * @return non-null
+ */
+ public abstract Type getType();
+
+ /**
+ * The {@link Type} of the {@link RegistrationEvent}
+ */
+ public static enum Type {
+
+ /**
+ * The {@link RegistrationPreRegisteredEvent}
+ */
+ PRE_REGISTERED,
+
+ /**
+ * The {@link RegistrationRegisteredEvent}
+ */
+ REGISTERED,
+
+ /**
+ * The {@link RegistrationPreDeregisteredEvent}
+ */
+ PRE_DEREGISTERED,
+
+ /**
+ * The {@link RegistrationDeregisteredEvent}
+ */
+ DEREGISTERED
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreDeregisteredEvent.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreDeregisteredEvent.java
index 6e194752..b98fc529 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreDeregisteredEvent.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreDeregisteredEvent.java
@@ -19,6 +19,8 @@
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.PRE_DEREGISTERED;
+
/**
* The before-{@link ServiceRegistry#deregister(Registration) deregister} event.
*
@@ -28,17 +30,39 @@
*/
public class RegistrationPreDeregisteredEvent extends RegistrationEvent {
+ /**
+ * Create a new {@link RegistrationPreDeregisteredEvent} indicating that a
+ * {@link Registration} is about to be deregistered from the {@link ServiceRegistry}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceRegistry registry = ...;
+ * Registration registration = ...;
+ * RegistrationPreDeregisteredEvent event = new RegistrationPreDeregisteredEvent(registry, registration);
+ * applicationContext.publishEvent(event);
+ * }
+ *
+ * @param registry the {@link ServiceRegistry} that will perform the deregistration
+ * @param source the {@link Registration} to be deregistered
+ */
public RegistrationPreDeregisteredEvent(ServiceRegistry registry, Registration source) {
super(registry, source);
}
+ /**
+ * Returns the {@link Type} of this event, which is always {@link Type#PRE_DEREGISTERED}.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationPreDeregisteredEvent event = ...;
+ * RegistrationEvent.Type type = event.getType();
+ * // type == RegistrationEvent.Type.PRE_DEREGISTERED
+ * }
+ *
+ * @return {@link Type#PRE_DEREGISTERED}
+ */
@Override
- public boolean isRegistered() {
- return false;
- }
-
- @Override
- public boolean isDeregistered() {
- return false;
+ public Type getType() {
+ return PRE_DEREGISTERED;
}
}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreRegisteredEvent.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreRegisteredEvent.java
index 2e0d192d..8ae5c353 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreRegisteredEvent.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationPreRegisteredEvent.java
@@ -19,6 +19,8 @@
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.PRE_REGISTERED;
+
/**
* The before-{@link ServiceRegistry#register(Registration) register} event.
*
@@ -28,17 +30,39 @@
*/
public class RegistrationPreRegisteredEvent extends RegistrationEvent {
+ /**
+ * Create a new {@link RegistrationPreRegisteredEvent} indicating that a
+ * {@link Registration} is about to be registered with the {@link ServiceRegistry}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceRegistry registry = ...;
+ * Registration registration = ...;
+ * RegistrationPreRegisteredEvent event = new RegistrationPreRegisteredEvent(registry, registration);
+ * applicationContext.publishEvent(event);
+ * }
+ *
+ * @param registry the {@link ServiceRegistry} that will perform the registration
+ * @param source the {@link Registration} to be registered
+ */
public RegistrationPreRegisteredEvent(ServiceRegistry registry, Registration source) {
super(registry, source);
}
+ /**
+ * Returns the {@link Type} of this event, which is always {@link Type#PRE_REGISTERED}.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationPreRegisteredEvent event = ...;
+ * RegistrationEvent.Type type = event.getType();
+ * // type == RegistrationEvent.Type.PRE_REGISTERED
+ * }
+ *
+ * @return {@link Type#PRE_REGISTERED}
+ */
@Override
- public boolean isRegistered() {
- return false;
- }
-
- @Override
- public boolean isDeregistered() {
- return false;
+ public Type getType() {
+ return PRE_REGISTERED;
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationRegisteredEvent.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationRegisteredEvent.java
index cc20ed1d..42932dc7 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationRegisteredEvent.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/registry/event/RegistrationRegisteredEvent.java
@@ -19,6 +19,8 @@
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.REGISTERED;
+
/**
* The after-{@link ServiceRegistry#register(Registration) register} event.
*
@@ -28,17 +30,39 @@
*/
public class RegistrationRegisteredEvent extends RegistrationEvent {
+ /**
+ * Create a new {@link RegistrationRegisteredEvent} indicating that a
+ * {@link Registration} has been registered with the {@link ServiceRegistry}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceRegistry registry = ...;
+ * Registration registration = ...;
+ * RegistrationRegisteredEvent event = new RegistrationRegisteredEvent(registry, registration);
+ * applicationContext.publishEvent(event);
+ * }
+ *
+ * @param registry the {@link ServiceRegistry} that performed the registration
+ * @param source the {@link Registration} that was registered
+ */
public RegistrationRegisteredEvent(ServiceRegistry registry, Registration source) {
super(registry, source);
}
+ /**
+ * Returns the {@link Type} of this event, which is always {@link Type#REGISTERED}.
+ *
+ * Example Usage:
+ *
{@code
+ * RegistrationRegisteredEvent event = ...;
+ * RegistrationEvent.Type type = event.getType();
+ * // type == RegistrationEvent.Type.REGISTERED
+ * }
+ *
+ * @return {@link Type#REGISTERED}
+ */
@Override
- public boolean isRegistered() {
- return true;
- }
-
- @Override
- public boolean isDeregistered() {
- return false;
+ public Type getType() {
+ return REGISTERED;
}
}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtils.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtils.java
index af21b9f1..1dae14d9 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtils.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtils.java
@@ -16,24 +16,44 @@
*/
package io.microsphere.spring.cloud.client.service.util;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import io.microsphere.annotation.Nonnull;
+import io.microsphere.annotation.Nullable;
+import io.microsphere.json.JSONArray;
+import io.microsphere.json.JSONObject;
import io.microsphere.logging.Logger;
-import io.microsphere.logging.LoggerFactory;
import io.microsphere.spring.web.metadata.WebEndpointMapping;
+import io.microsphere.spring.web.metadata.WebEndpointMapping.Builder;
import io.microsphere.util.BaseUtils;
+import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
+import java.net.URI;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
+import static io.microsphere.collection.ListUtils.newArrayList;
+import static io.microsphere.constants.SeparatorConstants.LINE_SEPARATOR;
+import static io.microsphere.constants.SymbolConstants.COLON_CHAR;
+import static io.microsphere.constants.SymbolConstants.COMMA;
+import static io.microsphere.constants.SymbolConstants.LEFT_SQUARE_BRACKET;
+import static io.microsphere.constants.SymbolConstants.RIGHT_SQUARE_BRACKET;
+import static io.microsphere.json.JSONUtils.jsonArray;
+import static io.microsphere.json.JSONUtils.readArray;
+import static io.microsphere.logging.LoggerFactory.getLogger;
import static io.microsphere.net.URLUtils.decode;
import static io.microsphere.net.URLUtils.encode;
import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_CONTEXT_PATH_METADATA_NAME;
import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_MAPPINGS_METADATA_NAME;
+import static io.microsphere.spring.web.metadata.WebEndpointMapping.Kind.valueOf;
+import static io.microsphere.spring.web.metadata.WebEndpointMapping.of;
+import static io.microsphere.util.StringUtils.EMPTY_STRING;
+import static io.microsphere.util.StringUtils.EMPTY_STRING_ARRAY;
+import static io.microsphere.util.StringUtils.isBlank;
+import static java.lang.String.valueOf;
+import static java.net.URI.create;
+import static java.util.Collections.emptyList;
/**
* {@link ServiceInstance} Utilities class
@@ -43,36 +63,192 @@
*/
public class ServiceInstanceUtils extends BaseUtils {
- private static final Logger logger = LoggerFactory.getLogger(ServiceInstanceUtils.class);
+ private static final Logger logger = getLogger(ServiceInstanceUtils.class);
+ /**
+ * Attach {@link WebEndpointMapping} metadata to the given {@link ServiceInstance}.
+ * The web endpoint mappings are serialized as JSON and stored in the service instance's
+ * metadata under the {@link io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants#WEB_MAPPINGS_METADATA_NAME} key.
+ *
+ * Example Usage:
+ *
{@code
+ * ServiceInstance serviceInstance = new DefaultServiceInstance("id", "service", "localhost", 8080, false);
+ * Collection mappings = new ArrayList<>();
+ * ServiceInstanceUtils.attachMetadata("/context", serviceInstance, mappings);
+ * }
+ *
+ * @param contextPath the web application context path
+ * @param serviceInstance the {@link ServiceInstance} to attach metadata to
+ * @param webEndpointMappings the collection of {@link WebEndpointMapping}s to attach
+ */
public static void attachMetadata(String contextPath, ServiceInstance serviceInstance, Collection webEndpointMappings) {
Map metadata = serviceInstance.getMetadata();
- StringJoiner jsonBuilder = new StringJoiner(",", "[", "]");
+ StringJoiner jsonBuilder = new StringJoiner(COMMA + LINE_SEPARATOR, LEFT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET);
webEndpointMappings.stream().map(WebEndpointMapping::toJSON).forEach(jsonBuilder::add);
String json = jsonBuilder.toString();
+ if (logger.isTraceEnabled()) {
+ logger.trace("Web Endpoint Mappings JSON: \n{}", json);
+ }
+ json = json.replace(LINE_SEPARATOR, EMPTY_STRING);
+ String encodedJson = encode(json);
+
metadata.put(WEB_CONTEXT_PATH_METADATA_NAME, contextPath);
- try {
- String encodedJson = encode(json);
- metadata.put(WEB_MAPPINGS_METADATA_NAME, encodedJson);
- } catch (IllegalArgumentException e) {
- logger.error("The JSON content of WebEndpointMappings can't be encoded : {}", json, e);
+ metadata.put(WEB_MAPPINGS_METADATA_NAME, encodedJson);
+ if (logger.isTraceEnabled()) {
+ logger.trace("ServiceInstance's metadata :");
+ metadata.forEach((name, value) -> logger.trace("{} : {}", name, value));
}
}
+ /**
+ * Get {@link WebEndpointMapping}s from {@link ServiceInstance}
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @return {@link WebEndpointMapping}s
+ */
+ @Nonnull
public static Collection getWebEndpointMappings(ServiceInstance serviceInstance) {
- List webEndpointMappings = Collections.emptyList();
+ String encodedJSON = getMetadata(serviceInstance, WEB_MAPPINGS_METADATA_NAME);
+ return parseWebEndpointMappings(encodedJSON);
+ }
+
+ /**
+ * Get the String representation of {@link ServiceInstance#getUri()}
+ *
+ * @param instance {@link ServiceInstance}
+ * @return the String representation of {@link ServiceInstance#getUri()}
+ */
+ @Nonnull
+ public static String getUriString(ServiceInstance instance) {
+ boolean isSecure = instance.isSecure();
+ String prefix = isSecure ? "https://" : "http://";
+ String host = instance.getHost();
+ int port = instance.getPort();
+ if (port <= 0) {
+ port = isSecure ? 443 : 80;
+ }
+ String portString = valueOf(port);
+ StringBuilder urlStringBuilder = new StringBuilder((isSecure ? 9 : 8) + host.length() + portString.length());
+ urlStringBuilder.append(prefix)
+ .append(host)
+ .append(COLON_CHAR)
+ .append(portString);
+ return urlStringBuilder.toString();
+ }
+
+ /**
+ * Alternative method of {@link ServiceInstance#getUri()} with the better performance
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @return {@link URI} instance
+ * @see DefaultServiceInstance#getUri(ServiceInstance)
+ */
+ @Nonnull
+ public static URI getUri(ServiceInstance serviceInstance) {
+ String uriString = getUriString(serviceInstance);
+ return create(uriString);
+ }
+
+ /**
+ * Get metadata by metadataName
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @param metadataName metadataName
+ * @return metadata value
+ */
+ @Nullable
+ public static String getMetadata(ServiceInstance serviceInstance, String metadataName) {
Map metadata = serviceInstance.getMetadata();
- String encodedJSON = metadata.get(WEB_MAPPINGS_METADATA_NAME);
- if (encodedJSON != null) {
- try {
- String json = decode(encodedJSON);
- ObjectMapper objectMapper = new ObjectMapper();
- webEndpointMappings = objectMapper.readValue(json, new TypeReference>() {
- });
- } catch (Throwable e) {
- logger.error("The encoded JSON content of WebEndpointMappings can't be parsed : {}", encodedJSON, e);
- }
+ return metadata.get(metadataName);
+ }
+
+ /**
+ * Set metadata by metadataName
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @param metadataName metadataName
+ * @param metadataValue metadata value
+ * @return the previous value associated with metadataName if found, null otherwise
+ */
+ public static String setMetadata(ServiceInstance serviceInstance, String metadataName, String metadataValue) {
+ Map metadata = serviceInstance.getMetadata();
+ return metadata.put(metadataName, metadataValue);
+ }
+
+ /**
+ * Remove metadata by metadataName
+ *
+ * @param serviceInstance {@link ServiceInstance}
+ * @param metadataName metadataName
+ * @return the value associated with metadataName if found, null otherwise
+ */
+ public static String removeMetadata(ServiceInstance serviceInstance, String metadataName) {
+ Map metadata = serviceInstance.getMetadata();
+ return metadata.remove(metadataName);
+ }
+
+ /**
+ * Set properties from source to target
+ *
+ * @param source source {@link ServiceInstance}
+ * @param target target {@link DefaultServiceInstance}
+ */
+ public static void setProperties(ServiceInstance source, DefaultServiceInstance target) {
+ target.setInstanceId(source.getInstanceId());
+ target.setServiceId(source.getServiceId());
+ target.setSecure(source.isSecure());
+ target.setHost(source.getHost());
+ target.setPort(source.getPort());
+ Map metadata = source.getMetadata();
+ metadata.clear();
+ metadata.putAll(source.getMetadata());
+ }
+
+ static List parseWebEndpointMappings(String encodedJSON) {
+ if (isBlank(encodedJSON)) {
+ return emptyList();
+ }
+ String json = decode(encodedJSON);
+ JSONArray jsonArray = jsonArray(json);
+ int size = jsonArray.length();
+ List webEndpointMappings = newArrayList(size);
+ for (int i = 0; i < size; i++) {
+ JSONObject jsonObject = jsonArray.optJSONObject(i);
+ WebEndpointMapping webEndpointMapping = parseWebEndpointMapping(jsonObject);
+ webEndpointMappings.add(webEndpointMapping);
}
return webEndpointMappings;
}
-}
+
+ static WebEndpointMapping parseWebEndpointMapping(JSONObject jsonObject) {
+ String kind = jsonObject.optString("kind");
+ int id = jsonObject.optInt("id");
+ boolean negated = jsonObject.optBoolean("negated");
+ String[] patterns = getArray(jsonObject, "patterns");
+ String[] methods = getArray(jsonObject, "methods");
+ String[] params = getArray(jsonObject, "params");
+ String[] headers = getArray(jsonObject, "headers");
+ String[] consumes = getArray(jsonObject, "consumes");
+ String[] produces = getArray(jsonObject, "produces");
+ Builder> builder = of(valueOf(kind))
+ .endpoint(Integer.valueOf(id))
+ .patterns(patterns)
+ .methods(methods)
+ .params(params)
+ .headers(headers)
+ .consumes(consumes)
+ .produces(produces);
+ if (negated) {
+ builder.negate();
+ }
+ return builder.build();
+ }
+
+ static String[] getArray(JSONObject jsonObject, String name) {
+ JSONArray jsonArray = jsonObject.optJSONArray(name);
+ return jsonArray == null ? EMPTY_STRING_ARRAY : readArray(jsonArray, String.class);
+ }
+
+ private ServiceInstanceUtils() {
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/condition/ConditionalOnUtilEnabled.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/condition/ConditionalOnUtilEnabled.java
new file mode 100644
index 00000000..a24ed4cb
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/condition/ConditionalOnUtilEnabled.java
@@ -0,0 +1,45 @@
+/*
+ * 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 io.microsphere.spring.cloud.commons.condition;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.commons.util.UtilAutoConfiguration;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.UTIL_ENABLED_PROPERTY_NAME;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * The conditional annotation meta-annotates {@link ConditionalOnProperty @ConditionalOnProperty} for
+ * {@link UtilAutoConfiguration} enabled.
+ *
+ * @author Mercy
+ * @see UtilAutoConfiguration
+ * @see ConditionalOnProperty
+ * @since 1.0.0
+ */
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
+@Documented
+@ConditionalOnProperty(name = UTIL_ENABLED_PROPERTY_NAME, matchIfMissing = true)
+public @interface ConditionalOnUtilEnabled {
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstants.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstants.java
index 62082630..b30fbe48 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstants.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstants.java
@@ -16,9 +16,9 @@
*/
package io.microsphere.spring.cloud.commons.constants;
-import org.springframework.cloud.client.CommonsClientAutoConfiguration;
-import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration;
+import io.microsphere.annotation.ConfigurationProperty;
+import static io.microsphere.annotation.ConfigurationProperty.APPLICATION_SOURCE;
import static io.microsphere.constants.PropertyConstants.ENABLED_PROPERTY_NAME;
import static io.microsphere.constants.PropertyConstants.MICROSPHERE_PROPERTY_NAME_PREFIX;
@@ -28,32 +28,7 @@
* @author Mercy
* @since 1.0.0
*/
-public interface CommonsPropertyConstants {
-
- /**
- * The property name prefix of Spring Cloud properties : "spring.cloud."
- */
- String SPRING_CLOUD_PROPERTY_PREFIX = "spring.cloud.";
-
- /**
- * The property name prefix of Spring Cloud Service Registry : "spring.cloud.service-registry."
- */
- String SERVICE_REGISTRY_PROPERTY_PREFIX = SPRING_CLOUD_PROPERTY_PREFIX + "service-registry.";
-
- /**
- * The property name for Spring Cloud Service Registry Auto-Registration Feature :
- * "spring.cloud.service-registry.auto-registration.enabled"
- *
- * @see AutoServiceRegistrationAutoConfiguration
- */
- String SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME = SERVICE_REGISTRY_PROPERTY_PREFIX + "auto-registration." + ENABLED_PROPERTY_NAME;
-
- /**
- * The property name for enabling Spring Cloud Features : "spring.cloud.features.enabled"
- *
- * @see CommonsClientAutoConfiguration.ActuatorConfiguration
- */
- String FEATURES_ENABLED_PROPERTY_NAME = SPRING_CLOUD_PROPERTY_PREFIX + "features." + ENABLED_PROPERTY_NAME;
+public interface CommonsPropertyConstants extends SpringCloudPropertyConstants {
/**
* The property name prefix of Microsphere Cloud : "microsphere.spring.cloud."
@@ -68,21 +43,39 @@ public interface CommonsPropertyConstants {
/**
* The property name for Multiple Service Registry Enabled Feature : "microsphere.spring.cloud.multiple-registration.enabled"
*/
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "false",
+ source = APPLICATION_SOURCE
+ )
String MULTIPLE_REGISTRATION_ENABLED_PROPERTY_NAME = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "multiple-registration." + ENABLED_PROPERTY_NAME;
/**
* The property name for Default Service Registry Type : "microsphere.spring.cloud.default-registration.type"
*/
+ @ConfigurationProperty(
+ type = Class.class,
+ source = APPLICATION_SOURCE
+ )
String MULTIPLE_REGISTRATION_DEFAULT_REGISTRATION_PROPERTY_NAME = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "default-registration.type";
/**
* The property name for Default Service Registry Type : "microsphere.spring.cloud.default-service-registry.type"
*/
+ @ConfigurationProperty(
+ type = Class.class,
+ source = APPLICATION_SOURCE
+ )
String MULTIPLE_REGISTRATION_DEFAULT_REGISTRY_PROPERTY_NAME = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "default-service-registry.type";
/**
* The property name for Composite Service Registry Enabled Feature : "microsphere.spring.cloud.composite-registration.enabled"
*/
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "false",
+ source = APPLICATION_SOURCE
+ )
String COMPOSITE_REGISTRATION_ENABLED_PROPERTY_NAME = MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX + "composite-registration." + ENABLED_PROPERTY_NAME;
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/SpringCloudPropertyConstants.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/SpringCloudPropertyConstants.java
new file mode 100644
index 00000000..0fce3f30
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/commons/constants/SpringCloudPropertyConstants.java
@@ -0,0 +1,97 @@
+/*
+ * 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 io.microsphere.spring.cloud.commons.constants;
+
+import io.microsphere.annotation.ConfigurationProperty;
+import org.springframework.cloud.client.CommonsClientAutoConfiguration;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration;
+import org.springframework.cloud.commons.util.UtilAutoConfiguration;
+
+import static io.microsphere.annotation.ConfigurationProperty.APPLICATION_SOURCE;
+import static io.microsphere.constants.PropertyConstants.ENABLED_PROPERTY_NAME;
+
+/**
+ * The property constants for Spring Cloud
+ *
+ * @author Mercy
+ * @see AutoServiceRegistrationAutoConfiguration
+ * @see CommonsClientAutoConfiguration
+ * @since 1.0.0
+ */
+public interface SpringCloudPropertyConstants {
+
+ /**
+ * The property name prefix of Spring Cloud properties : "spring.cloud."
+ */
+ String SPRING_CLOUD_PROPERTY_PREFIX = "spring.cloud.";
+
+ /**
+ * The property name prefix of Spring Cloud Service Registry : "spring.cloud.service-registry."
+ */
+ String SERVICE_REGISTRY_PROPERTY_PREFIX = SPRING_CLOUD_PROPERTY_PREFIX + "service-registry.";
+
+ /**
+ * The property name for Spring Cloud Service Registry Auto-Registration Feature :
+ * "spring.cloud.service-registry.auto-registration.enabled"
+ *
+ * @see AutoServiceRegistrationAutoConfiguration
+ */
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "true",
+ source = APPLICATION_SOURCE
+ )
+ String SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME = SERVICE_REGISTRY_PROPERTY_PREFIX + "auto-registration." + ENABLED_PROPERTY_NAME;
+
+ /**
+ * The property name for enabling Spring Cloud Features : "spring.cloud.features.enabled"
+ *
+ * @see CommonsClientAutoConfiguration.ActuatorConfiguration
+ */
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "true",
+ source = APPLICATION_SOURCE
+ )
+ String FEATURES_ENABLED_PROPERTY_NAME = SPRING_CLOUD_PROPERTY_PREFIX + "features." + ENABLED_PROPERTY_NAME;
+
+ /**
+ * The property name for enabling Spring Cloud Load-Balancer : "spring.cloud.loadbalancer.enabled"
+ *
+ * @see org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration
+ */
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "true",
+ source = APPLICATION_SOURCE
+ )
+ String LOAD_BALANCER_ENABLED_PROPERTY_NAME = SPRING_CLOUD_PROPERTY_PREFIX + "loadbalancer." + ENABLED_PROPERTY_NAME;
+
+
+ /**
+ * The property name for enabling Spring Cloud Util : "spring.cloud.util.enabled"
+ *
+ * @see UtilAutoConfiguration
+ */
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "true",
+ source = APPLICATION_SOURCE
+ )
+ String UTIL_ENABLED_PROPERTY_NAME = SPRING_CLOUD_PROPERTY_PREFIX + "util." + ENABLED_PROPERTY_NAME;
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstants.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstants.java
index c6071fe6..18b65053 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstants.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstants.java
@@ -1,7 +1,10 @@
package io.microsphere.spring.cloud.fault.tolerance.constants;
+import io.microsphere.annotation.ConfigurationProperty;
+
import java.util.concurrent.TimeUnit;
+import static io.microsphere.annotation.ConfigurationProperty.APPLICATION_SOURCE;
import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX;
/**
@@ -22,24 +25,24 @@ public interface FaultTolerancePropertyConstants {
*/
String LOAD_BALANCER_PROPERTY_PREFIX = FAULT_TOLERANCE_PROPERTY_NAME_PREFIX + "load-balancer.";
- /**
- * The metadata name of management
- */
- String MANAGEMENT_PORT_METADATA_NAME = "management-port";
-
- /**
- * The metadata name of start time
- */
- String START_TIME_METADATA_NAME = "start-time";
-
/**
* The metadata name of warm-up time
*/
+ @ConfigurationProperty(
+ type = long.class,
+ defaultValue = "600000",
+ source = APPLICATION_SOURCE
+ )
String WARMUP_TIME_PROPERTY_NAME = FAULT_TOLERANCE_PROPERTY_NAME_PREFIX + "warmup-time";
/**
* The property name of weight
*/
+ @ConfigurationProperty(
+ type = int.class,
+ defaultValue = "100",
+ source = APPLICATION_SOURCE
+ )
String WEIGHT_PROPERTY_NAME = FAULT_TOLERANCE_PROPERTY_NAME_PREFIX + "weight";
/**
@@ -51,5 +54,4 @@ public interface FaultTolerancePropertyConstants {
* The default property value of weight : 100
*/
int DEFAULT_WEIGHT_PROPERTY_VALUE = 100;
-
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobin.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobin.java
index b9596a00..b2991743 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobin.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobin.java
@@ -15,44 +15,153 @@ public class WeightedRoundRobin {
private volatile int weight;
- private LongAdder current = new LongAdder();
+ LongAdder current = new LongAdder();
private volatile long lastUpdate;
+ /**
+ * Create a new {@link WeightedRoundRobin} instance with the given identifier.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setWeight(5);
+ * }
+ *
+ * @param id the unique identifier for this weighted round-robin entry
+ */
public WeightedRoundRobin(String id) {
this.id = id;
}
+ /**
+ * Get the unique identifier for this {@link WeightedRoundRobin} entry.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * String id = wrr.getId(); // "server-1"
+ * }
+ *
+ * @return the identifier
+ */
public String getId() {
return id;
}
+ /**
+ * Get the current weight of this {@link WeightedRoundRobin} entry.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setWeight(5);
+ * int weight = wrr.getWeight(); // 5
+ * }
+ *
+ * @return the current weight
+ */
public int getWeight() {
return weight;
}
+ /**
+ * Set the weight for this {@link WeightedRoundRobin} entry and reset the current counter.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setWeight(10);
+ * }
+ *
+ * @param weight the new weight value
+ */
public void setWeight(int weight) {
this.weight = weight;
current.reset();
}
+ /**
+ * Increase the current counter by the weight value and return the updated value.
+ * Used during weighted round-robin selection to accumulate the weight for this entry.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setWeight(5);
+ * long current = wrr.increaseCurrent(); // 5
+ * current = wrr.increaseCurrent(); // 10
+ * }
+ *
+ * @return the updated current counter value
+ */
public long increaseCurrent() {
current.add(weight);
return current.longValue();
}
+ /**
+ * Subtract the total weight from the current counter after this entry has been selected.
+ * This is part of the weighted round-robin algorithm to reduce the selected entry's counter.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setWeight(5);
+ * wrr.increaseCurrent();
+ * wrr.sel(10); // subtract total weight of all entries
+ * }
+ *
+ * @param total the total weight of all entries to subtract
+ */
public void sel(int total) {
current.add(-1 * total);
}
+ /**
+ * Get the timestamp of the last update to this {@link WeightedRoundRobin} entry.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setLastUpdate(System.currentTimeMillis());
+ * long lastUpdate = wrr.getLastUpdate();
+ * }
+ *
+ * @return the last update timestamp in milliseconds
+ */
public long getLastUpdate() {
return lastUpdate;
}
+ /**
+ * Set the timestamp of the last update to this {@link WeightedRoundRobin} entry.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setLastUpdate(System.currentTimeMillis());
+ * }
+ *
+ * @param lastUpdate the last update timestamp in milliseconds
+ */
public void setLastUpdate(long lastUpdate) {
this.lastUpdate = lastUpdate;
}
+ /**
+ * Returns a string representation of this {@link WeightedRoundRobin} including
+ * its id, weight, current counter, and last update timestamp.
+ *
+ * Example Usage:
+ *
{@code
+ * WeightedRoundRobin wrr = new WeightedRoundRobin("server-1");
+ * wrr.setWeight(5);
+ * String s = wrr.toString(); // "WeightedRoundRobin[id='server-1', weight=5, current=0, lastUpdate=0]"
+ * }
+ *
+ * @return a string representation of this entry
+ */
@Override
public String toString() {
return new StringJoiner(", ", WeightedRoundRobin.class.getSimpleName() + "[", "]")
@@ -62,4 +171,4 @@ public String toString() {
.add("lastUpdate=" + lastUpdate)
.toString();
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtils.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtils.java
index 52ed5717..83424784 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtils.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtils.java
@@ -8,10 +8,6 @@
*/
public abstract class LoadBalancerUtils {
- private LoadBalancerUtils() {
- throw new UnsupportedOperationException();
- }
-
/**
* Calculate the weight according to the uptime proportion of warmup time
* the new weight will be within 1(inclusive) to weight(inclusive)
@@ -25,4 +21,7 @@ public static int calculateWarmupWeight(long uptime, long warmup, int weight) {
int ww = (int) (Math.round(Math.pow((uptime / (double) warmup), 2) * weight));
return ww < 1 ? 1 : (Math.min(ww, weight));
}
-}
+
+ private LoadBalancerUtils() {
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfiguration.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfiguration.java
index b4ee6190..b32ad811 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfiguration.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfiguration.java
@@ -16,7 +16,8 @@
*/
package io.microsphere.spring.cloud.fault.tolerance.tomcat.autoconfigure;
-import io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants;
+import io.microsphere.annotation.ConfigurationProperty;
+import io.microsphere.constants.PropertyConstants;
import io.microsphere.spring.cloud.fault.tolerance.tomcat.event.TomcatDynamicConfigurationListener;
import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.ObjectProvider;
@@ -35,8 +36,9 @@
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.event.EventListener;
-import static io.microsphere.constants.PropertyConstants.ENABLED_PROPERTY_NAME;
-import static io.microsphere.spring.cloud.fault.tolerance.tomcat.autoconfigure.TomcatFaultToleranceAutoConfiguration.TOMCAT_PROPERTY_PREFIX;
+import static io.microsphere.annotation.ConfigurationProperty.APPLICATION_SOURCE;
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.FAULT_TOLERANCE_PROPERTY_NAME_PREFIX;
+import static io.microsphere.spring.cloud.fault.tolerance.tomcat.autoconfigure.TomcatFaultToleranceAutoConfiguration.ENABLED_PROPERTY_NAME;
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
/**
@@ -44,7 +46,6 @@
* @since 1.0.0
*/
@ConditionalOnProperty(
- prefix = TOMCAT_PROPERTY_PREFIX,
name = ENABLED_PROPERTY_NAME,
matchIfMissing = true
)
@@ -59,8 +60,30 @@
})
public class TomcatFaultToleranceAutoConfiguration {
- public static final String TOMCAT_PROPERTY_PREFIX = FaultTolerancePropertyConstants.FAULT_TOLERANCE_PROPERTY_NAME_PREFIX + "tomcat";
+ public static final String TOMCAT_PROPERTY_PREFIX = FAULT_TOLERANCE_PROPERTY_NAME_PREFIX + "tomcat";
+ /**
+ * The property name to Tomcat's fault-tolerance enabled or not: "microsphere.spring.cloud.fault-tolerance.tomcat.enabled"
+ */
+ @ConfigurationProperty(
+ type = boolean.class,
+ defaultValue = "true",
+ source = APPLICATION_SOURCE
+ )
+ public static final String ENABLED_PROPERTY_NAME = TOMCAT_PROPERTY_PREFIX + "." + PropertyConstants.ENABLED_PROPERTY_NAME;
+
+ /**
+ * Handles the {@link WebServerInitializedEvent} to register a {@link TomcatDynamicConfigurationListener}
+ * when the embedded web server is a {@link TomcatWebServer}.
+ *
+ * Example Usage:
+ *
{@code
+ * // Automatically invoked by Spring when WebServerInitializedEvent is published.
+ * // The listener is registered as an ApplicationListener on the web application context.
+ * }
+ *
+ * @param event the {@link WebServerInitializedEvent} triggered after the web server starts
+ */
@EventListener(WebServerInitializedEvent.class)
public void onWebServerInitializedEvent(WebServerInitializedEvent event) {
WebServerApplicationContext webServerApplicationContext = event.getApplicationContext();
@@ -77,4 +100,4 @@ public void onWebServerInitializedEvent(WebServerInitializedEvent event) {
});
}
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListener.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListener.java
index 712ca8b1..500e3f82 100644
--- a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListener.java
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListener.java
@@ -18,7 +18,6 @@
import io.microsphere.logging.Logger;
-import io.microsphere.logging.LoggerFactory;
import org.apache.catalina.connector.Connector;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
@@ -37,6 +36,8 @@
import java.util.Set;
import java.util.stream.Collectors;
+import static io.microsphere.logging.LoggerFactory.getLogger;
+import static io.microsphere.spring.beans.BeanUtils.isBeanPresent;
import static io.microsphere.spring.boot.context.properties.bind.util.BindUtils.bind;
import static io.microsphere.spring.core.env.EnvironmentUtils.getProperties;
import static io.microsphere.spring.core.env.PropertySourcesUtils.getSubProperties;
@@ -51,7 +52,7 @@
*/
public class TomcatDynamicConfigurationListener implements ApplicationListener {
- private static final Logger logger = LoggerFactory.getLogger(TomcatDynamicConfigurationListener.class);
+ private static final Logger logger = getLogger(TomcatDynamicConfigurationListener.class);
private static final String SERVER_PROPERTIES_PREFIX = "server";
@@ -67,6 +68,25 @@ public class TomcatDynamicConfigurationListener implements ApplicationListenerExample Usage:
+ * {@code
+ * TomcatWebServer tomcatWebServer = ...;
+ * ServerProperties serverProperties = ...;
+ * ConfigurableApplicationContext context = ...;
+ * TomcatDynamicConfigurationListener listener =
+ * new TomcatDynamicConfigurationListener(tomcatWebServer, serverProperties, context);
+ * context.addApplicationListener(listener);
+ * }
+ *
+ * @param tomcatWebServer the {@link TomcatWebServer} to reconfigure dynamically
+ * @param serverProperties the current {@link ServerProperties}
+ * @param context the {@link ConfigurableApplicationContext} for environment access
+ */
public TomcatDynamicConfigurationListener(TomcatWebServer tomcatWebServer, ServerProperties serverProperties,
ConfigurableApplicationContext context) {
this.tomcatWebServer = tomcatWebServer;
@@ -75,7 +95,7 @@ public TomcatDynamicConfigurationListener(TomcatWebServer tomcatWebServer, Serve
this.context = context;
this.environment = environment;
- this.configurationPropertiesRebinderPresent = isBeanPresent(ConfigurationPropertiesRebinder.class);
+ this.configurationPropertiesRebinderPresent = isBeanPresent(context, ConfigurationPropertiesRebinder.class);
initCurrentServerProperties();
}
@@ -84,14 +104,22 @@ private void initCurrentServerProperties() {
this.currentServerProperties = getCurrentServerProperties(environment);
}
- private boolean isBeanPresent(Class> beanType) {
- return context.getBeanProvider(beanType).getIfAvailable() != null;
- }
-
+ /**
+ * Handles an {@link EnvironmentChangeEvent} by reconfiguring the Tomcat connector
+ * if any server-related properties have changed.
+ *
+ * Example Usage:
+ *
{@code
+ * // Automatically invoked by Spring when an EnvironmentChangeEvent is published.
+ * // Reconfigures Tomcat settings such as thread pool size, connection timeout, etc.
+ * }
+ *
+ * @param event the {@link EnvironmentChangeEvent} containing the changed property keys
+ */
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (!isSourceFrom(event)) {
- if(logger.isTraceEnabled()) {
+ if (logger.isTraceEnabled()) {
logger.trace("Current context[id : '{}'] receives the other changed property names : {}", context.getId(), event.getKeys());
}
return;
@@ -99,7 +127,7 @@ public void onApplicationEvent(EnvironmentChangeEvent event) {
Set serverPropertyNames = filterServerPropertyNames(event);
if (serverPropertyNames.isEmpty()) {
- if(logger.isTraceEnabled()) {
+ if (logger.isTraceEnabled()) {
logger.trace("Current context[id : '{}'] does not receive the property change of ServerProperties, keys : {}", context.getId(), event.getKeys());
}
return;
@@ -127,8 +155,8 @@ private boolean isServerPropertyName(String propertyName) {
private void configureTomcatIfChanged(Set serverPropertyNames) {
ServerProperties refreshableServerProperties = getRefreshableServerProperties(serverPropertyNames);
- if(logger.isTraceEnabled()) {
- logger.debug("The ServerProperties property is changed to: {}", getProperties(environment, serverPropertyNames));
+ if (logger.isTraceEnabled()) {
+ logger.trace("The ServerProperties property is changed to: {}", getProperties(environment, serverPropertyNames));
}
configureConnector(refreshableServerProperties);
// Reset current ServerProperties
@@ -155,7 +183,21 @@ private void configureConnector(ServerProperties refreshableServerProperties) {
configureHttp11Protocol(refreshableServerProperties, connector, protocolHandler);
}
- private void configureProtocol(ServerProperties refreshableServerProperties, ProtocolHandler protocolHandler) {
+ /**
+ * Configure the Tomcat {@link AbstractProtocol} settings such as thread pool sizes,
+ * accept count, connection timeout, and max connections from the refreshed {@link ServerProperties}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServerProperties refreshedProperties = ...;
+ * ProtocolHandler protocolHandler = connector.getProtocolHandler();
+ * listener.configureProtocol(refreshedProperties, protocolHandler);
+ * }
+ *
+ * @param refreshableServerProperties the refreshed {@link ServerProperties} to apply
+ * @param protocolHandler the {@link ProtocolHandler} to configure
+ */
+ void configureProtocol(ServerProperties refreshableServerProperties, ProtocolHandler protocolHandler) {
if (protocolHandler instanceof AbstractProtocol) {
ServerProperties.Tomcat refreshableTomcatProperties = refreshableServerProperties.getTomcat();
@@ -203,7 +245,23 @@ private void configureProtocol(ServerProperties refreshableServerProperties, Pro
}
}
- private void configureHttp11Protocol(ServerProperties refreshableServerProperties, Connector connector, ProtocolHandler protocolHandler) {
+ /**
+ * Configure the Tomcat {@link AbstractHttp11Protocol} settings such as max HTTP header size,
+ * max swallow size, and max HTTP form POST size from the refreshed {@link ServerProperties}.
+ *
+ * Example Usage:
+ *
{@code
+ * ServerProperties refreshedProperties = ...;
+ * Connector connector = tomcatWebServer.getTomcat().getConnector();
+ * ProtocolHandler protocolHandler = connector.getProtocolHandler();
+ * listener.configureHttp11Protocol(refreshedProperties, connector, protocolHandler);
+ * }
+ *
+ * @param refreshableServerProperties the refreshed {@link ServerProperties} to apply
+ * @param connector the Tomcat {@link Connector}
+ * @param protocolHandler the {@link ProtocolHandler} to configure
+ */
+ void configureHttp11Protocol(ServerProperties refreshableServerProperties, Connector connector, ProtocolHandler protocolHandler) {
if (protocolHandler instanceof AbstractHttp11Protocol) {
AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) protocolHandler;
@@ -213,8 +271,8 @@ private void configureHttp11Protocol(ServerProperties refreshableServerPropertie
// Max HTTP Header Size
configure("Tomcat HTTP Headers' max size(bytes)")
- .value(refreshableServerProperties::getMaxHttpHeaderSize)
- .compare(currentServerProperties::getMaxHttpHeaderSize)
+ .value(refreshableServerProperties::getMaxHttpRequestHeaderSize)
+ .compare(currentServerProperties::getMaxHttpRequestHeaderSize)
.as(this::toIntBytes)
.on(this::isPositive)
.apply(protocol::setMaxHttpHeaderSize);
@@ -245,9 +303,20 @@ private int toIntBytes(DataSize dataSize) {
return (int) dataSize.toBytes();
}
- private boolean isPositive(int value) {
+ /**
+ * Check whether the given integer value is positive (greater than zero).
+ *
+ * Example Usage:
+ *
{@code
+ * boolean result = listener.isPositive(10); // true
+ * boolean result2 = listener.isPositive(0); // false
+ * }
+ *
+ * @param value the value to check
+ * @return {@code true} if the value is greater than zero
+ */
+ boolean isPositive(int value) {
return value > 0;
}
-
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/loadbalancer/condition/ConditionalOnLoadBalancerEnabled.java b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/loadbalancer/condition/ConditionalOnLoadBalancerEnabled.java
new file mode 100644
index 00000000..5b04c06e
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/main/java/io/microsphere/spring/cloud/loadbalancer/condition/ConditionalOnLoadBalancerEnabled.java
@@ -0,0 +1,44 @@
+/*
+ * 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 io.microsphere.spring.cloud.loadbalancer.condition;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.LOAD_BALANCER_ENABLED_PROPERTY_NAME;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * The conditional annotation meta-annotates {@link ConditionalOnProperty @ConditionalOnProperty} for
+ * LoadBalancer enabled.
+ *
+ * @author Mercy
+ * @see org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration
+ * @see ConditionalOnProperty
+ * @since 1.0.0
+ */
+@Retention(RUNTIME)
+@Target({TYPE, METHOD})
+@Documented
+@ConditionalOnProperty(name = LOAD_BALANCER_ENABLED_PROPERTY_NAME, havingValue = "true", matchIfMissing = true)
+public @interface ConditionalOnLoadBalancerEnabled {
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring.factories b/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring.factories
index a0de4297..7586f87c 100644
--- a/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring.factories
+++ b/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring.factories
@@ -1,10 +1 @@
-org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
-io.microsphere.spring.cloud.client.discovery.autoconfigure.DiscoveryClientAutoConfiguration,\
-io.microsphere.spring.cloud.client.service.registry.autoconfigure.ServiceRegistryAutoConfiguration,\
-io.microsphere.spring.cloud.client.service.registry.autoconfigure.WebMvcServiceRegistryAutoConfiguration,\
-io.microsphere.spring.cloud.client.service.registry.autoconfigure.WebFluxServiceRegistryAutoConfiguration,\
-io.microsphere.spring.cloud.client.service.registry.autoconfigure.SimpleAutoServiceRegistrationAutoConfiguration,\
-io.microsphere.spring.cloud.client.service.registry.actuate.autoconfigure.ServiceRegistrationEndpointAutoConfiguration,\
-io.microsphere.spring.cloud.fault.tolerance.tomcat.autoconfigure.TomcatFaultToleranceAutoConfiguration
-
com.alibaba.cloud.nacos.registry.NacosServiceRegistry = com.alibaba.cloud.nacos.registry.NacosRegistration
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 7be8d06f..fd5ff8c4 100644
--- a/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/microsphere-spring-cloud-commons/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,6 +1,8 @@
io.microsphere.spring.cloud.client.discovery.autoconfigure.DiscoveryClientAutoConfiguration
+io.microsphere.spring.cloud.client.discovery.autoconfigure.ReactiveDiscoveryClientAutoConfiguration
io.microsphere.spring.cloud.client.service.registry.autoconfigure.ServiceRegistryAutoConfiguration
io.microsphere.spring.cloud.client.service.registry.autoconfigure.WebMvcServiceRegistryAutoConfiguration
io.microsphere.spring.cloud.client.service.registry.autoconfigure.WebFluxServiceRegistryAutoConfiguration
io.microsphere.spring.cloud.client.service.registry.autoconfigure.SimpleAutoServiceRegistrationAutoConfiguration
-io.microsphere.spring.cloud.client.service.registry.actuate.autoconfigure.ServiceRegistrationEndpointAutoConfiguration
\ No newline at end of file
+io.microsphere.spring.cloud.client.service.registry.actuate.autoconfigure.ServiceRegistrationEndpointAutoConfiguration
+io.microsphere.spring.cloud.fault.tolerance.tomcat.autoconfigure.TomcatFaultToleranceAutoConfiguration
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabledTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabledTest.java
index eab52d40..80986d9b 100644
--- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabledTest.java
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/condition/ConditionalOnFeaturesEnabledTest.java
@@ -17,43 +17,15 @@
package io.microsphere.spring.cloud.client.condition;
/**
- * {@link ConditionalOnFeaturesEnabled} Test
+ * {@link io.microsphere.spring.cloud.client.condition.ConditionalOnFeaturesEnabled} Test
*
* @author Mercy
- * @see ConditionalOnFeaturesEnabled
+ * @see io.microsphere.spring.cloud.client.condition.ConditionalOnFeaturesEnabled
* @since 1.0.0
*/
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
+import io.microsphere.spring.cloud.test.ConditionalOnPropertyEnabledTest;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-@ExtendWith(SpringExtension.class)
-@ContextConfiguration(classes = {
- ConditionalOnFeaturesEnabledTest.FeaturesConfiguration.class
-})
-@TestPropertySource(
- properties = {
- "spring.cloud.features.enabled=true"
- }
-)
-public class ConditionalOnFeaturesEnabledTest {
-
- @ConditionalOnFeaturesEnabled
- static class FeaturesConfiguration {
- }
-
- @Autowired
- private ObjectProvider featuresConfigurationProvider;
-
- @Test
- public void test() {
- assertNotNull(featuresConfigurationProvider.getIfAvailable());
- }
+@ConditionalOnFeaturesEnabled
+class ConditionalOnFeaturesEnabledTest extends ConditionalOnPropertyEnabledTest {
}
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/ReactiveDiscoveryClientAdapterTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/ReactiveDiscoveryClientAdapterTest.java
new file mode 100644
index 00000000..2f65a00d
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/ReactiveDiscoveryClientAdapterTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClient;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties;
+import reactor.core.Disposable;
+import reactor.core.publisher.Flux;
+import reactor.core.scheduler.Scheduler;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.spring.cloud.client.discovery.ReactiveDiscoveryClientAdapter.toList;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtilsTest.createDefaultServiceInstance;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static reactor.core.scheduler.Schedulers.immediate;
+import static reactor.core.scheduler.Schedulers.newSingle;
+
+/**
+ * {@link ReactiveDiscoveryClientAdapter}
+ *
+ * @author Mercy
+ * @see ReactiveDiscoveryClientAdapter
+ * @see ReactiveDiscoveryClient
+ * @see DiscoveryClient
+ * @since 1.0.0
+ */
+class ReactiveDiscoveryClientAdapterTest {
+
+ private DefaultServiceInstance serviceInstance;
+
+ private String appName = "test-service";
+
+ private SimpleReactiveDiscoveryProperties properties;
+
+ private ReactiveDiscoveryClient client;
+
+ private ReactiveDiscoveryClientAdapter adapter;
+
+ @BeforeEach
+ void setUp() {
+ Map> instances = new HashMap<>();
+ this.serviceInstance = createDefaultServiceInstance();
+ this.appName = this.serviceInstance.getServiceId();
+ instances.put(appName, ofList(this.serviceInstance));
+ this.properties = new SimpleReactiveDiscoveryProperties();
+ this.properties.setInstances(instances);
+ this.client = new SimpleReactiveDiscoveryClient(properties);
+ this.adapter = new ReactiveDiscoveryClientAdapter(client);
+ }
+
+ @Test
+ void testDescription() {
+ assertEquals("Simple Reactive Discovery Client", this.adapter.description());
+ }
+
+ @Test
+ void testGetInstances() {
+ List serviceInstances = this.adapter.getInstances(this.appName);
+ assertEquals(1, serviceInstances.size());
+ assertSame(this.serviceInstance, serviceInstances.get(0));
+ }
+
+ @Test
+ void testGetServices() {
+ List services = this.adapter.getServices();
+ assertEquals(1, services.size());
+ assertSame(appName, services.get(0));
+ }
+
+ @Test
+ void testProbe() {
+ this.adapter.probe();
+ }
+
+ @Test
+ void testGetOrder() {
+ assertEquals(this.client.getOrder(), this.adapter.getOrder());
+ }
+
+ @Test
+ void testToList() throws Exception {
+ assertList(immediate(), "1,2,3");
+ assertList(newSingle("test"), "1,2,3");
+ }
+
+ void assertList(Scheduler scheduler, T... values) throws Exception {
+ Flux flux = Flux.just(values);
+ Disposable disposable = scheduler.schedule(() -> {
+ List list = toList(flux);
+ assertEquals(ofList(values), list);
+ });
+ if (disposable instanceof Callable) {
+ ((Callable) disposable).call();
+ }
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClientIntegrationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClientIntegrationTest.java
new file mode 100644
index 00000000..be79b427
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClientIntegrationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.testcontainers.containers.ComposeContainer;
+import org.testcontainers.junit.jupiter.EnabledIfDockerAvailable;
+
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.testcontainers.containers.wait.strategy.Wait.forLogMessage;
+
+/**
+ * {@link UnionDiscoveryClient} Integration Test
+ *
+ * @author Mercy
+ * @since 1.0.0
+ */
+@EnabledIfSystemProperty(named = "testcontainers.enabled", matches = "true")
+@EnabledIfDockerAvailable
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ UnionDiscoveryClientIntegrationTest.class
+})
+@TestPropertySource(properties = {
+ "spring.application.name=test",
+ "spring.cloud.service-registry.auto-registration.enabled=true",
+ "microsphere.spring.cloud.client.discovery.mode=union",
+ "microsphere.spring.cloud.multiple-registration.enabled=true"
+})
+@EnableAutoConfiguration
+class UnionDiscoveryClientIntegrationTest {
+
+ private static ComposeContainer composeContainer;
+
+ @Autowired
+ private DiscoveryClient discoveryClient;
+
+ @BeforeAll
+ static void beforeAll() throws Exception {
+ ClassLoader classLoader = UnionDiscoveryClientIntegrationTest.class.getClassLoader();
+ URL resource = classLoader.getResource("META-INF/docker/service-registry-servers.yml");
+ File dockerComposeFile = new File(resource.toURI());
+ composeContainer = new ComposeContainer(dockerComposeFile);
+ composeContainer.waitingFor("nacos", forLogMessage(".*Nacos started successfully.*", 1))
+ .waitingFor("eureka", forLogMessage(".*Started EurekaServerApplication.*", 1))
+ .start();
+ }
+
+ @AfterAll
+ static void afterAll() {
+ composeContainer.stop();
+ }
+
+ @Test
+ void test() {
+ assertEquals(CompositeDiscoveryClient.class, discoveryClient.getClass());
+ CompositeDiscoveryClient compositeDiscoveryClient = CompositeDiscoveryClient.class.cast(discoveryClient);
+ List discoveryClients = compositeDiscoveryClient.getDiscoveryClients();
+ assertEquals(7, discoveryClients.size());
+ assertEquals(UnionDiscoveryClient.class, discoveryClients.get(0).getClass());
+ List services = compositeDiscoveryClient.getServices();
+ assertTrue(services.size() > 1);
+ }
+}
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClientTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClientTest.java
new file mode 100644
index 00000000..60b49dc0
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/UnionDiscoveryClientTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
+import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClient;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.commons.util.UtilAutoConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.List;
+
+import static io.microsphere.collection.Lists.ofList;
+import static java.util.Collections.emptyList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link UnionDiscoveryClient} Test
+ *
+ * @author Mercy
+ * @since 1.0.0
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ UtilAutoConfiguration.class,
+ SimpleDiscoveryClientAutoConfiguration.class,
+ CompositeDiscoveryClientAutoConfiguration.class,
+ UnionDiscoveryClient.class,
+ UnionDiscoveryClientTest.DummyDiscoveryClient.class,
+ UnionDiscoveryClientTest.class
+})
+@TestPropertySource(
+ properties = {
+ "spring.cloud.discovery.client.simple.order=-1",
+ "spring.cloud.discovery.client.simple.instances.test[0].instanceId=1",
+ "spring.cloud.discovery.client.simple.instances.test[0].serviceId=test",
+ "spring.cloud.discovery.client.simple.instances.test[0].host=127.0.0.1",
+ "spring.cloud.discovery.client.simple.instances.test[0].port=8080",
+ "spring.cloud.discovery.client.simple.instances.test[0].metadata.key-1=value-1"
+ }
+)
+class UnionDiscoveryClientTest {
+
+ @Autowired
+ private DiscoveryClient discoveryClient;
+
+ @Autowired
+ private UnionDiscoveryClient unionDiscoveryClient;
+
+ @Test
+ void test() {
+ assertEquals(CompositeDiscoveryClient.class, discoveryClient.getClass());
+ CompositeDiscoveryClient compositeDiscoveryClient = CompositeDiscoveryClient.class.cast(discoveryClient);
+ List discoveryClients = compositeDiscoveryClient.getDiscoveryClients();
+ assertEquals(3, discoveryClients.size());
+ assertEquals(UnionDiscoveryClient.class, discoveryClients.get(0).getClass());
+ assertEquals(SimpleDiscoveryClient.class, discoveryClients.get(1).getClass());
+ assertEquals(DummyDiscoveryClient.class, discoveryClients.get(2).getClass());
+ }
+
+ @Test
+ void testDescription() {
+ assertEquals("Composite Discovery Client", this.discoveryClient.description());
+ assertEquals("Union Discovery Client", this.unionDiscoveryClient.description());
+ }
+
+ @Test
+ void testGetInstances() {
+ assertServiceInstances(this.discoveryClient.getInstances("test"));
+ assertServiceInstances(this.unionDiscoveryClient.getInstances("test"));
+
+ assertTrue(this.discoveryClient.getInstances("unknown").isEmpty());
+ assertTrue(this.unionDiscoveryClient.getInstances("unknown").isEmpty());
+ }
+
+ @Test
+ void testGetServices() {
+ assertServices(this.discoveryClient.getServices());
+ assertServices(this.unionDiscoveryClient.getServices());
+ }
+
+ void assertServiceInstances(List serviceInstances) {
+ assertEquals(1, serviceInstances.size());
+ ServiceInstance serviceInstance = serviceInstances.get(0);
+ assertEquals("test", serviceInstance.getServiceId());
+ assertEquals("1", serviceInstance.getInstanceId());
+ assertEquals("127.0.0.1", serviceInstance.getHost());
+ assertEquals(8080, serviceInstance.getPort());
+ assertEquals("value-1", serviceInstance.getMetadata().get("key-1"));
+ }
+
+ void assertServices(List services) {
+ assertEquals(ofList("test"), services);
+ }
+
+ static class DummyDiscoveryClient implements DiscoveryClient {
+
+ @Override
+ public String description() {
+ return "Dummy Discovery Client";
+ }
+
+ @Override
+ public List getInstances(String serviceId) {
+ return emptyList();
+ }
+
+ @Override
+ public List getServices() {
+ return emptyList();
+ }
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/UnionDiscoveryClientTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfigurationTest.java
similarity index 77%
rename from microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/UnionDiscoveryClientTest.java
rename to microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfigurationTest.java
index 882ae70e..76fdc403 100644
--- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/UnionDiscoveryClientTest.java
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/DiscoveryClientAutoConfigurationTest.java
@@ -20,34 +20,37 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClient;
import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration;
import org.springframework.cloud.commons.util.UtilAutoConfiguration;
-import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.util.Arrays;
import java.util.List;
+import static io.microsphere.spring.cloud.client.discovery.autoconfigure.DiscoveryClientAutoConfiguration.DISCOVERY_CLIENT_PROPERTY_PREFIX;
+import static io.microsphere.spring.cloud.client.discovery.autoconfigure.DiscoveryClientAutoConfiguration.MODE_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.client.discovery.autoconfigure.DiscoveryClientAutoConfiguration.UNION_DISCOVERY_CLIENT_MODE;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
- * {@link UnionDiscoveryClient} Test
+ * {@link DiscoveryClientAutoConfiguration} Test
*
- * @author Mercy
+ * @author Mercy
+ * @see DiscoveryClientAutoConfiguration
* @since 1.0.0
*/
@ExtendWith(SpringExtension.class)
-@ContextConfiguration(classes = {
+@SpringBootTest(classes = {
UtilAutoConfiguration.class,
SimpleDiscoveryClientAutoConfiguration.class,
CompositeDiscoveryClientAutoConfiguration.class,
- DiscoveryClientAutoConfiguration.class,
- UnionDiscoveryClientTest.class
+ DiscoveryClientAutoConfiguration.class
})
@TestPropertySource(
properties = {
@@ -59,13 +62,20 @@
"spring.cloud.discovery.client.simple.instances.test[0].metadata.key-1=value-1"
}
)
-public class UnionDiscoveryClientTest {
+class DiscoveryClientAutoConfigurationTest {
@Autowired
private DiscoveryClient discoveryClient;
@Test
- public void test() {
+ void testConstants() {
+ assertEquals("microsphere.spring.cloud.client.discovery.", DISCOVERY_CLIENT_PROPERTY_PREFIX);
+ assertEquals("mode", MODE_PROPERTY_NAME);
+ assertEquals("union", UNION_DISCOVERY_CLIENT_MODE);
+ }
+
+ @Test
+ void test() {
assertEquals(CompositeDiscoveryClient.class, discoveryClient.getClass());
CompositeDiscoveryClient compositeDiscoveryClient = CompositeDiscoveryClient.class.cast(discoveryClient);
List discoveryClients = compositeDiscoveryClient.getDiscoveryClients();
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/ReactiveDiscoveryClientAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/ReactiveDiscoveryClientAutoConfigurationTest.java
new file mode 100644
index 00000000..529b0941
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/autoconfigure/ReactiveDiscoveryClientAutoConfigurationTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery.autoconfigure;
+
+
+import io.microsphere.spring.cloud.client.discovery.ReactiveDiscoveryClientAdapter;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient;
+import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration;
+import org.springframework.cloud.commons.util.UtilAutoConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.List;
+
+import static io.microsphere.collection.Lists.ofList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link ReactiveDiscoveryClientAutoConfiguration} Test
+ *
+ * @author Mercy
+ * @see ReactiveDiscoveryClientAutoConfiguration
+ * @since 1.0.0
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(classes = {
+ UtilAutoConfiguration.class,
+ SimpleReactiveDiscoveryClientAutoConfiguration.class,
+ CompositeDiscoveryClientAutoConfiguration.class,
+ ReactiveDiscoveryClientAutoConfiguration.class
+})
+@TestPropertySource(
+ properties = {
+ "spring.cloud.discovery.client.simple.instances.test[0].instanceId=1",
+ "spring.cloud.discovery.client.simple.instances.test[0].serviceId=test",
+ "spring.cloud.discovery.client.simple.instances.test[0].host=127.0.0.1",
+ "spring.cloud.discovery.client.simple.instances.test[0].port=8080",
+ "spring.cloud.discovery.client.simple.instances.test[0].metadata.key-1=value-1"
+ }
+)
+class ReactiveDiscoveryClientAutoConfigurationTest {
+
+ @Autowired
+ private DiscoveryClient discoveryClient;
+
+ @Autowired
+ private ReactiveDiscoveryClientAdapter adapter;
+
+ @Test
+ void test() {
+ assertEquals(CompositeDiscoveryClient.class, this.discoveryClient.getClass());
+ CompositeDiscoveryClient compositeDiscoveryClient = CompositeDiscoveryClient.class.cast(this.discoveryClient);
+ List discoveryClients = compositeDiscoveryClient.getDiscoveryClients();
+ assertEquals(1, discoveryClients.size());
+ assertSame(this.adapter, discoveryClients.get(0));
+ List services = compositeDiscoveryClient.getServices();
+ assertEquals(ofList("test"), services);
+ assertEquals(services, discoveryClients.get(0).getServices());
+ assertEquals(services, this.adapter.getServices());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstantsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstantsTest.java
new file mode 100644
index 00000000..131a2c1e
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/constants/DiscoveryClientConstantsTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery.constants;
+
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.COMPOSITE_DISCOVERY_CLIENT_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.DISCOVERY_CLIENT_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.REACTIVE_COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.REACTIVE_COMPOSITE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+import static io.microsphere.spring.cloud.client.discovery.constants.DiscoveryClientConstants.SIMPLE_REACTIVE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link DiscoveryClientConstants} Test
+ *
+ * @author Mercy
+ * @see DiscoveryClientConstants
+ * @since 1.0.0
+ */
+class DiscoveryClientConstantsTest {
+
+ @Test
+ void testConstants() {
+ assertEquals("org.springframework.cloud.client.discovery.DiscoveryClient", DISCOVERY_CLIENT_CLASS_NAME);
+ assertEquals("org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClient", COMPOSITE_DISCOVERY_CLIENT_CLASS_NAME);
+ assertEquals("org.springframework.cloud.client.CommonsClientAutoConfiguration", COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME);
+ assertEquals("org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration", REACTIVE_COMMONS_CLIENT_AUTO_CONFIGURATION_CLASS_NAME);
+ assertEquals("org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryClientAutoConfiguration", SIMPLE_REACTIVE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME);
+ assertEquals("org.springframework.cloud.client.discovery.composite.reactive.ReactiveCompositeDiscoveryClientAutoConfiguration", REACTIVE_COMPOSITE_DISCOVERY_CLIENT_AUTO_CONFIGURATION_CLASS_NAME);
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/util/DiscoveryUtilsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/util/DiscoveryUtilsTest.java
new file mode 100644
index 00000000..c795655a
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/discovery/util/DiscoveryUtilsTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.discovery.util;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties;
+
+import java.util.List;
+import java.util.Map;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.spring.cloud.client.discovery.util.DiscoveryUtils.getInstancesMap;
+import static io.microsphere.spring.cloud.client.discovery.util.DiscoveryUtils.simpleDiscoveryProperties;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtilsTest.createDefaultServiceInstance;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link DiscoveryUtils} Test
+ *
+ * @author Mercy
+ * @see DiscoveryUtils
+ * @since 1.0.0
+ */
+class DiscoveryUtilsTest {
+
+ private DefaultServiceInstance serviceInstance;
+
+ private SimpleDiscoveryProperties properties;
+
+ @BeforeEach
+ void setUp() {
+ this.serviceInstance = createDefaultServiceInstance();
+ this.properties = new SimpleDiscoveryProperties();
+ Map> instancesMap = this.properties.getInstances();
+ instancesMap.put(this.serviceInstance.getInstanceId(), ofList(this.serviceInstance));
+ }
+
+ @Test
+ void testGetInstancesMap() {
+ Map> instancesMap = getInstancesMap(this.properties);
+ assertEquals(1, instancesMap.size());
+ assertEquals(ofList(this.serviceInstance), instancesMap.get(this.serviceInstance.getInstanceId()));
+ }
+
+ @Test
+ void testGetInstancesMapFromSimpleReactiveDiscoveryProperties() {
+ SimpleReactiveDiscoveryProperties properties = new SimpleReactiveDiscoveryProperties();
+ Map> instancesMap = getInstancesMap(properties);
+ assertTrue(instancesMap.isEmpty());
+
+ properties.setInstances(this.properties.getInstances());
+ instancesMap = getInstancesMap(properties);
+ assertEquals(1, instancesMap.size());
+ assertEquals(ofList(this.serviceInstance), instancesMap.get(this.serviceInstance.getInstanceId()));
+ }
+
+ @Test
+ void testSimpleDiscoveryProperties() {
+ SimpleReactiveDiscoveryProperties properties = new SimpleReactiveDiscoveryProperties();
+ properties.setInstances(this.properties.getInstances());
+
+ SimpleDiscoveryProperties simpleDiscoveryProperties = simpleDiscoveryProperties(properties);
+ assertEquals(this.properties.getInstances(), simpleDiscoveryProperties.getInstances());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEventTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEventTest.java
new file mode 100644
index 00000000..da3a7ca9
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/event/ServiceInstancesChangedEventTest.java
@@ -0,0 +1,67 @@
+package io.microsphere.spring.cloud.client.event;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.ServiceInstance;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link ServiceInstancesChangedEvent} Test
+ *
+ * @author Mercy
+ * @see ServiceInstancesChangedEvent
+ * @since 1.0.0
+ */
+class ServiceInstancesChangedEventTest {
+
+ private String serviceName = "testService";
+
+ private ServiceInstancesChangedEvent event;
+
+ private ServiceInstance instance;
+
+ @BeforeEach
+ void setUp() {
+ this.instance = createInstance(serviceName);
+ this.event = new ServiceInstancesChangedEvent(serviceName, Arrays.asList(instance));
+ }
+
+ private ServiceInstance createInstance(String serviceName) {
+ DefaultServiceInstance instance = new DefaultServiceInstance();
+ instance.setServiceId(serviceName);
+ instance.setServiceId(UUID.randomUUID().toString());
+ instance.setHost("127.0.0.1");
+ instance.setPort(8080);
+ instance.setUri(URI.create("http://127.0.0.1:8080/info"));
+ return instance;
+ }
+
+ @Test
+ void testGetServiceName() {
+ assertEquals(this.serviceName, this.event.getServiceName());
+ assertEquals(this.serviceName, this.event.getSource());
+ }
+
+ @Test
+ void testGetServiceInstances() {
+ assertEquals(Arrays.asList(this.instance), this.event.getServiceInstances());
+ assertEquals(this.instance, this.event.getServiceInstances().get(0));
+ assertSame(this.instance, this.event.getServiceInstances().get(0));
+ }
+
+ @Test
+ void testProcessed() {
+ assertFalse(this.event.isProcessed());
+ this.event.processed();
+ assertTrue(this.event.isProcessed());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistrationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistrationTest.java
new file mode 100644
index 00000000..ffa974e5
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/DefaultRegistrationTest.java
@@ -0,0 +1,149 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.net.URI;
+
+import static java.lang.System.currentTimeMillis;
+import static java.net.URI.create;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link DefaultRegistration} Test
+ *
+ * @author Mercy
+ * @see DefaultRegistration
+ * @since 1.0.0
+ */
+public class DefaultRegistrationTest {
+
+ private DefaultRegistration registration;
+
+ @BeforeEach
+ void setUp() {
+ this.registration = createDefaultRegistration();
+ }
+
+ @Test
+ void testGetUri() {
+ assertNotNull(this.registration.getUri());
+ }
+
+ @Test
+ void testGetMetadata() {
+ assertNotNull(this.registration.getMetadata());
+ }
+
+ @Test
+ void testGetInstanceId() {
+ assertNotNull(this.registration.getInstanceId());
+ }
+
+ @Test
+ void testGetServiceId() {
+ assertEquals("test-service", this.registration.getServiceId());
+ }
+
+ @Test
+ void testGetHost() {
+ assertEquals("localhost", this.registration.getHost());
+ }
+
+ @Test
+ void testGetPort() {
+ assertEquals(8080, this.registration.getPort());
+ }
+
+ @Test
+ void testIsSecure() {
+ assertTrue(this.registration.isSecure());
+ }
+
+ @Test
+ void testSetInstanceId() {
+ this.registration.setInstanceId("test-instance-id");
+ assertEquals("test-instance-id", this.registration.getInstanceId());
+ }
+
+ @Test
+ void testSetServiceId() {
+ this.registration.setServiceId("test-service-id");
+ assertEquals("test-service-id", this.registration.getServiceId());
+ }
+
+ @Test
+ void testSetHost() {
+ this.registration.setHost("test-host");
+ assertEquals("test-host", this.registration.getHost());
+ }
+
+ @Test
+ void testSetPort() {
+ this.registration.setPort(9090);
+ assertEquals(9090, this.registration.getPort());
+ }
+
+ @Test
+ void testSetUri() {
+ URI uri = create("https://localhost:9090");
+ this.registration.setUri(uri);
+ assertEquals(uri, this.registration.getUri());
+ }
+
+ @Test
+ void testToString() {
+ assertNotNull(this.registration.toString());
+ }
+
+ @Test
+ void testEquals() {
+ DefaultRegistration registration = createDefaultRegistration();
+ registration.setInstanceId(this.registration.getInstanceId());
+ assertEquals(this.registration, registration);
+ }
+
+ @Test
+ void testHashCode() {
+ DefaultRegistration registration = createDefaultRegistration();
+ registration.setInstanceId(this.registration.getInstanceId());
+ assertEquals(this.registration.hashCode(), registration.hashCode());
+
+ }
+
+ @Test
+ void testGetScheme() {
+ assertNull(this.registration.getScheme());
+ }
+
+ public static DefaultRegistration createDefaultRegistration() {
+ DefaultRegistration defaultRegistration = new DefaultRegistration();
+ defaultRegistration.setInstanceId("ServiceInstance-" + currentTimeMillis());
+ defaultRegistration.setServiceId("test-service");
+ defaultRegistration.setHost("localhost");
+ defaultRegistration.setPort(8080);
+ defaultRegistration.setSecure(true);
+ return defaultRegistration;
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistrationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistrationTest.java
new file mode 100644
index 00000000..ac5f1520
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleAutoServiceRegistrationTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link MultipleAutoServiceRegistration} Test
+ *
+ * @author Mercy
+ * @see MultipleAutoServiceRegistration
+ * @since 1.0.0
+ */
+class MultipleAutoServiceRegistrationTest {
+
+ private DefaultRegistration defaultRegistration;
+
+ private MultipleRegistration registration;
+
+ private ServiceRegistry serviceRegistry;
+
+ private AutoServiceRegistrationProperties properties;
+
+ private MultipleAutoServiceRegistration autoServiceRegistration;
+
+ @BeforeEach
+ void setUp() {
+ this.defaultRegistration = createDefaultRegistration();
+ this.registration = new MultipleRegistration(ofList(defaultRegistration));
+ this.serviceRegistry = new InMemoryServiceRegistry();
+ this.properties = new AutoServiceRegistrationProperties();
+ this.autoServiceRegistration = new MultipleAutoServiceRegistration(registration, serviceRegistry, properties);
+ }
+
+ @Test
+ void testGetConfiguration() {
+ assertNull(this.autoServiceRegistration.getConfiguration());
+ }
+
+ @Test
+ void testIsEnabled() {
+ assertEquals(this.properties.isEnabled(), this.autoServiceRegistration.isEnabled());
+ }
+
+ @Test
+ void testGetRegistration() {
+ assertSame(this.registration, this.autoServiceRegistration.getRegistration());
+ }
+
+ @Test
+ void testGetManagementRegistration() {
+ assertSame(this.registration, this.autoServiceRegistration.getManagementRegistration());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistrationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistrationTest.java
new file mode 100644
index 00000000..5f8d08d5
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleRegistrationTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.serviceregistry.Registration;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link MultipleRegistration} Test
+ *
+ * @author Mercy
+ * @see MultipleRegistration
+ * @since 1.0.0
+ */
+class MultipleRegistrationTest {
+
+ private DefaultRegistration defaultRegistration;
+
+ private MultipleRegistration registration;
+
+ @BeforeEach
+ void setUp() {
+ this.defaultRegistration = createDefaultRegistration();
+ this.registration = new MultipleRegistration(ofList(defaultRegistration));
+ }
+
+ @Test
+ void testGetInstanceId() {
+ assertEquals(defaultRegistration.getInstanceId(), registration.getInstanceId());
+ }
+
+ @Test
+ void testGetServiceId() {
+ assertEquals(defaultRegistration.getServiceId(), registration.getServiceId());
+ }
+
+ @Test
+ void testGetHost() {
+ assertEquals(defaultRegistration.getHost(), registration.getHost());
+ }
+
+ @Test
+ void testGetPort() {
+ assertEquals(defaultRegistration.getPort(), registration.getPort());
+ }
+
+ @Test
+ void testIsSecure() {
+ assertEquals(defaultRegistration.isSecure(), registration.isSecure());
+ }
+
+ @Test
+ void testGetUri() {
+ assertEquals(defaultRegistration.getUri(), registration.getUri());
+ }
+
+ @Test
+ void testGetMetadata() {
+ assertEquals(defaultRegistration.getMetadata(), registration.getMetadata());
+ }
+
+ @Test
+ void testGetDefaultRegistration() {
+ assertEquals(defaultRegistration, registration.getDefaultRegistration());
+ }
+
+ @Test
+ void testSpecial() {
+ assertSame(registration, registration.special(Registration.class));
+ assertSame(defaultRegistration, registration.special(DefaultRegistration.class));
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryIntegrationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryIntegrationTest.java
new file mode 100644
index 00000000..3c00c890
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryIntegrationTest.java
@@ -0,0 +1,129 @@
+package io.microsphere.spring.cloud.client.service.registry;
+
+import com.netflix.appinfo.ApplicationInfoManager;
+import com.netflix.appinfo.InstanceInfo;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationPreRegisteredEvent;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.testcontainers.containers.ComposeContainer;
+import org.testcontainers.junit.jupiter.EnabledIfDockerAvailable;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Map;
+
+import static io.microsphere.lang.function.ThrowableAction.execute;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.testcontainers.containers.wait.strategy.Wait.forLogMessage;
+
+/**
+ * {@link MultipleServiceRegistry} Integration Test
+ *
+ * @author 韩超
+ * @author Mercy
+ * @see MultipleServiceRegistry
+ * @since 1.0.0
+ */
+@EnabledIfSystemProperty(named = "testcontainers.enabled", matches = "true")
+@EnabledIfDockerAvailable
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ MultipleServiceRegistryIntegrationTest.class,
+})
+@TestPropertySource(
+ properties = {
+ "spring.application.name=test",
+
+ "spring.cloud.service-registry.auto-registration.enabled=true",
+ "spring.cloud.nacos.discovery.namespace=f7ad23e0-f581-4516-9420-8c50aa6a7b89",
+ "spring.cloud.nacos.discovery.metadata.key=value",
+
+ "microsphere.spring.cloud.multiple-registration.enabled=true",
+ "microsphere.spring.cloud.default-registration.type=com.alibaba.cloud.nacos.registry.NacosRegistration",
+ "microsphere.spring.cloud.default-service-registry.type=com.alibaba.cloud.nacos.registry.NacosServiceRegistry",
+ }
+)
+@EnableAutoConfiguration
+class MultipleServiceRegistryIntegrationTest implements ApplicationListener {
+
+ private static ComposeContainer composeContainer;
+
+ @Autowired
+ private ServiceRegistry serviceRegistry;
+
+ @Autowired
+ private AutoServiceRegistrationProperties properties;
+
+ @Autowired
+ private Registration registration;
+
+ @Autowired
+ private MultipleAutoServiceRegistration autoServiceRegistration;
+
+ @Autowired
+ private ConfigurableApplicationContext context;
+
+ @BeforeAll
+ static void beforeAll() throws Exception {
+ ClassLoader classLoader = MultipleServiceRegistryIntegrationTest.class.getClassLoader();
+ URL resource = classLoader.getResource("META-INF/docker/service-registry-servers.yml");
+ File dockerComposeFile = new File(resource.toURI());
+ composeContainer = new ComposeContainer(dockerComposeFile);
+ composeContainer.waitingFor("nacos", forLogMessage(".*Nacos started successfully.*", 1))
+ .waitingFor("eureka", forLogMessage(".*Started EurekaServerApplication.*", 1))
+ .start();
+ }
+
+ @AfterAll
+ static void afterAll() {
+ composeContainer.stop();
+ }
+
+ @BeforeEach
+ void setUp() {
+ context.addApplicationListener(this);
+ }
+
+ @Override
+ public void onApplicationEvent(RegistrationPreRegisteredEvent event) {
+ onPreRegisteredEvent(event.getRegistration());
+ }
+
+ void onPreRegisteredEvent(Registration registration) {
+ this.registration.getMetadata().put("my-key", "my-value");
+ if (registration instanceof EurekaRegistration) {
+ EurekaRegistration eurekaRegistration = (EurekaRegistration) registration;
+ ApplicationInfoManager applicationInfoManager = eurekaRegistration.getApplicationInfoManager();
+ InstanceInfo instanceInfo = applicationInfoManager.getInfo();
+ Map metadata = registration.getMetadata();
+ // Sync metadata from Registration to InstanceInfo
+ instanceInfo.getMetadata().putAll(metadata);
+ }
+ }
+
+ @Test
+ void test() throws Exception {
+ assertNotNull(registration);
+ execute(autoServiceRegistration::start, e -> {
+
+ });
+ assertEquals(registration.getMetadata().get("my-key"), "my-value");
+ autoServiceRegistration.stop();
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryTest.java
index ee918731..0bef8aaa 100644
--- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryTest.java
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/MultipleServiceRegistryTest.java
@@ -1,113 +1,101 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
-import com.alibaba.cloud.nacos.NacosServiceAutoConfiguration;
-import com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration;
-import com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration;
-import com.netflix.appinfo.ApplicationInfoManager;
-import com.netflix.appinfo.InstanceInfo;
-import io.microsphere.spring.cloud.client.service.registry.autoconfigure.ServiceRegistryAutoConfiguration;
-import io.microsphere.spring.cloud.client.service.registry.event.RegistrationPreRegisteredEvent;
-import org.junit.jupiter.api.Disabled;
+
+import com.alibaba.cloud.nacos.registry.NacosRegistration;
+import com.alibaba.cloud.nacos.registry.NacosServiceRegistry;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.cloud.client.CommonsClientAutoConfiguration;
-import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration;
-import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
import org.springframework.cloud.client.serviceregistry.Registration;
import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
-import org.springframework.cloud.commons.util.UtilAutoConfiguration;
-import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration;
-import org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration;
-import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaRegistration;
-import org.springframework.context.ApplicationListener;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.TestPropertySource;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-
-import java.util.Map;
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.collection.Maps.ofMap;
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static io.microsphere.spring.cloud.client.service.registry.MultipleServiceRegistry.getRegistrationClass;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-@Disabled
-@ExtendWith(SpringExtension.class)
-@ContextConfiguration(classes = {
- AutoServiceRegistrationAutoConfiguration.class,
- CommonsClientAutoConfiguration.class,
- EurekaClientAutoConfiguration.class,
- DiscoveryClientOptionalArgsConfiguration.class,
- NacosServiceRegistryAutoConfiguration.class,
- NacosServiceAutoConfiguration.class,
- NacosDiscoveryAutoConfiguration.class,
- UtilAutoConfiguration.class,
- MultipleServiceRegistryTest.class,
- ServiceRegistryAutoConfiguration.class,
-})
-@TestPropertySource(
- properties = {
- "spring.application.name=test",
- "microsphere.spring.cloud.multiple-registration.enabled=true",
- "microsphere.spring.cloud.default-registration.type=com.alibaba.cloud.nacos.registry.NacosRegistration",
- "microsphere.spring.cloud.default-service-registry.type=com.alibaba.cloud.nacos.registry.NacosServiceRegistry",
- "spring.cloud.service-registry.auto-registration.enabled=true",
- "spring.cloud.nacos.discovery.namespace=f7ad23e0-f581-4516-9420-8c50aa6a7b89",
- "spring.cloud.nacos.discovery.metadata.key=value",
- "eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eureka",
- }
-)
-@EnableAutoConfiguration
-class MultipleServiceRegistryTest implements ApplicationListener {
-
- @Autowired
- private ServiceRegistry serviceRegistry;
-
- @Autowired
- private AutoServiceRegistrationProperties properties;
-
- @Autowired
- private Registration registration;
-
- @Autowired
- private MultipleAutoServiceRegistration autoServiceRegistration;
-
- @Override
- public void onApplicationEvent(RegistrationPreRegisteredEvent event) {
- this.registration.getMetadata().put("my-key", "my-value");
- if (event.getRegistration() instanceof EurekaRegistration) {
- EurekaRegistration eurekaRegistration = (EurekaRegistration) event.getRegistration();
-
-
- ApplicationInfoManager applicationInfoManager = eurekaRegistration.getApplicationInfoManager();
- InstanceInfo instanceInfo = applicationInfoManager.getInfo();
- Map metadata = registration.getMetadata();
- // Sync metadata from Registration to InstanceInfo
- instanceInfo.getMetadata().putAll(metadata);
- }
- }
+import static org.junit.jupiter.api.Assertions.assertNull;
- @Test
- public void test() throws Exception {
- assertNotNull(serviceRegistry);
- assertNotNull(registration);
- autoServiceRegistration.start();
- Thread.sleep(60 * 1000);
+/**
+ * {@link MultipleServiceRegistry} Test
+ *
+ * @author Mercy
+ * @see MultipleServiceRegistry
+ * @since 1.0.0
+ */
+class MultipleServiceRegistryTest {
- autoServiceRegistration.stop();
+ private DefaultRegistration defaultRegistration;
+
+ private MultipleRegistration registration;
+
+ private ServiceRegistry serviceRegistry;
+
+ private MultipleServiceRegistry multipleServiceRegistry;
+
+ @BeforeEach
+ void setUp() {
+ this.defaultRegistration = createDefaultRegistration();
+ this.registration = new MultipleRegistration(ofList(defaultRegistration));
+ this.serviceRegistry = new InMemoryServiceRegistry();
+ this.multipleServiceRegistry = new MultipleServiceRegistry(ofMap("default", serviceRegistry));
}
@Test
- public void testMetaData() throws Exception {
- assertNotNull(registration);
+ void testRegister() {
+ this.multipleServiceRegistry.register(this.registration);
+ }
- autoServiceRegistration.start();
+ @Test
+ void testDeregister() {
+ this.multipleServiceRegistry.deregister(this.registration);
+ }
- assertEquals(registration.getMetadata().get("my-key"), "my-value");
- Thread.sleep(60 * 1000);
+ @Test
+ void testClose() {
+ this.multipleServiceRegistry.close();
+ }
- autoServiceRegistration.stop();
+ @Test
+ void testStatus() {
+ testRegister();
+ this.multipleServiceRegistry.setStatus(this.registration, "UP");
+ assertEquals("UP", this.multipleServiceRegistry.getStatus(this.registration));
+ testDeregister();
+ this.multipleServiceRegistry.setStatus(this.registration, "UP");
+ assertNull(this.multipleServiceRegistry.getStatus(this.registration));
}
+ @Test
+ void testGetRegistrationClass() {
+ assertEquals(MultipleRegistration.class, getRegistrationClass(this.multipleServiceRegistry.getClass()));
+ assertEquals(NacosRegistration.class, getRegistrationClass(NacosServiceRegistry.class));
+ }
+ @Test
+ void testGetRegistrationClassWithNull() {
+ MultipleRegistration multipleRegistration = new MultipleRegistration(ofList(this.defaultRegistration)) {
+ @Override
+ public T special(Class specialClass) {
+ return null;
+ }
+ };
+ this.multipleServiceRegistry.register(multipleRegistration);
+ }
}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaDataTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaDataTest.java
new file mode 100644
index 00000000..7239edad
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/RegistrationMetaDataTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.collection.MapUtils.ofEntry;
+import static io.microsphere.collection.Maps.ofMap;
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link RegistrationMetaData} Test
+ *
+ * @author Mercy
+ * @see RegistrationMetaData
+ * @since 1.0.0
+ */
+class RegistrationMetaDataTest {
+
+ private DefaultRegistration defaultRegistration;
+
+ private RegistrationMetaData metaData;
+
+ @BeforeEach
+ void setUp() {
+ this.defaultRegistration = createDefaultRegistration();
+ Map metadata = this.defaultRegistration.getMetadata();
+ metadata.put("key1", "value1");
+ metadata.put("key2", "value2");
+ metadata.put("key3", "value3");
+ this.metaData = new RegistrationMetaData(ofList(defaultRegistration));
+ }
+
+ @Test
+ void testConstructor() {
+ Map metadata = this.defaultRegistration.getMetadata();
+ metadata.put(null, "null");
+ metadata.put("null", null);
+ this.metaData = new RegistrationMetaData(ofList(defaultRegistration));
+ testGet();
+ assertNull(this.metaData.get("null"));
+ }
+
+ @Test
+ void testSize() {
+ assertEquals(3, metaData.size());
+ }
+
+ @Test
+ void testIsEmpty() {
+ assertEquals(false, metaData.isEmpty());
+ }
+
+ @Test
+ void testContainsKey() {
+ assertTrue(metaData.containsKey("key1"));
+ assertFalse(metaData.containsKey("key4"));
+ }
+
+ @Test
+ void testContainsValue() {
+ assertTrue(metaData.containsValue("value1"));
+ assertFalse(metaData.containsValue("value4"));
+ }
+
+ @Test
+ void testGet() {
+ assertEquals("value1", metaData.get("key1"));
+ assertEquals("value2", metaData.get("key2"));
+ assertEquals("value3", metaData.get("key3"));
+ assertNull(metaData.get("key4"));
+ }
+
+ @Test
+ void testPut() {
+ metaData.put("key4", "value4");
+ assertEquals("value4", metaData.get("key4"));
+ }
+
+ @Test
+ void testRemove() {
+ metaData.remove("key1");
+ assertNull(metaData.get("key1"));
+ }
+
+ @Test
+ void testPutAll() {
+ metaData.putAll(ofMap("key4", "value4", "key5", "value5"));
+ assertEquals("value1", metaData.get("key1"));
+ assertEquals("value2", metaData.get("key2"));
+ assertEquals("value3", metaData.get("key3"));
+ assertEquals("value4", metaData.get("key4"));
+ assertEquals("value5", metaData.get("key5"));
+ }
+
+ @Test
+ void testClear() {
+ metaData.clear();
+ assertEquals(0, metaData.size());
+ }
+
+ @Test
+ void testKeySet() {
+ Set keys = metaData.keySet();
+ assertTrue(keys.contains("key1"));
+ assertTrue(keys.contains("key2"));
+ assertTrue(keys.contains("key3"));
+ assertFalse(keys.contains("key4"));
+ }
+
+ @Test
+ void testValues() {
+ Collection values = metaData.values();
+ assertTrue(values.contains("value1"));
+ assertTrue(values.contains("value2"));
+ assertTrue(values.contains("value3"));
+ assertFalse(values.contains("value4"));
+ }
+
+ @Test
+ void testEntrySet() {
+ Set> entries = metaData.entrySet();
+ assertTrue(entries.contains(ofEntry("key1", "value1")));
+ assertTrue(entries.contains(ofEntry("key2", "value2")));
+ assertTrue(entries.contains(ofEntry("key3", "value3")));
+ assertFalse(entries.contains(ofEntry("key4", "value4")));
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistrationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistrationTest.java
new file mode 100644
index 00000000..218a2f90
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/SimpleAutoServiceRegistrationTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link SimpleAutoServiceRegistration} Test
+ *
+ * @author Mercy
+ * @see SimpleAutoServiceRegistration
+ * @since 1.0.0
+ */
+class SimpleAutoServiceRegistrationTest {
+
+ private InMemoryServiceRegistry serviceRegistry;
+
+ private AutoServiceRegistrationProperties properties;
+
+ private DefaultRegistration registration;
+
+ private SimpleAutoServiceRegistration autoServiceRegistration;
+
+ @BeforeEach
+ void setUp() {
+ this.serviceRegistry = new InMemoryServiceRegistry();
+ this.properties = new AutoServiceRegistrationProperties();
+ this.registration = createDefaultRegistration();
+ this.autoServiceRegistration = new SimpleAutoServiceRegistration(serviceRegistry, properties, registration);
+ }
+
+ @Test
+ void testGetConfiguration() {
+ assertSame(this.properties, autoServiceRegistration.getConfiguration());
+ }
+
+ @Test
+ void testIsEnabled() {
+ assertSame(this.properties.isEnabled(), autoServiceRegistration.isEnabled());
+ }
+
+ @Test
+ void testGetRegistration() {
+ assertSame(this.registration, autoServiceRegistration.getRegistration());
+ }
+
+ @Test
+ void testGetManagementRegistration() {
+ assertSame(this.registration, autoServiceRegistration.getManagementRegistration());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/SimpleServiceRegistryTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/SimpleServiceRegistryTest.java
new file mode 100644
index 00000000..1b11e19e
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/SimpleServiceRegistryTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
+import org.springframework.cloud.client.discovery.simple.reactive.SimpleReactiveDiscoveryProperties;
+
+import java.util.List;
+import java.util.Map;
+
+import static io.microsphere.spring.cloud.client.discovery.util.DiscoveryUtils.simpleReactiveDiscoveryProperties;
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static io.microsphere.spring.cloud.client.service.registry.SimpleServiceRegistry.STATUS_KEY;
+import static java.util.Collections.emptyList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link SimpleServiceRegistry} Test
+ *
+ * @author Mercy
+ * @see SimpleServiceRegistry
+ * @since 1.0.0
+ */
+class SimpleServiceRegistryTest {
+
+ private DefaultRegistration registration;
+
+ private SimpleDiscoveryProperties properties;
+
+ private SimpleServiceRegistry registry;
+
+ @BeforeEach
+ void setUp() {
+ this.registration = createDefaultRegistration();
+ this.properties = new SimpleDiscoveryProperties();
+ this.registry = new SimpleServiceRegistry(this.properties);
+ }
+
+ @Test
+ void testConstructor() {
+ SimpleReactiveDiscoveryProperties properties = simpleReactiveDiscoveryProperties(this.properties);
+ this.registry = new SimpleServiceRegistry(properties);
+ testDeregister();
+ }
+
+ @Test
+ void testRegister() {
+ Map> instancesMap = this.properties.getInstances();
+ assertTrue(instancesMap.isEmpty());
+ this.registry.register(this.registration);
+
+ List instances = instancesMap.get(this.registration.getServiceId());
+ assertEquals(1, instances.size());
+ DefaultServiceInstance instance = instances.get(0);
+ assertSame(instance, this.registration);
+ }
+
+ @Test
+ void testDeregister() {
+ testRegister();
+ this.registry.deregister(this.registration);
+ List instances = getInstances(this.registration.getServiceId());
+ assertTrue(instances.isEmpty());
+ }
+
+ @Test
+ void testClose() {
+ Map> instancesMap = this.properties.getInstances();
+ assertTrue(instancesMap.isEmpty());
+ this.registry.close();
+ assertTrue(instancesMap.isEmpty());
+ }
+
+ @Test
+ void testSetStatus() {
+ testRegister();
+ DefaultServiceInstance instance = getInstance(this.registration.getServiceId(), this.registration.getInstanceId());
+ String status = "UP";
+ this.registry.setStatus(this.registration, status);
+ assertEquals(status, instance.getMetadata().get(STATUS_KEY));
+ }
+
+ @Test
+ void testGetStatus() {
+ testSetStatus();
+ assertEquals(this.registration.getMetadata().get(STATUS_KEY), this.registry.getStatus(this.registration));
+ }
+
+ List getInstances(String serviceId) {
+ return this.properties.getInstances().getOrDefault(serviceId, emptyList());
+ }
+
+ DefaultServiceInstance getInstance(String serviceId, String instanceId) {
+ List instances = getInstances(serviceId);
+ return instances.stream()
+ .filter(instance -> instance.getInstanceId().equals(instanceId))
+ .findFirst().orElse(null);
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfigurationTest.java
new file mode 100644
index 00000000..6bc1a536
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/actuate/autoconfigure/ServiceRegistrationEndpointAutoConfigurationTest.java
@@ -0,0 +1,46 @@
+package io.microsphere.spring.cloud.client.service.registry.actuate.autoconfigure;
+
+import io.microsphere.spring.cloud.client.service.registry.endpoint.ServiceDeregistrationEndpoint;
+import io.microsphere.spring.cloud.client.service.registry.endpoint.ServiceRegistrationEndpoint;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+/**
+ * {@link ServiceRegistrationEndpointAutoConfiguration} Test
+ *
+ * @author Mercy
+ * @see ServiceRegistrationEndpointAutoConfiguration
+ * @since 1.0.0
+ */
+@SpringBootTest(
+ classes = {
+ ServiceRegistrationEndpointAutoConfigurationTest.class
+ },
+ webEnvironment = RANDOM_PORT,
+ properties = {
+ "microsphere.spring.cloud.service-registry.auto-registration.simple.enabled=true",
+ "management.endpoint.serviceRegistration.enabled=true",
+ "management.endpoint.serviceDeregistration.enabled=true",
+ }
+)
+@EnableAutoConfiguration
+class ServiceRegistrationEndpointAutoConfigurationTest {
+
+ @Autowired
+ private ObjectProvider serviceRegistrationEndpoint;
+
+ @Autowired
+ private ObjectProvider serviceDeregistrationEndpoint;
+
+ @Test
+ void testEndpoints() {
+ assertNotNull(serviceRegistrationEndpoint.getIfAvailable());
+ assertNotNull(serviceDeregistrationEndpoint.getIfAvailable());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspectTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspectTest.java
new file mode 100644
index 00000000..cbdfc790
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/aspect/EventPublishingRegistrationAspectTest.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 io.microsphere.spring.cloud.client.service.registry.aspect;
+
+
+import io.microsphere.spring.cloud.client.service.registry.DefaultRegistration;
+import io.microsphere.spring.cloud.client.service.registry.InMemoryServiceRegistry;
+import io.microsphere.spring.cloud.client.service.registry.MultipleRegistration;
+import io.microsphere.spring.cloud.client.service.registry.MultipleServiceRegistry;
+import io.microsphere.spring.cloud.client.service.registry.RegistrationCustomizer;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationDeregisteredEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationPreDeregisteredEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationPreRegisteredEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationRegisteredEvent;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import java.util.Map;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link EventPublishingRegistrationAspect} Test
+ *
+ * @author Mercy
+ * @see EventPublishingRegistrationAspect
+ * @since 1.0.0
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration(classes = {
+ EventPublishingRegistrationAspect.class,
+ EventPublishingRegistrationAspectTest.Config.class
+})
+@DirtiesContext
+class EventPublishingRegistrationAspectTest {
+
+ @Autowired
+ private ServiceRegistry serviceRegistry;
+
+ @Autowired
+ private ConfigurableApplicationContext context;
+
+ private MultipleRegistration multipleRegistration;
+
+ @BeforeEach
+ void setUp() {
+ DefaultRegistration defaultRegistration = createDefaultRegistration();
+ this.multipleRegistration = new MultipleRegistration(ofList(defaultRegistration));
+ }
+
+ @Test
+ void testBeforeAndAfterRegister() {
+ this.context.addApplicationListener((ApplicationListener) event -> {
+ assertRegistration(event);
+ });
+ this.context.addApplicationListener((ApplicationListener) event -> {
+ assertRegistration(event);
+ });
+ this.serviceRegistry.register(multipleRegistration);
+ }
+
+ @Test
+ void testBeforeAndAfterDeregister() {
+ this.context.addApplicationListener((ApplicationListener) event -> {
+ assertRegistration(event);
+ });
+ this.context.addApplicationListener((ApplicationListener) event -> {
+ assertRegistration(event);
+ });
+ this.serviceRegistry.deregister(multipleRegistration);
+ }
+
+ void assertRegistration(RegistrationEvent event) {
+ assertSame(this.multipleRegistration, event.getRegistration());
+ }
+
+ @Import(InMemoryServiceRegistry.class)
+ @EnableAspectJAutoProxy
+ static class Config implements RegistrationCustomizer {
+
+ @Override
+ public void customize(Registration registration) {
+ }
+
+ @Bean
+ @Primary
+ public MultipleServiceRegistry multipleServiceRegistry(Map registriesMap) {
+ return new MultipleServiceRegistry(registriesMap);
+ }
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfigurationTest.java
new file mode 100644
index 00000000..9e1a0091
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/ServiceRegistryAutoConfigurationTest.java
@@ -0,0 +1,134 @@
+package io.microsphere.spring.cloud.client.service.registry.autoconfigure;
+
+import io.microsphere.spring.cloud.client.service.registry.InMemoryServiceRegistry;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationDeregisteredEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationPreDeregisteredEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationPreRegisteredEvent;
+import io.microsphere.spring.cloud.client.service.registry.event.RegistrationRegisteredEvent;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.DEREGISTERED;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.PRE_DEREGISTERED;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.PRE_REGISTERED;
+import static io.microsphere.spring.cloud.client.service.registry.event.RegistrationEvent.Type.REGISTERED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.springframework.aop.support.AopUtils.getTargetClass;
+
+/**
+ * {@link ServiceRegistryAutoConfiguration} Test
+ *
+ * @author Mercy
+ * @see ServiceRegistryAutoConfiguration
+ * @since 1.0.0
+ */
+@SpringBootTest(
+ classes = {
+ InMemoryServiceRegistry.class,
+ ServiceRegistryAutoConfiguration.class,
+ ServiceRegistryAutoConfigurationTest.class
+ }
+)
+@EnableAspectJAutoProxy
+class ServiceRegistryAutoConfigurationTest {
+
+ @Autowired
+ private ConfigurableApplicationContext context;
+
+ @Autowired
+ private ServiceRegistry serviceRegistry;
+
+ private Registration registration;
+
+ private int count;
+
+ @BeforeEach
+ void setUp() {
+ this.registration = createDefaultRegistration();
+ }
+
+ @Test
+ void testEventPublishingRegistrationAspect() {
+
+ context.addApplicationListener(this::onApplicationEvent);
+
+ serviceRegistry.register(registration);
+
+ assertEquals(2, count);
+
+ serviceRegistry.deregister(registration);
+
+ assertEquals(4, count);
+ }
+
+ private void onApplicationEvent(ApplicationEvent event) {
+ if (event instanceof RegistrationPreRegisteredEvent) {
+ onRegistrationPreRegisteredEvent((RegistrationPreRegisteredEvent) event);
+ } else if (event instanceof RegistrationRegisteredEvent) {
+ onRegistrationRegisteredEvent((RegistrationRegisteredEvent) event);
+ } else if (event instanceof RegistrationPreDeregisteredEvent) {
+ onRegistrationPreDeregisteredEvent((RegistrationPreDeregisteredEvent) event);
+ } else if (event instanceof RegistrationDeregisteredEvent) {
+ onRegistrationDeregisteredEvent((RegistrationDeregisteredEvent) event);
+ }
+ }
+
+ private void onRegistrationPreRegisteredEvent(RegistrationPreRegisteredEvent event) {
+ assertRegistrationEvent(event);
+ assertTrue(event.isPreRegistered());
+ assertFalse(event.isRegistered());
+ assertFalse(event.isPreDeregistered());
+ assertFalse(event.isDeregistered());
+ assertEquals(PRE_REGISTERED, event.getType());
+ }
+
+ private void onRegistrationRegisteredEvent(RegistrationRegisteredEvent event) {
+ assertRegistrationEvent(event);
+ assertFalse(event.isPreRegistered());
+ assertTrue(event.isRegistered());
+ assertFalse(event.isPreDeregistered());
+ assertFalse(event.isDeregistered());
+ assertEquals(REGISTERED, event.getType());
+ }
+
+ private void onRegistrationPreDeregisteredEvent(RegistrationPreDeregisteredEvent event) {
+ assertRegistrationEvent(event);
+ assertFalse(event.isPreRegistered());
+ assertFalse(event.isRegistered());
+ assertTrue(event.isPreDeregistered());
+ assertFalse(event.isDeregistered());
+ assertEquals(PRE_DEREGISTERED, event.getType());
+ }
+
+ private void onRegistrationDeregisteredEvent(RegistrationDeregisteredEvent event) {
+ assertRegistrationEvent(event);
+ assertFalse(event.isPreRegistered());
+ assertFalse(event.isRegistered());
+ assertFalse(event.isPreDeregistered());
+ assertTrue(event.isDeregistered());
+ assertEquals(DEREGISTERED, event.getType());
+ }
+
+ private void assertRegistrationEvent(RegistrationEvent event) {
+ Registration registration = event.getRegistration();
+ assertEquals(this.registration, registration);
+ assertSame(this.registration, registration);
+ assertSame(getTargetClass(this.serviceRegistry), getTargetClass(event.getRegistry()));
+ assertNotNull(event.getSource());
+ assertNotNull(event.getType());
+ count++;
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfigurationTest.java
new file mode 100644
index 00000000..8fa8f2e0
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/SimpleAutoServiceRegistrationAutoConfigurationTest.java
@@ -0,0 +1,54 @@
+package io.microsphere.spring.cloud.client.service.registry.autoconfigure;
+
+import io.microsphere.spring.cloud.client.service.registry.SimpleAutoServiceRegistration;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+/**
+ * {@link SimpleAutoServiceRegistrationAutoConfiguration} Test
+ *
+ * @author Mercy
+ * @see SimpleAutoServiceRegistrationAutoConfiguration
+ * @since 1.0.0
+ */
+@SpringBootTest(
+ classes = {
+ SimpleAutoServiceRegistrationAutoConfigurationTest.class
+ },
+ webEnvironment = RANDOM_PORT,
+ properties = {
+ "microsphere.spring.cloud.service-registry.auto-registration.simple.enabled=true",
+ "spring.application.name=test-service"
+ }
+)
+@EnableAutoConfiguration
+class SimpleAutoServiceRegistrationAutoConfigurationTest {
+
+ @Autowired
+ private Registration registration;
+
+ @Autowired
+ private ServiceRegistry serviceRegistry;
+
+ @Autowired
+ private SimpleAutoServiceRegistration simpleAutoServiceRegistration;
+
+ @Test
+ void test() {
+ assertEquals("test-service", registration.getServiceId());
+ assertNotNull(registration.getHost());
+ assertNotNull(registration.getPort());
+ assertNotNull(registration.getUri());
+ assertNotNull(registration.getInstanceId());
+ assertNotNull(registration.getMetadata());
+ }
+
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfigurationTest.java
new file mode 100644
index 00000000..a3fdb1e5
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebFluxServiceRegistryAutoConfigurationTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.autoconfigure;
+
+
+import io.microsphere.spring.webflux.annotation.EnableWebFluxExtension;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.TestPropertySource;
+
+/**
+ * {@link WebFluxServiceRegistryAutoConfiguration} Test
+ *
+ * @author Mercy
+ * @see WebFluxServiceRegistryAutoConfiguration
+ * @since 1.0.0
+ */
+@TestPropertySource(
+ properties = {
+ "spring.main.web-application-type=reactive"
+ }
+)
+@EnableWebFluxExtension
+@EnableAutoConfiguration
+class WebFluxServiceRegistryAutoConfigurationTest extends WebServiceRegistryAutoConfigurationTest {
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfigurationTest.java
index f0693bf7..cf751340 100644
--- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfigurationTest.java
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebMvcServiceRegistryAutoConfigurationTest.java
@@ -17,17 +17,7 @@
package io.microsphere.spring.cloud.client.service.registry.autoconfigure;
import io.microsphere.spring.webmvc.annotation.EnableWebMvcExtension;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.ObjectProvider;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.cloud.client.serviceregistry.Registration;
-
-import java.util.Map;
-
-import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_MAPPINGS_METADATA_NAME;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* {@link WebMvcServiceRegistryAutoConfiguration} Test
@@ -35,28 +25,7 @@
* @author Mercy
* @since 1.0.0
*/
-@SpringBootTest(
- classes = {WebMvcServiceRegistryAutoConfigurationTest.class},
- properties = {
- "microsphere.spring.cloud.simple.enabled=true",
- "spring.cloud.service-registry.auto-registration.enabled=true",
- "spring.cloud.kubernetes.enabled=false",
- "kubernetes.informer.enabled=false",
- "kubernetes.manifests.enabled=false",
- "kubernetes.reconciler.enabled=false"
- },
- webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
-)
-@EnableAutoConfiguration
@EnableWebMvcExtension
-public class WebMvcServiceRegistryAutoConfigurationTest {
-
- @Autowired
- private Registration registration;
-
- @Test
- public void test() {
- Map metadata = registration.getMetadata();
- assertNotNull(metadata.get(WEB_MAPPINGS_METADATA_NAME));
- }
-}
+@EnableAutoConfiguration
+class WebMvcServiceRegistryAutoConfigurationTest extends WebServiceRegistryAutoConfigurationTest {
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebServiceRegistryAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebServiceRegistryAutoConfigurationTest.java
new file mode 100644
index 00000000..2dd85f88
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/autoconfigure/WebServiceRegistryAutoConfigurationTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.autoconfigure;
+
+
+import io.microsphere.logging.test.jupiter.LoggingLevelsTest;
+import io.microsphere.spring.test.web.controller.TestController;
+import io.microsphere.spring.web.metadata.WebEndpointMapping;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.client.serviceregistry.Registration;
+
+import java.util.Collection;
+import java.util.Map;
+
+import static io.microsphere.collection.ListUtils.newLinkedList;
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_CONTEXT_PATH_METADATA_NAME;
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_MAPPINGS_METADATA_NAME;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.getWebEndpointMappings;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+import static org.springframework.http.HttpMethod.GET;
+
+/**
+ * {@link WebServiceRegistryAutoConfiguration} Test
+ *
+ * @author Mercy
+ * @see WebServiceRegistryAutoConfiguration
+ * @since 1.0.0
+ */
+@SpringBootTest(
+ classes = TestController.class,
+ properties = {
+ "microsphere.spring.cloud.service-registry.auto-registration.simple.enabled=true",
+ "spring.cloud.service-registry.auto-registration.enabled=true"
+ },
+ webEnvironment = RANDOM_PORT
+)
+abstract class WebServiceRegistryAutoConfigurationTest {
+
+ @Autowired
+ private Registration registration;
+
+ @Autowired
+ private WebServiceRegistryAutoConfiguration autoConfiguration;
+
+ @Test
+ void test() {
+ Map metadata = this.registration.getMetadata();
+ assertEquals("", metadata.get(WEB_CONTEXT_PATH_METADATA_NAME));
+ assertNotNull(metadata.get(WEB_MAPPINGS_METADATA_NAME));
+
+ Collection webEndpointMappings = getWebEndpointMappings(this.registration);
+ assertTrue(webEndpointMappings.size() >= 6);
+ }
+
+ @Test
+ @LoggingLevelsTest(levels = "ERROR")
+ void testExcludeMappings() {
+ WebEndpointMapping webEndpointMapping = WebEndpointMapping
+ .webmvc()
+ .endpoint(this)
+ .methods(GET)
+ .pattern("/actuator/test")
+ .build();
+
+ Collection webEndpointMappings = newLinkedList(ofList(webEndpointMapping));
+
+ this.autoConfiguration.excludeMappings(webEndpointMappings);
+ assertTrue(webEndpointMappings.isEmpty());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstantsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstantsTest.java
new file mode 100644
index 00000000..42598211
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/constants/InstanceConstantsTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.constants;
+
+
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.MANAGEMENT_PORT_METADATA_NAME;
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.START_TIME_METADATA_NAME;
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_CONTEXT_PATH_METADATA_NAME;
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_MAPPINGS_METADATA_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link InstanceConstants} Test
+ *
+ * @author Mercy
+ * @see InstanceConstants
+ * @since 1.0.0
+ */
+class InstanceConstantsTest {
+
+ @Test
+ void testConstants() {
+ assertEquals("web.mappings", WEB_MAPPINGS_METADATA_NAME);
+ assertEquals("web.context-path", WEB_CONTEXT_PATH_METADATA_NAME);
+ assertEquals("management-port", MANAGEMENT_PORT_METADATA_NAME);
+ assertEquals("start-time", START_TIME_METADATA_NAME);
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpointTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpointTest.java
new file mode 100644
index 00000000..e0264f87
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/AbstractServiceRegistrationEndpointTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.endpoint;
+
+import io.microsphere.spring.cloud.client.service.registry.InMemoryServiceRegistry;
+import io.microsphere.spring.cloud.client.service.registry.SimpleAutoServiceRegistration;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.web.context.WebServerInitializedEvent;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.cloud.client.serviceregistry.ServiceRegistry;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+
+import static io.microsphere.spring.cloud.client.service.registry.DefaultRegistrationTest.createDefaultRegistration;
+import static io.microsphere.spring.cloud.client.service.registry.endpoint.AbstractServiceRegistrationEndpoint.detectRunning;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link AbstractServiceRegistrationEndpoint} Test
+ *
+ * @author Mercy
+ * @see BaseServiceRegistrationEndpointTest
+ * @since 1.0.0
+ */
+@ContextConfiguration(classes = {
+ AbstractServiceRegistrationEndpointTest.class
+})
+@EnableAutoConfiguration
+class AbstractServiceRegistrationEndpointTest extends BaseServiceRegistrationEndpointTest {
+
+ @Test
+ void testOnApplicationEvent() {
+ AbstractServiceRegistrationEndpoint endpoint = new AbstractServiceRegistrationEndpoint() {
+ };
+ WebServer webServer = mock(WebServer.class);
+ when(webServer.getPort()).thenReturn(this.port);
+ WebServerInitializedEvent event = new ServletWebServerInitializedEvent(webServer, null);
+ endpoint.onApplicationEvent(event);
+ assertFalse(endpoint.isRunning());
+ }
+
+ @Test
+ void testDetectRunning() {
+ assertFalse(detectRunning(null));
+ ServiceRegistry serviceRegistry = new InMemoryServiceRegistry();
+ AutoServiceRegistrationProperties properties = new AutoServiceRegistrationProperties();
+ Registration registration = createDefaultRegistration();
+ SimpleAutoServiceRegistration simpleAutoServiceRegistration = new SimpleAutoServiceRegistration(serviceRegistry, properties, registration);
+ GenericApplicationContext context = new GenericApplicationContext();
+ context.refresh();
+ simpleAutoServiceRegistration.setApplicationContext(context);
+ assertFalse(detectRunning(simpleAutoServiceRegistration));
+ simpleAutoServiceRegistration.start();
+ assertTrue(detectRunning(simpleAutoServiceRegistration));
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/BaseServiceRegistrationEndpointTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/BaseServiceRegistrationEndpointTest.java
new file mode 100644
index 00000000..cbd55d63
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/BaseServiceRegistrationEndpointTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.endpoint;
+
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationProperties;
+import org.springframework.cloud.client.serviceregistry.Registration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
+
+/**
+ * {@link AbstractServiceRegistrationEndpoint} Base Test
+ *
+ * @author Mercy
+ * @see AbstractServiceRegistrationEndpoint
+ * @since 1.0.0
+ */
+@ExtendWith(SpringExtension.class)
+@SpringBootTest(
+ properties = {
+ "spring.application.name=test-app",
+ "spring.cloud.discovery.client.simple.instances.test[0].instanceId=1",
+ "spring.cloud.discovery.client.simple.instances.test[0].serviceId=test",
+ "spring.cloud.discovery.client.simple.instances.test[0].host=127.0.0.1",
+ "spring.cloud.discovery.client.simple.instances.test[0].port=8080",
+ "spring.cloud.discovery.client.simple.instances.test[0].metadata.key-1=value-1",
+ "microsphere.spring.cloud.service-registry.auto-registration.simple.enabled=true"
+ },
+ webEnvironment = RANDOM_PORT
+)
+class BaseServiceRegistrationEndpointTest {
+
+ @Autowired
+ protected Registration registration;
+
+ @LocalServerPort
+ protected Integer port;
+
+ @Autowired
+ protected AutoServiceRegistrationProperties autoServiceRegistrationProperties;
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpointTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpointTest.java
new file mode 100644
index 00000000..c1ea2d7e
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceDeregistrationEndpointTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.endpoint;
+
+
+import io.microsphere.logging.test.jupiter.LoggingLevelsTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link ServiceDeregistrationEndpoint} Test
+ *
+ * @author Mercy
+ * @see ServiceDeregistrationEndpoint
+ * @since 1.0.0
+ */
+@ContextConfiguration(classes = {
+ ServiceRegistrationEndpoint.class,
+ ServiceDeregistrationEndpoint.class
+})
+@EnableAutoConfiguration
+class ServiceDeregistrationEndpointTest extends BaseServiceRegistrationEndpointTest {
+
+ @Autowired
+ private ServiceRegistrationEndpoint serviceRegistrationEndpoint;
+
+ @Autowired
+ private ServiceDeregistrationEndpoint serviceDeregistrationEndpoint;
+
+ @Test
+ @LoggingLevelsTest(levels = "ERROR")
+ void testStop() {
+ assertEquals(this.serviceDeregistrationEndpoint.isRunning(), this.serviceDeregistrationEndpoint.stop());
+ assertEquals(this.serviceRegistrationEndpoint.isRunning(), this.serviceRegistrationEndpoint.start());
+ assertEquals(this.serviceDeregistrationEndpoint.isRunning(), this.serviceDeregistrationEndpoint.stop());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpointTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpointTest.java
new file mode 100644
index 00000000..77e9aff3
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/registry/endpoint/ServiceRegistrationEndpointTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.registry.endpoint;
+
+
+import io.microsphere.logging.test.jupiter.LoggingLevelsTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.Map;
+
+import static java.lang.Boolean.TRUE;
+import static java.lang.Integer.valueOf;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * {@link ServiceRegistrationEndpoint} Test
+ *
+ * @author Mercy
+ * @see ServiceRegistrationEndpoint
+ * @since 1.0.0
+ */
+@ContextConfiguration(classes = {
+ ServiceRegistrationEndpoint.class
+})
+@EnableAutoConfiguration
+class ServiceRegistrationEndpointTest extends BaseServiceRegistrationEndpointTest {
+
+ @Autowired
+ private ServiceRegistrationEndpoint endpoint;
+
+ @Test
+ void testMetadata() {
+ Map metadata = this.endpoint.metadata();
+ assertEquals("test-app", metadata.get("application-name"));
+ assertSame(this.registration, metadata.get("registration"));
+ assertEquals(this.port, metadata.get("port"));
+ assertNull(metadata.get("status"));
+ assertEquals(TRUE, metadata.get("running"));
+ assertEquals(TRUE, metadata.get("enabled"));
+ assertEquals(valueOf(0), metadata.get("phase"));
+ assertEquals(valueOf(0), metadata.get("order"));
+ assertSame(autoServiceRegistrationProperties, metadata.get("config"));
+ }
+
+ @Test
+ @LoggingLevelsTest(levels = "ERROR")
+ void testStart() {
+ assertEquals(this.endpoint.isRunning(), this.endpoint.start());
+ assertEquals(this.endpoint.isRunning(), this.endpoint.start());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtilsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtilsTest.java
new file mode 100644
index 00000000..55bc457f
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/client/service/util/ServiceInstanceUtilsTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 io.microsphere.spring.cloud.client.service.util;
+
+
+import io.microsphere.json.JSONObject;
+import io.microsphere.logging.test.jupiter.LoggingLevelsTest;
+import io.microsphere.spring.web.metadata.WebEndpointMapping;
+import io.microsphere.spring.web.metadata.WebEndpointMapping.Builder;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.DefaultServiceInstance;
+
+import java.net.URI;
+import java.util.Collection;
+
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.json.JSONUtils.jsonObject;
+import static io.microsphere.spring.cloud.client.service.registry.constants.InstanceConstants.WEB_CONTEXT_PATH_METADATA_NAME;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.attachMetadata;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.getMetadata;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.getUri;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.getUriString;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.getWebEndpointMappings;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.parseWebEndpointMapping;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.parseWebEndpointMappings;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.removeMetadata;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.setMetadata;
+import static io.microsphere.spring.cloud.client.service.util.ServiceInstanceUtils.setProperties;
+import static io.microsphere.spring.web.metadata.WebEndpointMapping.Kind.SERVLET;
+import static io.microsphere.spring.web.metadata.WebEndpointMapping.servlet;
+import static io.microsphere.util.StringUtils.EMPTY_STRING;
+import static io.microsphere.util.StringUtils.EMPTY_STRING_ARRAY;
+import static java.lang.System.currentTimeMillis;
+import static java.net.URI.create;
+import static java.util.Collections.emptyList;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link ServiceInstanceUtils} Test
+ *
+ * @author Mercy
+ * @see ServiceInstanceUtils
+ * @since 1.0.0
+ */
+public class ServiceInstanceUtilsTest {
+
+ private static final Integer WEB_ENDPOINT_MAPPING_ID = Integer.valueOf(12345);
+
+ private static final String WEB_ENDPOINT_MAPPING_URL_PATTERN = "/test";
+
+ private static final String WEB_ENDPOINT_MAPPING_METHOD = "GET";
+
+ private String context = "/";
+
+ private DefaultServiceInstance serviceInstance;
+
+ private Collection webEndpointMappings;
+
+ @BeforeEach
+ void setUp() {
+ this.serviceInstance = createDefaultServiceInstance();
+ this.webEndpointMappings = createWebEndpointMappings();
+ }
+
+ @Test
+ @LoggingLevelsTest(levels = "ERROR")
+ void testAttachMetadata() {
+ attachMetadata(this.context, this.serviceInstance, this.webEndpointMappings);
+ assertEquals(this.context, getMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME));
+ }
+
+ @Test
+ void testGetWebEndpointMappings() {
+ attachMetadata(this.context, this.serviceInstance, this.webEndpointMappings);
+ Collection webEndpointMappings = getWebEndpointMappings(this.serviceInstance);
+ assertEquals(1, webEndpointMappings.size());
+ WebEndpointMapping webEndpointMapping = webEndpointMappings.iterator().next();
+ assertEquals(WEB_ENDPOINT_MAPPING_ID, webEndpointMapping.getId());
+ assertTrue(webEndpointMapping.isNegated());
+ assertEquals(WEB_ENDPOINT_MAPPING_URL_PATTERN, webEndpointMapping.getPatterns()[0]);
+ assertEquals(WEB_ENDPOINT_MAPPING_METHOD, webEndpointMapping.getMethods()[0]);
+ assertEquals(SERVLET, webEndpointMapping.getKind());
+ assertArrayEquals(EMPTY_STRING_ARRAY, webEndpointMapping.getParams());
+ assertArrayEquals(EMPTY_STRING_ARRAY, webEndpointMapping.getHeaders());
+ assertArrayEquals(EMPTY_STRING_ARRAY, webEndpointMapping.getProduces());
+ assertArrayEquals(EMPTY_STRING_ARRAY, webEndpointMapping.getConsumes());
+ }
+
+ @Test
+ void testParseWebEndpointMapping() {
+ WebEndpointMapping webEndpointMapping = buildWebEndpointMapping(false);
+ String json = webEndpointMapping.toJSON();
+ JSONObject jsonObject = jsonObject(json);
+ WebEndpointMapping webEndpointMapping1 = parseWebEndpointMapping(jsonObject);
+ assertEquals(webEndpointMapping, webEndpointMapping1);
+ }
+
+ @Test
+ void testParseWebEndpointMappings() {
+ assertSame(emptyList(), parseWebEndpointMappings(null));
+ assertSame(emptyList(), parseWebEndpointMappings(EMPTY_STRING));
+ assertSame(emptyList(), parseWebEndpointMappings(" "));
+ }
+
+ @Test
+ void testGetUriString() {
+ assertEquals("http://localhost:8080", getUriString(this.serviceInstance));
+
+ String uriString = "https://localhost:8080";
+ this.serviceInstance.setUri(create(uriString));
+ assertEquals(uriString, getUriString(this.serviceInstance));
+ }
+
+ @Test
+ void testGetUriStringWithoutPort() {
+ String uriString = "http://localhost";
+ this.serviceInstance.setUri(create(uriString));
+ assertEquals(uriString + ":80", getUriString(this.serviceInstance));
+
+ uriString = "https://localhost";
+ this.serviceInstance.setUri(create(uriString));
+ assertEquals(uriString + ":443", getUriString(this.serviceInstance));
+ }
+
+ @Test
+ void testGetUri() {
+ URI uri = getUri(this.serviceInstance);
+ assertEquals(create("http://localhost:8080"), uri);
+ assertEquals(DefaultServiceInstance.getUri(this.serviceInstance), uri);
+
+ uri = create("https://localhost");
+ this.serviceInstance.setUri(uri);
+ uri = getUri(this.serviceInstance);
+ assertEquals(create("https://localhost:443"), uri);
+ assertEquals(DefaultServiceInstance.getUri(this.serviceInstance), uri);
+ }
+
+ @Test
+ void testMetadataOps() {
+ assertNull(getMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME));
+ assertNull(setMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME, this.context));
+ assertEquals(this.context, getMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME));
+ assertEquals(this.context, setMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME, EMPTY_STRING));
+ assertEquals(EMPTY_STRING, getMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME));
+
+ assertEquals(EMPTY_STRING, removeMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME));
+ assertNull(getMetadata(this.serviceInstance, WEB_CONTEXT_PATH_METADATA_NAME));
+ }
+
+ @Test
+ void testSetProperties() {
+ DefaultServiceInstance target = new DefaultServiceInstance();
+ setProperties(this.serviceInstance, target);
+ assertEquals(this.serviceInstance, target);
+ }
+
+ private Collection createWebEndpointMappings() {
+ return ofList(buildWebEndpointMapping(true));
+ }
+
+ private WebEndpointMapping buildWebEndpointMapping(boolean nagated) {
+ Builder> builder = servlet()
+ .endpoint(WEB_ENDPOINT_MAPPING_ID)
+ .method(WEB_ENDPOINT_MAPPING_METHOD)
+ .pattern(WEB_ENDPOINT_MAPPING_URL_PATTERN);
+ if (nagated) {
+ builder.negate();
+ }
+ return builder.build();
+ }
+
+ public static DefaultServiceInstance createDefaultServiceInstance() {
+ DefaultServiceInstance serviceInstance = new DefaultServiceInstance();
+ serviceInstance.setInstanceId("ServiceInstance-" + currentTimeMillis());
+ serviceInstance.setServiceId("test-service");
+ serviceInstance.setHost("localhost");
+ serviceInstance.setPort(8080);
+ serviceInstance.setSecure(false);
+ return serviceInstance;
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/condition/ConditionalOnUtilEnabledTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/condition/ConditionalOnUtilEnabledTest.java
new file mode 100644
index 00000000..ce62854f
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/condition/ConditionalOnUtilEnabledTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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 io.microsphere.spring.cloud.commons.condition;
+
+import io.microsphere.spring.cloud.test.ConditionalOnPropertyEnabledTest;
+
+/**
+ * {@link ConditionalOnUtilEnabled} Test
+ *
+ * @author Mercy
+ * @see ConditionalOnUtilEnabled
+ * @since 1.0.0
+ */
+@ConditionalOnUtilEnabled
+public class ConditionalOnUtilEnabledTest extends ConditionalOnPropertyEnabledTest {
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstantsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstantsTest.java
new file mode 100644
index 00000000..979ad63e
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/constants/CommonsPropertyConstantsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 io.microsphere.spring.cloud.commons.constants;
+
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.COMPOSITE_REGISTRATION_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.FEATURES_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MICROSPHERE_SPRING_CLOUD_WEB_MVC_PROPERTY_NAME_PREFIX;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MULTIPLE_REGISTRATION_DEFAULT_REGISTRATION_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MULTIPLE_REGISTRATION_DEFAULT_REGISTRY_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.MULTIPLE_REGISTRATION_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.SERVICE_REGISTRY_PROPERTY_PREFIX;
+import static io.microsphere.spring.cloud.commons.constants.CommonsPropertyConstants.SPRING_CLOUD_PROPERTY_PREFIX;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CommonsPropertyConstants} Test
+ *
+ * @author Mercy
+ * @see CommonsPropertyConstants
+ * @since 1.0.0
+ */
+class CommonsPropertyConstantsTest {
+
+ @Test
+ void testConstants() {
+ assertEquals("spring.cloud.", SPRING_CLOUD_PROPERTY_PREFIX);
+ assertEquals("spring.cloud.service-registry.", SERVICE_REGISTRY_PROPERTY_PREFIX);
+ assertEquals("spring.cloud.service-registry.auto-registration.enabled", SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME);
+ assertEquals("spring.cloud.features.enabled", FEATURES_ENABLED_PROPERTY_NAME);
+ assertEquals("microsphere.spring.cloud.", MICROSPHERE_SPRING_CLOUD_PROPERTY_NAME_PREFIX);
+ assertEquals("microsphere.spring.cloud.web.mvc.", MICROSPHERE_SPRING_CLOUD_WEB_MVC_PROPERTY_NAME_PREFIX);
+ assertEquals("microsphere.spring.cloud.multiple-registration.enabled", MULTIPLE_REGISTRATION_ENABLED_PROPERTY_NAME);
+ assertEquals("microsphere.spring.cloud.default-registration.type", MULTIPLE_REGISTRATION_DEFAULT_REGISTRATION_PROPERTY_NAME);
+ assertEquals("microsphere.spring.cloud.default-service-registry.type", MULTIPLE_REGISTRATION_DEFAULT_REGISTRY_PROPERTY_NAME);
+ assertEquals("microsphere.spring.cloud.composite-registration.enabled", COMPOSITE_REGISTRATION_ENABLED_PROPERTY_NAME);
+ }
+}
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/constants/SpringCloudPropertyConstantsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/constants/SpringCloudPropertyConstantsTest.java
new file mode 100644
index 00000000..41b9a193
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/commons/constants/SpringCloudPropertyConstantsTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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 io.microsphere.spring.cloud.commons.constants;
+
+
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.FEATURES_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.LOAD_BALANCER_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.SERVICE_REGISTRY_PROPERTY_PREFIX;
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.SPRING_CLOUD_PROPERTY_PREFIX;
+import static io.microsphere.spring.cloud.commons.constants.SpringCloudPropertyConstants.UTIL_ENABLED_PROPERTY_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link SpringCloudPropertyConstants} Test
+ *
+ * @author Mercy
+ * @see SpringCloudPropertyConstants
+ * @since 1.0.0
+ */
+class SpringCloudPropertyConstantsTest {
+
+ @Test
+ void testConstants() {
+ assertEquals("spring.cloud.", SPRING_CLOUD_PROPERTY_PREFIX);
+ assertEquals("spring.cloud.service-registry.", SERVICE_REGISTRY_PROPERTY_PREFIX);
+ assertEquals("spring.cloud.service-registry.auto-registration.enabled", SERVICE_REGISTRY_AUTO_REGISTRATION_ENABLED_PROPERTY_NAME);
+ assertEquals("spring.cloud.features.enabled", FEATURES_ENABLED_PROPERTY_NAME);
+ assertEquals("spring.cloud.loadbalancer.enabled", LOAD_BALANCER_ENABLED_PROPERTY_NAME);
+ assertEquals("spring.cloud.util.enabled", UTIL_ENABLED_PROPERTY_NAME);
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstantsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstantsTest.java
new file mode 100644
index 00000000..89c83ca7
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/constants/FaultTolerancePropertyConstantsTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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 io.microsphere.spring.cloud.fault.tolerance.constants;
+
+
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.DEFAULT_WARMUP_TIME_PROPERTY_VALUE;
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.DEFAULT_WEIGHT_PROPERTY_VALUE;
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.FAULT_TOLERANCE_PROPERTY_NAME_PREFIX;
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.LOAD_BALANCER_PROPERTY_PREFIX;
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.WARMUP_TIME_PROPERTY_NAME;
+import static io.microsphere.spring.cloud.fault.tolerance.constants.FaultTolerancePropertyConstants.WEIGHT_PROPERTY_NAME;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link FaultTolerancePropertyConstants} Test
+ *
+ * @author Mercy
+ * @see FaultTolerancePropertyConstants
+ * @since 1.0.0
+ */
+class FaultTolerancePropertyConstantsTest {
+
+ @Test
+ void testConstants() {
+ assertEquals("microsphere.spring.cloud.fault-tolerance.", FAULT_TOLERANCE_PROPERTY_NAME_PREFIX);
+ assertEquals("microsphere.spring.cloud.fault-tolerance.load-balancer.", LOAD_BALANCER_PROPERTY_PREFIX);
+ assertEquals("microsphere.spring.cloud.fault-tolerance.warmup-time", WARMUP_TIME_PROPERTY_NAME);
+ assertEquals("microsphere.spring.cloud.fault-tolerance.weight", WEIGHT_PROPERTY_NAME);
+ assertEquals(600000, DEFAULT_WARMUP_TIME_PROPERTY_VALUE);
+ assertEquals(100, DEFAULT_WEIGHT_PROPERTY_VALUE);
+ }
+
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobinTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobinTest.java
new file mode 100644
index 00000000..1710392f
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/WeightedRoundRobinTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 io.microsphere.spring.cloud.fault.tolerance.loadbalancer;
+
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.text.FormatUtils.format;
+import static java.lang.System.currentTimeMillis;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link WeightedRoundRobin} Test
+ *
+ * @author Mercy
+ * @see WeightedRoundRobin
+ * @since 1.0.0
+ */
+class WeightedRoundRobinTest {
+
+ private WeightedRoundRobin weightedRoundRobin;
+
+ @BeforeEach
+ void setUp() {
+ this.weightedRoundRobin = new WeightedRoundRobin("test-id");
+ }
+
+ @Test
+ void testGetId() {
+ assertEquals("test-id", weightedRoundRobin.getId());
+ }
+
+ @Test
+ void testWeight() {
+ this.weightedRoundRobin.setWeight(10);
+ assertEquals(10, this.weightedRoundRobin.getWeight());
+ }
+
+ @Test
+ void testIncreaseCurrent() {
+ this.weightedRoundRobin.setWeight(10);
+ assertEquals(10L, this.weightedRoundRobin.increaseCurrent());
+ assertEquals(20L, this.weightedRoundRobin.increaseCurrent());
+ assertEquals(30L, this.weightedRoundRobin.increaseCurrent());
+ }
+
+ @Test
+ void testSel() {
+ this.weightedRoundRobin.sel(10);
+ assertEquals(-10L, this.weightedRoundRobin.current.longValue());
+ }
+
+ @Test
+ void testLastUpdate() {
+ assertEquals(0L, this.weightedRoundRobin.getLastUpdate());
+ long now = currentTimeMillis();
+ this.weightedRoundRobin.setLastUpdate(now);
+ assertEquals(now, this.weightedRoundRobin.getLastUpdate());
+ }
+
+ @Test
+ void testToString() {
+ this.weightedRoundRobin.setWeight(10);
+ this.weightedRoundRobin.setLastUpdate(currentTimeMillis());
+ assertEquals(format("WeightedRoundRobin[id='{}', weight={}, current={}, lastUpdate={}]",
+ this.weightedRoundRobin.getId(),
+ this.weightedRoundRobin.getWeight(),
+ this.weightedRoundRobin.current,
+ this.weightedRoundRobin.getLastUpdate()),
+ this.weightedRoundRobin.toString());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtilsTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtilsTest.java
new file mode 100644
index 00000000..8c32d4e8
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/loadbalancer/util/LoadBalancerUtilsTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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 io.microsphere.spring.cloud.fault.tolerance.loadbalancer.util;
+
+
+import org.junit.jupiter.api.Test;
+
+import static io.microsphere.spring.cloud.fault.tolerance.loadbalancer.util.LoadBalancerUtils.calculateWarmupWeight;
+import static java.lang.System.currentTimeMillis;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link LoadBalancerUtils} Test
+ *
+ * @author Mercy
+ * @see LoadBalancerUtils
+ * @since 1.0.0
+ */
+class LoadBalancerUtilsTest {
+
+ @Test
+ void testCalculateWarmupWeight() throws InterruptedException {
+ long uptime = currentTimeMillis();
+ int weight = 10;
+ assertTrue(calculateWarmupWeight(uptime, uptime, weight) > 1);
+ assertTrue(calculateWarmupWeight(uptime, 1, weight) == 1);
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfigurationTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfigurationTest.java
index 320338a0..a5ab57d9 100644
--- a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfigurationTest.java
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/autoconfigure/TomcatFaultToleranceAutoConfigurationTest.java
@@ -25,10 +25,16 @@
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.context.WebServerApplicationContext;
+import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
+import org.springframework.boot.web.server.WebServer;
+import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
+import org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent;
import org.springframework.cloud.context.environment.EnvironmentManager;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* {@link TomcatFaultToleranceAutoConfiguration} Test
@@ -36,11 +42,9 @@
* @author Mercy
* @since 1.0.0
*/
-@SpringBootTest(classes = {
- TomcatFaultToleranceAutoConfigurationTest.class},
- webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@SpringBootTest(classes = TomcatFaultToleranceAutoConfigurationTest.class, webEnvironment = RANDOM_PORT)
@EnableAutoConfiguration
-public class TomcatFaultToleranceAutoConfigurationTest {
+class TomcatFaultToleranceAutoConfigurationTest {
@Autowired
private EnvironmentManager environmentManager;
@@ -48,6 +52,11 @@ public class TomcatFaultToleranceAutoConfigurationTest {
@Autowired
private WebServerApplicationContext context;
+ @Autowired
+ private TomcatFaultToleranceAutoConfiguration configuration;
+
+ private TomcatWebServer tomcatWebServer;
+
private Tomcat tomcat;
private Connector connector;
@@ -55,15 +64,15 @@ public class TomcatFaultToleranceAutoConfigurationTest {
private AbstractHttp11Protocol protocol;
@BeforeEach
- public void before() {
- TomcatWebServer tomcatWebServer = (TomcatWebServer) context.getWebServer();
- this.tomcat = tomcatWebServer.getTomcat();
+ void before() {
+ this.tomcatWebServer = (TomcatWebServer) context.getWebServer();
+ this.tomcat = this.tomcatWebServer.getTomcat();
this.connector = tomcat.getConnector();
this.protocol = (AbstractHttp11Protocol) connector.getProtocolHandler();
}
@Test
- public void testMinSpareThreads() {
+ void testMinSpareThreads() {
// default
assertEquals(10, protocol.getMinSpareThreads());
// changed
@@ -72,7 +81,7 @@ public void testMinSpareThreads() {
}
@Test
- public void testMaxThreads() {
+ void testMaxThreads() {
// default
assertEquals(200, protocol.getMaxThreads());
// changed
@@ -81,7 +90,7 @@ public void testMaxThreads() {
}
@Test
- public void testAcceptCount() {
+ void testAcceptCount() {
// default
assertEquals(100, protocol.getAcceptCount());
// changed
@@ -90,7 +99,7 @@ public void testAcceptCount() {
}
@Test
- public void testConnectionTimeout() {
+ void testConnectionTimeout() {
// default
assertEquals(60000, protocol.getConnectionTimeout());
// changed
@@ -99,7 +108,7 @@ public void testConnectionTimeout() {
}
@Test
- public void testMaxConnections() {
+ void testMaxConnections() {
// default
assertEquals(8192, protocol.getMaxConnections());
// changed
@@ -108,16 +117,16 @@ public void testMaxConnections() {
}
@Test
- public void testMaxHttpHeaderSize() {
+ void testMaxHttpHeaderSize() {
// default
assertEquals(8192, protocol.getMaxHttpHeaderSize());
// changed
- environmentManager.setProperty("server.max-http-header-size", "5120");
+ environmentManager.setProperty("server.max-http-request-header-size", "5120");
assertEquals(5120, protocol.getMaxHttpHeaderSize());
}
@Test
- public void testMaxSwallowSize() {
+ void testMaxSwallowSize() {
// default
assertEquals(1024 * 1024 * 2, protocol.getMaxSwallowSize());
// changed
@@ -126,11 +135,30 @@ public void testMaxSwallowSize() {
}
@Test
- public void testMaxHttpFormPostSize() {
+ void testMaxHttpFormPostSize() {
// default
assertEquals(1024 * 1024 * 2, connector.getMaxPostSize());
// changed
environmentManager.setProperty("server.tomcat.max-http-form-post-size", "10240");
assertEquals(10240, connector.getMaxPostSize());
}
+
+ @Test
+ void testOnWebServerInitializedEventWithWebServerInitializedEvent() {
+ WebServerInitializedEvent event = new WebServerInitializedEvent(this.tomcatWebServer) {
+ @Override
+ public WebServerApplicationContext getApplicationContext() {
+ return null;
+ }
+ };
+ configuration.onWebServerInitializedEvent(event);
+ }
+
+ @Test
+ void testOnWebServerInitializedEventWithNonTomcatWebServer() {
+ ServletWebServerApplicationContext context = (ServletWebServerApplicationContext) this.context;
+ WebServer webServer = mock(WebServer.class);
+ WebServerInitializedEvent event = new ServletWebServerInitializedEvent(webServer, context);
+ configuration.onWebServerInitializedEvent(event);
+ }
}
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListenerTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListenerTest.java
new file mode 100644
index 00000000..f2e67d85
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/fault/tolerance/tomcat/event/TomcatDynamicConfigurationListenerTest.java
@@ -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.
+ */
+
+package io.microsphere.spring.cloud.fault.tolerance.tomcat.event;
+
+
+import io.microsphere.spring.test.junit.jupiter.SpringLoggingTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.web.ServerProperties;
+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
+import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
+import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.mock.env.MockEnvironment;
+
+import static io.microsphere.collection.Sets.ofSet;
+
+/**
+ * {@link TomcatDynamicConfigurationListener} Testt
+ *
+ * @author Mercy
+ * @see TomcatDynamicConfigurationListener
+ * @since 1.0.0
+ */
+@SpringLoggingTest
+class TomcatDynamicConfigurationListenerTest {
+
+ private ServerProperties serverProperties;
+
+ private MockEnvironment environment;
+
+ private ConfigurableApplicationContext context;
+
+ private TomcatDynamicConfigurationListener listener;
+
+ @BeforeEach
+ void setUp() {
+ TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
+ TomcatWebServer tomcatWebServer = (TomcatWebServer) factory.getWebServer();
+ this.serverProperties = new ServerProperties();
+ this.environment = new MockEnvironment();
+ this.environment.setProperty("server.port", "8080");
+ this.context = new GenericApplicationContext();
+ this.context.setEnvironment(this.environment);
+ this.context.refresh();
+ this.listener = new TomcatDynamicConfigurationListener(tomcatWebServer, this.serverProperties, this.context);
+ }
+
+ @Test
+ void testOnApplicationEventOnDifferentEventSource() {
+ EnvironmentChangeEvent event = new EnvironmentChangeEvent(ofSet("test-key"));
+ this.listener.onApplicationEvent(event);
+ }
+
+ @Test
+ void testOnApplicationEventOnEmptyKeys() {
+ EnvironmentChangeEvent event = new EnvironmentChangeEvent(this.context, ofSet());
+ this.listener.onApplicationEvent(event);
+ }
+
+ @Test
+ void testOnApplicationEvent() {
+ String key = "server.tomcat.threads.max";
+ this.environment.setProperty(key, "100");
+ EnvironmentChangeEvent event = new EnvironmentChangeEvent(this.context, ofSet(key));
+ this.listener.onApplicationEvent(event);
+ }
+
+ @Test
+ void testConfigureProtocolWithNull() {
+ this.listener.configureProtocol(this.serverProperties, null);
+ }
+
+ @Test
+ void testConfigureHttp11ProtocolWithNull() {
+ this.listener.configureHttp11Protocol(this.serverProperties, null, null);
+ }
+
+ @Test
+ void testIsPositiveWithNegative() {
+ this.listener.isPositive(-1);
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/loadbalancer/condition/ConditionalOnLoadBalancerEnabledTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/loadbalancer/condition/ConditionalOnLoadBalancerEnabledTest.java
new file mode 100644
index 00000000..d251003f
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/loadbalancer/condition/ConditionalOnLoadBalancerEnabledTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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 io.microsphere.spring.cloud.loadbalancer.condition;
+
+import io.microsphere.spring.cloud.test.ConditionalOnPropertyEnabledTest;
+
+/**
+ * {@link ConditionalOnLoadBalancerEnabled @ConditionalOnLoadBalancerEnabled} Test
+ *
+ * @author Mercy
+ * @see ConditionalOnLoadBalancerEnabled
+ * @since 1.0.0
+ */
+@ConditionalOnLoadBalancerEnabled
+public class ConditionalOnLoadBalancerEnabledTest extends ConditionalOnPropertyEnabledTest {
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/test/ConditionalOnPropertyEnabledTest.java b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/test/ConditionalOnPropertyEnabledTest.java
new file mode 100644
index 00000000..b5b1aa7c
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/java/io/microsphere/spring/cloud/test/ConditionalOnPropertyEnabledTest.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 io.microsphere.spring.cloud.test;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.type.AnnotationMetadata;
+
+import java.util.Properties;
+import java.util.Set;
+
+import static io.microsphere.collection.SetUtils.newLinkedHashSet;
+import static io.microsphere.spring.beans.BeanUtils.isBeanPresent;
+import static io.microsphere.spring.core.annotation.AnnotationUtils.getAnnotationAttributes;
+import static io.microsphere.spring.test.util.SpringTestUtils.testInSpringContainer;
+import static io.microsphere.util.ArrayUtils.isEmpty;
+import static io.microsphere.util.ArrayUtils.length;
+import static java.lang.String.valueOf;
+import static java.lang.System.getProperties;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.springframework.core.type.AnnotationMetadata.introspect;
+import static org.springframework.util.StringUtils.hasText;
+
+/**
+ * Abstract test class for {@link ConditionalOnProperty @ConditionalOnProperty} On Enabled
+ *
+ * @author Mercy
+ * @see ConditionalOnProperty
+ * @since 1.0.0
+ */
+public abstract class ConditionalOnPropertyEnabledTest {
+
+ private AnnotationAttributes annotationAttributes;
+
+ @BeforeEach
+ void setUp() {
+ AnnotationMetadata annotationMetadata = introspect(getClass());
+ this.annotationAttributes = getAnnotationAttributes(annotationMetadata, ConditionalOnProperty.class);
+ }
+
+ @Test
+ void testConditionalOnPropertyEnabled() {
+ if (matchIfMissing()) {
+ testBean(true);
+ } else {
+ testConditionalOnPropertyEnabled(true);
+ }
+ }
+
+ @Test
+ void testConditionalOnPropertyDisabled() {
+ testConditionalOnPropertyEnabled(false);
+ }
+
+ /**
+ * Whether match if missing
+ *
+ * @return {@code true} if match if missing
+ */
+ protected boolean matchIfMissing() {
+ return this.annotationAttributes.getBoolean("matchIfMissing");
+ }
+
+ /**
+ * Get the property names of the {@link Conditional @Conditional}
+ *
+ * @return property names
+ */
+ protected Set getPropertyNames() {
+ String prefix = this.annotationAttributes.getString("prefix");
+ String[] names = this.annotationAttributes.getStringArray("name");
+ if (isEmpty(names)) {
+ names = this.annotationAttributes.getStringArray("value");
+ }
+ boolean hasPrefix = hasText(prefix);
+ Set propertyNames = newLinkedHashSet(length(names));
+ for (String name : names) {
+ String propertyName = hasPrefix ? prefix + name : name;
+ propertyNames.add(propertyName);
+ }
+ return propertyNames;
+ }
+
+ protected void testConditionalOnPropertyEnabled(boolean enabled) {
+ Set propertyNames = getPropertyNames();
+ Properties properties = getProperties();
+ try {
+ for (String propertyName : propertyNames) {
+ properties.setProperty(propertyName, valueOf(enabled));
+ }
+ testBean(enabled);
+ } finally {
+ for (String propertyName : propertyNames) {
+ properties.remove(propertyName);
+ }
+ }
+ }
+
+ protected void testBean(boolean present) {
+ testInSpringContainer(context -> {
+ assertEquals(present, isBeanPresent(context, getClass()));
+ }, getClass());
+ }
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/resources/application.yaml b/microsphere-spring-cloud-commons/src/test/resources/META-INF/config/default/test.yaml
similarity index 80%
rename from microsphere-spring-cloud-commons/src/test/resources/application.yaml
rename to microsphere-spring-cloud-commons/src/test/resources/META-INF/config/default/test.yaml
index db6965c0..943d64ad 100644
--- a/microsphere-spring-cloud-commons/src/test/resources/application.yaml
+++ b/microsphere-spring-cloud-commons/src/test/resources/META-INF/config/default/test.yaml
@@ -29,7 +29,9 @@ eureka:
---
spring:
- profiles: nacos
+ config:
+ activate:
+ on-profile: nacos
cloud:
nacos:
@@ -44,7 +46,9 @@ spring:
---
spring:
- profiles: eureka
+ config:
+ activate:
+ on-profile: eureka
eureka:
client:
@@ -55,7 +59,9 @@ eureka:
---
spring:
- profiles: zookeeper
+ config:
+ activate:
+ on-profile: zookeeper
cloud:
zookeeper:
enabled: true
@@ -64,7 +70,9 @@ spring:
---
spring:
- profiles: consul
+ config:
+ activate:
+ on-profile: consul
cloud:
consul:
@@ -75,7 +83,9 @@ spring:
---
spring:
- profiles: kubernetes
+ config:
+ activate:
+ on-profile: kubernetes
cloud:
kubernetes:
diff --git a/microsphere-spring-cloud-commons/src/test/resources/META-INF/docker/service-registry-servers.yml b/microsphere-spring-cloud-commons/src/test/resources/META-INF/docker/service-registry-servers.yml
new file mode 100644
index 00000000..8f34de59
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/resources/META-INF/docker/service-registry-servers.yml
@@ -0,0 +1,25 @@
+services:
+ zookeeper:
+ image: zookeeper
+ restart: always
+ ports:
+ - "2181:2181"
+
+ consul:
+ image: hashicorp/consul
+ ports:
+ - "8500:8500"
+
+ nacos:
+ image: nacos/nacos-server:v2.5.1
+ ports:
+ - "8848:8848"
+ environment:
+ - MODE=standalone
+
+ eureka:
+ image: aliaksandrvoitko/eureka-server
+ ports:
+ - "8761:8761"
+ environment:
+ - SERVER_PORT=8761
\ No newline at end of file
diff --git a/microsphere-spring-cloud-commons/src/test/resources/logback-test.xml b/microsphere-spring-cloud-commons/src/test/resources/logback-test.xml
new file mode 100644
index 00000000..0d11a9ed
--- /dev/null
+++ b/microsphere-spring-cloud-commons/src/test/resources/logback-test.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ ${ENCODER_PATTERN}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/microsphere-spring-cloud-openfeign/pom.xml b/microsphere-spring-cloud-openfeign/pom.xml
index be05c2cc..556c28bf 100644
--- a/microsphere-spring-cloud-openfeign/pom.xml
+++ b/microsphere-spring-cloud-openfeign/pom.xml
@@ -50,16 +50,18 @@
true
+
org.springframework.boot
spring-boot-starter-test
test
+
- org.springframework.cloud
- spring-cloud-starter-loadbalancer
- true
+ io.github.microsphere-projects
+ microsphere-spring-test
+ test
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/EnableFeignAutoRefresh.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/EnableFeignAutoRefresh.java
index 1587b120..efe2cc14 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/EnableFeignAutoRefresh.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/EnableFeignAutoRefresh.java
@@ -3,11 +3,13 @@
import org.springframework.context.annotation.Import;
import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
/**
* Enable Feign Auto Refresh
*
@@ -16,10 +18,11 @@
* @see FeignClientAutoRefreshAutoConfiguration
* @since 0.0.1
*/
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.TYPE})
+@Retention(RUNTIME)
+@Target(TYPE)
@Documented
-@Import(FeignClientAutoRefreshAutoConfiguration.class)
+@Inherited
+@Import(EnableFeignAutoRefresh.Marker.class)
public @interface EnableFeignAutoRefresh {
class Marker {
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientAutoRefreshAutoConfiguration.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientAutoRefreshAutoConfiguration.java
index 93c5e366..8d1fe2c5 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientAutoRefreshAutoConfiguration.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientAutoRefreshAutoConfiguration.java
@@ -1,15 +1,19 @@
package io.microsphere.spring.cloud.openfeign.autoconfigure;
+import io.microsphere.spring.cloud.openfeign.autoconfigure.EnableFeignAutoRefresh.Marker;
import io.microsphere.spring.cloud.openfeign.autorefresh.FeignClientConfigurationChangedListener;
import io.microsphere.spring.cloud.openfeign.autorefresh.FeignComponentRegistry;
import io.microsphere.spring.cloud.openfeign.components.NoOpRequestInterceptor;
import org.springframework.beans.factory.BeanFactory;
-import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.openfeign.FeignBuilderCustomizer;
import org.springframework.cloud.openfeign.FeignClientProperties;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
+import org.springframework.context.event.EventListener;
+
+import static io.microsphere.spring.cloud.openfeign.components.NoOpRequestInterceptor.INSTANCE;
/**
* The Auto-Configuration class for {@link EnableFeignAutoRefresh}
@@ -19,30 +23,89 @@
* @see EnableFeignAutoRefresh
* @since 0.0.1
*/
-@ConditionalOnBean(EnableFeignAutoRefresh.Marker.class)
-@AutoConfigureAfter(ConfigurationPropertiesRebinderAutoConfiguration.class)
+@ConditionalOnBean(Marker.class)
public class FeignClientAutoRefreshAutoConfiguration {
+ /**
+ * Creates a {@link FeignBuilderCustomizer} that adds the {@link NoOpRequestInterceptor}
+ * as a default request interceptor to every Feign client builder.
+ *
+ * Example Usage:
+ *
{@code
+ * // Automatically registered as a Spring bean; customizes every Feign builder
+ * FeignBuilderCustomizer customizer = addDefaultRequestInterceptorCustomizer();
+ * }
+ *
+ * @return a {@link FeignBuilderCustomizer} that adds the {@link NoOpRequestInterceptor}
+ */
@Bean
public FeignBuilderCustomizer addDefaultRequestInterceptorCustomizer() {
return builder -> {
- builder.requestInterceptor(NoOpRequestInterceptor.INSTANCE);
+ builder.requestInterceptor(INSTANCE);
};
}
- @Bean
- public FeignClientConfigurationChangedListener feignClientConfigurationChangedListener(FeignComponentRegistry registry) {
- return new FeignClientConfigurationChangedListener(registry);
+ /**
+ * Handles the {@link ApplicationReadyEvent} to register the
+ * {@link FeignClientConfigurationChangedListener} after the application is fully initialized.
+ *
+ * Example Usage:
+ *
{@code
+ * // Invoked automatically by the Spring event system on application ready
+ * onApplicationReadyEvent(applicationReadyEvent);
+ * }
+ *
+ * @param event the {@link ApplicationReadyEvent} fired when the application is ready
+ */
+ @EventListener(ApplicationReadyEvent.class)
+ public void onApplicationReadyEvent(ApplicationReadyEvent event) {
+ /**
+ * Make sure the FeignClientConfigurationChangedListener is registered after the ConfigurationPropertiesRebinder
+ */
+ registerFeignClientConfigurationChangedListener(event);
}
+ /**
+ * Creates the {@link FeignComponentRegistry} bean that tracks decorated Feign components
+ * and supports auto-refresh when configuration properties change.
+ *
+ * Example Usage:
+ *
{@code
+ * // Automatically registered as a Spring bean
+ * FeignComponentRegistry registry = feignClientRegistry(clientProperties, beanFactory);
+ * }
+ *
+ * @param clientProperties the {@link FeignClientProperties} providing the default config name
+ * @param beanFactory the {@link BeanFactory} used for component instantiation
+ * @return a new {@link FeignComponentRegistry} instance
+ */
@Bean
public FeignComponentRegistry feignClientRegistry(FeignClientProperties clientProperties, BeanFactory beanFactory) {
return new FeignComponentRegistry(clientProperties.getDefaultConfig(), beanFactory);
}
+ /**
+ * Creates the {@link FeignClientSpecificationPostProcessor} bean that injects
+ * the {@link io.microsphere.spring.cloud.openfeign.autorefresh.AutoRefreshCapability}
+ * into default Feign client specifications.
+ *
+ * Example Usage:
+ *
{@code
+ * // Automatically registered as a Spring bean
+ * FeignClientSpecificationPostProcessor processor = feignClientSpecificationPostProcessor();
+ * }
+ *
+ * @return a new {@link FeignClientSpecificationPostProcessor} instance
+ */
@Bean
public FeignClientSpecificationPostProcessor feignClientSpecificationPostProcessor() {
return new FeignClientSpecificationPostProcessor();
}
+ private void registerFeignClientConfigurationChangedListener(ApplicationReadyEvent event) {
+ ConfigurableApplicationContext context = event.getApplicationContext();
+ FeignComponentRegistry feignComponentRegistry = context.getBean(FeignComponentRegistry.class);
+ context.addApplicationListener(new FeignClientConfigurationChangedListener(feignComponentRegistry));
+ }
+
}
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientSpecificationPostProcessor.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientSpecificationPostProcessor.java
index 52eb071b..1ae0ec5e 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientSpecificationPostProcessor.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autoconfigure/FeignClientSpecificationPostProcessor.java
@@ -4,18 +4,16 @@
import io.microsphere.spring.cloud.openfeign.autorefresh.AutoRefreshCapability;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
-import org.springframework.cloud.context.named.NamedContextFactory;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
+import org.springframework.cloud.openfeign.FeignClientSpecification;
import static io.microsphere.logging.LoggerFactory.getLogger;
-import static io.microsphere.reflect.MethodUtils.findMethod;
+import static io.microsphere.util.ArrayUtils.arrayToString;
import static io.microsphere.util.ArrayUtils.combine;
import static org.springframework.aop.support.AopUtils.getTargetClass;
-import static org.springframework.util.ClassUtils.resolveClassName;
/**
+ * {@link BeanPostProcessor} for {@link FeignClientSpecification}
+ *
* @author 韩超
* @author Mercy
* @see org.springframework.cloud.openfeign.FeignClientSpecification
@@ -25,37 +23,41 @@ public class FeignClientSpecificationPostProcessor implements BeanPostProcessor
private static final Logger logger = getLogger(FeignClientSpecificationPostProcessor.class);
- private static final Class> AUTO_REFRESH_CAPABILITY_CLASS = AutoRefreshCapability.class;
-
- private static final String FEIGN_CLIENT_SPECIFICATION_CLASS_NAME = "org.springframework.cloud.openfeign.FeignClientSpecification";
-
- private static final Class> FEIGN_CLIENT_SPECIFICATION_CLASS = resolveClassName(FEIGN_CLIENT_SPECIFICATION_CLASS_NAME, null);
-
- private static final Method setConfigurationMethod = findMethod(FEIGN_CLIENT_SPECIFICATION_CLASS, "setConfiguration", Class[].class);
-
+ static final Class> AUTO_REFRESH_CAPABILITY_CLASS = AutoRefreshCapability.class;
+
+ static final Class> FEIGN_CLIENT_SPECIFICATION_CLASS = FeignClientSpecification.class;
+
+ /**
+ * Injects the {@link AutoRefreshCapability} into default {@link FeignClientSpecification}
+ * beans after initialization.
+ *
+ * Example Usage:
+ *
{@code
+ * // Invoked automatically by the Spring container during bean post-processing
+ * Object processed = postProcessAfterInitialization(bean, "default.my-client");
+ * }
+ *
+ * @param bean the bean instance that has been initialized
+ * @param beanName the name of the bean in the Spring context
+ * @return the (possibly modified) bean instance
+ * @throws BeansException if post-processing fails
+ */
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class> beanType = getTargetClass(bean);
if (FEIGN_CLIENT_SPECIFICATION_CLASS.isAssignableFrom(beanType) && beanName.startsWith("default")) {
- injectAutoRefreshCapability((NamedContextFactory.Specification) bean);
+ injectAutoRefreshCapability((FeignClientSpecification) bean);
}
return bean;
}
- private void injectAutoRefreshCapability(NamedContextFactory.Specification defaultSpecification) {
- if (setConfigurationMethod != null) {
- Class>[] originConfigurationClasses = defaultSpecification.getConfiguration();
- Class>[] newConfigurationClasses = combine(AUTO_REFRESH_CAPABILITY_CLASS, originConfigurationClasses);
- Object arg = newConfigurationClasses;
- try {
- setConfigurationMethod.setAccessible(true);
- setConfigurationMethod.invoke(defaultSpecification, arg);
- } catch (Throwable e) {
- if (logger.isWarnEnabled()) {
- logger.warn("FeignClientSpecification#setConfiguration(Class[]) can't be invoked , instance : {} , args : {}",
- defaultSpecification, Arrays.toString(newConfigurationClasses));
- }
- }
+ void injectAutoRefreshCapability(FeignClientSpecification specification) {
+ Class>[] originConfigurationClasses = specification.getConfiguration();
+ Class>[] newConfigurationClasses = combine(AUTO_REFRESH_CAPABILITY_CLASS, originConfigurationClasses);
+ specification.setConfiguration(newConfigurationClasses);
+ if (logger.isTraceEnabled()) {
+ logger.trace("The Configuration classes: before - {} , after - {}", arrayToString(originConfigurationClasses),
+ arrayToString(newConfigurationClasses));
}
}
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/AutoRefreshCapability.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/AutoRefreshCapability.java
index 0e29d0b2..d9dc7ddd 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/AutoRefreshCapability.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/AutoRefreshCapability.java
@@ -12,111 +12,239 @@
import io.microsphere.spring.cloud.openfeign.components.DecoratedDecoder;
import io.microsphere.spring.cloud.openfeign.components.DecoratedEncoder;
import io.microsphere.spring.cloud.openfeign.components.DecoratedErrorDecoder;
-import io.microsphere.spring.cloud.openfeign.components.DecoratedFeignComponent;
import io.microsphere.spring.cloud.openfeign.components.DecoratedQueryMapEncoder;
import io.microsphere.spring.cloud.openfeign.components.DecoratedRetryer;
import org.springframework.beans.BeansException;
+import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.cloud.openfeign.FeignClientProperties;
-import org.springframework.cloud.openfeign.FeignContext;
+import org.springframework.cloud.openfeign.FeignClientSpecification;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
+import static io.microsphere.spring.cloud.openfeign.components.DecoratedFeignComponent.instantiate;
+
/**
* @author 韩超
+ * @author Mercy
* @since 0.0.1
*/
public class AutoRefreshCapability implements Capability, ApplicationContextAware {
private final FeignComponentRegistry componentRegistry;
- private final FeignContext feignContext;
+
+ private final NamedContextFactory contextFactory;
+
private final FeignClientProperties clientProperties;
private String contextId;
- private static final String CONTEXT_ID_PROPERTY_NAME = "feign.client.name";
-
- public AutoRefreshCapability(FeignClientProperties clientProperties, FeignContext feignContext, FeignComponentRegistry componentRegistry) {
+ /**
+ * Constructs an {@link AutoRefreshCapability} with the required dependencies.
+ *
+ * Example Usage:
+ *
{@code
+ * AutoRefreshCapability capability = new AutoRefreshCapability(
+ * clientProperties, contextFactory, componentRegistry);
+ * }
+ *
+ * @param clientProperties the {@link FeignClientProperties} providing Feign client configuration
+ * @param contextFactory the {@link NamedContextFactory} for resolving per-client contexts
+ * @param componentRegistry the {@link FeignComponentRegistry} to register decorated components
+ */
+ public AutoRefreshCapability(FeignClientProperties clientProperties,
+ NamedContextFactory contextFactory,
+ FeignComponentRegistry componentRegistry) {
this.clientProperties = clientProperties;
- this.feignContext = feignContext;
+ this.contextFactory = contextFactory;
this.componentRegistry = componentRegistry;
}
+ /**
+ * Sets the {@link ApplicationContext} and extracts the Feign client context ID
+ * from the {@code spring.cloud.openfeign.client.name} property.
+ *
+ * Example Usage:
+ *
{@code
+ * AutoRefreshCapability capability = new AutoRefreshCapability(props, factory, registry);
+ * capability.setApplicationContext(applicationContext);
+ * }
+ *
+ * @param applicationContext the {@link ApplicationContext} for this Feign client
+ * @throws BeansException if the context cannot be set
+ */
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
- this.contextId = applicationContext.getEnvironment().getProperty(CONTEXT_ID_PROPERTY_NAME);
+ this.contextId = applicationContext.getEnvironment().getProperty("spring.cloud.openfeign.client.name");
}
-
+ /**
+ * Enriches the given {@link Retryer} by wrapping it in a {@link DecoratedRetryer}
+ * that supports auto-refresh on configuration changes.
+ *
+ * Example Usage:
+ *
{@code
+ * Retryer original = new Retryer.Default();
+ * Retryer enriched = capability.enrich(original);
+ * }
+ *
+ * @param retryer the original {@link Retryer} to enrich, or {@code null}
+ * @return the decorated {@link Retryer}, or {@code null} if the input is {@code null}
+ */
@Override
public Retryer enrich(Retryer retryer) {
- if (retryer == null)
+ if (retryer == null) {
return null;
+ }
- DecoratedRetryer decoratedRetryer = DecoratedFeignComponent.instantiate(DecoratedRetryer.class, Retryer.class,
- contextId, feignContext, clientProperties, retryer);
-
+ DecoratedRetryer decoratedRetryer = instantiate(DecoratedRetryer.class, Retryer.class,
+ contextId, contextFactory, clientProperties, retryer);
this.componentRegistry.register(contextId, decoratedRetryer);
return decoratedRetryer;
}
+ /**
+ * Enriches the given {@link Contract} by wrapping it in a {@link DecoratedContract}
+ * that supports auto-refresh on configuration changes.
+ *
+ * Example Usage:
+ *
{@code
+ * Contract original = new Contract.Default();
+ * Contract enriched = capability.enrich(original);
+ * }
+ *
+ * @param contract the original {@link Contract} to enrich, or {@code null}
+ * @return the decorated {@link Contract}, or {@code null} if the input is {@code null}
+ */
@Override
public Contract enrich(Contract contract) {
- if (contract == null)
+ if (contract == null) {
return null;
+ }
- DecoratedContract decoratedContract = DecoratedFeignComponent.instantiate(DecoratedContract.class, Contract.class,
- contextId, feignContext, clientProperties, contract);
+ DecoratedContract decoratedContract = instantiate(DecoratedContract.class, Contract.class,
+ contextId, contextFactory, clientProperties, contract);
this.componentRegistry.register(contextId, decoratedContract);
return decoratedContract;
}
+ /**
+ * Enriches the given {@link Decoder} by wrapping it in a {@link DecoratedDecoder}
+ * that supports auto-refresh on configuration changes.
+ *
+ * Example Usage:
+ *
{@code
+ * Decoder original = new Decoder.Default();
+ * Decoder enriched = capability.enrich(original);
+ * }
+ *
+ * @param decoder the original {@link Decoder} to enrich, or {@code null}
+ * @return the decorated {@link Decoder}, or {@code null} if the input is {@code null}
+ */
@Override
public Decoder enrich(Decoder decoder) {
- if (decoder == null)
+ if (decoder == null) {
return null;
+ }
- DecoratedDecoder decoratedDecoder = DecoratedFeignComponent.instantiate(DecoratedDecoder.class, Decoder.class,
- contextId, feignContext, clientProperties, decoder);
+ DecoratedDecoder decoratedDecoder = instantiate(DecoratedDecoder.class, Decoder.class,
+ contextId, contextFactory, clientProperties, decoder);
this.componentRegistry.register(contextId, decoratedDecoder);
return decoratedDecoder;
}
+ /**
+ * Enriches the given {@link Encoder} by wrapping it in a {@link DecoratedEncoder}
+ * that supports auto-refresh on configuration changes.
+ *
+ * Example Usage:
+ *
{@code
+ * Encoder original = new Encoder.Default();
+ * Encoder enriched = capability.enrich(original);
+ * }
+ *
+ * @param encoder the original {@link Encoder} to enrich, or {@code null}
+ * @return the decorated {@link Encoder}, or {@code null} if the input is {@code null}
+ */
@Override
public Encoder enrich(Encoder encoder) {
- if (encoder == null)
+ if (encoder == null) {
return null;
+ }
- DecoratedEncoder decoratedEncoder = DecoratedFeignComponent.instantiate(DecoratedEncoder.class, Encoder.class,
- contextId, feignContext, clientProperties, encoder);
+ DecoratedEncoder decoratedEncoder = instantiate(DecoratedEncoder.class, Encoder.class,
+ contextId, contextFactory, clientProperties, encoder);
this.componentRegistry.register(contextId, decoratedEncoder);
return decoratedEncoder;
}
+ /**
+ * Enriches the given {@link ErrorDecoder} by wrapping it in a {@link DecoratedErrorDecoder}
+ * that supports auto-refresh on configuration changes.
+ *
+ * Example Usage:
+ *
{@code
+ * ErrorDecoder original = new ErrorDecoder.Default();
+ * ErrorDecoder enriched = capability.enrich(original);
+ * }
+ *
+ * @param decoder the original {@link ErrorDecoder} to enrich, or {@code null}
+ * @return the decorated {@link ErrorDecoder}, or {@code null} if the input is {@code null}
+ */
public ErrorDecoder enrich(ErrorDecoder decoder) {
- if (decoder == null)
+ if (decoder == null) {
return null;
+ }
- DecoratedErrorDecoder decoratedErrorDecoder = DecoratedFeignComponent.instantiate(DecoratedErrorDecoder.class, ErrorDecoder.class,
- contextId, feignContext, clientProperties, decoder);
+ DecoratedErrorDecoder decoratedErrorDecoder = instantiate(DecoratedErrorDecoder.class, ErrorDecoder.class,
+ contextId, contextFactory, clientProperties, decoder);
this.componentRegistry.register(contextId, decoratedErrorDecoder);
return decoratedErrorDecoder;
}
+ /**
+ * Enriches the given {@link RequestInterceptor} by registering it in the
+ * {@link FeignComponentRegistry} as part of a {@link io.microsphere.spring.cloud.openfeign.components.CompositedRequestInterceptor}.
+ *
+ * Example Usage:
+ *
{@code
+ * RequestInterceptor original = template -> template.header("X-Custom", "value");
+ * RequestInterceptor enriched = capability.enrich(original);
+ * }
+ *
+ * @param requestInterceptor the original {@link RequestInterceptor} to enrich, or {@code null}
+ * @return the composited {@link RequestInterceptor}, or {@code null} if the input is {@code null}
+ */
@Override
public RequestInterceptor enrich(RequestInterceptor requestInterceptor) {
+ if (requestInterceptor == null) {
+ return null;
+ }
return this.componentRegistry.registerRequestInterceptor(contextId, requestInterceptor);
}
+ /**
+ * Enriches the given {@link QueryMapEncoder} by wrapping it in a {@link DecoratedQueryMapEncoder}
+ * that supports auto-refresh on configuration changes.
+ *
+ * Example Usage:
+ *
{@code
+ * QueryMapEncoder original = new QueryMapEncoder.Default();
+ * QueryMapEncoder enriched = capability.enrich(original);
+ * }
+ *
+ * @param queryMapEncoder the original {@link QueryMapEncoder} to enrich, or {@code null}
+ * @return the decorated {@link QueryMapEncoder}, or {@code null} if the input is {@code null}
+ */
@Override
public QueryMapEncoder enrich(QueryMapEncoder queryMapEncoder) {
- if (queryMapEncoder == null)
+ if (queryMapEncoder == null) {
return null;
+ }
- DecoratedQueryMapEncoder decoratedQueryMapEncoder = DecoratedFeignComponent.instantiate(DecoratedQueryMapEncoder.class, QueryMapEncoder.class,
- contextId, feignContext, clientProperties, queryMapEncoder);
+ DecoratedQueryMapEncoder decoratedQueryMapEncoder = instantiate(DecoratedQueryMapEncoder.class, QueryMapEncoder.class,
+ contextId, contextFactory, clientProperties, queryMapEncoder);
this.componentRegistry.register(contextId, decoratedQueryMapEncoder);
return decoratedQueryMapEncoder;
}
-
-}
+}
\ No newline at end of file
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignClientConfigurationChangedListener.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignClientConfigurationChangedListener.java
index c4cede8c..7fae5c63 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignClientConfigurationChangedListener.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignClientConfigurationChangedListener.java
@@ -15,19 +15,57 @@ public class FeignClientConfigurationChangedListener implements ApplicationListe
private final FeignComponentRegistry registry;
+ /**
+ * Constructs a listener that refreshes Feign components when the environment changes.
+ *
+ * Example Usage:
+ *
{@code
+ * FeignComponentRegistry registry = ...;
+ * FeignClientConfigurationChangedListener listener =
+ * new FeignClientConfigurationChangedListener(registry);
+ * }
+ *
+ * @param registry the {@link FeignComponentRegistry} used to refresh affected Feign components
+ */
public FeignClientConfigurationChangedListener(FeignComponentRegistry registry) {
this.registry = registry;
}
- private final String PREFIX = "feign.client.config.";
+ private final String PREFIX = "spring.cloud.openfeign.client.config.";
+ /**
+ * Handles an {@link EnvironmentChangeEvent} by resolving which Feign clients are affected
+ * and triggering a refresh on the corresponding components in the registry.
+ *
+ * Example Usage:
+ *
{@code
+ * // Invoked automatically by the Spring event system
+ * listener.onApplicationEvent(environmentChangeEvent);
+ * }
+ *
+ * @param event the {@link EnvironmentChangeEvent} containing the changed property keys
+ */
@Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
Map> effectiveClients = resolveChangedClient(event);
- effectiveClients.forEach(registry::refresh);
-
+ if (!effectiveClients.isEmpty()) {
+ effectiveClients.forEach(registry::refresh);
+ }
}
+ /**
+ * Resolves which Feign client names and their changed configuration keys are affected
+ * by the given {@link EnvironmentChangeEvent}.
+ *
+ * Example Usage:
+ *
{@code
+ * Map> changed = listener.resolveChangedClient(event);
+ * // e.g. {"my-client" -> {"retryer", "decoder"}}
+ * }
+ *
+ * @param event the {@link EnvironmentChangeEvent} containing the changed property keys
+ * @return a map of client names to their changed configuration sub-keys
+ */
protected Map> resolveChangedClient(EnvironmentChangeEvent event) {
Set keys = event.getKeys();
return keys.stream()
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignComponentRegistry.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignComponentRegistry.java
index ec97a928..4c20fae0 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignComponentRegistry.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/autorefresh/FeignComponentRegistry.java
@@ -8,139 +8,279 @@
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import io.microsphere.spring.cloud.openfeign.components.CompositedRequestInterceptor;
-import io.microsphere.spring.cloud.openfeign.components.NoOpRequestInterceptor;
import io.microsphere.spring.cloud.openfeign.components.Refreshable;
import org.springframework.beans.factory.BeanFactory;
-import org.springframework.util.ObjectUtils;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import static io.microsphere.collection.Lists.ofList;
+import static io.microsphere.collection.Maps.ofMap;
+import static io.microsphere.collection.Sets.ofSet;
+import static io.microsphere.constants.SymbolConstants.LEFT_SQUARE_BRACKET;
+import static io.microsphere.spring.boot.context.properties.source.util.ConfigurationPropertyUtils.toDashedForm;
+import static io.microsphere.spring.cloud.openfeign.components.NoOpRequestInterceptor.INSTANCE;
+import static io.microsphere.util.Assert.assertNoNullElements;
+import static io.microsphere.util.Assert.assertNotBlank;
+import static io.microsphere.util.Assert.assertNotEmpty;
+import static io.microsphere.util.Assert.assertNotNull;
+import static io.microsphere.util.StringUtils.isBlank;
+import static io.microsphere.util.StringUtils.substringBefore;
+
/**
+ * Feign Component Registry
+ *
* @author 韩超
+ * @author Mercy
* @since 0.0.1
*/
public class FeignComponentRegistry {
- private static final Map> configComponentMappings = new HashMap<>(16);
+ private static final Map> configComponentMappings = ofMap(
+ "retryer", Retryer.class,
+ "error-decoder", ErrorDecoder.class,
+ "request-interceptors", RequestInterceptor.class,
+ "default-request-headers", RequestInterceptor.class,
+ "default-query-parameters", RequestInterceptor.class,
+ "decoder", Decoder.class,
+ "encoder", Encoder.class,
+ "contract", Contract.class,
+ "query-map-encoder", QueryMapEncoder.class
+ );
private final Map> refreshableComponents = new ConcurrentHashMap<>(32);
- private final Map interceptorMap = new ConcurrentHashMap<>(32);
- private final String DEFAULT_CLIENT_NAME;
- private final BeanFactory beanFactory;
+ private final Map interceptorsMap = new ConcurrentHashMap<>(32);
- static {
- configComponentMappings.put("retryer", Retryer.class);
- configComponentMappings.put("errorDecoder", ErrorDecoder.class);
- configComponentMappings.put("error-decoder", ErrorDecoder.class);
- configComponentMappings.put("requestInterceptors", RequestInterceptor.class);
- configComponentMappings.put("request-interceptors", RequestInterceptor.class);
- configComponentMappings.put("defaultRequestHeaders", RequestInterceptor.class);
- configComponentMappings.put("default-request-headers", RequestInterceptor.class);
- configComponentMappings.put("defaultQueryParameters", RequestInterceptor.class);
- configComponentMappings.put("default-query-parameters", RequestInterceptor.class);
- configComponentMappings.put("decoder", Decoder.class);
- configComponentMappings.put("encoder", Encoder.class);
- configComponentMappings.put("contract", Contract.class);
- configComponentMappings.put("queryMapEncoder", QueryMapEncoder.class);
- configComponentMappings.put("query-map-encoder", QueryMapEncoder.class);
- }
+ private final String defaultClientName;
+
+ private final BeanFactory beanFactory;
+ /**
+ * Returns the Feign component class corresponding to the given configuration key.
+ *
+ * Example Usage:
+ *
{@code
+ * Class> componentClass = FeignComponentRegistry.getComponentClass("retryer");
+ * // returns Retryer.class
+ * }
+ *
+ * @param config the configuration property key (e.g. {@code "retryer"}, {@code "decoder"})
+ * @return the mapped Feign component {@link Class}, or {@code null} if not found
+ */
protected static Class> getComponentClass(String config) {
- if (ObjectUtils.isEmpty(config))
+ if (isBlank(config)) {
return null;
- //组合
- if (config.endsWith("]")) {
- for (Map.Entry> next : configComponentMappings.entrySet()) {
- if (config.startsWith(next.getKey()))
- return next.getValue();
- }
- } else {
- for (Map.Entry> next : configComponentMappings.entrySet()) {
- if (config.equals(next.getKey()))
- return next.getValue();
- }
}
- return null;
+ String normalizedConfig = normalizeConfig(config);
+ return configComponentMappings.get(normalizedConfig);
}
+ /**
+ * Normalizes a configuration key by stripping array index suffixes and converting
+ * to dashed form.
+ *
+ * Example Usage:
+ *
{@code
+ * String normalized = FeignComponentRegistry.normalizeConfig("requestInterceptors[0]");
+ * // returns "request-interceptors"
+ * }
+ *
+ * @param config the raw configuration property key
+ * @return the normalized, dashed-form configuration key
+ */
+ static String normalizeConfig(String config) {
+ String normalizedConfig = substringBefore(config, LEFT_SQUARE_BRACKET);
+ return toDashedForm(normalizedConfig);
+ }
+
+ /**
+ * Constructs a {@link FeignComponentRegistry} with the given default client name
+ * and {@link BeanFactory}.
+ *
+ * Example Usage:
+ *
{@code
+ * FeignComponentRegistry registry = new FeignComponentRegistry("default", beanFactory);
+ * }
+ *
+ * @param defaultClientName the name of the default Feign client configuration
+ * @param beanFactory the {@link BeanFactory} used for component resolution
+ */
public FeignComponentRegistry(String defaultClientName, BeanFactory beanFactory) {
- this.DEFAULT_CLIENT_NAME = defaultClientName;
+ this.defaultClientName = defaultClientName;
this.beanFactory = beanFactory;
}
+ /**
+ * Registers a list of {@link Refreshable} components for the specified Feign client.
+ *
+ * Example Usage:
+ *
{@code
+ * List components = List.of(decoratedContract, decoratedDecoder);
+ * registry.register("my-client", components);
+ * }
+ *
+ * @param clientName the Feign client name
+ * @param components the list of {@link Refreshable} components to register
+ */
public void register(String clientName, List components) {
+ assertNotBlank(clientName, () -> "The 'clientName' must not be blank!");
+ assertNotEmpty(components, () -> "The 'components' must not be empty!");
+ assertNoNullElements(components, () -> "The 'components' must not contain the null element!");
List componentList = this.refreshableComponents.computeIfAbsent(clientName, name -> new ArrayList<>());
- componentList.addAll(componentList);
+ componentList.addAll(components);
}
+ /**
+ * Registers a single {@link Refreshable} component for the specified Feign client.
+ *
+ * Example Usage:
+ *
{@code
+ * registry.register("my-client", decoratedContract);
+ * }
+ *
+ * @param clientName the Feign client name
+ * @param component the {@link Refreshable} component to register
+ */
public void register(String clientName, Refreshable component) {
- List componentList = this.refreshableComponents.computeIfAbsent(clientName, name -> new ArrayList<>());
- componentList.add(component);
+ register(clientName, ofList(component));
}
+ /**
+ * Registers a {@link RequestInterceptor} for the specified Feign client. Interceptors
+ * are collected into a {@link CompositedRequestInterceptor} per client.
+ *
+ * Example Usage:
+ *
{@code
+ * RequestInterceptor interceptor = template -> template.header("X-Custom", "value");
+ * RequestInterceptor result = registry.registerRequestInterceptor("my-client", interceptor);
+ * }
+ *
+ * @param clientName the Feign client name
+ * @param requestInterceptor the {@link RequestInterceptor} to register
+ * @return the {@link CompositedRequestInterceptor} if this is the first interceptor
+ * for the client, or {@link io.microsphere.spring.cloud.openfeign.components.NoOpRequestInterceptor#INSTANCE} otherwise
+ */
public RequestInterceptor registerRequestInterceptor(String clientName, RequestInterceptor requestInterceptor) {
- CompositedRequestInterceptor compositedRequestInterceptor = this.interceptorMap.computeIfAbsent(clientName, (name) -> new CompositedRequestInterceptor(clientName, beanFactory));
+ assertNotBlank(clientName, () -> "The 'clientName' must not be blank!");
+ assertNotNull(requestInterceptor, () -> "The 'requestInterceptor' must not be null!");
+ CompositedRequestInterceptor compositedRequestInterceptor = this.interceptorsMap.computeIfAbsent(clientName, (name) -> new CompositedRequestInterceptor(clientName, beanFactory));
if (compositedRequestInterceptor.addRequestInterceptor(requestInterceptor)) {
return compositedRequestInterceptor;
- } else return NoOpRequestInterceptor.INSTANCE;
+ }
+ return INSTANCE;
}
- public synchronized void refresh(String clientName, Set changedConfig) {
+ /**
+ * Refreshes the Feign components for the specified client whose configurations have changed.
+ *
+ * Example Usage:
+ *
{@code
+ * registry.refresh("my-client", "retryer", "decoder");
+ * }
+ *
+ * @param clientName the Feign client name
+ * @param changedConfigs the configuration keys that have changed
+ */
+ public void refresh(String clientName, String... changedConfigs) {
+ refresh(clientName, ofSet(changedConfigs));
+ }
+
+ /**
+ * Refreshes the Feign components for the specified client based on a set of changed
+ * configuration keys. If the default client configuration changed, all registered
+ * components are refreshed.
+ *
+ * Example Usage:
+ *
{@code
+ * Set changed = Set.of("my-client.retryer", "my-client.decoder");
+ * registry.refresh("my-client", changed);
+ * }
+ *
+ * @param clientName the Feign client name
+ * @param changedConfigs the set of changed configuration sub-keys
+ */
+ public synchronized void refresh(String clientName, Set changedConfigs) {
+ Set> effectiveComponents = new HashSet<>(changedConfigs.size());
- Set> effectiveComponents = new HashSet<>();
boolean hasInterceptor = false;
- for (String value : changedConfig) {
- value = value.replace(clientName + ".", "");
- Class> clazz = getComponentClass(value);
+ for (String changedConfig : changedConfigs) {
+ changedConfig = changedConfig.replace(clientName + ".", "");
+ Class> clazz = getComponentClass(changedConfig);
if (clazz != null) {
effectiveComponents.add(clazz);
- hasInterceptor = clazz.equals(RequestInterceptor.class);
+ hasInterceptor = RequestInterceptor.class.equals(clazz);
}
}
- if (DEFAULT_CLIENT_NAME.equals(clientName)) {
+ if (defaultClientName.equals(clientName)) {
//default configs changed, need refresh all
refreshableComponents.values().stream()
.flatMap(List::stream)
- .filter(component -> {
- Class> componentsClass = component.getClass();
- for (Class> actualComponent : effectiveComponents)
- if (actualComponent.isAssignableFrom(componentsClass))
- return true;
- return false;
- })
+ .filter(component -> isComponentPresent(component, effectiveComponents))
.forEach(Refreshable::refresh);
- if (hasInterceptor)
- this.interceptorMap.values()
- .forEach(CompositedRequestInterceptor::refresh);
+ if (hasInterceptor) {
+ this.interceptorsMap.values().forEach(CompositedRequestInterceptor::refresh);
+ }
return;
}
+
List components = this.refreshableComponents.get(clientName);
- if (components != null)
+ if (components != null) {
components.stream()
- .filter(component -> {
- Class> componentsClass = component.getClass();
- for (Class> actualComponent : effectiveComponents)
- if (actualComponent.isAssignableFrom(componentsClass))
- return true;
- return false;
- })
+ .filter(component -> isComponentPresent(component, effectiveComponents))
.forEach(Refreshable::refresh);
+ }
if (hasInterceptor) {
- CompositedRequestInterceptor requestInterceptor = this.interceptorMap.get(clientName);
+ CompositedRequestInterceptor requestInterceptor = this.interceptorsMap.get(clientName);
if (requestInterceptor != null)
requestInterceptor.refresh();
}
+ }
+ /**
+ * Checks whether the given {@link Refreshable} component's class is assignable from
+ * any of the effective component classes.
+ *
+ * Example Usage:
+ *
{@code
+ * boolean present = FeignComponentRegistry.isComponentPresent(
+ * refreshableComponent, List.of(Retryer.class, Decoder.class));
+ * }
+ *
+ * @param component the {@link Refreshable} component to check
+ * @param effectiveComponents the component classes to match against
+ * @return {@code true} if the component matches any of the effective classes
+ */
+ static boolean isComponentPresent(Refreshable component, Iterable> effectiveComponents) {
+ return isComponentClassPresent(component.getClass(), effectiveComponents);
}
+ /**
+ * Checks whether the given class is assignable from any of the effective component classes.
+ *
+ * Example Usage:
+ *
{@code
+ * boolean present = FeignComponentRegistry.isComponentClassPresent(
+ * DecoratedRetryer.class, List.of(Retryer.class));
+ * }
+ *
+ * @param componentsClass the class to check
+ * @param effectiveComponents the component classes to match against
+ * @return {@code true} if the class is assignable from any effective class
+ */
+ static boolean isComponentClassPresent(Class> componentsClass, Iterable> effectiveComponents) {
+ for (Class> actualComponent : effectiveComponents) {
+ if (actualComponent.isAssignableFrom(componentsClass)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/components/CompositedRequestInterceptor.java b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/components/CompositedRequestInterceptor.java
index 4cb3cf9f..4f051ee3 100644
--- a/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/components/CompositedRequestInterceptor.java
+++ b/microsphere-spring-cloud-openfeign/src/main/java/io/microsphere/spring/cloud/openfeign/components/CompositedRequestInterceptor.java
@@ -2,97 +2,150 @@
import feign.RequestInterceptor;
import feign.RequestTemplate;
-import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.openfeign.FeignClientProperties;
-import org.springframework.util.CollectionUtils;
+import org.springframework.cloud.openfeign.FeignClientProperties.FeignClientConfiguration;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
+
+import static io.microsphere.collection.CollectionUtils.isNotEmpty;
+import static io.microsphere.collection.MapUtils.isNotEmpty;
+import static java.util.Collections.unmodifiableSet;
+import static org.springframework.beans.BeanUtils.instantiateClass;
/**
* @author 韩超
+ * @author Mercy
* @since 0.0.1
*/
public class CompositedRequestInterceptor implements RequestInterceptor, Refreshable {
private final BeanFactory beanFactory;
- private final String contextId;
- private final Set set = new HashSet<>();
+ private final String contextId;
+ private final Set set = new LinkedHashSet<>();
+
+ /**
+ * Constructs a {@link CompositedRequestInterceptor} for the specified Feign client context.
+ *
+ * Example Usage:
+ *
{@code
+ * CompositedRequestInterceptor interceptor =
+ * new CompositedRequestInterceptor("my-client", beanFactory);
+ * }
+ *
+ * @param contextId the Feign client context ID
+ * @param beanFactory the {@link BeanFactory} for resolving interceptor instances
+ */
public CompositedRequestInterceptor(String contextId, BeanFactory beanFactory) {
this.beanFactory = beanFactory;
this.contextId = contextId;
}
+ /**
+ * Returns an unmodifiable view of the registered {@link RequestInterceptor} instances.
+ *
+ * Example Usage:
+ *
{@code
+ * Set interceptors = compositedInterceptor.getRequestInterceptors();
+ * }
+ *
+ * @return an unmodifiable {@link Set} of registered request interceptors
+ */
+ public Set getRequestInterceptors() {
+ return unmodifiableSet(set);
+ }
+ /**
+ * Applies all registered {@link RequestInterceptor} instances to the given
+ * {@link RequestTemplate} in order.
+ *
+ * Example Usage:
+ *
{@code
+ * RequestTemplate template = new RequestTemplate();
+ * compositedInterceptor.apply(template);
+ * }
+ *
+ * @param template the {@link RequestTemplate} to apply interceptors to
+ */
@Override
public void apply(RequestTemplate template) {
synchronized (this.set) {
- if (!this.set.isEmpty())
- set.forEach(requestInterceptor -> requestInterceptor.apply(template));
+ set.forEach(requestInterceptor -> requestInterceptor.apply(template));
}
-
}
+ /**
+ * Adds a {@link RequestInterceptor} to this composite. Returns {@code true} if this
+ * is the first interceptor added (i.e., the composite was previously empty).
+ *
+ * Example Usage:
+ *
{@code
+ * boolean wasFirst = compositedInterceptor.addRequestInterceptor(
+ * template -> template.header("Authorization", "Bearer token"));
+ * }
+ *
+ * @param requestInterceptor the {@link RequestInterceptor} to add
+ * @return {@code true} if this was the first interceptor added, {@code false} otherwise
+ */
public boolean addRequestInterceptor(RequestInterceptor requestInterceptor) {
synchronized (this.set) {
boolean isFirst = this.set.isEmpty();
this.set.add(requestInterceptor);
return isFirst;
}
-
}
private RequestInterceptor getInterceptorOrInstantiate(Class extends RequestInterceptor> clazz) {
- try {
- return this.beanFactory.getBean(clazz);
- } catch (Exception e) {
- return BeanUtils.instantiateClass(clazz);
- }
+ return getOrInstantiate(clazz);
}
+ /**
+ * Refreshes the set of {@link RequestInterceptor} instances by re-reading the
+ * {@link FeignClientProperties} configuration for request interceptors, default
+ * headers, and default query parameters.
+ *
+ * Example Usage:
+ *
{@code
+ * compositedInterceptor.refresh();
+ * }
+ */
@Override
public void refresh() {
- FeignClientProperties properties = this.beanFactory.getBean(FeignClientProperties.class);
+ FeignClientProperties properties = getOrInstantiate(FeignClientProperties.class);
Set> interceptors = new HashSet<>();
//headers
Map> headers = new HashMap<>();
Map> params = new HashMap<>();
- if (properties != null) {
- FeignClientProperties.FeignClientConfiguration defaultConfiguration = properties.getConfig().get(properties.getDefaultConfig());
- FeignClientProperties.FeignClientConfiguration current = properties.getConfig().get(contextId);
- if (defaultConfiguration != null && defaultConfiguration.getRequestInterceptors() != null)
- interceptors.addAll(defaultConfiguration.getRequestInterceptors());
- if (current != null && current.getRequestInterceptors() != null)
- interceptors.addAll(current.getRequestInterceptors());
-
- if (defaultConfiguration != null && defaultConfiguration.getDefaultRequestHeaders() != null)
- headers.putAll(defaultConfiguration.getDefaultRequestHeaders());
-
- if (current != null && current.getDefaultRequestHeaders() != null) {
- current.getDefaultRequestHeaders().forEach(headers::putIfAbsent);
- }
- if (defaultConfiguration != null && defaultConfiguration.getDefaultQueryParameters() != null)
- params.putAll(defaultConfiguration.getDefaultRequestHeaders());
+ Map config = properties.getConfig();
+ FeignClientConfiguration defaultConfiguration = config.get(properties.getDefaultConfig());
+ FeignClientConfiguration currentConfiguration = config.get(contextId);
- if (current != null && current.getDefaultQueryParameters() != null) {
- current.getDefaultQueryParameters().forEach(params::putIfAbsent);
- }
+ addAll(defaultConfiguration::getRequestInterceptors, interceptors);
+ addAll(currentConfiguration::getRequestInterceptors, interceptors);
- }
+ putIfAbsent(defaultConfiguration::getDefaultRequestHeaders, headers);
+ putIfAbsent(currentConfiguration::getDefaultRequestHeaders, headers);
+
+ putIfAbsent(defaultConfiguration::getDefaultQueryParameters, params);
+ putIfAbsent(currentConfiguration::getDefaultQueryParameters, params);
synchronized (this.set) {
this.set.clear();
- for (Class interceptorClass : interceptors)
+ for (Class interceptorClass : interceptors) {
set.add(getInterceptorOrInstantiate(interceptorClass));
+ }
- if (!CollectionUtils.isEmpty(headers))
+ if (isNotEmpty(headers))
set.add(requestTemplate -> {
Map> requestHeader = requestTemplate.headers();
headers.keySet().forEach(key -> {
@@ -102,7 +155,7 @@ public void refresh() {
});
});
- if (!CollectionUtils.isEmpty(params))
+ if (isNotEmpty(params))
set.add(requestTemplate -> {
Map> requestQueries = requestTemplate.queries();
params.keySet().forEach(key -> {
@@ -112,8 +165,66 @@ public void refresh() {
});
});
}
+ }
+ /**
+ * Adds all elements from the source collection (obtained via the supplier) into the
+ * target collection if the source is not empty.
+ *
+ * Example Usage:
+ *
{@code
+ * Collection target = new ArrayList<>();
+ * addAll(() -> List.of("a", "b"), target);
+ * }
+ *
+ * @param the element type
+ * @param sourceSupplier the supplier providing the source collection
+ * @param target the target collection to add elements to
+ */
+ static void addAll(Supplier> sourceSupplier, Collection target) {
+ Collection source = sourceSupplier.get();
+ if (isNotEmpty(source)) {
+ source.forEach(target::add);
+ }
+ }
+ /**
+ * Retrieves a bean of the given type from the {@link BeanFactory}, falling back to
+ * instantiation via the default constructor if the bean is not available.
+ *
+ * Example Usage:
+ *
{@code
+ * FeignClientProperties properties = getOrInstantiate(FeignClientProperties.class);
+ * }
+ *
+ * @param the type of the bean
+ * @param clazz the class of the bean to retrieve or instantiate
+ * @return an instance of the requested type
+ */
+ T getOrInstantiate(Class clazz) {
+ ObjectProvider beanProvider = this.beanFactory.getBeanProvider(clazz);
+ return beanProvider.getIfAvailable(() -> instantiateClass(clazz));
+ }
+ /**
+ * Puts all entries from the source map (obtained via the supplier) into the target
+ * map if the source is not empty, without overwriting existing keys.
+ *
+ * Example Usage:
+ *
{@code
+ * Map target = new HashMap<>();
+ * putIfAbsent(() -> Map.of("key", "value"), target);
+ * }
+ *
+ * @param the key type
+ * @param the value type
+ * @param sourceSupplier the supplier providing the source map
+ * @param target the target map to add entries to
+ */
+ static void putIfAbsent(Supplier