SpringBoot源码解读与原理分析(三十八)SpringBoot整合WebFlux(一)WebFlux的自动装配

本文主要是介绍SpringBoot源码解读与原理分析(三十八)SpringBoot整合WebFlux(一)WebFlux的自动装配,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 第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中的响应性

如上图,在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
源码1Publisher.javapublic interface Publisher<T> {public void subscribe(Subscriber<? super T> s);
}

由 源码1 可知,数据生产者Publisher只有一个方法subscribe,该方法会接收一个订阅者Subscriber,构成“订阅”关系。

注意,subscribe方法是一个类似于“工厂”的方法,它可以被多次调用,但是每次调用都会创建一个新的订阅关系,且一个订阅关系只能关联一个订阅者。

13.1.7.2 Subscriber
源码2Subscriber.javapublic 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
源码3Subscription.javapublic interface Subscription {public void request(long n);public void cancel();
}

Subscription可以看作是生产者和订阅者之间的订阅关系,完成了两者之间的交互。由 源码2 可知,订阅关系Subscription接口有2个方法:

  • request:用于主动请求数据/拉取数据;
  • cancel:用于放弃/停止拉取数据。
13.1.7.4 Processor
源码4Processor.javapublic interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

Processor可以理解为处理器,一般用于数据的中间环节处理(如数据转换、数据过滤等)。由 源码4 可知,Processor接口继承了Subscriber接口和Publisher接口,是生产者和订阅者的合体。

13.1.7.5 Flux
源码5Flux.javapublic abstract class Flux<T> implements CorePublisher<T> {...}

Flux定义了很多subscribe方法

**Flux可以理解为“非阻塞的Stream”。**由 源码5 和上图可知,它实现了Publisher接口,内部定义了很多subscribe方法。重载这么多个subscribe方法的目的在于简化操作。

13.1.7.6 Mono
源码6Mono.javapublic 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的编码方式

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中的数据模型作为响应主体完全可行。

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 {@Autowiredprivate UserHandler userHandler;@Beanpublic 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编码风格的示例项目搭建成功。

基于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

源码7ReactiveWebServerFactoryAutoConfiguration.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。

源码8ReactiveWebServerFactoryConfiguration.java@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ReactiveWebServerFactory.class)
@ConditionalOnClass({HttpServer.class})
static class EmbeddedNetty {@Bean@ConditionalOnMissingBeanReactorResourceFactory reactorServerResourceFactory() {return new ReactorResourceFactory();}@BeanNettyReactiveWebServerFactory 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资源的工厂,这个设计类似于线程池。

源码9ReactorResourceFactory.javapublic class ReactorResourceFactory implements InitializingBean, DisposableBean {// ......private Supplier<ConnectionProvider> connectionProviderSupplier = () -> ConnectionProvider.fixed("webflux", 500);//......
}
源码10ConnectionProvider.javastatic 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

源码11WebFluxAutoConfiguration.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

源码12WebFluxAutoConfiguration.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 静态资源映射
源码13WebFluxAutoConfiguration.javapublic static class WebFluxConfig implements WebFluxConfigurer {@Overridepublic 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());// ......}}
}
源码14ResourceProperties.javaprivate 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 视图解析器
源码15WebFluxAutoConfiguration.javapublic static class WebFluxConfig implements WebFluxConfigurer {@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {this.viewResolvers.orderedStream().forEach(registry::viewResolver);}
}

由 源码15 可知,WebFlux也支持视图跳转,底层也有视图解析器的配置。

13.3.4 EnableWebFluxConfiguration

EnableWebFluxConfiguration与WebMvc中的EnableWebMvcConfiguration相似。

源码16WebFluxAutoConfiguration.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
源码18WebFluxConfigurationSupport.java@Bean
public DispatcherHandler webHandler() {return new DispatcherHandler();
}

WebFlux中的核心前端控制器是DispatcherHandler,对应WebMvc中的DispatcherServlet。由 源码18 可知,DispatcherHandler组件的注册仅仅是创建一个新对象。

13.3.5.2 WebExceptionHandler
源码19WebFluxConfigurationSupport.java@Bean
@Order(0)
public WebExceptionHandler responseStatusExceptionHandler() {return new WebFluxResponseStatusExceptionHandler();
}

WebFlux中的异常状态响应器用于处理异常情况下的HTTP状态码响应,如 源码19 所示,其实现类是WebFluxResponseStatusExceptionHandler。

13.3.5.3 RequestMappingHandlerMapping、RequestMappingHandlerAdapter
源码20WebFluxConfigurationSupport.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
源码21WebFluxConfigurationSupport.java@Bean
public RouterFunctionMapping routerFunctionMapping(ServerCodecConfigurer serverCodecConfigurer) {RouterFunctionMapping mapping = createRouterFunctionMapping();mapping.setOrder(-1);  // 此处设置优先级高于RequestMappingHandlerMappingmapping.setMessageReaders(serverCodecConfigurer.getReaders());mapping.setCorsConfigurations(getCorsConfigurations());return mapping;
}

RouterFunctionMapping是基于函数式端点路由编程的Mapping处理器。由 源码21 可知,它的优先级高于RequestMappingHandlerMapping,这意味着WebFlux倾向于开发中使用函数式端点的Web开发,而不是传统的@RequestMapping注解式开发。

13.3.5.5 HandlerFunctionAdapter
源码22WebFluxConfigurationSupport.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。
源码23WebFluxConfigurationSupport.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。
源码24WebFluxConfigurationSupport.java@Bean
public ResponseBodyResultHandler responseBodyResultHandler(@Qualifier("webFluxAdapterRegistry") ReactiveAdapterRegistry reactiveAdapterRegistry,ServerCodecConfigurer serverCodecConfigurer,@Qualifier("webFluxContentTypeResolver") RequestedContentTypeResolver contentTypeResolver) {return new ResponseBodyResultHandler(serverCodecConfigurer.getWriters(),contentTypeResolver, reactiveAdapterRegistry);
}
  • ViewResolutionResultHandler:处理逻辑视图返回值。
源码25WebFluxConfigurationSupport.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的。
源码26WebFluxConfigurationSupport.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源码解读与原理分析

这篇关于SpringBoot源码解读与原理分析(三十八)SpringBoot整合WebFlux(一)WebFlux的自动装配的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/766700

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于