Spring Boot 中使用 Netty

2025/4/15

向?

一、什么是Netty

Netty 是 Java 中一個非常高性能的網絡通信框架,用來開發服務器和客戶端程序,主要用于處理 TCP/UDP 的網絡連接,比如:

  • 聊天服務

  • 實時推送

  • 高并發網絡通信(比如游戲、IoT、金融系統)

你可以把 Netty 理解為一種比 Java 原生 Socket 更方便、性能更強的“網絡搭建工具”。再詳細了解Netty的工作原理之前,我們先來看一下Java中最簡單的客戶端和服務器之間的連接。

二、最簡單的 Java 網絡通信

2.1什么是“客戶端”和“服務端”?

我們先理解一個現實生活的比喻奶茶店點單系統

  • 服務端(Netty 服務):奶茶店(固定位置,等待別人來點單)

  • 客戶端(瀏覽器、手機 App、Netty 客戶端):顧客(誰想喝奶茶誰來)

  • 通信方式(TCP):電話(通過電話點單)

還可以更加省略一點來說就是?💬 一個人發送消息(客戶端) ? 另一個人接收并回復(服務端)

2.2服務端

import java.io.*;
import java.net.*;public class Server {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(8080); // 在8080端口等別人來找System.out.println("服務端啟動,等待客戶端連接...");Socket socket = serverSocket.accept(); // 有人來連接,就接收它System.out.println("客戶端連接進來了");// 輸入輸出流:用來讀寫數據BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 讀PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 寫String line;while ((line = in.readLine()) != null) {System.out.println("收到客戶端消息:" + line);out.println("我收到了:" + line); // 回給客戶端}socket.close(); // 關閉連接serverSocket.close();}
}

2.3客戶端

import java.io.*;
import java.net.*;public class Client {public static void main(String[] args) throws Exception {Socket socket = new Socket("127.0.0.1", 8080); // 連接本機服務端System.out.println("連接服務端成功!");// 輸入輸出BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in)); // 你鍵盤輸入PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 發消息BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 收消息String msg;while ((msg = userInput.readLine()) != null) {out.println(msg); // 發給服務端String reply = in.readLine(); // 讀取服務端返回System.out.println("服務端說:" + reply);}socket.close();}
}

2.4 服務端和客戶端之間的通信

首先是服務端先啟動,會有如下顯示,同時告訴顧客我家的店的端口號是8080。

服務端啟動,等待客戶端連接...

然后有顧客想買東西,通過 new Socket("127.0.0.1", 8080); // 連接本機服務端,即走進服務器店的大門8080。而在服務器這端,通過serverSocket.accept(); 看見有人來連接,就接收它,服務它。這時候客戶端會輸出如下

連接服務端成功!

服務端會輸出如下:

客戶端連接進來了

在客戶端通過控制臺輸入:hello后,即通過如下代碼接收到了你的輸入,并存放在userInput變量中。

BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));

客戶端通過out對象發消息?

 PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 發消息

?客戶端通過in對象接受消息?

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 收消息

當 msg = userInput.readLine()) != null ,即當檢測到客戶端要發送消息就執行如下代碼:

out.println(msg); // 發給服務端
String reply = in.readLine(); // 讀取服務端返回
System.out.println("服務端說:" + reply);

out.println(msg)后,就將信息發送到了服務器端,服務器端就會輸出如下

收到客戶端消息:hello

同時在服務器端通過 out.println("我收到了:" + line); 回給客戶端,客戶端通過reply接收到消息,客戶端就會輸出

服務端說:我收到了:hello

2.5 客戶端和服務器端的關系如下:?

角色作用
Server永遠在等別人來(監聽端口)
Client主動發起連接
Input/Output收發消息用的“通道”

?二、為什么需要線程模型?(Thread Model)

理解了基礎的服務端和客戶端通信,我們可以繼續深入,了解一些稍微復雜一點的概念,即線程

在前面那個簡單的服務端/客戶端例子中,服務端是“串行”的,意思是:

  • 它在等待一個客戶端連接。

  • 收到消息后再回復,接著等待下一個連接。

但是如果你有很多客戶端同時發消息,服務端就會變得很慢,因為它只能一個一個地處理請求。

所以,我們需要更高效的處理方式:并發編程并發編程意味著能夠同時處理多個任務,不等一個任務完成再開始下一個。而且每個任務都不會相互阻塞。這就是 線程池事件循環模型 的價值所在。在 Netty 中:

  • 線程池:多個線程可以同時處理多個連接。

  • 事件循環模型:每個線程(事件循環)只負責自己的任務,它會不停地輪詢事件,比如客戶端連接、數據讀取等。

三、什么是“阻塞”和“非阻塞”?

? 阻塞:你去餐廳吃飯,服務員給你一個菜單,但你必須等著他們準備好菜才能吃,期間你不能干別的事。

? 非阻塞:你點菜后,服務員會告訴你“稍等一會兒”,然后你可以做其他事。只要菜做好了,服務員會告訴你,打斷你做其他事,給你菜。

TCP 通信中的阻塞和非阻塞:

  • 阻塞:當你發起連接或請求時,程序會一直等待,直到連接建立或數據返回。

  • 非阻塞:發起請求后,程序不再等待,會繼續執行其他任務。如果有返回結果,程序會處理返回。

Netty 默認就是 非阻塞 的,這樣它能同時處理很多連接,不會被一個請求堵住。

四、Netty 是如何處理高并發的?

Netty通過使用一個線程模型 EventLoop(事件循環)來處理高并發。EventLoopGroup:管理多個線程(可以理解為多個服務員),負責處理網絡事件。EventLoop:每個線程負責自己的一部分任務,比如處理某一個客戶端的請求。

舉例來看就是:

  • 一個服務端線程,負責監聽連接(等待“顧客”進店)。
  • 多個工作線程,負責實際的通信(幫“顧客”點單、做菜)。

4.1?EventLoop 和 NIO 的關系

Netty 使用了 NIO(非阻塞 IO) 模型。NIO 讓一個線程能處理多個連接。具體來說:

  • 使用 Selector 輪詢(檢查)每個連接的狀態,看是否有數據到達。

  • 使用 Channel 來表示網絡連接。

  • 使用 Buffer 來讀取和寫入數據。

這個模型讓 Netty 在面對數千個并發連接時,也能保持高效。

總結來看,Netty的EventLoopGroup管理多個線程,每個線程只干特定的事情,假設某個線程只干連接客戶端這個事情,又由于Netty引入了NIO模型,所以又讓這個負責處理連接的線程具備了同時處理多個連接請求的能力

五、實際的 Netty 服務端示例

public class EchoServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 負責接收連接EventLoopGroup workerGroup = new NioEventLoopGroup(); // 負責處理請求try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new EchoServerHandler());ChannelFuture f = b.bind(8080).sync();  // 綁定端口,開始監聽f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}// 處理客戶端發來的消息public static class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 接收到數據后直接寫回給客戶端System.out.println("收到消息:" + msg);ctx.writeAndFlush(msg);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close(); // 出現異常關閉連接}}
}
  • ServerBootstrap:是 Netty 中用于啟動服務端的核心類,啟動 Netty 服務端。
  • bossGroup 和 workerGroup:管理事件循環,分別處理接收連接和處理數據的任務。
  • EchoServerHandler:是我們自定義的業務處理邏輯,收到客戶端的消息就原封不動地回傳。

六、實際使用的Netty

6.1 NettyServer類

ServerBootstrap:Netty服務器啟動的核心類。

ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ServerChannelInitializer(delimiter, maxFrameLength)).localAddress(socketAddress).option(ChannelOption.SO_BACKLOG, 1024).childOption(ChannelOption.SO_KEEPALIVE, true);
  • .group(bossGroup, workGroup) 配置監聽線程和工作線程。
  • .channel(NioServerSocketChannel.class): 這里指定了服務端的 Channel 類型。NioServerSocketChannel 適用于 NIO(非阻塞 IO),這是一種處理高并發的方式。
  • .childHandler(new ServerChannelInitializer(delimiter, maxFrameLength)): 為每個連接配置一個 ChannelInitializer,在每個連接初始化時(每個客戶端連接時)會被調用。ServerChannelInitializer 是自定義的初始化類,配置如何處理數據的編解碼、業務邏輯等。
  • .localAddress(socketAddress): 配置綁定的本地地址和端口
  • .option(ChannelOption.SO_BACKLOG, 1024): 配置服務器端的連接隊列大小。隊列最大長度設置為 1024。
  • .childOption(ChannelOption.SO_KEEPALIVE, true): 設置 TCP KeepAlive,確保連接在空閑時依然存活。

6.1.1啟動并綁定端口

ChannelFuture channelFuture = serverBootstrap.bind(socketAddress).sync();
  • .bind(socketAddress): 綁定到指定的 socketAddress,開始監聽客戶端的連接。

  • .sync(): 阻塞方法,直到端口綁定成功并啟動后,才會繼續執行。ChannelFuture 用于獲取當前操作的結果(是否成功綁定)

6.2 SeverChannelInitializer類

在NettyServer類中,我們是調用了SeverChannelInitializer類的,我們使用SeverChannelInitializer類來配置如何處理數據的編解碼、業務邏輯等。當每個客戶端連接進來時,配置它的 Channel 的“流水線”——也就是這個連接收到/發送數據時,按什么順序怎么處理。可以把它理解為工廠生產線的“組裝說明書”。

package com....nettyService;import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LoggingHandler;public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {private String DELIMITER;private int MAXFRAMELENGTH;public ServerChannelInitializer(String delimiter, int maxFrameLength) {DELIMITER = delimiter;MAXFRAMELENGTH = maxFrameLength;}@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast("logging", new LoggingHandler("DEBUG"));socketChannel.pipeline().addLast("decoder", new HL7Decoder());socketChannel.pipeline().addLast("encoder", new HL7Encoder());socketChannel.pipeline().addLast(new NettyServerHandler());}}

SeverChannelInitializer首先繼承了ChannelInitializer<SocketChannel>,這樣沒有一個新的連接的時候Netty 就會調用 initChannel() 方法,給這個連接安裝一套“處理器組合”(pipeline)。

而這一套“處理器組合”當接收到客戶端發送的消息執行順序如下:

【客戶端】==> socketChannel
? ? ? ? ?↓
[LoggingHandler](打印日志)
? ? ? ? ?↓
[HL7Decoder](解碼消息)
? ? ? ? ?↓
[NettyServerHandler](業務處理)

當服務端要回復消息,其執行順序如下:

? ?NettyServerHandler.write()
? ? ? ? ?↓
? ? ? ?[HL7Encoder](編碼為字節)
? ? ? ? ?↓
? ? ? ? ?[LoggingHandler](打印)
? ? ? ? ?↓
? ? ? ? ? 【客戶端】

6.3 NettySeverHandler類

在SeverChannelInitializer類中,其寫好了業務處理順序,在處理業務時,其處理業務的核心是NettySeverHandler類來實現的

package com.....nettyService;import com...component.commons.utils.BeanUtils;
import com...emergency.service.impl.BS2800MPacketParse;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Component
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {private static final Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);private BS2800MPacketParse bs2800MPacketParse = BeanUtils.getBean(BS2800MPacketParse.class);/*** 裝載所有客戶端channel的組*/private static final Map<String, Channel> ipChannelMap = new HashMap<>();/*** 客戶端連接過來會觸發*/@Overridepublic void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {Channel channel = channelHandlerContext.channel();ipChannelMap.put(channel.remoteAddress().toString(), channel);logger.info("客戶端連接:" + channelHandlerContext);}/*** 客戶端發消息過來會觸發*/@Overridepublic void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {Channel channel = channelHandlerContext.channel();logger.info("服務端接收到客戶端消息");
//        logger.info("發送消息的客戶端地址:" + channel.remoteAddress());logger.info("發送消息的客戶端所發消息:" + msg);String result = msg;String msa = handleParams(channelHandlerContext, result);if (ObjectUtils.isNotEmpty(msa)) {channel.writeAndFlush(msa);}}@Overridepublic void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {super.channelReadComplete(channelHandlerContext);}@Overridepublic void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {Channel channel = channelHandlerContext.channel();// 當通道變為非活動狀態(斷開連接)時,將其從 ChannelGroup 中移除String ip = channel.remoteAddress().toString();if (ipChannelMap.containsKey(ip)) {ipChannelMap.remove(ip);if (!channel.isActive() || channel == null) {channelHandlerContext.close();}}logger.info("客戶端地址為:" + ip + "的連接已斷開");}/*** 發生異常觸發*/@Overridepublic void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {logger.warn(cause.toString());}/*** 處理接收報文消息*/public String handleParams(ChannelHandlerContext channelHandlerContext, String msg) {String msa = null;SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");Channel channel = channelHandlerContext.channel();if (channel.remoteAddress().toString().contains("10.10.51.213")) {if (ObjectUtils.isNotEmpty(msg)) {String result[] = msg.split("\r");if (ObjectUtils.isNotEmpty(result) && result.length > 0) {String msh = null;for (String string : result) {if (string.contains("MSH")) {msh = string;}}if (msh.contains("ORU^R01")) {Date date = new Date();String temp[] = msh.split("\\|", -1);if (ObjectUtils.isNotEmpty(temp) && temp.length > 9) {msa = "MSH|^~\\&|||||" + dateFormat.format(date) + "||ACK^R01|" + temp[9] + "|P|2.3.1||||0||ASCII|||";String str = "MSA|AA|" + temp[9] + "|Message accepted|||0|";msa = msa + "\r" + str;Map<String, String> paramMap = new HashMap<>();paramMap.put(temp[9], msg);bs2800MPacketParse.parse(msg);return msa;}}}}}return msa;}}

6.3.1繼承

NettyServerHandler繼承SimpleChannelInboundHandler<String> 每次接收到客戶端消息(已經是 String 類型,說明解碼器已完成解碼),就會觸發 channelRead0() 方法。我們可以在這里處理邏輯、保存數據、做回復等

6.3.2channelActive

有客戶端連接進來時,Netty 會自動調用這個方法。將客戶端的 Channel 保存到 ipChannelMap 中,方便后面用 IP 找到連接。同時打印客戶端連接信息。

6.3.3channelRead0

每當客戶端發一條消息過來,就會自動執行這里!先獲取當前的 Channel(對應客戶端)

Channel channel = channelHandlerContext.channel();

打印日志,方便調試看到收到的數據

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75979.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75979.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75979.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【QT】 QT定時器的使用

QT定時器的使用 1. QTimer介紹&#xff08;1&#xff09;QTimer的使用方法步驟示例代碼1&#xff1a;定時器的啟動和關閉現象&#xff1a;示例代碼2&#xff1a;定時器每隔1s在標簽上切換圖片現象&#xff1a; (2)實際開發的作用 2.日期 QDate(1)主要方法 3.時間 QTime(1)主要方…

排序算法詳細介紹對比及備考建議

文章目錄 排序算法對比基本概要 算法逐一介紹1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 選擇排序&#xff08;Selection Sort&#xff09;3. 插入排序&#xff08;Insertion Sort&#xff09;&#x1f31f;&#x1f31f;4. 希爾排序&#xff08;Shell Sort&#xff09…

Docker華為云創建私人鏡像倉庫

Docker華為云創建私人鏡像倉庫 在華為云官網的 產品 中搜索 容器鏡像服務 &#xff1a; 或者在其他頁面的搜索欄中搜索 容器鏡像服務 &#xff1a; 進入到頁面后&#xff0c;點擊 創建組織 &#xff08;華為云的鏡像倉庫稱為組織&#xff09;&#xff1a; 設置組織名字后&…

微信小程序-自定義toast

微信小程序-自定義toast 微信小程序原生的toast最多能顯示兩行文字。方案1:方案2 微信小程序原生的toast最多能顯示兩行文字。 有時候并不能滿足業務需求。所以我們需要使用第三方或者自定義。 方案1: 第三方vant-toast 微信小程序下載引入第三方vant之后。 在需要使用的頁面…

安卓手游逆向

一、環境安裝 1.1、安裝Java環境 1.2、安裝SDK環境 1.3、安裝NDK環境 二、APK 2.1、文件結構 2.2、打包流程 2.3、安裝流程 應用安裝涉及目錄: system/app ----->系統自帶的應用程序,獲得adb root權限才能刪除。 data/app ------->用戶程序安裝的目錄,安裝…

VSCode Continue 擴展踩坑記錄

Trae 是一款很優秀的 AI 開發工具&#xff0c;但目前支持的平臺還較少&#xff0c;比如不支持 Win7&#xff0c;不支持 Linux&#xff0c;為了在這些平臺上進行開發&#xff0c;我需要尋找一個替代品。經過網上搜索&#xff0c;選擇了 VSCode Continue 擴展&#xff0c;但在使…

Elasticsearch:AI 助理 - 從通才到專才

作者&#xff1a;來自 Elastic Thorben Jndling 在 AI 世界中&#xff0c;關于構建針對特定領域定制的大型語言模型&#xff08;large language models - LLM&#xff09;的話題備受關注 —— 不論是為了更好的安全性、上下文理解、專業能力&#xff0c;還是更高的準確率。這個…

【ARM】MDK燒錄提示Error:failed to execute‘ ‘

1、 文檔目標 解決在燒錄程序的時候&#xff0c;因為選擇了錯誤的燒錄方式導致下載失敗的情況。 2、 問題場景 在燒錄程序的時候出現了提示&#xff1a;“Error&#xff1a;failed to execute ’ ”&#xff08;如圖2-1&#xff09;。檢測Target->Debug配置發現沒有問題&a…

系統分析師(六)-- 計算機網絡

概述 TCP/IP 協議族 DNS DHCP 網絡規劃與設計 邏輯網絡設計 物理網絡設計 題目 層次化網絡設計 網絡冗余設計 綜合布線系統 IP地址 網絡接入技術 其他網絡技術應用 物聯網

優化運營、降低成本、提高服務質量的智慧物流開源了

智慧物流視頻監控平臺是一款功能強大且簡單易用的實時算法視頻監控系統。它的愿景是最底層打通各大芯片廠商相互間的壁壘&#xff0c;省去繁瑣重復的適配流程&#xff0c;實現芯片、算法、應用的全流程組合&#xff0c;從而大大減少企業級應用約95%的開發成本可通過邊緣計算技術…

從One-Hot到TF-IDF:NLP詞向量演進解析與業務實戰指南(一)

從One-Hot到TF-IDF&#xff1a;詞向量演進之路 開場白&#xff1a; 想象一下&#xff0c;你試圖用Excel表格分析《紅樓夢》的情感傾向——每個字詞都是孤立的單元格&#xff0c;計算機看到的只有冰冷的0和1&#xff0c;而“黛玉葬花”的凄美意境卻消失得無影無蹤。這就是NLP工…

2. kubernetes操作概覽

以下是 Kubernetes 的核心操作概覽&#xff0c;涵蓋常用命令、資源管理和典型場景的操作流程&#xff1a; 1. 核心操作工具 (1) kubectl 命令行工具 Kubernetes 的所有操作均通過 kubectl 實現&#xff0c;常用命令如下&#xff1a; 操作類型命令示例作用說明查看資源狀態ku…

從Ampere到Hopper:GPU架構演進對AI模型訓練的顛覆性影響

一、GPU架構演進的底層邏輯 AI大模型訓練效率的提升始終與GPU架構的迭代深度綁定。從Ampere到Hopper的演進路徑中&#xff0c;英偉達通過?張量核心升級?、?顯存架構優化?、?計算范式革新?三大技術路線&#xff0c;將LLM&#xff08;大語言模型&#xff09;訓練效率提升至…

p2p的發展

PCDN&#xff08;P2P內容分發網絡&#xff09;行業目前處于快速發展階段&#xff0c;面臨機遇與挑戰并存的局面。 一、發展機遇 技術融合推動 邊緣計算與5G普及&#xff1a;5G的高帶寬、低延遲特性與邊緣計算技術結合&#xff0c;顯著提升PCDN性能&#xff0c;降低延遲&#x…

計算機視覺與深度學習 | 視覺里程計(Visual Odometry, VO)學習思路總結

視覺里程計(Visual Odometry, VO)學習思路總結 視覺里程計(VO)是通過攝像頭捕獲的圖像序列估計相機運動軌跡的技術,廣泛應用于機器人、自動駕駛和增強現實等領域。以下是一個系統的學習路徑,涵蓋基礎理論、核心算法、工具及實踐建議:一、基礎理論與數學準備 核心數學工具…

Ubuntu 24.04 中文輸入法安裝

搜狗輸入法&#xff0c;在Ubuntu 24.04上使用失敗&#xff0c;安裝教程如下 https://shurufa.sogou.com/linux/guide 出現問題的情況&#xff0c;是這個帖子里描述的&#xff1a; https://forum.ubuntu.org.cn/viewtopic.php?t493893 后面通過google拼音輸入法解決了&#x…

阿里云 MSE Nacos 發布全新“安全防護”模塊,簡化安全配置,提升數據保護

作者&#xff1a;張文浩 阿里云在其微服務引擎&#xff08;MSE&#xff09;注冊配置中心 Nacos 上正式推出全新“安全防護”功能模塊&#xff0c;旨在幫助企業用戶有效管理安全狀態和降低開啟安全相關功能的學習成本&#xff0c;提升微服務架構的安全性。首期推出的“安全防護…

C#核心(23)StringBuilder

前言 我們先前已經了解了String的一些基本規則和常見的用法,今天就來講一下和string有所區別的StringBulider。 在 C# 中,StringBuilder 類是一個非常有用的工具,特別是在需要頻繁修改字符串時。與 String 類型不同,StringBuilder 類提供了一種動態字符串,可以在不創建新…

活動圖與流程圖的區別與聯系:深入理解兩種建模工具

目錄 前言1. 活動圖概述1.1 活動圖的定義1.2 活動圖的基本構成要素1.3 活動圖的應用場景 2. 流程圖概述2.1 流程圖的定義2.2 流程圖的基本構成要素2.3 流程圖的應用場景 3. 活動圖與流程圖的聯系4. 活動圖與流程圖的區別4.1 所屬體系不同4.2 表達能力差異4.3 使用目的與語境4.4…

idea運行springboot項目,運行時不能生成target

1&#xff0c;問題 項目本來運行正常&#xff0c;突然重啟項目運行時&#xff0c;提醒主類找不到&#xff0c;發現target未生成 2&#xff0c;解決辦法 查看.idea里面的文件&#xff0c;正常是下面這樣的 如果有缺失&#xff0c;刪除.idea里面的文件&#xff0c;清除idea緩…