상세 컨텐츠

본문 제목

[SpringBoot] 리액티브 프로그래밍, 웹플럭스, 타임리프 간단한 예제

Framework/Spring | SpringBoot

by yooputer 2023. 2. 24. 09:32

본문

그렉 턴키스트, 『스프링 부트 실전 활용 마스터』의 1장 스프링부트 웹 애플리케이션 만들기를 요약한 내용입니다.


프로젝트 생성

인텔리제이의 Spring Initializr로 새로운 프로젝트를 생성한다.

디펜던시는 Spring Reactive Web과 Thymeleaf 총 2개를 선택하고 프로젝트를 생성한다.

 

Dish 클래스와 Kitchen 서비스, Server 컨트롤러를 작성하고 실행후 /server 요청을 해봤는데 404 에러가 뜬다 왜지...?

-> 스프링부트 버전을 2.4.2로 바꾸어서 해결


ServerController, KitchenService, Dish 도메인 객체 정의

ServerController는 2개의 웹 메서드를 가지고 있다.

반환되는 미디어 타입은 text/event-steam이고 250ms마다 랜덤한 Dish를 반환한다.

deliverDishes 함수는 map 메서드를 사용해 Dish의 delivered를 true로 바꾸고 반환한다.

@RestController
public class ServerController {

    private final KitchenService kitchen;

    public ServerController(KitchenService kitchen) {
        this.kitchen = kitchen;
    }

    @GetMapping(value = "/server", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    Flux<Dish> serveDishes() {
        return this.kitchen.getDishes();
    }

    @GetMapping(value = "/served-dishes", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    Flux<Dish> deliverDishes(){
        return this.kitchen.getDishes()
                .map(dish -> Dish.deliver(dish));
    }
}

 

KitchenService의 getDishes 메서드는 Flux.generate()를 사용해 데이터를 비동기적으로 반환한다.

@Service
public class KitchenService {

    Flux<Dish> getDishes(){
        return Flux.<Dish> generate(sink -> sink.next(randomDish()))
                .delayElements(Duration.ofMillis(250));
    }

    private Dish randomDish(){
        return menu.get(picker.nextInt(menu.size()));
    }

    private List<Dish> menu = Arrays.asList(
            new Dish("Sesame chicken"),
            new Dish("Lo mein noodles, plain"),
            new Dish("Sweet & sour beef")
    );

    private Random picker = new Random();

}

 

Dish는 단순한 POJO(Plain Object Java Object) 객체로서 description 필드와 delivered 필드를 가지고 있고 게터, 세터, toString() 메서드와 deliver() 메서드를 가지고 있다.

public class Dish {

    private String description;
    private boolean delivered = false;

    public static Dish deliver(Dish dish){
        Dish deliveredDish = new Dish(dish.description);
        deliveredDish.delivered = true;
        return deliveredDish;
    }

    public Dish(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isDelivered() {
        return delivered;
    }

    @Override
    public String toString() {
        return "Dish{" +
                "description='" + description + '\'' +
                ", delivered=" + delivered +
                '}';
    }
}

 

위와 같이 작성하고 애플리케이션을 실행하고 localhost:8080/server를 요청하면 다음과 같이 비동기적으로 데이터 스트림이 반환된다

x 버튼을 누를 때까지 계속 나온다


템플릿 적용

실제 웹서비스를 만들기 위해서는 웹 페이지 화면을 반환해야한다.

타임리프는 리액티브 스트림을 완벽하게 지원하고 HTML과 100% 호환된다.

 

간단하게 타임리프 템플릿을 사용해보기 위해 인덱스 페이지를 반환하는 홈 컨트롤러를 작성한다.

Mono는 0개 또는 1개의 원소만 담을 수 있는 리액티브 컨테이너이다.

템플릿의 이름을 Mono.just()로 감싸 반환한다. 단순히 템플릿 이름만 반환할 때는 굳이 Mono에 담아 반환할 필요가 없다.

@Controller
public class HomeController {

    @GetMapping
    Mono<String> home(){
        return Mono.just("home");
    }

}

 

타임리프 뷰 리졸버는 컨트롤러가 반환한 템플릿 이름에 classpath:/templates/라는 접두어와 .html이라는 접미어를 붙인다.

타임리프는 DOM 기반 파서를 내장하고 있어서 모든 HTML 태그는 닫혀있어야 한다.

 

src/main/resources에 templates라는 디렉토리를 생성하고, templates 디렉토리 아래에 다음과 같이 home.html을 작성한다.

html 태그에 thymeleaf 관련 링크를 추가하고 title과 body에 내가 쓰고 싶은 내용을 적었다ㅎ

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Home Page</title>
</head>
<body>

    <h1>Welcome to My Home Page!</h1>

    <p>
        스프링부트 리액티브 프로그래밍 공부중입니다! 아좌아좌~~
    </p>

</body>
</html>

 

실행시키면 다음과 같이 아주 단순한 정적페이지를 볼 수 있다

 

관련글 더보기