<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      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 配置 較高,需要重構為響應式編程

      end. 參考

      1. https://segmentfault.com/a/1190000021133071 【思否-Spring5的WebClient使用詳解
      2. https://docs.spring.io/spring-framework/reference/integration/rest-clients.html 【Spring官網】
      posted @ 2025-05-18 19:53  別來無恙?  閱讀(775)  評論(5)    收藏  舉報
      主站蜘蛛池模板: 日韩黄色av一区二区三区| 久久被窝亚洲精品爽爽爽| 内地自拍三级在线观看| 中文字幕国产精品日韩| 性欧美乱熟妇xxxx白浆| 四虎库影成人在线播放| 无码人妻精品丰满熟妇区| 黑人巨大亚洲一区二区久| 国内综合精品午夜久久资源| 国产精品久久久久鬼色| 毛葺葺老太做受视频| 国产不卡一区二区在线| 亚洲精品美女一区二区| 亚洲精品日韩中文字幕| 日韩人妻中文字幕精品| 黑人大战中国av女叫惨了| 国产视频 视频一区二区| 滕州市| 在线观看精品日本一区二| 一区二区中文字幕av| 疯狂做受XXXX高潮国产| 中国性欧美videofree精品| 国产精品理论片| 亚洲精品亚洲人成人网| 中文字幕午夜福利片午夜福利片97| 精品无套挺进少妇内谢| 深夜福利啪啪片| 黑人猛精品一区二区三区| 少妇被粗大的猛烈xx动态图| 精品国产粉嫩一区二区三区| 日本中文字幕在线| 亚洲成av人片在www鸭子| 亚洲综合小说另类图片五月天| 亚洲av片在线免费观看| 国产偷自视频区视频| 黑人好猛厉害爽受不了好大撑| 精品亚洲综合一区二区三区| 人妻少妇偷人精品一区| 在线精品视频一区二区三四| 欧美日韩精品一区二区在线观看| 久久天堂无码av网站|