SpringWebFlux响应式非阻塞高并发框架

本文详细介绍了SpringWebFlux的底层架构,如何利用其快速处理高并发请求,以及与传统WebMVC的区别。重点讲解了DispatcherHandler的作用、Web程序开发中的注解使用和流式响应机制,以及ServerSentEvent(SSE)在实时数据推送中的应用。
摘要由CSDN通过智能技术生成

底层架构

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 配置中发现它需要的委托组件。它在应用程序上下文中检测以下内容:

  1. HandlerMapping – 将请求映射到controller中的handler
  2. HandlerAdapter – 适配器
  3. 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);
						}));
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值