在上一篇文章中,我介绍了Spring Web-Flux的基础知识,它表示Spring框架的Web层中的响应式支持。
我已经展示了使用Spring Data Cassandra并在Spring Web Layers中使用传统注释支持的端到端示例, 大致如下:
...
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
...
@RestController
@RequestMapping("/hotels")
public class HotelController {
@GetMapping(path = "/{id}")
public Mono<Hotel> get(@PathVariable("id") UUID uuid) {
...
}
@GetMapping(path = "/startingwith/{letter}")
public Flux<HotelByLetter> findHotelsWithLetter(
@PathVariable("letter") String letter) {
...
}
}
除了返回类型外,这看起来像传统的Spring Web注释,这些端点不是返回域类型,而是通过在Reactor-Core中实现Mono和Flux的实现返回Publisher类型,而Spring-Web则将内容流回。
在本文中,我将介绍一种不同的公开端点的方法-使用功能样式而不是注释样式。 让我承认,对于我了解暴露Web终结点的功能样式的理解,我发现Baeldung的文章和Rossen Stoyanchev的文章非常宝贵。
将注释映射到路线
让我从一些基于注释的端点开始,一个是检索实体,另一个是保存实体:
@GetMapping(path = "/{id}")
public Mono<Hotel> get(@PathVariable("id") UUID uuid) {
return this.hotelService.findOne(uuid);
}
@PostMapping
public Mono<ResponseEntity<Hotel>> save(@RequestBody Hotel hotel) {
return this.hotelService.save(hotel)
.map(savedHotel -> new ResponseEntity<>(savedHotel, HttpStatus.CREATED));
}
在公开端点的功能风格中,每个端点都将转换为RouterFunction ,它们可以组成以创建应用程序的所有端点,如下所示:
package cass.web;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.*;
public interface ApplicationRoutes {
static RouterFunction<?> routes(HotelHandler hotelHandler) {
return nest(path("/hotels"),
nest(accept(MediaType.APPLICATION_JSON),
route(GET("/{id}"), hotelHandler::get)
.andRoute(POST("/"), hotelHandler::save)
));
}
}
有一些辅助功能(嵌套,路由,GET,接受等),可以轻松地将所有RouterFunction组合在一起。 找到合适的RouterFunction后,该请求由HandlerFunction处理,该函数在上述示例中由HotelHandler抽象,并且保存和获取功能如下所示:
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.UUID;
@Service
public class HotelHandler {
...
public Mono<ServerResponse> get(ServerRequest request) {
UUID uuid = UUID.fromString(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
return this.hotelService.findOne(uuid)
.flatMap(hotel -> ServerResponse.ok().body(Mono.just(hotel), Hotel.class))
.switchIfEmpty(notFound);
}
public Mono<ServerResponse> save(ServerRequest serverRequest) {
Mono<Hotel> hotelToBeCreated = serverRequest.bodyToMono(Hotel.class);
return hotelToBeCreated.flatMap(hotel ->
ServerResponse.status(HttpStatus.CREATED).body(hotelService.save(hotel), Hotel.class)
);
}
...
}
这是原始基于注释的项目支持的所有API的完整RouterFunction的样子:
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.*;
public interface ApplicationRoutes {
static RouterFunction<?> routes(HotelHandler hotelHandler) {
return nest(path("/hotels"),
nest(accept(MediaType.APPLICATION_JSON),
route(GET("/{id}"), hotelHandler::get)
.andRoute(POST("/"), hotelHandler::save)
.andRoute(PUT("/"), hotelHandler::update)
.andRoute(DELETE("/{id}"), hotelHandler::delete)
.andRoute(GET("/startingwith/{letter}"), hotelHandler::findHotelsWithLetter)
.andRoute(GET("/fromstate/{state}"), hotelHandler::findHotelsInState)
));
}
}
测试功能路线
测试这些路由也很容易,Spring Webflux提供了一个WebTestClient来测试路由,同时提供模拟其背后的实现的能力。
例如,为了测试通过ID的get端点,我将WebTestClient绑定到之前定义的RouterFunction,并使用它提供的断言来测试行为。
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import java.util.UUID;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GetRouteTests {
private WebTestClient client;
private HotelService hotelService;
private UUID sampleUUID = UUID.fromString("fd28ec06-6de5-4f68-9353-59793a5bdec2");
@Before
public void setUp() {
this.hotelService = mock(HotelService.class);
when(hotelService.findOne(sampleUUID)).thenReturn(Mono.just(new Hotel(sampleUUID, "test")));
HotelHandler hotelHandler = new HotelHandler(hotelService);
this.client = WebTestClient.bindToRouterFunction(ApplicationRoutes.routes(hotelHandler)).build();
}
@Test
public void testHotelGet() throws Exception {
this.client.get().uri("/hotels/" + sampleUUID)
.exchange()
.expectStatus().isOk()
.expectBody(Hotel.class)
.isEqualTo(new Hotel(sampleUUID, "test"));
}
}
结论
定义路由的功能方式绝对不同于基于注释的方式–我喜欢这是定义端点以及如何处理端点调用的更为明确的方式,注释总是给人更多的感觉。神奇。
我在github存储库中有完整的工作代码,它可能比本文中的代码更容易遵循。
翻译自: https://www.javacodegeeks.com/2017/04/spring-web-flux-functional-style-cassandra-backend.html