본문 바로가기
Spring

[Spring] @ExceptionHandler, @ControllerAdvice를 통한 예외 처리

by 당코 2023. 3. 24.

스프링은 API 예외를 처리하기 위한 @ExceptionHandler이란 어노테이션을 제공한다.

 

@ExceptionHandler

에러를 전달하기 위한 DTO이다

@Data
@AllArgsConstructor
public class ErrorResult {
	private String code;
	private String message;
}

 

다음은 예외를 받고자 하는 컨트롤러에 @ExceptionHandler를 사용한 예시이다.

@Slf4j
@RestController
public class ApiExceptionV2Controller {
 	@ResponseStatus(HttpStatus.BAD_REQUEST)
 	@ExceptionHandler(IllegalArgumentException.class)
 	public ErrorResult illegalExHandle(IllegalArgumentException e) {
 		log.error("[exceptionHandle] ex", e);
 		return new ErrorResult("BAD", e.getMessage());
 	}
    
 	@ExceptionHandler
 	public ResponseEntity<ErrorResult> userExHandle(UserException e) {
 		log.error("[exceptionHandle] ex", e);
 		ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
 		return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
 	}
    
 	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
 	@ExceptionHandler
 	public ErrorResult exHandle(Exception e) {
 		log.error("[exceptionHandle] ex", e);
 		return new ErrorResult("EX", "내부 오류");
 	}
    
 	@GetMapping("/api2/members/{id}")
 	public MemberDto getMember(@PathVariable("id") String id) {
 		if (id.equals("ex")) {
 			throw new RuntimeException("잘못된 사용자");
 		}
 		if (id.equals("bad")) {
 			throw new IllegalArgumentException("잘못된 입력 값");
 		}
 		if (id.equals("user-ex")) {
 			throw new UserException("사용자 오류");
 		}
 		return new MemberDto(id, "hello " + id);
 	}
    
	@Data
	@AllArgsConstructor
	static class MemberDto {
		private String memberId;
		private String name;
	}
}

getMember 메서드에서는 RuntimeException, IllegalArgumentException, UserException 총 3개의 예외가 발생할 수 있다.

각각의 예외들을 처리하기 위한 컨트롤러를 @ExceptionHandler를 사용하여 만들어주었다.

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
	log.error("[exceptionHandle] ex", e);
	return new ErrorResult("BAD", e.getMessage());
}

예시중 하나를 살펴보면 IllegalArgumentException을 처리하는 @ExceptionHandler를 지정해 주었다.

@ExceptionHandler는 같은 클래스 내에서 지정한 예외가 발생할 경우 따로 try-catch문 없이 해당 예외를 처리해 준다.

예시에서는 예외 로그를 남기고 ErrorResult 객체에 예외 정보를 담아 리턴한다.

예외를 처리해서 정상적으로 반환이 되었기 때문에 HTTP상태 코드는 200으로 나간다.

따라서 예외가 발생했다는 것을 HTTP 상태코드에도 적용시키기 위해 @ResponseStatus를 사용하여 BAD_REQUEST(400) 오류로 변경하였다.

 

@ExceptionHandler(부모예외.class)
public String 부모예외처리()(부모예외 e) {}
@ExceptionHandler(자식예외.class)
public String 자식예외처리()(자식예외 e) {}

만약 부모인 Exception.class와 그 자식인 IllegalArgumentException.class가 같은 클래스 내에 @ExceptionHandler를 통해 지정되어 있다면 자식예외가 우선권을 가진다.

 

@ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
 	log.info("exception e", e);
}

위와 같이 여러 가지 예외를 하나의 컨트롤러에서 처리해 줄 수도 있다.

 

@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {}

@ExceptionHandler의 예외를 생략하면 메서드 파라미터의 예외가 지정된다.

 

 

@ControllerAdvice

@ExceptionHandler를 통해 API예외를 쉽게 처리할 수 있지만 한 가지 불편한 점이 있다.

정상 코드와 예외 코드가 하나의 클래스에 정의되어 있어야 하기 때문에 역할 분리를 할 수 없다.

@ControllerAdvice 또는 @RestControllerAdvice를 사용하면 위와 같은 문제를 해결할 수 있다.

@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {

	@ResponseStatus(HttpStatus.BAD_REQUEST)
	@ExceptionHandler(IllegalArgumentException.class)
	public ErrorResult illegalExHandle(IllegalArgumentException e) {
		log.error("[exceptionHandle] ex", e);
		return new ErrorResult("BAD", e.getMessage());
	}
    
	@ExceptionHandler
	public ResponseEntity<ErrorResult> userExHandle(UserException e) {
		log.error("[exceptionHandle] ex", e);
		ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
		return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
	}
    
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
	@ExceptionHandler
	public ErrorResult exHandle(Exception e) {
		log.error("[exceptionHandle] ex", e);
		return new ErrorResult("EX", "내부 오류");
	}
}

 

@ControllerAdvice는 대상을 지정해주지 않고 그냥 사용하면 모든 컨트롤러에 글로벌하게 적용된다.

 

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class,
				AbstractController.class})
public class ExampleAdvice3 {}

@ControllerAdvice 대상 컨트롤러 지정 방법

  • 특정 어노테이션이 있는 컨트롤러
  • 해당 패키지와 하위 모든 컨트롤러
  • 특정 클래스

 

 

출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard

'Spring' 카테고리의 다른 글

[Spring] 로그인 처리(쿠키, 세션)  (0) 2023.01.24
[Spring] 검증(Bean Validation)  (0) 2023.01.20
[Spring] 메시지와 국제화  (0) 2023.01.16
[Spring] @ModelAttribute 사용법  (0) 2023.01.13
[Spring] @RequestParam 사용법  (0) 2023.01.10