怠惰系エンジニアのメモ帳

勉強した内容をメモしていきます。

Spring Web Flux + Thymeleaf で画面出力したかった

Spring Framework5で追加されるSpring Web Flux と 、Thymeleaf で画面出力したかたが…できなかった。
(具体的には RouterFunctions でルーティングを定義したアプリでThymeleafが使えるのか知りたかった。)

もし解決方法をご存知の方がいらっしゃったら、ご教示いただけると幸いです。
また、記載内容等に間違いがある場合はご指摘お願いします。

(2017/08/08 追記) 画面出力できました。 Spring Web Flux + Thymeleafで画面出力する

※そもそも、WebFluxで画面描画って想定されているのか疑問…。

原因

指定されたViewを返す ThymeleafReactiveViewResolver に、getApplicationContext() が実装されていなかったため、Viewを返せなかったことが原因っぽい。

java.lang.NoSuchMethodError: org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver.getApplicationContext()Lorg/springframework/context/ApplicationContext;
    at org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver.loadView(ThymeleafReactiveViewResolver.java:581) ~[thymeleaf-spring5-3.0.6.M4.jar:3.0.6.M4]
    at org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver.resolveViewName(ThymeleafReactiveViewResolver.java:569) ~[thymeleaf-spring5-3.0.6.M4.jar:3.0.6.M4]
    at org.springframework.web.reactive.function.server.DefaultRenderingResponseBuilder$DefaultRenderingResponse.lambda$writeTo$0(DefaultRenderingResponseBuilder.java:163) ~[spring-webflux-5.0.0.BUILD-20170630.222607-342.jar:5.0.0.BUILD-SNAPSHOT]

今回使用したThymeleafのバージョンは3.0.6.RELEASE。
次期バージョンの3.0.7-SNAPSHOTでは getApplicationContext が実装されているので、とりあえずリリース待ち。
getApplicationContext が実装されたことで、Viewが返せるかは不明。

GitHub - ThymeleafReactiveViewResolver.java

環境

  • Spring Boot: 2.0.0.BUILD-SNAPSHOT
    • Spring Framewrok : 5.0.0.BUILD-SNAPSHOT
    • Spring Web Flux : 5.0.0.BUILD-SNAPSHOT
    • Thymeleaf : 3.0.6.RELEASE
  • Java: 1.8.0_65

やりたかったこと

localhost:8080/home にアクセスして、サーバからのメッセージ & 当日日付 を画面に出力。
なお、メッセージと日付はFormクラスを定義してThymeleafに渡す。

イメージ

f:id:tk5_21:20170702203403p:plain

コード

コードはざっくりと記載。

pom.xml

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

ルーティング定義とか

@SpringBootApplication
public class WebfluxDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebfluxDemoApplication.class, args);
    }

    @Bean
    RouterFunction<ServerResponse> routes(HelloHandler handler){
        return handler.routes();
    }
}
// static importのみ記載
import static org.springframework.web.reactive.function.BodyInserters.fromPublisher;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

@Component
public class HelloHandler {

    public RouterFunction<ServerResponse> routes() {
        return route(GET("/home"), this::home);
    }

    public Mono<ServerResponse> home(ServerRequest request) {
        return ok().render("home/home", new Form("Thymeleaf Hello", LocalDate.now()));
        // renderメソッドで view をレンダリングできる?
        // view は resources/templates/home/home.html を指定。
    }

}

フォームクラス

public class Form {
    private String message;  // 出力メッセージ
    private LocalDate today; // 当日日付
}

実行結果

java.lang.NoSuchMethodError: org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver.getApplicationContext()Lorg/springframework/context/ApplicationContext;
    at org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver.loadView(ThymeleafReactiveViewResolver.java:581) ~[thymeleaf-spring5-3.0.6.M4.jar:3.0.6.M4]
    at org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver.resolveViewName(ThymeleafReactiveViewResolver.java:569) ~[thymeleaf-spring5-3.0.6.M4.jar:3.0.6.M4]
    at org.springframework.web.reactive.function.server.DefaultRenderingResponseBuilder$DefaultRenderingResponse.lambda$writeTo$0(DefaultRenderingResponseBuilder.java:163) ~[spring-webflux-5.0.0.BUILD-20170630.222607-342.jar:5.0.0.BUILD-SNAPSHOT]
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:355) ~[reactor-core-3.1.0.BUILD-20170630.221141-124.jar:3.1.0.BUILD-SNAPSHOT]
    at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:210) ~[reactor-core-3.1.0.BUILD-20170630.221141-124.jar:3.1.0.BUILD-SNAPSHOT]
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:91) ~[reactor-core-3.1.0.BUILD-20170630.221141-124.jar:3.1.0.BUILD-SNAPSHOT]
    at reactor.core.publisher.FluxStream.subscribe(FluxStream.java:55) ~[reactor-core-3.1.0.BUILD-20170630.221141-124.jar:3.1.0.BUILD-SNAPSHOT]
    at reactor.core.publisher.FluxConcatMap.subscribe(FluxConcatMap.java:120) ~[reactor-core-3.1.0.BUILD-20170630.221141-124.jar:3.1.0.BUILD-SNAPSHOT]

デバッグで処理を追ってみましたが、ビュー名(home/home)は渡っていたので getApplicationContetx が実行されればイケるのかな?