Java中的CompletableFuture原理與用法
CompletableFuture 是由Java8引入的,這讓我們編寫清晰可讀的異步代碼變得更加容易,該類功能比Future 更加強(qiáng)大。
什么是 CompletableFuture
在Java中CompletableFuture用于異步編程,異步通常意味著非阻塞,運(yùn)行任務(wù)單獨(dú)的線程,與主線程隔離。并且通過回調(diào)可以在主線程中得到異步任務(wù)的執(zhí)行狀態(tài),是否完成和異常等信息。
通過這種方式,主線程不會(huì)被阻塞,不需要一直等到子線程完成。主線程可以并行的執(zhí)行其他任務(wù)。使用這種并行方式,可以極大的提高程序的性能。
為什么要引入 CompletableFuture
一些業(yè)務(wù)場景我們需要使用多線程異步執(zhí)行任務(wù),加快任務(wù)執(zhí)行速度,所以 JDK5 新增了 Future 接口,用于描述一個(gè)異步計(jì)算的結(jié)果。
雖然 Future 以及相關(guān)使用方法提供了異步執(zhí)行任務(wù)的能力,但是對于結(jié)果的獲取卻是很不方便,我們必須使用 Future.get 的方式阻塞調(diào)用線程,或者使用輪詢方式判斷 Future.isDone 任務(wù)是否結(jié)束,再獲取結(jié)果。
Future<String> future = executor.submit(()->{ Thread.sleep(2000); return "hello world"; }); // 獲取結(jié)果 System.out.println(future.get());
從上面的形式看來不能及時(shí)地得到計(jì)算結(jié)果,所以要實(shí)現(xiàn)真正的異步,上述這樣是完全不夠的。
若需要更強(qiáng)大的異步處理能力,單純使用 Future 接口或者 FutureTask 類并不能很好地完成以下業(yè)務(wù)場景:
- 將兩個(gè)異步計(jì)算合并為一個(gè),這兩個(gè)異步計(jì)算之間相互獨(dú)立,同時(shí)第二個(gè)又依賴于第一個(gè)的結(jié)果;
- 等待Future集合種的所有任務(wù)都完成;
- 僅等待Future集合種最快結(jié)束的任務(wù)完成(有可能因?yàn)樗麄冊噲D通過不同的方式計(jì)算同一個(gè)值),并返回它的結(jié)果;
- 通過編程方式完成一個(gè)Future任務(wù)的執(zhí)行(即以手工設(shè)定異步操作結(jié)果的方式);
- 應(yīng)對Future的完成時(shí)間(即當(dāng)Future的完成時(shí)間完成時(shí)會(huì)收到通知,并能使用Future的計(jì)算結(jié)果進(jìn)行下一步的的操作,不只是簡單地阻塞等待操作的結(jié)果)。
所以JDK 8.0新增了CompletableFuture 來解決上述這些痛點(diǎn)。
Future vs CompletableFuture
CompletableFuture 是 Future API的擴(kuò)展。
Future 被用于作為一個(gè)異步計(jì)算結(jié)果的引用。提供一個(gè) isDone() 方法來檢查計(jì)算任務(wù)是否完成。當(dāng)任務(wù)完成時(shí),get() 方法用來接收計(jì)算任務(wù)的結(jié)果。
Future 的局限性
不能手動(dòng)完成
當(dāng)你寫了一個(gè)函數(shù),用于通過一個(gè)遠(yuǎn)程API獲取一個(gè)電子商務(wù)產(chǎn)品最新價(jià)格。因?yàn)檫@個(gè) API 太耗時(shí),你把它允許在一個(gè)獨(dú)立的線程中,并且從你的函數(shù)中返回一個(gè) Future?,F(xiàn)在假設(shè)這個(gè)API服務(wù)宕機(jī)了,這時(shí)你想通過該產(chǎn)品的最新緩存價(jià)格手工完成這個(gè)Future 。你會(huì)發(fā)現(xiàn)無法這樣做。
Future 的結(jié)果在非阻塞的情況下,不能執(zhí)行更進(jìn)一步的操作
Future 不會(huì)通知你它已經(jīng)完成了,它提供了一個(gè)阻塞的 get() 方法通知你結(jié)果。你無法給 Future 植入一個(gè)回調(diào)函數(shù),當(dāng) Future 結(jié)果可用的時(shí)候,用該回調(diào)函數(shù)自動(dòng)的調(diào)用 Future 的結(jié)果。
多個(gè) Future 不能串聯(lián)在一起組成鏈?zhǔn)秸{(diào)用
有時(shí)候你需要執(zhí)行一個(gè)長時(shí)間運(yùn)行的計(jì)算任務(wù),并且當(dāng)計(jì)算任務(wù)完成的時(shí)候,你需要把它的計(jì)算結(jié)果發(fā)送給另外一個(gè)長時(shí)間運(yùn)行的計(jì)算任務(wù)等等。你會(huì)發(fā)現(xiàn)你無法使用 Future 創(chuàng)建這樣的一個(gè)工作流。
不能組合多個(gè) Future 的結(jié)果
假設(shè)你有10個(gè)不同的Future,你想并行的運(yùn)行,然后在它們運(yùn)行未完成后運(yùn)行一些函數(shù)。你會(huì)發(fā)現(xiàn)你也無法使用 Future 這樣做。
沒有異常處理
Future API 沒有任務(wù)的異常處理結(jié)構(gòu)居然有如此多的限制,幸好我們有CompletableFuture,你可以使用 CompletableFuture 達(dá)到以上所有目的。
CompletableFuture 實(shí)現(xiàn)了 Future 和 CompletionStage 接口,并且提供了許多關(guān)于創(chuàng)建,鏈?zhǔn)秸{(diào)用和組合多個(gè) Future 的便利方法集,而且有廣泛的異常處理支持。
CompletableFuture的應(yīng)用場景
- 執(zhí)行比較耗時(shí)的操作時(shí),尤其是那些依賴一個(gè)或多個(gè)遠(yuǎn)程服務(wù)的操作,使用異步任務(wù)可以改善程序的性能,加快程序的響應(yīng)速度。
- 使用CompletableFuture類,它提供了異常管理的機(jī)制,讓你有機(jī)會(huì)拋出、管理異步任務(wù)執(zhí)行種發(fā)生的異常。
- 如果這些異步任務(wù)之間相互獨(dú)立,或者他們之間的的某一些的結(jié)果是另一些的輸入,你可以講這些異步任務(wù)構(gòu)造或合并成一個(gè)。
CompletableFuture設(shè)計(jì)思想
CompletableFuture 按照類似“觀察者模式”的設(shè)計(jì)思想,原理分析可以從“觀察者”和“被觀察者”兩個(gè)方面著手。
由于回調(diào)種類多,但結(jié)構(gòu)差異不大,所以這里單以一元依賴中的thenApply為例,不再枚舉全部回調(diào)類型,如下圖所示:
被觀察者
- 每個(gè)CompletableFuture都可以被看作一個(gè)被觀察者,其內(nèi)部有一個(gè)Completion類型的鏈表成員變量stack,用來存儲(chǔ)注冊到其中的所有觀察者。當(dāng)被觀察者執(zhí)行完成后會(huì)彈棧stack屬性,依次通知注冊到其中的觀察者。上面例子中步驟fn2就是作為觀察者被封裝在UniApply中。
- 被觀察者CF中的result屬性,用來存儲(chǔ)返回結(jié)果數(shù)據(jù)。這里可能是一次RPC調(diào)用的返回值,也可能是任意對象,在上面的例子中對應(yīng)步驟fn1的執(zhí)行結(jié)果。
觀察者
CompletableFuture支持很多回調(diào)方法,例如thenAccept、thenApply、exceptionally等,這些方法接收一個(gè)函數(shù)類型的參數(shù)f,生成一個(gè)Completion類型的對象(即觀察者),并將入?yún)⒑瘮?shù)f賦值給Completion的成員變量fn,然后檢查當(dāng)前CF是否已處于完成狀態(tài)(即result!=null),如果已完成直接觸發(fā)fn,否則將觀察者Completion加入到CF的觀察者鏈stack中,再次嘗試觸發(fā),如果被觀察者未執(zhí)行完則其執(zhí)行完畢之后通知觸發(fā)。
- 觀察者中的dep屬性:指向其對應(yīng)的CompletableFuture,在上面的例子中dep指向CF2。
- 觀察者中的src屬性:指向其依賴的CompletableFuture,在上面的例子中src指向CF1。
- 觀察者Completion中的fn屬性:用來存儲(chǔ)具體的等待被回調(diào)的函數(shù)。這里需要注意的是不同的回調(diào)方法(thenAccept、thenApply、exceptionally等)接收的函數(shù)類型也不同,即fn的類型有很多種,在上面的例子中fn指向fn2。
CompletableFuture核心功能
CompletableFuture的功能主要體現(xiàn)在它的CompletionStage,如下圖所示:
CompletionStage接口定義了任務(wù)編排的方法,執(zhí)行某一階段,可以向下執(zhí)行后續(xù)階段,可以實(shí)現(xiàn)如下功能:
- 轉(zhuǎn)換(thenCompose)
- 組合(thenCombine)
- 消費(fèi)(thenAccept)
- 運(yùn)行(thenRun)
- 帶返回的消費(fèi)(thenApply)
具體其他功能大家可以根據(jù)需求自行查看。
CompletableFuture創(chuàng)建使用
創(chuàng)建CompletableFuture對象,提供了四個(gè)靜態(tài)方法用來創(chuàng)建CompletableFuture對象:
public static CompletableFuture<Void> runAsync(Runnable runnable) public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
Asynsc 表示異步,而 supplyAsync 與 runAsync 不同在與前者異步返回一個(gè)結(jié)果,后者是 void。第二個(gè)函數(shù)第二個(gè)參數(shù)表示是用我們自己創(chuàng)建的線程池,否則采用默認(rèn)的 ForkJoinPool.commonPool() 作為它的線程池.其中Supplier是一個(gè)函數(shù)式接口,代表是一個(gè)生成者的意思,傳入0個(gè)參數(shù),返回一個(gè)結(jié)果。
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{ return "hello world"; }); //阻塞的獲取結(jié)果 ''helllo world" System.out.println(future.get());
CompletableFuture用法詳解
沒有返回值的異步任務(wù)
如果你想異步的運(yùn)行一個(gè)后臺(tái)任務(wù)并且不需要任務(wù)返回結(jié)果,就可以使用 runAsync()。
runAsync() 返回一個(gè)新的 CompletableFuture,它在運(yùn)行給定操作后由在 ForkJoinPool.commonPool() 運(yùn)行的任務(wù)異步完成。
/** * 沒有返回值的異步任務(wù) */ @Test public void runAsync() throws Exception { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { log.info("Current thread name: {}", Thread.currentThread().getName()); // 模擬長時(shí)間運(yùn)行的作業(yè) try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); }; }); // 主線程阻塞 future.get(); System.out.println("主線程結(jié)束"); }
有返回值的異步任務(wù)
當(dāng)運(yùn)行一個(gè)異步任務(wù)并且需要有返回結(jié)果時(shí),就可以使用 supplyAsync()。
CompletableFuture.supplyAsync() 它持有 supplier<T> 并且返回 CompletableFuture<T>,T 是通過調(diào)用傳入的 supplier 取得的值的類型。
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() { @Override public String get() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Success"; } }); System.out.println(future.get());
Supplier<T> 是一個(gè)簡單的函數(shù)式接口,表示 supplier 的結(jié)果。它有一個(gè) get(),該方法可以寫入你的后臺(tái)任務(wù)中,并且返回結(jié)果。
還可以使用 lambda 表達(dá)式使得上面的示例更加簡明:
/** * 有返回值的異步任務(wù) */ @Test public void supplyAsync() throws Exception { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { log.info("Current thread name: {}", Thread.currentThread().getName()); // 模擬長時(shí)間運(yùn)行的作業(yè) try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); }; return "Success"; }); // 主線程阻塞 System.out.println(future.get()); }
上述 runAsync() 和 supplyAsync() 都是在單獨(dú)的線程中執(zhí)行他們的任務(wù),但在實(shí)際業(yè)務(wù)中我們不會(huì)只創(chuàng)建一個(gè)線程。
自定義線程池執(zhí)行方法
CompletableFuture 可以從全局的 ForkJoinPool.commonPool() 獲得一個(gè)線程中執(zhí)行這些任務(wù)。但也可以創(chuàng)建一個(gè)線程池并傳給 runAsync() 和 supplyAsync() 來讓他們從線程池中獲取一個(gè)線程執(zhí)行它們的任務(wù)。
CompletableFuture API 的所有方法都有兩個(gè)變體-一個(gè)接受Executor作為參數(shù),另一個(gè)不這樣:
static CompletableFuture<Void> runAsync(Runnable runnable) static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
創(chuàng)建一個(gè)線程池,并傳遞給其中一個(gè)方法:
Executor executor = Executors.newFixedThreadPool(10); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Success"; }, executor);
線程串行化
由于 CompletableFuture.get() 方法是阻塞的,它會(huì)一直等到 Future 完成,并且在完成后返回結(jié)果。但是,這是我們想要的嗎?對于構(gòu)建異步系統(tǒng),我們應(yīng)該附上一個(gè)回調(diào)給 CompletableFuture,當(dāng) Future 完成的時(shí)候,自動(dòng)的獲取結(jié)果。
如果不想等待結(jié)果返回,就可以把需要等待 Future 完成執(zhí)行的邏輯寫入到回調(diào)函數(shù)中。
可以使用 thenApply()、thenAccept() 、thenRun() 回調(diào)給 CompletableFuture。
thenApply()
當(dāng)一個(gè)線程依賴另一個(gè)線程時(shí),可以使用 thenApply() 來把這兩個(gè)線程串行化。
thenApply:可以使用 thenApply() 處理和改變 CompletableFuture 的結(jié)果。
/** * 使用 thenApply() 處理和改變CompletableFuture的結(jié)果 * * @throws Exception */ @Test public void thenApply1() throws Exception { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { return "Java"; }).thenApply(o -> { return "Hello " + o; }); System.out.println(future.get()); }
thenAccept()
如果你不想從你的回調(diào)函數(shù)中返回任何東西,僅僅想在 Future 完成后運(yùn)行一些代碼片段,你可以使用 thenAccept() 和 thenRun(),這些方法經(jīng)常在調(diào)用鏈的最末端的最后一個(gè)回調(diào)函數(shù)中使用。
thenAccept:消費(fèi)處理結(jié)果,接收任務(wù)的處理結(jié)果,并消費(fèi)處理,無返回結(jié)果。
@Test public void thenAccept() throws Exception { CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { // 模擬長時(shí)間運(yùn)行的作業(yè) try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Success"; }).thenAccept(o -> { if ("Success".equals(o)) { // 模擬長時(shí)間運(yùn)行的作業(yè) try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } } }); future.get(); log.info("======================"); System.out.println("結(jié)束."); }
thenRun()
thenRun() 不能訪 Future 的結(jié)果,它持有一個(gè) Runnable 返回 CompletableFuture:
@Test public void thenRun() throws Exception { CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> { // 模擬長時(shí)間運(yùn)行的作業(yè) try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new IllegalStateException(e); } return "Success"; }).thenRun(() -> { // 作業(yè)完成后執(zhí)行一些代碼片段 System.out.println("thenRun 執(zhí)行一些代碼片段"); }); future.get(); }
結(jié)果合并
thenCompose()
使用 thenCompose() 合并兩個(gè)有依賴關(guān)系的 CompletableFutures 的執(zhí)行結(jié)果。
private static Integer num = 10; @Test public void thenCompose() throws Exception { //第一步加 10 CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { System.out.println("讓num+10;任務(wù)開始"); num += 10; return num; }); //合并 CompletableFuture<Integer> future1 = future.thenCompose(i -> //再來一個(gè) CompletableFuture CompletableFuture.supplyAsync(() -> i + 1) ); System.out.println(future.get()); System.out.println(future1.get()); }
thenCombine()
使用 thenCombine() 組合兩個(gè)獨(dú)立的 future。當(dāng)兩個(gè)獨(dú)立的 Future 都完成的時(shí)候使用 thenCombine() 用來做一些事情。
@Test public void thenCompose() throws Exception { // 長方形:S=長*寬 //第一步加 10 CompletableFuture<Integer> lengthFuture = CompletableFuture.supplyAsync(() -> { return 50; }); CompletableFuture<Integer> widthFuture = CompletableFuture.supplyAsync(() -> { return 30; }); CompletableFuture<Integer> combinedFuture = lengthFuture.thenCombine(widthFuture, (t1, t2) -> { System.out.println(t1); System.out.println(t2); return t1 * t2; }); System.out.println(combinedFuture.get()); }
合并多個(gè)任務(wù)的結(jié)果
使用 thenCompose() 和 thenCombine() 可以把兩個(gè) CompletableFuture 組合在一起。如果要是想組合任意數(shù)量的 CompletableFuture,應(yīng)該怎么做?
可以使用 allOf 和 anyOf 組合任意多個(gè) CompletableFuture。這兩個(gè)函數(shù)都是靜態(tài)函數(shù),參數(shù)是變長的 CompletableFuture 的集合。
allOf 和 anyOf 的區(qū)別,前者是「與」,后者是「或」。
allOf()
allOf 的返回值是 CompletableFuture<Void> 類型,這是因?yàn)槊總€(gè)傳入的 CompletableFuture 的返回值都可能不同,所以組合的結(jié)果是 無法用某種類型來表示的,索性返回 Void 類型。那么,如何獲取每個(gè) CompletableFuture 的執(zhí)行結(jié)果呢?
例子: 并行地下載 N 個(gè)資源,待下載完成之后,并資源額外處理。
@Test public void allOf() throws Exception { // URL 列表集合 List<String> webLinks = Arrays.asList("https://www.baidu.com/", "https://www.bing.com/", "https://www.so.com/"); // 并行下載多個(gè)網(wǎng)頁 List<CompletableFuture<String>> contentFutureList = webLinks.stream().map(webLink -> downloadWebLink(webLink)).collect(Collectors.toList()); // 通過allof,等待所有網(wǎng)頁下載完畢,收集返回結(jié)果 CompletableFuture<Void> allFutures = CompletableFuture.allOf(contentFutureList.toArray(new CompletableFuture[contentFutureList.size()])); // 附上回調(diào)函數(shù),獲取結(jié)果集 // 方式一 CompletableFuture<List<String>> result = allFutures.thenApply(v -> contentFutureList.stream().map(o -> { try { return o.get(); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } }).collect(Collectors.toList())); System.out.println(result.get()); // 方式二 result = allFutures.thenApply(v -> contentFutureList.stream().map(CompletableFuture<String>::join).collect(Collectors.toList())); System.out.println(result.get()); } private CompletableFuture<String> downloadWebLink(String webLink) { return CompletableFuture.supplyAsync(() -> { // 模擬下載過程 System.out.println("開始下載網(wǎng)頁:" + webLink); return "這是一個(gè)網(wǎng)頁內(nèi)容"; }); }
這里有個(gè)關(guān)鍵問題,因?yàn)?allof 沒有返回值,所以通過 theApply,給 allFutures 附上一個(gè)回調(diào)函數(shù)。在回調(diào)函數(shù)里面,以此調(diào)用么一個(gè) Future 的 Get() 函數(shù),獲取結(jié)果后存入 List<String> 中。
anyOf()
anyOf 的含義是只要有任意一個(gè) CompletableFuture 結(jié)束,就可以做接下來的事情,而無須像 allOf 那樣,等待所有的 CompletableFuture 結(jié)束。
但由于每個(gè) CompletableFuture 的返回值類型可能不同,意味著無法判斷是什么類型,所以 anyOf 的返回值是 CompletableFuture<Object> 類型。
@Test public void anyOf() throws Exception { CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { throw new RuntimeException(e); } return "future1 結(jié)果"; }); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { throw new RuntimeException(e); } return "future2 結(jié)果"; }); CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } return "future3 結(jié)果"; }); CompletableFuture<Object> future = CompletableFuture.anyOf(future1, future2, future3); System.out.println(future.get()); }
在該例子中,因?yàn)?future1、future2、future3 的返回值都是 CompletableFuture<String>,所以anyOf 的返回的 Object 一定也是 String 類型。
并且在 3 個(gè) future 中,future3 睡眠時(shí)間最短,會(huì)最先執(zhí)行完成, anyOfFuture.get() 獲取的也就是 future3 的內(nèi)容。future1、future2 的返回結(jié)果會(huì)被丟棄。
異常處理
在調(diào)用 supplyAsync() 任務(wù)中發(fā)生一個(gè)錯(cuò)誤,這時(shí)候沒有任何 thenApply 會(huì)被調(diào)用并且 future 將以一個(gè)異常結(jié)束。如果在第一個(gè) thenApply() 發(fā)生錯(cuò)誤,這時(shí)候第二個(gè)和第三個(gè)將不會(huì)被調(diào)用,同樣的,future 將以異常結(jié)束。
exceptionally()
回調(diào)處理異常,從原始Future中生成的錯(cuò)誤恢復(fù)的機(jī)會(huì)。
@Test public void exceptionally() throws Exception { Integer age = -1; CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (age < 0) { throw new IllegalArgumentException("年齡不能為負(fù)數(shù)"); } return "張三"; }).exceptionally(ex -> { System.out.println(ex.getMessage()); return "Unknown!"; }); System.out.println(future.get()); }
handle()
從異常恢復(fù),無論一個(gè)異常是否發(fā)生它都會(huì)被調(diào)用。
@Test public void handle() throws Exception { Integer age = -1; CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { if (age < 0) { throw new IllegalArgumentException("年齡不能為負(fù)數(shù)"); } return "張三"; }).handle((res, ex) -> { if (ex != null) { System.out.println(ex.getMessage()); return "Unknown!"; } return res; }); System.out.println(future.get()); }
如果異常發(fā)生 res 參數(shù)將是 null,否則 ex 將是 null。
到此這篇關(guān)于Java中的CompletableFuture原理與用法的文章就介紹到這了,更多相關(guān)java CompletableFuture內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC自定義攔截器登錄檢測功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringMVC自定義攔截器登錄檢測功能的實(shí)現(xiàn),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08Java toString方法重寫工具之ToStringBuilder案例詳解
這篇文章主要介紹了Java toString方法重寫工具之ToStringBuilder案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08解決啟用 Spring-Cloud-OpenFeign 配置可刷新項(xiàng)目無法啟動(dòng)的問題
這篇文章主要介紹了解決啟用 Spring-Cloud-OpenFeign 配置可刷新項(xiàng)目無法啟動(dòng)的問題,本文重點(diǎn)給大家介紹Spring-Cloud-OpenFeign的原理及問題解決方法,需要的朋友可以參考下2021-10-10SpringBoot項(xiàng)目實(shí)現(xiàn)MyBatis流式查詢的教程詳解
這篇文章主要介紹了SpringBoot項(xiàng)目如何實(shí)現(xiàn)MyBatis的流式查詢,mybatis的流式查詢,有點(diǎn)冷門,實(shí)際用的場景比較少,但是在某些特殊場景下,卻是十分有效的一個(gè)方法,感興趣的同學(xué)可以參考一下2023-06-06springboot jpa實(shí)現(xiàn)優(yōu)雅處理isDelete的默認(rèn)值
如果多個(gè)實(shí)體類都有 isDelete 字段,并且你希望在插入時(shí)為它們統(tǒng)一設(shè)置默認(rèn)值時(shí)改怎么做呢,本文為大家整理了一些方法,希望對大家有所幫助2024-11-11詳解springboot中redis的使用和分布式session共享問題
這篇文章主要介紹了詳解springboot中redis的使用和分布式session共享問題,詳細(xì)的介紹了解決分布式系統(tǒng)的session如何共享問題,有興趣的可以了解一下2017-11-11SpringBoot將項(xiàng)目打成war包步驟解析
這篇文章主要介紹了SpringBoot將項(xiàng)目打成war包步驟解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03MyBatis自定義typeHandler的完整實(shí)例
這篇文章主要給大家介紹了關(guān)于MyBatis自定義typeHandler的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用MyBatis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04