-
@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 어노테이션이 적용된 매개변수를 처리합니다.
- MessageConverter 가 사용되는데 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)
- Jackson 은 JSON 필드의 이름을 Java 객체의 getter 및 setter 메소드와 일치시켜 JSON 객체의 필드를 Java 객체의 필드에 매핑한다.
- Jacson 은 getter 및 setter 메서드의 이름의 “get”, “set” 부분을 제거한다.
- 나머지 이름의 첫 번째 문자를 소문자로 변환한다.
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