[Java] Реактивное программирование со Spring, часть 3 WebFlux (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Это третья часть серии блогов о реактивном программировании, в которой я познакомлю вас с WebFlux - реактивным веб-фреймворком Spring. 1. ВВЕДЕНИЕ В SPRING WEBFLUXИсходный веб-фреймворк для Spring - Spring Web MVC - был построен для Servlet API и контейнеров Servlet.WebFlux был представлен как часть Spring Framework 5.0. В отличие от Spring MVC, он не требует Servlet API. Он полностью асинхронный и неблокирующий, реализует спецификацию Reactive Streams через проект Reactor (см. предыдущий пост в блоге ).WebFlux требует Reactor в качестве основной зависимости, но он также может взаимодействовать с другими реактивными библиотеками через Reactive Streams.1.1 МОДЕЛИ ПРОГРАММИРОВАНИЯSpring WebFlux поддерживает две разные модели программирования: на основе аннотаций и функциональную.1.1.1 АННОТИРОВАННЫЕ КОНТРОЛЛЕРЫЕсли вы работали со Spring MVC, модель на основе аннотаций будет выглядеть довольно знакомой, поскольку в ней используются те же аннотации из веб-модуля Spring, что и в Spring MVC. Основное отличие состоит в том, что теперь методы возвращают реактивные типы Mono и Flux. См. Следующий пример RestController с использованием модели на основе аннотаций:
@RestController
@RequestMapping("/students")
public class StudentController {
@Autowired
private StudentService studentService;
public StudentController() {
}
@GetMapping("/{id}")
public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) {
return studentService.findStudentById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@GetMapping
public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) {
return studentService.findStudentsByName(name);
}
@PostMapping
public Mono<Student> addNewStudent(@RequestBody Student student) {
return studentService.addNewStudent(student);
}
@PutMapping("/{id}")
public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) {
return studentService.updateStudent(id, student)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@DeleteMapping("/{id}")
public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {
return studentService.findStudentById(id)
.flatMap(s ->
studentService.deleteStudent(s)
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
)
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
}
Некоторые пояснения к функциям, использованным в примере:
- map функция используется для преобразования элемента, испускаемого Mono, применяя функцию синхронной к нему.
- flatMap функция используется для преобразования элемент, испускаемый Mono асинхронно, возвращая значение, излучаемого другим Mono.
- defaultIfEmpty функция обеспечивает значение по умолчанию, если Mono завершается без каких - либо данных.
1.1.2 ФУНКЦИОНАЛЬНЫЕ КОНЕЧНЫЕ ТОЧКИМодель функционального программирования основана на лямбда-выражении и оставляет за приложением полную обработку запроса. Он основан на концепциях HandlerFunctions и RouterFunctions.HandlerFunctions используются для генерации ответа на данный запрос:
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
Mono<T> handle(ServerRequest request);
}
RouterFunction используется для маршрутизации запросов к HandlerFunctions:
@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
Mono<HandlerFunction<T>> route(ServerRequest request);
...
}
Продолжая с тем же примером ученика, мы получим что-то вроде следующего, используя функциональный стиль. A StudentRouter:
@Configuration
public class StudentRouter {
@Bean
public RouterFunction<ServerResponse> route(StudentHandler studentHandler){
return RouterFunctions
.route(
GET("/students/{id:[0-9]+}")
.and(accept(APPLICATION_JSON)), studentHandler::getStudent)
.andRoute(
GET("/students")
.and(accept(APPLICATION_JSON)), studentHandler::listStudents)
.andRoute(
POST("/students")
.and(accept(APPLICATION_JSON)),studentHandler::addNewStudent)
.andRoute(
PUT("students/{id:[0-9]+}")
.and(accept(APPLICATION_JSON)), studentHandler::updateStudent)
.andRoute(
DELETE("/students/{id:[0-9]+}")
.and(accept(APPLICATION_JSON)), studentHandler::deleteStudent);
}
}
И StudentHandler:
@Component
public class StudentHandler {
private StudentService studentService;
public StudentHandler(StudentService studentService) {
this.studentService = studentService;
}
public Mono<ServerResponse> getStudent(ServerRequest serverRequest) {
Mono<Student> studentMono = studentService.findStudentById(
Long.parseLong(serverRequest.pathVariable("id")));
return studentMono.flatMap(student -> ServerResponse.ok()
.body(fromValue(student)))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> listStudents(ServerRequest serverRequest) {
String name = serverRequest.queryParam("name").orElse(null);
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(studentService.findStudentsByName(name), Student.class);
}
public Mono<ServerResponse> addNewStudent(ServerRequest serverRequest) {
Mono<Student> studentMono = serverRequest.bodyToMono(Student.class);
return studentMono.flatMap(student ->
ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(studentService.addNewStudent(student), Student.class));
}
public Mono<ServerResponse> updateStudent(ServerRequest serverRequest) {
final long studentId = Long.parseLong(serverRequest.pathVariable("id"));
Mono<Student> studentMono = serverRequest.bodyToMono(Student.class);
return studentMono.flatMap(student ->
ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(studentService.updateStudent(studentId, student), Student.class));
}
public Mono<ServerResponse> deleteStudent(ServerRequest serverRequest) {
final long studentId = Long.parseLong(serverRequest.pathVariable("id"));
return studentService
.findStudentById(studentId)
.flatMap(s -> ServerResponse.noContent().build(studentService.deleteStudent(s)))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
Некоторые пояснения к функциям, использованным в примере:
- switchIfEmpty функция имеет ту же цель, defaultIfEmpty, но вместо того, чтобы обеспечить значение по умолчанию, она используется для обеспечения альтернативного Mono.
Сравнивая две модели, мы видим, что:
- Для использования функционального варианта требуется еще немного кода для таких вещей, как получение входных параметров и синтаксический анализ до ожидаемого типа.
- Не полагаясь на аннотации, но написание явного кода предлагает некоторую большую гибкость и может быть лучшим выбором, если нам, например, нужно реализовать более сложную маршрутизацию.
1.2 ПОДДЕРЖКА СЕРВЕРАWebFlux работает в средах выполнения, отличных от сервлетов, таких как Netty и Undertow (неблокирующий режим), а также в средах выполнения сервлетов 3.1+, таких как Tomcat и Jetty.По умолчанию стартер Spring Boot WebFlux использует Netty, но его легко переключить, изменив зависимости Maven или Gradle.Например, чтобы переключиться на Tomcat, просто исключите spring-boot-starter-netty из зависимости spring-boot-starter-webflux и добавьте spring-boot-starter-tomcat:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-netty</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
1.3 КОНФИГУРАЦИЯSpring Boot обеспечивает автоматическую настройку Spring WebFlux, которая хорошо работает в общих случаях. Если вам нужен полный контроль над конфигурацией WebFlux, можно использовать аннотацию @EnableWebFlux (эта аннотация также потребуется в простом приложении Spring для импорта конфигурации Spring WebFlux).Если вы хотите сохранить конфигурацию Spring Boot WebFlux и просто добавить дополнительную конфигурацию WebFlux, вы можете добавить свой собственный класс @Configuration типа WebFluxConfigurer (но без @EnableWebFlux).Подробные сведения и примеры см. в документации по конфигурации WebFlux.2. ЗАЩИТА ВАШИХ КОНЕЧНЫХ ТОЧЕКЧтобы получить поддержку Spring Security WebFlux, сначала добавьте в свой проект зависимость spring-boot-starter-security. Теперь вы можете включить его, добавив @EnableWebFluxSecurity аннотацию в свой класс Configuration (доступно с Spring Security 5.0).В следующем упрощенном примере будет добавлена поддержка двух пользователей, один с ролью USER, а другой с ролью ADMIN, принудительно применить базовую аутентификацию HTTP и потребовать роль ADMIN для любого доступа к пути /student/admin:
@EnableWebFluxSecurity
public class SecurityConfig {
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User
.withUsername("user")
.password(passwordEncoder().encode("userpwd"))
.roles("USER")
.build();
UserDetails admin = User
.withUsername("admin")
.password(passwordEncoder().encode("adminpwd"))
.roles("ADMIN")
.build();
return new MapReactiveUserDetailsService(user, admin);
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/students/admin")
.hasAuthority("ROLE_ADMIN")
.anyExchange()
.authenticated()
.and().httpBasic()
.and().build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Также можно защитить метод, а не путь, сначала добавив аннотацию @EnableReactiveMethodSecurity к вашей конфигурации:
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
...
}
А затем добавляем @PreAuthorize аннотацию к защищаемым методам. Например, мы можем захотеть, чтобы наши методы POST, PUT и DELETE были доступны только для роли ADMIN. Затем к этим методам можно применить аннотацию PreAuthorize, например:
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) {
...
}
Spring Security предлагает дополнительную поддержку, связанную с приложениями WebFlux, например защиту CSRF, интеграцию OAuth2 и реактивную аутентификацию X.509. Для получения дополнительной информации прочтите следующий раздел в документации Spring Security: Реактивные приложения3. ВЕБ-КЛИЕНТSpring WebFlux также включает реактивный, полностью неблокирующий веб-клиент. У него есть функциональный, свободный API, основанный на Reactor.Давайте рассмотрим (еще раз) упрощенный пример того, как WebClient можно использовать для запроса нашего StudentController:
public class StudentWebClient {
WebClient client = WebClient.create("http://localhost:8080");
public Mono<Student> get(long id) {
return client
.get()
.uri("/students/" + id)
.headers(headers -> headers.setBasicAuth("user", "userpwd"))
.retrieve()
.bodyToMono(Student.class);
}
public Flux<Student> getAll() {
return client.get()
.uri("/students")
.headers(headers -> headers.setBasicAuth("user", "userpwd"))
.retrieve()
.bodyToFlux(Student.class);
}
public Flux<Student> findByName(String name) {
return client.get()
.uri(uriBuilder -> uriBuilder.path("/students")
.queryParam("name", name)
.build())
.headers(headers -> headers.setBasicAuth("user", "userpwd"))
.retrieve()
.bodyToFlux(Student.class);
}
public Mono<Student> create(Student s) {
return client.post()
.uri("/students")
.headers(headers -> headers.setBasicAuth("admin", "adminpwd"))
.body(Mono.just(s), Student.class)
.retrieve()
.bodyToMono(Student.class);
}
public Mono<Student> update(Student student) {
return client
.put()
.uri("/students/" + student.getId())
.headers(headers -> headers.setBasicAuth("admin", "adminpwd"))
.body(Mono.just(student), Student.class)
.retrieve()
.bodyToMono(Student.class);
}
public Mono<Void> delete(long id) {
return client
.delete()
.uri("/students/" + id)
.headers(headers -> headers.setBasicAuth("admin", "adminpwd"))
.retrieve()
.bodyToMono(Void.class);
}
}
4. ТЕСТИРОВАНИЕДля тестирования вашего реактивного веб-приложения WebFlux предлагает WebTestClient, который поставляется с API, аналогичным WebClient.Давайте посмотрим, как мы можем протестировать наш StudentController с помощью WebTestClient:
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class StudentControllerTest {
@Autowired
WebTestClient webClient;
@Test
@WithMockUser(roles = "USER")
void test_getStudents() {
webClient.get().uri("/students")
.header(HttpHeaders.ACCEPT, "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBodyList(Student.class);
}
@Test
@WithMockUser(roles = "ADMIN")
void testAddNewStudent() {
Student newStudent = new Student();
newStudent.setName("some name");
newStudent.setAddress("an address");
webClient.post().uri("/students")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(newStudent), Student.class)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON)
.expectBody()
.jsonPath("$.id").isNotEmpty()
.jsonPath("$.name").isEqualTo(newStudent.getName())
.jsonPath("$.address").isEqualTo(newStudent.getAddress());
}
...
}
5. WEBSOCKETS И RSOCKET5.1 ВЕБ-СОКЕТЫВ Spring 5 WebSockets также получает дополнительные реактивные возможности. Чтобы создать сервер WebSocket, вы можете создать реализацию WebSocketHandler интерфейса, которая содержит следующий метод:
Mono<Void> handle(WebSocketSession session)
Этот метод вызывается при установке нового соединения WebSocket и позволяет обрабатывать сеанс. Он принимает в WebSocketSession качестве входных данных и возвращает Mono <Void>, чтобы сигнализировать о завершении обработки сеанса приложением.WebSocketSession имеет методы, определенные для обработки входящих и исходящих потоков:
Flux<WebSocketMessage> receive()
Mono<Void> send(Publisher<WebSocketMessage> messages)
Spring WebFlux также предоставляет WebSocketClient реализации для Reactor Netty, Tomcat, Jetty, Undertow и стандартной Java.Для получения дополнительной информации прочтите следующую главу в документации Spring's Web on Reactive Stack: WebSockets5.2 RSOCKETRSocket - это протокол, моделирующий семантику реактивных потоков по сети. Это двоичный протокол для использования в транспортных потоках байтовых потоков, таких как TCP, WebSockets и Aeron. В качестве введения в эту тему я рекомендую следующий пост в блоге, который написал мой коллега Pär: An introduction to RSocketА для получения дополнительной информации о поддержке Spring Framework протокола RSocket6. ПОДВОДЯ ИТОГ…Это сообщение в блоге продемонстрировало, как WebFlux можно использовать для создания реактивного веб-приложения. В следующем и последнем посте этой серии будет показано, как мы можем сделать весь наш стек приложений полностью неблокирующим, также реализовав неблокирующую связь с базой данных - с помощью R2DBC (Reactive Relational Database Connectivity)!ССЫЛКИSpring Framework documentation - Web on Reactive StackSpring Boot Features - The Spring WebFlux frameworkSpring Security - Reactive Applications
===========
Источник:
habr.com
===========
===========
Автор оригинала: ANNA ERIKSSON
===========Похожие новости:
- [Java] Реактивное программирование со Spring, часть 2 Project Reactor (перевод)
- [Разработка веб-сайтов, JavaScript, HTML, Angular, ReactJS] Карго-культ HTML в современном фронтенде
- [Java] Реактивное программирование со Spring, часть 1 Введение (перевод)
- [C++, Подготовка технической документации] Документирование кодовой базы. Зачем и как?
- [JavaScript, ReactJS] Как начать работу с React Native, улучшить навигацию и перейти на новую библиотеку компонентов
- [JavaScript, LaTeX, Браузеры, Учебный процесс в IT] Парсинг Markdown и LaTeX в Grazie Chrome Plugin
- [Разработка веб-сайтов, JavaScript, Программирование, Управление разработкой] Круглый стол в Wrike: как перевести фронтенд на новый стек
- [Программирование, Java] Spring boot: маленькое приложение для самых маленьких
- [JavaScript, Интерфейсы, Математика] Конечные автоматы в реальной жизни: где мы их используем и почему
- [Разработка веб-сайтов, JavaScript, Программирование, ReactJS, Поисковая оптимизация] Next js. Куда, откуда и причем здесь google?
Теги для поиска: #_java, #_spring_boot_2, #_reactive_programming, #_webflux, #_java
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:28
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Это третья часть серии блогов о реактивном программировании, в которой я познакомлю вас с WebFlux - реактивным веб-фреймворком Spring. 1. ВВЕДЕНИЕ В SPRING WEBFLUXИсходный веб-фреймворк для Spring - Spring Web MVC - был построен для Servlet API и контейнеров Servlet.WebFlux был представлен как часть Spring Framework 5.0. В отличие от Spring MVC, он не требует Servlet API. Он полностью асинхронный и неблокирующий, реализует спецификацию Reactive Streams через проект Reactor (см. предыдущий пост в блоге ).WebFlux требует Reactor в качестве основной зависимости, но он также может взаимодействовать с другими реактивными библиотеками через Reactive Streams.1.1 МОДЕЛИ ПРОГРАММИРОВАНИЯSpring WebFlux поддерживает две разные модели программирования: на основе аннотаций и функциональную.1.1.1 АННОТИРОВАННЫЕ КОНТРОЛЛЕРЫЕсли вы работали со Spring MVC, модель на основе аннотаций будет выглядеть довольно знакомой, поскольку в ней используются те же аннотации из веб-модуля Spring, что и в Spring MVC. Основное отличие состоит в том, что теперь методы возвращают реактивные типы Mono и Flux. См. Следующий пример RestController с использованием модели на основе аннотаций: @RestController
@RequestMapping("/students") public class StudentController { @Autowired private StudentService studentService; public StudentController() { } @GetMapping("/{id}") public Mono<ResponseEntity<Student>> getStudent(@PathVariable long id) { return studentService.findStudentById(id) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } @GetMapping public Flux<Student> listStudents(@RequestParam(name = "name", required = false) String name) { return studentService.findStudentsByName(name); } @PostMapping public Mono<Student> addNewStudent(@RequestBody Student student) { return studentService.addNewStudent(student); } @PutMapping("/{id}") public Mono<ResponseEntity<Student>> updateStudent(@PathVariable long id, @RequestBody Student student) { return studentService.updateStudent(id, student) .map(ResponseEntity::ok) .defaultIfEmpty(ResponseEntity.notFound().build()); } @DeleteMapping("/{id}") public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) { return studentService.findStudentById(id) .flatMap(s -> studentService.deleteStudent(s) .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))) ) .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } }
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> { Mono<T> handle(ServerRequest request); } @FunctionalInterface
public interface RouterFunction<T extends ServerResponse> { Mono<HandlerFunction<T>> route(ServerRequest request); ... } @Configuration
public class StudentRouter { @Bean public RouterFunction<ServerResponse> route(StudentHandler studentHandler){ return RouterFunctions .route( GET("/students/{id:[0-9]+}") .and(accept(APPLICATION_JSON)), studentHandler::getStudent) .andRoute( GET("/students") .and(accept(APPLICATION_JSON)), studentHandler::listStudents) .andRoute( POST("/students") .and(accept(APPLICATION_JSON)),studentHandler::addNewStudent) .andRoute( PUT("students/{id:[0-9]+}") .and(accept(APPLICATION_JSON)), studentHandler::updateStudent) .andRoute( DELETE("/students/{id:[0-9]+}") .and(accept(APPLICATION_JSON)), studentHandler::deleteStudent); } } @Component
public class StudentHandler { private StudentService studentService; public StudentHandler(StudentService studentService) { this.studentService = studentService; } public Mono<ServerResponse> getStudent(ServerRequest serverRequest) { Mono<Student> studentMono = studentService.findStudentById( Long.parseLong(serverRequest.pathVariable("id"))); return studentMono.flatMap(student -> ServerResponse.ok() .body(fromValue(student))) .switchIfEmpty(ServerResponse.notFound().build()); } public Mono<ServerResponse> listStudents(ServerRequest serverRequest) { String name = serverRequest.queryParam("name").orElse(null); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .body(studentService.findStudentsByName(name), Student.class); } public Mono<ServerResponse> addNewStudent(ServerRequest serverRequest) { Mono<Student> studentMono = serverRequest.bodyToMono(Student.class); return studentMono.flatMap(student -> ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(studentService.addNewStudent(student), Student.class)); } public Mono<ServerResponse> updateStudent(ServerRequest serverRequest) { final long studentId = Long.parseLong(serverRequest.pathVariable("id")); Mono<Student> studentMono = serverRequest.bodyToMono(Student.class); return studentMono.flatMap(student -> ServerResponse.status(HttpStatus.OK) .contentType(MediaType.APPLICATION_JSON) .body(studentService.updateStudent(studentId, student), Student.class)); } public Mono<ServerResponse> deleteStudent(ServerRequest serverRequest) { final long studentId = Long.parseLong(serverRequest.pathVariable("id")); return studentService .findStudentById(studentId) .flatMap(s -> ServerResponse.noContent().build(studentService.deleteStudent(s))) .switchIfEmpty(ServerResponse.notFound().build()); } }
<dependency>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-netty</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </dependency> @EnableWebFluxSecurity
public class SecurityConfig { @Bean public MapReactiveUserDetailsService userDetailsService() { UserDetails user = User .withUsername("user") .password(passwordEncoder().encode("userpwd")) .roles("USER") .build(); UserDetails admin = User .withUsername("admin") .password(passwordEncoder().encode("adminpwd")) .roles("ADMIN") .build(); return new MapReactiveUserDetailsService(user, admin); } @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { return http.authorizeExchange() .pathMatchers("/students/admin") .hasAuthority("ROLE_ADMIN") .anyExchange() .authenticated() .and().httpBasic() .and().build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } @EnableWebFluxSecurity
@EnableReactiveMethodSecurity public class SecurityConfig { ... } @DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')") public Mono<ResponseEntity<Void>> deleteStudent(@PathVariable long id) { ... } public class StudentWebClient {
WebClient client = WebClient.create("http://localhost:8080"); public Mono<Student> get(long id) { return client .get() .uri("/students/" + id) .headers(headers -> headers.setBasicAuth("user", "userpwd")) .retrieve() .bodyToMono(Student.class); } public Flux<Student> getAll() { return client.get() .uri("/students") .headers(headers -> headers.setBasicAuth("user", "userpwd")) .retrieve() .bodyToFlux(Student.class); } public Flux<Student> findByName(String name) { return client.get() .uri(uriBuilder -> uriBuilder.path("/students") .queryParam("name", name) .build()) .headers(headers -> headers.setBasicAuth("user", "userpwd")) .retrieve() .bodyToFlux(Student.class); } public Mono<Student> create(Student s) { return client.post() .uri("/students") .headers(headers -> headers.setBasicAuth("admin", "adminpwd")) .body(Mono.just(s), Student.class) .retrieve() .bodyToMono(Student.class); } public Mono<Student> update(Student student) { return client .put() .uri("/students/" + student.getId()) .headers(headers -> headers.setBasicAuth("admin", "adminpwd")) .body(Mono.just(student), Student.class) .retrieve() .bodyToMono(Student.class); } public Mono<Void> delete(long id) { return client .delete() .uri("/students/" + id) .headers(headers -> headers.setBasicAuth("admin", "adminpwd")) .retrieve() .bodyToMono(Void.class); } } @ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class StudentControllerTest { @Autowired WebTestClient webClient; @Test @WithMockUser(roles = "USER") void test_getStudents() { webClient.get().uri("/students") .header(HttpHeaders.ACCEPT, "application/json") .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON) .expectBodyList(Student.class); } @Test @WithMockUser(roles = "ADMIN") void testAddNewStudent() { Student newStudent = new Student(); newStudent.setName("some name"); newStudent.setAddress("an address"); webClient.post().uri("/students") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .body(Mono.just(newStudent), Student.class) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON) .expectBody() .jsonPath("$.id").isNotEmpty() .jsonPath("$.name").isEqualTo(newStudent.getName()) .jsonPath("$.address").isEqualTo(newStudent.getAddress()); } ... } Mono<Void> handle(WebSocketSession session)
Flux<WebSocketMessage> receive()
Mono<Void> send(Publisher<WebSocketMessage> messages) =========== Источник: habr.com =========== =========== Автор оригинала: ANNA ERIKSSON ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 06:28
Часовой пояс: UTC + 5