动态实例化客户端API
一、介绍
完善数据加载流程,动态实例化客户端 API(ai_client_api) 并注册到 Spring 容器。
这是 Armory 动态装配 AI Agent 的首个节点,涵盖:数据获取 → 对象创建 → Bean 注册 三个关键步骤。理解本节操作后,后续直至完整 AI Agent 构建 的流程都会更易上手与掌握。
二、功能流程
如图,客户端API实例化过程设计;

整个 AI Agent 的实例化过程,本质上是对各个组件的创建与组装。为了提升代码的可维护性与扩展性,我们将该过程设计为一棵 规则树,通过节点的有序串联来驱动执行。这种模式的优势在于:模块化设计、便于扩展、代码复用度高。
从执行流程来看,系统从 开始节点 出发,依次进入 数据构建节点 与 API 构建节点。在 API 构建过程中,会先检查 动态上下文(DynamicContext) 中是否存在已从数据库获取的配置数据。若存在,则按顺序循环构建 API 对象,并将其 注册到 Spring 容器,从而完成组件的动态化装配。
三、编码实现
1. 工程结构

如图所示,先构建 AI Agent 使用的首个 API 节点。AiClientApiNode 的创建会直接复用 RootNode 预先加载到的配置数据,作为其构建输入。
当 AiClientApiNode 构建完成后,将通过 AbstractArmorySupport 提供的 Bean 注册能力,把生成的对象 注册到 Spring 容器 中。这一过程基于 Spring 的原生机制实现,确保组件以框架标准方式完成加载与管理。
2. Spring Bean 容器
类:AbstractArmorySupport
/**
* 通用的Bean注册方法
* @param beanName Bean名称
* @param beanClass Bean类型
* @param <T> Bean类型
*/
protected synchronized <T> void registerBean(String beanName, Class<T> beanClass, T beanInstance) {
// 获取Bean工厂
var beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
// 注册Bean
var beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(beanClass, () -> beanInstance);
beanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
var beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
// 如果Bean已存在,先移除
if (beanFactory.containsBeanDefinition(beanName)) {
beanFactory.removeBeanDefinition(beanName);
}
// 注册新的Bean
beanFactory.registerBeanDefinition(beanName, beanDefinition);
log.info("注册Bean: {} -> {}", beanName, beanInstance);
}
protected <T> T getBean(String beanName) {
return (T)applicationContext.getBean(beanName);
}第一步:获取 Bean 工厂: 使用 DefaultListableBeanFactory ——Spring 容器的核心实现类。它提供了对 Bean 生命周期的动态管理能力。
第二步:构建 Bean 定义: 通过 BeanDefinitionBuilder 创建 Bean 定义,例如:genericBeanDefinition(beanClass, () -> beanInstance),指定 Bean 类型 和 实例供应商,并设置作用域为 单例模式(SCOPE_SINGLETON)。
第三步:处理 Bean 冲突: 在注册前检查容器中是否已存在同名 Bean。如果存在,先移除旧的 Bean 定义,确保新 Bean 可以正确覆盖注册。
第四步:注册新 Bean: 将构建好的 Bean 定义注册到 Spring 容器,使其能够被正常加载和使用。
3. 节点构建(AiClientApiNode)
类:AiClientApiNode
@Slf4j
@Service
public class AiClientApiNode extends AbstractArmorySupport {
@Override
protected String doApply(ArmoryCommandEntity requestParameter, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws Exception {
log.info("Ai Agent 构建,API 构建节点 {}", JSON.toJSONString(requestParameter));
List<AiClientApiVO> aiClientApiList = dynamicContext.getValue(AiAgentEnumVO.AI_CLIENT_API.getDataName());
if (aiClientApiList == null || aiClientApiList.isEmpty()) {
log.warn("没有需要被初始化的 ai client api");
return null;
}
for (AiClientApiVO aiClientApiVO : aiClientApiList) {
// 构建 OpenAiApi
OpenAiApi openAiApi = OpenAiApi.builder()
.baseUrl(aiClientApiVO.getBaseUrl())
.apiKey(aiClientApiVO.getApiKey())
.completionsPath(aiClientApiVO.getCompletionsPath())
.embeddingsPath(aiClientApiVO.getEmbeddingsPath())
.build();
// 注册 OpenAiApi Bean 对象
registerBean(AiAgentEnumVO.AI_CLIENT_API.getBeanName(aiClientApiVO.getApiId()), OpenAiApi.class, openAiApi);
}
return router(requestParameter, dynamicContext);
}
@Override
public StrategyHandler<ArmoryCommandEntity, DefaultArmoryStrategyFactory.DynamicContext, String> get(ArmoryCommandEntity armoryCommandEntity, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws Exception {
return defaultStrategyHandler;
}
}doApply 是业务流程的核心处理方法。
- 数据获取:首先从上下文中获取已加载的
AIClientApi数据。如果数据为空,则直接返回null。在后续优化中,这里也可以调整为路由到下一个节点,以继续处理其他组件的实例化逻辑。 - 对象创建与注册:随后通过
for循环,不断创建OpenAiApi对象,并将其注册到 Spring 容器 中,完成动态实例化与管理。 - 节点路由:当注册完成后,会执行
router方法,将流程路由到下一个节点。目前get方法的设置是defaultStrategyHandler,意味着不会继续向下执行。后续会随着功能的开发,对该逻辑进行扩展和修改。
4. 节点路由(RootNode)
@Slf4j
@Service
public class RootNode extends AbstractArmorySupport {
private final Map<String, ILoadDataStrategy> loadDataStrategyMap;
@Resource
private AiClientApiNode aiClientApiNode;
public RootNode(Map<String, ILoadDataStrategy> loadDataStrategyMap) {
this.loadDataStrategyMap = loadDataStrategyMap;
}
@Override
protected void multiThread(ArmoryCommandEntity requestParameter, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {
// 获取命令;不同的命令类型,对应不同的数据加载策略
String commandType = requestParameter.getCommandType();
// 获取策略
AiAgentEnumVO aiAgentEnumVO = AiAgentEnumVO.getByCode(commandType);
String loadDataStrategyKey = aiAgentEnumVO.getLoadDataStrategy();
// 加载数据
ILoadDataStrategy loadDataStrategy = loadDataStrategyMap.get(loadDataStrategyKey);
loadDataStrategy.loadData(requestParameter, dynamicContext);
}
@Override
protected String doApply(ArmoryCommandEntity requestParameter, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws Exception {
log.info("Ai Agent 构建,数据加载节点 {}", JSON.toJSONString(requestParameter));
return router(requestParameter, dynamicContext);
}
@Override
public StrategyHandler<ArmoryCommandEntity, DefaultArmoryStrategyFactory.DynamicContext, String> get(ArmoryCommandEntity armoryCommandEntity, DefaultArmoryStrategyFactory.DynamicContext dynamicContext) throws Exception {
return aiClientApiNode;
}
}调整
RootNode#get()路由:将下一步明确指向AiClientApiNode,即在数据加载完成后立即进入 API 构建 节点。router行为说明:路由逻辑封装在模板基类中,可上溯查看其实现细节。调试建议:推荐使用 Debug 单步跟踪 验证“加载 → 路由 → 构建”的全链路执行路径;通过断点与变量观察可迅速掌握流程与状态变化,这也是工程实践中必备的能力。
四、功能测试
类:ai-agent-station-study-app/cn.cactusli.ai.test.domain.AgentTest
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class AgentTest {
@Resource
private DefaultArmoryStrategyFactory defaultArmoryStrategyFactory;
@Resource
private ApplicationContext applicationContext;
@Test
public void test_aiClientApiNode() throws Exception {
var armoryStrategyHandler =
defaultArmoryStrategyFactory.armoryStrategyHandler();
var apply = armoryStrategyHandler.apply(
ArmoryCommandEntity.builder()
.commandType(AiAgentEnumVO.AI_CLIENT.getCode())
.commandIdList(Arrays.asList("3001"))
.build(),
new DefaultArmoryStrategyFactory.DynamicContext());
var bean = applicationContext.getBean(AiAgentEnumVO.AI_CLIENT_API.getBeanName("1001"));
log.info("测试结果:{}", bean);
}
}测试结果
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build what is described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3
WARNING: A Java agent has been loaded dynamically (E:\Maven_repository\net\bytebuddy\byte-buddy-agent\1.15.11\byte-buddy-agent-1.15.11.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
25-09-10.14:51:39.656 [pool-2-thread-2 ] INFO AiClientLoadDataStrategy - 查询配置数据(ai_client_model) [3001]
25-09-10.14:51:39.656 [pool-2-thread-1 ] INFO AiClientLoadDataStrategy - 查询配置数据(ai_client_api) [3001]
25-09-10.14:51:39.656 [pool-2-thread-3 ] INFO AiClientLoadDataStrategy - 查询配置数据(ai_client_tool_mcp) [3001]
25-09-10.14:51:39.656 [pool-2-thread-4 ] INFO AiClientLoadDataStrategy - 查询配置数据(ai_client_system_prompt) [3001]
25-09-10.14:51:39.656 [pool-2-thread-5 ] INFO AiClientLoadDataStrategy - 查询配置数据(ai_client_advisor) [3001]
25-09-10.14:51:39.656 [pool-2-thread-6 ] INFO AiClientLoadDataStrategy - 查询配置数据(ai_client) [3001]
25-09-10.14:51:39.670 [pool-2-thread-6 ] INFO HikariDataSource - MainHikariPool - Starting...
25-09-10.14:51:39.823 [pool-2-thread-6 ] INFO HikariPool - MainHikariPool - Added connection com.mysql.cj.jdbc.ConnectionImpl@51f7416a
25-09-10.14:51:39.824 [pool-2-thread-6 ] INFO HikariDataSource - MainHikariPool - Start completed.
25-09-10.14:51:39.971 [main ] INFO RootNode - Ai Agent 构建,数据加载节点 {"commandIdList":["3001"],"commandType":"client"}
25-09-10.14:51:39.972 [main ] INFO AiClientApiNode - Ai Agent 构建,API 构建节点 {"commandIdList":["3001"],"commandType":"client"}
25-09-10.14:51:39.975 [main ] INFO AbstractArmorySupport - 注册Bean: ai_client_api_1001 -> org.springframework.ai.openai.api.OpenAiApi@496f699c
25-09-10.14:51:39.976 [main ] INFO AgentTest - 测试结果:org.springframework.ai.openai.api.OpenAiApi@496f699c测试结果可以看到,OpenAiApi 已经构建完成。