二、响应式编程WebFlux之服务器推送

(一)背景介绍
  我们可能会遇到一些需要网页与服务器端保持连接(起码看上去是保持连接)的需求,比如类似微信网页版的聊天类应用,比如需要频繁更新页面数据的监控系统页面或股票看盘页面。我们通常采用如下几种技术:

  • 短轮询:利用ajax定期向服务器请求,无论数据是否更新立马返回数据,高并发情况下可能会对服务器和带宽造成压力;
  • 长轮询:利用comet不断向服务器发起请求,服务器将请求暂时挂起,直到有新的数据的时候才返回,相对短轮询减少了请求次数;
  • SSE:服务端推送(Server Send
    Event),在客户端发起一次请求后会保持该连接,服务器端基于该连接持续向客户端发送数据,从HTML5开始加入。
  • Websocket:这是也是一种保持连接的技术,并且是双向的,从HTML5开始加入,并非完全基于HTTP,适合于频繁和较大流量的双向通讯场景。

  既然响应式编程是一种基于数据流的编程范式,自然在服务器推送方面得心应手,要求:Endpoint /times,每秒推送一次时间。

(二)Demo演示
  1.创建统一存放处理时间的Handler类

/**
 * 引入静态方法ok()
 */
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

/**
 * @author 咸鱼
 * @date 2018/6/28 16:38
 */
@Component
public class TimeHandler {
    /**
     * 在响应式编程中,请求和响应不再是HttpServletRequest和HttpServletResponse,而是变成了ServerRequest和ServerResponse
     * @param request 请求
     * @param response 响应
     * @return Mono和Flux主要用于响应式编程的“异步”数据流处理,不像我们以前直接返回String/List<T>....,而是直接包装成
     *         Mono和Flux对象。见文知意,Mono主要用于返回单个数据,Flux用于返回多个数据。比如我们要根据id查询某个User,
     *         那返回的肯定就是一个User,那么需要包装成Mono<User>;若我们需要获取所有User,这是一个集合,我们需要包装成
     *         Flux<User>。这里的单个数据并不是指一个数据,而是指封装好的一个对象;多个数据就是多个对象
     *         ServerRequest request, ServerResponse response
     */
    public Mono<ServerResponse> sendTimePerSec(ServerRequest request){
            //MediaType.TEXT_EVENT_STREAM表示Content-Type为text/event-stream,即SSE;
            return ok().contentType(MediaType.TEXT_EVENT_STREAM)
                    //利用interval生成每秒一个数据的流
                    .body(Flux.interval(Duration.ofSeconds(1))
                            /*
                             * map():可以将数据元素进行转换/映射,得到一个新元素
                             * map接受一个“Function的函数式接口”为参数,这个函数式的作用是定义转换操作的策略。
                             * Function函数式接口:接受一个输入参数,返回一个结果。参数与返回值的类型可以不同,
                             *                     说白了就是一个lambda表达式,“->”前面的是参数,“->”后面
                             *                     的是方法体,若可以直接返回,则省略return。
                             */
                            .map(l -> new SimpleDateFormat("HH-mm-ss").format(new Date()))
                            ,String.class);
    }
}

  2.在Spring容器配置RouterFunction

/**
 * 引入静态路由“router”
 */
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

/**
 * @author 咸鱼
 * @date 2018/6/28 16:55
 */
@Configuration
public class RouterConfig {
    private TimeHandler timeHandler;

    @Autowired
    public RouterConfig(TimeHandler timeHandler) {
        this.timeHandler = timeHandler;
    }

    /**
     * 注意方法名不能和类名重复
     */
    @Bean
    public RouterFunction<ServerResponse> timeRouter(){
        /**
         * 路由的写法也是固定的,第一个参数:路径,第二个参数:方法
         *     这里的转换成lambda表达式是:request -> timeHandler.getTime(request),隐藏了一个ServerRequest参数,所以
         * TimeHandler类中的方法都必须有一个参数“ServerRequest”
         */
        return route(GET("/times"), timeHandler::sendTimePerSec);
    }
}

  3.效果
这里写图片描述

猜你喜欢

转载自blog.csdn.net/panchang199266/article/details/80846708