[Java] Spring Boot Webflux 이해하기 -1 : 흐름 및 주요 특징 이해
해당 글에서는 Spring Boot Webflux에 대해 이해하고 전체적인 흐름, 특징에 대해서 이해를 돕기 위해 작성한 글입니다. 💡 [참고] Spring WebFlux 관련 글에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.분류링크Spring Boot Webflux 이해하기 -1 : 흐름 및 주요 특징 이해https://adjh54.tistory.com/232Spring Boot Webflux 이해하기 -2 : 활용하기https://adjh54.tistory.com/233Spring Boot WebFlux 이해하고 구현하기 -1 : 반응형 프로그래밍에서 WebFlux까지 흐름https://adjh54.tistory.com/627Spring Boot WebFlux 활용하여 구현하기 -2:..
✅ 1. Observer Pattern란?
소프트웨어 디자인 패턴 중 하나로, 어떤 객체의 상태 변화가 생기면, 그 객체를 구독한 다른 객체들에게 자동으로 알림을 보내는 방식
객체 간 1:N 관계를 정의해서, 한 객체의 상태 변화가 있을 때 그에 의존하는 객체들(옵서버)에게 자동으로 알림이 가도록 만드는 행동(Behavioral) 디자인 패턴
발행자(Subject)와 구독자(Observer) 간의 일대다 관계를 정의해, 발행자의 상태 변화가 있을 때 모든 구독자에게 알림을 전파하는 패턴
package ex08.polling;
publicclassApp {
publicstaticvoidmain(String[] args) {
LotteMartlotteMart=newLotteMart();
Customer1customer1=newCustomer1();
// 1. 마트는 입고 준비 (5초 걸림 - 별도 쓰레드)// 마트 입고 작업은 백그라운드 스레드에서 수행// 별도의 스레드로 돌리기 때문에, main 쓰레드에서 계속 입고 상태를 확인가능newThread(() -> {
lotteMart.received();
}).start();
// 2. 입고 확인 (데몬) - while문으로 계속 상태 확인 (100ms마다)// 메인 쓰레드에서 100ms마다 상태 확인 (폴링while (true) {
try {
Thread.sleep(100); // 주기적인 검사 (폴링 간격) - 0.1초마다 lotteMart.getValue() 호출해서 입고 여부 확인, 값이 null이면 계속 기다림
} catch (InterruptedException e) {
thrownewRuntimeException(e);
}
// 값이 설정되면 알림 보내고 루프 종료if (lotteMart.getValue() != null) { // request (polling <- 데몬으로 돌아서 계속 요청하니까 다른 요청을 받을 수가 없다
customer1.update(lotteMart.getValue() + "이 들어왔습니다."); // 상품 도착 알림 받고 breakbreak;
} else {
System.out.println("상품이 아직 들어오지 않았습니다");
}
}
}
}
✅ 특징 요약
항목
설명
🔁 Polling 방식
while (true) 루프로 계속 마트 상태를 확인함 (lotteMart.getValue())
🕰️ 주기적 확인
Thread.sleep(100)으로 100ms 간격으로 상태 확인 → 폴링 주기
🧵 멀티스레드 구조
입고 작업(received())은 별도 스레드에서 수행 → 메인 스레드가 폴링 가능
🧠 단순한 감시 로직
값이 null인지 아닌지만 확인 → 상태 감지 조건이 명확함
🔒 상태 직접 접근
옵저버가 Subject 상태를 직접 확인하는 구조 → Push가 아닌 Pull
💬 출력 메시지 분기
입고 전에는 "상품이 아직 들어오지 않았습니다" 반복 출력, 입고 후 "~이 들어왔습니다" 출력
🛑 한 번만 알림 후 종료
상품이 들어오면 update() 한 번 호출하고 break로 루프 종료함 (반복 감지 X)
✅ 장점
장점
설명
구현이 간단함
복잡한 이벤트 리스너 없이 상태만 보면 됨
병렬 구조 가능
Thread로 입고 처리와 감시를 동시에 수행
제어권 유지
감지 타이밍과 주기 설정이 명확함 (sleep()으로 직접 제어 가능)
❌ 단점
단점
설명
CPU 리소스 낭비
계속 상태를 확인하기 때문에, 의미 없는 반복이 발생 가능
실시간성 한계
100ms보다 빠른 반응은 불가능 (sleep 주기에 의존)
비효율적인 구조
상태가 거의 안 바뀌는 경우에도 계속 확인 요청 발생
Observer 패턴 구조와 다름
진짜 옵저버(Push 구조)가 아니라, 상태 직접 확인(Pull 구조)
🔹 2. Push 방식
발행자의 상태가 변경되면 옵저버에게 즉시 알려줌
📌 코드 흐름
EMart
package ex08.push.pub;
import ex08.push.sub.Customer;
import java.util.ArrayList;
import java.util.List;
// 구현할 때는 각 메서드의 책임을 알아야 한다publicclassEMartimplementsMart {
// 구독자 명단private List<Customer> customerList = newArrayList<Customer>();
// 구독 등록@Overridepublicvoidadd(Customer customer) {
customerList.add(customer);
}
// 구독 취소@Overridepublicvoidremove(Customer customer) {
customerList.remove(customer);
}
// 출판@Overridepublicvoidreceive() {
for (inti=0; i < 5; i++) {
System.out.println(".");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
thrownewRuntimeException(e);
}
}
notify("EMart : 딸기"); // 출판 완료 후 고객에게 알림 <- callback 후 push
}
// 알림@Overridepublicvoidnotify(String msg) {
for (Customer customer : customerList) {
customer.update(msg);
}
}
}
LotteMart
package ex08.push.pub;
import ex08.push.sub.Customer;
import java.util.ArrayList;
import java.util.List;
// 구현할 때는 각 메서드의 책임을 알아야 한다publicclassLotteMartimplementsMart {
// 구독자 명단private List<Customer> customerList = newArrayList<Customer>();
// 구독 등록@Overridepublicvoidadd(Customer customer) {
customerList.add(customer);
}
// 구독 취소@Overridepublicvoidremove(Customer customer) {
customerList.remove(customer);
}
// 출판@Overridepublicvoidreceive() {
for (inti=0; i < 5; i++) {
System.out.println(".");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
thrownewRuntimeException(e);
}
}
notify("LotteMart : 바나나"); // 출판 완료 후 고객에게 알림 <- callback 후 push
}
// 알림@Overridepublicvoidnotify(String msg) {
for (Customer customer : customerList) {
customer.update(msg);
}
}
}
→ 발행자(Subject, 여기선 EMart)가 상태 변화(=딸기 입고)를 감지하고, 즉시 옵저버(Customer)들에게 알리는 구조