Spring WebFlux详解

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可以显著提升应用的吞吐量和资源利用率。但要注意,响应式编程有一定的学习曲线,需要根据实际场景选择是否使用。


相关推荐
okseekw1 小时前
Java泛型从入门到实战:原理、用法与案例深度解析
java·后端
若水不如远方1 小时前
告别 RestHighLevelClient:Elasticsearch Java 新客户端实战与源码浅析
java·elasticsearch
文攀1 小时前
Go 语言 GMP 调度模型深度解析
后端·go·编程语言
银嘟嘟左卫门1 小时前
使用openEuler进行多核性能测评,从单核到多核的极致性能探索
后端
萝卜青今天也要开心1 小时前
2025年下半年系统架构设计师考后分享
java·数据库·redis·笔记·学习·系统架构
Unstoppable221 小时前
八股训练营第 39 天 | Bean 的作用域?Bean 的生命周期?Spring 循环依赖是怎么解决的?Spring 中用到了那些设计模式?
java·spring·设计模式
程序员根根1 小时前
JavaSE 进阶:多线程核心知识点(线程创建 vs 线程安全 + 线程池优化 + 实战案例
java
阿伟*rui1 小时前
互联网大厂Java面试:音视频场景技术攻防与系统设计深度解析
java·redis·websocket·面试·音视频·高并发·后端架构
Java天梯之路1 小时前
Spring AOP:面向切面编程的优雅解耦之道
java·spring·面试