❗️여기는 예외 처리를 Controller에서 해본 것이다. ❗️
이렇게 유효성 검증과 같이 잘못된 요청이 들어가면 에러가 발생합니다. 이 때, 이 에러가 왜 발생했는지 위의 사진에서는 알 수 없다. 이러한 에러메세지를 조금 더 구체적으로 알 수 있도록 예외 처리를 하려고 한다.
예외 처리를 하는 방법은 여러가지가 있다.
- 메서드 내에서 예외 사항을 예측하여 처리하는 try-catch문 이용
- 요구사항에 의한 예외 처리
- Spring Security에서 인터셉터로 잡아서 UnauthorizedException 같은 예외 처리
하지만 이런식으로 예외 처리를 여러 개 만들면 만들 수록 유지 보수가 어렵다.
이러한 부분을 개선하기 위해서 어노테이션을 이용한 예외처리를 하려고 한다.
✅ @ExceptionHandler 사용
- 메세지를 출력해서 어느 곳에 문제가 있는지 확인 가능
- 하지만 아래의 사진에서 확인할 수 있듯이 필요없이 광범위하게 정보를 받는다.
@RestController
@RequestMapping("/members")
@Validated
public class MemberController {
...
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
Member member = mapper.memberPostDtoToMember(memberDto);
Member response = memberService.createMember(member);
return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),HttpStatus.CREATED);
}
...
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException exception) {
final List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
}
}
✔️ FieldError
- 필드에 오류가 있는 에러
- 타입이 맞지 않을 때 스프링이 생성
- 개발자의 검증을 통해 오류가 있다면 직접 생성해서 BindingResult 의 addError() 메서드를 통해 넣을수 있다.
- 생성자의 매개 변수는 2가지.
- objectName: 오류가 발생한 객체 이름
- field: 오류 필드
- rejectedValue: 사용자가 입력한 값(거절된 값)
- bindingFailure: 타입 오류 같은 바인딩 실패인지, 검증 실패인지 구분 값
- codes: 메시지 코드
- arguments: 메시지에서 사용하는 인자
- defaultMessage: 기본 오류 메시지
public FieldError(String objectName, String field, String defaultMessage);
public FieldError(String objectName, String field,
@Nullable Object rejectedValue, boolean bindingFailure,
@Nullable String[] codes, @Nullable Object[] arguments,
@Nullable String defaultMessage);
✔️ BindingResult
- 검증 오류를 보관하는 객체
- 검증 오류를 보관하는 세 가지 방법
- @ModelAttribute 객체에 타입 오류등으로 바인딩이 실패하는 경우
(예 : 정수형 필드에 문자를 넣는 경우) - 개발자가 직접 넣어주는 경우
- Validator를 사용하는 경우 << 나의 케이스!
- @ModelAttribute 객체에 타입 오류등으로 바인딩이 실패하는 경우
참고한 사이트 : https://velog.io/@imcool2551/Spring-%EA%B2%80%EC%A6%9D1-BindingResult-MessageCodesResolver
✅ MethodArgumentNotValidException 와 ConstraintViolationException
- MethodArgumentNotValidException
- RequestBody로 들어오는 파라미터(여기서는 DTO)의 유효성을 검사
- 통과하지 못하면 발생하는 예외
- 해당 예외의 이유를 List<FieldError>로 받아 응답하였다.
- ConstraintViolationException
- URI의 유효성을 검사 (❗️HTTP Method 아님❗️)
- 예 : "localhost:8080/members/{member_id}"
- 마지막에 들어가는 member_id의 유효성을 검사
- List<ConstraintViolationError>으로 받아 응답할 예정이다.
- 헷갈리면 아래 코드와 사진 예시를 참조하면 된다.
✅ ErrorResponse 생성
- 위에 예시처럼 필요 없는 정보까지는 받을 필요가 없다.
- 그래서 ErrorResponse 클래스를 만들어 필요한 메세지만 받으려고 한다.
- 이번에는 MethodArgumentNotValidException가 아닌 ConstraintViolationException를 받아보려 한다.
- ErrorResponse를 만들어 주고, Controller에서 필요한 정보만 받아(stream 사용) List로 반환하여 ResponseEntity로 전달한다.
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.validation.BindingResult;
import javax.validation.ConstraintViolation;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Getter
@AllArgsConstructor
public class ErrorResponse {
private List<ConstraintViolationError> violationErrors;
@Getter
@AllArgsConstructor
public static class ConstraintViolationError {
private String field;
private Object rejectedValue;
private String reason;
}
}
@RestController
@RequestMapping("/members")
@Validated
public class MemberController {
...
@ExceptionHandler
public ResponseEntity handleConstraintException(ConstraintViolationException exception) {
final Set<ConstraintViolation<?>> violationExceptions = exception.getConstraintViolations();
List<ErrorResponse.ConstraintViolationError> errors = violationExceptions.stream()
.map(constraintViolation -> new ErrorResponse.ConstraintViolationError(
constraintViolation.getPropertyPath().toString(),
constraintViolation.getInvalidValue().toString(),
constraintViolation.getMessage()
)).collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
}
}
🚨 이렇게 컨트롤러에서 처리하면 발생하는 문제
- 각각의 Controller 클래스에서 @ExceptionHandler 애너테이션을 사용하여 Request Body에 대한 유효성 검증 실패에 대한 에러 처리를 해야됨 --> 각 Controller 클래스마다 코드 중복 발생
- Controller에서 처리해야 되는 예외(Exception)가 하나만 있는것이 아니기 때문에 하나의 Controller 클래스 내에서 @ExceptionHandler를 추가한 에러 처리 핸들러 메서드가 늘어나게 된다.
- 따라서 다음 포스팅에서는 예외 처리를 한 번에 처리할 예정이다.
'Spring' 카테고리의 다른 글
[Spring] 예외 처리 - 사용자 정의 (0) | 2022.10.27 |
---|---|
[Spring] 예외처리 - 공통화(@RestControllerAdvice) (0) | 2022.10.27 |
[Spring] MapStruct 사용하여 Mapper 구현하기 (0) | 2022.10.20 |
[Spring Framework] ResponseEntity (0) | 2022.08.25 |
[Spring] Spring MVC Controller 어노테이션 (0) | 2022.08.24 |
댓글