文章目录
- 前言
- 第13章 SpringBoot整合WebFlux
- 13.1 响应式编程与Reactor
- 13.1.1 命令式与响应式
- 13.1.2 异步非阻塞
- 13.1.3 观察者模式
- 13.1.4 响应性
- 13.1.5 响应式流
- 13.1.6 背压
- 13.1.7 Reactor
- 13.1.7.1 Publisher
- 13.1.7.2 Subscriber
- 13.1.7.3 Subscription
- 13.1.7.4 Processor
- 13.1.7.5 Flux
- 13.1.7.6 Mono
- 13.1.7.7 Scheduler
- 13.2 SpringBoot整合WebFlux示例项目
- 13.2.1 WebMvc的开发风格
- 13.2.2 过渡到WebFlux
- 13.2.3 WebFlux的函数式开发
- 12.2.3.1 Controller转Handler
- 12.2.3.2 RequestMapping转Router
- 13.2.4 WebMvc和WebFlux的对比
- 13.3 WebFlux的自动装配
- 13.3.1 ReactiveWebServerFactoryAutoConfiguration
- 13.3.2 WebFluxAutoConfiguration
- 13.3.3 WebFluxConfig
- 13.3.3.1 静态资源映射
- 13.3.3.2 视图解析器
- 13.3.4 EnableWebFluxConfiguration
- 13.3.5 WebFluxConfigurationSupport
- 13.3.5.1 DispatcherHandler
- 13.3.5.2 WebExceptionHandler
- 13.3.5.3 RequestMappingHandlerMapping、RequestMappingHandlerAdapter
- 13.3.5.4 RouterFunctionMapping
- 13.3.5.5 HandlerFunctionAdapter
- 13.3.5.6 ResultHandler
前言
SpringFramework 5.x中对于Web场景的开发提供了两套实现方案:WebMvc与WebFlux。
SpringBoot整合WebMvc基于Servlet,本质上是阻塞的,每个连接都会占用一个线程,因此基于Servlet的阻塞式Web框架在面对海量请求时,性能上没有优势。
为了解决该问题,SpringFramework 5.0版本后引入了WebMvc的孪生兄弟WebFlux,它是一个异步非阻塞式Web框架。
第13章 SpringBoot整合WebFlux
13.1 响应式编程与Reactor
13.1.1 命令式与响应式
- 命令式编程
在基于WebMvc的项目开发中,通过编写Controller前端控制器,注入Service业务逻辑类进行处理,Service中包含与数据库的交互、与中间件的通信等,这种编码风格就是命令式编程。
使用命令式编程的代码,就像是一组前后紧密联系的任务,有明确的先后执行顺序,后面的任务通常需要依赖前面的任务生成的结果才能正确执行。
命令式编程的特点是串行、阻塞。
- 响应式编程
响应式编程不再将这些紧密联系的任务看作一个整体,而是将其拆分为一个个可以并行执行的工作任务,这些任务之间互不干扰。
每个工作任务都可以接收特定的数据,并在处理完成后传递给下一个任务,同时继续处理下一组数据。
在响应式编程中,每个任务不会主动获取数据,而是被动地等待数据提供方给它提供数据,即主张数据以订阅的方式推送(被动接收),而不是以请求的方式拉取(主动获取)。
13.1.2 异步非阻塞
使用一个非常经典的故事来解释异步非阻塞。
假设有一个老张烧水的场景,老张有两把烧水壶,分别是没有哨的普通水壶以及壶盖上带哨的响水壶。烧水的场景包含以下4种:
- 同步阻塞式:使用普通水壶烧水,由于不清楚水烧开的时间,因此需要老张在水壶旁观察,等到水壶冒热气,壶里的水沸腾,老张将水壶离火,烧水结束。在该场景中,由于老张在烧水期间无法完成其他工作,只能等待水烧开,烧水占据了老张的注意力和时间,构成同步阻塞。
- 同步非阻塞:经过上一次烧水后,老张发现烧水太浪费自己的时间,于是下一次烧水时老张选择同时打游戏,每隔一小段时间就去看一下水壶里的水是否烧开,如果水还没有烧开就继续打游戏,水烧开则将水壶离火,烧水结束。在该场景中,老张没有一直盯着水壶,但还是会间歇性消耗精力,只不过在整个烧水的过程中,老张没有一直被水壶占用全部精力和时间,构成同步非阻塞。
- 异步阻塞式:间歇性观察水壶仍然不是最佳选择,老张选择使用响水壶烧水,但由于第一次使用响水壶烧水,老张不确定水壶上的哨是否好用,于是他像第一次烧水那样在水壶旁观察,等到水壶冒热气,同时哨声响起,老张将水壶离火,烧水结束。在该场景中老张不再主动关心水壶的状态,但精力和时间仍然被水壶占用,构成异步阻塞。
- 异步非阻塞:烧完水后老张发现自己很傻,因为哨声响起就意味着水已烧开,无须自己消耗精力和时间,于是后续烧水时,老张都是准备好后直接去打游戏,等到水壶哨声响起,再将水壶离火,烧水结束。在最终的场景中,老张不再主动关心水壶状态,也不需要间歇性检查水壶内水的状态,而只需要在水壶的哨声响起时处理水壶离火的任务,此场景就是异步非阻塞。
13.1.3 观察者模式
观察者模式也被称为“发布——订阅者模式”或“监听器模式”。当一个对象被修改/做出某些反应/发布一个信息时,会自动通知依赖它的对象(订阅者)。
观察者模式的三大核心是观察者、被观察的主题和订阅者。观察者(Observer)需要绑定订阅者(Subscriber),并且要观察指定的主题(Subject)。
13.1.4 响应性
如上图,在Excel表格中,A2单元格的值是通过公式=A0+A1
来定义的,因此最终得到的值是2。如果试着更改A0或A1单元格的值,A2单元格的值也会自动更新。这就是响应性的体现。
- A2单元格像是在“观察”着A0和A1单元格中输入的值,当A0或A1单元格中输入的值发生变化时,A2单元格的值也会随之变化,这本身就是观察者模式的体现。由此引出响应式编程的第一个关键概念:变化传递。即A0或A1单元格中输入的值发生变化时,这些变化的值会传递到A2单元格中。
- 在实际使用中,每修改一次A0或A1单元格的值,A2单元格的值就会随之变化,如果将这组变化的内容全部列举,可以形成一组单元格内容的变化事件记录。由此可以引出响应式编程的第二个关键概念:数据流。事件源的每一次变化连起来就是一个事件流。
- 在上面的Excel示例中,仅仅通过一个公式就将绑定了A2单元格和A0、A1单元格的关系。由此可以引出响应式编程的第三个关键概念:声明式。不需要编写命令式代码,仅靠声明两者之间的关系就可以形成双向绑定。
简单总结,响应式编程的三个关键点是变化传递、数据流和声明式。
13.1.5 响应式流
响应式流有别于Java8中的Stream。普通的Stream是同步阻塞的,在高并发场景下不能有效缓解压力大的问题,而响应式流是异步非阻塞的。
普通的Stream的一个关键特性是,一旦有了消费型方法,它就会将这个流中的所有数据处理完毕,如果这期间的数据量很大,Stream就无法对海量数据进行妥善处理;而响应式流可以通过背压对海量数据进行流量控制,以确保数据的接收速度和处理在合理范围内。
简单总结,响应式流的关键点是异步非阻塞和数据流速控制。
13.1.6 背压
**背压是控制数据流速的关键手段。**下面以一个模拟场景来解释:
假设你在一个知名手机生产大厂工作,你的职位是生产流水线上的一名普通工人,你的工作是负责流水线上的一个关键环节。该环节需要的加工时间比较长,而恰好近期与你共同负责相同工作的同事都请假了,剩下你单枪匹马仍然战斗在生产一线。
与此同时,负责你上游工作的同事似乎并不清楚你负责环节的生产现状,而且由于上司的激励政策,上游同事的生产效率非常高,导致你的待加工区积压了非常多半成品,但由于你负责的工序耗时长,积压的半成品过多无法及时处理,于是你不得不向上游同事反馈:你们做慢点,我的工作吞吐量有限。上游同事了解你的现状后改变了半成品处理策略,他们将处理好的半成品不直接传递给你,而暂时由上游同事保管,等你向他们反馈积压的半成品处理完毕后,再继续传递新的半成品。
由此可以体现背压的第一个策略:数据提供方将数据暂存,不传递给下游消费者。
一段时间之后,领导发现你的业绩非常好,于是你升职加薪,以经销商的身份销售该款手机。手机一上市就得到广大消费者的关注,你的店铺生意非常好。正当你的生意做得风生水起时,这批手机在售卖后的一段时间后传出硬件问题,市面销量急剧下降,作为经销商,你自然也不想再销售该款手机,于是你向厂商反映:请不要再提供该款手机。厂商也非常无奈,手机还在正常生产,但经销商都不再提货,于是只好将这部分成品废弃。
由此可以体现出背压的第二个策略:数据提供方将数据丢弃。
简单总结,背压是下游消费者“倒逼”上游数据生产者的数据提供速率,以避免被海量数据压垮,达到两者之间的动态平衡。
13.1.7 Reactor
市面上流行的响应式编程框架包括Reactor与ReactiveX(RxJava)。WebFlux底层使用Reactor提供响应式支撑。
Reactor的核心组件如下:
13.1.7.1 Publisher
源码1:Publisher.java
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
由 源码1 可知,数据生产者Publisher只有一个方法subscribe
,该方法会接收一个订阅者Subscriber,构成“订阅”关系。
注意,subscribe
方法是一个类似于“工厂”的方法,它可以被多次调用,但是每次调用都会创建一个新的订阅关系,且一个订阅关系只能关联一个订阅者。
13.1.7.2 Subscriber
源码2:Subscriber.java
public interface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
由 源码2 可知,数据订阅者Subscriber接口有4个方法,都是以on作为前缀,代表这些方法属于事件形式。
- onSubscribe:当触发订阅时触发;
- onNext:当接收到下一个数据时触发;
- onError:当出现异常时触发;
- onComplete:当生产者的数据都处理完时触发。
13.1.7.3 Subscription
源码3:Subscription.java
public interface Subscription {
public void request(long n);
public void cancel();
}
Subscription可以看作是生产者和订阅者之间的订阅关系,完成了两者之间的交互。由 源码2 可知,订阅关系Subscription接口有2个方法:
- request:用于主动请求数据/拉取数据;
- cancel:用于放弃/停止拉取数据。
13.1.7.4 Processor
源码4:Processor.java
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Processor可以理解为处理器,一般用于数据的中间环节处理(如数据转换、数据过滤等)。由 源码4 可知,Processor接口继承了Subscriber接口和Publisher接口,是生产者和订阅者的合体。
13.1.7.5 Flux
源码5:Flux.java
public abstract class Flux<T> implements CorePublisher<T> {...}
**Flux可以理解为“非阻塞的Stream”。**由 源码5 和上图可知,它实现了Publisher接口,内部定义了很多subscribe
方法。重载这么多个subscribe
方法的目的在于简化操作。
13.1.7.6 Mono
源码6:Mono.java
public abstract class Mono<T> implements CorePublisher<T> {...}
**Flux可以理解为“非阻塞的Optional”。**它也实现了Publisher接口,具备生产数据的能力,内部API和Flux相似。
13.1.7.7 Scheduler
Scheduler可以理解为“线程池”,由Schedulers工具类产生。
响应式线程池有以下几种类型:
- immediate:与主线程一致。
- single:只有一个线程的线程池。
- elastic:弹性线程池,线程池中的线程数量原则上无上限。
- parallel:并性线程池,线程池中的线程数量等于CPU的数量(JDK中的Runtime类可以调用
avaliableProcessors
方法来获取CPU梳理)。
13.2 SpringBoot整合WebFlux示例项目
WebMvc和WebFlux是地位同等的框架,因此SpringBoot为了避免开发者因WebFlux的使用门槛过高而放弃,在WebFlux的使用过程中允许采用WebMvc的开发风格,即使用@Controller+@RequestMapping注解组合实现基于WebFlux的前端控制和响应。
13.2.1 WebMvc的开发风格
- 导入WebFlux依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- 编写主启动类
@SpringBootApplication
public class WebFluxApp {
public static void main(String[] args) {
SpringApplication.run(WebFluxApp.class, args);
}
}
这时就可以启动项目了,启动后控制台输出以下信息:
Starting WebFluxApp on DESKTOP-VTHK7VU with PID 14732 (D:\learnspace\workspace\java_src\springboot-demo\springboot-08-webflux\target\classes started by win10 in D:\learnspace\workspace\java_src\springboot-demo)
No active profile set, falling back to default profiles: default
Netty started on port(s): 8080
Started WebFluxApp in 3.657 seconds (JVM running for 5.44)
可以发现,项目启动的嵌入式Web容器不再是Tomcat,而是Netty。
- 编写Controller类
@RestController
public class UserController {
@GetMapping("/hello")
public String hello() {
return "Hello WebFlux!";
}
@GetMapping("/list")
public List<Integer> list() {
return Arrays.asList(1, 2, 3);
}
}
编写完成后启动项目,使用工具访问 http://127.0.0.1:8080/hello
和 /list
,客户端可以正常接收到服务端的 “Hello WebFlux!” 字符串响应,说明WebFlux可以完美兼容WebMvc的编码方式。
13.2.2 过渡到WebFlux
Reactor中核心数据的封装模型是Mono和Flux,下面使用这两个模型对UserController进行改造。当返回单个对象时,使用Mono封装;当返回一组数据时,使用Flux封装。
@RestController
public class UserController {
@GetMapping("/hello2")
public Mono<String> hello2() {
return Mono.just("Hello WebFlux!");
}
@GetMapping("/list2")
public Flux<Integer> list2() {
return Flux.just(1, 2, 3);
}
}
重新启动项目,并访问 http://127.0.0.1:8080/hello2
和 /list2
,客户端仍然可以正常接收到服务端响应的正常数据,说明Reactor中的数据模型作为响应主体完全可行。
13.2.3 WebFlux的函数式开发
如果要完全丢弃WebMvc的编码风格,则需要使用WebFlux提供的一套全新的函数式API。
在WebMvc中,一个Controller类中标注了@RequestMapping注解的方法在底层会封装为一个个Handler,每个Handler都封装有URL+执行方法以及具体要反射执行的Method对象。这两个核心要素在WebFlux的编码风格中会转换为两个核心组件:HandlerFunction和RouterFunction。
12.2.3.1 Controller转Handler
WebFlux的编码风格不再使用@Controller注解,而是使用原始的@Component注解,且内部的方法不再需要多余的注解,只需要按照WebFlux的规则编写方法。
@Component
public class UserHandler {
public Mono<ServerResponse> hello3(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(Mono.just("Hello Handler"), String.class);
}
public Mono<ServerResponse> list3(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(Flux.just(1, 2, 3), Integer.class);
}
}
12.2.3.2 RequestMapping转Router
由于UserHandler中不再有@Controller注解,因此方法上也不再使用@RequestMapping注解封装URL信息,因此Spring无法感知IOC容器中哪些bean对象具备WebFlux前端控制器的能力,这就需要一个新的组件来定义Bean与具体路由的关系,这个组件就是RouterFunction。
在编写具体的路由规则时,需要一个配置类来编程式创建RouterFunction对象:
@Configuration(proxyBeanMethods = false)
public class UserRouterConfig {
@Autowired
private UserHandler userHandler;
@Bean
public RouterFunction<ServerResponse> helloRouter() {
return RouterFunctions.route(GET("/hello3").and(accept(MediaType.TEXT_PLAIN)), userHandler::hello3)
.andRoute(GET("/list3").and(accept(MediaType.APPLICATION_JSON)), userHandler::list3);
}
}
至此,一个基于WebFlux编码风格的示例项目搭建完毕。启动项目,并访问 http://127.0.0.1:8080/hello3
和 /list3
,客户端仍然可以正常接收到服务端响应的正常数据,说明基于WebFlux编码风格的示例项目搭建成功。
13.2.4 WebMvc和WebFlux的对比
- WebMvc基于原生Servlet,它是命令式编程+声明式映射,编码简单、便于调试;Servlet是阻塞的,更适合与传统关系型数据库等阻塞I/O的组件进行交互。
- WebFlux基于Reactor,它是异步非阻塞的,使用函数式编程,相较于命令式编程更加灵活,可以运行在Netty等纯异步非阻塞的Web容器,以及同时支持同步阻塞和异步非阻塞的基于Servlet 3.1及以上规范的Servlet容器中(如高版本的Tomcat等)。
- WebMvc和WebFlux都可以使用声明式映射注解编程,配置控制器和映射路径。
在实际的项目技术选型中,需要综合考虑项目中使用的技术栈、用户群规模、开发团队能力等多方面因素,决定是采用WebMvc还是WebFlux。
13.3 WebFlux的自动装配
WebFlux的自动装配类似于WebMvc,对应的自动配置类是ReactiveWebServerFactoryAutoConfiguration和WebFluxAutoConfiguration。
13.3.1 ReactiveWebServerFactoryAutoConfiguration
源码7:ReactiveWebServerFactoryAutoConfiguration.java
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ReactiveHttpInputMessage.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ReactiveWebServerFactoryConfiguration.EmbeddedTomcat.class,
ReactiveWebServerFactoryConfiguration.EmbeddedJetty.class,
ReactiveWebServerFactoryConfiguration.EmbeddedUndertow.class,
ReactiveWebServerFactoryConfiguration.EmbeddedNetty.class })
public class ReactiveWebServerFactoryAutoConfiguration
由 源码7 可知,该自动配置类使用@Import注解导入的核心配置类是BeanPostProcessorsRegistrar和几个嵌入式Web容器类。
与WebMvc的自动配置类ServletWebServerFactoryAutoConfiguration相比,导入的嵌入式Web容器多了一个Netty。
源码8:ReactiveWebServerFactoryConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ReactiveWebServerFactory.class)
@ConditionalOnClass({HttpServer.class})
static class EmbeddedNetty {
@Bean
@ConditionalOnMissingBean
ReactorResourceFactory reactorServerResourceFactory() {
return new ReactorResourceFactory();
}
@Bean
NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ReactorResourceFactory resourceFactory,
ObjectProvider<NettyRouteProvider> routes, ObjectProvider<NettyServerCustomizer> serverCustomizers) {
NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory();
serverFactory.setResourceFactory(resourceFactory);
routes.orderedStream().forEach(serverFactory::addRouteProviders);
serverFactory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
return serverFactory;
}
}
由 源码8 可知,EmbeddedNetty中注册的Bean包括NettyReactiveWebServerFactory和ReactorResourceFactory。
NettyReactiveWebServerFactory会在IOC容器的初始化阶段创建嵌入式Netty容器。
ReactorResourceFactory是一个可以管理Reactor Netty资源的工厂,这个设计类似于线程池。
源码9:ReactorResourceFactory.java
public class ReactorResourceFactory implements InitializingBean, DisposableBean {
// ......
private Supplier<ConnectionProvider> connectionProviderSupplier = () -> ConnectionProvider.fixed("webflux", 500);
//......
}
源码10:ConnectionProvider.java
static ConnectionProvider fixed(String name, int maxConnections) {
return fixed(name, maxConnections, DEFAULT_POOL_ACQUIRE_TIMEOUT);
}
static ConnectionProvider fixed(String name, int maxConnections, long acquireTimeout) {
return fixed(name, maxConnections, acquireTimeout, null, null);
}
static ConnectionProvider fixed(String name, int maxConnections, long acquireTimeout, @Nullable Duration maxIdleTime, @Nullable Duration maxLifeTime) {
// ......
return builder(name).maxConnections(maxConnections)
.pendingAcquireMaxCount(-1) // keep the backwards compatibility
.pendingAcquireTimeout(Duration.ofMillis(acquireTimeout))
.maxIdleTime(maxIdleTime)
.maxLifeTime(maxLifeTime)
.build();
}
public ConnectionProvider build() {
return new PooledConnectionProvider(this);
}
由 源码9-10 可知,ReactorResourceFactory内部组合了一个ConnectionProvider,它会初始化一个最大连接数为500的连接池,其落地实现类为PooledConnectionProvider。由此可以理解ReactorResourceFactory就是一个Reactor Netty的连接池。
13.3.2 WebFluxAutoConfiguration
源码11:WebFluxAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class, CodecsAutoConfiguration.class,
ValidationAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAutoConfiguration
由 源码11 可知,WebFluxAutoConfiguration生效的前提是当前项目的Web类型为REACTIVE(@ConditionalOnWebApplication注解),以及需要当前项目类路径下存在WebFluxConfigurer类(@ConditionalOnClass注解)。
在WebFluxAutoConfiguration的内部,有几个静态内部类根据不同功能和场景分别配置对应的组件。
13.3.3 WebFluxConfig
源码12:WebFluxAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
@Import({ EnableWebFluxConfiguration.class })
public static class WebFluxConfig implements WebFluxConfigurer {...}
由 源码12 可知,WebFluxConfig类使用@Import注解导入了EnableWebFluxConfiguration。
WebFluxConfig类本身实现了WebFluxConfigurer接口,因此具备配置WebFlux的能力。
13.3.3.1 静态资源映射
源码13:WebFluxAutoConfiguration.java
public static class WebFluxConfig implements WebFluxConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 前置检查 ......
if (!registry.hasMappingForPattern("/webjars/**")) {
ResourceHandlerRegistration registration = registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
// ......
}
String staticPathPattern = this.webFluxProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
ResourceHandlerRegistration registration = registry.addResourceHandler(staticPathPattern)
.addResourceLocations(this.resourceProperties.getStaticLocations());
// ......
}
}
}
源码14:ResourceProperties.java
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/" };
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
// ......
由 源码13-14 可知,```addResourceHandlers``方法会默认配置几个常用的约定好的静态文件的存放位置:/resources、/static、/public、/webjars等等。这些路径下的静态文件是可以被直接引用的。
13.3.3.2 视图解析器
源码15:WebFluxAutoConfiguration.java
public static class WebFluxConfig implements WebFluxConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
this.viewResolvers.orderedStream().forEach(registry::viewResolver);
}
}
由 源码15 可知,WebFlux也支持视图跳转,底层也有视图解析器的配置。
13.3.4 EnableWebFluxConfiguration
EnableWebFluxConfiguration与WebMvc中的EnableWebMvcConfiguration相似。
源码16:WebFluxAutoConfiguration.java
@Configuration(proxyBeanMethods = false)
public static class EnableWebFluxConfiguration extends DelegatingWebFluxConfiguration {...}
由 源码16 可知,EnableWebFluxConfiguration继承了父类DelegatingWebFluxConfiguration。
EnableWebFluxConfiguration配置类种注册的组件包括:
- FormattingConversionService:参数类型转换器。用于数据的类型转换,如日期与字符串之间的互相转换。
- Validator:JSR-303参数校验器。
- RequestMappingHandlerAdapter:标注了@RequestMapping注解的Handler的执行器。
- RequestMappingHandlerMapping:标注了@RequestMapping注解的Handler的处理器。
13.3.5 WebFluxConfigurationSupport
EnableWebFluxConfiguration继承父类DelegatingWebFluxConfiguration,而DelegatingWebFluxConfiguration又集成父类WebFluxConfigurationSupport。
WebFluxConfigurationSupport类中也有一些核心组件的注册。
13.3.5.1 DispatcherHandler
源码18:WebFluxConfigurationSupport.java
@Bean
public DispatcherHandler webHandler() {
return new DispatcherHandler();
}
WebFlux中的核心前端控制器是DispatcherHandler,对应WebMvc中的DispatcherServlet。由 源码18 可知,DispatcherHandler组件的注册仅仅是创建一个新对象。
13.3.5.2 WebExceptionHandler
源码19:WebFluxConfigurationSupport.java
@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {
return new WebFluxResponseStatusExceptionHandler();
}
WebFlux中的异常状态响应器用于处理异常情况下的HTTP状态码响应,如 源码19 所示,其实现类是WebFluxResponseStatusExceptionHandler。
13.3.5.3 RequestMappingHandlerMapping、RequestMappingHandlerAdapter
源码20:WebFluxConfigurationSupport.java
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
// ......
return mapping;
}
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
ServerCodecConfigurer serverCodecConfigurer,
@Qualifier("webFluxConversionService") FormattingConversionService conversionService,
@Qualifier("webFluxValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
// ......
return adapter;
}
因为WebFlux可以完美支持WebMvc中使用@RequestMapping注解的方式定义HAndler,支持这种方式的底层组件就是RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
13.3.5.4 RouterFunctionMapping
源码21:WebFluxConfigurationSupport.java
@Bean
public RouterFunctionMapping routerFunctionMapping(ServerCodecConfigurer serverCodecConfigurer) {
RouterFunctionMapping mapping = createRouterFunctionMapping();
mapping.setOrder(-1); // 此处设置优先级高于RequestMappingHandlerMapping
mapping.setMessageReaders(serverCodecConfigurer.getReaders());
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
RouterFunctionMapping是基于函数式端点路由编程的Mapping处理器。由 源码21 可知,它的优先级高于RequestMappingHandlerMapping,这意味着WebFlux倾向于开发中使用函数式端点的Web开发,而不是传统的@RequestMapping注解式开发。
13.3.5.5 HandlerFunctionAdapter
源码22:WebFluxConfigurationSupport.java
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
HandlerFunctionAdapter是Handler方法的执行器。由 源码22 可知,对应的支撑组件是HandlerFunctionAdapter,它可以直接提取出HandlerFunction中的Handler方法进行调用。
13.3.5.6 ResultHandler
ResultHandler是WebFlux中对返回值进行处理的组件,对应到WebMvc中则是HandlerMethodReturnValueHadnler。
默认情况下,WebFlux会注册4种不同的ResultHandler实现类。
- ResponseEntityResultHandler:处理HttpEntity和ResponseEntity。
源码23:WebFluxConfigurationSupport.java
@Bean
public ResponseEntityResultHandler responseEntityResultHandler(
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
ServerCodecConfigurer serverCodecConfigurer,
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
return new ResponseEntityResultHandler(serverCodecConfigurer.getWriters(),
contentTypeResolver, reactiveAdapterRegistry);
}
- ResponseBodyResultHandler:处理@RequestMapping的标注了@ResponseBody注解的Handler。
源码24:WebFluxConfigurationSupport.java
@Bean
public ResponseBodyResultHandler responseBodyResultHandler(
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
ServerCodecConfigurer serverCodecConfigurer,
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(),
contentTypeResolver, reactiveAdapterRegistry);
}
- ViewResolutionResultHandler:处理逻辑视图返回值。
源码25:WebFluxConfigurationSupport.java
@Bean
public ViewResolutionResultHandler viewResolutionResultHandler(
@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,
@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {
ViewResolverRegistry registry = getViewResolverRegistry();
List<ViewResolver> resolvers = registry.getViewResolvers();
ViewResolutionResultHandler handler = new ViewResolutionResultHandler(
resolvers, contentTypeResolver, reactiveAdapterRegistry);
handler.setDefaultViews(registry.getDefaultViews());
handler.setOrder(registry.getOrder());
return handler;
}
- ServerResponseResultHandler:处理返回值类型为ServerResponse的。
源码26:WebFluxConfigurationSupport.java
@Bean
public ServerResponseResultHandler serverResponseResultHandler(ServerCodecConfigurer serverCodecConfigurer) {
List<ViewResolver> resolvers = getViewResolverRegistry().getViewResolvers();
ServerResponseResultHandler handler = new ServerResponseResultHandler();
handler.setMessageWriters(serverCodecConfigurer.getWriters());
handler.setViewResolvers(resolvers);
return handler;
}
······
本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析