Skip to content

Spring WebFlux

Содержание

  1. Введение
  2. Основные концепции
  3. Настройка проекта
  4. Reactive Programming
  5. WebFlux API
  6. Функциональные эндпоинты
  7. WebClient
  8. Тестирование
  9. Примеры использования

Введение

Spring WebFlux - это реактивный веб-фреймворк, построенный на основе проекта Reactor. Он предоставляет неблокирующий, событийно-ориентированный подход к обработке веб-запросов.

Ключевые особенности:

  • Неблокирующая обработка запросов
  • Поддержка реактивных потоков
  • Функциональное программирование
  • Высокая масштабируемость
  • Работа с backpressure

Основные концепции

Реактивные типы

Mono<T> - контейнер для 0 или 1 элемента
Flux<T> - контейнер для 0..N элементов

Модели программирования

  1. Аннотационная (похожа на Spring MVC)
  2. Функциональная (использует функциональные эндпоинты)

Настройка проекта

Зависимости Maven

``` org.springframework.boot spring-boot-starter-webflux

<!-- Опционально для работы с реактивными БД -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>


## Reactive Programming

### Операторы Reactor
```java
// Примеры основных операторов
Flux.just(1, 2, 3, 4)
    .map(n -> n * 2)
    .filter(n -> n > 5)
    .subscribe(System.out::println);

Mono.just("Hello")
    .flatMap(s -> Mono.just(s + " World"))
    .subscribe(System.out::println);

Обработка ошибок

Flux.just(1, 2, 0)
    .map(i -> 100 / i)
    .onErrorReturn(0)
    .subscribe(System.out::println);

WebFlux API

Контроллер с аннотациями

@RestController
@RequestMapping("/api")
public class UserController {

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

    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userService.findById(id);
    }

    @PostMapping("/users")
    public Mono<User> createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

Реактивный репозиторий

public interface UserRepository extends ReactiveCrudRepository<User, String> {
    Flux<User> findByLastName(String lastName);
    Mono<User> findByEmail(String email);
}

Функциональные эндпоинты

Определение роутов

@Configuration
public class RouterConfig {

    @Bean
    public RouterFunction<ServerResponse> route(UserHandler userHandler) {
        return RouterFunctions
            .route(GET("/api/users").and(accept(APPLICATION_JSON)), userHandler::getAllUsers)
            .andRoute(GET("/api/users/{id}").and(accept(APPLICATION_JSON)), userHandler::getUser)
            .andRoute(POST("/api/users").and(accept(APPLICATION_JSON)), userHandler::createUser);
    }
}

Обработчик

@Component
public class UserHandler {

    private final UserService userService;

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

    public Mono<ServerResponse> getUser(ServerRequest request) {
        String userId = request.pathVariable("id");
        return userService.findById(userId)
            .flatMap(user -> ServerResponse.ok().bodyValue(user))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}

WebClient

Использование WebClient

WebClient webClient = WebClient.create("http://api.example.com");

Mono<User> user = webClient.get()
    .uri("/users/{id}", userId)
    .retrieve()
    .bodyToMono(User.class);

Flux<User> users = webClient.get()
    .uri("/users")
    .retrieve()
    .bodyToFlux(User.class);

Конфигурация WebClient

WebClient webClient = WebClient.builder()
    .baseUrl("http://api.example.com")
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .filter(ExchangeFilterFunction.ofRequestProcessor(
        clientRequest -> {
            log.debug("Request: {} {}", clientRequest.method(), clientRequest.url());
            return Mono.just(clientRequest);
        }
    ))
    .build();

Тестирование

Тестирование контроллеров

@WebFluxTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private UserService userService;

    @Test
    public void testGetUser() {
        Mono<User> userMono = Mono.just(new User("1", "John Doe"));
        when(userService.findById("1")).thenReturn(userMono);

        webTestClient.get().uri("/api/users/{id}", "1")
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.name").isEqualTo("John Doe");
    }
}

Примеры использования

Реактивный сервис

@Service
public class UserService {
    private final UserRepository userRepository;

    public Flux<User> findAll() {
        return userRepository.findAll()
            .delayElements(Duration.ofMillis(100)) // Имитация задержки
            .log();
    }

    public Mono<User> findById(String id) {
        return userRepository.findById(id)
            .switchIfEmpty(Mono.error(new UserNotFoundException(id)));
    }

    public Flux<User> findByLastName(String lastName) {
        return userRepository.findByLastName(lastName)
            .switchIfEmpty(Flux.error(new UsersNotFoundException(lastName)));
    }
}

Server-Sent Events (SSE)

@GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<User>> streamUsers() {
    return userService.findAll()
        .map(user -> ServerSentEvent.<User>builder()
            .data(user)
            .event("user-update")
            .build());
}

Советы и лучшие практики

  1. Используйте подходящие операторы Reactor
  2. Избегайте блокирующих операций
  3. Правильно обрабатывайте ошибки
  4. Используйте таймауты
  5. Тестируйте реактивный код

Производительность и оптимизация

  1. Настройка количества рабочих потоков
spring:
  webflux:
    base-path: /api
  reactor:
    netty:
      worker-count: 4
  1. Использование кэширования
Flux<User> cachedUsers = userService.findAll().cache();

Отладка и мониторинг

Логирование

Flux<User> users = userService.findAll()
    .log("com.example.user.flux", Level.INFO);

Метрики

Flux<User> users = userService.findAll()
    .name("user.fetch")
    .metrics()
    .log();