Netty核心功能之?dāng)?shù)據(jù)容器ByteBuf詳解
正文
網(wǎng)絡(luò)數(shù)據(jù)的基本單位總是字節(jié),Java NIO 提供了 ByteBuffer 作為它的字節(jié)容器,但是這個(gè)類使用起來過于復(fù)雜,而且也有些繁瑣。 Netty 的 ByteBuffer 替代品是 ByteBuf,一個(gè)強(qiáng)大的實(shí)現(xiàn),既解決了 JDK API 的局限性,又為網(wǎng)絡(luò)應(yīng)用程序的開發(fā)者提供了更好的 API。
1、簡(jiǎn)介
Netty 的數(shù)據(jù)處理 API 通過兩個(gè)組件暴露——abstract class ByteBuf 和 interface ByteBufHolder,下面是一些 ByteBuf API 的優(yōu)點(diǎn):
- 它可以被用戶自定義的緩沖區(qū)類型擴(kuò)展;
- 通過內(nèi)置的復(fù)合緩沖區(qū)類型實(shí)現(xiàn)了透明的零拷貝;
- 容量可以按需增長(zhǎng)(類似于 JDK 的 StringBuilder);
- 在讀和寫這兩種模式之間切換不需要調(diào)用 ByteBuffer 的 flip()方法;
- 讀和寫使用了不同的索引;
- 支持方法的鏈?zhǔn)秸{(diào)用;
- 支持引用計(jì)數(shù);
- 支持池化。
對(duì)比ByteBuffer的缺點(diǎn):
ByteBuffer長(zhǎng)度固定,一旦分配完成,它的容量不能動(dòng)態(tài)擴(kuò)展和收縮,當(dāng)需要編碼的POJO對(duì)象大于ByteBuffer的容量時(shí),會(huì)發(fā)生索引越界異常;ByteBuffer只有一個(gè)標(biāo)識(shí)位置的指針position,讀寫的時(shí)候需要手工調(diào)用flip()和rewind()等,使用者必須小心謹(jǐn)慎地處理這些API,否則很容易導(dǎo)致程序處理失??;ByteBuffer的API功能有限,一些高級(jí)和實(shí)用的特性它不支持,需要使用者自己編程實(shí)現(xiàn)。
2、ByteBuf 類——Netty 的數(shù)據(jù)容器
所有的網(wǎng)絡(luò)通信都涉及字節(jié)序列的移動(dòng),所以高效易用的數(shù)據(jù)結(jié)構(gòu)明顯是必不可少的。所以理解Netty 的 ByteBuf 是如何滿足這些需求的很重要。
2.1 工作原理
ByteBuf工作機(jī)制:ByteBuf維護(hù)了兩個(gè)不同的索引,一個(gè)用于讀取,一個(gè)用于寫入。readerIndex和writerIndex的初始值都是0,當(dāng)從ByteBuf中讀取數(shù)據(jù)時(shí),它的readerIndex將會(huì)被遞增(它不會(huì)超過writerIndex),當(dāng)向ByteBuf寫入數(shù)據(jù)時(shí),它的writerIndex會(huì)遞增。

ByteBuf的幾個(gè)特點(diǎn):
- 名稱以
readXXX或者writeXXX開頭的ByteBuf方法,會(huì)推進(jìn)對(duì)應(yīng)的索引,而以setXXX或getXXX開頭的操作不會(huì)。 - 在讀取之后,
0~readerIndex的就被視為discard的,調(diào)用discardReadBytes方法,可以釋放這部分空間,它的作用類似ByteBuffer的compact()方法。 readerIndex和writerIndex之間的數(shù)據(jù)是可讀取的,等價(jià)于ByteBuffer的position和limit之間的數(shù)據(jù)。writerIndex和capacity之間的空間是可寫的,等價(jià)于ByteBuffer的limit和capacity之間的可用空間。
2.2 ByteBuf的三種類型
堆緩沖區(qū)
最常用的 ByteBuf 模式是將數(shù)據(jù)存儲(chǔ)在 JVM 的堆空間中。這種模式被稱為支撐數(shù)組(backing array),它能在沒有使用池化的情況下提供快速的分配和釋放
優(yōu)點(diǎn):由于數(shù)據(jù)存儲(chǔ)在JVM的堆中可以快速創(chuàng)建和快速釋放,并且提供了數(shù)組的直接快速訪問的方法。
缺點(diǎn):每次讀寫數(shù)據(jù)都要先將數(shù)據(jù)拷貝到直接緩沖區(qū)(相關(guān)閱讀:Java NIO 直接緩沖區(qū)和非直接緩沖區(qū)對(duì)比)再進(jìn)行傳遞。
示例:
// 創(chuàng)建一個(gè)堆緩沖區(qū)
ByteBuf buffer = Unpooled.buffer(10);
String s = "waylau";
buffer.writeBytes(s.getBytes());
// 檢查是否是支撐數(shù)組
if (buffer.hasArray()) {
// 獲取支撐數(shù)組的引用
byte[] array = buffer.array();
// 計(jì)算第一個(gè)字節(jié)的偏移量
int offset = buffer.readerIndex() + buffer.arrayOffset();
// 可讀字節(jié)數(shù)
int length = buffer.readableBytes();
// 使用數(shù)組、偏移量和長(zhǎng)度作為參數(shù)調(diào)用自定義的使用方法
printBuffer(array, offset, length);
}
/**
* 打印出Buffer的信息
*
* @param buffer
*/
private static void printBuffer(byte[] array, int offset, int len) {
System.out.println("array:" + array);
System.out.println("array->String:" + new String(array));
System.out.println("offset:" + offset);
System.out.println("len:" + len);
}
/**
輸出結(jié)果:
array:[B@5b37e0d2
array->String:waylau
offset:0
len:6
*/
直接緩沖區(qū)
Direct Buffer在堆之外直接分配內(nèi)存,直接緩沖區(qū)不會(huì)占用堆的容量。
優(yōu)點(diǎn):在使用Socket傳遞數(shù)據(jù)時(shí)性能很好,由于數(shù)據(jù)直接在內(nèi)存中,不存在從JVM拷貝數(shù)據(jù)到直接緩沖區(qū)的過程,性能好。
缺點(diǎn):因?yàn)镈irect Buffer是直接在內(nèi)存中,所以分配內(nèi)存空間和釋放內(nèi)存比堆緩沖區(qū)更復(fù)雜和慢。
示例:
// 創(chuàng)建一個(gè)直接緩沖區(qū)
ByteBuf buffer = Unpooled.directBuffer(10);
String s = "waylau";
buffer.writeBytes(s.getBytes());
// 檢查是否是支撐數(shù)組.
// 不是支撐數(shù)組,則為直接緩沖區(qū)
if (!buffer.hasArray()) {
// 計(jì)算第一個(gè)字節(jié)的偏移量
int offset = buffer.readerIndex();
// 可讀字節(jié)數(shù)
int length = buffer.readableBytes();
// 獲取字節(jié)內(nèi)容
byte[] array = new byte[length];
buffer.getBytes(offset, array);
// 使用數(shù)組、偏移量和長(zhǎng)度作為參數(shù)調(diào)用自定義的使用方法
printBuffer(array, offset, length);
}
/**
* 打印出Buffer的信息
*
* @param buffer
*/
private static void printBuffer(byte[] array, int offset, int len) {
System.out.println("array:" + array);
System.out.println("array->String:" + new String(array));
System.out.println("offset:" + offset);
System.out.println("len:" + len);
}
/**
輸出結(jié)果:
array:[B@6d5380c2
array->String:waylau
offset:0
len:6
*/
復(fù)合緩沖區(qū)
復(fù)合緩沖區(qū)是 Netty 特有的緩沖區(qū)。本質(zhì)上類似于提供一個(gè)或多個(gè) ByteBuf 的組合視圖,可以根據(jù)需要添加和刪除不同類型的 ByteBuf。
優(yōu)點(diǎn):提供了一種訪問方式讓使用者自由地組合多個(gè)ByteBuf,避免了復(fù)制和分配新的緩沖區(qū)。
缺點(diǎn):不支持訪問其支撐數(shù)組。因此如果要訪問,需要先將內(nèi)容復(fù)制到堆內(nèi)存中,再進(jìn)行訪問。
示例:
// 創(chuàng)建一個(gè)堆緩沖區(qū)
ByteBuf heapBuf = Unpooled.buffer(3);
String way = "way";
heapBuf.writeBytes(way.getBytes());
// 創(chuàng)建一個(gè)直接緩沖區(qū)
ByteBuf directBuf = Unpooled.directBuffer(3);
String lau = "lau";
directBuf.writeBytes(lau.getBytes());
// 創(chuàng)建一個(gè)復(fù)合緩沖區(qū)
CompositeByteBuf compositeBuffer = Unpooled.compositeBuffer(10);
compositeBuffer.addComponents(heapBuf, directBuf); // 將緩沖區(qū)添加到符合緩沖區(qū)
// 檢查是否是支撐數(shù)組.
// 不是支撐數(shù)組,則為復(fù)合緩沖區(qū)
if (!compositeBuffer.hasArray()) {
for (ByteBuf buffer : compositeBuffer) {
// 計(jì)算第一個(gè)字節(jié)的偏移量
int offset = buffer.readerIndex();
// 可讀字節(jié)數(shù)
int length = buffer.readableBytes();
// 獲取字節(jié)內(nèi)容
byte[] array = new byte[length];
buffer.getBytes(offset, array);
// 使用數(shù)組、偏移量和長(zhǎng)度作為參數(shù)調(diào)用自定義的使用方法
printBuffer(array, offset, length);
}
}
/**
* 打印出Buffer的信息
*
* @param buffer
*/
private static void printBuffer(byte[] array, int offset, int len) {
System.out.println("array:" + array);
System.out.println("array->String:" + new String(array));
System.out.println("offset:" + offset);
System.out.println("len:" + len);
}
/**
輸出結(jié)果:
array:[B@4d76f3f8
array->String:way
offset:0
len:3
array:[B@2d8e6db6
array->String:lau
offset:0
len:3
*/
3、字節(jié)級(jí)操作
ByteBuf 提供了許多超出基本讀、寫操作的方法用于修改它的數(shù)據(jù)。
3.1 隨機(jī)訪問索引和順序訪問索引
如同在普通的 Java 字節(jié)數(shù)組中一樣,ByteBuf 的索引是從零開始的:第一個(gè)字節(jié)的索引是 0,最后一個(gè)字節(jié)的索引總是 數(shù)組容量 - 1。在ByteBuf的實(shí)現(xiàn)類中都有一個(gè)方法可以快速獲得容量值,那就是capacity()。有了這個(gè)capcity()方法,我們就能很簡(jiǎn)單的實(shí)現(xiàn)隨機(jī)訪問索引:
for (int i = 0;i<buf.capacity();i++){
char b = (char)buf.getByte(i);//通過 getBytes 系列接口來對(duì)ByteBuf進(jìn)行隨機(jī)訪問。
System.out.println(b);
}
Tips: 用getBytes隨機(jī)訪問不會(huì)改變r(jià)eaderIndex

通過 readerIndex() 和 writerIndex() 獲取讀Index和寫Index。
3.2 可丟棄字節(jié)
在上圖中標(biāo)記為可丟棄字節(jié)的分段包含了已經(jīng)被讀過的字節(jié)。通過調(diào)用 discardReadBytes()方法,可以丟棄它們并回收空間。這個(gè)分段的初始大小為 0,存儲(chǔ)在 readerIndex 中, 會(huì)隨著 read 操作的執(zhí)行而增加(get操作不會(huì)移動(dòng) readerIndex)。
下圖展示了在上圖的緩沖區(qū)上調(diào)用discardReadBytes()方法后的結(jié)果,需要注意的是丟棄并不是字節(jié)把已經(jīng)讀的字段的字節(jié)不要了,而是把尚未讀的字節(jié)數(shù)移到最開始。(這樣做對(duì)可寫分段的內(nèi)容并沒有任何的保證,因?yàn)橹皇且苿?dòng)了可以讀取的字節(jié)以及 writerIndex,而沒有對(duì)所有可寫入的字節(jié)進(jìn)行擦除寫)

3.3 可讀字節(jié)
ByteBuf 的可讀字節(jié)分段存儲(chǔ)了實(shí)際數(shù)據(jù)。新分配的、包裝的或者復(fù)制的緩沖區(qū)的默認(rèn)的 readerIndex 值為 0。任何名稱以 read 或者 skip 開頭的操作都將檢索或者跳過位于當(dāng)前 readerIndex 之前的數(shù)據(jù),并且在readerIndex 的基礎(chǔ)上增加已讀字節(jié)數(shù)。 如果被調(diào)用的方法需要一個(gè) ByteBuf 參數(shù)作為寫入的目標(biāo),并且沒有指定目標(biāo)索引參數(shù), 那么該目標(biāo)緩沖區(qū)的 writerIndex 也將被增加。
3.4 可寫字節(jié)
可寫字節(jié)分段是指一個(gè)擁有未定義內(nèi)容的、寫入就緒的內(nèi)存區(qū)域。新分配的緩沖區(qū)的 writerIndex 的默認(rèn)值為 0。任何名稱以 write 開頭的操作都將從當(dāng)前的 writerIndex 處 開始寫數(shù)據(jù),并且在writerIndex 的基礎(chǔ)上增加已寫字節(jié)數(shù)。如果寫操作的目標(biāo)也是 ByteBuf,并且沒有指定 源索引的值,則源緩沖區(qū)的 readerIndex 也同樣會(huì)被增加相同的大小。
3.5 索引管理
JDK 的 InputStream 定義了 mark(int readlimit)和 reset()方法,這些方法分別 被用來將流中的當(dāng)前位置標(biāo)記為指定的值,以及將流重置到該位置。
同樣,可以通過調(diào)用 markReaderIndex()、markWriterIndex()、resetWriterIndex() 和 resetReaderIndex()來標(biāo)記和重置 ByteBuf 的 readerIndex 和 writerIndex。這些和 InputStream 上的調(diào)用類似,只是沒有 readlimit 參數(shù)來指定標(biāo)記什么時(shí)候失效
也可以通過調(diào)用 readerIndex(int)或者 writerIndex(int)來將索引移動(dòng)到指定位置。試 圖將任何一個(gè)索引設(shè)置到一個(gè)無效的位置都將導(dǎo)致一個(gè) IndexOutOfBoundsException。
可以通過調(diào)用 clear()方法來將 readerIndex 和 writerIndex 都設(shè)置為 0。注意,這 并不會(huì)清除內(nèi)存中的內(nèi)容。調(diào)用后的存儲(chǔ)結(jié)構(gòu)如下圖所示:

調(diào)用 clear()比調(diào)用 discardReadBytes()輕量得多,因?yàn)樗鼘⒅皇侵刂盟饕粫?huì)復(fù) 制任何的內(nèi)存
3.6 派生緩沖區(qū)
派生緩沖區(qū)為 ByteBuf 提供了以專門的方式來呈現(xiàn)其內(nèi)容的視圖。這類視圖是通過以下方法被創(chuàng)建的:
- duplicate();
- slice();
- slice(int, int);
- Unpooled.unmodifiableBuffer(…);
- order(ByteOrder);
- readSlice(int)。
每個(gè)這些方法都將返回一個(gè)新的 ByteBuf 實(shí)例,它具有自己的讀索引、寫索引和標(biāo)記 索引。其內(nèi)部存儲(chǔ)和 JDK 的 ByteBuffer 一樣也是共享的。這使得派生緩沖區(qū)的創(chuàng)建成本是很低廉的,但是這也意味著,如果你修改了它的內(nèi)容,也同時(shí)修改了其對(duì)應(yīng)的源實(shí)例,所以要小心。
ByteBuf 復(fù)制 如果需要一個(gè)現(xiàn)有緩沖區(qū)的真實(shí)副本,請(qǐng)使用 copy()或者 copy(int, int)方 法。不同于派生緩沖區(qū),由這個(gè)調(diào)用所返回的 ByteBuf 擁有獨(dú)立的數(shù)據(jù)副本。

3.7 讀/寫操作
有兩種類別的讀/寫操作:
- get()和 set()操作,從給定的索引開始,并且保持索引不變;
- read()和 write()操作,從給定的索引開始,并且會(huì)根據(jù)已經(jīng)訪問過的字節(jié)數(shù)對(duì)索引進(jìn)行調(diào)整。
常用的 get()方法如下圖:

常用的 set()方法如下圖:

常用的 read()方法如下圖:

常用的 write()方法如下圖:

4、ByteBufHolder 接口
4.1 按需分配:ByteBufAllocator 接口
Netty 中內(nèi)存分配有一個(gè)最頂層的抽象就是ByteBufAllocator,負(fù)責(zé)分配所有ByteBuf 類型的內(nèi)存。他的一些操作如下:

可以通過 Channel(每個(gè)都可以有一個(gè)不同的 ByteBufAllocator 實(shí)例)或者綁定到 ChannelHandler 的 ChannelHandlerContext 獲取一個(gè)到 ByteBufAllocator 的引用,如下圖所示:

Netty提供了兩種ByteBufAllocator的實(shí)現(xiàn):PooledByteBufAllocator和UnpooledByteBufAllocator。前者池化了ByteBuf的實(shí)例以提高性能并最大限度地減少內(nèi)存碎片,這是通過一種 叫做jemalloc的方法來分配內(nèi)存的(閱讀資料:jemalloc剖析)。后者的實(shí)現(xiàn)不池化ByteBuf實(shí)例,并且在每次它被調(diào)用時(shí)都會(huì)返回一個(gè)新的實(shí)例。
4.2 Unpooled 緩沖區(qū)
可能某些情況下,你未能獲取一個(gè)到 ByteBufAllocator 的引用。對(duì)于這種情況,Netty 提 供了一個(gè)簡(jiǎn)單的稱為 Unpooled 的工具類,它提供了靜態(tài)的輔助方法來創(chuàng)建未池化的 ByteBuf 實(shí)例。他的一些操作如下:

4.3 ByteBufUtil 類
ByteBufUtil 提供了用于操作 ByteBuf 的靜態(tài)的輔助方法。這個(gè) API 是通用的,并且和池化無關(guān)。
5、引用計(jì)數(shù)
引用計(jì)數(shù)是一種通過在某個(gè)對(duì)象所持有的資源不再被其他對(duì)象引用時(shí)釋放該對(duì)象所持有的資源來優(yōu)化內(nèi)存使用和性能的技術(shù)。Netty 在第 4 版中為 ByteBuf 和 ByteBufHolder 引入了 引用計(jì)數(shù)技術(shù),它們都實(shí)現(xiàn)了 interface ReferenceCounted。
5.1 基本原理
一個(gè)新創(chuàng)建的引用計(jì)數(shù)對(duì)象的初始引用計(jì)數(shù)是1。
ByteBuf buf = ctx.alloc().directbuffer(); assert buf.refCnt() == 1;
當(dāng)你釋放掉引用計(jì)數(shù)對(duì)象,它的引用次數(shù)減1.如果一個(gè)對(duì)象的引用計(jì)數(shù)到達(dá)0,該對(duì)象就會(huì)被釋放或者歸還到創(chuàng)建它的對(duì)象池。
assert buf.refCnt() == 1; // release() returns true only if the reference count becomes 0. boolean destroyed = buf.release(); assert destroyed; assert buf.refCnt() == 0;
訪問引用計(jì)數(shù)為0的引用計(jì)數(shù)對(duì)象會(huì)觸發(fā)一次IllegalReferenceCountException。
assert buf.refCnt() == 0;
try {
buf.writeLong(0xdeadbeef);
throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {
// Expected
}
只要引用計(jì)數(shù)對(duì)象未被銷毀,就可以通過調(diào)用retain()方法來增加引用次數(shù)。
ByteBuf buf = ctx.alloc().directBuffer(); assert buf.refCnt() == 1; buf.retain(); assert buf.refCnt() == 2; boolean destroyed = buf.release(); assert !destroyed; assert buf.refCnt() == 1;
5.2 誰來銷毀
一般的原則是,最后訪問引用計(jì)數(shù)對(duì)象的部分負(fù)責(zé)對(duì)象的銷毀。更具體地來說:
- 如果一個(gè)[發(fā)送]組件要傳遞一個(gè)引用計(jì)數(shù)對(duì)象到另一個(gè)[接收]組件,發(fā)送組件通常不需要 負(fù)責(zé)去銷毀對(duì)象,而是將這個(gè)銷毀的任務(wù)推延到接收組件
- 如果一個(gè)組件消費(fèi)了一個(gè)引用計(jì)數(shù)對(duì)象,并且不知道誰會(huì)再訪問它(例如,不會(huì)再將引用 發(fā)送到另一個(gè)組件),那么,這個(gè)組件負(fù)責(zé)銷毀工作。
5.3 內(nèi)存泄漏問題
引用計(jì)數(shù)的缺點(diǎn)是,引用計(jì)數(shù)對(duì)象容易發(fā)生泄露。因?yàn)镴VM并不知道Netty的引用計(jì)數(shù)實(shí)現(xiàn),當(dāng)引用計(jì)數(shù)對(duì)象不 可達(dá)時(shí),JVM就會(huì)將它們GC掉,即時(shí)此時(shí)它們的引用計(jì)數(shù)并不為0。一旦對(duì)象被GC就不能再訪問,也就不能歸還到緩沖池,所以會(huì)導(dǎo)致內(nèi)存泄露。 慶幸的是,盡管發(fā)現(xiàn)內(nèi)存泄露很難,但是Netty會(huì)對(duì)分配的緩沖區(qū)的1%進(jìn)行采樣,來檢查你的應(yīng)用中是否存在內(nèi)存泄露。
內(nèi)存泄露檢查等級(jí)
總共有4個(gè)內(nèi)存泄露檢查等級(jí):
- DISABLED – 完全禁用檢查。不推薦。
- SIMPLE – 檢查1%的緩沖區(qū)是否存在內(nèi)存泄露。默認(rèn)。
- ADVANCED – 檢查1%的緩沖區(qū),并提示發(fā)生內(nèi)存泄露的位置
- PARANOID – 與ADVANCED等級(jí)一樣,不同的是會(huì)檢查所有的緩沖區(qū)。對(duì)于自動(dòng)化測(cè)試很有用,你可以讓構(gòu)建測(cè)試失敗 如果構(gòu)建輸出中包含’LEAK’ 用JVM選項(xiàng)
-Dio.netty.leakDetectionLevel來指定內(nèi)存泄露檢查等級(jí)
避免泄露最佳實(shí)踐
- 指定SIMPLE和PARANOI等級(jí),運(yùn)行單元測(cè)試和集成測(cè)試
- 在將你的應(yīng)用部署到整個(gè)集群前,盡可能地用足夠長(zhǎng)的時(shí)間,使用SIMPLE級(jí)別去調(diào)試你的程序,來看是否存在內(nèi)存泄露
- 如果存在內(nèi)存泄露,使用ADVANCED級(jí)別去調(diào)試程序,去獲取內(nèi)存泄漏的位置信息
- 不要將存在內(nèi)存泄漏的應(yīng)用部署到整個(gè)集群
以上就是Netty核心功能之?dāng)?shù)據(jù)容器ByteBuf詳解的詳細(xì)內(nèi)容,更多關(guān)于Netty 數(shù)據(jù)容器ByteBuf的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot2.0+elasticsearch5.5+rabbitmq搭建搜索服務(wù)的坑
這篇文章主要介紹了springboot2.0+elasticsearch5.5+rabbitmq搭建搜索服務(wù)的坑,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
java程序員自己的圖片轉(zhuǎn)文字OCR識(shí)圖工具分享
這篇文章主要介紹了java程序員自己的圖片轉(zhuǎn)文字OCR識(shí)圖工具,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
Springboot mybatisplus如何解決分頁(yè)組件IPage失效問題
這篇文章主要介紹了Springboot mybatisplus如何解決分頁(yè)組件IPage失效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Java使用組合模式實(shí)現(xiàn)表示公司組織結(jié)構(gòu)功能示例
這篇文章主要介紹了Java使用組合模式實(shí)現(xiàn)表示公司組織結(jié)構(gòu)功能,簡(jiǎn)單描述了組合模式的概念、功能并結(jié)合實(shí)例形式分析了Java使用組合模式實(shí)現(xiàn)公司組織結(jié)構(gòu)表示功能具體操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2018-05-05
springboot配置nacos的實(shí)現(xiàn)示例
本文將介紹如何在Spring?Boot中配置Nacos,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09
spring-security關(guān)閉登錄框的實(shí)現(xiàn)示例
這篇文章主要介紹了spring-security關(guān)閉登錄框的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
java_IO向文件中寫入和讀取內(nèi)容代碼實(shí)例
這篇文章主要介紹了java_IO向文件中寫入和讀取內(nèi)容,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03

