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