Spring WebFlux详解:响应式编程从入门到实战
前言
Spring WebFlux是Spring 5引入的响应式Web框架,它基于Reactor库实现,支持非阻塞式异步编程。在高并发场景下,WebFlux能够以更少的资源处理更多的请求。本文将深入讲解WebFlux的核心概念、编程模型和实战应用。
一、响应式编程概述
1.1 什么是响应式编程
响应式编程(Reactive Programming)是一种面向数据流和变化传播的编程范式,它基于异步数据流进行编程。
传统阻塞式 vs 响应式
┌─────────────────────────────────────────┐
│ 传统阻塞式(Spring MVC) │
│ ┌──────────┐ │
│ │ 线程1 │ ─────────────────────────→│
│ │ 请求A │ 等待IO... │ 处理 │
│ └──────────┘ │
│ ┌──────────┐ │
│ │ 线程2 │ ─────────────────────────→│
│ │ 请求B │ 等待IO... │ 处理 │
│ └──────────┘ │
│ 每个请求占用一个线程 │
├─────────────────────────────────────────┤
│ 响应式(Spring WebFlux) │
│ ┌──────────┐ │
│ │ 线程1 │ ─A─>─B─>─A─>─C─>─B─>─A─> │
│ │ (共享) │ 多个请求复用同一线程 │
│ └──────────┘ │
│ 少量线程处理大量并发请求 │
└─────────────────────────────────────────┘
1.2 响应式流规范(Reactive Streams)
响应式流规范定义了四个核心接口:
java
/**
* Publisher - 数据发布者
*/
public interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}
/**
* Subscriber - 数据订阅者
*/
public interface Subscriber<T> {
void onSubscribe(Subscription subscription);
void onNext(T item);
void onError(Throwable throwable);
void onComplete();
}
/**
* Subscription - 订阅关系
*/
public interface Subscription {
void request(long n); // 背压:请求n个元素
void cancel();
}
/**
* Processor - 处理器(同时是Publisher和Subscriber)
*/
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
1.3 Reactor核心类型
Reactor核心类型
┌─────────────────────────────────────────┐
│ Mono<T> │
│ - 表示0或1个元素的异步序列 │
│ - 类似于Optional的异步版本 │
│ - 适用于单值返回场景 │
├─────────────────────────────────────────┤
│ Flux<T> │
│ - 表示0到N个元素的异步序列 │
│ - 类似于List的异步版本 │
│ - 适用于多值/流式返回场景 │
└─────────────────────────────────────────┘
二、快速开始
2.1 Maven依赖
xml
<!-- Spring WebFlux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 响应式数据库(R2DBC) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<!-- 响应式MongoDB -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<!-- 响应式Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
2.2 第一个WebFlux应用
java
/**
* 基于注解的Controller(与Spring MVC类似)
*/
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
/**
* 返回单个用户(Mono)
*/
@GetMapping("/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userService.findById(id);
}
/**
* 返回用户列表(Flux)
*/
@GetMapping
public Flux<User> getAllUsers() {
return userService.findAll();
}
/**
* 创建用户
*/
@PostMapping
public Mono<User> createUser(@RequestBody User user) {
return userService.save(user);
}
/**
* 更新用户
*/
@PutMapping("/{id}")
public Mono<User> updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
public Mono<Void> deleteUser(@PathVariable Long id) {
return userService.deleteById(id);
}
}
/**
* 用户实体
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String email;
private Integer age;
}
2.3 函数式端点(Functional Endpoints)
java
/**
* 函数式路由配置
*/
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
return RouterFunctions.route()
// GET /api/users/{id}
.GET("/api/users/{id}", handler::getUser)
// GET /api/users
.GET("/api/users", handler::getAllUsers)
// POST /api/users
.POST("/api/users", handler::createUser)
// PUT /api/users/{id}
.PUT("/api/users/{id}", handler::updateUser)
// DELETE /api/users/{id}
.DELETE("/api/users/{id}", handler::deleteUser)
// 嵌套路由
.path("/api/admin", builder -> builder
.GET("/stats", handler::getStats)
.POST("/batch", handler::batchCreate)
)
.build();
}
}
/**
* Handler处理类
*/
@Component
public class UserHandler {
@Autowired
private UserService userService;
/**
* 获取单个用户
*/
public Mono<ServerResponse> getUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.findById(id)
.flatMap(user -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
}
/**
* 获取所有用户
*/
public Mono<ServerResponse> getAllUsers(ServerRequest request) {
Flux<User> users = userService.findAll();
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(users, User.class);
}
/**
* 创建用户
*/
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(User.class)
.flatMap(user -> userService.save(user))
.flatMap(savedUser -> ServerResponse
.status(HttpStatus.CREATED)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(savedUser));
}
/**
* 更新用户
*/
public Mono<ServerResponse> updateUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return request.bodyToMono(User.class)
.flatMap(user -> userService.update(id, user))
.flatMap(updatedUser -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(updatedUser))
.switchIfEmpty(ServerResponse.notFound().build());
}
/**
* 删除用户
*/
public Mono<ServerResponse> deleteUser(ServerRequest request) {
Long id = Long.parseLong(request.pathVariable("id"));
return userService.deleteById(id)
.then(ServerResponse.noContent().build());
}
/**
* 获取统计信息
*/
public Mono<ServerResponse> getStats(ServerRequest request) {
return userService.getStats()
.flatMap(stats -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(stats));
}
/**
* 批量创建
*/
public Mono<ServerResponse> batchCreate(ServerRequest request) {
Flux<User> users = request.bodyToFlux(User.class);
return userService.saveAll(users)
.collectList()
.flatMap(savedUsers -> ServerResponse
.status(HttpStatus.CREATED)
.bodyValue(savedUsers));
}
}
三、Mono和Flux操作
3.1 创建操作
java
/**
* Mono和Flux的创建方式
*/
public class CreateDemo {
public void monoCreate() {
// 创建包含值的Mono
Mono<String> mono1 = Mono.just("Hello");
// 创建空Mono
Mono<String> mono2 = Mono.empty();
// 创建错误Mono
Mono<String> mono3 = Mono.error(new RuntimeException("Error"));
// 延迟创建
Mono<String> mono4 = Mono.defer(() -> Mono.just("Deferred"));
// 从Callable创建
Mono<String> mono5 = Mono.fromCallable(() -> "FromCallable");
// 从CompletableFuture创建
Mono<String> mono6 = Mono.fromFuture(
CompletableFuture.supplyAsync(() -> "FromFuture")
);
}
public void fluxCreate() {
// 从多个值创建
Flux<Integer> flux1 = Flux.just(1, 2, 3, 4, 5);
// 从数组创建
Flux<String> flux2 = Flux.fromArray(new String[]{"A", "B", "C"});
// 从集合创建
Flux<String> flux3 = Flux.fromIterable(Arrays.asList("A", "B", "C"));
// 范围创建
Flux<Integer> flux4 = Flux.range(1, 10); // 1到10
// 间隔创建(每秒发射一个)
Flux<Long> flux5 = Flux.interval(Duration.ofSeconds(1));
// 生成器创建
Flux<Integer> flux6 = Flux.generate(
() -> 0, // 初始状态
(state, sink) -> {
sink.next(state);
if (state == 10) {
sink.complete();
}
return state + 1;
}
);
// 编程式创建
Flux<String> flux7 = Flux.create(sink -> {
sink.next("A");
sink.next("B");
sink.next("C");
sink.complete();
});
}
}
3.2 转换操作
java
/**
* 转换操作
*/
public class TransformDemo {
public void transformOperations() {
Flux<Integer> numbers = Flux.range(1, 10);
// map - 同步转换
Flux<String> mapped = numbers.map(n -> "Number: " + n);
// flatMap - 异步转换(返回Publisher)
Flux<String> flatMapped = numbers.flatMap(n ->
Mono.just("Number: " + n).delayElement(Duration.ofMillis(100))
);
// concatMap - 顺序异步转换
Flux<String> concatMapped = numbers.concatMap(n ->
Mono.just("Number: " + n).delayElement(Duration.ofMillis(100))
);
// filter - 过滤
Flux<Integer> filtered = numbers.filter(n -> n % 2 == 0);
// take - 取前N个
Flux<Integer> taken = numbers.take(5);
// skip - 跳过前N个
Flux<Integer> skipped = numbers.skip(3);
// distinct - 去重
Flux<Integer> distinct = Flux.just(1, 1, 2, 2, 3, 3).distinct();
// sort - 排序
Flux<Integer> sorted = Flux.just(3, 1, 2).sort();
}
public void monoTransform() {
Mono<User> userMono = Mono.just(new User(1L, "张三", "test@test.com", 25));
// map
Mono<String> nameMono = userMono.map(User::getName);
// flatMap
Mono<String> detailMono = userMono.flatMap(user ->
fetchUserDetails(user.getId())
);
// zipWith - 合并两个Mono
Mono<String> combined = userMono.zipWith(
Mono.just("额外信息"),
(user, extra) -> user.getName() + " - " + extra
);
}
private Mono<String> fetchUserDetails(Long id) {
return Mono.just("User details for " + id);
}
}
3.3 组合操作
java
/**
* 组合操作
*/
public class CombineDemo {
public void combineOperations() {
Flux<Integer> flux1 = Flux.just(1, 2, 3);
Flux<Integer> flux2 = Flux.just(4, 5, 6);
// concat - 顺序连接
Flux<Integer> concat = Flux.concat(flux1, flux2);
// 结果: 1, 2, 3, 4, 5, 6
// merge - 并行合并
Flux<Integer> merge = Flux.merge(flux1, flux2);
// 结果: 交错(取决于发射时间)
// zip - 配对合并
Flux<String> zip = Flux.zip(flux1, flux2, (a, b) -> a + "+" + b);
// 结果: "1+4", "2+5", "3+6"
// combineLatest - 最新值组合
Flux<String> combineLatest = Flux.combineLatest(
flux1, flux2, (a, b) -> a + ":" + b
);
}
public void monoZip() {
Mono<String> name = Mono.just("张三");
Mono<Integer> age = Mono.just(25);
Mono<String> email = Mono.just("zhangsan@example.com");
// 合并多个Mono
Mono<User> userMono = Mono.zip(name, age, email)
.map(tuple -> new User(null, tuple.getT1(), tuple.getT3(), tuple.getT2()));
// 使用zipWith
Mono<String> combined = name.zipWith(age, (n, a) -> n + " - " + a);
}
}
3.4 错误处理
java
/**
* 错误处理
*/
public class ErrorHandlingDemo {
public void errorHandling() {
Flux<Integer> flux = Flux.just(1, 2, 0, 4, 5)
.map(n -> 10 / n); // 会在0处抛出异常
// onErrorReturn - 返回默认值
flux.onErrorReturn(-1)
.subscribe(System.out::println);
// 结果: 10, 5, -1
// onErrorResume - 返回备用Publisher
flux.onErrorResume(e -> Flux.just(-1, -2, -3))
.subscribe(System.out::println);
// onErrorMap - 转换异常
flux.onErrorMap(ArithmeticException.class,
e -> new BusinessException("除零错误"))
.subscribe(System.out::println);
// doOnError - 记录错误(不处理)
flux.doOnError(e -> System.err.println("发生错误: " + e.getMessage()))
.subscribe(System.out::println);
// retry - 重试
flux.retry(3) // 重试3次
.subscribe(System.out::println);
// retryWhen - 高级重试策略
flux.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.maxBackoff(Duration.ofSeconds(10)))
.subscribe(System.out::println);
}
public Mono<User> getUser(Long id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)))
.onErrorResume(e -> {
if (e instanceof UserNotFoundException) {
return Mono.just(new User(-1L, "默认用户", "", 0));
}
return Mono.error(e);
});
}
}
class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("用户不存在: " + id);
}
}
3.5 背压处理
java
/**
* 背压处理
*/
public class BackpressureDemo {
public void backpressure() {
Flux<Integer> flux = Flux.range(1, 1000);
// onBackpressureBuffer - 缓冲
flux.onBackpressureBuffer(100)
.subscribe(new BaseSubscriber<Integer>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
request(10); // 初始请求10个
}
@Override
protected void hookOnNext(Integer value) {
System.out.println("处理: " + value);
request(1); // 每处理完一个请求一个
}
});
// onBackpressureDrop - 丢弃
flux.onBackpressureDrop(dropped ->
System.out.println("丢弃: " + dropped))
.subscribe();
// onBackpressureLatest - 只保留最新
flux.onBackpressureLatest()
.subscribe();
// limitRate - 限制速率
flux.limitRate(10) // 每次最多请求10个
.subscribe();
}
}
四、响应式数据访问
4.1 R2DBC(响应式关系数据库)
java
/**
* R2DBC Repository
*/
public interface UserRepository extends ReactiveCrudRepository<User, Long> {
Flux<User> findByAgeGreaterThan(Integer age);
Mono<User> findByEmail(String email);
@Query("SELECT * FROM users WHERE name LIKE :name")
Flux<User> searchByName(String name);
}
/**
* 用户实体(R2DBC)
*/
@Data
@Table("users")
public class User {
@Id
private Long id;
private String name;
private String email;
private Integer age;
}
/**
* 用户服务
*/
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Flux<User> findAll() {
return userRepository.findAll();
}
public Mono<User> findById(Long id) {
return userRepository.findById(id);
}
public Mono<User> save(User user) {
return userRepository.save(user);
}
public Flux<User> saveAll(Flux<User> users) {
return userRepository.saveAll(users);
}
public Mono<Void> deleteById(Long id) {
return userRepository.deleteById(id);
}
public Mono<User> update(Long id, User user) {
return userRepository.findById(id)
.flatMap(existing -> {
existing.setName(user.getName());
existing.setEmail(user.getEmail());
existing.setAge(user.getAge());
return userRepository.save(existing);
});
}
public Mono<UserStats> getStats() {
return userRepository.findAll()
.collect(Collectors.groupingBy(
u -> u.getAge() / 10 * 10, // 按年龄段分组
Collectors.counting()
))
.map(UserStats::new);
}
}
@Data
@AllArgsConstructor
class UserStats {
private Map<Integer, Long> ageDistribution;
}
4.2 响应式MongoDB
java
/**
* MongoDB响应式Repository
*/
public interface ArticleRepository extends ReactiveMongoRepository<Article, String> {
Flux<Article> findByAuthor(String author);
Flux<Article> findByTagsContaining(String tag);
@Query("{ 'title': { $regex: ?0, $options: 'i' } }")
Flux<Article> searchByTitle(String keyword);
}
/**
* 文章实体
*/
@Data
@Document(collection = "articles")
public class Article {
@Id
private String id;
private String title;
private String content;
private String author;
private List<String> tags;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
/**
* 文章服务
*/
@Service
public class ArticleService {
@Autowired
private ArticleRepository articleRepository;
@Autowired
private ReactiveMongoTemplate mongoTemplate;
public Flux<Article> findAll() {
return articleRepository.findAll();
}
public Mono<Article> findById(String id) {
return articleRepository.findById(id);
}
public Mono<Article> create(Article article) {
article.setCreatedAt(LocalDateTime.now());
article.setUpdatedAt(LocalDateTime.now());
return articleRepository.save(article);
}
/**
* 复杂查询
*/
public Flux<Article> search(ArticleSearchCriteria criteria) {
Query query = new Query();
if (criteria.getKeyword() != null) {
query.addCriteria(Criteria.where("title")
.regex(criteria.getKeyword(), "i"));
}
if (criteria.getAuthor() != null) {
query.addCriteria(Criteria.where("author").is(criteria.getAuthor()));
}
if (criteria.getTags() != null && !criteria.getTags().isEmpty()) {
query.addCriteria(Criteria.where("tags").all(criteria.getTags()));
}
return mongoTemplate.find(query, Article.class);
}
/**
* 聚合查询
*/
public Flux<AuthorStats> getAuthorStats() {
return mongoTemplate.aggregate(
Aggregation.newAggregation(
Aggregation.group("author")
.count().as("articleCount")
.first("author").as("author")
),
"articles",
AuthorStats.class
);
}
}
@Data
class ArticleSearchCriteria {
private String keyword;
private String author;
private List<String> tags;
}
@Data
class AuthorStats {
private String author;
private Long articleCount;
}
4.3 响应式Redis
java
/**
* Redis响应式配置
*/
@Configuration
public class RedisConfig {
@Bean
public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
ReactiveRedisConnectionFactory connectionFactory) {
StringRedisSerializer keySerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer valueSerializer =
new GenericJackson2JsonRedisSerializer();
RedisSerializationContext<String, Object> context =
RedisSerializationContext.<String, Object>newSerializationContext(keySerializer)
.value(valueSerializer)
.build();
return new ReactiveRedisTemplate<>(connectionFactory, context);
}
}
/**
* 缓存服务
*/
@Service
public class CacheService {
@Autowired
private ReactiveRedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存
*/
public Mono<Boolean> set(String key, Object value, Duration ttl) {
return redisTemplate.opsForValue().set(key, value, ttl);
}
/**
* 获取缓存
*/
public <T> Mono<T> get(String key, Class<T> type) {
return redisTemplate.opsForValue().get(key)
.cast(type);
}
/**
* 删除缓存
*/
public Mono<Boolean> delete(String key) {
return redisTemplate.delete(key)
.map(count -> count > 0);
}
/**
* 带缓存的数据访问
*/
public Mono<User> getUserWithCache(Long id) {
String cacheKey = "user:" + id;
return get(cacheKey, User.class)
.switchIfEmpty(
userRepository.findById(id)
.flatMap(user -> set(cacheKey, user, Duration.ofMinutes(30))
.thenReturn(user))
);
}
}
五、WebClient(响应式HTTP客户端)
5.1 基础使用
java
/**
* WebClient基础使用
*/
@Service
public class ExternalApiService {
private final WebClient webClient;
public ExternalApiService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl("https://api.example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
/**
* GET请求
*/
public Mono<User> getUser(Long id) {
return webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(User.class);
}
/**
* GET请求(返回列表)
*/
public Flux<User> getAllUsers() {
return webClient.get()
.uri("/users")
.retrieve()
.bodyToFlux(User.class);
}
/**
* POST请求
*/
public Mono<User> createUser(User user) {
return webClient.post()
.uri("/users")
.bodyValue(user)
.retrieve()
.bodyToMono(User.class);
}
/**
* PUT请求
*/
public Mono<User> updateUser(Long id, User user) {
return webClient.put()
.uri("/users/{id}", id)
.bodyValue(user)
.retrieve()
.bodyToMono(User.class);
}
/**
* DELETE请求
*/
public Mono<Void> deleteUser(Long id) {
return webClient.delete()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(Void.class);
}
}
5.2 高级配置
java
/**
* WebClient高级配置
*/
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
// 连接超时配置
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofSeconds(30))
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(30))
.addHandlerLast(new WriteTimeoutHandler(30)));
return WebClient.builder()
.baseUrl("https://api.example.com")
.clientConnector(new ReactorClientHttpConnector(httpClient))
// 默认请求头
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
// 过滤器
.filter(logRequest())
.filter(logResponse())
.filter(errorHandler())
.build();
}
/**
* 请求日志过滤器
*/
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
System.out.println("请求: " + clientRequest.method() + " " + clientRequest.url());
return Mono.just(clientRequest);
});
}
/**
* 响应日志过滤器
*/
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
System.out.println("响应状态: " + clientResponse.statusCode());
return Mono.just(clientResponse);
});
}
/**
* 错误处理过滤器
*/
private ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().isError()) {
return clientResponse.bodyToMono(String.class)
.flatMap(body -> Mono.error(
new WebClientException(clientResponse.statusCode(), body)
));
}
return Mono.just(clientResponse);
});
}
}
class WebClientException extends RuntimeException {
private final HttpStatusCode statusCode;
public WebClientException(HttpStatusCode statusCode, String message) {
super(message);
this.statusCode = statusCode;
}
}
5.3 并行请求
java
/**
* 并行请求示例
*/
@Service
public class AggregationService {
@Autowired
private WebClient webClient;
/**
* 并行调用多个API并聚合结果
*/
public Mono<AggregatedData> getAggregatedData(Long userId) {
Mono<User> userMono = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class);
Mono<List<Order>> ordersMono = webClient.get()
.uri("/users/{id}/orders", userId)
.retrieve()
.bodyToFlux(Order.class)
.collectList();
Mono<UserStats> statsMono = webClient.get()
.uri("/users/{id}/stats", userId)
.retrieve()
.bodyToMono(UserStats.class);
// 并行执行并合并结果
return Mono.zip(userMono, ordersMono, statsMono)
.map(tuple -> new AggregatedData(
tuple.getT1(),
tuple.getT2(),
tuple.getT3()
));
}
/**
* 批量请求
*/
public Flux<User> getUsersBatch(List<Long> ids) {
return Flux.fromIterable(ids)
.flatMap(id -> webClient.get()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(User.class)
.onErrorResume(e -> Mono.empty()),
10 // 最大并发数
);
}
}
@Data
@AllArgsConstructor
class AggregatedData {
private User user;
private List<Order> orders;
private UserStats stats;
}
@Data
class Order {
private Long id;
private String orderNo;
private BigDecimal amount;
}
六、实战案例
6.1 案例1:SSE(Server-Sent Events)
java
/**
* SSE实时推送
*/
@RestController
@RequestMapping("/api/events")
public class EventController {
@Autowired
private EventService eventService;
/**
* SSE事件流
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> ServerSentEvent.<String>builder()
.id(String.valueOf(sequence))
.event("message")
.data("事件 " + sequence + " - " + LocalDateTime.now())
.build());
}
/**
* 实时通知
*/
@GetMapping(value = "/notifications", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Notification> streamNotifications(
@RequestParam Long userId) {
return eventService.getNotifications(userId)
.delayElements(Duration.ofMillis(500));
}
/**
* 实时股票行情
*/
@GetMapping(value = "/stocks/{symbol}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<StockPrice> streamStockPrice(@PathVariable String symbol) {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> new StockPrice(
symbol,
100 + Math.random() * 10,
LocalDateTime.now()
));
}
}
@Data
@AllArgsConstructor
class Notification {
private String id;
private String message;
private LocalDateTime time;
}
@Data
@AllArgsConstructor
class StockPrice {
private String symbol;
private Double price;
private LocalDateTime timestamp;
}
6.2 案例2:WebSocket
java
/**
* WebSocket配置
*/
@Configuration
public class WebSocketConfig {
@Bean
public HandlerMapping webSocketHandlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>();
map.put("/ws/chat", new ChatWebSocketHandler());
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1);
return mapping;
}
@Bean
public WebSocketHandlerAdapter handlerAdapter() {
return new WebSocketHandlerAdapter();
}
}
/**
* WebSocket处理器
*/
@Slf4j
public class ChatWebSocketHandler implements WebSocketHandler {
private final Sinks.Many<String> sink = Sinks.many().multicast().onBackpressureBuffer();
@Override
public Mono<Void> handle(WebSocketSession session) {
// 接收消息
Mono<Void> receive = session.receive()
.map(WebSocketMessage::getPayloadAsText)
.doOnNext(message -> {
log.info("收到消息: {}", message);
sink.tryEmitNext(message);
})
.then();
// 发送消息
Mono<Void> send = session.send(
sink.asFlux()
.map(session::textMessage)
);
return Mono.zip(receive, send).then();
}
}
/**
* 聊天服务
*/
@Service
public class ChatService {
private final Map<String, Sinks.Many<ChatMessage>> rooms = new ConcurrentHashMap<>();
public Flux<ChatMessage> joinRoom(String roomId) {
return getRoom(roomId).asFlux();
}
public void sendMessage(String roomId, ChatMessage message) {
getRoom(roomId).tryEmitNext(message);
}
private Sinks.Many<ChatMessage> getRoom(String roomId) {
return rooms.computeIfAbsent(roomId, k ->
Sinks.many().multicast().onBackpressureBuffer()
);
}
}
@Data
@AllArgsConstructor
class ChatMessage {
private String from;
private String content;
private LocalDateTime timestamp;
}
6.3 案例3:限流与熔断
java
/**
* 限流配置
*/
@Service
public class RateLimitedService {
@Autowired
private ReactiveRedisTemplate<String, String> redisTemplate;
/**
* 令牌桶限流
*/
public Mono<Boolean> isAllowed(String key, int maxRequests, Duration window) {
String redisKey = "rate_limit:" + key;
return redisTemplate.opsForValue()
.increment(redisKey)
.flatMap(count -> {
if (count == 1) {
// 第一次请求,设置过期时间
return redisTemplate.expire(redisKey, window)
.thenReturn(true);
}
return Mono.just(count <= maxRequests);
});
}
/**
* 带限流的API调用
*/
public <T> Mono<T> rateLimited(String key, Mono<T> mono) {
return isAllowed(key, 100, Duration.ofMinutes(1))
.flatMap(allowed -> {
if (allowed) {
return mono;
}
return Mono.error(new RateLimitExceededException("请求过于频繁"));
});
}
}
/**
* 熔断器
*/
@Service
public class CircuitBreakerService {
private final io.github.resilience4j.circuitbreaker.CircuitBreaker circuitBreaker;
public CircuitBreakerService() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(3)
.slidingWindowSize(10)
.build();
this.circuitBreaker = CircuitBreakerRegistry.of(config)
.circuitBreaker("default");
}
/**
* 带熔断的调用
*/
public <T> Mono<T> withCircuitBreaker(Mono<T> mono) {
return mono.transformDeferred(
CircuitBreakerOperator.of(circuitBreaker)
);
}
}
class RateLimitExceededException extends RuntimeException {
public RateLimitExceededException(String message) {
super(message);
}
}
七、测试
7.1 单元测试
java
/**
* WebFlux单元测试
*/
@ExtendWith(SpringExtension.class)
@WebFluxTest(UserController.class)
class UserControllerTest {
@Autowired
private WebTestClient webTestClient;
@MockBean
private UserService userService;
@Test
void testGetUser() {
User user = new User(1L, "张三", "zhangsan@example.com", 25);
when(userService.findById(1L)).thenReturn(Mono.just(user));
webTestClient.get()
.uri("/api/users/1")
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.value(u -> {
assertEquals("张三", u.getName());
assertEquals(25, u.getAge());
});
}
@Test
void testGetAllUsers() {
List<User> users = Arrays.asList(
new User(1L, "张三", "zhangsan@example.com", 25),
new User(2L, "李四", "lisi@example.com", 30)
);
when(userService.findAll()).thenReturn(Flux.fromIterable(users));
webTestClient.get()
.uri("/api/users")
.exchange()
.expectStatus().isOk()
.expectBodyList(User.class)
.hasSize(2);
}
@Test
void testCreateUser() {
User user = new User(null, "王五", "wangwu@example.com", 28);
User savedUser = new User(3L, "王五", "wangwu@example.com", 28);
when(userService.save(any())).thenReturn(Mono.just(savedUser));
webTestClient.post()
.uri("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(user)
.exchange()
.expectStatus().isOk()
.expectBody(User.class)
.value(u -> assertEquals(3L, u.getId()));
}
}
7.2 StepVerifier测试
java
/**
* 使用StepVerifier测试响应式流
*/
class ReactiveStreamTest {
@Test
void testMono() {
Mono<String> mono = Mono.just("Hello");
StepVerifier.create(mono)
.expectNext("Hello")
.verifyComplete();
}
@Test
void testFlux() {
Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5);
StepVerifier.create(flux)
.expectNext(1, 2, 3, 4, 5)
.verifyComplete();
}
@Test
void testError() {
Flux<Integer> flux = Flux.just(1, 2, 3)
.concatWith(Mono.error(new RuntimeException("Error")));
StepVerifier.create(flux)
.expectNext(1, 2, 3)
.expectError(RuntimeException.class)
.verify();
}
@Test
void testWithVirtualTime() {
// 测试时间相关操作
StepVerifier.withVirtualTime(() ->
Flux.interval(Duration.ofSeconds(1)).take(3))
.expectSubscription()
.expectNoEvent(Duration.ofSeconds(1))
.expectNext(0L)
.thenAwait(Duration.ofSeconds(2))
.expectNext(1L, 2L)
.verifyComplete();
}
}
八、最佳实践
8.1 什么时候使用WebFlux
WebFlux适用场景
┌─────────────────────────────────────────┐
│ ✓ 适合使用WebFlux │
│ - 高并发IO密集型应用 │
│ - 流式数据处理 │
│ - 微服务网关 │
│ - 实时数据推送(SSE/WebSocket) │
│ - 需要响应式数据库驱动 │
├─────────────────────────────────────────┤
│ ✗ 不适合使用WebFlux │
│ - CPU密集型应用 │
│ - 使用阻塞式JDBC │
│ - 团队不熟悉响应式编程 │
│ - 现有项目迁移成本高 │
└─────────────────────────────────────────┘
8.2 避免常见错误
java
/**
* WebFlux常见错误及解决方案
*/
public class CommonMistakes {
// ❌ 错误:阻塞调用
public Mono<User> badExample(Long id) {
User user = userRepository.findByIdBlocking(id); // 阻塞!
return Mono.just(user);
}
// ✓ 正确:使用响应式API
public Mono<User> goodExample(Long id) {
return userRepository.findById(id);
}
// ❌ 错误:忘记订阅
public void forgotSubscribe() {
userRepository.save(user); // 没有订阅,不会执行!
}
// ✓ 正确:订阅或返回
public Mono<User> correctSubscribe() {
return userRepository.save(user); // 由调用者订阅
}
// ❌ 错误:在map中调用异步操作
public Flux<User> badAsyncInMap() {
return userIds.map(id -> userRepository.findById(id)); // 返回Mono<Mono<User>>
}
// ✓ 正确:使用flatMap
public Flux<User> goodAsyncInFlatMap() {
return userIds.flatMap(id -> userRepository.findById(id));
}
}
九、总结
核心知识点回顾
Spring WebFlux核心要点
│
├── 核心概念
│ ├── 响应式编程
│ ├── Reactive Streams规范
│ ├── Mono(0或1个元素)
│ └── Flux(0到N个元素)
│
├── 编程模型
│ ├── 注解式Controller
│ └── 函数式Endpoints
│
├── 操作符
│ ├── 创建操作
│ ├── 转换操作
│ ├── 组合操作
│ ├── 错误处理
│ └── 背压处理
│
├── 数据访问
│ ├── R2DBC(响应式SQL)
│ ├── Reactive MongoDB
│ └── Reactive Redis
│
├── HTTP客户端
│ └── WebClient
│
└── 高级特性
├── SSE
├── WebSocket
└── 限流熔断
Spring WebFlux为高并发场景提供了强大的响应式编程支持。正确使用WebFlux可以显著提升应用的吞吐量和资源利用率。但要注意,响应式编程有一定的学习曲线,需要根据实际场景选择是否使用。