版本说明
2.2.6.RELEASE
快速入门
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
主类
@RestController
@SpringBootApplication
public class WebfluxBilibiliApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxBilibiliApplication.class, args);
}
@RequestMapping("/greeting")
public Mono<String> greeting(){
return Mono.just("hello world");
}
}
测试
$ curl localhost:8080/greeting
hello world
Server-Sent Event(SSE)
SSE vs WebSocket
- SSE 是单向的。----
股票交易市场
- WebSocket是双向的。 双方可以互相发送数据,直到任意一方关闭链接。 —
聊天工具
示例1
定义接口
@RequestMapping("/sse")
public Flux<String> sse(){
//每一秒中推送一条数据
return Flux.interval(Duration.ofMillis(1000)).map(val -> "->" + val);
}
测试
$ curl localhost:8080/sse
->0->1->2->3->4->5->6->7->8->9->10->11->12->13 ###ctrl+c 终止命令
示例2
修改上述接口
指定mediaType= "text/event-stream"
@RequestMapping(value = "/sse",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> sse(){
//每一秒中推送一条数据
return Flux.interval(Duration.ofMillis(1000)).map(val -> "->" + val);
}
测试
$ curl localhost:8080/sse
data:->0
data:->1
data:->2
sse属性
https://docs.kilvn.com/JavaScript-Standards-Reference-Guide/htmlapi/eventsource.html
id
: 字符串,默认为空event
: 事件,默认为空data
: 传递的数据,为字符串或任意对象retry
:
定义接口
@RequestMapping(value = "/sse")
public Flux<ServerSentEvent<String>> sse(){
return Flux.interval(Duration.ofMillis(1000))
.map(val -> ServerSentEvent.<String>builder()
.id(UUID.randomUUID().toString())
.event("TEST_EVENT")
.data(val.toString())
.build()
);
}
测试
$ curl localhost:8080/sse
id:8e931afa-648d-4d07-8642-07364297cb37
event:TEST_EVENT
data:0
id:1de714ef-9937-467f-a1f9-462b8fd089cb
event:TEST_EVENT
data:1
EventSource
前端(浏览器)通过EventSource
组件来接收服务端推送来的消息。
定义接口
只需在前文中的接口上添加:@CrossOrigin("*")
允许跨域即可。
html
<script type="text/javascript">
if (!!window.EventSource) {
// 建立连接
var source = new EventSource("http://localhost:8080/sse");
/**
0,相当于常量EventSource.CONNECTING,表示连接还未建立,或者连接断线。
1,相当于常量EventSource.OPEN,表示连接已经建立,可以接受数据。
2,相当于常量EventSource.CLOSED,表示连接已断,且不会重连。
**/
console.info(source.readyState);
source.onopen = function(event) {
console.info("open");
};
//监听指定的时间名称
source.addEventListener("TEST_EVENT", function(event) {
var data = event.data;
var origin = event.origin; //服务器端URL的域名部分,即协议、域名和端口。
var lastEventId = event.lastEventId; //数据的编号,由服务器端发送。
console.info(origin+"->"+lastEventId +"->"+data);
}, false);
source.onerror = function(event) {
console.info(error);
};
//source.close(); //close方法用于关闭连接。
}
</script>
执行效果
WebFlux vs SpringMVC
响应式和非阻塞通常来讲也不会使应用运行的更快
。相反,非阻塞方式要求做更多的事情,而且还会稍微增加一些必要的处理时间。也就是说,还可能稍稍变慢一点,what,那为啥还要用它呢?响应式和非阻塞的关键好处是,在使用很少固定数目的线程和较少的内存情况下的扩展能力
。
服务端搭建
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Employee {
private int id;
private String name;
}
private List<Employee> list;
{
list = new ArrayList<>();
IntStream.rangeClosed(1, 100).forEach(index ->
list.add(new Employee(index, "name_" + index))
);
}
private void dosth() {
try {
//
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@RequestMapping(value = "/flux")
public Flux<Employee> findAll() {
LocalDateTime start = LocalDateTime.now();
Flux<Employee> flux = Flux.range(1, 5).map(val ->
{
dosth();
return list.stream().filter(employee -> employee.getId() == val).findFirst().get();
}
);
System.out.println("flux::" + Duration.between(start, LocalDateTime.now()).toMillis() + "ms");
return flux;
}
@RequestMapping(value = "/rest")
public List<Employee> findAll2() {
LocalDateTime start = LocalDateTime.now();
List<Employee> ret = IntStream.range(1, 5).mapToObj(val ->
{
dosth();
return list.stream().filter(employee -> employee.getId() == val).findFirst().get();
}
).collect(Collectors.toList());
System.out.println("rest::" + Duration.between(start, LocalDateTime.now()).toMillis() + "ms");
return ret;
}
浏览器请求
浏览器结果
后台日志
flux::4ms
rest::4040ms
使用webflux的前台响应时间反而比rest更耗时,
但是后台响应处理更为迅速。
WebClient
WebClient是基于Reactor实现的,非阻塞的。而RestTemplate
是阻塞的。
快速使用
Flux<WebfluxBilibiliApplication.Employee> flux = WebClient.create("http://localhost:8080/flux")
.get()
.retrieve()
.bodyToFlux(WebfluxBilibiliApplication.Employee.class);
Post传参
WebfluxBilibiliApplication.Employee employee = new WebfluxBilibiliApplication.Employee(999, "jhs");
WebClient.create("http://localhost:8080/save")
.post()
.body(Mono.just(employee), WebfluxBilibiliApplication.Employee.class)
.retrieve()
.bodyToFlux(WebfluxBilibiliApplication.Employee.class);
Form Data
MultiValueMap<String, String> formData = ... ;
Mono<Void> result = client.post()
.uri("/path", id)
.bodyValue(formData)
.retrieve()
.bodyToMono(Void.class);
///方法2
import static org.springframework.web.reactive.function.BodyInserters.*;
Mono<Void> result = client.post()
.uri("/path", id)
.body(fromFormData("k1", "v1").with("k2", "v2"))
.retrieve()
.bodyToMono(Void.class);
Multipart Data
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("fieldPart", "fieldValue");
builder.part("filePart1", new FileSystemResource("...logo.png"));
builder.part("jsonPart", new Person("Jason"));
builder.part("myPart", part); // Part from a server request
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
/
MultipartBodyBuilder builder = ...;
Mono<Void> result = client.post()
.uri("/path", id)
.body(builder.build())
.retrieve()
.bodyToMono(Void.class);
Client Filters
/1
WebClient client = WebClient.builder()
.filter((request, next) -> {
ClientRequest filtered = ClientRequest.from(request)
.header("foo", "bar")
.build();
return next.exchange(filtered);
})
.build();
/2
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
WebClient client = WebClient.builder()
.filter(basicAuthentication("user", "password"))
.build();
///3
WebClient client = WebClient.builder()
.filter((request, next) -> {
Optional<Object> usr = request.attribute("myAttribute");
// ...
})
.build();
client.get().uri("https://example.org/")
.attribute("myAttribute", "...")
.retrieve()
.bodyToMono(Void.class);
}
添加filter
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();