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.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源码解读与原理分析

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python爬虫——解析常用三大方式之Xpath

目录 Xpath 安装xpath 安装lxml库 导入lxml库 解析本地文件 etree.parse&#xff08;&#xff09; 解析服务器响应文件 etree.HTML() xpath基本语法 小案例&#xff1a;获取百度首页的百度一下 大案例&#xff1a;爬取站长素材图片 总结 Xpath 安装xpath 首先要学会安…

大模型(LLM)的量化技术Quantization原理学习

在自然语言处理领域&#xff0c;大型语言模型&#xff08;LLM&#xff09;在自然语言处理领域的应用越来越广泛。然而&#xff0c;随着模型规模的增大&#xff0c;计算和存储资源的需求也急剧增加。为了降低计算和存储开销&#xff0c;同时保持模型的性能&#xff0c;LLM大模型…

【排序算法】冒泡排序

目录 概述 冒泡排序原理 冒泡排序的Java实现 总结 概述 冒泡排序是一种简单但低效的排序算法。它重复地走访要排序的元素列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就交换它们&#xff0c;直到没有元素需要交换。这个过程类似于气泡在水中上浮的过程&am…

开源模型Mistral 7B+Amazon SageMaker部署指南

一、Mistral 7B简述 Mistral AI 是一家总部位于法国的 AI 公司&#xff0c;其使命是将公开可用的模型提升至最先进的性能水平。他们专注于构建快速而安全的大型语言模型&#xff08;LLM&#xff09;&#xff0c;此类模型可用于从聊天机器人到代码生成等各种任务。不久前其发布…

leetcode hot100 每日温度

在本题中&#xff0c;我们是通过单调栈来解决的&#xff0c;因为我们采用了栈的数据结构&#xff0c;并且&#xff0c;栈内存储的元素是单调的。 本题我们考虑&#xff0c;将气温数组元素的下标存入栈中&#xff0c;首先初始化要把0放入&#xff0c;0是下标的意思。然后我们拿…

谷歌seo推广哪里好优化?

大家都知道常规的SEO技巧&#xff0c;比如关键词优化、高质量的内容、外链建设之类的&#xff0c;这些是重点&#xff0c;但如果想锦上添花&#xff0c;我们可以尝试点不一样的 关注社区和参与论坛&#xff0c;这不仅是为了建立链接&#xff0c;更重要的是与目标群体建立连接&…

ZYNQ--MIG核配置

文章目录 MIG核配置界面多通道AXI读写DDR3MIG核配置界面 Clock Period: DDR3 芯片运行时钟周期,这个参数的范围和 FPGA 的芯片类型以及具体类型的速度等级有关。本实验选择 1250ps,对应 800M,这是本次实验所采用芯片可选的最大频率。注意这个时钟是 MIG IP 核产生,并输出给…

【Git】merge时报错:refusing to merge unrelated histories

文章目录 一、问题二、解决办法1、将feature分支的东西追加到master分支中2、将feature里的东西直接覆盖到master分支中 一、问题 今天将feature分支合并到master时报错&#xff1a;refusing to merge unrelated histories&#xff08;拒绝合并无关历史&#xff09; 报错原因&…

Python学习 day07(JSON)

JSON 各种编程语言存储数据的容器不尽相同&#xff0c;在Python中有字典dict这样的数据类型&#xff0c;而其他语言可能没有对应的字典&#xff0c;为了让不同的语言都能够相互通用的传递数据&#xff0c;JSON就是一种非常良好的中转数据格式&#xff0c;如下&#xff1a; JSON…

[线代]自用大纲

部分内容整理自张宇和网络 序 题型分布&#xff1a; 题型单题分值题目数量总分值选择题5315填空题515解答题12112 *一道大题可能用到六部分所有知识 矩阵 性质 k k k倍和乘积行列式 ∣ k A ∣ k n ∣ A ∣ |kA|k^n|A| ∣kA∣kn∣A∣ ∣ A B ∣ ≠ ∣ A ∣ ∣ B ∣ |AB|≠…

平台工程: 用Backstage构建开发者门户 - 2

本文介绍了如何使用开源Backstage构建自己的开发者门户&#xff0c;并基于此实践平台工程。本系列共两篇文章&#xff0c;这是第二篇。原文: Platform Engineering: Building Your Developer Portal with Backstage — Part 2 在本教程第一部分中我们了解了Backstage这个用于构…

【前端面试题5】利用 border 属性画一个三角形

举例1&#xff1a;利用 border 属性画一个三角形&#xff08;小技巧&#xff09; 完整代码如下&#xff1a; div{width: 0;height: 0;border: 50px solid transparent;border-top-color: red;border-bottom: none; }步骤如下&#xff1a; &#xff08;1&#xff09;当我们设…

手势识别应用介绍

目录 一、功能介绍 二、安装部署说明 2.1 文件目录说明 2.2 手势识别部分 一、功能介绍 这是一个通过摄像头捕获手势&#xff0c;根据不同的手势来做出不同操作的计算机程序。目前可以识别9种手势&#xff0c;可以根据识别到的手势&#xff0c;进行打开应用、增大音量、减小音量…

JProfiler 14 for Mac/win:Java开发者的性能分析利器

在Java开发的世界中&#xff0c;性能优化始终是一个不可忽视的议题。随着技术的不断进步&#xff0c;开发者对于工具的需求也日益增长。JProfiler 14&#xff0c;作为一款专为Java开发者设计的性能分析工具&#xff0c;无论是对于Mac用户还是Windows用户&#xff0c;都展现出了…

哈希表C++(Acwing)

代码&#xff1a; #include <iostream> #include <cstring>using namespace std;const int N 100003;int h[N], e[N], ne[N], idx;void insert(int x) {int k (x % N N) % N;//哈希,保证取模后为正数e[idx] x;ne[idx] h[k];h[k] idx; }bool find(int x) {in…

MySQL 常用优化方式

MySQL 常用优化方式 sql 书写顺序与执行顺序SQL设计优化使用索引避免索引失效分析慢查询合理使用子查询和临时表列相关使用 日常SQL优化场景limit语句隐式类型转换嵌套子查询混合排序查询重写 sql 书写顺序与执行顺序 (7) SELECT (8) DISTINCT <select_list> (1) FROM &…

DolphinScheduler——工作流实例的生命周期

目录 一、DolphinScheduler架构原理 1.1 系统架构图 1.2 DolphinScheduler核心概念 1.2 创建工作流 1.2.1 如何触发一个工作流实例 1.2.2 任务调度链路监控 1.2.3 Workflow-DAG解析 DAG解析 Dispatch分发流程 Master和Worker的交互过程 1.3 任务运行状态 该篇文章主…

【真机Bug】异步加载资源未完成访问单例导致资源创建失败

1.错误表现描述 抽卡时&#xff0c;10抽展示界面为A。抽取内容可能是整卡或者碎片&#xff0c;抽到整卡&#xff0c;会有立绘展示和点击详情的按钮。点击详情后出现详情页B。【此时界面A预制体被销毁&#xff0c;卡片数据进入数据缓存池】点击页面B的返回按钮&#xff0c;单例…

maven 包管理平台-05-multi module 多模块

拓展阅读 maven 包管理平台-01-maven 入门介绍 Maven、Gradle、Ant、Ivy、Bazel 和 SBT 的详细对比表格 maven 包管理平台-02-windows 安装配置 mac 安装配置 maven 包管理平台-03-maven project maven 项目的创建入门 maven 包管理平台-04-maven archetype 项目原型 ma…

183896-00-6,Biotin-C3-PEG3-C3-NH2,可以选择性降解靶蛋白

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;183896-00-6&#xff0c;Biotin-C3-PEG3-C3-NH2&#xff0c;Biotin-C3-PEG3-C3-amine&#xff0c;生物素-C3-PEG3-C3-胺 一、基本信息 【产品简介】&#xff1a;Biotin-PEG3-C3-NH2是一种PROTAC linker&#xff0c;…