Функциональное программирование в Spring MVC.
Spring 5.0 включил спецификацию ReactiveStreams и представил новый Reactive Stack в качестве альтернативы традиционному стеку сервлетов. И это также принесло разработчикам новую модель функционального программирования, но она поддерживается только в стеке Reactive.
Хорошей новостью является то, что в новой версии 5.2 функциональные возможности, такие как API, переносятся обратно в стек сервлетов. Для тех разработчиков, которые придерживаются стека сервлетов и хотят испытать новую модель программирования, это просто поразительная новость.
В этом посте давайте взглянем на новую функциональную функцию в Spring MVC.
Создайте проект Spring Boot, используя Spring initializr (добавить Интернет, JPA, Ломбока также Н2 стартеры как зависимости.
ПРИМЕЧАНИЕ. Выберите новую версию Spring Boot 2.2.0.BUILD-SNAPSHOT, чтобы получить новую версию Spring 5.2.M1 в ее зависимостях.
Создайте простую сущность JPA Post
.
@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
class Post {
@Id
@GeneratedValue(strategy = AUTO)
private Long id;
private String title;
private String content;
}
И создайте репозиторий для Post
Организация.
interface PostRepository extends JpaRepository<Post, Long> {}
Определите RouterFuncation
bean для обработки правил маршрутизации.
@Bean
public RouterFunction<ServerResponse> routes(PostHandler postController) {
return route(GET("/posts"), postController::all)
.andRoute(POST("/posts"), postController::create)
.andRoute(GET("/posts/{id}"), postController::get)
.andRoute(PUT("/posts/{id}"), postController::update)
.andRoute(DELETE("/posts/{id}"), postController::delete);
}
Коды почти такие же, как те, которые мы использовали в стеке Reactive, но здесь обратите внимание на ServerRequest
, ServerResponse
а также RouterFunction
импортируются из нового пакета:org.springframework.web.servlet.function
.
Давайте посмотрим на детали PostHandler
.
@Component
class PostHandler {
private final PostRepository posts;
public PostHandler(PostRepository posts) {
this.posts = posts;
}
public ServerResponse all(ServerRequest req) {
return ServerResponse.ok().body(this.posts.findAll());
}
public ServerResponse create(ServerRequest req) throws ServletException, IOException {
var saved = this.posts.save(req.body(Post.class));
return ServerResponse.created(URI.create("/posts/" + saved.getId())).build();
}
public ServerResponse get(ServerRequest req) {
return this.posts.findById(Long.valueOf(req.pathVariable("id")))
.map(post -> ServerResponse.ok().body(post))
.orElse(ServerResponse.notFound().build());
}
public ServerResponse update(ServerRequest req) throws ServletException, IOException {
var data = req.body(Post.class);
return this.posts.findById(Long.valueOf(req.pathVariable("id")))
.map(
post -> {
post.setTitle(data.getTitle());
post.setContent(data.getContent());
return post;
}
)
.map(post -> this.posts.save(post))
.map(post -> ServerResponse.noContent().build())
.orElse(ServerResponse.notFound().build());
}
public ServerResponse delete(ServerRequest req) {
return this.posts.findById(Long.valueOf(req.pathVariable("id")))
.map(
post -> {
this.posts.delete(post);
return ServerResponse.noContent().build();
}
)
.orElse(ServerResponse.notFound().build());
}
}
Это очень похоже на коды реактивного стека, но методы возвращают ServerResponse
вместо Mono<ServerResponse>
.
Как и функция RouterFunctionDSL, предоставляемая в стеке Reactive, правила маршрутизации также могут быть написаны на Kotlin DSL.
router {
"/posts".nest {
GET("", postHandler::all)
GET("{id}", postHandler::get)
POST("", postHandler::create)
PUT("{id}", postHandler::update)
DELETE("{id}", postHandler::delete)
}
}
Помимо этого, MockMvc также поддерживает Kotlin DSL, вы можете писать свои тесты в свободном стиле, как показано ниже.
@Test
fun `Get all posts should ok`() {
mockMvc
.get("/posts") {
accept = APPLICATION_JSON
headers {
contentLanguage = Locale.ENGLISH
}
}
.andExpect {
status { isOk }
content { contentType(APPLICATION_JSON_UTF8) }
jsonPath("$[0].title") { value(containsString("post")) }
}
.andDo {
print()
}
}
Проверьте исходные коды из моего Github и сравните его с коды который я написал для демонстрации реактивного стека.