Spring MVC - 기본 기능

유병익·2022년 10월 24일
0
post-thumbnail

1. 프로젝트 생성


1.1 사전 준비물


  • 프로젝트 선택
    • Project: Gradle Project
    • Spring Boot: 최신 버전 선택
    • Language: Java
  • Project Metadata
    • Group: hello
    • Artifact: springmvc
    • Name: springmvc
    • Package name: hello.springmvc
    • Packaging: Jar (주의!)
    • Java: 11
    • Dependencies: Spring Web, Thymeleaf, Lombok
  • build.gradle 설정
    plugins {
    		id 'org.springframework.boot' version '2.4.3'
    		id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    		id 'java'
    }
    group = 'hello'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
    configurations {
    		compileOnly {
    				extendsFrom annotationProcessor
    		}
    }
    repositories {
    		mavenCentral()
    }
    dependencies {
    		implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    		implementation 'org.springframework.boot:spring-boot-starter-web'
    		compileOnly 'org.projectlombok:lombok'
    		annotationProcessor 'org.projectlombok:lombok'
    		testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    test {
    		useJUnitPlatform()
    }

1.2 Welcome Page


  • index.html
    <!DOCTYPE html>
    <html>
    <head>
    		 <meta charset="UTF-8">
    		 <title>Title</title>
    </head><body>
    <ul>
    		 <li>로그 출력
    				 <ul>
    						 <li><a href="/log-test">로그 테스트</a></li>
    				 </ul>
    		 </li>
     <!-- -->
     <li>요청 매핑
    		 <ul>
    				 <li><a href="/hello-basic">hello-basic</a></li>
    				 <li><a href="/mapping-get-v1">HTTP 메서드 매핑</a></li>
    				 <li><a href="/mapping-get-v2">HTTP 메서드 매핑 축약</a></li>
    				 <li><a href="/mapping/userA">경로 변수</a></li>
    				 <li><a href="/mapping/users/userA/orders/100">경로 변수 다중</a></li>
    				 <li><a href="/mapping-param?mode=debug">특정 파라미터 조건 매핑</a></li>
    				 <li><a href="/mapping-header">특정 헤더 조건 매핑(POST MAN 필요)</a></li>
    				 <li><a href="/mapping-consume">미디어 타입 조건 매핑 Content-Type(POST MAN 필요)</a></li>
    				 <li><a href="/mapping-produce">미디어 타입 조건 매핑 Accept(POST MAN 필요)</a></li>
    		 </ul>
     </li>
     <li>요청 매핑 - API 예시
    		 <ul>
    				 <li>POST MAN 필요</li>
    		 </ul>
     </li>
     <li>HTTP 요청 기본
    		 <ul>
    				 <li><a href="/headers">기본, 헤더 조회</a></li>
    		 </ul>
     </li>
     <li>HTTP 요청 파라미터
    		 <ul>
    				 <li><a href="/request-param-v1?username=hello&age=20">요청 파라미터 v1</a></li>
    				 <li><a href="/request-param-v2?username=hello&age=20">요청 파라미터 v2</a></li>
    				 <li><a href="/request-param-v3?username=hello&age=20">요청 파라미터 v3</a></li>
    				 <li><a href="/request-param-v4?username=hello&age=20">요청 파라미터 v4</a></li>
    				 <li><a href="/request-param-required?username=hello&age=20">요청 파라미터 필수</a></li>
    				 <li><a href="/request-param-default?username=hello&age=20">요청 파라미터 기본 값</a></li>
    				 <li><a href="/request-param-map?username=hello&age=20">요청 파라미터 MAP</a></li>
    				 <li><a href="/model-attribute-v1?username=hello&age=20">요청 파라미터 @ModelAttribute v1</a></li>
    				 <li><a href="/model-attribute-v2?username=hello&age=20">요청 파라미터 @ModelAttribute v2</a></li>
    		 </ul>
     </li>
     <li>HTTP 요청 메시지
    		 <ul>
    				 <li>POST MAN</li>
    		 </ul>
     </li>
     <li>HTTP 응답 - 정적 리소스, 뷰 템플릿
    		 <ul>
    				 <li><a href="/basic/hello-form.html">정적 리소스</a></li>
    				 <li><a href="/response-view-v1">뷰 템플릿 v1</a></li>
    				 <li><a href="/response-view-v2">뷰 템플릿 v2</a></li>
    		 </ul>
     </li>
     <li>HTTP 응답 - HTTP API, 메시지 바디에 직접 입력
    		 <ul>
    				 <li><a href="/response-body-string-v1">HTTP API String v1</a></li>
    				 <li><a href="/response-body-string-v2">HTTP API String v2</a></li>
    				 <li><a href="/response-body-string-v3">HTTP API String v3</a></li>
    				 <li><a href="/response-body-json-v1">HTTP API Json v1</a></li>
    				 <li><a href="/response-body-json-v2">HTTP API Json v2</a></li>
    		 </ul>
     </li>
    </ul>
    </body>
    </html>

학습할 내용을 편리하게 참고하기 위한 Welcome 페이지

Spring Boot에 Jar 를 사용할 때, /resources/static/index.hml 위치에 index.html 파일을 두면 Welcome 페이지로 처리해준다.

2. Logging


📌 운영 시스템에서는 System.out.println() - Console 사용 X
→ 별도의 로깅 라이브러리를 사용

2.1 Loggin Library


Spring Boot Library를 사용하면 Spring Boot Logging Library(spring-boot-starter-logging)가 함께 포함된다.

2.1.1 Spring Boot Logging Library


📌 Logback, Log4J, Log4J2 등 여러 Library를 통합해 Interface로 제공하는 것이 바로 SLF4J

📌 실무에서는 대부분 Logback 사용

2.1.2 Log 선언 및 사용


  • Code

    package hello.springmvc.basic;
    
    import lombok.extern.slf4j.Slf4j;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /*
    @Slf4j 를 사용하면 private final Logger log = LoggerFactory.getLogger(getClass()); 생략 가능
    */
    
    @Slf4j   
    @RestController
    public class LogTestController {
    
    //  private final Logger log = LoggerFactory.getLogger(getClass());
    
        @RequestMapping("/log-test")
        public String logTest(){
            String name = "Spring";
    
    			  //System.out.println("name = " + name);
            //log.trace("trace log ="+name); 
    				//이 방법은 권장 X, 불필요한 작업에 리소스를 사용할 여지가 있다.
    
            log.trace("trace Log={}", name);
            log.debug("debug Log={}", name);
            log.info("info Log={}", name);
            log.warn("warn Log={}", name);
            log.error("error Log={}", name);
    
            return "ok";
        }
    }
  • @Slf4j

    • Lombok에서 제공하는 Annotation
    • private final Logger log = LoggerFactory.getLogger(getClass());생략 가능
  • application.properties

    #전체 로그 레벨 설정(기본 info)
    logging.level.root=info
    #hello.springmvc 패키지와 그 하위 로그 레벨 설정
    logging.level.hello.springmvc=debug
  • LEVEL

    • TRACE > DEBUG > INFO > WARN > ERROR
    • 일반적으로 개발 서버는 DEBUG, 운영 서버는 INFO 출력

2.1.3 올바른 Log 사용법


log.debug("data="+data);
  • 로그 출력 레벨을 info로 설정해도 해당 코드에 있는 "data="+data가 실제 실행이 되어 버린다.
  • 결과적으로 문자 더하기 연산이 발생한다.
log.debug("data={}", data);
  • info로 설정하면 아무일도 발생하지 않는다.
  • 따라서 앞과 같은 의미없는 연산이 발생하지 않는다.

2.1.4 Log의 장점


  • Thread, Class 정보 등 부가 정보를 함께 볼 수 있음
  • 출력 형식 조정 가능
  • 설정 정보만을 수정해서 상황에 맞게 Log 조절 가능
  • Console 출력 뿐만 아니라 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
    • 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
  • System.out보다 성능이 좋다.

참고

SLF4J - http://www.slf4j.org

Logback - http://logback.qos.ch

Spring Boot Log 기능 -https://docs.spring.io/spring-boot/docs/current/reference/html/spring-bootfeatures.html#boot-features-logging

3. HTTP Request Mapping


3.1 @RequestMapping 기능


  • MappingController
    package hello.springmvc.basic.requestmapping;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    @Slf4j
    @RestController
    public class MappingController {
    
        @RequestMapping("/hello-basic")
        public String helloBasic() {
            log.info("helloBasic");
            return "ok";
        }
    
        /**
         * method 특정 HTTP 메서드 요청만 허용
         * GET, HEAD, POST, PUT, PATCH, DELETE
         */
        @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
        public String mappingGetV1() {
            log.info("mappingGetV1");
            return "ok";
        }
    
        /**
         * 편리한 축약 애노테이션 (코드보기)
         *
         * @GetMapping
         * @PostMapping
         * @PutMapping
         * @DeleteMapping
         * @PatchMapping
         */
        @GetMapping(value = "/mapping-get-v2")
        public String mappingGetV2() {
            log.info("mapping-get-v2");
            return "ok";
        }
    
        /**
         * PathVariable 사용
         * 변수명이 같으면 생략 가능
         *
         * @PathVariable("userId") String userId -> @PathVariable String userId
         */
        @GetMapping("/mapping/{userId}")
        public String mappingPath(@PathVariable String userId) {
            log.info("mappingPath userId={}", userId);
            return "ok";
        }
    
        /**
         * PathVariable 사용 다중
         */
        @GetMapping("/mapping/users/{userId}/orders/{orderId}")
        public String mappingPath(@PathVariable String userId, @PathVariable Long
                orderId) {
            log.info("mappingPath userId={}, orderId={}", userId, orderId);
            return "ok";
        }
    
        /**
         * 특정 헤더로 추가 매핑
         * headers="mode",
         * headers="!mode"
         * headers="mode=debug"
         * headers="mode!=debug" (! = )
         */
        @GetMapping(value = "/mapping-header", headers = "mode=debug")
        public String mappingHeader() {
            log.info("mappingHeader");
            return "ok";
        }
    
        /**
         * Content-Type 헤더 기반 추가 매핑 Media Type
         * consumes="application/json"
         * consumes="!application/json"
         * consumes="application/*"
         * consumes="*\/*"
         * MediaType.APPLICATION_JSON_VALUE
         */
        @PostMapping(value = "/mapping-consume", consumes = "application/json")
        public String mappingConsumes() {
            log.info("mappingConsumes");
            return "ok";
        }
    
        /**
         * Accept 헤더 기반 Media Type
         * produces = "text/html"
         * produces = "!text/html"
         * produces = "text/*"
         * produces = "*\/*"
         */
        @PostMapping(value = "/mapping-produce", produces = "text/html")
        public String mappingProduces() {
            log.info("mappingProduces");
            return "ok";
        }
    
    }

3.1.1 URL Mapping


		@RequestMapping("/hello-basic")
    	public String helloBasic() {
            log.info("helloBasic");
            return "ok";
    }
  • @RequestMapping("/hello-basic")
    • /hello-basic URL이 호출되면 해당 메서드가 실행되도록 Mapping
    • 대부분의 속성을 배열로 제공 → 다중 설정 가능
      • ex) @RequestMapping({"/hello-basic", "/hello-go"})

3.1.2 HTTP Method Mapping


		/**
     * method 특정 HTTP 메서드 요청만 허용
     * GET, HEAD, POST, PUT, PATCH, DELETE
     */
    @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
    public String mappingGetV1() {
        log.info("mappingGetV1");
        return "ok";
    }
  • HTTP 메서드
    • @RequestMapping에 method 속성으로 HTTP Method 지정 X → HTTP Method와 무관하게 호출
      → GET, HEAD, POST, PUT, PATCH, DELETE 모두 허용
		 /**
     * 편리한 축약 애노테이션 (코드보기)
     *
     * @GetMapping
     * @PostMapping
     * @PutMapping
     * @DeleteMapping
     * @PatchMapping
     */
    @GetMapping(value = "/mapping-get-v2")
    public String mappingGetV2() {
        log.info("mapping-get-v2");
        return "ok";
    }
  • HTTP Method를 축약한 Annotation을 사용하는 것이 더 직관적

3.1.3 PathVariable 사용


		/**
     * PathVariable 사용
     * 변수명이 같으면 생략 가능
     * @PathVariable("userId") String userId -> @PathVariable String userId
     * PathVariable 다중 사용 가능
     */
    @GetMapping("/mapping/users/{userId}/orders/{orderId}")
    public String mappingPath(@PathVariable String userId, @PathVariable Long
            orderId) {
        log.info("mappingPath userId={}, orderId={}", userId, orderId);
        return "ok";
    }
  • 최근 HTTP API는 리소스 경로에 식별자를 넣는 스타일 선호
  • @PathVariable을 사용하면 매칭 되는 부분을 조회 가능
  • @PathVariable의 이름 == Parameter 이름 → 생략 가능

3.1.4 특정 파라미터 조건 매핑


		/**
     * 특정 헤더로 추가 매핑
     * headers="mode",
     * headers="!mode"
     * headers="mode=debug"
     * headers="mode!=debug" (! = )
     */
    @GetMapping(value = "/mapping-header", headers = "mode=debug")
    public String mappingHeader() {
        log.info("mappingHeader");
        return "ok";
    }
  • 특정 파라미터가 있거나 없는 조건을 추가할 수 있다.
  • 잘 사용하지는 않는다.

3.1.5 특정 헤더 조건 매핑


		/**
     * 특정 헤더로 추가 매핑
     * headers="mode",
     * headers="!mode"
     * headers="mode=debug"
     * headers="mode!=debug" (! = )
     */
    @GetMapping(value = "/mapping-header", headers = "mode=debug")
    public String mappingHeader() {
        log.info("mappingHeader");
        return "ok";
    }
  • 파라미터 매핑과 비슷하지만, HTTP 헤더를 사용한다.

3.1.6 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume


		/**
     * Content-Type 헤더 기반 추가 매핑 Media Type
     * consumes="application/json"
     * consumes="!application/json"
     * consumes="application/*"
     * consumes="*\/*"
     * MediaType.APPLICATION_JSON_VALUE
     */
    @PostMapping(value = "/mapping-consume", consumes = "application/json")
    public String mappingConsumes() {
        log.info("mappingConsumes");
        return "ok";
    }
  • HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑
  • 만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type) 반환

3.1.7 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce


		/**
     * Accept 헤더 기반 Media Type
     * produces = "text/html"
     * produces = "!text/html"
     * produces = "text/*"
     * produces = "*\/*"
     */
    @PostMapping(value = "/mapping-produce", produces = "text/html")
    public String mappingProduces() {
        log.info("mappingProduces");
        return "ok";
    }
  • HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑
  • 만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환

3.1.8 API 사용 예시


  • Code
    package hello.springmvc.basic.requestmapping;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.*;
    
    @Slf4j
    @RestController
    @RequestMapping("/mapping/users")
    public class MappingClassController {
    
    //    회원 목록 조회: GET /users
    //    회원 등록: POST /users
    //    회원 조회: GET /users/{userId}
    //    회원 수정: PATCH /users/{userId}
    //    회원 삭제: DELETE /users/{userId}
    
        @GetMapping
        public String user() {
            return "get users";
        }
    
        @PostMapping
        public String addUser() {
            return "post user";
        }
    
        @GetMapping("/{userId}")
        public String findUser(@PathVariable String userId) {
            return "get userId = " + userId;
        }
    
        @PatchMapping("/{userId}")
        public String updateUser(@PathVariable String userId) {
            return "update userId = " + userId;
        }
    
        @DeleteMapping("/{userId}")
        public String deleteUser(@PathVariable String userId) {
            return "delete userId = " + userId;
        }
    }
  • @RequestMapping("/mapping/users")
    • 클래스 레벨에 매핑 → Method 레벨의 매핑 정보와 조합해서 사용

4. HTTP Request


4.1 HTTP Request Header 조회


  • RequestHeaderController
    package hello.springmvc.basic.request;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.tomcat.jni.Local;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.ResponseEntity;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.servlet.function.ServerRequest;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Locale;
    
    @Slf4j
    @RestController
    public class RequestHeaderController {
    
        @RequestMapping("/headers")
        public String headers(HttpServletRequest request,
                              HttpServletResponse response,
                              HttpMethod httpMethod,
                              Locale locale,
                              @RequestHeader MultiValueMap<String, String> headerMap,
                              @RequestHeader("host") String host,
                              @CookieValue(value = "myCookie", required = false) String cookie) {
    
            log.info("request={}", request);
            log.info("response={}", response);
            log.info("httpMethod={}", httpMethod);
            log.info("locale={}", locale);
            log.info("headerMap={}", headerMap);
            log.info("header host={}", host);
            log.info("myCookie={}", cookie);
            return "ok";
    
        }
    }
  • HttpMethod
    • HTTP 메서드 조회
  • Locale
    • Locale 정보 조회
  • @RequestHeader MultiValueMap<String, String> headerMap
    • 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
  • @RequestHeader("host") String host
    • 특정 HTTP 헤더를 조회한다.
    • 속성
      • 필수 값 여부: required
      • 기본 값 속성: defaultValue
  • @CookieValue(value = "myCookie", required = false) String cookie
    • 특정 쿠키를 조회한다.
    • 속성
      • 필수 값 여부: required
      • 기본 값: defaultValue

MultiValueMap

  • 하나의 키에 여러 값을 받을 수 있다.
  • HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.

5. HTTP Request Data 조회


📌 클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터
    • /url?username=hello&age=20
    • URL의 쿼리 파라미터에 데이터를 포함해서 전달
  • POST - HTML Form
    • content-type: application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
  • HTTP message body에 데이터를 직접 담아서 요청
    - HTTP API에서 주로 사용, JSON, XML, TEXT
    - 주로 JSON 사용

5.1 HTTP Request Parameter - Query Parameter , HTML Form


GET Query Parameter 이든 POST HTML Form 이든 형식이 같으므로 구분없이 조회 가능

→ 간단하게 요청 파라미터(request parameter) 조회라 함

  • RequestParamController
    package hello.springmvc.basic.request;
    
    import hello.springmvc.basic.HelloData;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Map;
    
    @Slf4j
    @Controller
    public class RequestParamController {
    
        @RequestMapping("/request-param-v1")
        public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
            String username = request.getParameter("username");
            int age = Integer.parseInt(request.getParameter("age"));
            log.info("username = {}, age = {}", username, age);
    
            response.getWriter().write("ok");
        }
    
        @ResponseBody
        // 없으면 return 하는 string으로 Viewresolver를 통해 해당하는 이름의 view를 찾게됨, 이걸 쓰면 response body부분에 return 하는 값을 넣어서 반환
        @RequestMapping("/request-param-v2")
        public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge) {
            log.info("username = {}, age = {}", memberName, memberAge);
            return "ok";
        }
    
        //HTTP 파라미터명과 변수명이 같으면 ("~~") 부분 생략 가능!
        @ResponseBody
        @RequestMapping("/request-param-v3")
        public String requestParamV3(@RequestParam String username, @RequestParam int age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    
        //기본 자료형에 대해 @RequestParam 생략 가능;  근데 써주는게 좀 더 직관적으로 이해하기 편함
        @ResponseBody
        @RequestMapping("/request-param-v4")
        public String requestParamV4(String username, int age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    
        // Java에선 기본형(int, byte, char, short, long, float, double, boolean)에
        // null을 입력 할 수 없음
        // null을 int형에 넣으려면 Integer으로 선언
        // required = true 일 때, /request-param-required/username= 와 같이 빈문자가 입력되면 입력이 된 것으로 받아들임
        @ResponseBody
        @RequestMapping("/request-param-required")
        public String requestParamRequired(@RequestParam(required = true) String username, @RequestParam(required = false) Integer age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    
        // 사실상 defaultValue를 사용하면 required가 의미가 없음
        // 빈 문자열이 들어오더라도 defaultValue가 적용됨
        // null이 들어와도 defaultValue가 적용됨
        @ResponseBody
        @RequestMapping("/request-param-default")
        public String requestParamDefault(@RequestParam(required = true, defaultValue = "guest") String username, @RequestParam(required = false, defaultValue = "-1") int age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    
        @ResponseBody
        @RequestMapping("/request-param-map")
        public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
            log.info("username = {}, age = {}", paramMap.get("username"), paramMap.get("age"));
            return "ok";
        }
    
        @ResponseBody
        @RequestMapping("/model-attribute-v1")
        public String modelAttributeV1(@ModelAttribute HelloData helloData) {
            log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
            log.info("helloData = {}", helloData);
            return "ok";
        }
    
        //@ModelAttribute 는 생략 가능하지만 혼돈이 생길 수도 있음
        @ResponseBody
        @RequestMapping("/model-attribute-v2")
        public String modelAttributeV2(HelloData helloData) {
            log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
            log.info("helloData = {}", helloData);
            return "ok";
        }
    
    }

5.1.1 HttpServletRequest


		@RequestMapping("/request-param-v1")
    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));
        log.info("username = {}, age = {}", username, age);

        response.getWriter().write("ok");
    }
  • request.getParameter()
    • 단순히 HttpServletRequest가 제공하는 방식으로 Request Parameter 조회

5.1.2 @RequestParam


  • @RequestParam

    		@ResponseBody
        // @ResponseBody가 없으면 return 하는 string으로 Viewresolver를 통해 해당하는 이름의 view를 찾음 
    		// @ResponseBody를 추가하면 response body에 return하는 값을 넣어서 반환
        @RequestMapping("/request-param-v2")
        public String requestParamV2(@RequestParam("username") String memberName, @RequestParam("age") int memberAge) {
            log.info("username = {}, age = {}", memberName, memberAge);
            return "ok";
        }
    • @RequestParam
      • Parameter 이름으로 바인딩
      • @RequestParam의 name 속성이 Parameter 이름으로 사용
    • @ResponseBody
      • View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
  • @ReqeustParam name 속성 생략

    		//HTTP Parameter 이름과 변수 이름이 같으면 @RequestParma의 name 속성 생략 가능!
        @ResponseBody
        @RequestMapping("/request-param-v3")
        public String requestParamV3(@RequestParam String username, @RequestParam int age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    • HTTP Parameter 이름 == 변수 이름
      → @RequestParma의 name 속성 생략 가능
  • @RequestParam 생략

    		//기본 자료형에 대해 @RequestParam 생략 가능
        //히지만, 써주는 것이 직관적으로 이해하기 편함
        @ResponseBody
        @RequestMapping("/request-param-v4")
        public String requestParamV4(String username, int age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    • String , int , Integer 등의 단순 타입이면 @RequestParam 도 생략 가능
  • Required

    		// Java에선 기본형(int, byte, char, short, long, float, double, boolean)에
        // null을 입력 할 수 없음
        // null을 int형에 넣으려면 Integer으로 선언
        // "" != null 
        @ResponseBody
        @RequestMapping("/request-param-required")
        public String requestParamRequired(@RequestParam(required = true) String username, @RequestParam(required = false) Integer age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    • @RequestParam.required
      - Parameter 필수 여부
      - Default = true

    ⚠️ 주의

    • 기본형(primitive)에는 null을 입력할 수 없다.
      - ex) null을 int에 입력
      → Integer로 변경하거나 defaultValue 사용
  • DefaultValue
    		// 사실상 defaultValue를 사용하면 required가 의미가 없음
        // 빈 문자열이 들어오더라도 defaultValue가 적용됨
        // null이 들어와도 defaultValue가 적용됨
        @ResponseBody
        @RequestMapping("/request-param-default")
        public String requestParamDefault(@RequestParam(required = true, defaultValue = "guest") String username, @RequestParam(required = false, defaultValue = "-1") int age) {
            log.info("username = {}, age = {}", username, age);
            return "ok";
        }
    • 파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용 가능
      • String의 경우 null과 빈 문자열(””) 모두 defaultValue 할당
    • 이미 기본 값이 있기 때문에 required 는 의미 없음
  • Parameter를 Map으로 조회하기 - requestParamMap
    		@ResponseBody
        @RequestMapping("/request-param-map")
        public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
            log.info("username = {}, age = {}", paramMap.get("username"), paramMap.get("age"));
            return "ok";
        }
    • Parameter를 Map , MultiValueMap으로 조회 가능
    • Parameter 값이 하나가 아니라면, MultiValueMap 사용

5.1.3 @ModelAttribute


  • @ModelAttribute

    		@ResponseBody
        @RequestMapping("/model-attribute-v1")
        public String modelAttributeV1(@ModelAttribute HelloData helloData) {
            log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
            log.info("helloData = {}", helloData);
            return "ok";
        }
    • 동작 순서
      1. HelloData 객체를 생성한다.
      2. Request Parameter 이름으로 HelloData 객체의 Property를 찾는다.
      3. 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)
        • ex) 파라미터 이름이 username → setUsername() 메서드를 찾아서 호출 +값을 입력

          Property

          객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다.

  • @ModelAttribute 생략

    		//@ModelAttribute 는 생략 가능하지만 혼돈이 생길 수도 있음
        @ResponseBody
        @RequestMapping("/model-attribute-v2")
        public String modelAttributeV2(HelloData helloData) {
            log.info("username = {}, age = {}", helloData.getUsername(), helloData.getAge());
            log.info("helloData = {}", helloData);
            return "ok";
        }
    • @ModelAttribute 생략 가능

      @RequestParam 또는 @ModelAttribute 생략시 다음 규칙 적용

      • String , int , Integer 같은 단순 타입 ⇒ @RequestParam
      • 나머지(argument resolver 로 지정해둔 타입 외) ⇒ @ModelAttribute

5.2 HTTP Request Message - Message Body - 단순 텍스트


  • HTTP API에서 주로 사용

    • ex) JSON, XML, TEXT ..
  • 데이터 형식은 주로 JSON사용

  • HTTP Message Body를 통해 직접 데이터가 넘어오는 경우

    • @RequestParam , @ModelAttribute 를 사용 X
      • HTML Form 형식으로 전달되는 경우는 Request Parameter로 인정
  • RequestBodyStringController

    package hello.springmvc.basic.request;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.HttpEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.StreamUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.Writer;
    import java.nio.charset.StandardCharsets;
    
    @Slf4j
    @Controller
    public class RequestBodyStringController {
    
        @PostMapping("/request-body-string-v1")
        public void requestBodyStringV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
            ServletInputStream inputStream = request.getInputStream();
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    
            log.info("messageBody = {}", messageBody);
            response.getWriter().write("ok");
        }
    
        @PostMapping("/request-body-string-v2")
        public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    
            log.info("messageBody = {}", messageBody);
            responseWriter.write("ok");
        }
    
        /**
         * HttpEntity: HTTP header, body 정보를 편리하게 조회
         * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
         * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
         *
         * 응답에도 HttpEntity 사용 가능
         * - 메시지 바디 정보 직접 반환(view 조회X)
         * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
         */
        @PostMapping("/request-body-string-v3")
        public void requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
            String messageBody = httpEntity.getBody();
            log.info("messageBody = {}", messageBody);
            //responseWriter.write("ok");
        }
    
        /**
         * @RequestBody
         * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
         * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
         *
         * @ResponseBody
         * - 메시지 바디 정보 직접 반환(view 조회X)
         * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
         */
        @ResponseBody
        @PostMapping("/request-body-string-v4")
        public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
            log.info("messageBody = {}", messageBody);
            return "ok";
        }
    }
  • Spring MVC가 지원하는 Parameter

    • InputStream(Reader)
      • HTTP 요청 메시지 바디의 내용을 직접 조회
    • OutputStream(Writer)
      • HTTP 응답 메시지의 바디에 직접 결과 출력
    • HttpEntity
      • HTTP header, body 정보를 편리하게 조회
      • 응답에도 사용 가능
      • view 조회X.
    • RequestEntity
      • HttpMethod, url 정보가 추가
      • 요청에서 사용
    • ResponseEntity
      • HTTP 상태 코드 설정 가능
      • 응답에서 사용
  • @RequestBody

    • HTTP Message Body 정보 조회 가능

    📌 Header정보가 필요하다면 HttpEntity 또는 @RequestHeader 사용

Request Paramter vs HTTP Message Body

  • @RequestParam , @ModelAttribute → Request Paramter를 조회하는 기능
  • @RequestBody → HTTP Message Body를 직접 조회하는 기능
  • @ResponseBody → 응답 결과를 HTTP Message Body에 담아서 전달
    • View 사용 X

5.3 HTTP Request Message - Message Body - JSON


  • RequestBodyJsonController
    package hello.springmvc.basic.request;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import hello.springmvc.basic.HelloData;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.HttpEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.StreamUtils;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    
    /**
     * {"username":"hello", "age":20}
     * content-type: application/json
     */
    @Slf4j
    @Controller
    public class RequestBodyJsonController {
    
        private ObjectMapper objectMapper = new ObjectMapper();
    
        @PostMapping("/request-body-json-v1")
        public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
            ServletInputStream inputStream = request.getInputStream();
            String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
    
            log.info("messageBody = {}",messageBody);
    
            HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
            log.info("username = {}, userage = {}",helloData.getUsername(),helloData.getAge());
    
            response.getWriter().write("ok");
        }
    
        /**
         * @RequestBody
         * HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
         *
         * @ResponseBody
         * - 모든 메서드에 @ResponseBody 적용
         * - 메시지 바디 정보 직접 반환(view 조회X)
         * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
         */
        @ResponseBody
        @PostMapping("/request-body-json-v2")
        public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
            log.info("messageBody = {}",messageBody);
    
            HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
            log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
    
            return "ok";
        }
    
        /**
         * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
         * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype:
        application/json)
         *
         */
        @ResponseBody
        @PostMapping("/request-body-json-v3")
        public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
            log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
            return "ok";
        }
    
        @ResponseBody
        @PostMapping("/request-body-json-v4")
        public String requestBodyJsonV4(HttpEntity<HelloData> data) throws IOException {
            HelloData helloData = data.getBody();
            log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
            return "ok";
        }
    
        /**
         * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
         * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype:
        application/json)
         *
         * @ResponseBody 적용
         * - 메시지 바디 정보 직접 반환(view 조회X)
         * - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
        (Accept: application/json)
         */
        @ResponseBody
        @PostMapping("/request-body-json-v5")
        public HelloData requestBodyJsonV5(@RequestBody HelloData helloData) throws IOException {
    
            log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
            return helloData;
        }
    }

5.3.1 HttpServletRequest


		private ObjectMapper objectMapper = new ObjectMapper();

    @PostMapping("/request-body-json-v1")
    public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        log.info("messageBody = {}",messageBody);
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username = {}, userage = {}",helloData.getUsername(),helloData.getAge());
        response.getWriter().write("ok");
    }
  • HttpServletRequest 사용
  • HTTP Message Body에서 데이터를 읽어와서, 문자로 변환
  • 문자로 변환된 JSON 데이터를 objectMapper(Jackson Library) 사용 → 자바 객체로 변환

5.3.2 @RequestBody 문자 변환


		/**
     * @RequestBody
     * HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     *
     * @ResponseBody
     * - 모든 메서드에 @ResponseBody 적용
     * - 메시지 바디 정보 직접 반환(view 조회X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     */
    @ResponseBody
    @PostMapping("/request-body-json-v2")
    public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
        log.info("messageBody = {}",messageBody);

        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());

        return "ok";
    }
  • @RequestBody를 사용해서 HTTP Message에서 데이터를 꺼내고 messageBody에 저장
  • messageBody 를 objectMapper를 통해서 자바 객체로 변환

5.3.3 @RequestBody 객체 변환


		/**
     * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
     * HttpMessageConverter 사용
			 -> MappingJackson2HttpMessageConverter (contenttype: application/json)
     *
     */
    @ResponseBody
    @PostMapping("/request-body-json-v3")
    public String requestBodyJsonV3(@RequestBody HelloData helloData) throws IOException {
        log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
        return "ok";
    }
		@ResponseBody
    @PostMapping("/request-body-json-v4")
    public String requestBodyJsonV4(HttpEntity<HelloData> data) throws IOException {
        HelloData helloData = data.getBody();
        log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
        return "ok";
    }
  • @RequestBody에 직접 만든 객체를 지정할 수 있다.

  • HttpEntity , @RequestBody를 사용하면,

    → HTTP Message Converter가 HTTP Message Body의 내용을 지정한 문자나 객체 등으로 변환

    📌 HTTP Message Converter는 문자 뿐만 아니라 JSON도 객체로 변환

5.3.4 @ResponseBody


		/**
     * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
     * HttpMessageConverter 사용 
     *  -> MappingJackson2HttpMessageConverter (content-type:application/json)
     *
     * @ResponseBody 적용
     * - 메시지 바디 정보 직접 반환(view 조회X)
     * - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
    (Accept: application/json)
     */
    @ResponseBody
    @PostMapping("/request-body-json-v5")
    public HelloData requestBodyJsonV5(@RequestBody HelloData helloData) throws IOException {

        log.info("username = {}, age = {}",helloData.getUsername(),helloData.getAge());
        return helloData;
    }
  • 응답의 경우에도 @ResponseBody를 사용하면 객체를 HTTP Message Body에 직접 넣어줄 수 있다.
    • HttpEntity역시 사용 가능

6. HTTP Response Message


6.1 HTTP Response Message - 정적 리소스


  • Spring Boot는 Classpath의 특정 디렉토리에 있는 정적 리소스 제공
    • /static , /public , /resources , /META-INF/resources
  • src/main/resources 는 리소스를 보관하는 장소 + Classpath의 시작 경로
    • 위 디렉토리에 리소스를 넣어두면 Spring Boot가 정적 리소스로 서비스 제공
  • 정적 리소스는 해당 파일을 변경 없이 그대로 서비스하는 것

6.2 HTTP Response Message - View Template 사용


  • Template를 통해 동적으로 HTML 생성

    • View가 응답을 만들어서 전달
    • 일반적으로 HTML을 동적으로 생성하는 용도로 사용.
  • Spring Boot는 기본 View Template 경로 제공

    • src/main/resources/templates
  • ResponseViewController

    package hello.springmvc.basic.response;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class ResponseViewController {
    
        @RequestMapping("/response-view-v1")
        public ModelAndView responseViewV1(){
            ModelAndView modelAndView = new ModelAndView("/response/hello")
                    .addObject("data","hello!");
            return modelAndView;
        }
    
        //@ResponseBody
        //이 경우 View를 찾지 않고 Response Body에 직접 문자열을 실어서 반환
        @RequestMapping("/response-view-v2")
        public String responseViewV2(Model model){
            model.addAttribute("data","hello!");
            return "response/hello";
        }
    
        /*
        @Controller 사용 && HttpServletResponse ,OutputStream 사용 X
        -> 요청 URL을 논리 View 이름으로 사용
    
        **명시성이 떨어짐 -> 권장하지 않음
         */
        @RequestMapping("/response/hello")
        public void responseViewV3(Model model){
            model.addAttribute("data","hello!");
        }
    
    }

6.2.1 String 반환 - View or HTTP 메시지


  • @ResponseBody가 없으면 return 값으로 View Resolver 실행
    • View를 찾고, Rendering
  • @ResponseBody가 있으면 HTTP Message Body에 직접 return 값을 입력

6.2.2 void 반환


  • @Controller 사용 + HTTP Message Body를 처리하는 Parameter (HttpServletResponse , OutputStream(Writer))가 없으면 요청 URL을 참고해서 논리 View 이름으로 사용
📌 이 방식은 명시성이 너무 떨어져 권장하지 않는다.

6.3 HTTP Response Message - Message Body


📌 HTTP API의 경우에는 HTML이 아니라 Data를 전달

→ HTTP Message Body에 JSON같은 형식으로 Data 입력

  • ResponseBodyController
    package hello.springmvc.basic.response;
    
    import hello.springmvc.basic.HelloData;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Controller
    @Slf4j
    public class ResponseBodyController {
    
        @GetMapping("/response-body-string-v1")
        public void responseBodyV1(HttpServletResponse response) throws IOException {
            response.getWriter().write("ok");
        }
    
        @GetMapping("/response-body-string-v2")
        public ResponseEntity<String> responseBodyV2() throws IOException {
            return new ResponseEntity<>("ok", HttpStatus.OK);
        }
    
        @ResponseBody
        @GetMapping("/response-body-string-v3")
        public String responseBodyV3() throws IOException {
            return "ok";
        }
    
        @GetMapping("/response-body-json-v1")
        public ResponseEntity<HelloData> responseJsonV1() throws IOException {
            HelloData helloData = new HelloData();
            helloData.setUsername("userA");
            helloData.setAge(10);
            return new ResponseEntity<>(helloData,HttpStatus.OK);
    
        }
    
        @ResponseStatus(HttpStatus.OK)
        @ResponseBody
        @GetMapping("/response-body-json-v2")
        public HelloData responseJsonV2() throws IOException {
            HelloData helloData = new HelloData();
            helloData.setUsername("userA");
            helloData.setAge(10);
            return helloData;
    
        }
    
    }

6.3.1 HttpServletResponse


  • HttpServletResponse객체를 통해서 HTTP Message Body에 직접 응답 메시지 입력

6.3.2 ResponseEntity


  • HttpEntity 를 상속
    • HttpEntity는 HTTP Message의 Header, Body 정보를 가지고 있다.
  • ResponseEntity는 추가로 HTTP 응답 코드를 설정 가능

6.3.3 @ResponseBody


  • @ResponseBody 를 사용하면 View를 사용 X
  • HTTP Message Converter를 통해서 HTTP Message Body에 직접 입력
  • ResponseEntity도 동일한 방식으로 동작

6.3.4 ResponseEntity<객체>


  • ResponseEntity 반환
  • HTTP Message Converter를 통해서 JSON 형식으로 변환 및 반환

6.3.5 @ResponseBody


  • @ResponseBody 를 사용하면 응답 코드를 설정하기 까다롭다.
  • @ResponseStatus Annotation을 사용하면 응답 코드 설정 가능 -
    📌Annotation 이기에, 응답 코드를 동적으로 변경할 수는 없다.
    프로그램 조건에 따라서 동적으로 변경하려면 ResponseEntity를 사용

@RestController

  • @Controller 대신 @RestController 사용 → 해당 Controller에 모두 @ResponseBody 적용
    • View Template 사용 X
    • HTTP Message Body에 직접 데이터 입력
  • Rest API(HTTP API)를 만들 때 사용
  • 참고로 @ResponseBody 는 클래스 레벨에 두면 전체에 메서드에 적용

7. HTTP Message Converter


7.1 개요


📌 JSON 데이터를 HTTP Message Body에서 직접 읽거나 쓰는 경우

HTTP Message Converter를 사용

  • @ResponseBody 사용 원리

    • @ResponseBody를 사용하면 HTTP Message Body에 문자 내용을 직접 반환
    • viewResolver 대신에 HttpMessageConverter 가 동작
    • 여러 HttpMessageConverter가 기본으로 등록되어 있음
      • StringHttpMessageConverter
        • 기본 문자 처리
      • MappingJackson2HttpMessageConverter
        • 기본 객체처리

참고


응답의 경우 클라이언트의 HTTP Accept Header & 서버의 Controller 반환 타입 정보를 조합해서 HttpMessageConverter 선택

Spring MVC는 아래 경우 HTTP Message Converter 적용

  • HTTP 요청 → @RequestBody , HttpEntity(RequestEntity)
  • HTTP 응답 → @ResponseBody , HttpEntity(ResponseEntity)
  • HTTP Message Converter Interface
    package org.springframework.http.converter;
    public interface HttpMessageConverter<T> {
    		boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    		boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    		List<MediaType> getSupportedMediaTypes();
    		T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
    		void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
    }
  • HTTP 메시지 컨버터는 HTTP 요청, HTTP 응답 둘 다 사용된다.
  • canRead() , canWrite()
    • Message Converter가 해당 클래스, 미디어타입을 지원하는지 체크
  • read() , write()
    • Message Converter를 통해서 메시지를 읽고 쓰는 기능

Spring Boot 기본 Message Converter (일부 생략)

  • ByteArrayHttpMessageConverter
  • StringHttpMessageConverter
  • MappingJackson2HttpMessageConverter

7.2 ByteArrayHttpMessageConverter


  • byte[] 데이터 처리
  • 클래스 타입
    • byte[]
  • Media Type
    • */*

7.3 StringHttpMessageConverter


//	content-type: application/json

@RequestMapping
void hello(@RequetsBody String data) {}
  • String 문자로 데이터를 처리
  • 클래스 타입
    • String
  • Media Type
    • */*

7.4 MappingJackson2HttpMessageConverter


//		content-type: application/json@RequestMapping
void hello(@RequetsBody HelloData data) {}
  • 클래스 타입
    • 객체 또는 HashMap
  • Media Type
    • application/json 관련

7.5 Message Converter 동작 과정


7.5.1 HTTP Request Data 읽기


  1. HTTP 요청
  2. Controller에서 @RequestBody , HttpEntity Parameter를 사용
  3. Message Converter가 Message를 읽을 수 있는지 확인
    • canRead() 호출
      • 클래스 타입 지원 여부 확인
        • ex) @RequestBody 의 대상 클래스 (byte[] , String , HelloData)
      • Content-Type 미디어 타입 지원 여부 확인
        • ex) text/plain , application/json , /
  4. canRead() 조건 만족 → read() 를 호출 → 객체 생성 & 반환

7.5.2 HTTP Response Data 생성


  1. Controller에서 @ResponseBody , HttpEntity 으로 반환
  2. 메시지 컨버터가 메시지를 쓸 수 있는지 확인
    • canWrite() 호출
      • 클래스 타입 지원 여부 확인
      • HTTP Request Accept의 Media Tyep 지원 여부 확인
        • 정확하게는 @RequestMapping의 produces
        • ex) text/plain , application/json , /
  3. canWrite() 조건 만족 → write()호출 → HTTP Response Message Body에 데이터 입력

8. Request Mapping Handler Adapter


8.1 구조


  1. RequestMappingHandlerAdapter 호출
    • @RequestMapping을 처리하는 Handler Adapter
  2. RequestMappingHandlerAdapterParameter & Annotation 기반으로 필요한Argument Resolver호출
  3. Argument Resolver는 HTTP Message Converter를 호출해 필요한 객체를 생성 및 반환
  4. Argument ResolverController에 필요한 전달 데이터 생성
  5. 필요한 Parameter가 준비되면, Controller 호출

8.2 ArgumentResolver


  • HandlerMethodArgumentResolver
    public interface HandlerMethodArgumentResolver {
    
    		boolean supportsParameter(MethodParameter parameter);
    
    		@Nullable
    		Object resolveArgument(MethodParameter parameter, 
    													 @Nullable ModelAndViewContainer mavContainer,
    													 NativeWebRequest webRequest, 
    													 @Nullable WebDataBinderFactory binderFactory) throws Exception;
    }

8.2.1 동작 방식


  1. ArgumentResolversupportsParameter() 호출
    • 해당 Paramter 지원 여부 확인
  2. 지원한다면, resolveArgument() 호출 → HTTP Message Converter를 호출해 실제 객체 생성
  3. 생성된 객체를 Controller에 전달

8.3 ReturnValueHandler


  • HandlerMethodReturnValueHandler
    public interface HandlerMethodReturnValueHandler {
    
    	boolean supportsReturnType(MethodParameter returnType);
    
    	void handleReturnValue(@Nullable Object returnValue, 
    												 MethodParameter returnType,
    												 ModelAndViewContainer mavContainer, 
    												 NativeWebRequest webRequest) throws Exception;
    }

8.3.1 동작 방식


  1. ReturnValueHandlersupportsReturnType() 호출

    • 해당 응답 값을 처리할 수 있는지 확인
  2. 처리할 수 있다면 handleReturnValue() 호출

    HTTP Message Converter를 통해Response Message 생성

참고

profile
Backend 개발자가 되고 싶은

0개의 댓글