mirror of
https://github.com/OtusTeam/Spring.git
synced 2026-05-30 10:50:42 +00:00
2022-05 - 22
This commit is contained in:
+2
@@ -2,10 +2,12 @@ package ru.otus.spring.dao;
|
||||
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.logging.LogMe;
|
||||
|
||||
@Repository
|
||||
public class PersonDaoSimple implements PersonDao {
|
||||
|
||||
@LogMe
|
||||
public Person findByName(String name) {
|
||||
return new Person(name, 18);
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package ru.otus.spring.logging;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface LogMe {
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class LoggingAspect {
|
||||
|
||||
@Before("execution(* ru.otus.spring.dao.PersonDaoSimple.*(..))")
|
||||
@Before("@annotation(ru.otus.spring.logging.LogMe)")
|
||||
public void logBefore(JoinPoint joinPoint) {
|
||||
System.out.println("Прокси : " + joinPoint.getThis().getClass().getName());
|
||||
System.out.println("Класс : " + joinPoint.getTarget().getClass().getName());
|
||||
|
||||
+8
@@ -17,4 +17,12 @@ public class Person {
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Person{" +
|
||||
"name='" + name + '\'' +
|
||||
", age=" + age +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -8,6 +8,8 @@ import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
|
||||
|
||||
// -ooo---ooo---ooo|---------
|
||||
// --------------------X-----
|
||||
Flux<Person> findByName(String name);
|
||||
|
||||
@Query("{ 'name': ?0 }")
|
||||
|
||||
@@ -13,7 +13,9 @@ public class Main {
|
||||
|
||||
ReactiveProcessingService service = context.getBean(ReactiveProcessingService.class);
|
||||
|
||||
service.printHello("Ivan");
|
||||
for (int i = 0; i < 100000; ++i) {
|
||||
service.printHello("Ivan");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -21,9 +21,10 @@ public class ReactiveProcessingService {
|
||||
// Создаём sink (ранее - процессор)
|
||||
// Это reactor-овская реализация reactive-stream интерфейса
|
||||
// Обрабатывает данные как простой последовательный вызов методов :)
|
||||
sink = Sinks.many().multicast().directBestEffort();
|
||||
sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
// Здесь мы настраиваем flow
|
||||
flow = sink.asFlux()
|
||||
.parallel(2)
|
||||
.map(nonFluxService::nonFluxSayHello)
|
||||
.subscribe(this::printMessage);
|
||||
// в идеале в коде выше должен быть doOnNext
|
||||
@@ -36,7 +37,7 @@ public class ReactiveProcessingService {
|
||||
* @param name это имя будет приходить из не-reactor окружения
|
||||
*/
|
||||
public void printHello(String name) {
|
||||
sink.tryEmitNext(new Message(name));
|
||||
if (sink.tryEmitNext(new Message(name)).isFailure()) logger.error("!!!!!!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
@@ -8,6 +8,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class ReactorController {
|
||||
@@ -17,8 +18,14 @@ public class ReactorController {
|
||||
return Mono.just("one");
|
||||
}
|
||||
|
||||
@GetMapping("/flux/three")
|
||||
public Flux<Integer> three() {
|
||||
return Flux.just(300, 600, 900);
|
||||
}
|
||||
|
||||
@GetMapping("/flux/ten")
|
||||
public Flux<Integer> list() {
|
||||
// 12345678910|------
|
||||
return Flux.range(1, 10);
|
||||
}
|
||||
|
||||
|
||||
+14
-5
@@ -2,19 +2,28 @@ package ru.otus.spring;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
public class RxJava2Controller {
|
||||
|
||||
@GetMapping("/rx/one")
|
||||
public Single<String> single() {
|
||||
return Single.just("one");
|
||||
public Single<Integer> single() {
|
||||
return Single.just("one")
|
||||
.map(s -> s.length());
|
||||
}
|
||||
|
||||
@GetMapping("/rx/ten")
|
||||
public Flowable<Integer> list() {
|
||||
return Flowable.range(1, 10);
|
||||
// single()
|
||||
// .subscribe(response -> request.sendToClient(response))
|
||||
|
||||
@GetMapping(value = "/rx/ten")
|
||||
public Flowable<Long> list() {
|
||||
// --0--1--2--3--4--...
|
||||
return Flowable.interval(2, TimeUnit.SECONDS)
|
||||
.map(i -> i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
target/
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.otus</groupId>
|
||||
<artifactId>spring-22</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spring-22-exercise</module>
|
||||
<module>spring-22-solution</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.otus</groupId>
|
||||
<artifactId>spring-22-exercise</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Зависимости WebFlux -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Зависимости Reactive SpringData -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Тестирование -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,69 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.*;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext context = SpringApplication.run(Main.class);
|
||||
PersonRepository repository = context.getBean(PersonRepository.class);
|
||||
|
||||
repository.saveAll(Arrays.asList(
|
||||
new Person("Pushkin", 22),
|
||||
new Person("Lermontov", 22),
|
||||
new Person("Tolstoy", 60)
|
||||
)).subscribe(p -> System.out.println(p.getLastName()));
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> composedRoutes(PersonRepository repository) {
|
||||
return route()
|
||||
// Обратите внимание на использование хэндлера
|
||||
.GET("/func/person", accept(APPLICATION_JSON), new PersonHandler(repository)::list)
|
||||
// Обратите внимание на использование pathVariable
|
||||
.GET("/func/person/{id}", accept(APPLICATION_JSON),
|
||||
request -> repository.findById(request.pathVariable("id"))
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromValue(person)))
|
||||
.switchIfEmpty(notFound().build())
|
||||
).build();
|
||||
}
|
||||
|
||||
// Это пример хэндлера, который даже не бин
|
||||
static class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
Mono<ServerResponse> list(ServerRequest request) {
|
||||
// Обратите внимание на пример другого порядка создания response от Flux
|
||||
return ok().contentType(APPLICATION_JSON).body(repository.findAll(), Person.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package ru.otus.spring.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
@Document
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
@Field("name")
|
||||
private String lastName;
|
||||
|
||||
private int age;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person(String lastName, int age) {
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface PersonRepository
|
||||
extends ReactiveMongoRepository<Person, String> {
|
||||
|
||||
Flux<Person> findAll();
|
||||
|
||||
Mono<Person> findById(String id);
|
||||
|
||||
Mono<Person> save(Mono<Person> person);
|
||||
|
||||
Flux<Person> findAllByLastName(String lastName);
|
||||
|
||||
Flux<Person> findAllByAge(int age);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@RestController
|
||||
public class AnnotatedController {
|
||||
|
||||
@GetMapping("/flux/one")
|
||||
public Mono<String> one() {
|
||||
return Mono.just("one");
|
||||
}
|
||||
|
||||
@GetMapping("/flux/ten")
|
||||
public Flux<Integer> list() {
|
||||
return Flux.range(1, 10).delayElements(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> stream() {
|
||||
return Flux.generate(() -> 0, (state, emitter) -> {
|
||||
emitter.next(state);
|
||||
return state + 1;
|
||||
})
|
||||
.delayElements(Duration.ofSeconds(1L))
|
||||
.map(i -> "" + i);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
@RestController
|
||||
public class PersonController {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonController(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@GetMapping("/person")
|
||||
public Flux<Person> all() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
public Mono<Person> byId(@PathVariable("id") String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
public Mono<Person> save(@RequestBody Mono<Person> dto) {
|
||||
return repository.save(dto);
|
||||
}
|
||||
|
||||
@GetMapping("/person/find")
|
||||
public Flux<Person> byName(@RequestParam("name") String name) {
|
||||
return repository.findAllByLastName(name);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@DataMongoTest
|
||||
public class PersonRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private PersonRepository repository;
|
||||
|
||||
@Test
|
||||
public void shouldSetIdOnSave() {
|
||||
Mono<Person> personMono = repository.save(new Person("Bill", 12));
|
||||
|
||||
StepVerifier
|
||||
.create(personMono)
|
||||
.assertNext(person -> assertNotNull(person.getId()))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
@SpringBootTest
|
||||
public class PersonControllerTest {
|
||||
|
||||
@Autowired
|
||||
private RouterFunction<ServerResponse> route;
|
||||
|
||||
@Test
|
||||
public void testRoute() {
|
||||
WebTestClient client = WebTestClient
|
||||
.bindToRouterFunction(route)
|
||||
.build();
|
||||
|
||||
client.get()
|
||||
.uri("/func/person")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.otus</groupId>
|
||||
<artifactId>spring-22-solution</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Зависимости WebFlux -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Зависимости Reactive SpringData -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Тестирование -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,82 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.*;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext context = SpringApplication.run(Main.class);
|
||||
PersonRepository repository = context.getBean(PersonRepository.class);
|
||||
|
||||
repository.saveAll(Arrays.asList(
|
||||
new Person("Pushkin", 22),
|
||||
new Person("Lermontov", 22),
|
||||
new Person("Tolstoy", 60)
|
||||
)).subscribe(p -> System.out.println(p.getLastName()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> composedRoutes(PersonRepository repository) {
|
||||
return route()
|
||||
// эта функция должна стоять раньше findAll - порядок следования роутов - важен
|
||||
.GET("/func/person", queryParam("name", StringUtils::isNotEmpty),
|
||||
request -> request.queryParam("name")
|
||||
.map(repository::findAllByLastName)
|
||||
.map(persons -> ok().body(persons, Person.class))
|
||||
.orElse(badRequest().build())
|
||||
)
|
||||
// пример другой реализации - начиная с запроса репозитория
|
||||
.GET("/func/person", queryParam("age", StringUtils::isNotEmpty),
|
||||
req -> repository.findAllByLastName(
|
||||
req.queryParam("age").orElseThrow(IllegalArgumentException::new)
|
||||
)
|
||||
.collectList()
|
||||
.flatMap(persons -> ok().body(persons, Person.class)))
|
||||
// Обратите внимание на использование хэндлера
|
||||
.GET("/func/person", accept(APPLICATION_JSON), new PersonHandler(repository)::list)
|
||||
// Обратите внимание на использование pathVariable
|
||||
.GET("/func/person/{id}", accept(APPLICATION_JSON),
|
||||
request -> repository.findById(request.pathVariable("id"))
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromValue(person)))
|
||||
.switchIfEmpty(notFound().build())
|
||||
).build();
|
||||
}
|
||||
|
||||
// Это пример хэндлера, который даже не бин
|
||||
static class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
Mono<ServerResponse> list(ServerRequest request) {
|
||||
// Обратите внимание на пример другого порядка создания response от Flux
|
||||
return ok().contentType(APPLICATION_JSON).body(repository.findAll(), Person.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package ru.otus.spring.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
@Document
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
@Field("name")
|
||||
private String lastName;
|
||||
|
||||
private int age;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person(String lastName, int age) {
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface PersonRepository
|
||||
extends ReactiveMongoRepository<Person, String> {
|
||||
|
||||
Flux<Person> findAll();
|
||||
|
||||
Mono<Person> findById(String id);
|
||||
|
||||
Mono<Person> save(Mono<Person> person);
|
||||
|
||||
Flux<Person> findAllByLastName(String lastName);
|
||||
|
||||
Flux<Person> findAllByAge(int age);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@RestController
|
||||
public class AnnotatedController {
|
||||
|
||||
@GetMapping("/flux/one")
|
||||
public Mono<String> one() {
|
||||
return Mono.just("one")
|
||||
.map(String::toUpperCase);
|
||||
}
|
||||
|
||||
@GetMapping("/flux/ten")
|
||||
public Flux<Integer> list() {
|
||||
return Flux.range(1, 10);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> stream() {
|
||||
return Flux.generate(() -> 0, (state, emitter) -> {
|
||||
emitter.next(state);
|
||||
return state + 1;
|
||||
})
|
||||
.delayElements(Duration.ofSeconds(1L))
|
||||
.map(i -> "" + i);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
@RestController
|
||||
public class PersonController {
|
||||
|
||||
private PersonRepository repository;
|
||||
|
||||
public PersonController(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@GetMapping("/person")
|
||||
public Flux<Person> all() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
public Mono<Person> byId(@PathVariable("id") String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
@GetMapping("/person/byname")
|
||||
public Flux<Person> byName(@RequestParam("name") String lastName) {
|
||||
return repository.findAllByLastName(lastName);
|
||||
}
|
||||
|
||||
@GetMapping("/person/byage")
|
||||
public Flux<Person> byAge(@RequestParam int age) {
|
||||
return repository.findAllByAge(age);
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
public Mono<Person> save(@RequestBody Mono<Person> dto) {
|
||||
return repository.save(dto);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@DataMongoTest
|
||||
public class PersonRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private PersonRepository repository;
|
||||
|
||||
@Test
|
||||
public void shouldSetIdOnSave() {
|
||||
Mono<Person> personMono = repository.save(new Person("Bill", 12));
|
||||
|
||||
StepVerifier
|
||||
.create(personMono)
|
||||
.assertNext(person -> assertNotNull(person.getId()))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
@SpringBootTest
|
||||
public class PersonControllerTest {
|
||||
|
||||
@Autowired
|
||||
private RouterFunction<ServerResponse> route;
|
||||
|
||||
@Test
|
||||
public void testRoute() {
|
||||
WebTestClient client = WebTestClient
|
||||
.bindToRouterFunction(route)
|
||||
.build();
|
||||
|
||||
client.get()
|
||||
.uri("/func/person")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user