이번 글에서는 @Valid
에 의해 발생되는 MethodArgumentNotValidException.class
에 속하는
'BindingResult' 에 대해 알아보고자 한다.
- 문제 상황, 찾아보게 된 계기 - MethodArgumentNotValidException 에 속하는 BindingResult 가 무엇인가? (이동)
- 학습 과정 - BindingResult 파고들기 (이동)
- 결론 - 예외처리 코드가 동작하는 원리에 대한 이해 (이동)
1. 문제상황 - MethodArgumentNotValidException 에 속하는 BindingResult 가 무엇인가?
Client 로부터의 입력 예외, business 로직 예외를 처리하기 위해 위 접은 글들을 참고하며 예외 처리 클래스를 작성하고 있었다. 정말 친절하게 설명을 잘 해주셔서 천천히 로직을 따라가고 있었는데, 갑자기 막히는 곳이 생겼다.
아래 코드에서 ExceptionResponse 는 공통 예외 응답을 위한 클래스, FieldException 은 예외가 발생한 필드, 값, 이유 를 포함하는 nested class 이다. (이해를 위해 모든 코드를 이미지로 올렸으니 create() 메소드만 잘 따라가면 된다.)
첫 번째 이미지에서 @Valid
는 MethodArgumentNotValidException
을 발생시키므로,MethodArgumentNotValidException e
를 파라미터로 받는 것 (line18) 까지는 이해가 되었다.
그런데 그 이후
e.getBindingResult()
로 BindingResult 를 가져와서BindingResult
에서FieldError
를 원소로 가지는 List 를 만들고- 그 List 를 이용해서
field
,RejectedValue
,DefaultMessage
를 FieldException 에 매핑하는데
'BindingResult' 가 도저히 뭔지 알 수가 없었다.
위 글들 뿐만 아니라 다른 글들에서도 많이 볼 수 있는 형태의 코드였기 때문에 조금 더 구글링을 해보니 BindingResult
라는 것이 어떤 필드를 포함하고 있고, 어떤 역할인지 나오기는 했지만, 정확히 어떤 흐름으로 저렇게 매핑이 될 수 있는지에 대한 포스팅은 찾을 수가 없어서 답답한 마음에 쓰게 되었다.
2. 학습 과정 - BindingResult 파고들기
위와 같이 중단점을 찍어서 디버깅모드로 살펴보았다. @Valid 에 걸리도록 예외를 낸 다음에 관찰해보니 일단 한 가지를 알 수 있었다.
- MethodArgumentNotValidException 안에는 여러 필드가 있고, 그 중 bindingResult 안을 살펴보면 error 와 관련된 여러 값들이 담겨있다.
저렇게 갖고 들어온 BindingResult 가 ExceptionResponse.create(), FieldException.create() 를 거쳐 아래와 같이 List 를 하나 만들게 되는데
BindingResult.getFieldErrors()
구현 코드를 살펴보니
BindingResult 내부의 errors 의 개수만큼 반복하여 해당 객체를 FieldError 라는 형태로 List 에 담아 반환하고 있었다.
결과적으로 BindingResult.getFieldErrors()
를 통해 FieldError 로 이루어진 List 를 반환 받고, 각 원소를 돌면서 개발자가 작성한 FieldException 이라는 클래스의 각 필드에 매핑하는 작업을 수행한 것이다.
3. 결론 - 예외처리 코드가 동작하는 원리에 대한 이해
요약하자면,
- @Valid 는 MethodArgumentNotValidException 을 발생시킨다.
- MethodArgumentNotValidException 에는 여러 값들이 있으며, 그 중 BindingResult 라는 객체에 예외에 대한 좀 더 자세하고 많은 정보들이 담겨있다.
- BindingResult 객체 안에서 error 와 관련된 필요한 값을 가져오기 위해
BindingResult.getFieldErrors()
메소드를 이용하여 BindingResult 가 갖고 있는 errors 를 FieldError 라는 객체 형태로 반환받고 - FieldError 객체에서 필요한 값들을 사용자(개발자) 가 정의한 예외 객체에 매핑하여 사용한다.
- Field : 객체에서 예외가 발생한 field
- RejectedValue : 어떤 값으로 인해 예외가 발생하였는지
- DefaultMessage : 해당 예외가 발생했을 때 제공할 message 는 무엇인지
결과적으로 'exceptions' 배열에서 볼 수 있듯 예외에 해당하는 field, value, reason 이 매핑되어 나오는 것을 확인할 수 있다.
댓글