Spring WebFlux
Содержание
- Введение
- Основные концепции
- Настройка проекта
- Reactive Programming
- WebFlux API
- Функциональные эндпоинты
- WebClient
- Тестирование
- Примеры использования
Введение
Spring WebFlux - это реактивный веб-фреймворк, построенный на основе проекта Reactor. Он предоставляет неблокирующий, событийно-ориентированный подход к обработке веб-запросов.
Ключевые особенности:
- Неблокирующая обработка запросов
- Поддержка реактивных потоков
- Функциональное программирование
- Высокая масштабируемость
- Работа с backpressure
Основные концепции
Реактивные типы
Mono<T> - контейнер для 0 или 1 элемента
Flux<T> - контейнер для 0..N элементов
Модели программирования
- Аннотационная (похожа на Spring MVC)
- Функциональная (использует функциональные эндпоинты)
Настройка проекта
Зависимости Maven
```
<!-- Опционально для работы с реактивными БД -->
<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());
}
Советы и лучшие практики
- Используйте подходящие операторы Reactor
- Избегайте блокирующих операций
- Правильно обрабатывайте ошибки
- Используйте таймауты
- Тестируйте реактивный код
Производительность и оптимизация
- Настройка количества рабочих потоков
spring:
webflux:
base-path: /api
reactor:
netty:
worker-count: 4
- Использование кэширования
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();