Try Spring WebFlux (mainly Router Functions)

Overview

I was a little nervous when I heard Reactive Web Application by Spring Framework 5.0 at Java Day Tokyo 2017, so Spring 5 I tried Spring WebFlux added from.

Click here for reference materials. First Spring WebFlux (Part 1-Try Spring WebFlux) The author is Toshiaki Maki, who is the same as the speaker. (In fact, in the session, he said something like "I'm writing a blog, so if you read it, you can try it.")


Until you start the app for the time being

This time I used Spring Boot 2.0.0 M1. (Because Spring Boot supports Spring 5 from 2.0.0)

Create it with Spring Initializer. Set Spring Boot version to 2.0.0 M1 and add "Reactive Web". image.png (By the way, actuator is not supported at the time of M1. Sorry)

So, I started it and confirmed that Netty started up. It's OK for the time being. Below is an excerpt of the startup log.

2017-05-20 14:14:17.447  INFO 4880 --- [  restartedMain] o.s.w.r.r.m.a.ControllerMethodResolver   : Looking for @ControllerAdvice: org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext@30361c29: startup date [Sat May 20 14:14:16 JST 2017]; root of context hierarchy
2017-05-20 14:14:17.916  INFO 4880 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-05-20 14:14:17.944  INFO 4880 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-05-20 14:14:18.241  INFO 4880 --- [  restartedMain] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2017-05-20 14:14:18.246  INFO 4880 --- [  restartedMain] itaka.demo.WebfluxSampleApplication      : Started WebfluxSampleApplication in 2.181 seconds (JVM running for 3.946)

Routing definition with Router Functions

In WebFlux, if there is a @Bean such asRouterFunction <ServerResponse>, it will be treated as a routing definition. So, if you write it in the class targeted for Component Scan, it looks good anywhere.

//I have some static imports, so please refer to the reference material for details.
@Bean
public RouterFunction<ServerResponse> routes() {
    return route(GET("/"), req -> ok().body(Flux.just("Hello", "WebFlux"), String.class));
}

So, when I start it and access http: // localhost: 8080 /, "Hello WebFlux" is returned. that's all.

How to handle PathVariable?

  1. Describe as "{var}" in the argument of RequestPredicate (GET or POST) as before.
  2. Get with .pathVariable () of ServerRequest

You can go with the procedure of.

@Bean
public RouterFunction<ServerResponse> routes() {
    return RouterFunctions
            .route(GET("/hello/{name}"), req ->
                    ok().body(Flux.just("Hello", req.pathVariable("name")), String.class));
}

Routing priority

What happens if you define both the PathVariable routing above and the fixed path definition routing "/ hello / hoge"? From the conclusion, __ the first one is given priority __. simple.

When Path Variable routing is written first

RouterFunctions
 .route(GET("/hello/{name}"), req -> ...)
 .AndRroute(GET("/hello/hoge"), req -> ...);

-> Path Variable has priority.

When the routing of the fixed path definition is written first

RouterFunctions
 .route(GET("/hello/hoge"), req -> ...)
 .AndRroute(GET("/hello/{name}"), req -> ...);

-> Fixed path definition takes precedence.

If the number of Routing definitions increases

If it is about the scale of the sample, there is no problem even if you connect the route definition as described above, but in the actual application, this means that all Mapping is described in one Controller class __ So it's not good. Therefore, you can write a little neatly by doing the following.

  1. Create a method that returns RouterFunction <ServerResponse> and put it in another class
  2. Call method 1 within the original @ Bean defined method

HelloRouter.java


@Component
public class HelloRouter {

    private static final String PATH = "/hello";

    public RouterFunction<ServerResponse> route() {
        return RouterFunctions
                .route(GET(PATH), this::hello)
                .andRoute(GET(PATH + "/hoge"), this::helloHoge)
                .andRoute(GET(PATH + "/{name}"), this::helloName);
    }

    private Mono<ServerResponse> hello(ServerRequest req) {
        return ok().body(Flux.just("Hello", "WebFlux"), String.class);
    }

    private Mono<ServerResponse> helloName(ServerRequest req) {
        return ok().body(Flux.just("Hello", req.pathVariable("name")), String.class);
    }

    private Mono<ServerResponse> helloHoge(ServerRequest req) {
        return ok().body(Flux.just("Hoge", "Hoge"), String.class);
    }
}

HelloRouter route()Call


@Bean
public RouterFunction<ServerResponse> routes(HelloRouter helloRouter) {
    return helloRouter.route();
}

Furthermore, multiple RouterFunction <ServerResponse> can be connected by a method chain with .and.

.and()Rubbing multiple Router Functions with


//In StreamRouter"/strem"It is assumed that the routing of
@Bean
public RouterFunction<ServerResponse> routes(HelloRouter helloRouter, StreamRouter streamRouter) {
    return helloRouter.route()
            .and(streamRouter.route());
}

This sets the following routing.

(Addition) Improvement by RouterFunctions.nest ()

Comments, so try rewriting the above code further using nest (). (Thanks to Maki!)

RouterFunctions.nest()Routing definition in a hierarchical manner


public RouterFunction<ServerResponse> routes() {
    return nest(path("/hello"),
            route(GET("/"), this::hello)
                .andRoute(GET("/hoge"), this::helloHoge)
                .andRoute(GET("/{name}"), this::helloName));
}

By the way, I also statically imported RouterFunctions.route (), so it was very refreshing! Since you can understand the Routing contents only by looking at the Router Function part, this is also a better place than the definition in @Controller.


Play with infinite Stream

The Flux set in.body ()of ServerResponse is a class that implements Publisher of Reactive Stream, and it seems that you can realize a Creative Stream with a good feeling.

As a concrete example, let's implement something that "when you access it, the numbers are returned endlessly and the numbers are counted up". The method is as follows.

  1. Create an infinite Stream
  2. Make Flux from Stream of 1
  3. Set 2 Flux as body
@Bean
public RouterFunction<ServerResponse> routes() {
    Stream<Integer> stream = Stream.iterate(0, i -> i + 1);
    Flux<Integer> flux = Flux.fromStream(stream).delayElements(Duration.ofSeconds(1));

    return RouterFunctions
            .route(GET("/stream"), req ->
                    ok().contentType(MediaType.APPLICATION_STREAM_JSON).body(flux, Integer.class));
}

Now, when you access http: // localhost: 8080 / stream, the numbers will flow endlessly. However, since it flows with great momentum as it is, in the above example, .delayElements () is used to make it flow with an interval.


Summary

While saying "try WebFlux", I ended up experimenting with Router Functions more than half. In order to experience "WebFlux is amazing", it seems better to create an application that can be understood as being complicated or "super fast because it is Non Blocking".

Router Functiuons was more exciting than when I was listening to the Java Day Tokyo session. I used only GET this time, but of course other HttpMethods can be used, and complex routing conditions can be created using.and (),.or (),.negate (), etc. It looks fun because you can also do it. However, at the moment (Spring Boot 2.0.0), it seems that it cannot coexist with @Controller, so it is a little difficult to apply it to existing applications (all must be rewritten). If you do, you should do it with a new application.

The code of what I tried this time is as follows. (Since I refactored while writing, there is a slight deviation from the code in this article) IsaoTakahashi/webflux-sample - GitHub

Recommended Posts

Try Spring WebFlux (mainly Router Functions)
Try the Spring WebFlux tutorial
Try using Spring JDBC
Try Spring Boot from 0 to 100.
Try implementing a WebFlux session
Try using Spring Boot Security
Try implementing a WebFlux filter
Try Spring Boot on Mac