1. 개요
스프링으로 프로젝트를 진행하다가 만난 상황입니다. 회원가입 시 이메일이 중복되었는지 확인하기 위해 다음과 같이 코드를 작성했습니다.
$.ajax({
type: "post",
url: "/register/validate/email",
data: {
email: email
},
success: function (result){
console.log(result);
},
error: function (result){
console.log(result);
$('.email__result').html(response.message);
isValidatedEmail = false;
}
});
ajax를 통해 유효성 검사하는 코드입니다.
@PostMapping("/register/validate/email")
public ResponseEntity<Object> validateEmail(@RequestParam String email){
memberService.validatePresentEmail(email);
BaseResponse response = new BaseResponse(HttpStatus.OK, "사용 가능한 이메일입니다.", true);
return ResponseEntity
.status(HttpStatus.OK)
.body(response);
}
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {
public void validatePresentEmail(String email) {
Optional<Member> findMember = memberRepository.findByEmail(email);
if(findMember.isPresent()){
throw new MemberPresentException("이미 가입된 이메일입니다.");
}
}
}
컨트롤러에서는 email이 사용가능한 지 확인한 후 응답합니다.
@ExceptionHandler(PresentException.class)
public ResponseEntity<Object> handlePresentException(Exception e){
log.info("예외 발생 - PresentException : {}", e.getMessage());
BaseResponse response = new BaseResponse(HttpStatus.CONFLICT, e.getMessage(), false);
return ResponseEntity
.status(HttpStatus.CONFLICT)
.body(response);
}
ControllerAdvice 예외처리입니다. 코드만 보면 문제가 없었는데, Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation]
예외가 발생합니다.
2. 원인 분석
그런데 이상했습니다. HttpMediaTypeNotAcceptableException은 클라이언트의 Accept 헤더와 서버의 응답 형식이 일치하지 않을 때 발생합니다. 하지만 저는 컨트롤러의 클래스 레벨에 @RestController 어노테이션을 삽입했습니다. 따라서 모든 메소드는 @ResponseBody 어노테이션이 붙을 것입니다.
@ResponseBody가 붙은 메소드는 뷰 템플릿을 응답하는 것이 아니라 json으로 응답이 갑니다. 또 응답을 받는 ajax 코드에서 header의 accept 설정을 하지 않으면 기본 값으로 */*가 설정되는데 이는 모든 미디어 타입을 허용한다는 뜻입니다.
디버깅으로 찾아봐도, 도저히 모르겠어서 인터넷 검색을 해봤는데 다음과 같은 결과를 얻었습니다.
"Could not find acceptable representation" using spring-boot-starter-web
https://stackoverflow.com/questions/28466207/could-not-find-acceptable-representation-using-spring-boot-starter-web
저는 컨트롤러에서 ResponseEntity로 응답을 내릴 때 body 메소드에 BaseResponse란 객체를 만들어서, 생성자로 값을 초기화한 이후 해당 객체를 담았습니다. HttpMediaTypeNotAcceptableException이 발생한 이유는 이 객체에 getter를 만들지 않아 생긴 예외였습니다.
@RequiredArgsConstructor
public class BaseResponse {
private final HttpStatus status;
private final String message;
private final boolean isSuccess;
}
하... 진짜 이럴 때 정말 짜증나면서도 예외를 해결해서 기쁘달까요. 복잡미묘합니다. 위의 코드에서 getter를 추가하면 성공적으로 로직이 작동합니다. 아무튼 저 위의 스택 오버플로우 링크를 가보시면 최고로 많이 좋아요를 받은 답변에 이런 내용이 있었습니다.
... and in your example will result in [].
대충 결과가 []를 받을 것이라는 뜻 같습니다. 그냥 블로그 포스팅을 끝내면 심심하니까 디버깅 모드로 한번 확인해보겠습니다.
보시면 141번 라인에 returnValue에서 200 OK는 정상적으로 찍힙니다. 이는 제가 컨트롤러에서 내려준 응답과 같습니다. 그리고 다음에 BaseResponse 객체의 값이 []인 것을 확인할 수 있었습니다.
3. 결론
ResponseEntity body 메소드의 파라미터로 객체를 넣은 후 반환하면, 해당 반환 객체에 getter가 있는지 확인합시다.