일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 컴퓨터공학학사취득
- 모놀리식
- Pass By Value
- fastapi
- 은행IT
- union
- Homebrew
- 맥북셋팅
- 계정계
- 의사결정나무모형
- it자격증
- 학점은행제
- 맥북
- 코어뱅킹
- 학점은행제무료강의
- 코딩테스트
- 프로그래머스
- DB
- python
- MSA
- 오라클
- 맥북환경설정
- jdk17
- 디렉토리계층구조
- 개인프로필스튜디오창업
- jdk
- SQL
- 채널계
- oracleapex
- 렌탈스튜디오창업
- Today
- Total
개발머해니
[스프링] 예외처리 종류과 효과적인 사용 방법 본문
스프링 예외처리 종류
스프링에서 예외처리는 크게 3가지로 나눌 수 있습니다.
① @ExceptionHandler : Controller Level
② @ControllerAdvice : Global Level
③ try ~ catch문 : Method Level
@ExceptionHandler : 컨트롤러단에서 처리
- @ExceptionHandler는 Controller계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능입니다.
- Service, Repository에서 발생하는 에러는 제외합니다.
@RestController
@RequestMapping("/person")
public class PersonController {
@GetMapping
public String test(){
return "test";
}
@GetMapping("/exception")
public String exception1(){
throw new NullPointerException();
}
@GetMapping("/exception2")
public String exception2(){
throw new ClassCastException();
}
@ExceptionHandler({NullPointerException.class, ClassCastException.class})
public String handle(Exception ex){
return "Exception Handle!!!";
}
}
@ControllerAdvice : 전역에서 처리
- @ControllerAdvice는 @Controller와 handler에서 발생하는 에러들을 모두 잡아줍니다.
- @ControllerAdvice안에서 @ExceptionHandler를 사용하여 에러를 잡을 수 있습니다.
@RestControllerAdvice
public class ControllerSupport {
@ExceptionHandler({NullPointerException.class, ClassCastException.class})
public String handle(Exception ex) {
return "Exception Handle!!!";
}
}
@RestController
@RequestMapping("/school")
public class SchoolController {
@GetMapping
public String test(){
return "test";
}
@GetMapping("/exception")
public String exception1(){
throw new NullPointerException();
}
@GetMapping("/exception2")
public String exception2(){
throw new ClassCastException();
}
}
@RestController
@RequestMapping("/person")
public class PersonController {
@GetMapping
public String test(){
return "test";
}
@GetMapping("/exception")
public String exception1(){
throw new NullPointerException();
}
@GetMapping("/exception2")
public String exception2(){
throw new ClassCastException();
}
}
try ~ catch문 : 메서드 단위에서 처리
- throw 키워드를 사용하여 프로그래머가 고의로 예외를 발생시킬수 있습니다.
- try 문에서 Exception 예외가 발생할 경우 catch (Exception e) 로 빠져서 그 안의 실행문을 실행한다.
- 마지막 finally블럭은 try-catch문과 함께 예외발생 여부과 관계없이 "항상. 무조건" 실행되어야할 코드를 적는다.
- 예외 발생시 try → catch → finally 순으로, 발생 하지 않은 경우 try → finally 순으로 실행됩니다.
스프링 Exception 전략
아래 글에 효과적인 Exception 전략 내용이 인상적이라 중요한 핵심 내용 몇가지만 요약해보겠습니다.
통일된 Error Response 객체
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ErrorResponse {
private String message;
private int status;
private List<FieldError> errors;
private String code;
...
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class FieldError {
private String field;
private String value;
private String reason;
...
}
}
이처럼 통일된 POJO 객체로 Error Response를 관리하면, 리턴 타입을 ResponseEntity<ErrorResponse>으로 반환하여 무슨 데이터가 어떻게 있는지 명확하게 추론하기 쉽도록 구성할 수 있습니다.
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ControllerAdvice로 모든 예외를 핸들링
스프링 및 라이브러리 등 자체적으로 발생하는 예외는 @ExceptionHandler 으로 추가해서 적절한 Error Response를 만들고 비즈니스 요구사항에 예외일 경우 BusinessException 으로 통일성 있게 처리하는 것이 좋습니다.
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* javax.validation.Valid or @Validated 으로 binding error 발생시
* HttpMessageConverter 에서 등록한 HttpMessageConverter binding 못할경우 발생
* 주로 @RequestBody, @RequestPart 어노테이션에서 발생
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("handleMethodArgumentNotValidException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
/**
* @ModelAttribut 으로 binding error 발생시 BindException 발생
* ref https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-modelattrib-method-args
*/
@ExceptionHandler(BindException.class)
protected ResponseEntity<ErrorResponse> handleBindException(BindException e) {
log.error("handleBindException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
/**
* enum type 일치하지 않아 binding 못할 경우 발생
* 주로 @RequestParam enum으로 binding 못했을 경우 발생
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
log.error("handleMethodArgumentTypeMismatchException", e);
final ErrorResponse response = ErrorResponse.of(e);
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
/**
* 지원하지 않은 HTTP method 호출 할 경우 발생
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error("handleHttpRequestMethodNotSupportedException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.METHOD_NOT_ALLOWED);
return new ResponseEntity<>(response, HttpStatus.METHOD_NOT_ALLOWED);
}
/**
* Authentication 객체가 필요한 권한을 보유하지 않은 경우 발생
*/
@ExceptionHandler(AccessDeniedException.class)
protected ResponseEntity<ErrorResponse> handleAccessDeniedException(AccessDeniedException e) {
log.error("handleAccessDeniedException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.HANDLE_ACCESS_DENIED);
return new ResponseEntity<>(response, HttpStatus.valueOf(ErrorCode.HANDLE_ACCESS_DENIED.getStatus()));
}
/**
* 비즈니스 요규사항에 따른 Exception
*/
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessException e) {
log.error("handleEntityNotFoundException", e);
final ErrorCode errorCode = e.getErrorCode();
final ErrorResponse response = ErrorResponse.of(errorCode);
return new ResponseEntity<>(response, HttpStatus.valueOf(errorCode.getStatus()));
}
/**
* 개발자가 직접 핸들링해서 다른 예외로 던지지 않으면 모두 이곳으로 모인다.
*/
@ExceptionHandler(Exception.class)
protected ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("handleEntityNotFoundException", e);
final ErrorResponse response = ErrorResponse.of(ErrorCode.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
컨트롤러 예외 처리
컨트롤러에서 모든 요청에 대한 값 검증을 진행하고 이상이 없을 시에 서비스 레이어를 호출해야 합니다. 위에서도 언급했듯이 잘못된 값이 있으면 서비스 레이어에서 정상적인 작업을 진행하기 어렵습니다. 무엇보다 컨트롤러의 책임을 다하고 있지 않으면 그 책임은 자연스럽게 다른 레이어로 전해지게 되며 이렇게 넘겨받은 책임을 처리하는데 큰 비용과 유지보수 하기 어려워질 수밖에 없습니다.
@RestController
@RequestMapping("/members")
public class MemberApi {
private final MemberSignUpService memberSignUpService;
@PostMapping
public MemberResponse create(@RequestBody @Valid final SignUpRequest dto) {
final Member member = memberSignUpService.doSignUp(dto);
return new MemberResponse(member);
}
}
Reuqest Body @Valid 어노테이션으로 예외를 발생시킬 수 있습니다. 이 예외는 @ControllerAdvice에서 적절하게 핸들링 됩니다.
public class SignUpRequest {
@Valid private Email email;
@Valid private Name name;
}
public class Name {
@NotEmpty private String first;
private String middle;
@NotEmpty private String last;
}
public class Email {
@javax.validation.constraints.Email
private String value;
}
Try Catch 전략
Checked Exception이 발생하면 더 구체적인 Unchecked Exception을 발생시키고 예외에 대한 메시지를 명확하게 전달하는 것이 효과적입니다.
Checked Exception과 Unchecked Exception :
Checked Exception | Unchecked Exception | |
처리 여부 | 반드시 예외 처리 해야함 | 예외 처리 하지 않아도됨 |
트랜잭션 Rollback 여부 | Rollback 안됨 | Rollback 진행 |
대표 Exception | IOException, SQLException | NullPointerException, IllegalArgumentException |
'백엔드' 카테고리의 다른 글
[DB] 오라클에 이모지(Emoji)를 저장할 수 있을까? (0) | 2023.08.31 |
---|---|
[스프링] 클라이언트가 에러났을때도 ResponseDTO를 내려달라고 하면 어떻게 해야할까? (0) | 2023.08.30 |
[DB] 인덱스는 어떤 기준으로 잡는 게 좋을까? (0) | 2023.08.27 |
[DB] 파티션의 개념, 필요성, 종류 (0) | 2023.08.27 |
[DB] DML을 하면 무조건 인덱스의 성능저하를 가져올까? (0) | 2023.08.26 |