ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • @RequestBody, @ResponseBody ??
    웹 개발 2024. 1. 27. 02:53
    반응형

    1. RuqeustBody

    • HttpMessageConverter 가 Http Request Body 내에 있는 데이터를 Java 객체로 변환해주는 역할
    • @Valid를 붙이면 검증을 할 수 있고, 실패 시 MEthodArgumentNotValidException 을 던진다.
    • 주로 POST 또는 PUT 요청과 함께 사용
    • 주로 JSON 또는 XML 형식의 데이터를 받아서 자바 객체로 변환하는데 사용

    2. ResponseBody

    • 컨트롤러 메서드가 반환하는 데이터를 HTTP 응답의 메시지 바디(body)에 넣어주는 역할
    • 주로 컨트롤로 메서드가 JSON 또는 XML 형식의 데이터를 반환할 때 사용
    • 스프링은 반환된 객체를 해당 형식의 데이터로 변환하고 응답 메시지를 바디에 담아 클라이언트로 전송

    스프링 4.0 이후에는 @RestController 어노테이션에 @Controller, @ResponseBody 가 함께 있어 생략해서 사용할 수 있다.

     

     

    3. Spring MVC 구조

     1) 클라이언트의 요청:
      - 클라이언트가 HTTP 요청을 서버에 전송합니다.

     2) DispatcherServlet의 역할:
      - 클라이언트의 모든 요청은 DispatcherServlet이라는 서블릿이 처음으로 받게 됩니다.
      - DispatcherServlet은 요청을 전방향으로 디스패치하여 알맞은 핸들러(Handler)에게 전달합니다.

     3) HandlerMapping - RequestMapping 검색:
      - DispatcherServlet은 HandlerMapping에게 요청 경로에 대응되는 핸들러(컨트롤러)를 찾아달라고 요청합니다.
      - HandlerMapping은 @RequestMapping 어노테이션을 통해 등록된 핸들러를 실행할 수 있는 적절한 HandlerAdpater에게 반환합니다.

     * HandlerAdapter 인터페이스를 구현한 다양한 클래스
      (1) AnnotationMethodHandlerAdapter
        - @RequestMapping과 같은 어노테이션 기반의 핸들러 메소드를 처리합니다.
        - HTTP 요청을 핸들러 메소드와 매핑하고, 메소드를 실행하여 적절한 응답을 생성합니다.
      (2) HttpRequestHandlerAdapter
        - HttpRequestHandler 인터페이스를 구현한 핸들러를 처리합니다.
        - HttpRequestHandler는 서블릿과 비슷한 형태로 HTTP 요청을 처리하는 인터페이스입니다.
      (3) SimpleControllerHandlerAdapter
        - Controller 인터페이스를 구현한 핸들러를 처리합니다.
        - 간단한 컨트롤러 인터페이스를 통해 핸들러를 처리합니다.
      (4) SimpleServletHandlerAdapter
        - Servlet 인터페이스를 구현한 핸들러를 처리합니다.
        - 기존의 서블릿을 Spring의 핸들러로 사용할 수 있도록 지원합니다.
      (5) RequestMappingHandlerAdapter
       - @RequestMapping 어노테이션을 사용한 핸들러 메소드를 처리합니다.
       - HandlerMethod를 사용하여 핸들러를 처리하며, 메소드의 인자 바인딩 및 리턴 값을 처리합니다.
      (6) SimpleUrlHandlerAdapter
       - UrlHandler 인터페이스를 구현한 핸들러를 처리합니다.
       - 특정 URL 패턴에 대한 핸들러를 지원합니다

     

    4) HandlerAdapter - RequestMapping 처리:
      - DispatcherServlet은 찾아낸 핸들러를 실행하기 위해 HandlerAdapter에게 요청을 전달합니다.
      - RequestMappingHandlerAdapter는 HandlerMethod 객체를 생성하고, 해당 메서드에 전달될 매개변수들을 준비합니다.
       * HandlerMethod 는 관련된 정보를 캡슐화하고, 매개변수 바인딩 및 리턴 값 처리 등을 수행.

    5) HandlerMethodArgumentResolver - 매개변수 해석:
      - HandlerMethodArgumentResolver는 컨트롤러 메서드의 매개변수를 해석합니다.
      - @RequestBody 어노테이션을 가진 매개변수가 있다면, 요청 본문(body)의 데이터를 해당 객체로 변환합니다.
      - 이
    때 사용자가 설정한 HandlerMethodArgumentResolver 중에서 RequestBodyArgumentResolver가 실행되어 @RequestBody 어노테이션이 적용된 매개변수를 처리합니다.
      - M
    essageConverter 가 사용되는데 JSON 형식의 데이터를 전송한 경우


      * HandlerMethodArgumentReslover 의 상속 구조이고, 

     * RequestResponseBodyMethodProcessor 의 역할
      -
    @RequestBody로 주석이 달린 메서드 인수를 확인하고 HttpMessageConverter를 사용하여 요청.
      - 응답의 본문을 읽고 쓰는 방식으로 @ResponseBody로 주석이 달린 메서드의 반환 값을 처리합니다.
      - @RequestBody 메서드 인수는 유효성 검사 어노테이션이 달린 경우에도 유효성을 검사합니다.
      - 유효성 검사에 실패하는 경우 MethodArgumentNotValidException이 발생시키고 DefaultHandlerExceptionResolver가 구성된 경우 HTTP 400 응답 상태 코드가 발생합니다.

     

    6) Controller Method Execution:
      - DispatcherServlet은 최종적으로 컨트롤러 메서드를 실행하고, 매개변수에 전달된 값들을 사용하여 비즈니스 로직을 처리합니다.

     

    7) HandlerMethodReturnValueHandler - 반환값 처리:
      - 컨트롤러 메서드의 실행 결과를 클라이언트에 응답으로 보내기 위해 HandlerMethodReturnValueHandler가 호출됩니다.
      - @ResponseBody 어노테이션이 적용되어 있다면, 해당 값은 응답의 본문으로 변환되며 이 때 MessageConverter 가 사용됩니다.
      -
    사용자가 설정한 HandlerMethodReturnValueHandler 중에서 ResponseBodyReturnValueHandler가 실행되어 @ResponseBody 어노테이션이 적용된 반환값을 처리합니다.

     

    8) 응답 전송:
     -
    DispatcherServlet은 최종적으로 응답을 생성하고 클라이언트에게 전송합니다.

     

    4. @RequestBody 의 @NoarguConstructor와 @Getter

    * ObjectMapper (https://jenkov.com/tutorials/java-json/jackson-objectmapper.html#how-jackson-objectmapper-matches-json-fields-to-java-fields)

    1. Jackson 은 JSON 필드의 이름을 Java 객체의 getter 및 setter 메소드와 일치시켜 JSON 객체의 필드를 Java 객체의 필드에 매핑한다.
    2. Jacson 은 getter 및 setter 메서드의 이름의 “get”, “set” 부분을 제거한다.
    3. 나머지 이름의 첫 번째 문자를 소문자로 변환한다.

     

     1) ReqeustBody는 HandlerMethodArgumentResolver 단계에서 Json 데이터를 객체로 변환하기 위해 MessageConverter 가 사용된다.
      - RequestResponseBodyMethodProcessor 의 readWithMessageConvters 를 실행

     - AbstractMessageConverterMethodArgumentResolver 의 readWithMessageConverters 를 실행
    : readWithMessageConverters 에서 주어진 HttpInputMessage 로부터 각 파라미터를 MessageConverter 를 통해 읽습니다.


     2)
    MessageConvter 의 사용에서 JSON 의 요청으로 MappingJackson2HttpMessagConvter 를 선택되서 사용하게 되는데,여기서 ObjectMapper 의 readValue() 가 호출된다.
      - MappingJackson2HttpMessageConveter 가 선택

      - AbstractJackson2HttpMessageConverter 의 read() 를 통해 JavaType 을 읽고, objectMapper 를 이용

      - Jackson 라이브러리의 objectMapper 의 readValue 가 호출되고 그 안의 _readMapAndClose 가 호출된다… 흘러 흘러….
      - Jackson 라이브러리의 BeanDeserializer 가 호출되는데, 이는 Jackson 의 objectMapper 가 JSON 을 Java 객체로 변환할 때 사용된다.
      -
    StdValueInstantiator 클래스의 createUsingDefault 까지 오게 되는데.. createUsingDefault() 메서드는 객체 인스턴스를 생성하는 역할을 하는데 이 때, 기본 생성자(default constructor) 를 사용하여 인스터를 생성한다.
    여기서 주목할 점은, Jackson 이 JSON 데이터를 Java 객체로 변환할 때, 해당 객체의 클래스에 기본 생성자가 반드시 필요하다는 것이다.!!!

     

     3) 이후에 기본생성자가 존재할 때 Reflection 을 활용하여 객체를 생성하게 된다.
      - _defaultCreate.call() 에서 newInstance의 리플렉션을 활용하여 객체를 생성하게 한다.



    5. 내가 개발하고 있는 서비스는 기본생성자가 없는데 왜 될까?
    @JsonCreator 란?
      -
    JackSON 라이브러리에서 사용되는 어노테이션인데, JSON 데이터를 Java 객체로 역직렬화할 때 사용되는 생성자나 정적 팩토리 메서드에 붙여서 사용한다.
      -
    JackSON 에게 어떤 생성자 또는 메서드를 사용하여 객체를 생성해야 하는지 알려주는 역할을 한다.
      -
    ObjectMapper 내부에는 property 와 생성자가 위임된 경우에는 그 정보를 이용해서 직렬화/역직렬화를 할 수 있는데, 여기서의 위임 오너테이션(@JsonProperty, @JsonAutoDetect, @JsonCreator) 중 하나이다. → 어노테이션이 작성되면 생성자와 property 값이 위임되서 기본생성자 없이도 정상적으로 작동한다.
      -
    만약 생성자 인자가 하나인 결우 @JsonCreator 를 추가해주는 기능이 동작하지 않아 직접 생성자에 붙여줘야한다.
      -
    기본 생성자를 통한 역직렬화를 하지 않고 지정해준 생성자를 가지고 역직렬화를 진행하겠다고 알려주는 역할도 있다.

    ** 그건 바로 JacksonAutoConfiguration!!
     - 스프링 부트에서 Jackson 라이브러리의 자동 구성을 위한 클래스로, 스프링 부트에서 자동으로 ObjectMapper 를 설정하고 Bean 으로 등록해준다.
     -
    그 중 ParameterNamesModuleConfiguration 이 있는데, Jackson 모듈 중 하나로, 생성자의 매개변수 일므을 사용하여 객체를 직렬화 및 역직렬화하는데 도움을 준다.

    - ParameterNamesModule를 생성할 떄 인자로 받는 Mode Enum 은 JsonCreator.Mode 이다.


    - 그렇다면 Getter 와 Setter 는?
      : 결론은 ObjectMapper가 바인딩하는데 기본 생성자와 Getter 와 Setter 중 둘 중 하나만 있으면 되며, 일반적으로 Getter 를 추가하여 Object를 받고 이후에 값을 꺼내 사용하기 편리하게 한다.

     

     

    6. 우리의 Test 코드 작성은 Mockito 를 사용하는데 Response 객체에는 기본생성자가 필요했고, 꼭, @NoArgsConstructor(access = AccessLevel.PRIVATE) 을 명시해주었다.
    늘 실제 서비스 운영에 필요한 코드는 아니지만 테스트코드 때문에 들어가는 코드라 찜찜했었는데, 이번에 내용을 알게되었고, 수정할 수 있었다.
     >> 바로 Mock 테스트 시에 사용하려는 ObjectMapper 의 설정 문제였다.

    우리의 테스트 코드에 사용되는 ObjectMapper 코드는


    였고, 기본 생성자가 없다면

    Cannot construct instance of `com.midasit.rpm.account.domain.order.license.dto.LicenseCmsUsageResponse$Results` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (StringReader); line: 1, column: 2] com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.midasit.rpm.account.domain.order.license.dto.LicenseCmsUsageResponse$Results` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (StringReader); line: 1, column: 2] ..... 생략

    와 같은 에러를 볼 수 있었다.

    그래서 해결책은 ObjectMapper 설정에 ParameterNamesModule 설정을 함께 해주어 ObjectMapper 를 만드는 것이다.

    이러면 위의 설명대로 @NoArgsConstructor(access = AccessLevel.PRIVATE) 를 명시하지않고도 테스트가 성공하는 것을 볼 수 있었다.

     

    후후.

    반응형

    '웹 개발' 카테고리의 다른 글

    Pageable 을 파헤치자.  (39) 2024.04.29
    자바 HashTable 과 HashMap  (0) 2024.03.28
    User-Agent 정보 가져오기  (0) 2024.03.20
    정적 코드 분석 도구 Sonarqube 도입 제안  (2) 2024.02.24
    Flyway  (0) 2022.12.15
Designed by Tistory.