[왜?] dto에 @getter 어노테이션이 없으면 안 돼?[트러블슈팅]

2024. 1. 3. 00:46·왜?
반응형

1. 에러 발생

마이페이지를 위해 Member를 조회한 뒤, dto를 통해 반환하는 API입니다.

406 HttpMediaTypeNotAcceptableException 에러가 발생했습니다.

 

Controller

    @GetMapping(value = "/user")
    public ResponseEntity<MemberInfoResponse> getUserInfo(@AuthenticationPrincipal MemberPrincipal memberPrincipal) {
        Long id = memberPrincipal.getMember().getId();
        MemberInfoResponse memberInfoResponse = memberService.getMemberInfo(id);
        return ResponseEntity.ok(memberInfoResponse);
    }

 

Service

    public MemberInfoResponse getMemberInfo(Long id) {
        Member member = memberQueryRepository.findMemberById(id).orElseThrow(() -> new MemberNotFoundException());
        return MemberInfoResponse.of(member);
    }

 

Dto

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberInfoResponse {
    private String email;
    private String username;
    private String oauthId;
    private OAuthProvider oAuthProvider;
    private boolean isBan;
    private AccountType accountType;

    public static MemberInfoResponse of(Member member) {
        return MemberInfoResponse.builder()
                .email(member.getEmail())
                .username(member.getUsername())
                .oauthId(member.getOauthId())
                .oAuthProvider(member.getOAuthProvider())
                .isBan(member.isBan())
                .accountType(member.getAccountType())
                .build();
    }
}

 

Exception

org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

받아들일 수 있는 표현을 못 찾아..? 이게 무슨 소리야!

 

2. HttpMediaTypeNotAcceptableException

Exception thrown when the request handler cannot generate a response that is acceptable by the client.

공식 문서를 보니 클라이언트가 받아들일 수 있는 응답을 생성할 수 없을 때 발생하는 예외라고 한다.

 

그러면 언제 어디에서 발생하는가?

구글링을 한 결과 AbstractMessageConverterMethodProcessor라는 클래스의 writeWithMessageConverters 함수에서 발생하는 예외라는 결론이 나왔습니다.

요청의 Accept 헤더에서 나타낸 조건이 메시지 컨버터에서 충족되지 않을 때 발생한다고 합니다.

 

AbstractMessageConverterMethodProcessor 클래스는 무슨 역할을 해?

Extends AbstractMessageConverterMethodArgumentResolver with the ability to handle method return values by writing to the response with HttpMessageConverters
이를 해석해보면 다음과 같습니다.

AbstractMessageConverterMethodArgumentResolver를 상속한 것으로, HttpMessageConverters를 사용하여 메서드의 반환 값을 응답으로 작성할 수 있는 기능을 추가했습니다.

 

3. HttpMessageConverts

공식문서를 보니 AbstractMessageConverterMethodProcessor 클래스에서 HttpMessageConverts를 사용해 반환 값을 응답으로 작성해야 하는데 하지 못 한다는 결론이 나왔습니다.

 

HttpMessageConverts란?

read, write, canread, canwrite등 다양한 메서드를 지원하는 클래스이며 StringHttpMessageConverter, MappingJackson2HttpMessageConventer 등 다양한 구현 클래스를 지원합니다.

이때 Read, Write 함수를 주의깊게 봐야 합니다.

Read

  • 주어진 입력 메시지에서 주어진 타입의 객체를 읽어 들여 반환합니다.
  • @RequestBody 어노테이션을 지정할 경우, Request Header의 타입에 맞게 Body 정보를 변환합니다.

write

  • 주어진 객체를 주어진 출력 메시지에 씁니다.
  • @ResponseBody 어노테이션을 지정할 경우, 결과를 View가 아닌 Content Type에 맞게 반환합니다.
    • 객체일 경우 Json으로 반환합니다.

 

@RestController

필자는 Controller가 아닌 RestController 어노테이션을 사용하고 있었다.

RestController는 Controller과 달리 Restful 한 웹 서비스를 제공하기 위한 Controller입니다.

@ResponseBody를 포함하고 있어 모든 메서드의 반환 값이 HTTP 응답의 본문으로 변환되어 전송됩니다.

 

그러면 맨 위에 있는 Controller의 반환 값은 Json임을 알 수 있습니다.

return ResponseEntity.ok(memberInfoResponse);

HttpMessageConverts를 사용해 반환 값을 응답으로 작성해야 하는데 하지 못 한다는 결론이 나왔습니다.

그러면 @ResponseBody에 문제가 있는 거네?

 

4. Request & Response Body

1. @RequestBody

You can use the @RequestBody annotation to have the request body read and deserialized into an Object through an HttpMessageConverter The following example uses a @RequestBody argument:
이를 해석해 보면 다음과 같습니다.

RequestBody는 HTTP 요청의 본문을 읽고 역직렬화하여 HttpMessageConverter을 통해 객체로 변환할 수 있습니다. 

이때 HttpMessageConverter이 상황에 맞는 구현체를 사용하게 해 줍니다. 필자의 경우 application/json이므로 MappingJackson2HttpMessageConverter이다. MappingJackson2HttpMessageConverter은 ObjectMapper에 의해 Json 문자열을 객체로 역직렬화시켜줍니다.

*역직렬화 -> json, Byte등의 형식을 객체로 만드는 과정

2. @ResponseBody

You can use the @ResponseBody annotation on a method to have the return serialized to the response body through an HttpMessageWriter.
이를 해석해 보면 다음과 같습니다.

ResponseBody는 HttpMessageWriter을 통해 반환 값을 응답 본문으로 직렬화하여 전송할 수 있습니다.

이때 @RequestBody와 마찬가지로 MappingJackson2HttpMessageConverter 구현체를 사용해 객체를 Json으로 직렬화시켜줍니다.

*직렬화 -> 객체를 json, Byte등 통신하기 쉬운 형태로 만드는 과

5. ObjectMapper

dto의 모든 멤버 변수가 private인 것으로 가정하고 설명하겠습니다.

ObjectMapper는 기본적으로 필드를 대상으로 하는데 그중에 public 필드를 대상으로 합니다.

하지만 저희의 dto는 private이므로 에러가 생깁니다.

 

그러면 public으로 하면 되는 거 아닌가요?
음... 괜찮은 생각일 수도 있지만 public으로 하면 안 되는 이유는 다음 글에서 설명드리겠습니다.

 

@Getter

Getter 어노테이션을 사용하면 모든 멤버 변수에 대해 get~~ 메서드가 생성됩니다. 

이때 Jackson은 getter 메서드 중 get을 제외한 이름의 첫 문자를 소문자로 치환하여 필드명을 유추합니다.

쉽게 말하자면 getter을 이용해 private 변수를 public처럼 인식할 수 있게 한다는 것입니다.

 

6. 추가적인 정보

ObjectMapper은 역직렬화 과정에서 객체를 생성할 때 기본생성자를 사용해 객체를 만듭니다.
객체를 생성한 뒤, getter을 이용해 정보들을 동적으로 추가합니다. (Reflection)

 

그러므로 기본 생성자를 생성해 주는 @ NoArgsConstructor 어노테이션을 붙여야 합니다.

@JsonIgnore 등 다양한 방법이 있지만, 다른 글에서 더 자세히 설명하겠다.

 

참고 문서

HttpMediaTypeNotAcceptableException 공식 문서

HttpMessageConverter 공식 문서

RequestBody 공식 문서

ResponseBody 공식 문서

 

반응형
저작자표시 동일조건 (새창열림)
'왜?' 카테고리의 다른 글
  • [왜?] DTO <--> Entity 어느 방향으로 하는 것이 효율적일까? (feat. mapper)
  • [왜?] 파일 전송에 presigned url이라는 방법을 사용했어?
  • [왜?] MVC 패턴을 선택했을까? feat.우테코
  • [왜?] RefreshToken을 Redis에 저장할까?
코딩하는_대학생
코딩하는_대학생
Java Developer, Open Source Enthusiast, Proud Son
  • 코딩하는_대학생
    코딩하는 대학생에서 개발자까지
    코딩하는_대학생
  • 전체
    오늘
    어제
    • 분류 전체보기 (217)
      • 코딩하는 대학생의 책 추천 (8)
        • 클린코드 (5)
        • 헤드퍼스트 디자인패턴 (3)
      • Backend (7)
        • Spring (14)
        • AWS (3)
        • 회고 (4)
        • Redis (5)
        • 다양한 시각에서 바라본 백엔드 (3)
      • Python (35)
        • 개념 및 정리 (15)
        • 백준 문제풀이 (20)
      • JAVA (17)
        • 개념 및 정리 (14)
        • 백준 문제풀이 (2)
      • 왜? (7)
      • C언어 (42)
        • 개념 및 정리 (9)
        • 백준 문제풀이 (32)
      • 개인 공부 (27)
        • 대학 수학 (5)
        • 대학 영어 (10)
        • 시계열데이터 처리 및 분석 (5)
        • 컴퓨터 네트워크 (6)
        • 운영체제 (1)
      • 솔직 리뷰 (23)
        • 꿀팁 (6)
        • IT기기 (1)
        • 국내 여행 (7)
        • 맛집 (2)
        • 알바 리뷰 (2)
      • 대외활동 (17)
        • 체리피우미 3기 (4)
        • 꿀잠이들 6기 (13)
      • 음식 평가 (1)
      • 일상 & 근황 (2)
  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
코딩하는_대학생
[왜?] dto에 @getter 어노테이션이 없으면 안 돼?[트러블슈팅]
상단으로

티스토리툴바