Spring WebFlux 是 Spring Framework 的一部分,它提供了构建响应式 Web 应用程序的能力。WebFlux 支持非阻塞 I/O,使得应用程序能够高效地处理大量并发请求。下面来详细介绍 Spring WebFlux。
1. 入门指南
1.1 安装和设置
为了开始使用 Spring WebFlux,你需要先安装 Java 和 Maven 或 Gradle。然后,你可以使用 Spring Initializr 来创建一个新的项目。以下是使用 Maven 的示例 pom.xml
文件:
<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>
<groupId>com.example</groupId>
<artifactId>webflux-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>webflux-demo</name>
<description>Demo project for Spring WebFlux</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 创建一个简单的 REST API
接下来,我们创建一个简单的 REST API。
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> routes(GreetingHandler greetingHandler) {
return route(GET("/greeting"), greetingHandler::greet);
}
}
@Component
class GreetingHandler {
public Mono<ServerResponse> greet(ServerRequest request) {
return ok()
.contentType(APPLICATION_JSON)
.body(Mono.just(new Greeting("Hello, World!")), Greeting.class);
}
}
record Greeting(String message) {}
2. 响应式编程基础
2.1 Mono 和 Flux
- Mono:表示单个结果的容器,类似于
Future<T>
。 - Flux:表示零个、一个或多个结果的容器,类似于
Stream<T>
。
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class ReactiveBasics {
public static void main(String[] args) {
// Mono example
Mono.just("Hello")
.subscribe(System.out::println);
// Flux example
Flux.range(1, 10)
.map(i -> i * 2)
.subscribe(System.out::println);
}
}
3. 控制器与路由
3.1 使用 @RestController
使用 @RestController
和 @GetMapping
等注解来定义 RESTful 控制器。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class GreetingController {
@GetMapping("/greeting")
public Mono<Greeting> greet() {
return Mono.just(new Greeting("Hello, World!"));
}
}
4. 处理表单和请求体
4.1 处理 POST 请求
使用 @PostMapping
注解来处理 POST 请求,并通过 @RequestBody
注解来读取请求体。
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController
public class ItemController {
@PostMapping("/items")
public Mono<Item> createItem(@RequestBody Mono<Item> itemMono) {
return itemMono
.map(item -> {
// Perform some operation on the item
return item;
});
}
}
record Item(String name, String description) {}
如果要使用Spring Data R2DBC和数据库交互,则Item定义如下:
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
import reactor.core.publisher.Mono;
@Table("items")
public record Item(
@Id
Long id,
String name,
String description
) {
public Item {
// 如果没有提供 id,则默认为 null
this.id = (this.id == null) ? null : this.id;
}
}
pom.xml添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
application.yml添加配置:
spring:
r2dbc:
url: r2dbc:postgresql://localhost:5432/mydb
username: myuser
password: mypassword
database: mydb
5. 异常处理
5.1 使用 @ExceptionHandler
使用 @ControllerAdvice
和 @ExceptionHandler
来处理异常。
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import reactor.core.publisher.Mono;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = IllegalArgumentException.class)
public Mono<ResponseEntity<String>> handleIllegalArgumentException(IllegalArgumentException ex) {
return Mono.just(new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST));
}
}
6. 数据访问
6.1 使用 Spring Data R2DBC
使用 Spring Data R2DBC 来访问关系型数据库。
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Repository
public interface ItemRepository extends ReactiveCrudRepository<Item, Long> {
Flux<Item> findByName(String name);
}
// 在 ItemController 中使用 ItemRepository
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
public class ItemController {
private final ItemRepository itemRepository;
@Autowired
public ItemController(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
@GetMapping("/items")
public Flux<Item> getItems() {
return itemRepository.findAll();
}
}
7. 安全性和认证
7.1 使用 Spring Security
使用 Spring Security 来保护你的 WebFlux 应用程序。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/greeting").permitAll()
.anyExchange().authenticated()
.and()
.formLogin()
.and()
.build();
}
}
8. 高级主题
8.1 非阻塞 I/O
使用非阻塞 I/O 来提高性能。
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class NonBlockingIOExample {
public static void main(String[] args) {
WebClient client = WebClient.create("https://api.example.com");
Mono<String> response = client.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
}
}
8.2 自定义函数式路由
使用函数式编程风格来定义路由。
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.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class FunctionalRoutingConfig {
@Bean
public RouterFunction<ServerResponse> routerFunction() {
return RouterFunctions
.route(GET("/greeting"), req -> ServerResponse.ok().bodyValue("Hello, World!"))
.andRoute(GET("/items"), req -> ServerResponse.ok().bodyValue("List of items"));
}
}
9. 性能优化
9.1 缓存
使用 Spring Cache 或其他缓存机制来提高性能。
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class ItemService {
@Cacheable(value = "items", key = "#id")
public Mono<Item> getItem(Long id) {
// Fetch the item from the database or other source
return Mono.just(new Item("Name", "Description"));
}
}
9.2 并发处理
利用并行处理来提高并发能力。
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.scheduler.Schedulers;
@Service
public class ItemService {
public Flux<Item> fetchItemsInParallel() {
return Flux.range(1, 100)
.parallel(10)
.runOn(Schedulers.boundedElastic())
.map(i -> new Item("Name " + i, "Description " + i))
.sequential();
}
}
10. 测试
10.1 单元测试
使用 JUnit 和 MockWebServer 进行单元测试。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
@WebFluxTest
public class GreetingControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
public void shouldReturnDefaultGreeting() {
webTestClient.get().uri("/greeting")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello, World!");
}
}
11. 部署
11.1 生产部署
使用 Spring Boot 打包工具将应用打包成可执行的 JAR 文件。
mvn clean package
java -jar target/webflux-demo-0.0.1-SNAPSHOT.jar
12. 进阶主题
12.1 WebSocket 支持
使用 WebSockets 实现实时通信。
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import reactor.core.publisher.Mono;
public class ChatWebSocketHandler implements WebSocketHandler {
@Override
public Mono<Void> handle(WebSocketSession session) {
return session.receive()
.map(WebSocketMessage::getPayloadAsText)
.doOnNext(message -> System.out.println("Received message: " + message))
.then(session.send(Mono.just(session.textMessage("Echo: " + message))))
.then();
}
}
12.2 客户端支持
使用 Spring WebFlux Client 来实现客户端逻辑。
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientExample {
public static void main(String[] args) {
WebClient client = WebClient.create("https://api.example.com");
Mono<String> response = client.get()
.uri("/data")
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
}
}
13. 结论
Spring WebFlux 提供了强大的工具来构建响应式的 Web 应用程序。随着你对这些特性的熟悉程度加深,你可以根据自己的需求来扩展和定制你的应用程序。