minghxx.blog
  • [Spring] 스프링 MVC 1편 6) 스프링 MVC 기본 기능(2)
    2023년 11월 08일 09시 43분 37초에 업로드 된 글입니다.
    작성자: 민발자
    728x90

    스프링 MVC 1편 백엔드 웹 개발 핵심 기술 

    Session 6 스프링 MVC 기본 기능

    8. HTTP 요청 파라미터 - @ModelAttribute

    1) @ModelAttribute

    요청 파라미터를 받아서 필요한 객체를 만들고 객체에 값을 넣어주어야 한다

    스프링 mvc는 이 과정을 자동화해주는 @ModelAttribute 기능을 제공한다.

     

    2) HelloData

    @Data
    public class HelloData {
      private String username;
      private int age;
    }

    롬복 @Data 사용하면 @Getter, @Setter, @ToStirng, @EqualsAndHashCode, @RequiredArgsConstructor를 자동으로 적용

     

    3) modelAttributeV1

    public String modelAttributeV1(@ModelAttribute HelloData helloData) { ... }

    HelloData 객체가 생성되고 요청 파라미터 값도 모두 들어가 있다.

     

    4) @ModelAttribute 동작 

    HelloData 객체 생성

    요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다.

    해당 프로퍼티의 setter를 호출해서 파라미터 값을 입력(바인딩)한다.

     

    5) 프로퍼티

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

    username 프로퍼티 값을 변경하면 setUsername()이 호출되고, 조회하면 getUsername()이 호출된다.

     

    6) 바인딩 오류

    숫자가 들어가야 할 곳에 문자를 넣으면 BindException 발생

     

    7) @ModelAttribute 생략 - modelAttributeV2

    public String modelAttributeV2(HelloData helloData) { ... }

    @RequestParam도 생략가능하니 혼란 발생할 수 있다.

    String, int, Integer 같은 단순 타입 ▶ @RequestParam

    argument resolver로 지정해 둔 타입 외 나머지 ▶ @ModelAttribute 


    9. HTTP 요청 메시지 - 단순 텍스트

    1) HTTP 메시지 바디에 데이터를 직접 담아서 요청

    주로 json 사용

    요청 파라미터와 다르게 @RequestParam, @ModelAttribute 사용 불가(HTML Form 형식으로 전달되는 경우 요청 파라미터로 인정)

    ▶ InputStream을 사용해 직접 읽을 수 있다.

     

    2) Input, Output 스트림, Reader

    /**
    * InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
    * OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
    */
    @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");
    }

     

    InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회

    OutputStream(Writer) : HTTP 응답 메시지 바디에 직접 결과 출력

     

    3) HttpEntity

    /**
     * HttpEntity: HTTP header, body 정보를 편리하게 조회
     * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용 *
     * 응답에서도 HttpEntity 사용 가능
     * - 메시지 바디 정보 직접 반환(view 조회X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     */
    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
        String messageBody = httpEntity.getBody();
        log.info("messageBody={}", messageBody);
    
        return new HttpEntity<>("ok");
    }

    HttpEntity : HTTP header, body 정보를 편리하게 조회, 메시지 바디 정보를 직접 조회, 요청 파라미터를 조회하는 기능과 관계없음

    HttpEntity는 응답에서도 사용가능, 메시지 바디 정보 직접 반환, 헤더 정보 포함 가능, view 조회 X

    - HttpEntity를 상속받은 다음 객체들도 같은 기능 제공

    RequestEntity : HttpMethod, url 정보 추가, 요청에서 사용

    ResponseEntity : HTTP 상태코드 설정 가능, 응답에서 사용

     

    4) @RequestBody

     /**
     * @RequestBody
     * - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     * @ResponseBody
     * - 메시지 바디 정보 직접 반환(view 조회X)
     * - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     */
     public HttpEntity<String> requestBodyStringV4(@RequestBody String messageBody){ ... }

    실무에서 많이 사용

    @RequestBody를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.

    헤더 정보가 필요하다면 HttpEntity를 사용하거나 @RequestHeader를 사용하면 된다.

    요청 파라미터 조회와 전혀 관계없다!!

     

    5) 요청 파라미터 vs HTTP 메시지 바디

    요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute

    HTTP 미시지 바디를 직접 조회하는 기능 : @RequestBody

     

    6) @ResponseBody

    응답 결과를 HTTP 메시지 바디에 직접 담아 전달, view 사용하지 않음


    10. HTTP 요청 메시지 - json

    1) @RequestBody 문자 변환

    @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 메시지에 데이터를 꺼내고 messageBody에 저장

    문자로 된 json 데이터인 messageBody를 ObjectMapper를 통해 자바 객체로 변환

     

    2) @RequestBody 객체 변환

    /**
    * @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
    * HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (content-type: application/json)
    */
    @ResponseBody
    @PostMapping("/request-body-json-v3")
    public String requestBodyJsonV3(@RequestBody HelloData helloData) {
    	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
    	return "ok";
    }

    @RequestBody에 직접 만든 객체를 지정할 수 있다.

    HttpEntity, @RequestBody를 사용하면 HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 문자나 객체 등으로 변환해 줌, json도 객체로 변환해 줌

    이때 @RequestBody는 생략 불가능 ▶ @ModelAttribute가 적용됨

    HTTP 요청 시에 content-type: application/json인지 꼭 확인! 그래야 json을 처리할 수 있는 HTTP 메시지 컨버터가 실행

     

    3) HttpEntity 사용

    @ResponseBody
    @PostMapping("/request-body-json-v4")
    public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
        HelloData data = httpEntity.getBody();
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        return "ok";
    }

     

     

    4) json 응답

    /**
     * @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 data){
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        return data;
    }

    @ResponseBody 응답의 경우 해당 객체를 HTTP 메시지 바디에 직접 넣어줄 수 있다.

    - 동작

    @RequestBody 요청 json 요청 → HTTP 메시지 컨버터 객체 

    @ResponseBody 응답 객체  HTTP 메시지 컨버터  json 응답


    11. 응답 - 정적 리소스, 뷰 템플릿

    1) 스프링에서 응답 데이터를 만드는 방법 3가지

    - 정적 리소스

    웹 브라우저에 정적인 HTML, CSS, js을 제공할 때는 정적 리소스 사용

    - 뷰 템플릿 사용

    웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿 사용

    - HTTP 메시지 사용

    HTTP API를 제공하는 경우에는 HTML이 아닌 데이터를 전달, HTTP 메시지 바디에 json 형식으로 데이터를 실어 전송

     

    2) 정적 리소스

    스프링 부트는 /static, /public, /resources, /META-INF/resources 디렉터리에 있는 정적 리소스 제공

    파일을 변경 없이 그대로 그대로 서비스

     

    3) 뷰 템플릿

    뷰 템플릿을 거쳐 HTML이 생성되고 뷰가 응답을 만들어 전달

    일반적으로 HTML을 동적으로 생성하는 용도로 사용

    스프링 부트는 기본 뷰 템플릿 경로를 제공 src/main/resources/templates

    -Contorller + ModelAndView 반환

    @RequestMapping("/response-view-v1")
    public ModelAndView responseViewV1() {
        ModelAndView mav = new ModelAndView("response/hello").addObject("data", "hello!!!!");
        return mav;
    }

     

    -hello.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    <p th:text="${data}">empty</p>
    </body>
    </html>

    렌더링 될 때 data를 전달받은 값으로 바꿔서 렌더링

     

    - String 반환

    @RequestMapping("/response-view-v2")
    public String responseViewV2(Model model) {
        model.addAttribute("data", "hello!!!!");
        return "response/hello";
    }

    반환된 문자열은 뷰의 논리이름

    @ResponseBody 사용하면 뷰 템플릿이 렌더링 해주는 것이 아니라 메시지 바디에 그래로 문자열 전달

    @ResponseBody가 없으면 뷰 리졸버 실행해 뷰를 찾고 렌더링

     

    - void 반환

    @RequestMapping("/response/hello")
    public void responseViewV3(Model model) {
        model.addAttribute("data", "hello!!!!");
    }

    컨트롤러를 사용하고 HttpServletResponse, OutputStream 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 온 컨트롤러의 경로를 논리 뷰 이름으로 사용 /response/hello → response/hello

    명시성이 떨어지고 요청 경로와 뷰 이름이 동일한 경우도 많이 없어서 권장하지 않음

     

    4) thymeleaf 설정

    thymeleaf 라이브러리를 추가하면 스프링 부트가 자동으로 ThymeleafViewResolver와 필요한 스프링 빈들을 등록해주고 설정 정보도 자동으로 설정해 줌

    spring.thymeleaf.prefix=classpath:/templates/
    spring.thymeleaf.suffix=.html

    12. HTTP 응답 - HTTP API, 메시지 바디에 직접 입력

    1) responseBodyV1

    HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 응답 메시지 전달

    response.getWriter().write("ok")

     

     

    2) responseBodyV2

    ResponseEntity는 HTTP 메시지 헤더, 바디, HTTP 응답 코드 설정 가능

    뷰를 사용하지 않고 HTTP 컨버터 통해 메시지를 직접 입력

     return new ResponseEntity<>("ok", HttpStatus.OK);

     

     

    3) responseBodyV3

    @ResponseBody 애노테이션을 사용하면 뷰를 사용하지 않고 HTTP 컨버터 통해 메시지를 직접 입력 가능

    return "ok";

     

    4) responseBodyJsonV1

    ResponseEntity를 반환 HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어 반환

    return new ResponseEntity<>(helloData, HttpStatus.OK);

     

     

    5) responseBodyJsonV2

    @ResponseBody 애노테이션을 사용해서  HTTP 컨버터 통해 JSON 형식으로 변환되어 반환

    응답코드는 애노테이션 사용해 설정 가능 @ResponseStatus(HttpStatus.OK)

     

    6) @RestController

    @ResponseBody 클래스 레벨에 두면 모든 컨트롤러에 적용 

    뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력 Rest API 만들 때 사용하는 컨트롤러

    @ResponseBody + @Contorller = @RestController


    13. HTTP 메시지 컨버터

    1) @ResponseBody 사용 원리

    클라이언트 요청

    컨트롤러 호출 @ResponseBody를 사용

    HttpMessageConverter가 동작

    기본 문자 → StringHttpMessageConverter

    기본 객체  MappingJackson2HttpMessageConverter

    HTTP 바디에 문자 내용을 직접 반환 

    - 참고 : 응답의 경우 클라이언트의 HTTp Accept 헤더와 서버의 컨트롤러 반환 타입 정보를 조합해 HttpMessageConverter가 선택

     

    2) 스프링 MVC HttpMessageConverter 적용

    HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)

    HTTP 응답 : @ResponseBody, HttpEntity(ResponsetEntity)

     

    3) 주요한 메시지 컨버터

    대상 클래스 타입과 미디어 타입을 체크해서 사용 여부를 결정

     

    4) HTTP 요청 데이터 읽기

    HTTP 요청이 오고 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용

    메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 호출

    대상 클래스 타입 지원하는지, Content-Type 미디어 타입을 지원하는지 조건을 만족하면 read()를 호출해서 객체를 생성하고 반환

     

    5) HTTP 응답 데이터 생성

    컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다.

    메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWriter() 호출

    대상 클래스 타입 지원하는지, Content-Type 미디어 타입을 지원하는지 조건을 만족하면 write() 호출해서 HTTP 응답 메시지 바디에 데이터 생성


    14. 요청 매핑 핸들러 어댑터 구조

    1) 스프링 mvc 구조

    RequestMappingHandlerAdapter 핸들러 어댑터와 HTTP 메시지 컨버터 연관

     

    2) RequestMappingHandlerAdapter 동작 방식

    RequestMapping 핸들러 어댑터 호출

    ArgumentResolver가 컨트롤러의 파라미터, 애노테이션 정보를 기반으로 전달 데이터를 생성해 컨트롤러로 넘겨줌

    컨트롤러 호출

    ReturnValueHandler가 컨트롤러의 반환 값을 변환

     

    3) ArgumentResolver

    애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있는데 이것을 유연하게 처리해주는 것이 ArgumentResolver

    ArgumentResolver가 컨트롤러의 파라미터, 애노테이션 정보를 기반으로 전달 데이터를 생성하고 파라미터 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨줌

    ArgumentResolver supportParameter()를 호출해서 해당 파라미터를 지원하는지 체크, 지원하면 resolveArgument() 호출해서 실제 객체를 생성, 생성된 객체가 컨트롤러 호출 시 넘어가는 것

     

    4) ReturnValueHandler
    응답 값을 변환하고 처리함

    컨트롤러에서 String, ModelAndView로 반환할 때 ReturnValueHandler가 응답 값을 변환해 줌

     

    5) HTTP 메시지 컨버터 위치

    HTTP 메시지 컨버터를 사용하는 @RequestBody도 컨트롤러가 필요로 하는 파라미터 값에 사용된다.

    @ResponseBody의 경우에도 컨트롤러 반환 값을 이용

    요청의 경우 @RequestBody, HttpEntity를 처리하는 ArgumentResolver가 있음 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것

    응답의 경우 @ResponseBody, HttpEntity를 처리하는 ReturnValueHandler가 있음, 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만듦

     

     

     

     

     

     

    728x90
    댓글