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 extends AgentBootstrap> 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 extends AgentBootstrap> bootstrapClass) {
+ try {
+ Constructor extends AgentBootstrap> 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 实例