지금까지 웬만한 개발은 Spring MVC로 작성한 탓에 WebFlux를 공부하면서 새로 태어난 기분이었다. 정말 간단한 요청/응답 방식으로 개발을 했기 때문에 이번 공부에서 많은 것을 얻어 갈 수 있었다.
WebFlux로 코딩하기 전에 Flux가 어떤 흐름으로 작동되는지 알아보자.
현재 코드는 실제로 개발할 때 사용되진 않고 흐름을 알기 위해 작성된 코드이다.
절대, 실제 프로젝트에서 개발 용도로 사용하면 안된다.
제일 먼저 Filter를 구성해보자.
// MyFilter.java
public class MyFilter implements Filter { // 1
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("필터 실행됨");
HttpServletResponse servletResponse = (HttpServletResponse) response; // 2
servletResponse.setContentType("text/event-stream; charset=utf-8");
PrintWriter out = servletResponse.getWriter(); // 3
for(int i = 0; i < 5; i++) { // 4
out.print("응답 " + i + "\n");
out.flush();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
번호 주석 순으로 설명을 하자면,
Filter
를 상속받고 doFiler
메서드를 오버라이딩한다.ServletResponse
객체인 response
를 HttpServletResponse
로 다운캐스팅한다.다음으로, Filter를 설정해보자.
// MyFilterConfig.java
@Configuration
public class MyFilterConfig{
@Bean
public FilterRegistrationBean<Filter> addFilter(){
System.out.println("필터 등록됨.");
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new MyFilter());
bean.addUrlPatterns("/sse");
return bean;
}
}
프로젝트 실행시, bean을 하나 생성한다.
bean에 대한 내용은 Filter
객체를 담고 있는 FilterRegistrationBean
을 반환해주는데, 전에 만들어줬던 MyFilter로 생성한다.
그리고, /sse
라는 url패턴으로 등록하고 해당 url로 요청시 Filter
에 걸리게 설정한다.
하나의 브라우저에 localhost:8080/sse를 띄우면, 다음과 같은 결과를 볼 수 있다.
이처럼, loop가 끝날 때까지 브라우저에 출력해준다.
한가지 특징이 있다면, 뒤로가기 버튼 위에 계속 빙글빙글 돌고 있다는 것이다.~저게 뭔지 모르겠다~
즉, 필터가 작동 중이라는 것이고, loop가 끝나면 필터를 종료시켜 나머지 작업을 진행한다.
당연한 것이라고 생각할 수 있지만, 우리는 이것을 통해 이벤트가 발생하면 즉각적으로 반응하게 구현할 수 있다.
어떤 이벤트를 발생할지 생각해보자.
가장 단순한 것은 어떤 데이터가 들어왔거나, 바뀌었거나 등등을 생각해볼 수 있다.
그럼, String 리스트와 boolean 변수를 이용하여 boolean 값이 true일 때 이벤트를 발생시켜보자.
이벤트를 발생시키기 위한 객체이다. 코드는 아래와 같다.
// EventNotify.java
@Component
public class EventNotify {
private final List<String> events = new ArrayList<>(); // 1
private boolean change = false; // 2
public void add(String data) { // 3
events.add(data);
change = true;
}
public List<String> getEvents() {
return events;
}
public boolean isChange() {
return change;
}
public void setChange(boolean change) {
this.change = change;
}
}
true
로 바꿔 변화했다는 것을 알려준다.그리고, EventNotify
객체를 받아야 하므로 기존 MyFilter
를 수정해주자.
public class MyFilter implements Filter {
private EventNotify eventNotify; // 1
public MyFilter(EventNotify eventNotify) {
this.eventNotify = eventNotify;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
/*
* 중략
*/
while(true){ // 2
try {
if (eventNotify.isChange()){ // 3
int last = eventNotify.getEvents().size() - 1;
out.print("응답 " + eventNotify.getEvents().get(last) + "\n");
out.flush();
eventNotify.setChange(false);
}
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
EventNotify
필드와 생성자를 만들어준다.while(true)
로 계속 필터에 머물도록 실행해준다. 이벤트를 통해 필터에서 바로 처리하기 위함이다.eventNotify
필드가 바뀌었다면, 데이터가 들어있는 리스트의 마지막 요소를 브라우저로 뿌린다. 그 후, change
를 false
로 바꾼다.다음으로, EventNotify
에 데이터를 넣어줄 필터가 필요하다.
이름은 단순하게 MyFilter2
라고 하자.
// MyFilter2.java
public class MyFilter2 implements Filter {
private final EventNotify eventNotify;
public MyFilter2(EventNotify eventNotify) {
this.eventNotify = eventNotify;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("필터2 실행됨");
// /add를 통해 데이터를 발생시켜서 /sse에 반영
eventNotify.add("새로운 데이터");
}
}
똑같이, EventNotify
필드와 생성자를 생성해준다.
그리고 MyFilter2
가 실행하면 eventNotify
에 새로운 데이터
라는 데이터를 넣는 로직을 작성해보자.
// MyFilterConfig.java
@Configuration
@RequiredArgsConstructor
public class MyFilterConfig{
private final EventNotify eventNotify;
@Bean
public FilterRegistrationBean<Filter> addFilter(){
System.out.println("필터 등록됨.");
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new MyFilter(eventNotify)); // 1
bean.addUrlPatterns("/sse");
return bean;
}
@Bean
public FilterRegistrationBean<Filter> addFilter2(){ // 2
System.out.println("필터 등록됨.");
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new MyFilter2(eventNotify));
bean.addUrlPatterns("/add");
return bean;
}
}
MyFilter
생성자로 수정한다.MyFilter2
에 해당하는 bean을 등록한다. 이때, MyFilter2
는 /add
url 패턴에 필터가 걸리게 설정한다.모든 준비는 완료되었다.
바로 실행해보자.
하나의 브라우저에 즉각적인 응답을 확인하기 위해 localhost:8080/sse를 띄우고, 데이터를 삽입하는 (이벤트를 발생시키는) URL localhost:8080/add로 요청을 보내면 아래와 같은 결과를 확인 할 수 있다.
Spring MVC에서도 Flux와 비슷한 흐름을 경험할 수 있어서 WebFlux를 공부하는데 큰 도움이 됐다.
비록, 지금 올린 코드를 그대로 개발에 사용할 순 없지만 이러한 흐름을 알고 공부하는 것과 모르고 공부하는 것에는 큰 차이에 있을 것이라고 생각한다.
백엔드 개발자의 주요 역할은 클라이언트가 요구하는 데이터를 빠르고 정확하게 제공하는 것이라고 생각한다.
사람이 많이 몰리는 대규모 트래픽에도 문제없이 작동돼야 하기 때문에 논블로킹 + 비동기로 처리하는 WebFlux를 알아둬야 한다고 생각한다.
다음 포스트에서 WebFlux에서 제공하는 인터페이스를 다뤄보겠다.