타임리프 ( thymeleaf )
spring.thymeleaf.prefix= ViewResolver를 사용하기 위한 접두사, 기본값 classpath:/templates/
spring.thymeleaf.suffix= 접미사, 기본값 .html
spring.thymeleaf.encoding= template의 인코딩, 기본값 UTF-8
spring.thymeleaf.check-template= 렌더링되기 전에 템플릿이 존재하는지 확인, 기본값 true
spring.thymeleaf.check-template-location= 템플릿 ㅜ이치가 존재하는지 확인, 기본값 true
spring.thymeleaf.mode= 타임리프를 사용하기 위한 TemplateMode, 기본값 HTML
spring.thymeleaf.cache= 템플릿을 캐시할 것인지 아닌지 설정, 기본값 true
spring.thymeleaf.template-resolver-order= ViewResolver의 순서, 기본값 1
spring.thymeleaf.view-names= ViewResolver로 해결할 수 있는 뷰 이름. 쉼표로 구분
spring.thymeleaf.excluded-view-names= 제외시켜야할 뷰 이름. 쉼표로 구분
spring.thymeleaf.enabled= 타임리프 동작 유무. 기본값 true
spring.thymeleaf.enable-spring-el-compiler= SpEL 표현법의 컴파일 사용 여부. 기본값 false
spring.thymeleaf.reactive.max-chunk-size= 응답을 쓰는 데 사용할 데이터 버퍼의 최대 크기. 기본값 0
spring.thymeleaf.reactive.media-types= text/html과 같은 뷰 기술에 의해 지원되는 미디어 유형
spring.thymeleaf.reactive.full-mode-view-names= FULL 모드에서 동작해야 하는 쉼표로 구분된 목록의 뷰 이름. 기본값은 겂고 FULL모드는 기본적으로 차단 모드를 의미
spring.thymeleaf.reactive.chunked-mode-view-names= chunked 모드에서 동작해야 하는 쉼표로 구분된 목록의 뷰 이름
사이트)
https://mkyong.com/spring-boot/spring-boot-webflux-thymeleaf-reactive-example/
https://mkyong.com/spring-boot/spring-boot-webflux-server-sent-events-example/
http://wiki.sys4u.co.kr/pages/viewpage.action?pageId=8552646
https://dzone.com/articles/spring-webflux-a-basic-crud-application-part-1
https://springframework.guru/spring-web-reactive/
https://www.baeldung.com/spring-webflux
https://freedeveloper.tistory.com/80
실습전 주의사항
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
starter-web을 pom.xml에서 빼주어야됩니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
기존에 webflux와 같이 pom.xml에 있으면 webflux는 제기능을 못합니다.
자세한것은 아래 Reference 문서를 보시면 되겠습니다.
애플리케이션에 모듈 spring-boot-starter-web
과spring-boot-starter-webflux
모듈을 모두 추가하면 WebFlux가 아닌 Spring Boot 자동 구성 Spring MVC가 생성됩니다. 이 동작은 많은 Spring 개발자spring-boot-starter-webflux
가 반응성을 사용하기 위해 Spring MVC 애플리케이션에 추가 하기 때문에 선택되었습니다WebClient
. 선택한 애플리케이션 유형을로 설정하여 선택을 계속 적용 할 수 있습니다SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE).
에러걸리시 뜨는 페이지)
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sun Feb 09 03:33:41 KST 2020There was an unexpected error (type=Internal Server Error, status=500).An error happened during template parsing (template: "class path resource [templates/list.html]")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 2020-02-09 03:33:41.673 ERROR 7248 --- [nio-8080-exec-7] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-7] Exception processing template "list": An error happened during template parsing (template: "class path resource [templates/list.html]") org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]") ... Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "movie.name" (template: "list" - line 21, col 21) ... Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "movie.name" (template: "list" - line 21, col 21) ... Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'name' cannot be found on object of type 'reactor.core.publisher.FluxConcatMap' - maybe not public or not valid? ... 2020-02-09 03:33:41.673 DEBUG 7248 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Error rendering view [org.thymeleaf.spring5.view.ThymeleafView@27d95d1f] org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]") ... Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "movie.name" (template: "list" - line 21, col 21) ... Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "movie.name" (template: "list" - line 21, col 21) ... Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'name' cannot be found on object of type 'reactor.core.publisher.FluxConcatMap' - maybe not public or not valid? ... 2020-02-09 03:33:41.674 DEBUG 7248 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Unresolved failure from "ASYNC" dispatch: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]") 2020-02-09 03:33:41.674 ERROR 7248 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'name' cannot be found on object of type 'reactor.core.publisher.FluxConcatMap' - maybe not public or not valid? ... 2020-02-09 03:33:41.674 ERROR 7248 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]")] with root cause org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'name' cannot be found on object of type 'reactor.core.publisher.FluxConcatMap' - maybe not public or not valid? ... 2020-02-09 03:33:41.675 DEBUG 7248 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={} 2020-02-09 03:33:41.675 DEBUG 7248 --- [nio-8080-exec-7] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse) 2020-02-09 03:33:41.677 DEBUG 7248 --- [nio-8080-exec-7] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8] 2020-02-09 03:33:41.677 DEBUG 7248 --- [nio-8080-exec-7] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500 | cs |
해결법1)
https://freedeveloper.tistory.com/80
https://okky.kr/article/456316
https://junjangsee.github.io/2019/04/30/spring/spring-06/
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(DemoApplication.class);
application.setWebApplicationType(WebApplicationType.REACTIVE);
// starter-web은 SERVLET으로 자동으로 선택한다. 이것을 강제로 REACTIVE로 바꾼다.
application.run(args);
}
}
해결법2)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
을 삭제
실행화면1)
package com.example.demo.Domain;
public class Movie {
private String name;
private Integer score;
public Movie() {
}
public Movie(String name, Integer score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
package com.example.demo.Repository;
import com.example.demo.Domain.Movie;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface MovieRepository {
Flux<Movie> findAll();
Mono<Movie> findById(String id);
Mono<Movie> save(Mono<Movie> order);
Movie save(Movie movie);
}
package com.example.demo.Repository;
import com.example.demo.Domain.Movie;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
@Repository
public class ReactiveMovieRepository implements MovieRepository {
private static List<Movie> movie = new ArrayList<>();
// 1차
static {
movie.add(new Movie("Polar (2019)", 64));
movie.add(new Movie("Iron Man (2008)", 79));
movie.add(new Movie("The Shawshank Redemption (1994)", 93));
movie.add(new Movie("Forrest Gump (1994)", 83));
movie.add(new Movie("Glass (2019)", 70));
}
// 2차
@PostConstruct
public void init() {
for (int i = 0; i < 25; i++ ) {
var Mov = generate();
movie.add(Mov);
}
}
@Override
public Flux<Movie> findAll() {
//simulate stream data with 2 seconds delay.
return Flux.fromIterable(movie).delayElements(Duration.ofMillis(100));
}
@Override
public Mono<Movie> findById(String id) {
Movie movieItem = null;
movieItem = containsId(id);
if(movieItem == null){
return null;
}
return Mono.just(movieItem);
}
@Override
public Mono<Movie> save(Mono<Movie> order) {
return order.map(this::save);
}
@Override
public Movie save(Movie movieItem) {
movie.add(movieItem);
return movieItem;
}
public Movie generate() {
var amount = ThreadLocalRandom.current().nextInt(1000);
return new Movie(UUID.randomUUID().toString(), amount);
}
public static Movie containsId(String id) {
for (Movie object : movie) {
if (object.getName() == id) {
return object;
}
}
return null;
}
}
package com.example.demo.Controller;
import com.example.demo.Repository.MovieRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.thymeleaf.spring5.context.webflux.IReactiveDataDriverContextVariable;
import org.thymeleaf.spring5.context.webflux.ReactiveDataDriverContextVariable;
import reactor.core.publisher.Mono;
@Controller
public class MovieController {
@Autowired
private MovieRepository movieRepository;
@RequestMapping("/")
public String index(final Model model) {
// loads 1 and display 1, stream data, data driven mode.
IReactiveDataDriverContextVariable reactiveDataDrivenMode =
new ReactiveDataDriverContextVariable(movieRepository.findAll(), 1);
model.addAttribute("movies", reactiveDataDrivenMode);
// classic, wait repository loaded all and display it.
//model.addAttribute("movies", movieRepository.findAll());
return "index";
}
@RequestMapping("/list")
public Mono<String> list(Model model) {
var movies = movieRepository.findAll();
model.addAttribute("movies", movies);
return Mono.just("list");
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link data-th-href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link data-th-href="@{/css/main.css}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div id="title">
<h1>Spring WebFlux + Thymeleaf</h1>
</div>
<table id="allMovies" class="table table-striped">
<thead>
<tr>
<th width="50%">Name</th>
<th>Score</th>
</tr>
</thead>
<tbody>
<tr class="result" data-th-each="movie : ${movies}">
<td>[[${movie.name}]]</td>
<td>[[${movie.score}]]</td>
</tr>
</tbody>
</table>
</div>
</div>
list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Orders</title>
</head>
<body>
<h1>Orders</h1>
<table>
<thead>
<tr>
<th></th>
<th>Id</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr th:each="movie : ${movies}">
<td th:text="${movieStat.count}">1</td>
<td th:text="${movie.name}"></td>
<td th:text="${#numbers.formatCurrency(movie.score)}" style="text-align: right"></td>
</tr>
</tbody>
</table>
</body>
</html>
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
springboot2_Reactive_example2.zip
'WEB > 스프링 부트 2' 카테고리의 다른 글
TestRestTemplate, WebTestClient (0) | 2020.02.11 |
---|---|
Spring Security (0) | 2020.02.10 |
WebFlux (0) | 2020.02.07 |
비동기 Emitter (0) | 2020.02.06 |
Jetty SSL, Http to Https (0) | 2020.02.06 |