일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- JAVA 재귀함수
- git workflow
- 탐욕 알고리즘
- Spring MVC
- 코드스테이츠
- 함수형 인터페이스
- 리눅스 사용권한
- Spring
- N:N
- AOP
- Spring 예외처리
- 스키마 설계
- set-version
- ubuntu
- 스키마 디자인
- O(log n)
- git 설정
- ubuntu 패스워드
- root passwd
- mapstruct
- 자료구조
- file i/o
- Java
- RestControllerAdvice
- 배열 탐색
- http 응답코드
- ubuntu passwd
- char to int
- REST HTTP API
- custom exception
Archives
- Today
- Total
개발소설
Spring MVC 예외처리 본문
@ExceptionHandler를 이용한 예외 처리
@ExceptionHandler를 이용한 Controller 레벨에서의 예외 처리
@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);
}
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e) {
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
return new ResponseEntity<>(fieldErrors, HttpStatus.BAD_REQUEST);
}
- Controller 내의 @ExceptionHandler 적용
- postMethod(postMember)에서 유효성 검증이 실패 했을때
- RequestBody의 유효하지 않은 요청 데이터가 포함되어 있을때
- MethodArgumentNotValidException 발생
- @ExceptionHandler가 적용된 handleException()가 메서드가 예외를 전달 받음
- e.getBindingResult().getFieldErrors()를 통해 발생한 에러 정보를 저장 (fieldErrors)
- ResponseEntity를 통해 ResponseBody로 전달
- 해당 방법은 에러메시지의 내용을 전체 전달하기 때문에 불필요한 정보까지 전부 Response Body로 전달함
- 정보 가공이 안됨
Error Response 클래스를 통한 전달
- 필요한 정보만 가공하여 전달
ErrorResponse 클래스
package com.codestates.response.v1;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@Getter
@AllArgsConstructor
public class ErrorResponse {
private List<FieldError> fieldErrors;
@Getter
@AllArgsConstructor
public static class FieldError{
private String field;
private Object rejectedValue;
private String reason;
}
}
- 필요한 정보만 필드로 생성
- fieldErrors가 배열인 이유
- DTO 클래스에서 검증해야 되는 멤버 변수에서 유효성 검증에 실패하는 멤버 변수들이 하나 이상이 될 수 있기 때문에 유효성 검증 실패 에러 역시 하나 이상이 될 수 있기 때문에
- JSON 응답 객체가 배열
- FieldError 클래스는 static 멤버 클래스, ErrorResponse의 멤버
Controller내 적용
import com.codestates.response.v1.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import java.util.stream.Collectors;
@ExceptionHandler
public ResponseEntity handleException(MethodArgumentNotValidException e){
final List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<ErrorResponse.FieldError> errors =
fieldErrors.stream()
.map(error -> new ErrorResponse.FieldError(
error.getField(),
error.getRejectedValue(),
error.getDefaultMessage()))
.collect(Collectors.toList());
return new ResponseEntity<>(new ErrorResponse(errors), HttpStatus.BAD_REQUEST);
}
- e.getBindingResult().getFieldErrors()로 생성한 fieldErrors를 ErrorResponse.FieldError로 가공(Stream)
- ErrorResponse.FieldError(list)를 ResponseEntity를 통해 ResponseBody로 전달
- 필요한 정보들만 선택적으로 골라서 Response Body를 전달하는 것
@ExceptionHandler의 단점 - 현재 예외처리 방법
- 각각의 Controller에서 (coffee, member등) @ExceptionHandler로 유효성 검증 실패 예외처리를 해야함
- 코드 중복
- Controller에서 발생하는 예외가 유효성 검증 실패만 있는것이 아니라 다른 예외도 있기 때문에 Controller내에 @ExceptionHandler을 사용하여 추가적인 예외처리 메서드를 만들어야함
- 코드 복잡도 및 가독성
@RestControllerAdvice를 사용한 예외 처리 공통화
- @RestControllerAdvice 애너테이션을 추가하면 여러 개의 Controller 클래스에서 @ExceptionHandler, @InitBinder 또는 @ModelAttribute가 추가된 메서드를 공유해서 사용
- @RestControllerAdvice 애너테이션을 추가한 클래스를 이용하면 예외 처리를 공통화 (AOP)
ExceptionAdvice 클래스
- Controller 클래스에서 발생하는 예외들을 공통으로 처리할 클래스
import com.codestates.response.v2.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
@RestControllerAdvice
public class GlobalExceptionAdvice {
// (1)
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
final ErrorResponse response = ErrorResponse.of(e.getBindingResult());
return response;
}
// (2)
@ExceptionHandler
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleConstraintViolationException(
ConstraintViolationException e) {
final ErrorResponse response = ErrorResponse.of(e.getConstraintViolations());
return response;
}
}
- @RestControllerAdvice 애너테이션 추가시 Controller 클래스에서 발생하는 예외를 처리
- @ResponsStatus 애너테이션을 이용해서 Http Status를 Http Response에 포함
- (1) : 유효성 검증 예외 처리 (MethodArgumentNotValidException)
- of() 메서드를 호출, 아규먼트로 BindingResult를 보낸다.
- (2) : uri 가 변수로 넘어오는 경우 예외 (ConstraintViolationException)
- of() 메서드를 호출, 아규먼트로 ConstraintViolations을 보낸다.
- patch, delete 메서드 등에서 특정 엔드포인트로 접근할때
- http://127.0.0.1:8080/v5/members/1 - 멤버 1로 접근
- 0이나 음수등으로 접근하는 경우 ConstraintViolationException 예외 발생
ErrorResponse 수정
package com.codestates.response.v2;
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
public class ErrorResponse {
private List<FieldError> fieldErrors; // (1)
private List<ConstraintViolationError> violationErrors; // (2)
// (3)
private ErrorResponse(List<FieldError> fieldErrors, List<ConstraintViolationError> violationErrors) {
this.fieldErrors = fieldErrors;
this.violationErrors = violationErrors;
}
// (4) BindingResult에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(BindingResult bindingResult) {
return new ErrorResponse(FieldError.of(bindingResult), null);
}
// (5) Set<ConstraintViolation<?>> 객체에 대한 ErrorResponse 객체 생성
public static ErrorResponse of(Set<ConstraintViolation<?>> violations) {
return new ErrorResponse(null, ConstraintViolationError.of(violations));
}
// (6) Field Error 가공
@Getter
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 -> new FieldError(
error.getField(),
error.getRejectedValue() == null ?
"" : error.getRejectedValue().toString(),
error.getDefaultMessage()))
.collect(Collectors.toList());
}
}
// (7) ConstraintViolation Error 가공
@Getter
public static class ConstraintViolationError {
private String propertyPath;
private Object rejectedValue;
private String reason;
private ConstraintViolationError(String propertyPath, Object rejectedValue,
String reason) {
this.propertyPath = propertyPath;
this.rejectedValue = rejectedValue;
this.reason = reason;
}
public static List<ConstraintViolationError> of(
Set<ConstraintViolation<?>> constraintViolations) {
return constraintViolations.stream()
.map(constraintViolation -> new ConstraintViolationError(
constraintViolation.getPropertyPath().toString(),
constraintViolation.getInvalidValue().toString(),
constraintViolation.getMessage()
)).collect(Collectors.toList());
}
}
}
- (1) : MethodArgumentNotValidException으로부터 발생하는 에러 정보를 담는 멤버 변수
- (2) : ConstraintViolationException으로부터 발생하는 에러 정보를 담는 멤버 변수
- (3) : ErrorResponse 클래스의 생성자 접근제어자를 private로 하여 new()를 통한 생성 불가
- (4) : MethodArgumentNotValidException에 대한 ErrorResponse 객체를 생성
- **of() 메서드를 호출하는 쪽에서 BindingResult 객체를 파라미터로 넘겨야 한다. - 생성자 오버로딩
- 에러 정보를 추출하고 가공하는 일은 ErrorResponse 클래스의 static 멤버 클래스인 FieldError 클래스에게 위임
- (5) : ConstraintViolationException에 대한 ErrorResponse 객체를 생성
- of() 메서드를 호출하는 쪽에서 Set<ConstraintViolation<?>> 객체를 파라미터로 넘겨야 한다. - 생성자 오버로딩
- 에러 정보를 추출하고 가공하는 일은 ErrorResponse 클래스의 static 멤버 클래스인 ConstraintViolationError 클래스에게 위임
- (6) : 필드(DTO 클래스의 멤버 변수)의 유효성 검증에서 발생하는 에러 정보를 생성 (가공)
- (7) : URI 변수 값에 대한 에러 정보를 생성 (가공)
**of() 메서드
- 정적 팩토리 메서드
- 직접적(new)으로 생성자를 통해 객체를 생성하는 것이 아닌 메서드를 통해서 객체를 생성하는 것
- 네이밍 컨벤션(Naming Convention)
- 주로 객체 생성 시 어떤 값들의(of~) 객체를 생성한다는 의미에서 of() 메서드를 사용
요약
- Controller 클래스 레벨에서 @ExceptionHandler 애너테이션을 사용하면 해당 Controller에서 발생하는 예외를 처리할 수 있다.
- 필요한 Error 정보만 담을 수 있는 Error 전용 Response 객체를 사용하면 클라이언트에게 조금 더 친절한 에러 정보를 제공할 수 있다.
- @ExceptionHandler 애너테이션 방식은 Controller마다 동일하게 발생하는 예외 처리에 대한 중복 코드가 발생할 수 있다.
- @ExceptionHandler 애너테이션 방식은 다양한 유형의 예외를 처리하기에는 적절하지 않은 방식이다.
- @RestControllerAdvice 애너테이션을 추가한 클래스를 이용하면 예외 처리를 공통화할 수 있다.
- @RestControllerAdvice 애너테이션을 사용하면 JSON 형식의 데이터를 Response Body로 전송하기 위해 ResponseEntity로 래핑 할 필요가 없다.
- @ResponseStatus 애너테이션으로 HTTP Status를 대신 표현할 수 있다.
- 너무 많은 @RestControllerAdvice를 사용하면 애플리케이션 실행 타임이 늘어난다. -적절한 개수를 잘 정해서 사용
'Spring Framework' 카테고리의 다른 글
Spring Data JDBC (0) | 2023.04.19 |
---|---|
비즈니스 로직에 대한 예외 처리, 사용자 정의 예외(Custom Exception) (0) | 2023.04.18 |
DI를 통한 서비스 계층 ↔ API 계층 연동, 매퍼(Mapper), 엔티티(Entity) (0) | 2023.04.14 |
DTO(Data Transfer Object) (0) | 2023.04.13 |
SpringMVC - Controller (0) | 2023.04.12 |
Comments