HTTP API Exception
이전에 웹에서 웹페이지로 오류를 처리하는 것을 살펴보았다.
우리는 API json을 통해서도 Error를 처리할 수 있어야 한다.
그에 따라, API에서 오류를 처리하는 방법을 알아보려고 한다.
이를 살펴보기 위해 간단한 예제를 가져왔다.
@Getter
@AllArgsConstructor
public class Person {
@NotBlank
private String id;
@NotBlank
private String pw;
@NotBlank
private String pwConfirm;
}
- 값을 필히 입력해야하는 아이디가 존재한다.
- 값을 필히 입력해야하는 패스워드가 존재한다.
- 값을 필히 입력해야하는 패스워드 확인 값이 존재한다.
@RestController
public class Controller1 {
@RequestMapping("/check")
public String check(@Validated @RequestBody Person person, BindingResult bindingResult) throws BindException {
if (!person.getPw().equals(person.getPwConfirm())) {
bindingResult.reject("notmatchPW", null);
}
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
return "확인되었습니다!";
}
}
- 위의 검증들을 만족할뿐만 아니라, pw와 pwConfirm이 같아야만 한다.
이 검증들이 하나라도 실패할 경우 에러를 반환해주어야 하는데 우리는 이를 어떻게 처리할 수 있을까?
Spring에서 API 에러 처리를 위해 제공하는 것은 3가지이다.
- DefaultHandlerExceptionResolver
- ResponseStatusExceptionResolver
- ExceptionHandlerExceptionResolver
이 세 가지에 대해서 알아보고 이를 이용해 처리해보자.
DefaultHandlerExceptionResolver
이것은 스프링 내부에서 발생하는 스프링 예외를 해결한다.
우리가 파라미터를 바인딩하는 시점에 타입이 맞지 않아 발생하는 TypeMismatchException과 같은 예외를 해결해준다.
이를 직접 확인해보자.
@Getter
@AllArgsConstructor
public class Person {
@NotBlank
private String id;
@NotBlank
private String pw;
@NotBlank
private String pwConfirm;
private Integer test;
}
- 임의로 test라는 Integer 타입의 정수를 추가했다.
- 다음과 같이 BAD_REQUEST오류를 자동으로 발생해준다.
이와 같이 타입 미스매치가 발생하면 자동으로 Spring에서 해결해준다.
ExceptionHandlerExceptionResolver
(1)@ExceptionHandler
@ExceptionHandler를 처리한다.
@ExceptionHandler(BindException.class)
public ApiValidationExceptionEntity argumentException(BindException ex, Locale locale) {
List<ValidationEntity> ve = ex.getAllErrors().stream()
.map(error -> new ValidationEntity(error.getCode(), messageSource.getMessage(error, locale)))
.collect(Collectors.toList());
return new ApiValidationExceptionEntity(HttpStatus.BAD_REQUEST, ve);
}
- 다음과 같이 발생한 예외를 명시하고 해당 예외에 대한 메소드 처리를 진행해준다.
- 해당 예외의 자식 예외까지 모두 잡아준다.
@ExceptionHandler
public ApiValidationExceptionEntity argumentException(BindException ex, Locale locale) {
List<ValidationEntity> ve = ex.getAllErrors().stream()
.map(error -> new ValidationEntity(error.getCode(), messageSource.getMessage(error, locale)))
.collect(Collectors.toList());
return new ApiValidationExceptionEntity(HttpStatus.BAD_REQUEST, ve);
}
- 파라미터로 받는 예외가 실제로 핸들링하는 예외와 같다면 생략 가능하다.
(2)@ControllerAdvice
@ExceptionHandler들을 따로 모아놓아 이를 적용시켜줄 수 있다.
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {
- 스프링 공식 문서에 있는 @ControllerAdvice의 예제이다.
- 해당 애노테이션을 가지고 있는 곳에만 적용 가능하다.
- 해당 패키지 안에 있는 곳에만 적용 가능하다.
- 특정 클래스를 지정 가능하다.
- 생략한다면 모든 컨트롤러에 지정한다.
실제로 적용하면 다음과 같다.
@RestControllerAdvice
@AllArgsConstructor
public class ApiValidationException {
private MessageSource messageSource;
@ExceptionHandler
public ApiValidationExceptionEntity argumentException(BindException ex, Locale locale) {
List<ValidationEntity> ve = ex.getAllErrors().stream()
.map(error -> new ValidationEntity(error.getCode(), messageSource.getMessage(error, locale)))
.collect(Collectors.toList());
return new ApiValidationExceptionEntity(HttpStatus.BAD_REQUEST, ve);
}
}
- BindException에 대해 적용한다.
- FieldError, GlobalError를 캐치해 이를 메세지에 담아준다.
- 메세지는 errors.properties(메세지 소스)를 이용하였다.
해당 결과를 보면 다음과 같다.
이처럼 Spring에서 우리가 원하는 규약으로 메세지를 설정할 수 있다.
하지만, HTTP 상태 코드는 200 OK로 정상적으로 된 것처럼 표기되었다.
우리는 HTTP 상태 코드를 BAD_REQUEST로 변경해주어야 하는데 이를 해결해주는 것이 ResponseStatusExceptionResolver이다.
ResponseStatusExceptionResolver
HTTP 상태 코드를 직접 지정해줄 수 있다.
@ResponseStatus를 활용한다.
@RestControllerAdvice
@AllArgsConstructor
public class ApiValidationException {
private MessageSource messageSource;
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiValidationExceptionEntity argumentException(BindException ex, Locale locale) {
List<ValidationEntity> ve = ex.getAllErrors().stream()
.map(error -> new ValidationEntity(error.getCode(), messageSource.getMessage(error, locale)))
.collect(Collectors.toList());
return new ApiValidationExceptionEntity(HttpStatus.BAD_REQUEST, ve);
}
}
- @ResponseStatus를 이용하여 BAD_REQUEST를 적용해주었다.
결과를 확인해보면 다음과 같다.
- 400 Bad Request가 잘 적용되어있다.
이렇게 API를 통해 진행하는 경우 이와 같은 방식으로 오류를 처리할 수 있다.
이 메세지는 언제든지 커스텀하게 만들 수 있으므로 상황에 맞게 잘 처리하면 될 것 같다.
'스프링' 카테고리의 다른 글
[Spring] 파일 및 이미지 업로드 (4) | 2021.10.20 |
---|---|
[JPA] N+1 문제 (0) | 2021.10.19 |
[Spring] 오류 페이지 (0) | 2021.09.27 |
[Spring] 필터, 인터셉터 (0) | 2021.09.26 |
[Spring] 세션(Session) (0) | 2021.09.25 |