본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Spring framework/Spring Webflux

spring webflux 3 (웹플럭스 적용기, 함수형과 반응형)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 3. 12.

 

웹플럭스는 대부분의 코딩 방식을 람다 또는 함수형으로 표현하고 있다.

메소드형태도 대부분 체이닝 방식을 써서 작업 할 때 메소드명이 차레대로 이어지는 걸 보면 해당 코드의 이해가 쉬워진다.

 

그러면, 전에 만들었던 RoutsConfig 클래스에 매소드를 한개 추가하여보자.

새로 만들 메소드 이름은 requestGetParam이라는 메소드이다.

@Bean
public RouterFunction<ServerResponse> requestGetParam() {
    RequestPredicate predicate = RequestPredicates.GET("/request").and(RequestPredicates.accept(MediaType.TEXT_PLAIN));
    //1. Request + Predicate(요청 + ~이다) => 사용자의 요청 종류 및 형태
		
    //2.웹플럭스에게 전달할 RouterFunction 클래스를 제작, 함수형태로 전달한다.
    RouterFunction<ServerResponse> response = RouterFunctions.route(predicate, (request)->{   //3. 내부 람다식은 HandlerFunction 인터페이스의 모양이다.     
        System.out.println(request.queryParams());
        String justText = "get param";
        Mono<String> mapper = Mono.just(justText);  //4.Mono클래스를 통해서 전달할 내용을 만들고
        Mono<ServerResponse> res = ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(BodyInserters.fromProducer(mapper, String.class));  //5. 어떤 형태로 전달할지 작성후
        return res;  //6.전달!
    });
    return response;  //7. 해당 함수를 웹플럭스에게 전달한다.
}

 

설명을 전부 한번 달아보았다.

맨 처음에 보이는 RequestPredicate 클래스는 사용자의 요청형태를 정의하는 클래스이다.

 

또한, 웹플럭스는 복잡한 설정 및 사용법을 줄이기 위해서 RouterFunctions 라는 클래스를 제공하여 해당 클래스의 route 메소드만 활용한 다면 대부분 행동을 쉽게 할 수 있도록 구성되어 있다.

 

메소드가 대부분 직관적으로 이름이 되어있어 어렵지가 않다.

요청에 대한 행동을 정의하는 HandlerFunction 인터페이스 같은 경우에는 ServerRequest라는 클래스 객체를 파라미터로 사용하여 사용자가 다룰 수 있게 해 주었다.

그래서 System.out.println(request.queryParams()); 를 통해서 요청에 대한 파리미터를 한번 출력해 보았다.

파라미터도 잘 받아지고, 응답도 잘 하는군!

 

여기서 흥미로운 것은 함수형으로 구현되어 있기 때문에 재미있는 현상이 존재한다.

아래 requestPathAndParam이라는 메소드를 추가하여보자.

@Bean
public RouterFunction<ServerResponse> requestPathAndParam() {
    System.out.println("METHOD START!!!!");
    final int number[] = {10};
    final RequestPredicate predicate = RequestPredicates.GET("/path/{name}").and(RequestPredicates.accept(MediaType.TEXT_PLAIN));		

    RouterFunction<ServerResponse> response = RouterFunctions.route(predicate, (request)->{
			
        System.out.println(request.queryParams());
        System.out.println(request.pathVariables()); // path같은 것도 되는구나!
			
        String justText = "path and param";
        Mono<String> mapper = Mono.just(justText);
        Mono<ServerResponse> res = ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(BodyInserters.fromProducer(mapper, String.class));
        number[0] = number[0]+1; // 출력하면 11이 계속 나오겠지??
        System.out.printf("number : %d\n",number[0]);
        return res;
    });
    System.out.println("METHOD END!!!!");
    return response;
}	

 

그리고 재시작을 하여보면 아래와같은 재미있는 현상을 볼 수 있다.

number로 선언한 숫자 배열값이 증가한다

 

"METHOD START!!!!" 라는 문구와 "METHOD END!!!!" 문구가 딱 한번만 출력되었다.

그리고 숫자 배열로 선언한 number 배열의 첫번째 데이터가 요청을 할 때마다 1씩 증가하는 것도 보인다.

 

사용자의 요청이 들어 올 때 마다 requestPathAndParam 메소드가 호출될 줄 알았지만,

웹플럭스에서는requestPathAndParam을 계속해서 호출하는 것이 아니라, requestPathAndParam에 정의된 함수를 요청에 대해서 적용하기 때문에 마치 메소드 내부의 숫자배열 number가 static하게 사용되는 것을 볼 수가 있다.

 

 

일반 요청에 대해서 몇가지 메소드를 살펴 보았으니 파일업로드와 다운로드에 대한 형태도 잠시 살펴보자.

파일을 다운로드 하는 것도 크게 어렵지 않다.

fileDownload라는 메소드를 추가하여 보자.

    //파일 다운로드
    @Bean
    public RouterFunction<ServerResponse> fileDownload() {
        RequestPredicate predicate = RequestPredicates.GET("/file").and(RequestPredicates.accept(MediaType.TEXT_PLAIN));
        RouterFunction<ServerResponse> response = RouterFunctions.route(predicate, (request)->{        
            Resource resource = new FileSystemResource("D:/test.csv");
            Mono<Resource> mapper = Mono.just(resource);  //Mono의 제네릭에 의해 다양한 클래스를 받을 수 있다.
            
            //헤더라는 메소드와 바디라는 메소드는 이름만 봐도 어떠한 역할을 하는 지 알 것 같다.
            Mono<ServerResponse> res = ServerResponse.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"test.csv\"")
                    .body(BodyInserters.fromProducer(mapper, Resource.class));
            return res;  
        });
        return response;
    }

 

RouterFunction 클래스의 핵심은 HandlerFunction 인터페이스가 사용 하고 있는 Mono라는 클래스이다.

요녀석만 위 내용처럼 자유롭게 사용한다면 1차고비는 넘어가게 되는 것 같다.

 

단순하게 생각한다면 저 Mono라는 클래스에 제네릭 타입만 바꾸어서 작업하면 될 것 같은데..

그 생각을 깨버리는 구간이 바로 파일 업로드를 구현할 때 이다.

fileUpload 메소드를 한번 만들어 보았다. Multipart 요청에 대해서 응답하게 하였다.

    //파일 업로드
    @Bean
    public RouterFunction<ServerResponse> fileUpload() {
        RequestPredicate predicate = RequestPredicates.POST("/fileUpload").and(RequestPredicates.accept(MediaType.MULTIPART_FORM_DATA));
        RouterFunction<ServerResponse> response = RouterFunctions.route(predicate, (request)->{        

            Mono<String> mapper = request.multipartData().map(it -> it.get("files"))
                    .flatMapMany(Flux::fromIterable)
                    .cast(FilePart.class)
                    .flatMap(it -> it.transferTo(Paths.get("D:/test_upload/" + it.filename())))
                    .then(Mono.just("OK"));				
            Mono<ServerResponse> res = ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(BodyInserters.fromProducer(mapper, String.class));
            return res;  
        });
        return response;
    }

 

request 객체에서 multipartData 라는 메소드를 호출하면 Mono<MultiValueMap<String, Part>> 라는 객체를 리턴한다.

계속 사용한 메소드는 Mono의 just 메소드랑 subscribe 메소드인데..

Flux는 또 뭐란말인가..

그래도 다행인 것은 마치 Java의 컬렉션을 다룰 때 사용한 stream사용하는 방법과 비슷한 느낌으로의 메소드가 보이고 있다라는 점 이다.

 

결국 이 웹플럭스를 잘 다루고 못 다루고는 저 Mono라는 클래스에 대해서 제대로 살펴보아야 한다는 점 이다.

아래 사이트는 공식 홈페이지이다.

https://projectreactor.io/

 

다음번에는 요 Mono와 Flux의 사용법에 대해서 좀 더 파 보아야 할 것 같다.

spring webflux!

example.zip
0.07MB

 

 

* 악성코드나 바이러스 없습니다!!! >ㅁ<

* 내용을 채우고 수정중입니다.

* 튜토리얼이나 가이드 목적보다도 개념정리에 목적을 두고 쓰고있습니다. 틀린부분이나 누락된 부분은 꼭 알려주세요!

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글