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的库和框架的兼容性可能不够理想。
适用范围:
- 需要处理高并发、实时性要求高的网络应用程序;
- 适合微服务架构和需要异步通信的项目;
- 处理大规模数据流和事件驱动的应用场景。