π― λͺ©ν : Spring MVCμμμ μμΈ μ²λ¦¬ νμ΅
@ExceptionHandler@ControllerAdvice & @RestControllerAdvice@ExceptionHandler μ΄λ
Έν
μ΄μ
μ΄ μλ€.μμ μ½λ
@RestController
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
private final MemberMapper mapper;
public MemberController(MemberService memberService, MemberMapper mapper) {
this.memberService = memberService;
this.mapper = mapper;
}
@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 handlerException(MethodArgumentNotValidException e) {
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<ErrorResponse.FieldError> errors =
fieldErrors.stream()
.map(error -> ErrorResponse.FieldError.builder()
.field(error.getField())
.rejectedValue(error.getRejectedValue())
.reason(error.getDefaultMessage())
.build())
.collect(Collectors.toList());
return new ResponseEntity<>(ErrorResponse.of(errors), HttpStatus.BAD_REQUEST);
}
}
MethodArgumentNotValidExceptionμ΄ λ°μνλ€.@ExceptionHandlerλ μν΄μλ ν΄λμ€ λ΄λΆμ μμΈμ λν΄μλ§ μ²λ¦¬ν μ μλ Local λ²μλ₯Ό κ°μ§κ³ μλ€.@ExceptionHandler Local μμΈ μ²λ¦¬μ λ¨μ μ 극볡νκΈ° μν΄ Spring μμλ μ΄ν리μΌμ΄μ
μ μμΌλ‘ @ExceptionHandlerλ₯Ό μ¬μ©ν μ μκ² μ§μν΄μ€λ€.@ControllerAdvice μ @RestControllerAdviceκ° μ μ μμΈ μ²λ¦¬λ₯Ό μ§μν΄μ£Όλ μ΄λ
Έν
μ΄μ
μ΄λ€.@ControllerAdviceλ μμΈ μ²λ¦¬ν μλ¬ View λ±μ ν΅νμ¬ μ²λ¦¬ ν μ μκ² ν΄μ€λ€.@RestControllerAdviceλ REST APIμ λν μμΈ μ²λ¦¬λ₯Ό νΈλ¦¬νκ² μ§μν΄μ£Όλ©°, @ResponseBodyλ₯Ό λ΄μ₯νκ³ μμ΄ μλ΅ λ°μ΄ν° ν¬λ§·μ μ©μ΄νλ€.@RestControllerAdvice = @ControllerAdvice + @ResponseBody@ControllerAdviceμ μμΈ μ²λ¦¬ λ²μλ Dispatcher Servlet λ΄λΆμμ μ΄λ£¨μ΄ μ§λ€.μμ μ½λ
// MemberController.java
@RestController
@RequestMapping("/members")
public class MemberController {
private final MemberService memberService;
private final MemberMapper mapper;
public MemberController(MemberService memberService, MemberMapper mapper) {
this.memberService = memberService;
this.mapper = mapper;
}
@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);
}
}
// GlobalExceptionAdvice.java
@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException e
) {
ErrorResponse response = ErrorResponse.of(e.getBindingResult());
return response;
}
}
// ErrorResponse.java
@Getter
public class ErrorResponse {
private List<FieldError> fieldErrors;
private List<ConstraintViolationError> violationErrors;
private ErrorResponse(final List<FieldError> fieldErrors,
final List<ConstraintViolationError> violationErrors) {
this.fieldErrors = fieldErrors;
this.violationErrors = violationErrors;
}
public static ErrorResponse of(BindingResult bindingResult) {
return new ErrorResponse(FieldError.of(bindingResult), null);
}
@Getter
@Builder
public static class FieldError {
private String field;
private Object rejectedValue;
private String reason;
private FieldError(String field, Object rejectedValue, String reason) {
this.field = field;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<FieldError> of(BindingResult bindingResult) {
final List<org.springframework.validation.FieldError> fieldErrors =
bindingResult.getFieldErrors();
return fieldErrors.stream()
.map(error -> ErrorResponse.FieldError.builder()
.field(error.getField())
.rejectedValue(error.getRejectedValue())
.reason(error.getDefaultMessage())
.build())
.collect(Collectors.toList());
}
}
}
GlobalExceptionAdvice.javaλ₯Ό μ μνμ¬ λͺ¨λ Controller μμ λ°μνλ μμΈλ₯Ό 곡ν΅μ μΌλ‘ μ²λ¦¬ν μ μλλ‘ νμλ€.GlobalExceptionAdvice.javaμμ handleMethodArgumentNotValidExceptionλ©μλμ ErrorResponseλ₯Ό λ°ννκ² νμ¬ μ½λλ₯Ό μ‘°κΈλ κ°κ²°ν΄μ‘λ€.@RestControllerAdviceμμ μ§μν΄μ£Όλ @ResponseBodyκΈ°λ₯μΌλ‘ νΉλ³ν λ§΅ν μμ΄ JSON νμ
μ λ°μ΄ν°λ‘ μλ΅μ ν μ μκ² νμλ€.@ResponseStatus(HttpStatus.BAD_REQUEST)λ₯Ό μ§μ ν΄ μ€μΌλ‘μ HTTP μνμ½λκΉμ§ μλ΅ λ©μΈμ§μ λ³΄λ΄ μ€ μ μλ€.GlobalExceptionAdvice.javaμμ μλ¬ λ°μ΄ν°λ₯Ό κ°μ§κ³ νμν λ°μ΄ν°λ₯Ό μΆμΆνκ³ κ°κ³΅νλ μν μ ErrorResponse.javaμ μμ νμμΌλ©° μμ±μλ₯Ό privateμ²λ¦¬ ν¨μΌλ‘μ of λ©μλλ₯Ό ν΅ν΄μλ§ μλ΅ κ°μ²΄λ₯Ό μμ±ν μ μκ² νμλ€.ErrorResponse.java λ΄λΆμμλ νλΌλ―Έν°λ‘ μ λ¬ λ°μ BindingResult λ°μ΄ν°μμ νμν λ°μ΄ν°λ§ μΆμΆνκ³ κ°κ³΅νλ μν μ λ΄λΆ static ν΄λμ€ FieldErrorμ μμνμ¬ λͺ¨λ ν΄λμ€ λ€μ μν κ³Ό μ±
μμ λͺ
νν ν΄ μ£Όμλ€.Reference
https://www.digitalocean.com/community/tutorials/spring-mvc-exception-handling-controlleradvice-exceptionhandler-handlerexceptionresolver
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
https://jaehun2841.github.io/2018/08/30/2018-08-25-spring-mvc-handle-exception/#HandlerExceptionResolver%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B2%98%EB%A6%AC