@AIService实现原理
一、整体架构概览
在 LangChain4j 中,@AIService 注解能够将一个普通的 Java 接口直接转化为可调用的 AI 服务代理,其背后依托于 Spring 容器集成、动态代理与 LangChain4j 核心的执行链。整体调用层次可以概括为:
应用代码调用接口方法
动态代理(JDK Proxy)
ServiceInvocationHandler
解析方法注解与参数
构建聊天请求
路由到执行器
同步:SynchronousChatExecutor ──── ChatModel
流式:StreamingChatExecutor ──── StreamingChatModel
从 Spring 容器的角度来看,这一机制的启动由 AiServiceAutoConfig 触发,在 BeanFactory 后处理阶段完成接口定义到 AiServiceFactory 的动态替换,最终在第一次注入时由 AiServiceFactory 创建代理对象。
二、Spring 容器中的注册与 Bean 替换
应用启动时,AiServiceAutoConfig 内部注册了一个 BeanFactoryPostProcessor,名为 aiServicesRegisteringBeanFactoryPostProcessor。它在所有常规 Bean 定义加载完毕、实例化之前执行,负责扫描所有被 @AIService 标记的接口。
其核心处理流程如下:
-
1. 遍历所有 Bean 定义,找出标注了 @AIService的接口。 -
2. 构建新的 GenericBeanDefinition,将原始接口对应的 Bean 类型从接口本身替换为AiServiceFactory,并将接口的Class作为构造器参数传入:
GenericBeanDefinitionaiServiceBeanDefinition=newGenericBeanDefinition();
aiServiceBeanDefinition.setBeanClass(AiServiceFactory.class);
aiServiceBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aiServiceClass);
-
3. 注入依赖引用。通过一系列 addBeanReference方法,根据注解属性为AiServiceFactory配置其所依赖的 Spring Bean,例如:
img -
• chatModel:对话模型 -
• streamingChatModel:流式对话模型 -
• chatMemory:会话记忆 -
• retrievalAugmentor:RAG 检索增强器 -
• moderationModel:内容安全模型等 -
4. 替换原始 Bean 定义,从 BeanDefinitionRegistry中移除旧的接口定义,并注册新的AiServiceFactory定义,确保容器后续初始化的 Bean 实例由AiServiceFactory提供:
BeanDefinitionRegistryregistry= (BeanDefinitionRegistry) beanFactory;
registry.removeBeanDefinition(aiService);
registry.registerBeanDefinition(lowercaseFirstLetter(aiService), aiServiceBeanDefinition);
三、代理对象的创建
当应用启动后,其他 Bean 首次依赖注入被 @AIService 标记的接口时,Spring 会调用 AiServiceFactory#getObject() 来获取实例。该方法的核心逻辑是调用 DefaultAiServices#build(aiServiceClass),完成动态代理的构建。
在 DefaultAiServices#build 内部,主要完成两项工作:
-
1. 收集上下文信息。读取之前为 AiServiceFactory注入的所有依赖,封装为AiServiceContext对象。其中包含 ChatModel、StreamingChatModel、ChatMemory、RetrievalAugmentor 等关键组件,以及针对该服务接口的方法级别的增强配置(如全局 @SystemMessage 等)。 -
2. 创建 JDK 动态代理。使用 java.lang.reflect.Proxy.newProxyInstance生成代理对象,并传入一个统一的InvocationHandler(通常为ServiceInvocationHandler),所有对接口方法的调用均会被该 Handler 拦截:
Proxy.newProxyInstance(
aiServiceClass.getClassLoader(),
newClass<?>[]{aiServiceClass},
serviceInvocationHandler
);
其中 ServiceInvocationHandler 持有前面构建好的 AiServiceContext,使其能够在每一次方法拦截时,获取到正确的配置和模型实例。

四、代理方法调用与执行链
当应用调用 langChainAiService.someMethod(...) 时,调用将进入 ServiceInvocationHandler#invoke。此时执行链如下:
-
1. 方法识别与解析
Handler 通过反射获取被调用的Method对象,并结合AiServiceContext中缓存的解析结果,识别出: -
• 方法上的 @SystemMessage、@UserMessage等模板注解 -
• 参数上的 @V,@UserMessage,@UserName等注解 -
• 返回类型是同步类型( String,AiMessage,Response<AiMessage>等)还是流式类型(TokenStream,Flux<String>等) -
2. 请求构建
利用模板引擎将方法参数和注解中的占位符拼接成最终的ChatRequest(包括系统消息、用户消息、历史记忆等)。 -
3. 路由到执行器
根据返回类型选择合适的ChatExecutor:
img -
• 若为同步返回,则使用 SynchronousChatExecutor,其内部通过ChatModel.chat(...)发送请求并等待完整响应。 -
• 若为流式返回(例如 TokenStream),则使用StreamingChatExecutor,其内部通过StreamingChatModel.chat(...)获取流式令牌序列。 -
4. 结果处理
执行器将模型返回的原始结果映射为方法声明的返回类型,必要时进行转换(例如从AiMessage提取文本)。最终将结果返回给调用方,整个过程对调用者透明。
下面是一个典型的阻塞调用堆栈示意图,展示了代理调用如何穿透到具体的模型:
LangChainAiService.chat("你好")
→ $Proxy.invoke()
→ ServiceInvocationHandler.invoke()
→ SynchronousChatExecutor.execute()
→ ChatModel.chat(chatRequest)
→ 大模型服务
← AiMessage
← Response<AiMessage> / String
← 转换后的返回类型
← 最终结果
五、流式输出原理
流式输出与阻塞调用的区别仅在于 ServiceInvocationHandler 内部对返回类型的判断。当检测到方法声明返回 TokenStream 或 Flux<String> 等流式类型时,框架会切换为 StreamingChatExecutor,并与 StreamingChatModel 协作。
具体链路为:
LangChainAiService.streamChat("讲个故事")
→ $Proxy.invoke()
→ ServiceInvocationHandler.invoke()
→ StreamingChatExecutor.execute()
→ StreamingChatModel.chat(chatRequest, streamingResponseHandler)
→ 逐 token 回调
→ StreamingResponseHandler.onNext(token)
→ TokenStream 分发 token
← 流式结果
← TokenStream
← 返回 TokenStream 给调用方
在 StreamingChatModel 的实现中,通常通过 SSE 或 WebSocket 协议从大模型服务端持续接收令牌,并通过 StreamingResponseHandler 回调将令牌传递至用户侧的 TokenStream,从而实现流式输出的实时反馈。
六、利用的 Spring Boot 核心机制
1. 自动配置(Auto-Configuration)
AiServiceAutoConfig 是典型的 Spring Boot 自动配置类,通过 spring.factories 或 @AutoConfiguration 注册。它在应用启动时自动生效,负责初始化 aiServicesRegisteringBeanFactoryPostProcessor 以及所需的模型 Bean(如 ChatModel、StreamingChatModel)。
-
• 作用:无需手动编写 @Bean或 XML 配置,只要引入 starter 并在application.yml中添加模型参数,整个 AI 服务基础设施就自动就绪。
2. BeanFactoryPostProcessor
aiServicesRegisteringBeanFactoryPostProcessor 实现了 BeanDefinitionRegistryPostProcessor,这是 Spring 容器生命周期中最早期的扩展点之一。它能在所有 Bean 定义加载完成、但尚未实例化之前,对 Bean 定义进行动态修改。
-
• 具体操作:扫描 @AIService接口,将其对应的 Bean 定义替换为AiServiceFactory的定义,并注入相关依赖引用。 -
• 为何必须用这个扩展点:若等到 Bean 实例化之后再做替换,将无法影响依赖注入关系。
3. FactoryBean
AiServiceFactory 实现了 Spring 的 FactoryBean 接口,负责延迟创建实际的对象。
-
• 实现 getObject():该方法内部调用DefaultAiServices#build生成 JDK 动态代理。 -
• 关键作用:通过 FactoryBean,Spring 容器管理的 Bean 类型可以保持为原始接口(如Assistant),但实际创建的实例由AiServiceFactory控制,完美隐藏了代理创建细节。
4. 依赖注入(Dependency Injection)
替换后的 AiServiceFactory 定义通过 addBeanReference 声明了对其他 Bean(如 ChatModel)的依赖。Spring 容器会自动按名称或类型注入这些依赖,确保 AiServiceContext 能拿到所有必需的组件。
-
• 这体现了 Spring 的 IoC 能力:框架自动装配复杂的依赖图,用户只需声明 @AIService接口即可。
5. 动态代理(AOP 本质)
虽然这里没有使用 @Aspect 注解,但 JDK 动态代理本身就是 Spring AOP 的底层机制之一。Proxy.newProxyInstance 创建的代理会被 Spring 容器当作 Bean 注入给其他组件,所有方法调用都会被 ServiceInvocationHandler 拦截,本质上是一种无切入点的编程式 AOP 实现。
七、总结
@AIService 巧妙利用了 Spring Boot 的自动配置、BeanFactoryPostProcessor 的早期干预能力、FactoryBean 的延迟对象创建,以及依赖注入和动态代理机制,构建了一个零代码侵入的声明式 AI 服务框架。这些 Spring 特性的组合使得复杂的模型调用与代理路由对开发者完全透明。
它将声明式接口与具体的模型调用解耦,通过 Spring 容器扩展点完成 Bean 定义的动态替换,利用 JDK 动态代理和 InvocationHandler 实现了方法的拦截与智能路由。其内部架构可总结为:
-
• 配置层: @AIService注解及属性定义 -
• 容器集成层: AiServiceAutoConfig与AiServiceFactory实现 Spring 原生容器的透明集成 -
• 代理执行层: DefaultAiServices、ServiceInvocationHandler及上下文对象AiServiceContext完成请求构建与结果映射 -
• 模型适配层: ChatModel与StreamingChatModel统一抽象,使得底层实现可以灵活切换
这一设计既保留了 Spring 开发者的使用习惯,又提供了对同步与流式调用的一致抽象,是 LangChain4j 与 Spring 生态深度结合的典型范例。
在之前梳理的 @AIService 原理基础上,这里附加一个可直接运行的 Spring Boot 示例,帮助你直观感受从接口定义到代理调用、再到请求路由的完整过程。
示例:基于 @AIService 的对话服务
1. 依赖配置(pom.xml 关键片段)
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-spring-boot-starter</artifactId>
<version>0.35.0</version>
</dependency>
<!-- 根据需要引入模型适配器,这里以 OpenAI 为例 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>0.35.0</version>
</dependency>
2. 定义 AI 服务接口
使用 @AIService 声明一个接口,同时利用方法注解配置提示词模板。
package com.example.demo.service;
import dev.langchain4j.service.AiService;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
@AiService// 标记为 AI 服务接口
publicinterfaceAssistant {
@SystemMessage("你是一个乐于助人的客服助手,回答要简洁。")
String chat(@UserMessage String userMessage);
@SystemMessage("你是一个故事讲述者,根据主题生成一个短故事。")
@UserMessage("请根据以下主题生成故事:{{it}}")
String tellStory(@V("it") String topic);
}
3. 模型 Bean 配置
Spring Boot 自动配置会根据 application.yml 创建 ChatModel 和 StreamingChatModel 等 Bean,无需手动编写。例如:
langchain4j:
open-ai:
api-key:${OPENAI_API_KEY}
model-name:gpt-3.5-turbo
# 流式模型会同时自动创建 StreamingChatModel
4. 调用 AI 服务
在任意 Spring 组件中注入 Assistant 接口并直接调用,背后便是动态代理的能力。
package com.example.demo.controller;
import com.example.demo.service.Assistant;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/assistant")
publicclassAssistantController {
privatefinal Assistant assistant;
publicAssistantController(Assistant assistant) {
this.assistant = assistant;
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
// 调用代理对象,实际会通过 ServiceInvocationHandler 执行
return assistant.chat(message);
}
@GetMapping("/story")
public String story(@RequestParam String topic) {
return assistant.tellStory(topic);
}
}
示例与原理的映射
-
• 注册阶段:应用启动时, AiServiceAutoConfig内的BeanFactoryPostProcessor扫描到Assistant接口标注了@AIService,将其 Bean 定义替换为AiServiceFactory,并自动注入ChatModel(来自 OpenAI 自动配置)等依赖。 -
• 代理创建:当 AssistantController需要注入Assistant时,Spring 调用AiServiceFactory#getObject(),内部通过DefaultAiServices#build创建 JDK 动态代理。ServiceInvocationHandler持有AiServiceContext(包含ChatModel、方法注解元数据等)。 -
• 方法调用:调用 assistant.chat("你好")触发代理的invoke方法,ServiceInvocationHandler解析出@SystemMessage和@UserMessage模板,拼接出完整的ChatRequest,然后交给SynchronousChatExecutor调用ChatModel.chat(),最后将AiMessage内容转换为String返回。 -
• 流式调用(可扩展):如果接口方法返回 TokenStream,则会路由到StreamingChatExecutor,底层使用StreamingChatModel实现令牌流的实时传输。
夜雨聆风