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样例:
@Configuration
public class RouterFunctionConfig {
@Bean
public 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的函数式实现:
@Configuration
public class RouterFunctionConfig {
@Autowired
private TacoRepository tacoRepository;
@Bean
public 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 {
@Test
public 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请求
@Test
public 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 {
@Autowired
private WebTestClient testClient;
}