2

1. Introduction to WebFlux

WebFlux is a new reactive web framework introduced in Spring Framework 5.0. Realize the Reactive Streams specification through the Reactor project, a completely asynchronous and non-blocking framework. It does not speed up the execution of the program itself, but in the case of high concurrency, asynchronous IO can handle higher throughput with a small number of stable threads, avoiding the accumulation of threads caused by file IO/network IO congestion.

1.1 Features of WebFlux

WebFlux has the following features:

  • asynchronous non-blocking -can give an example of uploading. Compared with Spring MVC, which is a synchronous blocking IO model, Spring WebFlux handles it like this: When the thread finds that the file data is not transmitted well, it does other things first. When the file is ready, the thread is notified to process (here is the input non-blocking method), and when it is received And write to the disk (this step can also use asynchronous non-blocking mode) and then notify the thread to process the response (here is the output non-blocking mode).
  • Responsive Functional Programming -Compared to the synchronous and blocking Pull mode of Java 8 Stream, Spring Flux adopts the Reactor Stream asynchronous and non-blocking Push mode. The writing adopts the Java lambda method, which is close to the natural language form and easy to understand.
  • not restricted to Servlet -can run in traditional Servlet container (version 3.1+), and can also run in NIO containers such as Netty and Undertow.

1.2 Design goals of WebFlux

  • Applicable to high concurrency
  • High throughput
  • Scalability

Two, Spring WebFlux component introduction

2.1 HTTPHandler

A simple abstraction for processing requests and responses to adapt to the APIs of different HTTP service containers.

2.2 WebHandler

An abstract interface for processing business requests, which defines a series of processing behaviors. The relevant core implementation classes are as follows;

2.3 DispatcherHandler

The total controller for request processing, the actual work is handled by multiple configurable components.

WebFlux is compatible with Spring MVC's programming development methods based on @Controller, @RequestMapping and other annotations, and can be switched smoothly.

2.4 Functional Endpoints

This is a lightweight functional programming model. It is an alternative to the programming model based on @Controller, @RequestMapping and other annotations. It provides a set of functional APIs for creating Router, Handler and Filter. The call processing components are as follows:

Simple RouterFuntion routing registration and business processing process:

@Bean
public RouterFunction<ServerResponse> initRouterFunction() {
    return RouterFunctions.route()
        .GET("/hello/{name}", serverRequest -> {
            String name = serverRequest.pathVariable("name");
            return ServerResponse.ok().bodyValue(name);
        }).build();
}

Request forwarding process:

2.5 Reactive Stream

This is an important component. WebFlux uses Reactor to rewrite the traditional Spring MVC logic. Among them, Flux and Mono are two key concepts in Reactor. Only by mastering these two concepts can you understand how WebFlux works.

Both Flux and Mono implement Reactor's Publisher interface, which is a time publisher and provides a subscription interface for consumers. When an event occurs, Flux or Mono will notify consumers of the corresponding event by calling back the corresponding method of the consumer. This is the so-called reactive programming model.

Mono work flow chart

It will only be done after sending a single result.

Flux work flow chart

Send out zero or more, possibly infinite results before completion.

对于流式媒体类型:application/stream+json 或者 text/event-stream ,可以让调用端获得服务器滚动结果。
对于非流类型:application/json  WebFlux 默认JSON编码器会将序列化的JSON 一次性刷新到网络,这并不意味着阻塞,因为结果Flux<?> 是以反应式方式写入网络的,没有任何障碍。

Three, WebFlux working principle

3.1 Component assembly process

process related source code analysis -WebFluxAutoConfiguration

@Configuration
//条件装配 只有启动的类型是REACTIVE时加载
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
//只有存在 WebFluxConfigurer实例  时加载
@ConditionalOnClass(WebFluxConfigurer.class)
//在不存在  WebFluxConfigurationSupport实例时 加载
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
//在之后装配
@AutoConfigureAfter({ ReactiveWebServerFactoryAutoConfiguration.class,
      CodecsAutoConfiguration.class, ValidationAutoConfiguration.class })
//自动装配顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAutoConfiguration {
   @Configuration
   @EnableConfigurationProperties({ ResourceProperties.class, WebFluxProperties.class })
   //接口编程 在装配WebFluxConfig 之前要先 装配EnableWebFluxConfiguration
   @Import({ EnableWebFluxConfiguration.class })
   public static class WebFluxConfig implements WebFluxConfigurer {
      //隐藏部分源码
     /**
     * Configuration equivalent to {@code @EnableWebFlux}.
     */
   } 
    @Configuration
    public static class EnableWebFluxConfiguration
            extends DelegatingWebFluxConfiguration {
        //隐藏部分代码
    }
    @Configuration
    @ConditionalOnEnabledResourceChain
    static class ResourceChainCustomizerConfiguration {
        //隐藏部分代码
    }
    private static class ResourceChainResourceHandlerRegistrationCustomizer
            implements ResourceHandlerRegistrationCustomizer {
        //隐藏部分代码
    }

When WebFluxAutoConfiguration is automatically assembled, EnableWebFluxConfiguration is automatically assembled first and EnableWebFluxConfiguration->DelegatingWebFluxConfiguration ->WebFluxConfigurationSupport.

Finally, WebFluxConfigurationSupport not only configures DispatcherHandler, but also configures many other WebFlux core components including exception handler WebExceptionHandler, mapping handler handler HandlerMapping, request adapter HandlerAdapter, response handler HandlerResultHandler and so on.

The initialization process of DispatcherHandler creation is as follows;

public class WebFluxConfigurationSupport implements ApplicationContextAware {
   //隐藏部分代码
   @Nullable
   public final ApplicationContext getApplicationContext() {
      return this.applicationContext;
   }
    //隐藏部分代码
   @Bean
   public DispatcherHandler webHandler() {
      return new DispatcherHandler();
   }
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
   @Nullable
   private List<HandlerMapping> handlerMappings;
   @Nullable
   private List<HandlerAdapter> handlerAdapters;
   @Nullable
   private List<HandlerResultHandler> resultHandlers;
       @Override
   public void setApplicationContext(ApplicationContext applicationContext) {
        initStrategies(applicationContext);
   }
   protected void initStrategies(ApplicationContext context) {
         //注入handlerMappings
      Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerMapping.class, true, false);

      ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
      AnnotationAwareOrderComparator.sort(mappings);
      this.handlerMappings = Collections.unmodifiableList(mappings);
       //注入handlerAdapters
      Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerAdapter.class, true, false);

      this.handlerAdapters = new ArrayList<>(adapterBeans.values());
      AnnotationAwareOrderComparator.sort(this.handlerAdapters);
      //注入resultHandlers
      Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
            context, HandlerResultHandler.class, true, false);

      this.resultHandlers = new ArrayList<>(beans.values());
      AnnotationAwareOrderComparator.sort(this.resultHandlers);
   }

process related source code analysis- HTTPHandlerAutoConfiguration

The loading process of WebFlux core components has been explained above. When are these components injected into the corresponding container context? In fact, it is injected when the container context is refreshed.

org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#onRefresh

public class ReactiveWebServerApplicationContext extends GenericReactiveWebApplicationContext
      implements ConfigurableWebServerApplicationContext {
   @Override
   protected void onRefresh() {
      super.onRefresh();
      try {
         createWebServer();
      }
      catch (Throwable ex) {
         throw new ApplicationContextException("Unable to start reactive web server", ex);
      }
   }
   private void createWebServer() {
      WebServerManager serverManager = this.serverManager;
      if (serverManager == null) {
         String webServerFactoryBeanName = getWebServerFactoryBeanName();
         ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);
         boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
         // 这里创建容器管理时注入httpHandler
         this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
         getBeanFactory().registerSingleton("webServerGracefulShutdown",
               new WebServerGracefulShutdownLifecycle(this.serverManager));
         // 注册一个 web容器启动服务类,该类继承了SmartLifecycle
         getBeanFactory().registerSingleton("webServerStartStop",
               new WebServerStartStopLifecycle(this.serverManager));
      }
      initPropertySources();
   }
   protected HttpHandler getHttpHandler() {
        String[] beanNames = getBeanFactory().getBeanNamesForType(HttpHandler.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException(
                    "Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean.");
        }
        if (beanNames.length > 1) {
            throw new ApplicationContextException(
                    "Unable to start ReactiveWebApplicationContext due to multiple HttpHandler beans : "
                            + StringUtils.arrayToCommaDelimitedString(beanNames));
        }
        //容器上下文获取httpHandler
        return getBeanFactory().getBean(beanNames[0], HttpHandler.class);
    }

And this HTTPHandler is assembled by HTTPHandlerAutoConfiguration.

@Configuration
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnMissingBean(HttpHandler.class)
@AutoConfigureAfter({ WebFluxAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class HttpHandlerAutoConfiguration {
   @Configuration
   public static class AnnotationConfig {
      private ApplicationContext applicationContext;
      public AnnotationConfig(ApplicationContext applicationContext) {
         this.applicationContext = applicationContext;
      }
      //构建WebHandler
      @Bean
      public HttpHandler httpHandler() {
         return WebHttpHandlerBuilder.applicationContext(this.applicationContext)
               .build();
      }
   }

process related source code analysis-

org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#createWebServer. When creating the WebServerManager container manager, the corresponding web container instance will be obtained and the response HTTPHandler will be injected.

class WebServerManager {
   private final ReactiveWebServerApplicationContext applicationContext;
   private final DelayedInitializationHttpHandler handler;
   private final WebServer webServer;
   WebServerManager(ReactiveWebServerApplicationContext applicationContext, ReactiveWebServerFactory factory,
         Supplier<HttpHandler> handlerSupplier, boolean lazyInit) {
      this.applicationContext = applicationContext;
      Assert.notNull(factory, "Factory must not be null");
      this.handler = new DelayedInitializationHttpHandler(handlerSupplier, lazyInit);
      this.webServer = factory.getWebServer(this.handler);
   }
}

Take the Tomcat container as an example to show the creation process, using TomcatHTTPHandlerAdapter to connect the Servlet request to the HTTPHandler component.

public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFactory implements ConfigurableTomcatWebServerFactory {
    //隐藏部分代码   
    @Override
    public WebServer getWebServer(HttpHandler httpHandler) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        connector.setThrowOnFailure(true);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        configureEngine(tomcat.getEngine());
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        TomcatHttpHandlerAdapter servlet = new TomcatHttpHandlerAdapter(httpHandler);
        prepareContext(tomcat.getHost(), servlet);
        return getTomcatWebServer(tomcat);
    }
}

Finally, the Spring container is loaded through the SmartLifecycle implementation class WebServerStartStopLifecycle to start the Web container.

For the WebServerStartStopLifecycle registration process, please refer to: org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#createWebServer

3.2 Complete request processing flow

(Quoted from: https://blog.csdn.net )

The figure shows a call link for HTTP request processing. It is written in the Reactor Stream method, and only when subscirbe is finally called can the business logic be executed. When developing based on WebFlux, avoid blocking logic in the controller. List the following examples to see the difference in request processing between Spring MVC and Spring Webflux.

@RestControllerpublic
class TestController {
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    @GetMapping("sync")
    public String sync() {
        logger.info("sync method start");
        String result = this.execute();
        logger.info("sync method end");
        return result;
    }
    @GetMapping("async/mono")
    public Mono<String> asyncMono() {
        logger.info("async method start");
        Mono<String> result = Mono.fromSupplier(this::execute);
        logger.info("async method end");
        return result;
    }
    private String execute() {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello";
    }
}

Log output

2021-05-31 20:14:52.384  INFO 3508 --- [nio-8080-exec-2] c.v.internet.webflux.web.TestController  : sync method start
2021-05-31 20:14:57.385  INFO 3508 --- [nio-8080-exec-2] c.v.internet.webflux.web.TestController  : sync method end
2021-05-31 20:15:09.659  INFO 3508 --- [nio-8080-exec-3] c.v.internet.webflux.web.TestController  : async method start
2021-05-31 20:15:09.660  INFO 3508 --- [nio-8080-exec-3] c.v.internet.webflux.web.TestController  : async method end

From the above example, we can see that the sync() method blocks the request, while asyncMono() does not block the request and returns immediately. The specific business logic of the asyncMono() method is wrapped in the Supplier in Mono. When execute processes the business logic, it responds to the browser through a callback method.

Four, storage support

Once the control layer uses Spring Webflux, the security authentication layer and data access layer must use the Reactive API to truly achieve asynchronous non-blocking.

NOSQL Database

  • MongoDB (org.springframework.boot:spring-boot-starter-data-mongodb-reactive)。
  • Redis(org.springframework.boot:spring-boot-starter-data-redis-reactive)。

Relational Database

  • H2 (io.r2dbc:r2dbc-h2)
  • MariaDB (org.mariadb:r2dbc-mariadb)
  • Microsoft SQL Server (io.r2dbc:r2dbc-mssql)
  • MySQL (dev.miku:r2dbc-mysql)
  • jasync-sql MySQL (com.github.jasync-sql:jasync-r2dbc-mysql)
  • Postgres (io.r2dbc:r2dbc-postgresql)
  • Oracle (com.oracle.database.r2dbc:oracle-r2dbc)

Five, summary

There are many evaluations about Spring MVC and Spring WebFlux, and this article quotes them for a brief explanation. Reference: " Spring: Blocking vs non-blocking: R2DBC vs JDBC and WebFlux vs Web MVC ".

basically depends on

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-r2dbc</artifactId> 
</dependency> 
<!-- r2dbc 连接池 --> 
<dependency> 
    <groupId >io.r2dbc</groupId> 
    <artifactId>r2dbc-pool</artifactId> 
</dependency> 
<!--r2dbc mysql 库--> 
<dependency> 
    <groupId>dev.miku</groupId> 
    <artifactId>r2dbc- mysql</artifactId> 
</dependency> 
<!--自动配置需要引入一个嵌入式数据库类型对象--> 
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-jdbc</artifactId> 
</dependency>
<!-- 反应方程式 web 框架 webflux--> 
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-webflux</artifactId> 
</dependency>

The effect under the same data is as follows: ;

Spring MVC + JDBC performs best under low concurrency, but WebFlux + R2DBC uses the least memory for each processing request under high concurrency.

Spring WebFlux + R2DBC has excellent throughput performance under high concurrency.

Author: vivo internet server team-Zhou Changqing

vivo互联网技术
3.3k 声望10.2k 粉丝