SpringBoot3整合AI
玩一下AI
1. SSE協議
我們都知道tcp,ip,http,https,websocket等等協議,今天了解一個新的協議SSE協議(Server-Sent Events)
SSE(Server-Sent Events) 是一種允許服務器主動向客戶端推送數據的輕量級協議,基于 HTTP 長連接,實現 單向通信(服務器→客戶端)。它是 W3C 標準,瀏覽器原生支持,無需額外插件(如 EventSource API)
核心特點與工作原理
- 單向通信:僅服務器向客戶端發送數據,適合實時通知、日志流、實時更新等場景。
- 基于 HTTP:客戶端通過
GET請求建立連接,服務器返回特殊格式的文本流(text/event-stream),連接保持打開狀態,直到服務器主動關閉或超時。 - 自動重連:瀏覽器內置重連機制,連接斷開后自動嘗試重新連接。
- 數據格式:每條消息以
\n分隔,支持事件類型、數據內容、重試時間等字段,例如:
data: Hello, SSE! // 數據內容
event: customEvent // 自定義事件類型(可選)
id: 123 // 消息ID(可選)
retry: 5000 // 重連時間(毫秒,可選)
\n
適用于無需雙向通信,僅需服務器單向推送數據?!颈热绗F在的 gpt,豆包這個問答形式】
前端客戶端可以使用原生的 EventSource API:
// 創建EventSource實例,連接服務器
const eventSource = new EventSource('/sse-endpoint');
// 監聽默認事件("message")
eventSource.onmessage = (event) => {
console.log('Received:', event.data);
};
// 監聽自定義事件(如"customEvent")
eventSource.addEventListener('customEvent', (event) => {
console.log('Custom Event:', event.data);
});
// 處理錯誤
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
// 瀏覽器會自動重連,無需手動處理
};
服務端可用的就太多了。(本文以SpringBoot3.4.2為例子)
在知道這個協議之前,我們想要達到gpt這種問答形式,輸出內容是一點一點拼接的,該怎么弄呢?是不是還可以用websocket。
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 單向(服務器→客戶端) | 雙向(全雙工) |
| 協議 | 基于 HTTP(升級為長連接) | 獨立協議(ws:// 或 wss://) |
| 二進制支持 | 僅文本(text/event-stream) |
支持文本和二進制 |
| 自動重連 | 瀏覽器內置 | 需手動實現 |
| 復雜度 | 簡單(服務端實現輕量) | 較復雜(需處理握手、心跳) |
| 適用場景 | 服務器單向推送數據 | 雙向交互(聊天、實時協作) |
下面結合Spring Boot 簡單用一下SSE
// sse協議測試
@PostMapping(value = "/chat", produces = "text/event-stream;charset=UTF-8")
public SseEmitter streamSseMvc() {
// 感謝評論區:鍵盤三個鍵 指出該timeout問題。
// 有一點需要注意的是:這里的time_out參數,是SseEmitter(session會話)的存活時間. 這一點需要注意一下
SseEmitter emitter = new SseEmitter(30_000L);
// 模擬發送消息
System.out.println("SSE connection started");
ScheduledFuture<?> future = service.scheduleAtFixedRate(() -> {
try {
String message = "Message at " + System.currentTimeMillis();
emitter.send(SseEmitter.event().data(message));
} catch (IOException e) {
try {
emitter.send(SseEmitter.event().name("error").data(Map.of("error", e.getMessage())));
} catch (IOException ex) {
// ignore
}
emitter.completeWithError(e);
}
}, 0, 5, TimeUnit.SECONDS);
emitter.onCompletion(() -> {
System.out.println("SSE connection completed");
});
emitter.onTimeout(() -> {
System.out.println("SSE connection timed out");
emitter.complete();
});
emitter.onError((e) -> {
System.out.println("SSE connection error: " + e.getMessage());
emitter.completeWithError(e);
});
return emitter;
}
在SpringBoot中,用SseEmitter就可以達到這個效果了,它也和Websocket一樣有onXXX這種類似的方法。上面是使用一個周期性的任務,來模擬AI生成對話的效果的。emitter.send(SseEmitter.event().data(message)); 這個就是服務端向客戶端推送數據。
2. okhttp3+sse+deepseek
簡單示例:就問一句話
申請deepseekKey這里就略過了,各位讀者自行去申請。【因為deepseek官網示例是用的okhttp,所以我這里也用okhttp了】
我們先準備一個接口
@RestController
@RequestMapping("/deepseek")
public class DeepSeekController {
@Resource
private DeepSeekUtil deepSeekUtil;
/**
* 訪問deepseek-chat
*/
@PostMapping(value = "/chat", produces = "text/event-stream;charset=UTF-8")
public SseEmitter chatSSE() throws IOException {
SseEmitter emitter = new SseEmitter(60000L);
deepSeekUtil.sendChatReqStream("123456", "你會MySQL數據庫嗎?", emitter);
return emitter; // 這里把該sse對象返回了
}
private boolean notModel(String model) {
return !"deepseek-chat".equals(model) && !"deepseek-reasoner".equals(model);
}
}
可以看到我們創建了一個SseEmitter對象,傳給了我們自定義的工具
@Component
public class DeepSeekUtil {
public static final String DEEPSEEK_CHAT = "deepseek-chat";
public static final String DEEPSEEK_REASONER = "deepseek-reasoner";
public static final String url = "https://api.deepseek.com/chat/completions";
// 存儲每個用戶的消息列表
private static final ConcurrentHashMap<String, List<Message>> msgList = new ConcurrentHashMap<>();
// 1.調用api,然后以以 SSE(server-sent events)的形式以流式發送消息增量。消息流以 data: [DONE] 結尾。
public void sendChatReqStream(String uid, String message, SseEmitter sseEmitter) throws IOException {
// 1.構建一個普通的聊天body請求
AccessRequest tRequest = buildNormalChatRequest(uid, message);
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
// 封裝請求體參數
MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON.toJSONString(tRequest), mediaType);
// 構建請求和請求頭
Request request = new Request.Builder()
.url(url)
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "text/event-stream")
// 比如你的key是:s-123456
// .addHeader("Authorization", "Bearer s-123456")
.addHeader("Authorization", "Bearer 你的key")
.build();
// 創建一個監聽器
SseChatListener listener = new SseChatListener(sseEmitter);
RealEventSource eventSource = new RealEventSource(request, listener);
eventSource.connect(client);
}
private AccessRequest buildNormalChatRequest(String uid, String message) {
// 這里,我們messages,添加了一條“你會MySQL數據庫嗎?",來達到一種對話具有上下文的效果
List<Message> messages = msgList.computeIfAbsent(uid, k -> new ArrayList<>());
messages.add(new Message("user", message));
/*
[
{"system", "你好, 我是DeepSeek-AI助手!"},
{"user", "你會MySQL數據庫嗎?"}
]
*/
AccessRequest request = new AccessRequest();
request.setMessages(messages);
request.setModel(DEEPSEEK_CHAT);
request.setResponse_format(Map.of("type", "text"));
request.setStream(true); // 設置為true
request.setTemperature(1.0);
request.setTop_p(1.0);
return request;
}
@PostConstruct
public void init() {
List<Message> m = new ArrayList<Message>();
m.add(new Message("system", "你好, 我是DeepSeek-AI助手!"));
// 初始化消息列表
msgList.put("123456", m);
}
}
// 請求體,參考deepseek官網
public class AccessRequest {
private List<Message> messages;
private String model; // 默認模型為deepseek-chat
private Double frequency_penalty = 0.0;
private Integer max_tokens;
private Double presence_penalty = 0.0;
//{
// "type": "text"
//}
private Map<String, String> response_format;
private Object stop = null; // null
private Boolean stream; //如果設置為 True,將會以 SSE(server-sent events)的形式以流式發送消息增量。消息流以 data: [DONE] 結尾。
private Object stream_options = null;
private Double temperature; // 1
private Double top_p; // 1
private Object tools; // null
private String tool_choice = "none";
private Boolean logprobs = false;
private Integer top_logprobs = null;
// get set
}
監聽器
@Slf4j
public class SseChatListener extends EventSourceListener {
private SseEmitter sseEmitter;
public SseChatListener( SseEmitter sseEmitter) {
this.sseEmitter = sseEmitter;
}
/**
* 事件
*/
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
//log.info("sse數據:{}", data);
DeepSeekResponse deepSeekResponse = JSON.parseObject(data, DeepSeekResponse.class);
DeepSeekResponse.Choice[] choices = deepSeekResponse.getChoices();
try {
// 發送給前端【客戶端】
sseEmitter.send(SseEmitter.event().data(choices[0]));
} catch (IOException e) {
log.error("數據發送異常");
throw new RuntimeException(e);
}
}
/**
* 建立sse連接
*/
@Override
public void onOpen(final EventSource eventSource, final Response response) {
log.info("建立sse連接... {}");
}
/**
* sse關閉
*/
@Override
public void onClosed(final EventSource eventSource) {
log.info("sse連接關閉:{}");
}
/**
* 出錯了
*/
@Override
public void onFailure(final EventSource eventSource, final Throwable t, final Response response) {
log.error("使用事件源時出現異常......");
}
}
// DeepSeekResponse.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeepSeekResponse {
private String id;
private String object;
private Long created;
private String model;
private String system_fingerprint;
private Choice[] choices;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Choice {
private Integer index;
private Delta delta;
private Object logprobs;
private String finish_reason;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Delta {
private String content;
}
}
然后我們用apifox測試一下:

返回這些信息,然后把ai返回的存起來,具體怎么存,就靠讀者自行發揮了,添加到該對話,使該對話具有上下文。【DeepSeek /chat/completions API 是一個“無狀態” API,即服務端不記錄用戶請求的上下文,用戶在每次請求時,需將之前所有對話歷史拼接好后,傳遞給對話 API?!?/p>
[
{"system", "你好, 我是DeepSeek-AI助手!"},
{"user", "你會MySQL數據庫嗎?"},
{"ststem", "是的,我熟悉........"} // 把ai返回的存起來
]
下一次對話的時候,請求體AccessRequest里面的List<Message> messages就向上面那樣,再往后添加用戶問的消息。
上面的例子還有一些小問題,比如說什么時候斷開連接那些的。
3. SpringAI
Spring AI 是一個專注于 AI 工程的應用框架,其目標是將 Spring 生態的 “POJO 構建塊” 和模塊化設計引入 AI 場景,簡化企業數據與第三方模型的對接和使用。
下面快速接入deepseek
<!--maven的pom.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.feng.ai</groupId>
<artifactId>spring-ai-chat</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-ai-chat</name>
<description>spring-ai-chat</description>
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!--openAI-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.44</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
然后是配置文件
spring:
application:
name: spring-ai-chat
ai:
# The DeepSeek API doesn't support embeddings, so we need to disable it.
openai:
embedding:
enabled: false
base-url: https://api.deepseek.com
api-key: 你的key
chat:
options:
model: deepseek-reasoner # 使用推理模型
stream-usage: true
controller
@Slf4j
@RestController
@RequestMapping("/sp/deepseek")
public class SpDeepseekController {
@Resource( name = "openAiChatModel")
private OpenAiChatModel deepseekModel;
// 直接回答 --- stream-usage: false
//@GetMapping("/simpleChat")
//public R chat() {
// String call = deepseekModel.call("你好, 你會java嗎?");
// return R.success().setData("call", call);
//}
// 流式回答
@PostMapping(value = "/streamChat", produces = "text/event-stream;charset=UTF-8")
public Flux<SpMessage> streamChat(@RequestBody Map<String, String> p) {
String userMessage = p.get("userMessage");
String sessionId = p.get("sessionId");
Prompt prompt = new Prompt(new UserMessage(userMessage));
StringBuilder modelStr = new StringBuilder();
return deepseekModel.stream(prompt)
.doOnSubscribe(subscription -> log.info("SSE 連接已啟動: {}", sessionId))
.doOnComplete(() -> log.info("SSE 連接已關閉: {}", sessionId))
.doOnCancel(() -> log.info("SSE 連接已取消: {}", sessionId))
.timeout(Duration.ofSeconds(60)) // 超時設置
.filter(chatResponse -> chatResponse.getResult().getOutput().getText() != null) // 過濾掉空的響應
.map(chatResponse -> {
//log.info("SSE 響應: {}", chatResponse.getResult().getOutput());
modelStr.append(chatResponse.getResult().getOutput().getText());
return SpMessage.builder()
.role("system")
.content(chatResponse.getResult().getOutput().getText())
.build();
}
);
}
}
TODO:上面的對話沒有記憶,新的請求來了,ai模型并不會帶上以前的場景,故需要記憶化。 記憶化的同時還要注意如果把該會話歷史中所有的對話全部傳給deepseek的話,可能導致 token 超限,故還需要做一個窗口,避免把太多歷史對話傳過去了。
4. 延伸-Http遠程調用
在不討論微服務架構模式下,我們平時開發難免會碰到需要遠程調用接口的情況,【比如說上面調用deepseek的服務】,那么,我們怎么做才是比較好的方式呢?
一次良好的調用過程,我們應該要考慮這幾點:超時處理、重試機制、異常處理、日志記錄;
此外,于性能來說,我們要避免頻繁創建連接帶來的開銷,可以使用連接池管理;
① RestTemplate
RestTemplate 是一個同步的 HTTP 客戶端,提供了簡單的方法來發送 HTTP 請求并處理響應。它支持常見的 HTTP 方法(GET、POST、PUT、DELETE 等),并能自動處理 JSON/XML 的序列化和反序列化,這個也是我們非常熟悉的。
下面由于是基于SpringBoot3.4.3,所以httpclient的版本是httpclient5.
@Configuration
public class RestConfig {
@Bean("restTemplate")
public RestTemplate restTemplate() {
// 使用Apache HttpClient連接池(替代默認的 SimpleClientHttpRequestFactory)
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100); // 最大連接數
connectionManager.setDefaultMaxPerRoute(20); // 每個路由的最大連接數
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.evictIdleConnections(TimeValue.of(10, TimeUnit.SECONDS))// 清理空閑連接
.build();
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(3000); // 連接超時(ms)
factory.setReadTimeout(5000); // 讀取超時(ms)
RestTemplate restTemplate = new RestTemplate(factory);
// 添加自定義的錯誤處理器
restTemplate.setErrorHandler(new CustomErrorHandler());
// 添加日志攔截器
restTemplate.getInterceptors().add(new LoggingInterceptor());
return restTemplate;
}
}
@Slf4j
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
@NotNull
@Override
public ClientHttpResponse intercept(HttpRequest request, @NotNull byte[] body, ClientHttpRequestExecution execution) throws IOException {
log.info("請求地址: {} {}", request.getMethod(), request.getURI());
log.info("請求頭: {}", request.getHeaders());
log.info("請求體: {}", new String(body, StandardCharsets.UTF_8));
ClientHttpResponse response = execution.execute(request, body);
log.info("響應狀態碼: {}", response.getStatusCode());
return response;
}
}
@Slf4j
public class CustomErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(@NotNull ClientHttpResponse response) throws IOException {
// 獲取 HTTP 狀態碼
HttpStatusCode statusCode = response.getStatusCode();
return statusCode.isError(); // 判斷狀態碼是否為錯誤狀態碼 【4xx、5xx是true,執行下面的handleError,其他的就false】
}
@Override
public void handleError(@NotNull URI url, @NotNull HttpMethod method, @NotNull ClientHttpResponse response) throws IOException {
log.info("請求地址: {} Method: {}",url, method);
HttpStatusCode code = response.getStatusCode();
if (code.is4xxClientError()) {
log.info("客戶端錯誤:{}", code.value());
// xxx
} else {
log.info("服務器錯誤:{}", code.value());
// xxx
}
}
}
重試降級機制:
@Configuration
@EnableRetry // 開啟重試 -- 需要引入AOP
public class RetryConfig {
}
// 在service層調用的時候
@Service
public class OrderService {
@Resource
private RestTemplate restTemplate;
@Retryable(
maxAttempts = 3,
backoff = @Backoff(delay = 1000, multiplier = 2), // 重試間隔 1s, 2s, 4s
retryFor = {Exception.class} // 默認重試所有異常
//retryFor = {ResourceAccessException.class} // 僅在網絡異常時重試
)
public String queryOrder(String orderId) {
return restTemplate.getForObject("/orders/" + orderId, String.class); // 遠程調用
}
@Recover // 重試全部失敗后的降級方法
public String fallbackQueryOrder(ResourceAccessException e, String orderId) {
return "默認訂單";
}
}
當然還可以再遠程調用那里try catch起來,有異常的時候,throw出去可以被@Retryable捕獲。
② RestClient
Spring Framework 6.1 引入了全新的同步 HTTP 客戶端 RestClient,它在底層使用了與 RestTemplate 相同的基礎設施(比如消息轉換器和攔截器),但提供了如同 WebClient 一樣的現代、流式(fluent)API,兼顧了簡潔性與可復用性。與傳統的阻塞式 RestTemplate 相比,RestClient 更加直觀易用,同時也保持了對同步調用語境的全量支持
同步調用:RestClient 是一個阻塞式客戶端,每次 HTTP 請求都會阻塞調用線程直到響應完成。
流式 API:借鑒 WebClient 的設計風格,所有操作均可鏈式調用,代碼更具可讀性和可維護性。
復用基礎組件:與 RestTemplate 共用 HTTP 請求工廠、消息轉換器、攔截器等組件,便于平滑遷移與統一配置
@Configuration
@Slf4j
public class RestClientConfig {
@Bean("serviceARestClient")
public RestClient restClientA(@Value("${api-service.a-base-url}") String baseUrl) {
// 創建連接池
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
manager.setMaxTotal(100);
manager.setDefaultMaxPerRoute(20);
// 創建HttpClient
HttpClient httpClient = HttpClientBuilder.create()
.setConnectionManager(manager)
.build();
// 創建HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(3000);
factory.setReadTimeout(5000);
return RestClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultCookie("myCookie", "1234")
.requestInterceptor(clientRequestInterceptor())
.requestFactory(factory) // 連接池與超時
.build();
}
@Bean("serviceBRestClient")
public RestClient restClientB(@Value("${api-service.b-base-url}") String baseUrl) {
return RestClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultCookie("myCookie", "1234")
.requestInterceptor(clientRequestInterceptor())
.build();
}
private ClientHttpRequestInterceptor clientRequestInterceptor() {
return (request, body, execution) -> {
// 添加統一請求頭(如認證信息)
request.getHeaders().add("my-head", "head-gggggg");
// 日志記錄
log.debug("Request: {} {}", request.getMethod(), request.getURI());
request.getHeaders().forEach((name, values) ->
values.forEach(value -> log.debug("Header: {}={}", name, value)));
ClientHttpResponse response = execution.execute(request, body);
log.debug("Response status: {}", response.getStatusCode());
return response;
};
}
}
簡單調用:
@Service
public class AService {
@Resource(name = "serviceARestClient")
private RestClient restClientA;
public String queryA(String a) {
return restClientA.get()
.uri("/api/a?a={a}", a)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new HttpClientErrorException(response.getStatusCode());
})
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new ServerErrorException(response.getStatusCode().toString(), null);
})
.body(String.class);
}
// 復雜query參數
public String queryA(String a, String b) {
return restClientA.get()
.uri( uriBuilder ->
uriBuilder.path("/api/bbb")
.queryParam("a", 25)
.queryParam("b", "30")
.build()
)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new HttpClientErrorException(response.getStatusCode());
})
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new ServerErrorException(response.getStatusCode().toString(), null);
})
.body(String.class);
}
// post
public String postA(String a) {
HashMap<String, Object> map = new HashMap<>();
map.put("a", a); map.put("page", 1); map.put("size", 10);
return restClientA.post()
.uri("/api/post")
.body(map)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
throw new HttpClientErrorException(response.getStatusCode());
})
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new ServerErrorException(response.getStatusCode().toString(), null);
})
.body(String.class);
}
}
③ WebClient
Spring框架中包含的原始web框架Spring web MVC是專門為Servlet API和Servlet容器構建的。響應式堆棧web框架Spring WebFlux是在5.0版本中添加的。它是完全非阻塞的,支持響應式流回壓,并運行在諸如Netty、Undertow和Servlet容器之類的服務器上。
這兩個web框架都鏡像了它們的源模塊的名字(Spring-webmvc和Spring-webflux 他們的關系圖如下,節選自官網),并在Spring框架中共存。每個模塊都是可選的。應用程序可以使用其中一個或另一個模塊,或者在某些情況下,兩者都使用——例如,Spring MVC控制器與響應式WebClient。它對同步和異步以及流方案都有很好的支持。
非阻塞異步模型:基于 Reactor 庫(Mono/Flux)實現異步調用,避免線程阻塞,通過少量線程處理高并發請求,顯著提升性能
函數式編程:支持鏈式調用(Builder 模式)與 Lambda 表達式,代碼更簡潔
流式傳輸:支持大文件或實時數據的分塊傳輸(Chunked Data),減少內存占用。

這里就不介紹了。
| 特性 | RestTemplate | RestClient | WebClient |
|---|---|---|---|
| 模型 | 阻塞,同步 | 阻塞,同步,流式 API | 非阻塞,響應式【學習曲線較為陡峭】 |
| API 風格 | 模板方法 (getForObject, exchange 等) |
鏈式流式 (get().uri()...retrieve()) |
鏈式流式,支持 Mono/Flux |
| 可擴展性 | 依賴大量重載方法 | 可配置攔截器、初始器,支持自定義消息轉換器 | 強大的過濾器、攔截器與背壓支持 |
| 性能 | 受限于線程池 | 同 RestTemplate,但更簡潔 |
更佳,適合高并發場景 |
| 遷移成本 | 低 | 較低,可自然承接現有 RestTemplate 配置 |
較高,需要重構為響應式編程 |

浙公網安備 33010602011771號