From 428f133ac3d5db4a829a3b63496f568829278ad7 Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Thu, 30 Apr 2026 22:12:04 +0800 Subject: [PATCH] refactor(agent): introduce AgentBootstrap for startup wiring and simplify app launch --- .../main/java/work/slhaf/partner/Main.java | 7 +- .../runtime/PartnerAgentBootstrap.java | 18 ++ .../slhaf/partner/framework/agent/Agent.java | 183 ++++++++++++++++-- .../agent/factory/AgentRegisterFactory.kt | 6 +- 4 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 Partner-Core/src/main/java/work/slhaf/partner/runtime/PartnerAgentBootstrap.java diff --git a/Partner-Core/src/main/java/work/slhaf/partner/Main.java b/Partner-Core/src/main/java/work/slhaf/partner/Main.java index edc6e39c..ea59e63c 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/Main.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/Main.java @@ -1,15 +1,10 @@ package work.slhaf.partner; -import work.slhaf.partner.common.vector.VectorClientRegistry; import work.slhaf.partner.framework.agent.Agent; -import work.slhaf.partner.runtime.gateway.WebSocketGatewayRegistration; public class Main { public static void main(String[] args) { - boolean launched = Agent.newAgent(Main.class) - .addGatewayRegistration(WebSocketGatewayRegistration.INSTANCE) - .addConfigurable(new VectorClientRegistry()) - .launch(); + boolean launched = Agent.newAgent(Main.class).launch(); if (!launched) { System.exit(1); } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/runtime/PartnerAgentBootstrap.java b/Partner-Core/src/main/java/work/slhaf/partner/runtime/PartnerAgentBootstrap.java new file mode 100644 index 00000000..72986f71 --- /dev/null +++ b/Partner-Core/src/main/java/work/slhaf/partner/runtime/PartnerAgentBootstrap.java @@ -0,0 +1,18 @@ +package work.slhaf.partner.runtime; + +import work.slhaf.partner.common.vector.VectorClientRegistry; +import work.slhaf.partner.framework.agent.Agent; +import work.slhaf.partner.runtime.gateway.WebSocketGatewayRegistration; + +public final class PartnerAgentBootstrap extends Agent.AgentBootstrap { + + public PartnerAgentBootstrap(Agent.AgentApp agentApp) { + super(agentApp); + } + + @Override + protected void bootstrap() { + addGatewayRegistration(WebSocketGatewayRegistration.INSTANCE); + addConfigurable(new VectorClientRegistry()); + } +} diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/Agent.java b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/Agent.java index fa8844c1..3984e499 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/Agent.java +++ b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/Agent.java @@ -2,6 +2,7 @@ package work.slhaf.partner.framework.agent; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.reflections.util.ClasspathHelper; import work.slhaf.partner.framework.agent.config.ConfigCenter; import work.slhaf.partner.framework.agent.config.Configurable; import work.slhaf.partner.framework.agent.exception.AgentStartupException; @@ -9,6 +10,7 @@ import work.slhaf.partner.framework.agent.exception.ExceptionReporter; import work.slhaf.partner.framework.agent.exception.ExceptionReporterHandler; import work.slhaf.partner.framework.agent.factory.AgentRegisterFactory; import work.slhaf.partner.framework.agent.factory.context.AgentContext; +import work.slhaf.partner.framework.agent.factory.context.AgentRegisterContext; import work.slhaf.partner.framework.agent.interaction.AgentGatewayRegistration; import work.slhaf.partner.framework.agent.interaction.AgentGatewayRegistry; import work.slhaf.partner.framework.agent.log.LogAdviceProvider; @@ -16,9 +18,12 @@ import work.slhaf.partner.framework.agent.log.TraceSinkRegistry; import work.slhaf.partner.framework.agent.model.ModelRuntimeRegistry; import work.slhaf.partner.framework.agent.state.StateCenter; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; +import java.net.URL; import java.nio.file.Path; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.*; /** *

Agent 启动入口

@@ -33,7 +38,8 @@ public final class Agent { public static class AgentApp { - private final Class applicationClass; + private final Set scanPackages = new LinkedHashSet<>(); + private final Set scanDirs = new LinkedHashSet<>(); private final Set gatewayRegistrations = new LinkedHashSet<>(); private final Set exceptionReporters = new LinkedHashSet<>(); private final Set configurables = new LinkedHashSet<>(); @@ -41,40 +47,47 @@ public final class Agent { private final Set postShutdownHooks = new LinkedHashSet<>(); private AgentApp(Class clazz) { - this.applicationClass = clazz; + this.scanPackages.add(clazz.getPackageName()); } - public AgentApp addGatewayRegistration(AgentGatewayRegistration... registrations) { + private void addScanPackage(String packageName) { + if (packageName != null && !packageName.isBlank()) { + this.scanPackages.add(packageName); + } + } + + private void addScanDir(String scanDir) { + if (scanDir != null && !scanDir.isBlank()) { + this.scanDirs.add(scanDir); + } + } + + private void addGatewayRegistration(AgentGatewayRegistration... registrations) { this.gatewayRegistrations.addAll(Set.of(registrations)); - return this; } - public AgentApp addConfigurable(Configurable configurable) { + private void addConfigurable(Configurable configurable) { this.configurables.add(configurable); - return this; } - public AgentApp addExceptionReporter(ExceptionReporter... exceptionReporters) { + private void addExceptionReporter(ExceptionReporter... exceptionReporters) { this.exceptionReporters.addAll(Set.of(exceptionReporters)); - return this; } - public AgentApp addPreShutdownHook(String name, Runnable action) { - return addPreShutdownHook(name, 0, action); + private void addPreShutdownHook(String name, Runnable action) { + addPreShutdownHook(name, 0, action); } - public AgentApp addPreShutdownHook(String name, int order, Runnable action) { + private void addPreShutdownHook(String name, int order, Runnable action) { this.preShutdownHooks.add(new LifecycleHook(name, order, action)); - return this; } - public AgentApp addPostShutdownHook(String name, Runnable action) { - return addPostShutdownHook(name, 0, action); + private void addPostShutdownHook(String name, Runnable action) { + addPostShutdownHook(name, 0, action); } - public AgentApp addPostShutdownHook(String name, int order, Runnable action) { + private void addPostShutdownHook(String name, int order, Runnable action) { this.postShutdownHooks.add(new LifecycleHook(name, order, action)); - return this; } public boolean launch() { @@ -83,6 +96,13 @@ public final class Agent { ConfigCenter.INSTANCE.toString(); StateCenter.INSTANCE.toString(); + Path externalModuleDir = ConfigCenter.INSTANCE.getPaths().getResourcesDir().resolve("module"); + addScanDir(externalModuleDir.toString()); + + AgentRegisterContext bootstrapContext = buildRegisterContext(); + runBootstraps(bootstrapContext); + AgentRegisterContext registerContext = buildRegisterContext(); + // Keep startup order explicit so registries are ready before component scanning. for (ExceptionReporter exceptionReporter : exceptionReporters) { exceptionReporter.register(); @@ -102,9 +122,7 @@ public final class Agent { registerShutdownHooks(); - Path externalModuleDir = ConfigCenter.INSTANCE.getPaths().getResourcesDir().resolve("module"); - AgentRegisterFactory.addScanDir(externalModuleDir.toString()); - AgentRegisterFactory.launch(applicationClass.getPackageName()); + AgentRegisterFactory.launch(registerContext); // Try to init configurable, and start config listening ConfigCenter.INSTANCE.initAll(); @@ -120,6 +138,78 @@ public final class Agent { } } + private AgentRegisterContext buildRegisterContext() { + return new AgentRegisterContext(new ArrayList<>(buildScanUrls())); + } + + private Set buildScanUrls() { + Set urls = new LinkedHashSet<>(); + for (String packageName : scanPackages) { + urls.addAll(ClasspathHelper.forPackage(packageName)); + } + for (String scanDir : scanDirs) { + urls.addAll(scanDirToUrls(scanDir)); + } + return urls; + } + + private Set scanDirToUrls(String scanDir) { + Set urls = new LinkedHashSet<>(); + File file = new File(scanDir); + if (!file.exists() || !file.isDirectory()) { + return urls; + } + try { + File[] files = file.listFiles(); + if (files == null) { + return urls; + } + for (File item : files) { + if (item.getName().endsWith(".jar")) { + urls.add(item.toURI().toURL()); + } + } + return urls; + } catch (Exception e) { + throw new AgentStartupException("Failed to load scan dir URLs from: " + scanDir, "agent-bootstrap", e); + } + } + + private void runBootstraps(AgentRegisterContext context) { + context.getReflections().getSubTypesOf(AgentBootstrap.class).stream() + .filter(this::isConcreteBootstrap) + .map(this::instantiateBootstrap) + .sorted(Comparator.comparingInt(AgentBootstrap::order)) + .forEach(AgentBootstrap::bootstrap); + } + + private boolean isConcreteBootstrap(Class bootstrapClass) { + int modifiers = bootstrapClass.getModifiers(); + return !bootstrapClass.isInterface() + && !bootstrapClass.isAnnotation() + && !bootstrapClass.isEnum() + && !bootstrapClass.isArray() + && !bootstrapClass.isPrimitive() + && !Modifier.isAbstract(modifiers) + && !bootstrapClass.isSynthetic() + && !bootstrapClass.isAnonymousClass() + && !bootstrapClass.isLocalClass(); + } + + private AgentBootstrap instantiateBootstrap(Class bootstrapClass) { + try { + Constructor constructor = bootstrapClass.getDeclaredConstructor(AgentApp.class); + constructor.setAccessible(true); + return constructor.newInstance(this); + } catch (Exception e) { + throw new AgentStartupException( + "Failed to instantiate AgentBootstrap: " + bootstrapClass.getName(), + "agent-bootstrap", + e + ); + } + } + private void registerShutdownHooks() { AgentContext.INSTANCE.addPreShutdownHook( "agent-gateway-registry-close", @@ -153,4 +243,55 @@ public final class Agent { private record LifecycleHook(String name, int order, Runnable action) { } + public static abstract class AgentBootstrap { + + private final AgentApp agentApp; + + protected AgentBootstrap(AgentApp agentApp) { + this.agentApp = Objects.requireNonNull(agentApp, "agentApp"); + } + + public int order() { + return 0; + } + + protected abstract void bootstrap(); + + protected final void addScanPackage(String packageName) { + agentApp.addScanPackage(packageName); + } + + protected final void addScanDir(String scanDir) { + agentApp.addScanDir(scanDir); + } + + protected final void addPreShutdownHook(String name, Runnable action) { + agentApp.addPreShutdownHook(name, action); + } + + protected final void addPreShutdownHook(String name, int order, Runnable action) { + agentApp.addPreShutdownHook(name, order, action); + } + + protected final void addPostShutdownHook(String name, Runnable action) { + agentApp.addPostShutdownHook(name, action); + } + + protected final void addPostShutdownHook(String name, int order, Runnable action) { + agentApp.addPostShutdownHook(name, order, action); + } + + protected final void addExceptionReporter(ExceptionReporter... exceptionReporters) { + agentApp.addExceptionReporter(exceptionReporters); + } + + protected final void addConfigurable(Configurable configurable) { + agentApp.addConfigurable(configurable); + } + + protected final void addGatewayRegistration(AgentGatewayRegistration... gatewayRegistrations) { + agentApp.addGatewayRegistration(gatewayRegistrations); + } + } + } diff --git a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/factory/AgentRegisterFactory.kt b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/factory/AgentRegisterFactory.kt index 04b4425e..1cb181f3 100644 --- a/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/factory/AgentRegisterFactory.kt +++ b/Partner-Framework/src/main/java/work/slhaf/partner/framework/agent/factory/AgentRegisterFactory.kt @@ -33,7 +33,11 @@ object AgentRegisterFactory { @JvmStatic fun launch(packageName: String) { urls.addAll(packageNameToURL(packageName)) - val registerContext = AgentRegisterContext(urls) + launch(AgentRegisterContext(urls)) + } + + @JvmStatic + fun launch(registerContext: AgentRegisterContext) { // 1. 校验 Component 级别注解是否合规,避免注入到异常位置 ComponentAnnotationValidatorFactory().execute(registerContext) // 2. 收集所有的 AgentComponent 实例