乐于分享
好东西不私藏

@AIService实现原理

@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. 1. 遍历所有 Bean 定义,找出标注了 @AIService 的接口。
  2. 2. 构建新的 GenericBeanDefinition,将原始接口对应的 Bean 类型从接口本身替换为 AiServiceFactory,并将接口的 Class 作为构造器参数传入:
GenericBeanDefinitionaiServiceBeanDefinition=newGenericBeanDefinition();
aiServiceBeanDefinition.setBeanClass(AiServiceFactory.class);
aiServiceBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aiServiceClass);
  1. 3. 注入依赖引用。通过一系列 addBeanReference 方法,根据注解属性为 AiServiceFactory 配置其所依赖的 Spring Bean,例如:

    img
    • • chatModel:对话模型
    • • streamingChatModel:流式对话模型
    • • chatMemory:会话记忆
    • • retrievalAugmentor:RAG 检索增强器
    • • moderationModel:内容安全模型等
  2. 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. 1. 收集上下文信息。读取之前为 AiServiceFactory 注入的所有依赖,封装为 AiServiceContext 对象。其中包含 ChatModel、StreamingChatModel、ChatMemory、RetrievalAugmentor 等关键组件,以及针对该服务接口的方法级别的增强配置(如全局 @SystemMessage 等)。
  2. 2. 创建 JDK 动态代理。使用 java.lang.reflect.Proxy.newProxyInstance 生成代理对象,并传入一个统一的 InvocationHandler(通常为 ServiceInvocationHandler),所有对接口方法的调用均会被该 Handler 拦截:
Proxy.newProxyInstance(
    aiServiceClass.getClassLoader(),
newClass<?>[]{aiServiceClass},
    serviceInvocationHandler
);

其中 ServiceInvocationHandler 持有前面构建好的 AiServiceContext,使其能够在每一次方法拦截时,获取到正确的配置和模型实例。

img

四、代理方法调用与执行链

当应用调用 langChainAiService.someMethod(...) 时,调用将进入 ServiceInvocationHandler#invoke。此时执行链如下:

  1. 1. 方法识别与解析
    Handler 通过反射获取被调用的 Method 对象,并结合 AiServiceContext 中缓存的解析结果,识别出:
    • • 方法上的 @SystemMessage@UserMessage 等模板注解
    • • 参数上的 @V@UserMessage@UserName 等注解
    • • 返回类型是同步类型(StringAiMessageResponse<AiMessage> 等)还是流式类型(TokenStreamFlux<String> 等)
  2. 2. 请求构建
    利用模板引擎将方法参数和注解中的占位符拼接成最终的 ChatRequest(包括系统消息、用户消息、历史记忆等)。
  3. 3. 路由到执行器
    根据返回类型选择合适的 ChatExecutor

    img
    • • 若为同步返回,则使用 SynchronousChatExecutor,其内部通过 ChatModel.chat(...) 发送请求并等待完整响应。
    • • 若为流式返回(例如 TokenStream),则使用 StreamingChatExecutor,其内部通过 StreamingChatModel.chat(...) 获取流式令牌序列。
  4. 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(如 ChatModelStreamingChatModel)。

  • • 作用:无需手动编写 @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 原生容器的透明集成
  • • 代理执行层DefaultAiServicesServiceInvocationHandler 及上下文对象 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 实现令牌流的实时传输。