refactor(agent): introduce AgentBootstrap for startup wiring and simplify app launch

This commit is contained in:
2026-04-30 22:12:04 +08:00
parent 7aab236221
commit 428f133ac3
4 changed files with 186 additions and 28 deletions

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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.*;
/**
* <h2>Agent 启动入口</h2>
@@ -33,7 +38,8 @@ public final class Agent {
public static class AgentApp {
private final Class<?> applicationClass;
private final Set<String> scanPackages = new LinkedHashSet<>();
private final Set<String> scanDirs = new LinkedHashSet<>();
private final Set<AgentGatewayRegistration> gatewayRegistrations = new LinkedHashSet<>();
private final Set<ExceptionReporter> exceptionReporters = new LinkedHashSet<>();
private final Set<Configurable> configurables = new LinkedHashSet<>();
@@ -41,40 +47,47 @@ public final class Agent {
private final Set<LifecycleHook> 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<URL> buildScanUrls() {
Set<URL> urls = new LinkedHashSet<>();
for (String packageName : scanPackages) {
urls.addAll(ClasspathHelper.forPackage(packageName));
}
for (String scanDir : scanDirs) {
urls.addAll(scanDirToUrls(scanDir));
}
return urls;
}
private Set<URL> scanDirToUrls(String scanDir) {
Set<URL> 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);
}
}
}

View File

@@ -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 实例