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

news2024/10/5 19:18:27

文章目录

  • 前言
  • 第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.java

public interface Publisher<T> {
    public void subscribe(Subscriber<? super T> s);
}

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

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

13.1.7.2 Subscriber
源码2Subscriber.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
源码3Subscription.java

public interface Subscription {
    public void request(long n);
    public void cancel();
}

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

  • request:用于主动请求数据/拉取数据;
  • cancel:用于放弃/停止拉取数据。
13.1.7.4 Processor
源码4Processor.java

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

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

13.1.7.5 Flux
源码5Flux.java

public abstract class Flux<T> implements CorePublisher<T> {...}

Flux定义了很多subscribe方法

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

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

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 {

    @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编码风格的示例项目搭建成功。

基于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
    @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资源的工厂,这个设计类似于线程池。

源码9ReactorResourceFactory.java

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

源码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.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());
            // ......
        }
    }
}
源码14ResourceProperties.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 视图解析器
源码15WebFluxAutoConfiguration.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相似。

源码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);  // 此处设置优先级高于RequestMappingHandlerMapping
    mapping.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源码解读与原理分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1482531.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RuoYi-Vue-Plus功能分析-jackson配置

文章目录 前言一、配置文件二、配置类三、注解四、json工具类1. 工具内容2. 使用工具 前言 前端在给我发送请求的时候一般包含三个部分url&#xff0c;header&#xff0c;body。那么就会涉及我们后端如何接收这些请求参数并且我们处理完毕参数后前端又如何接收参数 通过url传…

源码解析篇 | YOLOv8官方源码项目目录结构解析

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLOv8是一种目标检测算法&#xff0c;它是YOLO&#xff08;You Only Look Once&#xff09;系列算法的第8个版本。YOLOv8相比于之前的版本&#xff0c;在检测精度和速度上都有所提升&#xff0c;它在各种场景下都表现出色…

初阶数据结构:栈与队列

目录 1. 简述&#xff1a;栈2. 栈的功能分析与实现2.1 功能分析2.2 栈的实现2.2.1 栈的结构创建与初始化2.2.2 压栈&#xff0c;出栈与判空&#xff1a;2.2.3 获取栈顶元素&#xff0c;检索栈的长度与栈的销毁 3. 简述&#xff1a;队列4. 队列的功能分析与实现4.1 队列的功能分…

基于springboot+vue的美食推荐商城

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

重塑计算:ICP 打造无限智能合约云解决方案

作者&#xff1a;Lynn Cadet 编译&#xff1a;TinTinLand 原文&#xff1a;https://www.hostingadvice.com/blog/internet-computer-offers-a-limitless-smart-contract-cloud-solution/ 摘要&#xff1a;与其前身互联网一样&#xff0c;区块链行业寻求引领一个新时代及其经济…

腾讯云优惠券一共有3个渠道可以领取,你知道吗?

腾讯云代金券领取渠道有哪些&#xff1f;腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券&#xff0c;大家也可以在腾讯云百科蹲守代金券&#xff0c;因为腾讯云代金券领取渠道比较分散&#xff0c;腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

Ubuntu22.04下安装Spark2.4.0(Local模式)

一、版本信息 虚拟机产品&#xff1a;VMware Workstation 17 Pro 虚拟机版本&#xff1a;17.0.0 build-20800274 ISO映像文件&#xff1a;ubuntukylin-22.04-pro-amd64.iso Hadoop版本&#xff1a;Hadoop 3.1.3 JDK版本&#xff1a;Java JDK 1.8 Spark版本&#xff1a;S…

1905_ARMv7-M的堆栈寄存器

1905_ARMv7-M的堆栈寄存器 全部学习汇总&#xff1a; g_arm_cores: ARM内核的学习笔记 (gitee.com) ARMv7-M实现了2种堆栈&#xff0c;分别是MSP和PSP。复位的时候默认是MSP&#xff0c;而当前是哪种可以通过CONTROL.SPSEL寄存器的bit来查看。 SP寄存器的最低2bit&#xff0c;S…

叠氮生物素,Biotin-azide ,含有生物素基团和叠氮基团

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;生物素-叠氮&#xff0c;生物素叠氮&#xff0c;叠氮生物素&#xff0c;Biotin-azide &#xff0c;Azide-Biotin&#xff0c;Biotin-N3&#xff0c;N3-Biotin&#xff0c;908007-17-0 一、基本信息 【产品简介】&a…

根据二层封装协议决定—网络类型

目录 一、网络类型的分类 二、数据链路层协议 MA网络 以太网协议 P2P网络 一、网络类型的分类 P2P --- point to point --- 点到点网络 MA --- Multi-Access Network --- 多点接入网络 BMA --- Broadcast Multi-Access Network --- 广播型多点接入网络 NBMA --- Non-Bro…

如何更好的引导大语言模型进行编程的高效开发流程?

这张图片展示了一种如何更好地引导大语言模型进行编程的方法。 首先&#xff0c;最简单也是最有效的方法是让大语言模型重复运行多次&#xff0c;每次增加一些额外的信息&#xff0c;直到获得想要的结果。这种方法虽然简单&#xff0c;但可能需要多次尝试才能得到满意的结果。…

AI搜索工具颠覆百度搜索,直接给出结果

这可能是一款足够颠覆搜索引擎的AI工具&#xff0c;绝对是AI产品中的国货之光。 它就是秘塔AI搜索&#xff0c;主打的亮点就是没有广告&#xff0c;我们进入官网之后&#xff0c;可以在对话框输入我们想要了解的事件。 比如最近比较热门的是中国AI教父李一舟&#xff0c;它会执…

netlink原理及应用

什么是netlink netlink是一种基于网络的通信机制&#xff0c;允许内核内部、内核与用户态应用之间甚至用户态应用之间进行通信&#xff1b;netlink的主要作用是内核与用户态之间通信&#xff1b;它的思想是&#xff0c;基于BSD的socket使用网络框架在内核和用户态之间进行通信…

Python算法题集_括号生成

Python算法题集_括号生成 题22&#xff1a;括号生成1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【堆栈回溯】2) 改进版一【切片回溯】3) 改进版二【列表缓存逐层扩充】 4. 最优算法5. 相关资源 本文为Python算法题集之一的代码示例 题22&am…

#单片机(TB6600驱动42步进电机)

1.IDE:keil 2.设备:保密 3.实验&#xff1a;使用单片机通过普通IO口控制TB6600驱动42步进电机 4.时序图&#xff1a; TB6600 ENA、ENA-DIR-、DIRPUL-、PULB-、BA、A-VCC、GND使能电机&#xff08;直接悬空不接&#xff09;方向脉冲输入&#xff08;普通IO口模拟即可&#xff…

回归啦!!!

消失的日子在实习&#xff0c;今天最后一天了来看看自己的学习日志&#xff0c;有没有可以和小伙伴交流的部分吧&#xff01; 目录 一、产品one ①简介 ②底层原理 ③知识点一 作用一&#xff1a;日志采集 作用二&#xff1a;实时监测 作用三&#xff1a;规则匹配 作用…

足底筋膜炎的症状及治疗

足底筋膜炎症状&#xff1a;足底筋膜炎通常表现为足跟部疼痛&#xff0c;尤其是在晨起或长时间站立、行走后加重。疼痛可能向足底前部或足弓处放射&#xff0c;严重时可能影响行走。此外&#xff0c;患者还可能出现足跟部肿胀、皮肤温度升高、局部压痛等症状。 足底筋膜炎治疗方…

WinForm、Wpf自动升级 AutoUpdater.NET

Github AutoUpdater.NET 目录 一、IIS部署 更新站点 二、创建Winform 一、IIS部署 更新站点 IIS默认站点目录下创建 目录 Downloads、Updates Updates目录创建文件 UpdateLog.html、AutoUpdaterStarter.xml UpdateLog.html&#xff1a; <html><body><h1…

FPGA开源项目分享——2D N-Body重力模拟器

​导语 今天继续康奈尔大学FPGA 课程ECE 5760的典型案例分享——2D N-Body重力模拟器。 &#xff08;更多其他案例请参考网站&#xff1a; Final Projects ECE 5760&#xff09; 1. 项目概述 项目网址 Grav Sim 项目说明 该项目的目标是创建一个用DE1-SOC进行硬件加速的2…

FPGA-VGA成像原理与时序

什么是VGA: VGA, Video Graphics Array。即视频图形阵列,具有分辨率高、显示速率快、颜色丰富等优点。VGA接口不但是CRT显示设备的标准接口,同样也是LCD液晶显示设备的标准接口,具有广泛的应用范围。在FGPA中,常广泛用于图像处理等领域。 VGA 显示器成像原理 在 VGA 标准刚兴…