11.1 使用Spring WebFlux
- 导入依赖
项目中其他依赖 ```java<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>
org.springframework.boot spring-boot-starter-parent 2.0.4.RELEASE
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!-- Necessary Boot starters --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-rest</artifactId></dependency><!-- Test dependencies --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- Necessary Boot starters --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency>
<a name="ZPntS"></a>## 11.1.1 编写反应式控制器DesignTacoController片段:```java@GetMapping("/recent")public Iterable<Taco> recentTacos() {PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());return tacoRepository.findAll(page).getContent();}
重写为反应式:
@GetMapping("/recent")public Flux<Taco> recentTacos() {return Flux.fromIterable(tacoRepo.findAll()).take(12);}
借助Flux.fromIterable(),我们可以将Iterable
如果repository⼀开始就给我们⼀个Flux那就更好了,就没有必要进⾏转换了。如果能够实现这⼀点,那么recentTacos()将会写成如下形式:
@GetMapping("/recent")public Flux<Taco> recentTacos() {return tacoRepository.findAll().take(12);}
返回单个值
之前的样子:
@GetMapping("/{id}")public Taco tacoById(@PathVariable("id") String id) {Optional<Taco> optTaco = tacoRepo.findById(id);if (optTaco.isPresent()) {return optTaco.get();}return null;}
重写为反应式:
@GetMapping("/{id}")public Mono<Taco> tacoById(@PathVariable("id") String id){return tacoRepository.findById(id);}
实现输入的反应式
原始的postTaco()实现:
@PostMapping(consumes="application/json")@ResponseStatus(HttpStatus.CREATED)public Taco postTaco(@RequestBody Taco taco) {return tacoRepo.save(taco);}
反应式实现:
@PostMapping(consumes = "application/json")@ResponseStatus(HttpStatus.CREATED)public Mono<Taco> postTaco(@RequestBody Mono<Taco> tacoMono) {return tacoRepository.saveAll(tacoMono).next();}
11.2 定义函数式请求处理器
使⽤Spring的函数式编程模型编写API会涉及4个主要的类型:
- RequestPredicate:声明要处理的请求类型。
- RouterFunction:声明如何将请求路由到处理器代码中。
- ServerRequest:代表⼀个HTTP请求,包括对请求头和请求体的访问。
- ServerResponse:代表⼀个HTTP响应,包括响应头和响应体信息。
下⾯是⼀个将所有类型组合在⼀起的Hello World样例:
@Configurationpublic class RouterFunctionConfig {@Beanpublic RouterFunction<?> helloRouterFunction() {return RouterFunctions.route(RequestPredicates.GET("/hello"),serverRequest -> ServerResponse.ok().body(Mono.just("hello world!"), String.class)).andRoute(RequestPredicates.GET("/bye"), serverRequest -> ServerResponse.ok().body(Mono.just("see ya!"), String.class));}}
如下的配置类是DesignTacoController的函数式实现:
@Configurationpublic class RouterFunctionConfig {@Autowiredprivate TacoRepository tacoRepository;@Beanpublic RouterFunction<?> routerFunction() {return RouterFunctions.route(RequestPredicates.GET("/design/taco"), this::recents).andRoute(RequestPredicates.POST("/design"), this::postTaco);}public Mono<ServerResponse> recents(ServerRequest request) {return ServerResponse.ok().body(tacoRepository.findAll().take(12), Taco.class);}public Mono<ServerResponse> postTaco(ServerRequest request) {Mono<Taco> taco = request.bodyToMono(Taco.class);Mono<Taco> savedTaco = tacoRepository.save(taco);return ServerResponse.created(URI.create("http://localhost:8080/design/taco/" + savedTaco.block().getId())).body(savedTaco, Taco.class);}}
11.3 测试反应式控制器
11.3.1 测试get请求
public class DesignTacoControllerTest {@Testpublic void shouldReturnRecentTacos() {Taco[] tacos = {testTaco(1L), testTaco(2L),testTaco(3L), testTaco(4L),testTaco(5L), testTaco(6L),testTaco(7L), testTaco(8L),testTaco(9L), testTaco(10L),testTaco(11L), testTaco(12L),testTaco(13L), testTaco(14L),testTaco(15L), testTaco(16L)};Flux<Taco> tacoFlux = Flux.just(tacos);TacoRepository tacoRepository = Mockito.mock(TacoRepository.class);Mockito.when(tacoRepository.findAll()).thenReturn(tacoFlux);WebTestClient testClient = WebTestClient.bindToController(new DesignTacoController(tacoRepository)).build();testClient.get().uri("/design/recent").exchange().expectStatus().isOk().expectBody().jsonPath("$").isArray().jsonPath("$").isNotEmpty().jsonPath("$[0].id").isEqualTo(tacos[0].getId().toString()).jsonPath("$[0].name").isEqualTo("Taco 1").jsonPath("$[1].id").isEqualTo(tacos[1].getId().toString()).jsonPath("$[1].name").isEqualTo("Taco 2").jsonPath("$[11].id").isEqualTo(tacos[11].getId().toString()).jsonPath("$[11].name").isEqualTo("Taco 12").jsonPath("$[12]").doesNotExist();}private Taco testTaco(Long number) {Taco taco = new Taco();taco.setId(UUID.randomUUID().toString());taco.setName("Taco " + number);ArrayList<Ingredient> ingredients = new ArrayList<>();ingredients.add(new Ingredient("INGA", "Ingredient A", Ingredient.Type.WRAP));ingredients.add(new Ingredient("INGB", "Ingredient B", Ingredient.Type.PROTEIN));taco.setIngredients(ingredients);return taco;}}
也可以用下面的替换上面对应的部分
testClient.get().uri("/design/recent").accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectBodyList(Taco.class).contains(Arrays.copyOf(tacos, 12));
11.3.2 测试Post请求
@Testpublic void shouldSaveTaco() {TacoRepository tacoRepository = Mockito.mock(TacoRepository.class);Mono<Taco> unsavedTacoMono = Mono.just(testTaco(null));Taco savedTaco = testTaco(null);Mono<Taco> savedTacoMono = Mono.just(savedTaco);Mockito.when(tacoRepository.save(ArgumentMatchers.any())).thenReturn(savedTacoMono);WebTestClient testClient = WebTestClient.bindToController(new DesignTacoController(tacoRepository)).build();testClient.post().uri("/design").contentType(MediaType.APPLICATION_JSON).body(unsavedTacoMono, Taco.class).exchange().expectStatus().isCreated().expectBody(Taco.class).isEqualTo(savedTaco);}
11.3.3 使用实时服务器进行测试
@RunWith(SpringRunner.class)@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)public class DesignTacoControllerWebTest {@Autowiredprivate WebTestClient testClient;}
