解锁异步响应力量:浅析Spring WebFlux

2024年 3月 6日 109.9k 0

1.WebFlux简介

Spring WebFlux是Spring框架的一个模块,用于构建反应式、异步和事件驱动的应用程序。它提供了一种基于Reactive Streams标准的编程模型,能够处理大量并发请求和高吞吐量,同时具有较低的资源消耗。

传统的Servlet API和Spring MVC是基于同步阻塞式编程模型的,而Spring WebFlux则是基于响应式编程模型的,相比较下有如下优势:

并发处理:

  • Servlet API和Spring MVC:采用同步阻塞IO模型,每个请求都会占用一个线程,如果有大量的长时间IO操作或者并发请求,会导致线程资源耗尽。
  • Spring WebFlux:采用非阻塞IO模型,在IO操作完成前不会阻塞线程,因此能够更高效地处理大量并发请求。

编程模型:

  • Servlet API和Spring MVC:基于Servlet规范,通常使用注解或者配置文件来定义控制器和URL映射。
  • Spring WebFlux:提供了注解式编程和函数式编程两种风格的路由定义方式,使得开发者可以选择更适合自己项目的方式进行开发。

线程模型:

  • Servlet API和Spring MVC:每个请求需要绑定一个线程,线程池负责管理这些线程。
  • Spring WebFlux:基于事件驱动和响应式流,不需要为每个请求分配独立线程,能够更有效地利用系统资源。

应用场景:

  • Servlet API和Spring MVC:更适合传统的同步IO应用,如CRUD操作等。
  • Spring WebFlux:适合处理I/O密集型应用,如服务器推送事件、WebSocket通信以及需要处理大量并发请求的场景。

2.响应式编程

谈到Spring WebFlux就需要着重解释下响应式编程模型,响应式编程是一种面向数据流和变化传播的编程范式。它倡导使用异步数据流来构建事件驱动的、可扩展的应用程序。在响应式编程中,数据和事件以流的形式进行处理,并且整个系统会对这些流式数据进行监听和响应。主要优势如下:

  • 异步和非阻塞:响应式编程采用异步编程模型,能够更高效地利用系统资源,同时避免线程阻塞,提高系统的并发处理能力。
  • 事件驱动:通过订阅数据流和事件,系统能够更灵活地响应外部输入,适合处理实时性要求高的场景。
  • 可扩展性:采用反应式编程模型可以轻松地构建可扩展的系统,因为它强调组件间的解耦和消息传递。
  • 简洁清晰:响应式编程模型通常使用函数式风格,代码更加简洁清晰,易于理解和维护。

举个例子:

假设我们需要实时获取多个城市的天气数据并展示给用户,这就涉及到了异步获取数据和实时推送数据给前端的需求。

在传统的同步编程模型中,如果我们采用每个城市一个线程的方式来获取天气数据,可能会因为网络IO等待而造成大量线程阻塞,浪费系统资源。而且随着城市数量的增多,线程管理和资源利用将变得更加困难。

通过采用响应式编程模型,我们可以将天气数据看作一个反应式流,通过订阅这个数据流,可以实时地获取各个城市的天气信息。当某个城市的天气数据发生变化时,系统能够及时地向订阅者发送新的数据,从而实现实时更新。

另外,由于天气数据的获取和推送是异步非阻塞的,不需要为每个城市的天气数据分配独立的线程,能够更高效地利用系统资源,提高系统的并发处理能力。

3.Spring WebFlux核心组件

Spring WebFlux 框架由以下核心组件组成:

(1)Handler

Handler是Spring WebFlux中用于处理请求的核心组件。它可以是一个函数式的处理器,也可以是一个注解式的控制器(类似于Spring MVC中的Controller)。当接收到一个HTTP请求时,WebFlux会根据路由规则把请求映射到对应的Handler上,然后由Handler来实际处理请求并生成响应。在函数式编程风格中,Handler通常是一个处理器函数,而在注解式编程中,Handler通常是一个带有@RequestMapping注解的控制器方法。

(2)核心控制器DispatcherHandler

核心控制器DispatcherHandler等同于阻塞方式的DispatcherServlet,DispatcherHandler实现ApplicationContextAware,那么必然会调用setApplicationContext方法。

public class DispatcherHandler implements WebHandler, ApplicationContextAware {
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        initStrategies(applicationContext);
    }
}

initStrategies初始化

获取HandlerMapping,HandlerAdapter,HandlerResultHandler的所有实例

protected void initStrategies(ApplicationContext context) {
    //获取HandlerMapping及其子类型的bean
    //HandlerMapping根据请求request获取handler执行链
    Map mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerMapping.class, true, false);
 
    ArrayList mappings = new ArrayList(mappingBeans.values());
    //排序
    AnnotationAwareOrderComparator.sort(mappings);
    this.handlerMappings = Collections.unmodifiableList(mappings);
 
    //获取HandlerAdapter及其子类型的bean
    Map adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerAdapter.class, true, false);
 
    this.handlerAdapters = new ArrayList(adapterBeans.values());
    //排序
    AnnotationAwareOrderComparator.sort(this.handlerAdapters);
 
    //获取HandlerResultHandler及其子类型的bean
    Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerResultHandler.class, true, false);
 
    this.resultHandlers = new ArrayList(beans.values());
    AnnotationAwareOrderComparator.sort(this.resultHandlers);
}

DispatcherHandler的总体流程

  • 通过HandlerMapping(和DispatcherServlet中的HandlerMapping不同)获取到HandlerAdapter放到ServerWebExchange的属性中
  • 获取到HandlerAdapter后触发handle方法,得到HandlerResult
  • 通过HandlerResult,触发handleResult,针对不同的返回类找到不同的HandlerResultHandler如视图渲染ViewResolutionResultHandler、ServerResponseResultHandler、ResponseBodyResultHandler、ResponseEntityResultHandler不同容器有不同的实现,如Reactor,Jetty,Tomcat等。

(3)Router

Router用于定义URL路由规则,将请求映射到对应的Handler上。它负责根据请求的URL路径和HTTP方法找到对应的Handler,并将请求转发给该Handler进行处理。Router可以通过多种方式定义,包括函数式路由定义和注解式路由定义。在函数式编程风格中,我们可以使用Java 8的lambda表达式和RouterFunctions来定义路由规则;而在注解式编程中,我们可以使用类似@RequestMapping和@RestController这样的注解来定义路由规则。

(4)RouterFunction

RouterFunction是用于定义路由规则的主要接口,它可以将HTTP请求映射到对应的处理函数。通过RouterFunction,我们可以根据请求的URL路径和HTTP方法找到对应的Handler,并将请求转发给该Handler进行处理。

@Configuration
public class RouterConfig {

    @Bean
    public RouterFunction helloRouterFunction(HelloHandler helloHandler) {
        return route(GET("/hello"), helloHandler::handleHello);
    }
}

在这段示例代码中,我们首先通过@Configuration注解标记了RouterConfig类,使其成为Spring的配置类。然后,我们使用@Bean注解定义了一个名为helloRouterFunction的Bean,它返回一个RouterFunction实例。

这里的route(GET("/hello"), helloHandler::handleHello)表示当收到GET请求且路径为"/hello"时,将请求转发给helloHandler的handleHello方法进行处理。这里使用了静态导入,使得可以直接调用GET方法,简化路由规则的定义。

@Component
public class HelloHandler {
    public Mono handleHello(ServerRequest request) {
        return ServerResponse.ok().bodyValue("Hello, WebFlux!");
    }
}

在HelloHandler类中,我们通过@Component注解标记为Spring管理的组件,并编写了handleHello方法来处理请求。该方法返回一个Mono,在本例中是将"Hello, WebFlux!"字符串作为响应体返回给客户端。

(5)WebHandler

WebHandler是处理请求和生成响应的抽象接口,它充当了请求和响应对象之间的桥梁。在WebFlux中,WebHandler接口定义了handle方法,用于处理ServerHttpRequest和ServerHttpResponse对象。它提供了一种统一的处理机制,使得不同类型的Handler可以与请求-响应生命周期进行交互。

4.选WebFlux还是Spring MVC?

首先你需要明确一点就是:WebFlux不是Spring MVC的替代方案,WebFlux并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性 。虽然WebFlux也可以被运行在Servlet容器上(需是Servlet 3.1+以上的容器),但是WebFlux

主要还是应用在异步非阻塞编程模型,而Spring MVC是同步阻塞的,如果你目前在Spring MVC框架中大量使用非同步方案,那么,WebFlux才是你想要的,否则,使用Spring MVC才是你的首选。

在微服务架构中,Spring MVC和WebFlux可以混合使用,比如已经提到的,对于那些IO密集型服务(如网关),我们就可以使用WebFlux来实现。

从上图中,可以一眼看出Spring MVC和Spring WebFlux的相同点和不同点:

相同点:

  • 都可以使用Spring MVC注解,如@Controller,方便我们在两个Web框架中自由转换;
  • 均可以使用Tomcat,Jetty,Undertow Servlet容器(Servlet 3.1+);
  • ...

不同点:

  • Spring MVC因为是使用的同步阻塞式,更方便开发人员编写功能代码,Debug测试等,一般来说,如果Spring MVC能够满足的场景,就尽量不要用WebFlux;
  • WebFlux默认情况下使用Netty作为服务器;
  • WebFlux不支持MySql;

5.WebFlux与Spring框架集成

pom.xml:


    4.0.0
    com.example
    webflux-demo
    1.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.3
    
    
        
            org.springframework.boot
            spring-boot-starter-webflux
        
    

在上述配置中,我们使用了Spring Boot的起步依赖spring-boot-starter-webflux来引入WebFlux相关的依赖。接下来,我们将创建一个简单的Controller并将其拆分到单独的类中。

首先,创建一个名为 HelloController 的类:

import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Mono;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

public class HelloController {
    public RouterFunction route() {
        return route(GET("/hello"), this::handleHello);
    }

    private Mono handleHello(ServerRequest request) {
        return ServerResponse.ok().bodyValue("Hello, WebFlux!");
    }
}

然后在主应用程序中注入HelloController:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

@SpringBootApplication
public class WebFluxIntegrationDemo {

    public static void main(String[] args) {
        SpringApplication.run(WebFluxIntegrationDemo.class, args);
    }

    @Bean
    public RouterFunction monoRouterFunction(HelloController helloController) {
        return helloController.route();
    }
}

在这个示例中,我们通过@Bean注解将HelloController注入到主应用程序中,并且调用其route方法来定义路由规则。

6.总结

关于pring WebFlux 很多很多,本文主要总结如下几点

优点:

  • 非阻塞和异步:能够处理大量并发请求,提高系统的吞吐量。
  • 响应式编程模型:支持响应式数据流处理,适合处理实时、事件驱动的应用场景。
  • 弹性和扩展性:能够轻松地与反应式数据流、异步数据库等集成,方便构建弹性和可扩展的系统。

缺点:

  • 学习曲线较陡峭:相对于传统的Servlet编程模型,对于开发人员需要一定的学习成本。
  • 对某些传统的基于Servlet的库和框架的兼容性可能不够理想。

适用范围:

  • 需要处理高并发、实时性要求高的网络应用程序;
  • 适合微服务架构和需要异步通信的项目;
  • 处理大规模数据流和事件驱动的应用场景。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论