Spring WebFlux learning record

Keywords: Java Spring WebFlux reactor

This article explains what WebFlux is step by step in the following order:

1,Reactive Stream
2,Reactor
3,WebFlux

Among them, Reactive Stream is an important new feature of Java 9, and Reactor is equivalent to Java 8 Stream + java 9 Reactive Stream. The core of the final WebFlux is developed based on the relevant API of Reactor.

1,Reactive Stream

Reactive flow is a set of data processing specifications based on publish / subscribe mode introduced by Java 9. Its goal is to realize the asynchronous flow of data in a non blocking back pressure way.

This is a definition extracted from the Internet, in which back pressure was originally an engineering concept, which refers to the reverse pressure from downstream to upstream due to sudden thinning and sharp bending of the pipeline during pipeline transportation, which is extended to: in the process of data flow transmission, When the publisher produces data faster than the subscriber consumes data, the subscriber tells the publisher that more data is not needed for the time being. It's actually flow control.

Java 9's responsive flow is reduced to four interfaces:

  • Publisher
  • Subscriber
  • Subscription
  • Processor

Publisher is the publisher interface, Subscriber is the Subscriber interface, and Subscription interface is used to contact the publisher and Subscriber. You can understand it as "contract". Data streaming can be carried out only after the two have signed the contract. Processor interface is both publisher and Subscriber, which is used for intermediate data processing.

The following is a case written with these four interfaces:

public class FlowDemo {

    public static void main(String[] args) throws InterruptedException {

        // Define publisher
        SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();

        MyProcessor processor = new MyProcessor();

        publisher.subscribe(processor);

        // Define subscribers
        Flow.Subscriber<String> subscriber = new Flow.Subscriber<>() {

            private Flow.Subscription subscription;

            @Override
            public void onSubscribe(Flow.Subscription subscription) {
                // To save the subscription relationship, you need to use it to respond to the publisher
                this.subscription = subscription;
                // Request a data
                this.subscription.request(1);
            }

            @Override
            public void onNext(String item) {
                // Receive data and process
                System.out.println("subscriber receive data : " + item);
                // Request another data
                this.subscription.request(1);
                // When the target is reached, call cancel to tell the publisher that it will no longer receive data
                //this.subscription.cancel();
            }

            @Override
            public void onError(Throwable throwable) {
                // An exception occurred
                throwable.printStackTrace();
                // Stop receiving data
                this.subscription.cancel();
            }

            @Override
            public void onComplete() {
                System.out.println("subscriber Processing completed");
            }
        };

        // Establish subscription relationship between publisher and subscriber
        processor.subscribe(subscriber);

        // Production data and release
        for (int i = 0, j = 1; i < 4; i++, j *= -1) {
            System.out.println("publisher Generate data: " + i * j);
            publisher.submit(i * j);
        }

        // Close publisher
        publisher.close();

        // Main thread delay
        Thread.currentThread().join(10000);

        // Close transit
        processor.close();
    }

}

class MyProcessor extends SubmissionPublisher<String> implements Flow.Processor<Integer, String> {

    private Flow.Subscription subscription;

    @Override
    public void onSubscribe(Flow.Subscription subscription) {
        this.subscription = subscription;
        this.subscription.request(1);
    }

    @Override
    public void onNext(Integer item) {
        System.out.println("processor receive data : " + item);
        item = item < 0 ? -item : item;
        this.submit(item + "((after conversion)");
        this.subscription.request(1);
    }

    @Override
    public void onError(Throwable throwable) {
        throwable.printStackTrace();
        this.subscription.cancel();
    }

    @Override
    public void onComplete() {
        System.out.println("processor Transfer completed");
    }
}

In this case, the Publisher implementation class SubmissionPublisher is used to define a Publisher, and the Subscriber is used to define a Subscriber. Among the implemented methods, onSubscribe is used to establish contact with the Publisher, onNext is used to receive data, onError is used for exception handling, and onComplete is executed after the Publisher is closed, The intermediate performs the responsibilities of the Publisher and Subscriber by inheriting the SubmissionPublisher and implementing the interface Processor. The case execution results are as follows:

The publisher establishes a subscription relationship with the relay, and the relay establishes a subscription relationship with the subscriber. The publisher sends the circularly generated data to the relay, and the relay processes all negative numbers as positive numbers, and then sends them to the subscriber.

2,Reactor

Reactor and Spring are brother projects, focusing on Server-side responsive programming. They are a Java 8-based responsive library that implements the responsive stream specification. Although they are implemented based on Java 8, simply speaking, reactor can be understood as Java 8 stream + Java 9 reactive stream.

In Reactor, publishers are defined by two classes: Flux and Mono: a Flux object represents a sequence containing 0-N elements, and a Mono object represents a sequence containing 0-1 elements.

The code example is as follows:

public class ReactorDemo {

    public static void main(String[] args) {

        // Define subscribers
        Subscriber<Integer> subscriber = new Subscriber<Integer>() {

            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription subscription) {
                // To save the subscription relationship, you need to use it to respond to the publisher
                this.subscription = subscription;
                // Request a data
                this.subscription.request(1);
            }

            @Override
            public void onNext(Integer item) {
                // Receive data and process
                System.out.println("Data received: " + item);
                // Request another data
                this.subscription.request(1);
                // When the target is reached, call cancel to tell the publisher that it will no longer receive data
                //this.subscription.cancel();
            }

            @Override
            public void onError(Throwable throwable) {
                // An exception occurred
                throwable.printStackTrace();
                // Stop receiving data
                this.subscription.cancel();
            }

            @Override
            public void onComplete() {
                System.out.println("Processing completed");
            }
        };

        String[] strings = {"1","2","3"};

        Flux.fromArray(strings)
                // jdk8 stream
                .map(Integer::parseInt)
                // jdk9 reactive stream
                .subscribe(subscriber);
    }

}

The subscriber in the code is actually copied from the code in the first step. You can see that the two codes are different in the introduction of the subscriber's package. The reason is that the case in the first step is developed based on Java 9, while the case in the second step is developed based on Java 8 + Reactor related jar packages.

The code first uses the fromArray method of Flux to transform the string array into a data stream. Then, because the data consumed by the subscribers is Integer type, String is converted to Integer by map, and finally the subscribe method is used to convective consumption, which can be passed to a subscriber.

The sample execution results are as follows:

3,Spring WebFlux

The main purpose of speaking about Reactor is to introduce Flux and Mono. In fact, understanding this part is enough to learn WebFLux (you may be able to learn if you don't understand it).

Spring WebFlux is a responsive programming framework launched with spring 5. Compared with Spring MVC, the biggest feature of WebFlux is non blocking. This part is directly explained by code.

Class 3.1 MVC development mode

First, create a test project and add the following dependencies to the pom file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Then create a controller and write the following test code:

@GetMapping("/1")
public String get1() {
    long start = System.currentTimeMillis();
    log.info("get1 start");
    String str = createStr("1");
    log.info("get1 end");
    long end = System.currentTimeMillis();
    log.info("get1 use time: {}", end - start);
    return str;
}

@GetMapping("/2")
public Mono<String> get2() {
    long start = System.currentTimeMillis();
    log.info("get2 start");
    Mono<String> mono = Mono.fromSupplier(() -> createStr("2"));
    log.info("get2 end");
    long end = System.currentTimeMillis();
    log.info("get2 use time: {}", end - start);
    return mono;
}

private String createStr(String str) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return str;
}

Among them, the get1 method is a typical MVC writing method, calling createStr method in the method, in order to simulate blocking, adding 5 seconds delay in the createStr method, and the get2 method is the writing of WebFlux. In the method body, a data stream is constructed by statement Mono.fromSupplier (() - > createStr ("2")). If the flow is not consumed in the method body, createstr will not be executed, and the blocking time of 5 seconds will not exist. The code execution results are as follows:

It can be seen that the get2 method does not generate delay, because it only returns a flow, which is consumed by the Spring WebFlux framework after the method body is executed. Taking running on Tomcat as an example, this writing method will not cause the Servlet thread to be in a blocked state, so as to improve the throughput.

The above shows the case where the return value is Mono. The following shows a case where the return value is Flux:

@GetMapping(value = "/3", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> flux() {
    long start = System.currentTimeMillis();
    log.info("get3 start");
    Flux<String> stringFlux = Flux.fromStream(IntStream.range(1, 5).mapToObj(
        i -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "flux data " + i + "\n";
        }
    ));
    log.info("get3 end");
    long end = System.currentTimeMillis();
    log.info("get3 use time: {}", end - start);
    return stringFlux;
}

The biggest difference between Flux and Mono is that the data stream generated by Flux may have multiple elements. There are two ways to return to the caller: one is to return all elements after consumption, and the other is to return one after consumption. Mediatype.text in the above code_ EVENT_ STREAM_ Value is to tell the browser to return one by one, as follows:
First return:

Fourth return:

Similarly, the time consumed in the method is as follows:

3.2 Router Function development mode

In fact, the development mode of the above code is not different from that of Spring MVC. It is also to write a controller, in which the methods are annotated with @ RequestMapping, and WebFlux has another development called Router Function.

Corresponding to the methods in the controller, the Router Function development mode has the HandlerFunction class:

Mono<T extends ServerResponse> handle(ServerRequest request);

The request routing is implemented by the class RouterFunction:

Mono<HandlerFunction<T>> route(ServerRequest request);

For example, rewrite the above get2 method as follows:

@Component
public class TestHandler {

    public Mono<ServerResponse> get2(ServerRequest serverRequest) {
        return ok().contentType(MediaType.TEXT_PLAIN).body(Mono.fromSupplier(() -> createStr("2")), String.class);
    }

    private String createStr(String str) {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return str;
    }

}

@Configuration
public class RouterConfig {

    @Resource
    private TestHandler testHandler;

    @Bean
    public RouterFunction<ServerResponse> timerRouter() {
        return route(GET("/get2"), testHandler::get2);
    }
}

After starting the service, accessing / get will call the get2 method of testHandler to process the request.

reference:

1,https://blog.csdn.net/get_set/article/details/79466657

2,https://www.bilibili.com/video/BV1y44y117pp?share_source=copy_web

Posted by johnwayne77 on Tue, 19 Oct 2021 14:12:09 -0700