• 가이드 원본 사이트

  • multipart-file upload 관련 HTTP 요청을 처리할 수 있는 서버 애플리케이션을 만들어볼 것이다.

Thymeleaf

Spring MVC

  • 이번 application은 MVC 구조를 따른다. 여기서 View 부분에 Thymeleaf를 활용하는 형태.

  • 이번 application은 multi-part된 file을 HTTP를 통해 받은 다음에 이를 저장할 것이다.

Servlet, web.xml, 그리고 Spring에서의 현재

  • Java의 경우 MVC의 Controller 부분 구현에 대해서 Servlet이라는 class를 활용하며, 이 ServletServlet Container에 의해 관리가 된다.

  • Servlet의 환경설정, Servlet의 유효시간, Servlet이 무슨 url에서 온 request를 처리할지 등을 다 설정하는 환경설정 관련 파일이 바로 web.xml이다.

  • 예전에는 Servlet을 프로그래밍하는데... 먼저 해당 interface를 implement한 HTTPServlet 같은 class를 extend해가지고 구현을 했다. 그리고 환경 설정 같은 경우 직접 web.xml의 조작을 필요로 했다.

  • Spring의 등장 이후, web.xml의 구축은 굳이 우리가 할 필요 없고 수정사항만 application.properties에 넣으면 되며, Servlet interface의 구현 class들을 직접 활용하는 대신 @RESTController, 혹은 그냥 @Controller을 활용해가지고 프로그래밍 하는 것도 가능하게 되었다.

  • 위처럼 Spring에서 만든 annotation인 @Controller들의 경우에는 Spring에서 bean으로 취급을 한다. 하지만 이 @Controller들이 활용하는, 즉 @Controller아래에서 MVC controller의 controller역할을 하는 Servlet Container은 Spring에서 Bean으로 취급을 하지 않는다. 이 점 유의.

  • 하지만 Spring에서 자동으로 servlet container을 auto-configure 및 실행하는 것은 맞으며, 앞에 말했듯 이 servlet container 관련 설정을 application.properties에서 하는 것이 가능하다.

  • 결론은, multi-part file upload를 위해 우리가 추가적으로 고려해야 할 환경 관련 설정은 하나도 없다. 그냥 dependency만 처음에 initializr로 잘 넣으면 된다. (web, thymeleaf)

  • 굳이 multi-part upload에 대한 세부사항을 조절하고 싶은 경우, MultiPartConfigElement class를 활용하면 된다. Spring에서 MultiPartConfigElement를 활용해 관련 환경 설정을 하는 방법 아니면 servlet의 multipart 운용을 어떻게 할지를 application.properties에서 설정해도 되며 이 튜토리얼에서는 이 방식을 통해 최대 업로드 파일 크기 및 최대 요청 파일 크기를 제한해 뒀다.

구현 관련

listUploadedFiles method 관련

Model interface

  • documentation

  • MVC의 Model에 해당하는 interface다.

  • MVC model을 알고 있다면, View가 여기서 data를 확인해가지고 user에게 어떻게 전시할지를 업데이트함을 알고 있을 것이다. 이 Model 역할을 하는 애들에 관한 interface가 Model interface라고 생각하면 된다.

  • listUploadFiles에서 해당 interface 부류의 한 객체에 특정 attribute를 추가하고 있는걸 볼 수 있다. 이는 특정 view가 참고할 data를 추가하는 것으로 볼 수 있다. 이 부분에 대해 더 자세히 알아보려면 몇가지를 더 설명해야 한다.

MvcUriComponentsBuilder class

Collectors class

  • documentation

  • data들을 특정 collection으로 aggregate할 때 쓰임.

  • 여기서는 List를 만드는데 사용.

listUploadedFiles의 역할

  • 한 문장으로 코드가 이루어져있는데, storageServiceloadAll의 결과물에 대해서 FileUploadController class에 있는 serveFile이라는 method를 전부 적용해가지고 UriComponent가 element로 있는 List를 만든 다음에 Model type인 model에 files라는 attribute로 집어넣고 있다. 여기서 fromMethodName의 세번째 parameter의 argument는 serveFile에 전달할 parameter이며, loadAll()의 결과물에 전처리를 해가지고 전달을 하고 있다.

  • 참고로 storageService의 type은 StorageService로 이 예제에서 직접 정의한 interface인데, 그걸 잠깐 확인해보면 loadAllJava에 기본으로 있는 class인 Path에 대한 Stream을 만든다는 것이다. 이 점과 loadAlllistUploadFiles라는 메서드의 이름, 그리고 listUploadFiles@GetMapping("/")으로 되어 있는 것을 통해 유추할 수 있는게 뭐냐면 현재 storage에 전달되어 있는 파일들의 경로를 전부 추출해서 Stream형태로 전달한다는 것이다.

  • 그리고 이 Streammapping을 적용해가지고 각 Path에 대한 UriComponentBuilder을 만들어 List에 저장하고, 그것을 model에다가 files라는 attribute로 저장하는 것이다.

  • 이후 uploadForm이라는 String을 반환하는 것을 볼 수 있다. 이건 사실 렌더링할 View template를 지칭하는 것이다. 해당 이름의 template은 프로젝트의 resources 디렉토리에 html 확장자로 저장되어 있는걸 볼 수 있으며 Thymeleaf를 활용하는 것도 확인 가능.

  • 그 html 파일을 자세히 살펴보면 다음과 같은 영역이 있다.

<div>
    <ul>
        <li th:each="file : ${files}">
            <a th:href="${file}" th:text="${file}" />
        </li>
    </ul>
</div>
  • 이는 thymeleaf iterator을 활용하는 html 구문이다. files라는 attribute가 iterate 대상이고, 하는것은 대충 업로드 된 파일들이 무엇인지를 전부 출력.

  • listUploadedFiles는 그냥 /에 대해 요청이 왔을 때, 업로드 된 파일들이 무엇인지 그 경로를 전부 출력하는데 사용되는 method인 것이다.

@ResponseBody, ResponseEntity

  • @RequestBody, @ResponseBody는 JSON을 Java 객체로, Java 객체를 JSON으로 변환할 때 사용되는 annotation이다.

  • 관련 글

  • 이 때 좀 구체적으로 보낼 response에 대해 설정을 하고 싶을 때 사용되는게 바로 ResponseEntity class이다. Header, Body에 status code까지 추가가 가능하다.

  • documentation

  • 예시의 경우, 특정 resource에 대해 요청이 왔을 때 이 파일이 존재하면 그 파일을 전달하고, 없으면 오류를 반환하는 method인 serveFile에서 활용되었다.

@PathVariable

  • URL의 path부분에 있는 경로를 변수로 사용하겠다는 annotation

  • 구체적으로 path의 어느 부분을 사용할지는 Mapping부분의 {}로 둘러싸인 이름 부분을 통해 정한다. 즉 코드의 경우 /files 다음의 부분을 변수로 사용하겠다는 것을 뜻함.

  • serveFile method에서 사용중.

  • 관련 글

Resource

  • Spring에서 제공하는 Interface로, low-level data를 나타내는 추상화된 녀석이다.

  • Java에도 비슷한 역할을 하는 URL이라는 class가 있지만 별로여가지고, 일반적인 low-level-resource를 접근하는데 필요한 기능들 및 존재 여부에 대한 기능들을 implement하는 틀을 만든게 바로 이 interface이다.

  • documentation

  • 여기서는 그냥 파일을 Resource로 취급 하고 있다.

MultipartFile

  • 서버에서 multipart-file upload를 지원해야 하다보니 관련 요청을 받을텐데, 그럴려면 multipart-file을 나타내는 class가 필요할거고 그게 바로

  • 말 그대로, multi-part file을 나타내는 class다.

  • documentation

  • 임시로 디스크나 메모리에 저장되어 있는데, 이를 구체적으로 어디에 넣을지 추가 조작을 우리가 해야 한다.

RedirectAttributes

  • interface로, Model interface를 확장한 interface이다.

  • documentation

  • 관련 유용한 글 1(PRG pattern). 해당 interface가 Post/Redirect/Get(PRG) pattern이랑 관련되어 있는 interface라 이에 대해 잘 모르면 좌측 글 참고바람.

  • 관련 유용한 글 2. RedirectAttribute가 PRG pattern에서 어떻게 사용되는지에 대한 글이다.

  • 위 두 글을 읽으면 이 class에 대해서 충분히 알 수 있고, 또 handleFileUpload가 무슨 일을 하는지도 알 수 있다. 간단히 말하자면 storageServicestore을 활용해 file의 저장을 한 다음, redirectAttribute를 통해 redirection 후에 있을 GET parameter을 미리 저장하고 "redirect:/"을 retur해 해당 위치에 대한 GET 요청을 하도록 유도해 PRG pattern까지 구현.

@ExceptionHandler

  • Controller에서 발생한 exception을 처리하는 method임을 지정하는 annotation이다.

  • 여기서는 자체 정의한 exception인 StorageFileNotFoundException을 처리하는 method를 지정하기 위해 사용했다. 참고로 모든 StorageException을 처리하는 것은 아니라는 점 유의. 파일 저장을 관리하는 storageService측에서 어떤 파일을 찾는데 실패했을 때 나오는 exception만 처리 하며 이 경우 파일을 못찾았다는 response를 보낸다.

@Service

  • StorageService라는 자체 class는 @Service annotation을 달고 있다.

  • @Component를 포함하고 있는 annotation이다. 즉 bean으로 취급된다.

  • 그럼 구체적으로 무슨 역할인가? 일단 이론적으론 DDD 패턴에서 business model을 담당하는 class임을 의미한다. Spring의 경우 service layer에 속할 bean이라는 것을 표기할 때 이 annotation을 사용한다.

  • @Component 대비 특별한 기능은 없다. 보통 business logic에 속하는 녀석이다 + @Transaction annotation을 활용해 Transaction management에 용이하다를 표기하는 정도?

@EnableConfigurationProperties, @ConfigurationProperties

  • StorageProperties라는 class가 @ConfigurationProperties가 붙어있고, main application에 @EnableConfigurationProperties가 붙어있는데, 전자는 configuration bean들을 enable하는데 사용되며, 후자는 해당 class의 configuration을 application.properties등을 통해 설정하는 것이 가능하다는 것을 의미한다.

  • 이 때 application.properties에서 사용해야 하는 이름이 @ConfigurationProperties에 주어지는데 여기서는 그게 storage다. 이를 활용해 StoragePropertieslocation 값을 바꾸는 것이 가능하다.

결과물

  • 뭔가 많이 복잡해보이지만, 구현이 그런 것이고 결국 크게 보면 multipart-file에 대한 upload 지원 및 현재 업로드 된 파일들이 무엇인지를 웹페이지에 전시하는것 말고 딱히 하는 것은 없다.

  • 이 때 server이 운용되는 시스템의 filesystem을 직접적으로 활용하고 있는데, 실제로는 이러는 것이 좋지 않고 관계형 DB, 혹은 NoSQL DB, 혹은 그냥 임시 저장소에다가 저장을 해두는 것이 좋다.

  • https://github.com/baekrang256/Backend-Practice/tree/main/SpringTutorial/uploadingfiles

profile
안 흔하고 싶은 개발자. 관심 분야 : 임베디드/컴퓨터 시스템 및 아키텍처/웹/AI

0개의 댓글