π― λͺ©ν : 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