Java 響應(yīng)式編程之Spring WebFlux+Reactor 實(shí)戰(zhàn)攻略
在傳統(tǒng)的Java Web開發(fā)中,Spring MVC基于Servlet API構(gòu)建,采用的是同步阻塞的I/O模型。這種模型在高并發(fā)、高IO等待的場(chǎng)景下會(huì)面臨性能瓶頸——每個(gè)請(qǐng)求都需要占用一個(gè)獨(dú)立的線程,大量線程的創(chuàng)建和切換會(huì)消耗過(guò)多的系統(tǒng)資源。為了解決這一問(wèn)題,響應(yīng)式編程應(yīng)運(yùn)而生。
本文將帶你深入了解Java響應(yīng)式編程的核心,重點(diǎn)講解Spring WebFlux框架與Reactor響應(yīng)式庫(kù)的實(shí)戰(zhàn)用法,通過(guò)大量可運(yùn)行的示例代碼,幫助你快速上手并理解響應(yīng)式編程的精髓。
一、響應(yīng)式編程核心概念
在開始實(shí)戰(zhàn)之前,我們先明確幾個(gè)響應(yīng)式編程的核心概念,為后續(xù)學(xué)習(xí)打下基礎(chǔ)。
1.1 什么是響應(yīng)式編程?
響應(yīng)式編程是一種基于數(shù)據(jù)流(Data Stream)和變化傳播(Propagation of Change)的編程范式。簡(jiǎn)單來(lái)說(shuō),就是當(dāng)數(shù)據(jù)發(fā)生變化時(shí),系統(tǒng)會(huì)自動(dòng)響應(yīng)這些變化,而無(wú)需主動(dòng)輪詢或等待。
其核心特性包括:
- 非阻塞:操作不會(huì)阻塞當(dāng)前線程,而是在任務(wù)完成后通過(guò)回調(diào)函數(shù)通知結(jié)果,極大提升了線程利用率。
- 異步:任務(wù)的執(zhí)行與結(jié)果的處理不在同一時(shí)間線,避免了線程的閑置等待。
- 數(shù)據(jù)流驅(qū)動(dòng):一切操作圍繞數(shù)據(jù)流展開,支持對(duì)數(shù)據(jù)流的過(guò)濾、轉(zhuǎn)換、合并等多種操作。
- 背壓(Backpressure):消費(fèi)者可以告知生產(chǎn)者自己的處理能力,避免生產(chǎn)者發(fā)送數(shù)據(jù)過(guò)快導(dǎo)致消費(fèi)者過(guò)載,這是響應(yīng)式編程區(qū)別于傳統(tǒng)異步編程的關(guān)鍵特性。
1.2 Reactor:Java響應(yīng)式編程的基石
Reactor是Spring官方推薦的響應(yīng)式編程庫(kù),也是Spring WebFlux的底層依賴。它實(shí)現(xiàn)了Reactive Streams規(guī)范,提供了兩個(gè)核心的數(shù)據(jù)流類型:Mono和Flux,用于處理01個(gè)元素和0N個(gè)元素的場(chǎng)景。
Reactive Streams是一個(gè)行業(yè)標(biāo)準(zhǔn),定義了響應(yīng)式數(shù)據(jù)流的發(fā)布者(Publisher)、訂閱者(Subscriber)、訂閱(Subscription)和處理器(Processor)四個(gè)核心接口,目的是實(shí)現(xiàn)不同響應(yīng)式庫(kù)之間的互操作性。
1.2.1 Mono與Flux的區(qū)別
| 類型 | 描述 | 適用場(chǎng)景 |
|---|---|---|
| Mono | 表示0或1個(gè)元素的異步序列,可能成功完成或失敗 | 查詢單個(gè)對(duì)象(如根據(jù)ID查詢用戶)、執(zhí)行無(wú)返回值的操作(如插入數(shù)據(jù)) |
| Flux | 表示0到N個(gè)元素的異步序列,支持背壓機(jī)制 | 查詢列表數(shù)據(jù)(如分頁(yè)查詢用戶)、處理流式數(shù)據(jù)(如日志流、消息流) |
二、環(huán)境搭建:Spring WebFlux+Reactor入門
接下來(lái)我們搭建一個(gè)基礎(chǔ)的Spring WebFlux項(xiàng)目,體驗(yàn)響應(yīng)式編程的基本用法。
2.1 項(xiàng)目依賴配置(Maven)
創(chuàng)建一個(gè)Maven項(xiàng)目,在pom.xml中添加以下依賴:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.15</version>
<relativePath/>
</parent>
<dependencies>
<!-- Spring WebFlux 核心依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Reactor 測(cè)試依賴 -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok 簡(jiǎn)化代碼 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot 測(cè)試依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>說(shuō)明:spring-boot-starter-webflux已經(jīng)包含了Reactor的核心依賴(reactor-core),無(wú)需額外引入。
2.2 啟動(dòng)類編寫
創(chuàng)建Spring Boot啟動(dòng)類,與Spring MVC的啟動(dòng)類類似,只需添加@SpringBootApplication注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebFluxReactorApplication {
public static void main(String[] args) {
SpringApplication.run(WebFluxReactorApplication.class, args);
}
}三、Reactor核心實(shí)戰(zhàn):Mono與Flux操作
Reactor提供了豐富的操作符(Operator)用于處理Mono和Flux數(shù)據(jù)流,本節(jié)通過(guò)示例講解常用操作。
3.1 數(shù)據(jù)流的創(chuàng)建
常用的創(chuàng)建方法:just()、fromIterable()、empty()、error()等。
import io.projectreactor.core.publisher.Flux;
import io.projectreactor.core.publisher.Mono;
import org.junit.Test;
public class ReactorCreateTest {
// 測(cè)試Flux創(chuàng)建
@Test
public void testFluxCreate() {
// 1. 創(chuàng)建包含多個(gè)元素的Flux
Flux<String> flux1 = Flux.just("Java", "Spring", "WebFlux");
flux1.subscribe(System.out::println); // 訂閱并打印元素
System.out.println("=====");
// 2. 從集合創(chuàng)建Flux
List<Integer> list = Arrays.asList(1, 2, 3, 4);
Flux<Integer> flux2 = Flux.fromIterable(list);
flux2.subscribe(num -> System.out.println("數(shù)字:" + num));
System.out.println("=====");
// 3. 創(chuàng)建空Flux(會(huì)觸發(fā)onComplete回調(diào))
Flux<Void> flux3 = Flux.empty();
flux3.subscribe(
null,
error -> System.err.println("錯(cuò)誤:" + error),
() -> System.out.println("flux3 完成")
);
}
// 測(cè)試Mono創(chuàng)建
@Test
public void testMonoCreate() {
// 1. 創(chuàng)建包含單個(gè)元素的Mono
Mono<String> mono1 = Mono.just("Hello Reactor");
mono1.subscribe(System.out::println);
System.out.println("=====");
// 2. 創(chuàng)建空Mono
Mono<String> mono2 = Mono.empty();
mono2.subscribe(
str -> System.out.println("元素:" + str),
error -> System.err.println("錯(cuò)誤:" + error),
() -> System.out.println("mono2 完成")
);
System.out.println("=====");
// 3. 創(chuàng)建包含錯(cuò)誤的Mono
Mono<String> mono3 = Mono.error(new RuntimeException("測(cè)試錯(cuò)誤"));
mono3.subscribe(
str -> System.out.println("元素:" + str),
error -> System.err.println("錯(cuò)誤:" + error.getMessage()),
() -> System.out.println("mono3 完成")
);
}
}運(yùn)行測(cè)試方法,可以看到不同創(chuàng)建方式的輸出結(jié)果。注意:Reactor的數(shù)據(jù)流只有在被訂閱(subscribe)后才會(huì)開始執(zhí)行,這就是“惰性求值”特性。
3.2 常用操作符實(shí)戰(zhàn)
操作符用于對(duì)數(shù)據(jù)流進(jìn)行轉(zhuǎn)換、過(guò)濾、聚合等處理,以下是最常用的操作符示例。
3.2.1 轉(zhuǎn)換操作:map與flatMap
map:同步轉(zhuǎn)換,將一個(gè)元素轉(zhuǎn)換為另一個(gè)元素;flatMap:異步轉(zhuǎn)換,將一個(gè)元素轉(zhuǎn)換為一個(gè)新的數(shù)據(jù)流(Mono/Flux),并合并這些數(shù)據(jù)流。
import io.projectreactor.core.publisher.Flux;
import org.junit.Test;
public class ReactorMapTest {
@Test
public void testMap() {
// map:將字符串轉(zhuǎn)換為其長(zhǎng)度
Flux<String> flux = Flux.just("Java", "Spring", "WebFlux");
flux.map(String::length)
.subscribe(length -> System.out.println("字符串長(zhǎng)度:" + length));
}
@Test
public void testFlatMap() {
// flatMap:將每個(gè)數(shù)字轉(zhuǎn)換為一個(gè)新的Flux(0到該數(shù)字),并合并所有Flux
Flux<Integer> flux = Flux.just(2, 3);
flux.flatMap(num -> Flux.range(0, num))
.subscribe(System.out::println);
// 輸出:0,1,0,1,2
}
}3.2.2 過(guò)濾操作:filter與take
filter:根據(jù)條件過(guò)濾元素;take:獲取前N個(gè)元素。
import io.projectreactor.core.publisher.Flux;
import org.junit.Test;
public class ReactorFilterTest {
@Test
public void testFilter() {
// 過(guò)濾出偶數(shù)
Flux<Integer> flux = Flux.range(1, 10); // 生成1到10的數(shù)字
flux.filter(num -> num % 2 == 0)
.subscribe(even -> System.out.println("偶數(shù):" + even));
}
@Test
public void testTake() {
// 獲取前3個(gè)元素
Flux<String> flux = Flux.just("A", "B", "C", "D", "E");
flux.take(3)
.subscribe(str -> System.out.println("前3個(gè)元素:" + str));
}
}3.2.3 聚合操作:reduce與collectList
reduce:將數(shù)據(jù)流中的元素聚合為一個(gè)值;collectList:將數(shù)據(jù)流中的所有元素收集到一個(gè)List中,返回Mono。
import io.projectreactor.core.publisher.Flux;
import org.junit.Test;
public class ReactorAggregateTest {
@Test
public void testReduce() {
// 計(jì)算1到10的和
Flux<Integer> flux = Flux.range(1, 10);
flux.reduce((a, b) -> a + b)
.subscribe(sum -> System.out.println("1到10的和:" + sum));
}
@Test
public void testCollectList() {
// 將元素收集到List中
Flux<String> flux = Flux.just("Java", "Spring", "WebFlux");
flux.collectList()
.subscribe(list -> System.out.println("元素列表:" + list));
}
}四、Spring WebFlux實(shí)戰(zhàn):響應(yīng)式Web開發(fā)
Spring WebFlux支持兩種編程模型:基于注解的編程模型(類似Spring MVC)和基于函數(shù)式的編程模型。本節(jié)分別講解兩種模型的實(shí)戰(zhàn)用法。
4.1 基于注解的編程模型(常用)
這種模型與Spring MVC非常相似,使用@RestController、@GetMapping、@PostMapping等注解,上手成本低。
4.1.1 實(shí)體類定義
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
}4.1.2 模擬數(shù)據(jù)服務(wù)
創(chuàng)建UserService,模擬數(shù)據(jù)庫(kù)操作:
import io.projectreactor.core.publisher.Flux;
import io.projectreactor.core.publisher.Mono;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
// 模擬數(shù)據(jù)庫(kù)
private static final Map<Long, User> USER_MAP = new HashMap<>();
static {
USER_MAP.put(1L, new User(1L, "張三", 20));
USER_MAP.put(2L, new User(2L, "李四", 25));
USER_MAP.put(3L, new User(3L, "王五", 30));
}
// 查詢所有用戶
public Flux<User> findAll() {
return Flux.fromIterable(USER_MAP.values());
}
// 根據(jù)ID查詢用戶
public Mono<User> findById(Long id) {
return Mono.justOrEmpty(USER_MAP.get(id));
}
// 添加用戶
public Mono<User> addUser(User user) {
// 生成唯一ID(簡(jiǎn)化處理)
Long id = USER_MAP.keySet().stream().max(Long::compare).orElse(0L) + 1;
user.setId(id);
USER_MAP.put(id, user);
return Mono.just(user);
}
// 更新用戶
public Mono<User> updateUser(Long id, User user) {
if (!USER_MAP.containsKey(id)) {
return Mono.error(new RuntimeException("用戶不存在"));
}
user.setId(id);
USER_MAP.put(id, user);
return Mono.just(user);
}
// 刪除用戶
public Mono<Void> deleteUser(Long id) {
USER_MAP.remove(id);
return Mono.empty(); // 無(wú)返回值,用Mono<Void>表示
}
}4.1.3 控制器編寫
import io.projectreactor.core.publisher.Flux;
import io.projectreactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
// 查詢所有用戶
@GetMapping
public Flux<User> findAll() {
return userService.findAll();
}
// 根據(jù)ID查詢用戶
@GetMapping("/{id}")
public Mono<User> findById(@PathVariable Long id) {
return userService.findById(id);
}
// 添加用戶
@PostMapping
public Mono<User> addUser(@RequestBody Mono<User> userMono) {
// 接收請(qǐng)求體為Mono<User>,通過(guò)flatMap調(diào)用服務(wù)方法
return userMono.flatMap(userService::addUser);
}
// 更新用戶
@PutMapping("/{id}")
public Mono<User> updateUser(@PathVariable Long id, @RequestBody Mono<User> userMono) {
return userMono.flatMap(user -> userService.updateUser(id, user));
}
// 刪除用戶
@DeleteMapping("/{id}")
public Mono<Void> deleteUser(@PathVariable Long id) {
return userService.deleteUser(id);
}
}4.1.4 接口測(cè)試
啟動(dòng)項(xiàng)目后,使用Postman或curl測(cè)試接口:
- 查詢所有用戶:GET http://localhost:8080/users → 返回所有用戶的JSON數(shù)組
- 根據(jù)ID查詢:GET http://localhost:8080/users/1 → 返回張三的信息
- 添加用戶:POST http://localhost:8080/users,請(qǐng)求體{“name”:“趙六”,“age”:35} → 返回添加后的用戶信息
- 更新用戶:PUT http://localhost:8080/users/4,請(qǐng)求體{“name”:“趙六更新”,“age”:36} → 返回更新后的用戶信息
- 刪除用戶:DELETE http://localhost:8080/users/4 → 無(wú)返回值,狀態(tài)碼200
4.2 基于函數(shù)式的編程模型
Spring WebFlux的函數(shù)式模型基于RouterFunction和HandlerFunction,更符合響應(yīng)式編程的函數(shù)式風(fēng)格,適用于簡(jiǎn)單的API場(chǎng)景。
4.2.1 編寫處理器(Handler)
處理器負(fù)責(zé)處理具體的請(qǐng)求邏輯,類似控制器中的方法:
import io.projectreactor.core.publisher.Flux;
import io.projectreactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
@Component
public class UserHandler {
@Autowired
private UserService userService;
// 查詢所有用戶
public Mono<ServerResponse> findAll(ServerRequest request) {
Flux<User> userFlux = userService.findAll();
// 返回JSON數(shù)組,媒體類型為APPLICATION_JSON
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userFlux, User.class);
}
// 根據(jù)ID查詢用戶
public Mono<ServerResponse> findById(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
Mono<User> userMono = userService.findById(id);
// 如果用戶存在,返回200;不存在,返回404
return userMono.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fromValue(user)))
.switchIfEmpty(ServerResponse.notFound().build());
}
// 添加用戶
public Mono<ServerResponse> addUser(ServerRequest request) {
Mono<User> userMono = request.bodyToMono(User.class);
return userMono.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userService.addUser(user), User.class));
}
}4.2.2 編寫路由配置(Router)
路由配置用于將請(qǐng)求路徑映射到對(duì)應(yīng)的處理器方法:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
@Configuration
public class UserRouter {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler userHandler) {
return RouterFunctions
.route(GET("/func/users"), userHandler::findAll)
.andRoute(GET("/func/users/{id}"), userHandler::findById)
.andRoute(POST("/func/users"), userHandler::addUser);
}
}4.2.3 測(cè)試函數(shù)式接口
啟動(dòng)項(xiàng)目后,測(cè)試函數(shù)式接口:
- 查詢所有用戶:GET http://localhost:8080/func/users
- 根據(jù)ID查詢:GET http://localhost:8080/func/users/1
- 添加用戶:POST http://localhost:8080/func/users,請(qǐng)求體{“name”:“孫七”,“age”:40}
五、關(guān)鍵拓展知識(shí)點(diǎn)
掌握以下拓展知識(shí)點(diǎn),能幫助你更好地應(yīng)對(duì)實(shí)際開發(fā)中的問(wèn)題。
5.1 背壓機(jī)制詳解
背壓是響應(yīng)式編程的核心特性之一,用于解決“生產(chǎn)者速度大于消費(fèi)者處理速度”的問(wèn)題。Reactor通過(guò)Subscription接口實(shí)現(xiàn)背壓:消費(fèi)者通過(guò)request(n)方法告知生產(chǎn)者自己最多能處理n個(gè)元素,生產(chǎn)者根據(jù)這個(gè)需求發(fā)送數(shù)據(jù)。
import io.projectreactor.core.publisher.Flux;
import org.junit.Test;
public class BackpressureTest {
@Test
public void testBackpressure() {
Flux.range(1, 100) // 生產(chǎn)者生成1到100的數(shù)字
.subscribe(
num -> {
System.out.println("處理元素:" + num);
try {
Thread.sleep(100); // 模擬消費(fèi)者處理耗時(shí)
} catch (InterruptedException e) {
e.printStackTrace();
}
},
error -> System.err.println("錯(cuò)誤:" + error),
() -> System.out.println("處理完成"),
subscription -> subscription.request(2) // 初始請(qǐng)求2個(gè)元素
);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}上述代碼中,消費(fèi)者初始請(qǐng)求2個(gè)元素,處理完后會(huì)自動(dòng)請(qǐng)求后續(xù)元素(Reactor的默認(rèn)行為),避免了數(shù)據(jù)堆積。
5.2 異常處理策略
響應(yīng)式數(shù)據(jù)流中的異常會(huì)終止數(shù)據(jù)流,Reactor提供了多種異常處理操作符:
- onErrorReturn:當(dāng)發(fā)生異常時(shí),返回一個(gè)默認(rèn)值;
- onErrorResume:當(dāng)發(fā)生異常時(shí),切換到一個(gè)新的數(shù)據(jù)流;
- retry:當(dāng)發(fā)生異常時(shí),重試指定次數(shù)。
import io.projectreactor.core.publisher.Mono;
import org.junit.Test;
public class ErrorHandleTest {
@Test
public void testOnErrorReturn() {
Mono.error(new RuntimeException("測(cè)試異常"))
.onErrorReturn("默認(rèn)值") // 發(fā)生異常時(shí)返回默認(rèn)值
.subscribe(System.out::println); // 輸出:默認(rèn)值
}
@Test
public void testOnErrorResume() {
Mono.error(new RuntimeException("測(cè)試異常"))
.onErrorResume(error -> Mono.just("從異常中恢復(fù)")) // 切換到新的Mono
.subscribe(System.out::println); // 輸出:從異常中恢復(fù)
}
@Test
public void testRetry() {
Mono<Integer> mono = Mono.defer(() -> {
System.out.println("執(zhí)行方法");
return Math.random() > 0.5 ? Mono.just(1) : Mono.error(new RuntimeException("隨機(jī)異常"));
});
mono.retry(2) // 最多重試2次
.subscribe(
num -> System.out.println("成功:" + num),
error -> System.err.println("最終失?。? + error)
);
}
}5.3 Spring WebFlux與Spring MVC的對(duì)比
| 特性 | Spring MVC | Spring WebFlux |
|---|---|---|
| 編程模型 | 基于注解(@Controller等) | 注解模型 + 函數(shù)式模型 |
| IO模型 | 同步阻塞IO | 異步非阻塞IO |
| 底層依賴 | Servlet API | Reactor + Netty(默認(rèn))/Servlet 3.1+ |
| 適用場(chǎng)景 | 普通Web應(yīng)用,CPU密集型場(chǎng)景 | 高并發(fā)、高IO等待場(chǎng)景(如API網(wǎng)關(guān)、消息處理) |
| 數(shù)據(jù)訪問(wèn) | JPA、MyBatis(同步) | R2DBC(響應(yīng)式關(guān)系型數(shù)據(jù)庫(kù))、MongoDB Reactive等 |
六、總結(jié)
本文從響應(yīng)式編程的核心概念出發(fā),詳細(xì)講解了Reactor庫(kù)的Mono與Flux操作,以及Spring WebFlux的兩種編程模型(注解式和函數(shù)式),并通過(guò)完整的實(shí)戰(zhàn)示例幫助你快速上手。同時(shí),我們還拓展了背壓機(jī)制、異常處理等關(guān)鍵知識(shí)點(diǎn),以及Spring WebFlux與Spring MVC的區(qū)別。
響應(yīng)式編程的核心價(jià)值在于提升高并發(fā)場(chǎng)景下的系統(tǒng)吞吐量和資源利用率,但它也帶來(lái)了一定的學(xué)習(xí)成本,需要轉(zhuǎn)變傳統(tǒng)的同步編程思維。建議在實(shí)際項(xiàng)目中,根據(jù)業(yè)務(wù)場(chǎng)景選擇合適的技術(shù)框架:如果是高并發(fā)、高IO等待的場(chǎng)景,Spring WebFlux是不錯(cuò)的選擇;如果是普通Web應(yīng)用,Spring MVC依然是更成熟、更易維護(hù)的方案。
后續(xù)可以進(jìn)一步學(xué)習(xí)響應(yīng)式數(shù)據(jù)訪問(wèn)(如R2DBC、MongoDB Reactive)、Spring Cloud Gateway(基于WebFlux的API網(wǎng)關(guān))等內(nèi)容,深入挖掘響應(yīng)式編程的潛力。
到此這篇關(guān)于Java 響應(yīng)式編程之Spring WebFlux+Reactor 實(shí)戰(zhàn)攻略的文章就介紹到這了,更多相關(guān)Spring WebFlux Reactor 實(shí)戰(zhàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
spring boot自定義log4j2日志文件的實(shí)例講解
下面小編就為大家分享一篇spring boot自定義log4j2日志文件的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置方法,看這篇夠了
關(guān)于JDK+Tomcat+eclipse+MyEclipse的配置問(wèn)題,很多朋友都搞不太明白,網(wǎng)上一搜配置方法多種哪種最精簡(jiǎn)呢,今天小編給大家分享一篇文章幫助大家快速掌握J(rèn)DK Tomcat eclipse MyEclipse配置技巧,需要的朋友參考下吧2021-06-06
java 實(shí)現(xiàn)文件夾的拷貝實(shí)例代碼
這篇文章主要介紹了java 實(shí)現(xiàn)文件夾的拷貝實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04
Java中將異步調(diào)用轉(zhuǎn)為同步的五種實(shí)現(xiàn)方法
本文介紹了將異步調(diào)用轉(zhuǎn)為同步阻塞模式的五種方法:wait/notify、ReentrantLock+Condition、Future、CountDownLatch和CyclicBarrier,每種方法都有其適用場(chǎng)景和核心機(jī)制,可以根據(jù)具體需求選擇合適的方法,需要的朋友可以參考下2025-02-02
多模塊的springboot項(xiàng)目發(fā)布指定模塊的腳本方式
該文章主要介紹了如何在多模塊的SpringBoot項(xiàng)目中發(fā)布指定模塊的腳本,作者原先的腳本會(huì)清理并編譯所有模塊,導(dǎo)致發(fā)布時(shí)間過(guò)長(zhǎng),通過(guò)簡(jiǎn)化腳本,只使用`mvn clean install`命令,可以快速發(fā)布指定模塊及其依賴的模塊2025-01-01

