1
Introduction to Spring WebFlux
2017-11-24
Toshiaki Maki (@making) #jsug #sf_a1
Who am I ?
2
Toshiaki Maki (@making) https://blog.ik.am
Sr. Solutions Architect @Pivotal Japan
Spring Framework 💖
Cloud Foundry 💖
DEMO
4
📱📱📱📱📱📱📱📱📱
📱📱📱📱📱📱📱📱📱4
📱📱📱📱📱📱📱📱📱4
🗄
📈
Devices Dashboard
HTTP POST
Server-Sent Events
∞🔄
http://bit.ly/demoiot
❓How many threads are this app using?
5
❓How many threads are this app using?
5
1. 200 -
2. 100 - 200
3. 50 - 100
4. 10 - 50
5. 1 - 10
6
Web Stacks in Spring 5
7
Servlet Container
Servlet API
Spring MVC
Servlet Stack
Web Stacks in Spring 5
7
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
Web Stacks in Spring 5
7
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet StackBlocking
Web Stacks in Spring 5
7
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet StackBlocking
Web Stacks in Spring 5
7
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet StackBlocking
Non-Blocking
Web Stacks in Spring 5
7
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet StackBlocking
Non-Blocking
8
Servlet StackThread
Thread
Thread
Thread
Thread
…⏳
9
Reactive Stack
🔄 Thread
🔄 Thread
🔄 Thread
🔄 Thread
❓Why are we introducing Spring WebFlux?
10
❓Why are we introducing Spring WebFlux?
10
The goal of Spring WebFlux is to offer Spring
developers a non-blocking event-loop style
programming model similar to node.js.
the non-blocking async programming model is more
efficient for latency-sensitive workloads.
– Blocking threads consume resources
– mobile applications and interconnected
microservices
❓Why are we introducing Spring WebFlux?
10
The goal of Spring WebFlux is to offer Spring
developers a non-blocking event-loop style
programming model similar to node.js.
Web Stacks in Spring 5
11
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
Publisher Subscriber
Reactive Streams
2
Publisher Subscriber
subscribe
Reactive Streams
2
Publisher Subscriber
request(n)
Backpressure
Reactive Streams
2
Publisher Subscriber
Backpressure
onNext(data)
onNext(data)
onNext(data)
Reactive Streams
2
Publisher Subscriber
Backpressure
Error|Complete
Reactive Streams
2
13
13
4
Flux<T>
Mono<T>
5
Flux<T> is a Publisher<T> for 0..n elements
6
Flux<T>
Mono<T>
7
Mono<T> is a Publisher<T> for 0..1 element
Reactor
18
Flux<String> s = Flux
.just("a", "b", "c", "d")
.map(String::toUpperCase);
s.log("demo").subscribe();
Reactor
18
Flux<String> s = Flux
.just("a", "b", "c", "d")
.map(String::toUpperCase);
s.log("demo").subscribe();
Need to be subscribed!
Reactor
18
Flux<String> s = Flux
.just("a", "b", "c", "d")
.map(String::toUpperCase);
s.log("demo").subscribe();
Need to be subscribed!
00:58:33.902 [main] INFO demo - | request(unbounded)
00:58:33.903 [main] INFO demo - | onNext(A)
00:58:33.903 [main] INFO demo - | onNext(B)
00:58:33.903 [main] INFO demo - | onNext(C)
00:58:33.903 [main] INFO demo - | onNext(D)
00:58:33.903 [main] INFO demo - | onComplete()
Reactor
19
Flux<String> s = Flux
.just("a", "b", "c", "d")
.map(String::toUpperCase)
.delayElements(Duration.ofSeconds(1));
s.log("demo").subscribe();
Reactor
19
Flux<String> s = Flux
.just("a", "b", "c", "d")
.map(String::toUpperCase)
.delayElements(Duration.ofSeconds(1));
s.log("demo").subscribe();
00:59:42.949 [main] INFO demo - request(unbounded)
00:59:43.992 [parallel-1] INFO demo - onNext(A)
00:59:44.994 [parallel-2] INFO demo - onNext(B)
00:59:45.997 [parallel-3] INFO demo - onNext(C)
00:59:46.999 [parallel-4] INFO demo - onNext(D)
00:59:47.001 [parallel-4] INFO demo - onComplete()
Reactor (Hot Stream♨)
20
Flux<String> s = Flux.<String>create(sink ->
sink.next(...);
});
s.log("demo").subscribe();
Reactor (Hot Stream♨)
21
twitter4j.TwiterStream tw = ...;
Flux<String> s = Flux.<String>create(sink ->
sink.next(status.getText());
});
s.log("tweet").subscribe();
Reactor (Hot Stream♨)
22
twitter4j.TwiterStream tw = ...;
Flux<String> s = Flux.<String>create(sink ->
tw.addListener(new StatusAdapter() {
public void onStatus(Status status) {
sink.next(status.getText()); }
public void onException(Exception e) {
sink.error(e); }});
sink.onCancel(tw::shutdown);
tw.sample();
});
s.log("tweet").subscribe();
Operators in Reactor
23
• map / indexed / flatMap / flatMapMany
• collectList / collectMap / count
• concat / merge / zip / when / combineLatest
• repeat / interval
• filter / sample / take / skip
• window / windowWhile / buffer
• ...
https://projectreactor.io/docs/core/release/reference/docs/index.html#which-operator
zip
24
zip
24
zip
25
Flux<GHUser> github = findGitHubUsers("foo", "bar",
"hoge");

Flux<FBUser> facebook = findFacebookUsers("foo",
"bar", "hoge");
Flux<User> users = Flux.zip(github, facebook)
.map(tpl -> new User(tpl.getT1(), tpl.getT2()));
users.subscribe();
flatMap
26
flatMap
27
Flux<GHUser> github = findGitHubUsers("foo", "bar",
"hoge");

Flux<User> users = github.flatMap(g
-> findTweets(g)
.collectList()
.map(tweets -> new User(g, tweets)));
users.subscribe();
flatMap
28
Flux<GHUser> github = findGitHubUsers("foo", "bar",
"hoge");

Flux<User> users = github.map(g
-> findTweets(g)
.collectList()
.map(tweets -> new User(g, tweets)));
users.subscribe();
flatMap
28
Flux<GHUser> github = findGitHubUsers("foo", "bar",
"hoge");

Flux<User> users = github.map(g
-> findTweets(g)
.collectList()
.map(tweets -> new User(g, tweets)));
users.subscribe();
⚠ Can be compiled but
findTweets won't be
subscribed
flatMap
29
Flux<GHUser> github = findGitHubUsers("foo", "bar",
"hoge");
Flux<User> users = github.map(g -> {
Mono<User> u = findTweets(g)
.collectList()
.map(tweets -> new User(g, tweets));
u.subscribe();
return u;
});
users.subscribe();
This will work,
but use flatMap instead
Web Stacks in Spring 5
30
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
Spring MVC controller
31
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public String hello() {
return hello.sayHello();
}
}
Spring MVC controller
31
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public String hello() {
return hello.sayHello();
}
}
Blocking / Synchronous
Spring WebFlux controller
32
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public String hello() {
return hello.sayHello();
}
}
Spring WebFlux controller
32
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public String hello() {
return hello.sayHello();
}
}
Non-blocking / Synchronous
Spring WebFlux controller
33
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello);
}
}
Spring WebFlux controller
33
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello);
}
}
Non-blocking / Asynchronous
Spring WebFlux controller
33
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello);
}
}
Non-blocking / Asynchronous
You don't need to subscribe the stream
Spring WebFlux controller
34
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello)
.flatMap(foo::abc)
.map(bar::xyz);}
Non-blocking / Asynchronous
Spring WebFlux controller
34
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello)
.flatMap(foo::abc)
.map(bar::xyz);}
Non-blocking / Asynchronous
Could be executed
in the other thread
Spring MVC controller
35
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello)
.flatMap(foo::abc)
.map(bar::xyz);}
Blocking / Asynchronous
Spring MVC controller
35
@RestController
public class HelloController {
private final Hello hello;
public HelloController(Hello hello) {
this.hello = hello;
}
@GetMapping("hello")
public Mono<String> hello() {
return Mono.fromCallable(hello::sayHello)
.flatMap(foo::abc)
.map(bar::xyz);}
Blocking / Asynchronous
Async support in
Servlet 3
∞ Stream
36
@RestController
public class HelloController {
@GetMapping("hello")
public Flux<String> hello() {
Flux<String> hello = Flux.just("hello")
.delayElement(Duration.ofMillis(100));
hello.subscribe();
return hello.repeat(); // ∞ Stream
}
}
DEMO
Content Negotiation
38
Accept: text/event-stream
Accept: application/stream+json
Accept: application/json
Content Negotiation
38
Accept: text/event-stream
Accept: application/stream+json
Accept: application/json
✅ Backpressure
✅ Backpressure
❌ Backpressure
Content Negotiation
39
@GetMapping("hello")
public Flux<Message> hello() {
return Mono.just(new Message("a"))
.repeat()
.take(100)
.log("message");
}
40
$ curl -v -H "Accept: text/event-stream" localhost:8080/messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: text/event-stream
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream
<
data:{"text":"a"}
data:{"text":"a"}
data:{"text":"a"}
...
40
$ curl -v -H "Accept: text/event-stream" localhost:8080/messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: text/event-stream
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream
<
data:{"text":"a"}
data:{"text":"a"}
data:{"text":"a"}
...
INFO 48330 --- [ctor-http-nio-2] message : request(1)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : request(31)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : request(24)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-2] message : request(24)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-2] message : onComplete()
40
$ curl -v -H "Accept: text/event-stream" localhost:8080/messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: text/event-stream
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream
<
data:{"text":"a"}
data:{"text":"a"}
data:{"text":"a"}
...
INFO 48330 --- [ctor-http-nio-2] message : request(1)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : request(31)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : request(24)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-2] message : request(24)
INFO 48330 --- [ctor-http-nio-2] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-2] message : onComplete()
Backpressure
(FYI) In case of Servlet Stack
41
(FYI) In case of Servlet Stack
41
INFO 61528 --- [nio-8080-exec-1] message : request(1)
INFO 61528 --- [nio-8080-exec-1] message : onNext(a)
INFO 61528 --- [ MvcAsync1] message : request(1)
INFO 61528 --- [ MvcAsync1] message : onNext(a)
INFO 61528 --- [ MvcAsync2] message : request(1)
... ...
INFO 61528 --- [ MvcAsync98] message : request(1)
INFO 61528 --- [ MvcAsync98] message : onNext(a)
INFO 61528 --- [ MvcAsync99] message : request(1)
INFO 61528 --- [ MvcAsync99] message : onNext(a)
INFO 61528 --- [ MvcAsync99] message : onComplete()
(FYI) In case of Servlet Stack
41
INFO 61528 --- [nio-8080-exec-1] message : request(1)
INFO 61528 --- [nio-8080-exec-1] message : onNext(a)
INFO 61528 --- [ MvcAsync1] message : request(1)
INFO 61528 --- [ MvcAsync1] message : onNext(a)
INFO 61528 --- [ MvcAsync2] message : request(1)
... ...
INFO 61528 --- [ MvcAsync98] message : request(1)
INFO 61528 --- [ MvcAsync98] message : onNext(a)
INFO 61528 --- [ MvcAsync99] message : request(1)
INFO 61528 --- [ MvcAsync99] message : onNext(a)
INFO 61528 --- [ MvcAsync99] message : onComplete()
✅ Backpressure
42
$ curl -v -H "Accept: application/stream+json" localhost:8080/
messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/stream+json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json;charset=UTF-8
<
{"text":"a"}
{"text":"a"}
{"text":"a"}
...
42
$ curl -v -H "Accept: application/stream+json" localhost:8080/
messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/stream+json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json;charset=UTF-8
<
{"text":"a"}
{"text":"a"}
{"text":"a"}
...
INFO 48330 --- [ctor-http-nio-4] message : request(1)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : request(31)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : request(24)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-4] message : request(24)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-4] message : onComplete()
42
$ curl -v -H "Accept: application/stream+json" localhost:8080/
messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/stream+json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json;charset=UTF-8
<
{"text":"a"}
{"text":"a"}
{"text":"a"}
...
INFO 48330 --- [ctor-http-nio-4] message : request(1)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : request(31)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : request(24)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-4] message : request(24)
INFO 48330 --- [ctor-http-nio-4] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-4] message : onComplete()
✅ Backpressure
43
$ curl -v -H "Accept: application/json" localhost:8080/messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
<
[{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"},
{"text":"a"},{"text":"a"}]
43
$ curl -v -H "Accept: application/json" localhost:8080/messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
<
[{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"},
{"text":"a"},{"text":"a"}]
INFO 48330 --- [ctor-http-nio-3] message : request(unbounded)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onComplete()
43
$ curl -v -H "Accept: application/json" localhost:8080/messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/json;charset=UTF-8
<
[{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"},
{"text":"a"},{"text":"a"}]
INFO 48330 --- [ctor-http-nio-3] message : request(unbounded)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
... ...
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onNext(a)
INFO 48330 --- [ctor-http-nio-3] message : onComplete()
❌ Backpressure
Flux -> Mono
44
@GetMapping("hello")
public Mono<List<Message>> hello() {
return Mono.just(new Message("a"))
.repeat()
.take(100)
.collectList()
.log("message");
}
Flux -> Mono
45
@GetMapping("hello")
public Mono<List<Message>> hello() {
return Mono.just(new Message("a"))
.repeat()
.take(100)
.collectList()
.log("message");
}
$ curl -v -H "Accept: application/stream+json" localhost:8080/
messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/stream+json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json;charset=UTF-8
<
[{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"},
{"text":"a"},{"text":"a"}]
Flux -> Mono
45
@GetMapping("hello")
public Mono<List<Message>> hello() {
return Mono.just(new Message("a"))
.repeat()
.take(100)
.collectList()
.log("message");
}
$ curl -v -H "Accept: application/stream+json" localhost:8080/
messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/stream+json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json;charset=UTF-8
<
[{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"},
{"text":"a"},{"text":"a"}]
INFO 48330 --- [ctor-http-nio-3] message : | request(1)
INFO 48330 --- [ctor-http-nio-3] message : | onNext([a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a])
INFO 48330 --- [ctor-http-nio-3] message : | request(31)
INFO 48330 --- [ctor-http-nio-3] message : | onComplete()
Flux -> Mono
45
@GetMapping("hello")
public Mono<List<Message>> hello() {
return Mono.just(new Message("a"))
.repeat()
.take(100)
.collectList()
.log("message");
}
$ curl -v -H "Accept: application/stream+json" localhost:8080/
messages
> GET /messages HTTP/1.1
> Host: localhost:8080
> Accept: application/stream+json
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: application/stream+json;charset=UTF-8
<
[{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"},
{"text":"a"},{"text":"a"}]
INFO 48330 --- [ctor-http-nio-3] message : | request(1)
INFO 48330 --- [ctor-http-nio-3] message : | onNext([a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a,
a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a])
INFO 48330 --- [ctor-http-nio-3] message : | request(31)
INFO 48330 --- [ctor-http-nio-3] message : | onComplete()
❌ Backpressure
Spring WebFlux controller (Receiving Flux)
46
@RestController
public class HelloController {
@PostMapping("upper")
public Flux<String>
upper(@RequestBody Flux<String> input) {
Flux<String> output = input
.map(String::toUpperCase);
return output;
}
}
47
$ telnet localhost 8080
POST /upper HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
Accept: text/event-stream
Content-Type: text/plain
3
foo
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream
5
data:
4
FOO
1
4
java
5
data:
5
JAVA
1
47
$ telnet localhost 8080
POST /upper HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
Accept: text/event-stream
Content-Type: text/plain
3
foo
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream
5
data:
4
FOO
1
4
java
5
data:
5
JAVA
1
Request Body
47
$ telnet localhost 8080
POST /upper HTTP/1.1
Host: localhost:8080
Transfer-Encoding: chunked
Accept: text/event-stream
Content-Type: text/plain
3
foo
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream
5
data:
4
FOO
1
4
java
5
data:
5
JAVA
1
Request Body
Response Body
Spring WebFlux controller (Receiving Flux)
48
@PostMapping("upper")
public Flux<String>
upper(@RequestBody Flux<String> input) {
Flux<String> output = input
.map(String::toUpperCase)
.flatMap(s -> Flux
.interval(Duration.ofSeconds(1))
.map(i -> s + i)
.take(3));
return output;
}
49
3
foo
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream
5
data:
5
FOO0
1
5
data:
5
FOO1
1
5
data:
5
FOO2
1
DEMO
Spring MVC controller (Receiving Flux)
51
@RestController
public class HelloController {
@PostMapping("upper")
public Flux<String>
hello(@RequestBody Flux<String> input) {
Flux<String> output = input
.map(String::toUpperCase);
return output;
}
}
Spring MVC controller (Receiving Flux)
51
@RestController
public class HelloController {
@PostMapping("upper")
public Flux<String>
hello(@RequestBody Flux<String> input) {
Flux<String> output = input
.map(String::toUpperCase);
return output;
}
}
🙅
Spring MVC controller (Receiving Flux)
51
@RestController
public class HelloController {
@PostMapping("upper")
public Flux<String>
hello(@RequestBody Flux<String> input) {
Flux<String> output = input
.map(String::toUpperCase);
return output;
}
}
🙅Reactive type are supported only as
controller method return values
Broadcast stream
52
@RestController
public class HelloController {
private final Flux<String> flux;
public HelloController() {
this.flux = this.createHotStream().share();
this.flux.subscribe();
}
@GetMapping("hello")
public Flux<String> hello() {
return this.flux;
}}
Broadcast stream
52
@RestController
public class HelloController {
private final Flux<String> flux;
public HelloController() {
this.flux = this.createHotStream().share();
this.flux.subscribe();
}
@GetMapping("hello")
public Flux<String> hello() {
return this.flux;
}}
this stream is shared by all http
clients!
53
📱📱📱📱📱📱📱📱📱
📱📱📱📱📱📱📱📱📱53
📱📱📱📱📱📱📱📱📱53
🗄
📈
Devices Dashboard
HTTP POST
Server-Sent Events
∞🔄
♨
54
📱📱📱📱📱📱📱📱📱
📱📱📱📱📱📱📱📱📱54
📱📱📱📱📱📱📱📱📱54
🗄
📈
Devices Dashboard
HTTP POST
Server-Sent Events
∞🔄
📈📈📈
♨
54
📱📱📱📱📱📱📱📱📱
📱📱📱📱📱📱📱📱📱54
📱📱📱📱📱📱📱📱📱54
🗄
📈
Devices Dashboard
HTTP POST
Server-Sent Events
∞🔄
📈📈📈
♨ Shared
Web Stacks in Spring 5
55
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
Web Stacks in Spring 5
55
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
•Annotated Controller
•Function Endpoints (WebFlux.fn)
@Configuration
public class RouterConfig {
@Bean
public RouterFunctions<ServerResponse> routes() {
return route(GET("hello"), req -> {
return ServerReponse.ok()
.body(Mono.just("hello"), String.class);
});
}
}
Spring WebFlux.fn
56
@Configuration
public class RouterConfig {
@Bean
public RouterFunctions<ServerResponse> routes() {
return route(GET("hello"), req -> {
return ServerReponse.ok()
.body(Mono.just("hello"), String.class);
});
}
}
lambda
Spring WebFlux.fn
56
Spring WebFlux.fn
57
@Configuration
public class RouterConfig {
@Bean
public RouterFunctions<ServerResponse>
routes(HelloHandler helloHandler) {
return route(GET("hello"), helloHandler::hello);
}
}
Spring WebFlux.fn
58
public RouterFunctions<ServerResponse>
routes(PersonHandeler ph) {
return route(GET("person"), ph::findAll)
.andRoute(POST("person"), ph::create)
.andRoute(GET("person/{id}"), ph::findOne)
.andRoute(PUT("person/{id}"), ph::update)
.andRoute(DELETE("person/{id}"), ph::delete));
}
Spring WebFlux.fn
59
public RouterFunctions<ServerResponse>
routes(PersonHandeler ph) {
return nest(path("person"),
route(GET("/"), ph::findAll)
.andRoute(POST("/"), ph::create)
.andRoute(GET("/{id}"), ph::findOne)
.andRoute(PUT("/{id}"), ph::update)
.andRoute(DELETE("/{id}"), ph::delete)));
}
Web Stacks in Spring 5
60
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
Web Stacks in Spring 5
60
Servlet Container
Servlet API
Spring MVC
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Servlet Stack
Reactive Web Client
Reactive Web Client
61
@RestController
public class HelloController {
private final WebClient client;
public HelloController(WebClinet.Builer b) {
this.client = b.build();
}
@GetMapping("hello")
public Flux<String> hello() {
return this.client.get().uri("http://blahblah")
.retrieve().bodyToFlux(String.class);
}}
Reactive Web Client
62
Flux<User> users = webClient.get()
.uri("http://user-service")
.exchange() // => Mono<ClientResponse>
.flatMap(r -> r.bodyToFlux(User.class));
// short-cut
Flux<User> users = webClient.get()
.uri("http://user-service")
.retrieve()
.bodyToFlux(User.class);
Reactive Web Client
63
Mono<User> user = webClient.get()
.uri("http://user-service/{id}", id)
.header("X-Foo", "foo")
.retrieve().bodyToMono(User.class);
Mono<User> user = ...
Mono<Void> created = webClient.post()
.uri("http://user-service")
.body(user, User.class)
.retrieve().bodyToMono(Void.class);
From RestTemplate to WebClient
64
@Controller
public class UserController {
@GetMapping("users")
public String hello(Model model) {
List<User> users = restTemplate
.getForObject("/users", List.class);
model.addAttribute("users", users);
return "users";
}
}
From RestTemplate to WebClient
65
@Controller
public class UserController {
@GetMapping("users")
public String hello(Model model) {
Flux<User> users = client.get().uri("/users")
.retrieve().bodyToFlux(String.class);
model.addAttribute("users", users);
return "users";
}
}
66
<html>
<body>
<ul>
<li data-th-each="user : ${user}">[[${user}]]</li>
</ul>
</body>
</html>
66
<html>
<body>
<ul>
<li data-th-each="user : ${user}">[[${user}]]</li>
</ul>
</body>
</html>
😀 Spring WebFlux
66
<html>
<body>
<ul>
<li data-th-each="user : ${user}">[[${user}]]</li>
</ul>
</body>
</html>
😀 Spring WebFlux
😨 Spring MVC
From RestTemplate to WebClient (Spring MVC)
67
@Controller
public class UserController {
@GetMapping("users")
public Mono<String> hello(Model model) {
Flux<User> users = client.get().uri("/users")
.retrieve().bodyToFlux(String.class);
users.collectList().map(x -> {
model.addAttribute("users", x);
return "users";
});
}
From RestTemplate to WebClient (Spring MVC)
67
@Controller
public class UserController {
@GetMapping("users")
public Mono<String> hello(Model model) {
Flux<User> users = client.get().uri("/users")
.retrieve().bodyToFlux(String.class);
users.collectList().map(x -> {
model.addAttribute("users", x);
return "users";
});
}
😀 Spring MVC
From RestTemplate to WebClient (Spring MVC)
68
@Controller
public class UserController {
@GetMapping("users")
public Mono<String> hello(Model model) {
Flux<User> users = ...;
Mono<Sting> foo = ...;
return Mono.zip(user.collectList(), foo)
   .map(tpl -> {
model.addAttribute("users", tpl.getT1());
model.addAttribute("foo", tpl.getT2());
return "users";
});}}
69
String url = "http://httpbin.org/delay/1";
@GetMapping("hello")
public Mono<String> hello() {
Mono<String> mono = Mono.just(restTemplate
.getForObject(url));
return mono;
}
⚠ Don't block the thread in WebFlux!
69
String url = "http://httpbin.org/delay/1";
@GetMapping("hello")
public Mono<String> hello() {
Mono<String> mono = Mono.just(restTemplate
.getForObject(url));
return mono;
}
⚠ Don't block the thread in WebFlux!
69
String url = "http://httpbin.org/delay/1";
@GetMapping("hello")
public Mono<String> hello() {
Mono<String> mono = Mono.just(restTemplate
.getForObject(url));
return mono;
} 😠 Blocking!
⚠ Don't block the thread in WebFlux!
70
String url = "http://httpbin.org/delay/1";
@GetMapping("hello")
public Mono<String> hello() {
Mono<String> mono = Mono
.fromCallable(() ->restTemplate.getForObject(url))
.subscribeOn(Schedulers.elastic());
return mono;
}
⚠ Don't block the thread in WebFlux!
70
String url = "http://httpbin.org/delay/1";
@GetMapping("hello")
public Mono<String> hello() {
Mono<String> mono = Mono
.fromCallable(() ->restTemplate.getForObject(url))
.subscribeOn(Schedulers.elastic());
return mono;
} Switch the execution context
71
Concurrency
Throughput [trans / sec]
Core i7 2.7 GHz
4 Core x HT
Reactive Support in Spring Projects
72
Spring Data
Reactive Stack
Netty, Servlet 3.1+,
Undertow
Reactive Streams
Spring WebFlux
Spring Security
Thymeleaf
Spring Data Kay
73
Reactive support for
• Redis
• MongoDB
• Couchbase
• Cassandra
Infinite streams from the database with @Tailable
Reactive Non-Blocking Data Access
74
public interface ReactiveCrudRepository<ID,T> {
Mono<T> findById(ID id);
Mono<T> findById(Mono<ID> id);
Flux<T> findAll();
Mono<Long> count();
Mono<T> save(T entity);
Mono<T> saveAll(Publisher<T> entityStream);
Mono<Void> delete(T entity)
// ...
}
Tailable Cursor Support for MongoDB
75
public interface MessageRepository
extends ReactiveMongoRepository<Message,String>
{
@Tailable
Flux<Message>
findByUpdatedAtGreaterThan(Instant target);
}
Broadcast updated messages in MongoDB
76
@RestController
public class HelloController {
private final Flux<Message> messages;
public HelloController(MessageRepository repo) {
this.messages = repo
.findByUpdatedAtGreaterThan(Instant.now())
.share();
this.messages.subscribe();
}
@GetMapping("messages")
public Flux<Message> messages() {
return this.messages;
}}
But, but, JDBC is blocking .... 😭
77
But, but, JDBC is blocking .... 😭
77
Let's see what will happen in next Java
But, but, JDBC is blocking .... 😭
77
Let's see what will happen in next Java
https://static.rainfocus.com/oracle/oow17/sess/1491948952321001dm4m/PF/JavaOne%2017%20-
%20CON1491_1506954898905001IipH.pdf
java.sql2
78
http://cr.openjdk.java.net/~lancea/apidoc/java/sql2/package-summary.html
java.sql2
78
http://cr.openjdk.java.net/~lancea/apidoc/java/sql2/package-summary.html
😢 Does not seem to support
Reactive Streams (j.u.c.Flow)
Spring Security Reactive
79
@Bean
public SecurityWebFilterChain
springWebFilterChain(HttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/v2/**").hasRole("ADMIN")
.and().httpBasic().and().build();
}
@Bean
public MapUserDetailsRepository userDetailsRepository() {
return new MapUserDetailsRepository(...);
}
Thymeleaf 3.0 reactive support
80
• "full mode"
• "chunked mode"

=> Progressive rendering. Good for large pages
• "data-driven mode" 

=> Rendering template fragments as Server-Sent Events

• https://github.com/thymeleaf/thymeleaf-spring/issues/132
• https://github.com/spring-projects/spring-boot/issues/8124
• https://speakerdeck.com/dfernandez/o-2017-getting-thymeleaf-ready-
for-spring-5-and-reactive?slide=18
transfer-encoding: chunked
Data-driven mode
81
@GetMapping("users")
public String hello(Model model) {
Flux<User> users = userService.findAll();
ReactiveDataDriverContextVariable v
= new ReactiveDataDriverContextVariable(users, 1);
model.addAttribute("users", v);
return "users";
}
DEMO
Reactive Application Patterns (as of 2017)
83
Reactive Application Patterns (as of 2017)
83
Spring WebFluxSpring WebFlux
Frontend Backend
HTTP NoSQL
WebClient Spring Data
Reactive Application Patterns (as of 2017)
84
Spring MVCSpring WebFlux
Frontend Backend
HTTP RDB
JDBC
WebClient
Reactive Application Patterns (as of 2017)
85
Spring MVCSpring MVC
Frontend Backend
HTTP RDB
JDBC
WebClient
Spring Cloud Gateway
86
A Gateway built on Spring Framework 5.0 and Spring
Boot 2.0 providing routing and more
http://cloud.spring.io/spring-cloud-gateway/
http://cloud.spring.io/spring-cloud-gateway/2.0.x/single/
spring-cloud-gateway.html
Start Spring 5 & Spring Boot 2
87
Servlet Stack ⇄ Reactive Stack
88
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Reactive Stack
Servlet Stack
Servlet Stack ⇄ Reactive Stack
89
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Reactive Stack
Servlet Stack
+
WebClient
Thanks!
90
Resources
• Servlet vs Reactive Stacks in Five Use Cases
• Spring Boot 2 0 Web Applications
• Spring Framework 5: Themes & Trends
• Why Spring ❤ Kotlin

Introduction to Spring WebFlux #jsug #sf_a1

  • 1.
    1 Introduction to SpringWebFlux 2017-11-24 Toshiaki Maki (@making) #jsug #sf_a1
  • 2.
    Who am I? 2 Toshiaki Maki (@making) https://blog.ik.am Sr. Solutions Architect @Pivotal Japan Spring Framework 💖 Cloud Foundry 💖
  • 3.
  • 4.
  • 5.
    ❓How many threadsare this app using? 5
  • 6.
    ❓How many threadsare this app using? 5 1. 200 - 2. 100 - 200 3. 50 - 100 4. 10 - 50 5. 1 - 10
  • 7.
  • 8.
    Web Stacks inSpring 5 7 Servlet Container Servlet API Spring MVC Servlet Stack
  • 9.
    Web Stacks inSpring 5 7 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack
  • 10.
    Web Stacks inSpring 5 7 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet StackBlocking
  • 11.
    Web Stacks inSpring 5 7 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet StackBlocking
  • 12.
    Web Stacks inSpring 5 7 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet StackBlocking Non-Blocking
  • 13.
    Web Stacks inSpring 5 7 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet StackBlocking Non-Blocking
  • 14.
  • 15.
    9 Reactive Stack 🔄 Thread 🔄Thread 🔄 Thread 🔄 Thread
  • 16.
    ❓Why are weintroducing Spring WebFlux? 10
  • 17.
    ❓Why are weintroducing Spring WebFlux? 10 The goal of Spring WebFlux is to offer Spring developers a non-blocking event-loop style programming model similar to node.js.
  • 18.
    the non-blocking asyncprogramming model is more efficient for latency-sensitive workloads. – Blocking threads consume resources – mobile applications and interconnected microservices ❓Why are we introducing Spring WebFlux? 10 The goal of Spring WebFlux is to offer Spring developers a non-blocking event-loop style programming model similar to node.js.
  • 19.
    Web Stacks inSpring 5 11 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
    5 Flux<T> is aPublisher<T> for 0..n elements
  • 29.
  • 30.
    7 Mono<T> is aPublisher<T> for 0..1 element
  • 31.
    Reactor 18 Flux<String> s =Flux .just("a", "b", "c", "d") .map(String::toUpperCase); s.log("demo").subscribe();
  • 32.
    Reactor 18 Flux<String> s =Flux .just("a", "b", "c", "d") .map(String::toUpperCase); s.log("demo").subscribe(); Need to be subscribed!
  • 33.
    Reactor 18 Flux<String> s =Flux .just("a", "b", "c", "d") .map(String::toUpperCase); s.log("demo").subscribe(); Need to be subscribed! 00:58:33.902 [main] INFO demo - | request(unbounded) 00:58:33.903 [main] INFO demo - | onNext(A) 00:58:33.903 [main] INFO demo - | onNext(B) 00:58:33.903 [main] INFO demo - | onNext(C) 00:58:33.903 [main] INFO demo - | onNext(D) 00:58:33.903 [main] INFO demo - | onComplete()
  • 34.
    Reactor 19 Flux<String> s =Flux .just("a", "b", "c", "d") .map(String::toUpperCase) .delayElements(Duration.ofSeconds(1)); s.log("demo").subscribe();
  • 35.
    Reactor 19 Flux<String> s =Flux .just("a", "b", "c", "d") .map(String::toUpperCase) .delayElements(Duration.ofSeconds(1)); s.log("demo").subscribe(); 00:59:42.949 [main] INFO demo - request(unbounded) 00:59:43.992 [parallel-1] INFO demo - onNext(A) 00:59:44.994 [parallel-2] INFO demo - onNext(B) 00:59:45.997 [parallel-3] INFO demo - onNext(C) 00:59:46.999 [parallel-4] INFO demo - onNext(D) 00:59:47.001 [parallel-4] INFO demo - onComplete()
  • 36.
    Reactor (Hot Stream♨) 20 Flux<String>s = Flux.<String>create(sink -> sink.next(...); }); s.log("demo").subscribe();
  • 37.
    Reactor (Hot Stream♨) 21 twitter4j.TwiterStreamtw = ...; Flux<String> s = Flux.<String>create(sink -> sink.next(status.getText()); }); s.log("tweet").subscribe();
  • 38.
    Reactor (Hot Stream♨) 22 twitter4j.TwiterStreamtw = ...; Flux<String> s = Flux.<String>create(sink -> tw.addListener(new StatusAdapter() { public void onStatus(Status status) { sink.next(status.getText()); } public void onException(Exception e) { sink.error(e); }}); sink.onCancel(tw::shutdown); tw.sample(); }); s.log("tweet").subscribe();
  • 39.
    Operators in Reactor 23 •map / indexed / flatMap / flatMapMany • collectList / collectMap / count • concat / merge / zip / when / combineLatest • repeat / interval • filter / sample / take / skip • window / windowWhile / buffer • ... https://projectreactor.io/docs/core/release/reference/docs/index.html#which-operator
  • 40.
  • 41.
  • 42.
    zip 25 Flux<GHUser> github =findGitHubUsers("foo", "bar", "hoge");
 Flux<FBUser> facebook = findFacebookUsers("foo", "bar", "hoge"); Flux<User> users = Flux.zip(github, facebook) .map(tpl -> new User(tpl.getT1(), tpl.getT2())); users.subscribe();
  • 43.
  • 44.
    flatMap 27 Flux<GHUser> github =findGitHubUsers("foo", "bar", "hoge");
 Flux<User> users = github.flatMap(g -> findTweets(g) .collectList() .map(tweets -> new User(g, tweets))); users.subscribe();
  • 45.
    flatMap 28 Flux<GHUser> github =findGitHubUsers("foo", "bar", "hoge");
 Flux<User> users = github.map(g -> findTweets(g) .collectList() .map(tweets -> new User(g, tweets))); users.subscribe();
  • 46.
    flatMap 28 Flux<GHUser> github =findGitHubUsers("foo", "bar", "hoge");
 Flux<User> users = github.map(g -> findTweets(g) .collectList() .map(tweets -> new User(g, tweets))); users.subscribe(); ⚠ Can be compiled but findTweets won't be subscribed
  • 47.
    flatMap 29 Flux<GHUser> github =findGitHubUsers("foo", "bar", "hoge"); Flux<User> users = github.map(g -> { Mono<User> u = findTweets(g) .collectList() .map(tweets -> new User(g, tweets)); u.subscribe(); return u; }); users.subscribe(); This will work, but use flatMap instead
  • 48.
    Web Stacks inSpring 5 30 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack
  • 49.
    Spring MVC controller 31 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public String hello() { return hello.sayHello(); } }
  • 50.
    Spring MVC controller 31 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public String hello() { return hello.sayHello(); } } Blocking / Synchronous
  • 51.
    Spring WebFlux controller 32 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public String hello() { return hello.sayHello(); } }
  • 52.
    Spring WebFlux controller 32 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public String hello() { return hello.sayHello(); } } Non-blocking / Synchronous
  • 53.
    Spring WebFlux controller 33 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello); } }
  • 54.
    Spring WebFlux controller 33 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello); } } Non-blocking / Asynchronous
  • 55.
    Spring WebFlux controller 33 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello); } } Non-blocking / Asynchronous You don't need to subscribe the stream
  • 56.
    Spring WebFlux controller 34 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello) .flatMap(foo::abc) .map(bar::xyz);} Non-blocking / Asynchronous
  • 57.
    Spring WebFlux controller 34 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello) .flatMap(foo::abc) .map(bar::xyz);} Non-blocking / Asynchronous Could be executed in the other thread
  • 58.
    Spring MVC controller 35 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello) .flatMap(foo::abc) .map(bar::xyz);} Blocking / Asynchronous
  • 59.
    Spring MVC controller 35 @RestController publicclass HelloController { private final Hello hello; public HelloController(Hello hello) { this.hello = hello; } @GetMapping("hello") public Mono<String> hello() { return Mono.fromCallable(hello::sayHello) .flatMap(foo::abc) .map(bar::xyz);} Blocking / Asynchronous Async support in Servlet 3
  • 60.
    ∞ Stream 36 @RestController public classHelloController { @GetMapping("hello") public Flux<String> hello() { Flux<String> hello = Flux.just("hello") .delayElement(Duration.ofMillis(100)); hello.subscribe(); return hello.repeat(); // ∞ Stream } }
  • 61.
  • 62.
    Content Negotiation 38 Accept: text/event-stream Accept:application/stream+json Accept: application/json
  • 63.
    Content Negotiation 38 Accept: text/event-stream Accept:application/stream+json Accept: application/json ✅ Backpressure ✅ Backpressure ❌ Backpressure
  • 64.
    Content Negotiation 39 @GetMapping("hello") public Flux<Message>hello() { return Mono.just(new Message("a")) .repeat() .take(100) .log("message"); }
  • 65.
    40 $ curl -v-H "Accept: text/event-stream" localhost:8080/messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: text/event-stream > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: text/event-stream < data:{"text":"a"} data:{"text":"a"} data:{"text":"a"} ...
  • 66.
    40 $ curl -v-H "Accept: text/event-stream" localhost:8080/messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: text/event-stream > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: text/event-stream < data:{"text":"a"} data:{"text":"a"} data:{"text":"a"} ... INFO 48330 --- [ctor-http-nio-2] message : request(1) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : request(31) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : request(24) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-2] message : request(24) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-2] message : onComplete()
  • 67.
    40 $ curl -v-H "Accept: text/event-stream" localhost:8080/messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: text/event-stream > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: text/event-stream < data:{"text":"a"} data:{"text":"a"} data:{"text":"a"} ... INFO 48330 --- [ctor-http-nio-2] message : request(1) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : request(31) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : request(24) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-2] message : request(24) INFO 48330 --- [ctor-http-nio-2] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-2] message : onComplete() Backpressure
  • 68.
    (FYI) In caseof Servlet Stack 41
  • 69.
    (FYI) In caseof Servlet Stack 41 INFO 61528 --- [nio-8080-exec-1] message : request(1) INFO 61528 --- [nio-8080-exec-1] message : onNext(a) INFO 61528 --- [ MvcAsync1] message : request(1) INFO 61528 --- [ MvcAsync1] message : onNext(a) INFO 61528 --- [ MvcAsync2] message : request(1) ... ... INFO 61528 --- [ MvcAsync98] message : request(1) INFO 61528 --- [ MvcAsync98] message : onNext(a) INFO 61528 --- [ MvcAsync99] message : request(1) INFO 61528 --- [ MvcAsync99] message : onNext(a) INFO 61528 --- [ MvcAsync99] message : onComplete()
  • 70.
    (FYI) In caseof Servlet Stack 41 INFO 61528 --- [nio-8080-exec-1] message : request(1) INFO 61528 --- [nio-8080-exec-1] message : onNext(a) INFO 61528 --- [ MvcAsync1] message : request(1) INFO 61528 --- [ MvcAsync1] message : onNext(a) INFO 61528 --- [ MvcAsync2] message : request(1) ... ... INFO 61528 --- [ MvcAsync98] message : request(1) INFO 61528 --- [ MvcAsync98] message : onNext(a) INFO 61528 --- [ MvcAsync99] message : request(1) INFO 61528 --- [ MvcAsync99] message : onNext(a) INFO 61528 --- [ MvcAsync99] message : onComplete() ✅ Backpressure
  • 71.
    42 $ curl -v-H "Accept: application/stream+json" localhost:8080/ messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/stream+json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/stream+json;charset=UTF-8 < {"text":"a"} {"text":"a"} {"text":"a"} ...
  • 72.
    42 $ curl -v-H "Accept: application/stream+json" localhost:8080/ messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/stream+json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/stream+json;charset=UTF-8 < {"text":"a"} {"text":"a"} {"text":"a"} ... INFO 48330 --- [ctor-http-nio-4] message : request(1) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : request(31) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : request(24) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-4] message : request(24) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-4] message : onComplete()
  • 73.
    42 $ curl -v-H "Accept: application/stream+json" localhost:8080/ messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/stream+json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/stream+json;charset=UTF-8 < {"text":"a"} {"text":"a"} {"text":"a"} ... INFO 48330 --- [ctor-http-nio-4] message : request(1) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : request(31) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : request(24) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-4] message : request(24) INFO 48330 --- [ctor-http-nio-4] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-4] message : onComplete() ✅ Backpressure
  • 74.
    43 $ curl -v-H "Accept: application/json" localhost:8080/messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/json;charset=UTF-8 < [{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"}, {"text":"a"},{"text":"a"}]
  • 75.
    43 $ curl -v-H "Accept: application/json" localhost:8080/messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/json;charset=UTF-8 < [{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"}, {"text":"a"},{"text":"a"}] INFO 48330 --- [ctor-http-nio-3] message : request(unbounded) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onComplete()
  • 76.
    43 $ curl -v-H "Accept: application/json" localhost:8080/messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/json;charset=UTF-8 < [{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"}, {"text":"a"},{"text":"a"}] INFO 48330 --- [ctor-http-nio-3] message : request(unbounded) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) ... ... INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onNext(a) INFO 48330 --- [ctor-http-nio-3] message : onComplete() ❌ Backpressure
  • 77.
    Flux -> Mono 44 @GetMapping("hello") publicMono<List<Message>> hello() { return Mono.just(new Message("a")) .repeat() .take(100) .collectList() .log("message"); }
  • 78.
    Flux -> Mono 45 @GetMapping("hello") publicMono<List<Message>> hello() { return Mono.just(new Message("a")) .repeat() .take(100) .collectList() .log("message"); } $ curl -v -H "Accept: application/stream+json" localhost:8080/ messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/stream+json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/stream+json;charset=UTF-8 < [{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"}, {"text":"a"},{"text":"a"}]
  • 79.
    Flux -> Mono 45 @GetMapping("hello") publicMono<List<Message>> hello() { return Mono.just(new Message("a")) .repeat() .take(100) .collectList() .log("message"); } $ curl -v -H "Accept: application/stream+json" localhost:8080/ messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/stream+json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/stream+json;charset=UTF-8 < [{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"}, {"text":"a"},{"text":"a"}] INFO 48330 --- [ctor-http-nio-3] message : | request(1) INFO 48330 --- [ctor-http-nio-3] message : | onNext([a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]) INFO 48330 --- [ctor-http-nio-3] message : | request(31) INFO 48330 --- [ctor-http-nio-3] message : | onComplete()
  • 80.
    Flux -> Mono 45 @GetMapping("hello") publicMono<List<Message>> hello() { return Mono.just(new Message("a")) .repeat() .take(100) .collectList() .log("message"); } $ curl -v -H "Accept: application/stream+json" localhost:8080/ messages > GET /messages HTTP/1.1 > Host: localhost:8080 > Accept: application/stream+json > < HTTP/1.1 200 OK < transfer-encoding: chunked < Content-Type: application/stream+json;charset=UTF-8 < [{"text":"a"},{"text":"a"},...,{"text":"a"},{"text":"a"}, {"text":"a"},{"text":"a"}] INFO 48330 --- [ctor-http-nio-3] message : | request(1) INFO 48330 --- [ctor-http-nio-3] message : | onNext([a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]) INFO 48330 --- [ctor-http-nio-3] message : | request(31) INFO 48330 --- [ctor-http-nio-3] message : | onComplete() ❌ Backpressure
  • 81.
    Spring WebFlux controller(Receiving Flux) 46 @RestController public class HelloController { @PostMapping("upper") public Flux<String> upper(@RequestBody Flux<String> input) { Flux<String> output = input .map(String::toUpperCase); return output; } }
  • 82.
    47 $ telnet localhost8080 POST /upper HTTP/1.1 Host: localhost:8080 Transfer-Encoding: chunked Accept: text/event-stream Content-Type: text/plain 3 foo HTTP/1.1 200 OK transfer-encoding: chunked Content-Type: text/event-stream 5 data: 4 FOO 1 4 java 5 data: 5 JAVA 1
  • 83.
    47 $ telnet localhost8080 POST /upper HTTP/1.1 Host: localhost:8080 Transfer-Encoding: chunked Accept: text/event-stream Content-Type: text/plain 3 foo HTTP/1.1 200 OK transfer-encoding: chunked Content-Type: text/event-stream 5 data: 4 FOO 1 4 java 5 data: 5 JAVA 1 Request Body
  • 84.
    47 $ telnet localhost8080 POST /upper HTTP/1.1 Host: localhost:8080 Transfer-Encoding: chunked Accept: text/event-stream Content-Type: text/plain 3 foo HTTP/1.1 200 OK transfer-encoding: chunked Content-Type: text/event-stream 5 data: 4 FOO 1 4 java 5 data: 5 JAVA 1 Request Body Response Body
  • 85.
    Spring WebFlux controller(Receiving Flux) 48 @PostMapping("upper") public Flux<String> upper(@RequestBody Flux<String> input) { Flux<String> output = input .map(String::toUpperCase) .flatMap(s -> Flux .interval(Duration.ofSeconds(1)) .map(i -> s + i) .take(3)); return output; }
  • 86.
    49 3 foo HTTP/1.1 200 OK transfer-encoding:chunked Content-Type: text/event-stream 5 data: 5 FOO0 1 5 data: 5 FOO1 1 5 data: 5 FOO2 1
  • 87.
  • 88.
    Spring MVC controller(Receiving Flux) 51 @RestController public class HelloController { @PostMapping("upper") public Flux<String> hello(@RequestBody Flux<String> input) { Flux<String> output = input .map(String::toUpperCase); return output; } }
  • 89.
    Spring MVC controller(Receiving Flux) 51 @RestController public class HelloController { @PostMapping("upper") public Flux<String> hello(@RequestBody Flux<String> input) { Flux<String> output = input .map(String::toUpperCase); return output; } } 🙅
  • 90.
    Spring MVC controller(Receiving Flux) 51 @RestController public class HelloController { @PostMapping("upper") public Flux<String> hello(@RequestBody Flux<String> input) { Flux<String> output = input .map(String::toUpperCase); return output; } } 🙅Reactive type are supported only as controller method return values
  • 91.
    Broadcast stream 52 @RestController public classHelloController { private final Flux<String> flux; public HelloController() { this.flux = this.createHotStream().share(); this.flux.subscribe(); } @GetMapping("hello") public Flux<String> hello() { return this.flux; }}
  • 92.
    Broadcast stream 52 @RestController public classHelloController { private final Flux<String> flux; public HelloController() { this.flux = this.createHotStream().share(); this.flux.subscribe(); } @GetMapping("hello") public Flux<String> hello() { return this.flux; }} this stream is shared by all http clients!
  • 93.
  • 94.
  • 95.
  • 96.
    Web Stacks inSpring 5 55 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack
  • 97.
    Web Stacks inSpring 5 55 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack •Annotated Controller •Function Endpoints (WebFlux.fn)
  • 98.
    @Configuration public class RouterConfig{ @Bean public RouterFunctions<ServerResponse> routes() { return route(GET("hello"), req -> { return ServerReponse.ok() .body(Mono.just("hello"), String.class); }); } } Spring WebFlux.fn 56
  • 99.
    @Configuration public class RouterConfig{ @Bean public RouterFunctions<ServerResponse> routes() { return route(GET("hello"), req -> { return ServerReponse.ok() .body(Mono.just("hello"), String.class); }); } } lambda Spring WebFlux.fn 56
  • 100.
    Spring WebFlux.fn 57 @Configuration public classRouterConfig { @Bean public RouterFunctions<ServerResponse> routes(HelloHandler helloHandler) { return route(GET("hello"), helloHandler::hello); } }
  • 101.
    Spring WebFlux.fn 58 public RouterFunctions<ServerResponse> routes(PersonHandelerph) { return route(GET("person"), ph::findAll) .andRoute(POST("person"), ph::create) .andRoute(GET("person/{id}"), ph::findOne) .andRoute(PUT("person/{id}"), ph::update) .andRoute(DELETE("person/{id}"), ph::delete)); }
  • 102.
    Spring WebFlux.fn 59 public RouterFunctions<ServerResponse> routes(PersonHandelerph) { return nest(path("person"), route(GET("/"), ph::findAll) .andRoute(POST("/"), ph::create) .andRoute(GET("/{id}"), ph::findOne) .andRoute(PUT("/{id}"), ph::update) .andRoute(DELETE("/{id}"), ph::delete))); }
  • 103.
    Web Stacks inSpring 5 60 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack
  • 104.
    Web Stacks inSpring 5 60 Servlet Container Servlet API Spring MVC Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Servlet Stack Reactive Web Client
  • 105.
    Reactive Web Client 61 @RestController publicclass HelloController { private final WebClient client; public HelloController(WebClinet.Builer b) { this.client = b.build(); } @GetMapping("hello") public Flux<String> hello() { return this.client.get().uri("http://blahblah") .retrieve().bodyToFlux(String.class); }}
  • 106.
    Reactive Web Client 62 Flux<User>users = webClient.get() .uri("http://user-service") .exchange() // => Mono<ClientResponse> .flatMap(r -> r.bodyToFlux(User.class)); // short-cut Flux<User> users = webClient.get() .uri("http://user-service") .retrieve() .bodyToFlux(User.class);
  • 107.
    Reactive Web Client 63 Mono<User>user = webClient.get() .uri("http://user-service/{id}", id) .header("X-Foo", "foo") .retrieve().bodyToMono(User.class); Mono<User> user = ... Mono<Void> created = webClient.post() .uri("http://user-service") .body(user, User.class) .retrieve().bodyToMono(Void.class);
  • 108.
    From RestTemplate toWebClient 64 @Controller public class UserController { @GetMapping("users") public String hello(Model model) { List<User> users = restTemplate .getForObject("/users", List.class); model.addAttribute("users", users); return "users"; } }
  • 109.
    From RestTemplate toWebClient 65 @Controller public class UserController { @GetMapping("users") public String hello(Model model) { Flux<User> users = client.get().uri("/users") .retrieve().bodyToFlux(String.class); model.addAttribute("users", users); return "users"; } }
  • 110.
    66 <html> <body> <ul> <li data-th-each="user :${user}">[[${user}]]</li> </ul> </body> </html>
  • 111.
    66 <html> <body> <ul> <li data-th-each="user :${user}">[[${user}]]</li> </ul> </body> </html> 😀 Spring WebFlux
  • 112.
    66 <html> <body> <ul> <li data-th-each="user :${user}">[[${user}]]</li> </ul> </body> </html> 😀 Spring WebFlux 😨 Spring MVC
  • 113.
    From RestTemplate toWebClient (Spring MVC) 67 @Controller public class UserController { @GetMapping("users") public Mono<String> hello(Model model) { Flux<User> users = client.get().uri("/users") .retrieve().bodyToFlux(String.class); users.collectList().map(x -> { model.addAttribute("users", x); return "users"; }); }
  • 114.
    From RestTemplate toWebClient (Spring MVC) 67 @Controller public class UserController { @GetMapping("users") public Mono<String> hello(Model model) { Flux<User> users = client.get().uri("/users") .retrieve().bodyToFlux(String.class); users.collectList().map(x -> { model.addAttribute("users", x); return "users"; }); } 😀 Spring MVC
  • 115.
    From RestTemplate toWebClient (Spring MVC) 68 @Controller public class UserController { @GetMapping("users") public Mono<String> hello(Model model) { Flux<User> users = ...; Mono<Sting> foo = ...; return Mono.zip(user.collectList(), foo)    .map(tpl -> { model.addAttribute("users", tpl.getT1()); model.addAttribute("foo", tpl.getT2()); return "users"; });}}
  • 116.
    69 String url ="http://httpbin.org/delay/1"; @GetMapping("hello") public Mono<String> hello() { Mono<String> mono = Mono.just(restTemplate .getForObject(url)); return mono; }
  • 117.
    ⚠ Don't blockthe thread in WebFlux! 69 String url = "http://httpbin.org/delay/1"; @GetMapping("hello") public Mono<String> hello() { Mono<String> mono = Mono.just(restTemplate .getForObject(url)); return mono; }
  • 118.
    ⚠ Don't blockthe thread in WebFlux! 69 String url = "http://httpbin.org/delay/1"; @GetMapping("hello") public Mono<String> hello() { Mono<String> mono = Mono.just(restTemplate .getForObject(url)); return mono; } 😠 Blocking!
  • 119.
    ⚠ Don't blockthe thread in WebFlux! 70 String url = "http://httpbin.org/delay/1"; @GetMapping("hello") public Mono<String> hello() { Mono<String> mono = Mono .fromCallable(() ->restTemplate.getForObject(url)) .subscribeOn(Schedulers.elastic()); return mono; }
  • 120.
    ⚠ Don't blockthe thread in WebFlux! 70 String url = "http://httpbin.org/delay/1"; @GetMapping("hello") public Mono<String> hello() { Mono<String> mono = Mono .fromCallable(() ->restTemplate.getForObject(url)) .subscribeOn(Schedulers.elastic()); return mono; } Switch the execution context
  • 121.
    71 Concurrency Throughput [trans /sec] Core i7 2.7 GHz 4 Core x HT
  • 122.
    Reactive Support inSpring Projects 72 Spring Data Reactive Stack Netty, Servlet 3.1+, Undertow Reactive Streams Spring WebFlux Spring Security Thymeleaf
  • 123.
    Spring Data Kay 73 Reactivesupport for • Redis • MongoDB • Couchbase • Cassandra Infinite streams from the database with @Tailable
  • 124.
    Reactive Non-Blocking DataAccess 74 public interface ReactiveCrudRepository<ID,T> { Mono<T> findById(ID id); Mono<T> findById(Mono<ID> id); Flux<T> findAll(); Mono<Long> count(); Mono<T> save(T entity); Mono<T> saveAll(Publisher<T> entityStream); Mono<Void> delete(T entity) // ... }
  • 125.
    Tailable Cursor Supportfor MongoDB 75 public interface MessageRepository extends ReactiveMongoRepository<Message,String> { @Tailable Flux<Message> findByUpdatedAtGreaterThan(Instant target); }
  • 126.
    Broadcast updated messagesin MongoDB 76 @RestController public class HelloController { private final Flux<Message> messages; public HelloController(MessageRepository repo) { this.messages = repo .findByUpdatedAtGreaterThan(Instant.now()) .share(); this.messages.subscribe(); } @GetMapping("messages") public Flux<Message> messages() { return this.messages; }}
  • 127.
    But, but, JDBCis blocking .... 😭 77
  • 128.
    But, but, JDBCis blocking .... 😭 77 Let's see what will happen in next Java
  • 129.
    But, but, JDBCis blocking .... 😭 77 Let's see what will happen in next Java https://static.rainfocus.com/oracle/oow17/sess/1491948952321001dm4m/PF/JavaOne%2017%20- %20CON1491_1506954898905001IipH.pdf
  • 130.
  • 131.
  • 132.
    Spring Security Reactive 79 @Bean publicSecurityWebFilterChain springWebFilterChain(HttpSecurity http) { return http.authorizeExchange() .pathMatchers("/v2/**").hasRole("ADMIN") .and().httpBasic().and().build(); } @Bean public MapUserDetailsRepository userDetailsRepository() { return new MapUserDetailsRepository(...); }
  • 133.
    Thymeleaf 3.0 reactivesupport 80 • "full mode" • "chunked mode"
 => Progressive rendering. Good for large pages • "data-driven mode" 
 => Rendering template fragments as Server-Sent Events
 • https://github.com/thymeleaf/thymeleaf-spring/issues/132 • https://github.com/spring-projects/spring-boot/issues/8124 • https://speakerdeck.com/dfernandez/o-2017-getting-thymeleaf-ready- for-spring-5-and-reactive?slide=18 transfer-encoding: chunked
  • 134.
    Data-driven mode 81 @GetMapping("users") public Stringhello(Model model) { Flux<User> users = userService.findAll(); ReactiveDataDriverContextVariable v = new ReactiveDataDriverContextVariable(users, 1); model.addAttribute("users", v); return "users"; }
  • 135.
  • 136.
  • 137.
    Reactive Application Patterns(as of 2017) 83 Spring WebFluxSpring WebFlux Frontend Backend HTTP NoSQL WebClient Spring Data
  • 138.
    Reactive Application Patterns(as of 2017) 84 Spring MVCSpring WebFlux Frontend Backend HTTP RDB JDBC WebClient
  • 139.
    Reactive Application Patterns(as of 2017) 85 Spring MVCSpring MVC Frontend Backend HTTP RDB JDBC WebClient
  • 140.
    Spring Cloud Gateway 86 AGateway built on Spring Framework 5.0 and Spring Boot 2.0 providing routing and more http://cloud.spring.io/spring-cloud-gateway/ http://cloud.spring.io/spring-cloud-gateway/2.0.x/single/ spring-cloud-gateway.html
  • 141.
    Start Spring 5& Spring Boot 2 87
  • 142.
    Servlet Stack ⇄Reactive Stack 88 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> Reactive Stack Servlet Stack
  • 143.
    Servlet Stack ⇄Reactive Stack 89 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webflux</artifactId> </dependency> <dependency> <groupId>io.projectreactor.ipc</groupId> <artifactId>reactor-netty</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> Reactive Stack Servlet Stack + WebClient
  • 144.
    Thanks! 90 Resources • Servlet vsReactive Stacks in Five Use Cases • Spring Boot 2 0 Web Applications • Spring Framework 5: Themes & Trends • Why Spring ❤ Kotlin