生成 reachability-metadata.json
注意:自动生成并不能解决所有问题
使用 GraalVM 运行 jar 包后,访问业务 API 接口
${GRAALVM_HOME}/bin/java -agentlib:native-image-agent=config-output-dir=./target/native-image-config -jar ./target/graalvm-demo-0.0.1-SNAPSHOT.jar
业务 API 接口运行完成后,正常终止程序
快速生成 reflect-config.json、resource-config.json
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Merge reachability-metadata.json (generated by GraalVM native-image agent)
into the corresponding native-image config files.
Usage:
python3 merge_native_image_config.py --target-dir PATH [--source PATH]
--target-dir Directory containing native-image config files
(reflect-config.json, resource-config.json, etc.).
--source Path to reachability-metadata.json (source).
Default: target/native-image-config/reachability-metadata.json
The script merges into:
reflect-config.json — "reflection" node
resource-config.json — "resources" node
Additional top-level keys from reachability-metadata.json can be supported
by extending the KEY_TO_FILE map below.
Rules:
- Only ADD new entries; existing entries are never deleted.
- Duplicate detection is keyed on the primary identifier:
reflection: "type" (source) → "name" (target)
resources: "glob" (source) → "pattern" (target)
- If a target file does not exist, a default (empty) structure is created.
- Target file format is preserved.
- After merging, run: mvn spotless:apply -pl server -am
"""
import argparse
import json
import os
import sys
from typing import Any, Dict, List, Optional, Set, Union
# ---------------------------------------------------------------------------
# Path defaults — relative to this script's location
# ---------------------------------------------------------------------------
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
SEATA_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "..")) # script is at script/native/
DEFAULT_SOURCE_FILE = os.path.join(
SEATA_ROOT, "target", "native-image-config", "reachability-metadata.json"
)
# ---------------------------------------------------------------------------
# Mapping: top-level key in source → (target_filename, id_mapping, format_type)
#
# id_mapping: (source_key, target_key)
# The field used to detect duplicates (and renamed when needed).
#
# format_type:
# "list" — target file is a JSON array (e.g. reflect-config.json)
# "includes"— target file has {"resources": {"includes": [...]}} (e.g. resource-config.json)
# ---------------------------------------------------------------------------
KEY_TO_FILE: Dict[str, tuple] = {
"reflection": ("reflect-config.json",
("type", "name"),
"list"),
"resources": ("resource-config.json",
("glob", "pattern"),
"includes"),
}
def load_json(path: str) -> Any:
"""Load and return a JSON file."""
with open(path, "r", encoding="utf-8") as fh:
return json.load(fh)
def save_json(path: str, data: Any) -> None:
"""Save data as JSON with consistent formatting.
Uses the same indent as the source file (2 spaces) and appends a
trailing newline so Spotless can operate cleanly."""
with open(path, "w", encoding="utf-8") as fh:
json.dump(data, fh, indent=2, ensure_ascii=False)
fh.write("\n")
# ---------------------------------------------------------------------------
# Merge workers
# ---------------------------------------------------------------------------
def merge_list_format(
source_entries: List[dict],
target_list: List[dict],
src_key: str,
tgt_key: str,
) -> int:
"""Merge source entries into a list-format target (e.g. reflect-config.json).
Returns the number of newly added entries."""
# Build existing-id set from target
existing_ids: Set[str] = set()
for entry in target_list:
if isinstance(entry, dict):
val = entry.get(tgt_key)
if isinstance(val, str):
existing_ids.add(val)
added = 0
for src_entry in source_entries:
identifier = src_entry.get(src_key)
if not isinstance(identifier, str):
continue
if identifier in existing_ids:
continue
# Build target entry: rename the key, copy everything else
new_entry = dict(src_entry)
new_entry[tgt_key] = new_entry.pop(src_key)
target_list.append(new_entry)
existing_ids.add(identifier)
added += 1
return added
def merge_includes_format(
source_entries: List[dict],
target_includes: List[dict],
src_key: str,
tgt_key: str,
) -> int:
"""Merge source entries into an includes-format target (e.g. resource-config.json).
Returns the number of newly added entries."""
# Build existing-id set from target includes
existing_ids: Set[str] = set()
for entry in target_includes:
if isinstance(entry, dict):
val = entry.get(tgt_key)
if isinstance(val, str):
existing_ids.add(val)
added = 0
for src_entry in source_entries:
identifier = src_entry.get(src_key)
if not isinstance(identifier, str):
continue
if identifier in existing_ids:
continue
new_entry: Dict[str, Any] = {tgt_key: identifier}
target_includes.append(new_entry)
existing_ids.add(identifier)
added += 1
return added
def create_default_data(fmt: str) -> Any:
"""Return a default (empty) data structure for the given format."""
if fmt == "list":
return []
elif fmt == "includes":
return {"resources": {"includes": []}}
else:
raise ValueError(f"Unknown format: {fmt}")
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def build_arg_parser() -> argparse.ArgumentParser:
"""Build and return the argument parser."""
parser = argparse.ArgumentParser(
description=(
"Merge reachability-metadata.json (generated by GraalVM native-image "
"agent) into the corresponding native-image config files."
),
)
parser.add_argument(
"--source",
default=DEFAULT_SOURCE_FILE,
help=(
"Path to reachability-metadata.json (source). "
"Default: %(default)s"
),
)
parser.add_argument(
"--target-dir",
required=True,
help=(
"Directory containing native-image config files "
"(reflect-config.json, resource-config.json, etc.)."
),
)
return parser
def main() -> int:
"""Run the merge and report results."""
parser = build_arg_parser()
args = parser.parse_args()
source_file: str = args.source
target_dir: str = args.target_dir
# --- Check source ---
if not os.path.isfile(source_file):
print(f"[ERROR] Source file not found: {source_file}")
print(" Run the GraalVM native-image agent first to generate it.")
return 1
print(f"[INFO] Source: {source_file}")
source = load_json(source_file)
# Validate source structure: must be a JSON object
if not isinstance(source, dict):
print("[ERROR] Source file must be a JSON object with top-level keys.")
return 1
source_keys_found = set(source.keys())
supported_keys = set(KEY_TO_FILE.keys())
unsupported = source_keys_found - supported_keys
if unsupported:
print(f"[WARN] Unsupported top-level keys in source (skipped): {unsupported}")
print(f" Extend KEY_TO_FILE in this script to add support.")
total_added = 0
for key, (filename, (src_key, tgt_key), fmt) in KEY_TO_FILE.items():
if key not in source:
print(f"[SKIP] '{key}' — not found in source")
continue
target_path = os.path.join(target_dir, filename)
if not os.path.isfile(target_path):
print(f"[NEW] '{filename}' — target file not found, creating default")
os.makedirs(target_dir, exist_ok=True)
target_data = create_default_data(fmt)
save_json(target_path, target_data)
else:
target_data = load_json(target_path)
source_entries = source[key]
if not isinstance(source_entries, list):
print(f"[SKIP] '{key}' — expected a list in source")
continue
if fmt == "list":
if not isinstance(target_data, list):
print(f"[ERROR] '{filename}' expected a JSON array (list).")
return 1
added = merge_list_format(source_entries, target_data,
src_key, tgt_key)
save_json(target_path, target_data)
elif fmt == "includes":
# Navigate to the includes list
includes = target_data.get("resources", {}).get("includes")
if not isinstance(includes, list):
print(f"[ERROR] '{filename}' missing resources.includes list.")
return 1
added = merge_includes_format(source_entries, includes,
src_key, tgt_key)
save_json(target_path, target_data)
else:
print(f"[WARN] Unknown format '{fmt}' for '{key}' — skipped")
continue
print(f"[OK] {filename:<30s} {len(source_entries):>4d} source"
f" → +{added} new")
total_added += added
print(f"\n[DONE] {total_added} entries added across all files.")
if total_added > 0:
print(" Next: run 'mvn spotless:apply -pl server -am' to format the JSON.")
return 0
if __name__ == "__main__":
sys.exit(main())