Spring响应式编程从0到1实战教程

4 阅读3分钟

🌊 Spring响应式编程从0到1实战教程

[分类:编程语言/Java]

完整代码驱动式教学 —— Mono/Flux、WebFlux、R2DBC、测试与最佳实践

📖 目录

📦 第1章:环境准备

1.1 开发环境要求

  • JDK 11+ (推荐 JDK 17)
  • Maven 3.6+ / Gradle 6+
  • IntelliJ IDEA / VSCode
  • Docker (可选, 用于数据库)

1.2 Maven依赖 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
    </parent>
    <dependencies>
        <!-- WebFlux 响应式Web -->
        <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>
        <!-- PostgreSQL驱动 -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>r2dbc-postgresql</artifactId>
        </dependency>
        <!-- Lombok -->
        <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>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

⚡ 第2章:Reactor核心概念实战

2.1 Mono 和 Flux 基础

// Mono示例
Mono<String> mono = Mono.just("Hello Reactive");
mono.subscribe(
    data -> System.out.println("收到: " + data),
    error -> System.err.println("错误: " + error),
    () -> System.out.println("完成")
);

// Flux示例
Flux<String> flux = Flux.just("A", "B", "C");
flux.subscribe(System.out::println);

// StepVerifier 测试
StepVerifier.create(Flux.range(1, 3))
    .expectNext(1, 2, 3)
    .verifyComplete();

2.2 常用操作符

// map 转换
Flux.range(1, 5).map(i -> i * 2);        // 2,4,6,8,10

// filter 过滤
Flux.range(1, 5).filter(i -> i % 2 == 0); // 2,4

// flatMap 异步扁平化
Flux.just("hello", "world")
    .flatMap(word -> Flux.fromArray(word.split(""))); 

// zip 合并
Flux.zip(Flux.just("A","B"), Flux.just(1,2))
    .map(t -> t.getT1() + t.getT2());     // [A1, B2]

🏗️ 第3章:构建响应式REST API

3.1 实体类 User

@Data
@NoArgsConstructor
@AllArgsConstructor
@Table("users")
public class User {
    @Id private Long id;
    private String username;
    private String email;
    private Integer age;
    private LocalDateTime createdAt;
}

3.2 响应式Repository

public interface UserRepository extends ReactiveCrudRepository<User, Long> {
    Mono<User> findByUsername(String username);
    Flux<User> findByAgeBetween(Integer min, Integer max);
    @Query("SELECT * FROM users WHERE email LIKE $1")
    Flux<User> findByEmailDomain(String domain);
}

3.3 服务层 UserService

@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
    private final UserRepository userRepository;

    public Mono<User> createUser(User user) {
        user.setCreatedAt(LocalDateTime.now());
        return userRepository.save(user)
            .doOnSuccess(u -> log.info("创建用户: {}", u));
    }

    public Mono<User> getUserById(Long id) {
        return userRepository.findById(id)
            .switchIfEmpty(Mono.error(new RuntimeException("用户不存在")));
    }

    public Flux<User> getAllUsers() {
        return userRepository.findAll();
    }

    // 更新、删除等略...
}

3.4 控制器 UserController

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<User> createUser(@RequestBody User user) {
        return userService.createUser(user);
    }

    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> getUserById(@PathVariable Long id) {
        return userService.getUserById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }

    @GetMapping
    public Flux<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamUsers() {
        return userService.getAllUsers().delayElements(Duration.ofSeconds(1));
    }
}

🔄 第4章:函数式路由

@Configuration
public class RouterConfig {
    @Bean
    public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {
        return RouterFunctions.route()
            .path("/api/users", builder -> builder
                .GET("/", handler::getAllUsers)
                .GET("/{id}", handler::getUserById)
                .POST("/", handler::createUser)
                .PUT("/{id}", handler::updateUser)
                .DELETE("/{id}", handler::deleteUser)
            )
            .build();
    }
}

@Component
@RequiredArgsConstructor
public class UserHandler {
    private final UserService userService;

    public Mono<ServerResponse> getAllUsers(ServerRequest request) {
        return ServerResponse.ok().body(userService.getAllUsers(), User.class);
    }

    public Mono<ServerResponse> getUserById(ServerRequest request) {
        return userService.getUserById(Long.valueOf(request.pathVariable("id")))
            .flatMap(user -> ServerResponse.ok().bodyValue(user))
            .switchIfEmpty(ServerResponse.notFound().build());
    }

    // 其他方法类似...
}

🗄️ 第5章:配置与数据库

application.yml

spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/reactive_db
    username: postgres
    password: password
    pool:
      initial-size: 5
      max-size: 20
  webflux:
    base-path: /api
server:
  port: 8080
  netty:
    connection-timeout: 2s

schema.sql

CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL,
    age INTEGER,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

⚠️ 第6章:错误处理与重试

全局异常处理

@Configuration
public class ExceptionConfig {
    @Bean
    @Order(-2)
    public ErrorResponseExceptionHandler errorResponseExceptionHandler(
            ErrorAttributes errorAttributes, ServerCodecConfigurer codecConfigurer) {
        return new ErrorResponseExceptionHandler(errorAttributes, codecConfigurer);
    }
}

// 自定义错误属性
static class ErrorResponseExceptionHandler extends AbstractErrorWebExceptionHandler {
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);
        // 增强字段
        errorAttributes.put("timestamp", System.currentTimeMillis());
        return errorAttributes;
    }
}

重试机制 (Resilience4j + Reactor)

// Reactor内置重试
Mono.fromCallable(this::unstableCall)
    .retryWhen(RetrySpec.fixedDelay(3, Duration.ofSeconds(1))
        .filter(throwable -> throwable instanceof RuntimeException));

// Resilience4j重试
RetryConfig config = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofSeconds(1))
    .build();
Retry retry = RetryRegistry.of(config).retry("userService");

Mono.fromCallable(this::unstableCall)
    .transformDeferred(RetryOperator.of(retry));

🧪 第7章:测试

7.1 控制器单元测试 (WebTestClient)

@ExtendWith(SpringExtension.class)
@WebFluxTest(UserController.class)
class UserControllerTest {
    @Autowired WebTestClient webTestClient;
    @MockBean UserService userService;

    @Test
    void testGetUserById() {
        User user = new User(1L, "test", "test@ex.com", 25, null);
        when(userService.getUserById(1L)).thenReturn(Mono.just(user));

        webTestClient.get().uri("/api/users/1")
            .exchange()
            .expectStatus().isOk()
            .expectBody(User.class).isEqualTo(user);
    }
}

7.2 集成测试

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
    @Autowired WebTestClient webTestClient;
    @Autowired R2dbcEntityTemplate entityTemplate;

    @BeforeEach void clean() {
        entityTemplate.delete(User.class).all().block();
    }

    @Test
    void testCreateAndFind() {
        User newUser = new User(null, "integ", "i@test.com", 30, null);
        webTestClient.post().uri("/api/users").bodyValue(newUser)
            .exchange().expectStatus().isCreated()
            .expectBody().jsonPath("$.id").exists();
    }
}

📊 第8章:性能监控

Actuator + Prometheus

<!-- 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
# application.yml 配置
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true

自定义指标注解

@Aspect
@Component
public class MetricsAspect {
    @Around("@annotation(timed)")
    public Object measure(ProceedingJoinPoint pjp, Timed timed) throws Throwable {
        Timer.Sample sample = Timer.start(meterRegistry);
        String method = pjp.getSignature().getName();
        Object result = pjp.proceed();

        if (result instanceof Mono) {
            return ((Mono<?>) result).doOnSuccessOrError((v, t) -> 
                sample.stop(Timer.builder(timed.value())
                    .tag("method", method)
                    .tag("status", t == null ? "success" : "error")
                    .register(meterRegistry)));
        }
        // Flux类似...
        return result;
    }
}

🚀 第9章:生产环境最佳实践

熔断器配置 (Resilience4j)

@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
    CircuitBreakerConfig config = CircuitBreakerConfig.custom()
        .failureRateThreshold(50)
        .slowCallRateThreshold(50)
        .slowCallDurationThreshold(Duration.ofSeconds(2))
        .permittedNumberOfCallsInHalfOpenState(10)
        .slidingWindowSize(100)
        .minimumNumberOfCalls(10)
        .waitDurationInOpenState(Duration.ofSeconds(30))
        .build();
    return CircuitBreakerRegistry.of(config);
}

public <T> Mono<T> withCircuitBreaker(Mono<T> publisher, String name) {
    return publisher.transformDeferred(
        CircuitBreakerOperator.of(circuitBreakerRegistry().circuitBreaker(name))
    );
}

连接池调优

spring:
  r2dbc:
    pool:
      initial-size: 10
      max-size: 50
      max-idle-time: 30m
      max-life-time: 1h
      validation-query: SELECT 1
  webflux:
    max-in-memory-size: 16MB
server:
  netty:
    max-connections: 10000
    worker-threads: 4

📁 第10章:完整项目结构

reactive-demo/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/com/example/reactivedemo/
│   │   │   ├── ReactiveDemoApplication.java
│   │   │   ├── config/
│   │   │   ├── controller/
│   │   │   ├── handler/
│   │   │   ├── model/
│   │   │   ├── repository/
│   │   │   ├── service/
│   │   │   ├── exception/
│   │   │   └── metrics/
│   │   └── resources/
│   │       ├── application.yml
│   │       └── schema.sql
│   └── test/
│       └── java/...

▶️ 启动应用 & 测试API

@SpringBootApplication
@EnableR2dbcRepositories
public class ReactiveDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReactiveDemoApplication.class, args);
    }
}

🌐 测试命令

# 创建用户
curl -X POST http://localhost:8080/api/users \
  -H "Content-Type: application/json" \
  -d '{"username":"john","email":"john@ex.com","age":25}'

# 查询所有用户
curl http://localhost:8080/api/users

# 流式响应 (SSE)
curl http://localhost:8080/api/users/stream

# 更新
curl -X PUT http://localhost:8080/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"username":"john_new","email":"j@ex.com","age":26}'

# 删除
curl -X DELETE http://localhost:8080/api/users/1

📘 说明:确保 PostgreSQL 已启动并创建了 reactive_db 数据库,用户名密码与配置一致。

总结

本教程从环境准备开始,逐步介绍了Spring响应式编程的核心概念和实战应用,包括:

  • Reactor核心库的Mono和Flux使用
  • 响应式REST API的构建
  • 函数式路由的实现
  • R2DBC响应式数据库操作
  • 错误处理与重试机制
  • 测试策略与实践
  • 性能监控与生产环境最佳实践

通过完整的代码示例和项目结构,帮助开发者快速上手Spring响应式编程,构建高性能、可伸缩的响应式应用。

💰 为什么选择 32ai?

低至 0.56 : 1 比率 🔗 快速访问: 点击访问 — 直连、无需魔法。

标签:Spring、响应式编程、WebFlux、R2DBC、Java

欢迎在评论区交流讨论!

原创声明:本文为原创教程,转载请注明出处