2个demo的源码在GitHub上 链接点这里
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
特性
- 惰性计算
- 函数是“第一等公民”
- 只使用表达式而不使用语句
- 没有副作用
Spring官方文档: In plain terms reactive programming is about non-blocking applications that are asynchronous and event-driven and require a small number of threads to scale vertically (i.e. within the JVM) rather than horizontally (i.e. through clustering).
简单来说响应式编程是关于异步的事件驱动的需要少量线程的垂直扩展而非水平扩展的无阻塞应用
A key aspect of reactive applications is the concept of backpressure which is a mechanism to ensure producers don’t overwhelm consumers. For example in a pipeline of reactive components extending from the database to the HTTP response when the HTTP connection is too slow the data repository can also slow down or stop completely until network capacity frees up.
Reactive programming also leads to a major shift from imperative to declarative async composition of logic. It is comparable to writing blocking code vs using the CompletableFuture from Java 8 to compose follow-up actions via lambda expressions.
For a longer introduction check the blog series “Notes on Reactive Programming” by Dave Syer.
百度百科: 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
4个关键词:
- Responsive: 可响应的。要求系统尽可能做到在任何时候都能及时响应。
- Resilient: 可恢复的。要求系统即使出错了,也能保持可响应性。
- Elastic: 可伸缩的。要求系统在各种负载下都能保持可响应性。
- Message Driven:消息驱动的。要求系统通过异步消息连接各个组件。
Excel表格就是一个响应式的(演示示例)
backpressure: backpressure(背压)概念的理解
左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。
在Web容器的选择上,Spring WebFlux既支持像Tomcat,Jetty这样的的传统容器(前提是支持Servlet 3.1 Non-Blocking IO API),又支持像Netty,Undertow那样的异步容器。不管是何种容器,Spring WebFlux都会将其输入输出流适配成Flux
格式,以便进行统一处理。
值得一提的是,除了新的Router Functions接口,Spring WebFlux同时支持使用老的Spring MVC注解声明Reactive Controller。和传统的MVC Controller不同,Reactive Controller操作的是非阻塞的ServerHttpRequest
和ServerHttpResponse
,而不再是Spring MVC里的HttpServletRequest
和HttpServletResponse
。
@GetMapping("/reactive/restaurants")
public Flux findAll() {
return restaurantRepository.findAll();
}
可以看到主要变化就是在 返回的类型上Flux
Flux和Mono 是 Reactor 中的流数据类型,其中Flux会发送多次,Mono会发送0次或一次
使用webflux需要具备的基础是Reactive programming 的理解。
Reactor 的基础 和 熟练的java8 lambda使用
Server类:
public class Server {
public static final String HOST = "localhost";
public static final int PORT = 8080;
private Routes routes = new Routes(); // ③
public static void main(String[] args) throws Exception {
Server server = new Server();
server.startReactorServer(); // ①
// server.startTomcatServer(); // ②
System.out.println("Press ENTER to exit.");
System.in.read();
}
}
①:Netty
public void startReactorServer() throws InterruptedException {
RouterFunction route = routes.rout(); // ④
HttpHandler httpHandler = toHttpHandler(route);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}
②:Tomcat
public void startTomcatServer() throws LifecycleException {
RouterFunction> route = routes.rout();
HttpHandler httpHandler = toHttpHandler(route);
Tomcat tomcatServer = new Tomcat();
tomcatServer.setHostname(HOST);
tomcatServer.setPort(PORT);
Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
rootContext.addServletMapping("/", "httpHandlerServlet");
tomcatServer.start();
}
③:Route
public class Routes {
public RouterFunction rout() {
CustomerRepository repository = new CustomerRepositoryImpl();
CustomerHandler handler = new CustomerHandler(repository);
return nest(
path("/person"),
nest(accept(APPLICATION_JSON),
route(GET("/{id}"), handler::getPerson)
.andRoute(method(HttpMethod.GET), handler::listPeople)
).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson)
).andNest(
path("/product"),
route(path("/"), serverRequest ->
ServerResponse.ok().contentType(APPLICATION_JSON)
.body(fromPublisher(Flux.just(new Product(1, "PC", 1000.00)), Product.class))
)
);
}
}
route中设置uri地址对应的资源,可以创建多个route
Route创建的方法:
RouterFunction
通过RouterFunctions
来创建Route
RouterFunctions.route(RequestPredicate predicate, HandlerFunction
其中 RequestPredicate 是一个函数式接口,接受一个’T’类型返回一个布尔类型。
大多数情况,我们都可以通过RequestPredicates类的静态方法来创建这个对象,比如:
public static RequestPredicate GET(String pattern) {
return method(HttpMethod.GET).and(path(pattern));
}
HandlerFunction
也是一个函数式接口,接受一个ServerResponse
的子类返回Mono,可以把这个对象当作实际处理逻辑的部分。
public static
nest 方法同样有一个 RequestPredicate 但是第二个参数变成了 RouterFunction
,这个方法的作用是为了分支uri。
在RESTFUL风格的接口中,会有目录层级的概念,这里的nest方法就可以理解为层级的关系,比如上述的”/person”后面的”/{id}” 就是相当于 “/person/{id}”
public static RequestPredicate accept(MediaType... mediaTypes)
accept可以用来设定接收或者返回的请求类型 Filter
通过RouterFunction的 filter方法可以添加过滤器,next.handle(request);
的前后可以添加一些需要做的事
.filter((request, next) -> {
System.out.println("Before handler invocation: " + request.path());
Response> response = next.handle(request);
Object body = response.body();
System.out.println("After handler invocation: " + body);
return response;
})
Handler:
int personId = Integer.valueOf(request.pathVariable("id"));
来获取返回的类型 Mono
可以通过Spring 提供的 ServerResponse 来创建
状态码方法,可以使用现成的,比如成功的状态码:
static ServerResponse.BodyBuilder ok() {
return status(HttpStatus.OK);
}
也可以自己定义:
static ServerResponse.BodyBuilder ok() {
return status(HttpStatus.OK);
}
最后就是返回的内容:
<T, P extends Publisher<T>> Mono<ServerResponse> body(P var1, Class<T> var2);
<T, P extends Publisher<T>> Mono<ServerResponse> body(P var1, ParameterizedTypeReference<T> var2);
Mono<ServerResponse> syncBody(Object var1);
Mono<ServerResponse> body(BodyInserter, ? super ServerHttpResponse> var1);
Mono<ServerResponse> render(String var1, Object... var2);
Mono<ServerResponse> render(String var1, Map<String, ?> var2);
一般常用的通过 body方法来放入返回的内容
Flux people = this.repository.all();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Customer.class);
使用BodyInserters的构建方法 fromObject(); fromPublisher();等
使用 build 方法
Mono<ServerResponse> build(Publisher<Void> var1);
[演示]
通过springboot来使用webFlux 会更简单:
[springboot的版本必须是2.0以上]
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.0.M4version>
<relativePath/>
parent>
webFlux 的依赖包
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
入口类和普通的springboot项目一样
@SpringBootApplication
public class WebfluxApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxApplication.class, args);
}
}
创建Route:
@Configuration
public class WebRoutes {
@Bean
public RouterFunction route() {
return RouterFunctions.route(
GET("/"), request -> {
Mono user = Mono.just(new User("1", "a"));
return ok().body(fromPublisher(user, User.class));
});
}
}
此时我们的项目已经可以运行了,启动时可以看到 已经创建了地址映射 ‘/’
Mapped (GET && /) -> com.lin.webflux.web.WebRoutes$$Lambda$190/874765360@44a44d5d
创建多个Route 只需要上述的相同操作,比如每个模块一个route bean
[例子]
==RESTFUL API 和 eventStram 和 websocket编程==
spring在5.0版本提供了对kotlin的支持,
- 空安全api
- 支持kotlin不可变类通过optional参数和默认值
- 函数式bean定义 DSL
- 函数式routing定义 DSL
- 利用Kotlin具体的类型参数指定显式类避免使用序列化/反序列化的各种API比如RestTemplate或WebFlux的API。
- kotlin 空安全支持 @Autowired或@Inject 和 @RequestParam、@RequestHeader等
- kotlin脚本同时支持 springMVC 和 Spring WebFlux
- 支持kotlin构造器自动注入
- kotlin反射用来定义接口方法参数
主要介绍2点
1. Bean 和 route 的 DSL
spring注入bean的方式我们知道的有2中,xml配置文件和注解。如果使用kotlin的话使用dsl的方式会更方便。
```
fun beans() = beans {
bean()
bean {
Routes(ref(), ref())
}
bean("webHandler") {
RouterFunctions.toWebHandler(ref().router(), HandlerStrategies.builder().viewResolver(ref()).build())
}
bean("messageSource") {
ReloadableResourceBundleMessageSource().apply {
setBasename("messages")
setDefaultEncoding("UTF-8")
}
}
bean {
val prefix = "classpath:/templates/"
val suffix = ".mustache"
val loader = MustacheResourceTemplateLoader(prefix, suffix)
MustacheViewResolver(Mustache.compiler().withLoader(loader)).apply {
setPrefix(prefix)
setSuffix(suffix)
}
}
profile("foo") {
bean()
}
}
class Foo
```
bean() 等价于 在UserHandler的类上面加上 @Component 或者在xml 里配置一个bean
bean() 可以接受配置参数:
```
name: String? = null,
scope: Scope? = null,
isLazyInit: Boolean? = null,
isPrimary: Boolean? = null,
autowireMode: Autowire = Autowire.NO,
isAutowireCandidate: Boolean? = null,
crossinline function: BeanDefinitionContext.() -> T
```
bean {
Routes(ref(), ref())
}
ref() 是让spring自动初始化Routes构造器注入的bean
Router 的 DSL
fun router() = router {
accept(TEXT_HTML).nest {
GET("/") { ok().render("index") }
GET("/sse") { ok().render("sse") }
GET("/users", userHandler::findAllView)
}
"/api".nest {
accept(APPLICATION_JSON).nest {
GET("/users", userHandler::findAll)
}
accept(TEXT_EVENT_STREAM).nest {
GET("/users", userHandler::stream)
}
}
resources("/**", ClassPathResource("static/"))
}.filter { request, next ->
next.handle(request).flatMap {
if (it is RenderingResponse) RenderingResponse.from(it).modelAttributes(attributes(request.locale(), messageSource)).build() else it.toMono()
}
}
可以更方便的创建route