Spring WebFlux 响应式编程学习笔记(一)

各位Javaer们,你们都在用SpringMVC吧?当咱们不亦乐乎的用着SpringMVC框架的时候,Spring5.x又悄(da)无(zhang)声(qi)息(gu)的推出了Spring WebFlux。web? 不是已经有SpringMVC这么好用的东西了么,为啥又冒出个WebFlux? 这玩意儿是什么鬼?html

Spring WebFlux特性

异步非阻塞

SpringMVC是同步阻塞的IO模型,资源浪费相对来讲比较严重,当咱们在处理一个比较耗时的任务时,例如:上传一个比较大的文件,首先,服务器的线程一直在等待接收文件,在这期间它就像个傻子同样等在那儿(放学别走),什么都干不了,好不容易等到文件来了而且接收完毕,咱们又要将文件写入磁盘,在这写入的过程当中,这根线程又再次懵bi了,又要等到文件写完才能去干其它的事情。这一前一后的等待,不浪费资源么?java

没错,Spring WebFlux就是来解决这问题的,Spring WebFlux能够作到异步非阻塞。仍是上面那上传文件的例子,Spring WebFlux是这样作的:线程发现文件还没准备好,就先去作其它事情,当文件准备好以后,通知这根线程来处理,当接收完毕写入磁盘的时候(根据具体状况选择是否作异步非阻塞),写入完毕后通知这根线程再来处理(异步非阻塞状况下)。这个用脚趾头都能看出相对SpringMVC而言,能够节省系统资源。666啊,有木有!react

响应式(reactive)函数编程

若是你以为java8的lambda写起来很爽,那么,你会再次喜欢上Spring WebFlux,由于它支持函数式编程,得益于对于reactive-stream的支持(经过reactor框架来实现的),喜欢java8 stream的又有福了。为何要函数式编程? 这个别问我,我也不知道,或许是由于bi格高吧,哈哈,开玩笑啦。web

再也不拘束于Servlet容器

之前,咱们的应用都运行于Servlet容器之中,例如咱们你们最为熟悉的Tomcat, Jetty...等等。而如今Spring WebFlux不只能运行于传统的Servlet容器中(前提是容器要支持Servlet3.1,由于非阻塞IO是使用了Servlet3.1的特性),还能运行在支持NIO的Netty和Undertow中。spring

因此,看完Spring WebFlux的新特性以后,心里五味杂陈的我,只能用一个表情来形容:shell

可是学习仍是要学的,毕竟Spring推出的......编程

Spring WebFlux是随Spring 5推出的响应式Web框架。创建在异步非阻塞的IO框架之上的一个新的,其基本的架构以下:
api

Spring提供了完整的支持响应式的服务端技术栈。spring-mvc

如上图所示,左侧为基于spring-webmvc的技术栈,右侧为基于spring-webflux的技术栈,能够看到SpringMVC技术栈给予Serverlet容器,如Tomcat容器,SpringWebFlux基于HTTP/Reactive Stream.服务器

WebFlux 依赖构建

依赖于SpringBoot的强大,咱们只要在配置文件添加依赖便可。

Gradle 依赖

compile('org.springframework.boot:spring-boot-starter-webflux')

或者Maven构建的依赖于

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

SpringMVC注解方式实现

Spring团队在开发WebFlux上尽可能和SpringMVC靠拢,所以咱们能够直接使用一个简单的SpringMVC项目有改形成Spring WebFlux的项目,具体以下。

改造 Spring MVC

下面是咱们再熟悉不过的接口应用,访问http://localhost:8080/mono 便可看到返回了一个字符串

@GetMapping("/mono")
    public String baseApi() {
        return "Hello,Reactive Program";
    }

改造后的内容以下:

@GetMapping("/mono")
    public Mono<String> baseApi() {                  //1
        return Mono.just("Hello,Reactive Program");   //2
    }

主要有两处改造

  • 1 返回的再也不是简单的对象,而是使用的是Mono封装的单个文档信息(返回集合使用Flux )

  • 2 返回的时候咱们须要构造一个Mono类型的数据,所以使用Mono.just(T t) 构造

能够看大,执行的结果以下:

$ curl -X GET http://localhost:8080/mono
Hello,Reactive Program

效果和SpringMVC 并没有区别,一样的咱们返回集合列表查看效果

@GetMapping("/flux")
    public Flux<String> getFluxString() {
        String[] dataSet = new String[]{"This is 1", "This is 2", "This is 3", "This is 4"};
        return Flux.fromIterable(Arrays.asList(dataSet));
    }

分析过程

结果也和预期一致,那么不只要思考了,一样和SpringMVC达到一致的效果,为何咱们要用WebFlux?

首先看着这二者并没有区别,其实实际上和文章首页的架构图示同样,其底层核心的变了,实现接口,并再是基于Servlet,而是基于Http/Reactive Stream ,咱们在接口方法添加参数

@GetMapping("/flux")
    public Flux<String> getFluxString(HttpServerRequest request) {....}

此时访问flux接口,会报错

java.lang.IllegalStateException: No primary or default constructor found for interface org.springframework.http.HttpRequest

意思是非法的状态异常,没有org.springframework.http.HttpRequest的构造参数被发现,这说明WebFlux的实现已经再也不是Serverlet了

实现Server Send Event

下面我是实现SSE(服务器推送),注意这里和Socket有所区别,Socket是双向通讯,这是单向通讯,由服务器向客户端推送消息

@GetMapping(value = "/sse/object", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<Book> sseBook() {
        return Flux.interval(Duration.ofSeconds(1))
            .map(
                 second ->
                    new Book()
                    .setId(Stirng.valueOf(second))
                    .setName("深刻浅出Flux响应式Web编程" + second)
                    .setPrice("12")
                    ).take(5);
    }

模型Book须要lombok支持,没有的话,请手动完成set、get方法,并在Set方法尾部return this

@Data
@Accessors(chain = true)
public class Book {
    private String id;
    private String name;
    private String price;
    private Date createTime = new Date();
}

首先说明一下produces = MediaType.TEXT_EVENT_STREAM_VALUE 表示这是一个事件流,返回的是Flux类型,推送的间隔为1s,最后take(times)表示推送的次数,没有take表示无限流,times表示推送的次数,咱们在shell中尝试调用下,看看效果

$ curl -X GET http://localhost:8080/sse/object
data:{"id":"0","name":"Flux响应式Web编程0","price":"12","createTime":"2018-09-09T12:46:10.445+0000"}

data:{"id":"1","name":"Flux响应式Web编程1","price":"12","createTime":"2018-09-09T12:46:11.444+0000"}

data:{"id":"2","name":"Flux响应式Web编程2","price":"12","createTime":"2018-09-09T12:46:12.444+0000"}

data:{"id":"3","name":"Flux响应式Web编程3","price":"12","createTime":"2018-09-09T12:46:13.445+0000"}

data:{"id":"4","name":"Flux响应式Web编程4","price":"12","createTime":"2018-09-09T12:46:14.444+0000"}

须要注意的是,在建立时间上,是每一个1s钟由服务器推送过来的,这是和SpringMVC有着巨大的区别.

RouterFunctin 实现方式

Spring团队在实现WebFlux的有了另外的实现方式,利用RouterFuntion & HandleFunction,这里不作过多的赘述,这种方式的效果和上述效果一致,能够对比学习,代码以下:

向Spring容器中注入RouterFunctionBean对象

@Configuration
public class RouteConfig {

    @Bean
    public RouterFunction<ServerResponse> timeRoute(){
       return route(GET("/time"),TimeHandle::getTime)
               .andRoute(GET("/sse"),TimeHandle::sendTimeWithSSE);
    }
}

具体逻辑实现

public class TimeHandle {

    private static SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");

    public static Mono<ServerResponse>  getTime(ServerRequest serverRequest){
        return ok().contentType(MediaType.APPLICATION_JSON_UTF8).body(Mono.just(simpleDateFormat.format(new Date())),String.class);
    }

    // 实现时间SSE推送注意MediaType类型
    public static Mono<ServerResponse> sendTimeWithSSE(ServerRequest serverRequest){
        return  ok().contentType(MediaType.TEXT_EVENT_STREAM).body(
                Flux.interval(Duration.ofSeconds(1)).map(value -> simpleDateFormat.format(new Date()))
                ,String.class);
    }
}

总体来讲仍是比较简单的,请继续关注后期的WebFlux的学习过程~

参考文章

相关文章
相关标签/搜索