底层架构
SpringWebFlux底层基于:Netty+Reactor,在处理高并发的大量请求时,相比于WebMVC,具有更快的速度,和更少的资源消耗
服务器程序
使用WebFlux提供的api可以写一个简单的Web容器处理程序
public static void main(String[] args) {
//创建一个netty服务器
DisposableServer server = HttpServer.create()
.host("localhost")
.port(15100)
.handle(new ReactorHttpHandlerAdapter(new HttpHandler() {
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
System.out.println(Thread.currentThread().getName());
return response.writeWith(Mono.just(response.bufferFactory().wrap("hello".getBytes())));
}
}))
.bindNow();
try {
System.in.read();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
底层是基于Netty进行网络通信,而Netty采用多线程协作的非阻塞的方式来处理请求,所以Netty的吞吐量比Tomcat大得多
这段代码的核心handle部分,这和Netty中的childHandler等效,都是由worker线程去执行
DispatcherHandler
中心处理器,负责在项目启动之后,接收netty容器封装好的exchange对象,并调度包括各种处理器在内的Bean对象,进行请求的处理
和MVC的DispatcherServlet的角色一样
Web程序开发
注解及其使用方式和SpringMVC中的一样
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public Flux<String> login(@RequestBody User user) {
return Flux.just("欢迎!" + user.getUsername(), user.getPassword());
}
}
流式响应
WebFlux底层基于Rractor的响应式流,所以可以直接给客户端响应元素流,特别是在传输一些大型文件,或者是生成式的数据时,就可以一边生成,一边发送给客户端展示,将大大提升用户体验
响应体类型是text/event-stream
@GetMapping(value = "/chat",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chat(){
return Flux.just("你好,","请问","有什么","可以","帮到","你的?")
.log()
.delayElements(Duration.ofSeconds(1));
}
SSE
Server Send Event:服务端推送事件
通过异步数据流,就能很方便的向客户端推送数据流,客户端将以stream的方式接收,和我们平时下载文件的原理是一样
public final class ServerSentEvent<T> {
@Nullable
private final String id;
@Nullable
private final String event;
@Nullable
private final Duration retry;
@Nullable
private final String comment;
@Nullable
private final T data;
private ServerSentEvent(@Nullable String id, @Nullable String event, @Nullable Duration retry,
@Nullable String comment, @Nullable T data) {
this.id = id;
this.event = event;
this.retry = retry;
this.comment = comment;
this.data = data;
}
/**
* Return the {@code id} field of this event, if available.
*/
@Nullable
public String id() {
return this.id;
}
/**
* Return the {@code event} field of this event, if available.
*/
@Nullable
public String event() {
return this.event;
}
/**
* Return the {@code retry} field of this event, if available.
*/
@Nullable
public Duration retry() {
return this.retry;
}
/**
* Return the comment of this event, if available.
*/
@Nullable
public String comment() {
return this.comment;
}
/**
* Return the {@code data} field of this event, if available.
*/
@Nullable
public T data() {
return this.data;
}
@Override
public String toString() {
return ("ServerSentEvent [id = '" + this.id + "\', event='" + this.event + "\', retry=" +
this.retry + ", comment='" + this.comment + "', data=" + this.data + ']');
}
/**
* Return a builder for a {@code SseEvent}.
* @param <T> the type of data that this event contains
* @return the builder
*/
public static <T> Builder<T> builder() {
return new BuilderImpl<>();
}
/**
* Return a builder for a {@code SseEvent}, populated with the given {@linkplain #data() data}.
* @param <T> the type of data that this event contains
* @return the builder
*/
public static <T> Builder<T> builder(T data) {
return new BuilderImpl<>(data);
}
/**
* A mutable builder for a {@code SseEvent}.
*
* @param <T> the type of data that this event contains
*/
public interface Builder<T> {
/**
* Set the value of the {@code id} field.
* @param id the value of the id field
* @return {@code this} builder
*/
Builder<T> id(String id);
/**
* Set the value of the {@code event} field.
* @param event the value of the event field
* @return {@code this} builder
*/
Builder<T> event(String event);
/**
* Set the value of the {@code retry} field.
* @param retry the value of the retry field
* @return {@code this} builder
*/
Builder<T> retry(Duration retry);
/**
* Set SSE comment. If a multi-line comment is provided, it will be turned into multiple
* SSE comment lines as defined in Server-Sent Events W3C recommendation.
* @param comment the comment to set
* @return {@code this} builder
*/
Builder<T> comment(String comment);
/**
* Set the value of the {@code data} field. If the {@code data} argument is a multi-line
* {@code String}, it will be turned into multiple {@code data} field lines as defined
* in the Server-Sent Events W3C recommendation. If {@code data} is not a String, it will
* be {@linkplain org.springframework.http.codec.json.Jackson2JsonEncoder encoded} into JSON.
* @param data the value of the data field
* @return {@code this} builder
*/
Builder<T> data(@Nullable T data);
/**
* Builds the event.
* @return the built event
*/
ServerSentEvent<T> build();
}
private static class BuilderImpl<T> implements Builder<T> {
@Nullable
private String id;
@Nullable
private String event;
@Nullable
private Duration retry;
@Nullable
private String comment;
@Nullable
private T data;
public BuilderImpl() {
}
public BuilderImpl(T data) {
this.data = data;
}
@Override
public Builder<T> id(String id) {
this.id = id;
return this;
}
@Override
public Builder<T> event(String event) {
this.event = event;
return this;
}
@Override
public Builder<T> retry(Duration retry) {
this.retry = retry;
return this;
}
@Override
public Builder<T> comment(String comment) {
this.comment = comment;
return this;
}
@Override
public Builder<T> data(@Nullable T data) {
this.data = data;
return this;
}
@Override
public ServerSentEvent<T> build() {
return new ServerSentEvent<>(this.id, this.event, this.retry, this.comment, this.data);
}
}
}
@GetMapping(value = "/chat",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> chat(){
return Flux.just("你","好","世","界","!!!")
.map(item -> ServerSentEvent.builder(item)
.id(item)
.comment("备注")
.event("事件")
.build());
}
handle()方法
DispathcerHandle的核心方法,对标DispatcherServlet的doDispatch方法
HTTP 请求处理程序控制器的中央调度程序。分派给已注册的处理程序以处理请求,提供方便的映射工具。DispatcherHandler 从 Spring 配置中发现它需要的委托组件。它在应用程序上下文中检测以下内容:
- HandlerMapping – 将请求映射到controller中的handler
- HandlerAdapter – 适配器
- HandlerResultHandler – 用于处理handler返回的结果
DispatcherHandler 还被设计为一个 Spring Bean 本身,并实现 ApplicationContextAware 以访问它运行的上下文。如果 DispatcherHandler 被声明为名为“webHandler”的 bean,则 WebHttpHandlerBuilder.applicationContext(ApplicationContext) 会发现它,它将处理链与 WebFilter、WebExceptionHandler 和其他 Bean 放在一起。DispatcherHandler Bean 声明包含在@EnableWebFlux配置中。
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
//确保路径映射集合不为空
if (this.handlerMappings == null) {
return createNotFoundError();
}
//判断是否跨域
if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return handlePreFlight(exchange);
}
//
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()//从元素流中拿一个元素
.switchIfEmpty(createNotFoundError())//如果元素为空,就抛出异常
.flatMap(handler -> invokeHandler(exchange, handler))//将请求交给对应的handler处理,并得到返回结果
.flatMap(result -> handleResult(exchange, result));
}
invokeHandler
执行处理器方法
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
if (ObjectUtils.nullSafeEquals(exchange.getResponse().getStatusCode(), HttpStatus.FORBIDDEN)) {
return Mono.empty(); // 跨域注入
}
if (this.handlerAdapters != null) {
for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
//遍历处理器映射集合,找到匹配的支持的适配器
if (handlerAdapter.supports(handler)) {
return handlerAdapter.handle(exchange, handler);
}
}
}
//没有找到支持的适配器,报空指针异常
return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}
handleResult
private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
return getResultHandler(result).handleResult(exchange, result)
.checkpoint("Handler " + result.getHandler() + " [DispatcherHandler]")
.onErrorResume(ex ->
result.applyExceptionHandler(ex).flatMap(exResult -> {
String text = "Exception handler " + exResult.getHandler() +
", error=\"" + ex.getMessage() + "\" [DispatcherHandler]";
return getResultHandler(exResult).handleResult(exchange, exResult).checkpoint(text);
}));
}