函数式编程
WebFlux 还支持基于 lambda 表达式的函数式编程模型。与基于 Java 注解的编程模型相比,函数式编程模型的抽象层次更低,代码编写更灵活,可以满足一些对动态性要求更高的场景。不过在编写时的代码复杂度也较高,学习曲线也较陡。开发人员可以根据实际的需要来选择合适的编程模型。目前 Spring Boot 不支持在一个应用中同时使用两种不同的编程模式。
为了说明函数式编程模型的用法,我们使用 Spring Initializ 来创建一个新的 WebFlux 项目。在函数式编程模型中,每个请求是由一个函数来处理的, 通过接口 org.springframework.web.reactive.function.server.HandlerFunction 来表示。HandlerFunction 是一个函数式接口,其中只有一个方法 Mono<T extends ServerResponse> handle(ServerRequest request),因此可以用 labmda 表达式来实现该接口。接口 ServerRequest 表示的是一个 HTTP 请求。通过该接口可以获取到请求的相关信息,如请求路径、HTTP 头、查询参数和请求内容等。方法 handle 的返回值是一个 Mono<T extends ServerResponse>对象。接口 ServerResponse 用来表示 HTTP 响应。ServerResponse 中包含了很多静态方法来创建不同 HTTP 状态码的响应对象。
创建一个Spring Boost项目
可以通过 Spring initializer 创建一个Spring Boot项目,你可以添加需要的依赖,比如Reactive Web,h2,lombok等等。
如图:
我的项目结构如下:
项目结构分析
实体类post.java,使用lombok插件
Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。官方地址:https://projectlombok.org/,github地址:https://github.com/rzwitserloot/lombok。
@Data
@ToString
@Builder //构造一个实例,属性不需要单独set
@NoArgsConstructor
@AllArgsConstructor
public class Post {
private Long id;
private String title;
private String content;
/**
* @Builder 的原理,这段代码的意义
*
**/
/*
private Post(Long id,String title,String content) {
this.id = id;
this.title = title;
this.content = content;
}
public static <T> PostBuilder<T> builder() {
return new PostBuilder<T>();
}
public static class PostBuilder<T> {
private Long id;
private String title;
private String content;
private PostBuilder() {}
public PostBuilder id(Long id) {
this.id = id;
return this;
}
public PostBuilder title(String title) {
this.title = title;
return this;
}
public PostBuilder content(String content) {
this.content = content;
return this;
}
@java.lang.Override
public String toString() {
return "PostBuilder(id = " + id + ", title = " + title + ", content = " + content + ")";
}
public Post build() {
return new Post(id, title, content);
}
}
*/
}
dao层:PostRepository.java
@Component
public class PostRepository {
//模拟数据库
private Map<Long, Post> data=new HashMap<>();
//设置ID
private AtomicLong nextIdGenerator = new AtomicLong(1L);
//初始化数据
public PostRepository() {
Stream.of("post one", "post two").forEach(title->{
Long id=this.nextId();
data.put(id, Post.builder().id(id).title(title).content("你是"+id).build());
});
}
private Long nextId() {
return nextIdGenerator.getAndIncrement(); //the previous value
}
public Flux<Post> findAll() {
return Flux.fromIterable(data.values());
}
public Mono<Post> findById(Long id) {
if (data.get(id)==null) {
return Mono.empty();
}
return Mono.just(data.get(id));
}
public Mono<Post> save(Post post) {
Long id = nextId();
Post saved = Post.builder().id(id).title(post.getTitle()).content(post.getContent()).build();
data.put(id, saved);
return Mono.just(saved);
}
public Mono<Post> update(Long id,Post post){
Post old=data.get(id);
old.setTitle(post.getTitle());
old.setContent(post.getContent());
data.put(id, old);
return Mono.just(old);
}
public Mono<Post> createOrUpdate(Post post){
if (post.getId()==null||post.getId()==0) {
return save(post);
}else{
return update(post.getId(), post);
}
}
public Mono<Void> delete(Long id) {
/*Post deleted = data.get(id);
data.remove(id);
return Mono.just(deleted);*/
data.remove(id);
return Mono.empty();
}
}
service层:PostHandler.class
package com.kongl.example.service;
import java.net.URI;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import org.springframework.web.reactive.function.server.RouterFunction;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.*;
import com.kongl.example.dao.PostRepository;
import com.kongl.example.entity.Post;
import reactor.core.publisher.Mono;
/**
*
* @author KL
* @date 2018年1月24日
* @description
*
*/
@Component
public class PostHandler {
private final PostRepository posts;
public PostHandler(PostRepository posts) {
this.posts = posts;
}
/**
*
* @author KL
* @date 2018年1月24日
* @description: 获取全部实例
* @return Mono<ServerResponse>
*/
public Mono<ServerResponse> all(ServerRequest req) {
return ok().body(posts.findAll(), Post.class);
}
public Mono<ServerResponse> create(ServerRequest req) {
return req.body(BodyExtractors.toMono(Post.class))
.flatMap(post -> this.posts.createOrUpdate(post))
.flatMap(p -> created(URI.create("/posts/" + p.getId())).build());
}
public Mono<ServerResponse> get(ServerRequest req) {
return this.posts.findById(Long.valueOf(req.pathVariable("id")))
.flatMap(post -> ok().syncBody(post))
.switchIfEmpty(notFound().build());
}
public Mono<ServerResponse> update(ServerRequest req) {
/**
* Aggregate given monos into a new Mono that will be fulfilled
* when all of the given Monos have been fulfilled,
* aggregating their values according to the provided combinator function.
* An error will cause pending results to be cancelled
* and immediate error emission to the returned Mono.
* 根据提供的组合功能聚合它们的值
*/
return Mono
.zip(
(data) -> {
Post p = (Post) data[0]; //原始数据
Post p2 = (Post) data[1]; //修改的数据
p.setTitle(p2.getTitle());
p.setContent(p2.getContent());
return p;
},
this.posts.findById(Long.valueOf(req.pathVariable("id"))),
req.bodyToMono(Post.class)
)
.cast(Post.class) //Cast the current Mono produced type into a target produced type.
.flatMap(post -> this.posts.createOrUpdate(post))
.flatMap(post -> ServerResponse.noContent().build());
}
public Mono<ServerResponse> delete(ServerRequest req) {
/**
* 服务器成功处理了请求,但没返回任何内容。
*/
return ServerResponse.noContent().build(this.posts.delete(Long.valueOf(req.pathVariable("id"))));
}
}
/**
* Aggregate given monos into a new Mono that will be fulfilled
* when all of the given Monos have been fulfilled,
* aggregating their values according to the provided combinator function.
* An error will cause pending results to be cancelled
* and immediate error emission to the returned Mono.
* 根据提供的组合功能聚合它们的值
*/
return Mono
.zip(
(data) -> {
Post p = (Post) data[0]; //原始数据
Post p2 = (Post) data[1]; //修改的数据
p.setTitle(p2.getTitle());
p.setContent(p2.getContent());
return p;
},
this.posts.findById(Long.valueOf(req.pathVariable("id"))),
req.bodyToMono(Post.class)
)
.cast(Post.class) //Cast the current Mono produced type into a target produced type.
.flatMap(post -> this.posts.createOrUpdate(post))
.flatMap(post -> ServerResponse.noContent().build());
}
public Mono<ServerResponse> delete(ServerRequest req) {
/**
* 服务器成功处理了请求,但没返回任何内容。
*/
return ServerResponse.noContent().build(this.posts.delete(Long.valueOf(req.pathVariable("id"))));
}
}
这里是一个PostHandler类,这个handler 类就像 Spring Web 中的 Service beans 一样,在这里我们需要编写该服务的大部分业务功能。ServerResponse 就像 Spring Web 中的 ResponseEntity 类一样,我们可以在 ServerResponse 对象中打包 Response 的数据、状态码、头信息等。
ServerResponse 有很多有用的默认方法,notFound(),ok(),accepted(),created(),noContent()等,可用于创建不同类型的反馈。PostHandler有不同的方法,都返回 Mono<ServerResponse>,其中postRepository.findAll() 返回Flux<Post>,而这句代码ok().body(posts.findAll(), Post.class),可将此 Flux <Post> 转换为 Mono<ServerResponse>,这表明只要可用时,都可发起 ServerResponse 的流。
在命令式编程风格中,数据接收前线程会一直阻塞,这样使得其线程在数据到来前无法运行。而响应式(反应式)编程中,我们定义一个获取数据的流,然后定义一个在数据到来后的回调函数操作。这样不会使线程堵塞,在数据被返回时,可用线程就用于执行。
应用程序路由的路由类RouteConfig.java
package com.kongl.example.route;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import com.kongl.example.service.PostHandler;
@Configuration
@EnableWebFlux
public class RouteConfig {
@Bean
public RouterFunction<ServerResponse> routes(PostHandler postHandler){
return route(GET("/posts"), postHandler::all)
.andRoute(POST("/posts").and(contentType(APPLICATION_JSON)), postHandler::create)
.andRoute(GET("/posts/{id}"), postHandler::get)
.andRoute(PUT("/posts/{id}"), postHandler::update) //默认参数为application/json
.andRoute(DELETE("/posts/{id}"), postHandler::delete);
}
}
方法 RouterFunctions.route 用来根据 Predicate 是否匹配来确定 HandlerFunction 是否被应用。RequestPredicates 中包含了很多静态方法来创建常用的基于不同匹配规则的 Predicate。如 RequestPredicates.path 用来根据 HTTP 请求的路径来进行匹配。此处我们检查请求的路径是/posts
使用postman测试
按照正常的springboot项目启动,
大伙会发现Netty 来运行 Reactive 应用,springboot2.0后默认Netty,可能因为 Netty 是基于异步和事件驱动的。
get:
post:
put:
delete: