전에 @ExceptionHandler(IllegalArgumentException.class) 해서 컨트롤러 안에 에러를 처리 할 로직을 넣는 것은 나름 좋은 방법이었지만,
에러와 컨트롤러가 분리가 안됀다. 완벽히는 힘들더라도 잘 분리가 되어야만 유지보수 하기가 편하다.
@ControllerAdvice 또는 @RestControllerAdvice를 쓰면 컨트롤러와 예외처리 로직을 분리할 수 있다.
@Slf4j
@RestController
public class ApiExceptionV2Controller {
@GetMapping("/api2/members/{id}")
public ApiExceptionController.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 ApiExceptionController.MemberDto(id, "hello " + id);
}
@GetMapping("/api2/response-status-ex1")
public String responseStatusEx1(){
throw new BadRequestException();
}
@GetMapping("/api2/response-status-ex2")
public String responseStatusEx2(){
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
}
@GetMapping("/api2/default-handler-ex")
public String defaultException(@RequestParam Integer data){
return "ok";
}
@Data
@AllArgsConstructor
static class MemberDto{
private String memberId;
private String name;
}
}
기존에 있던 예외처리에 대한 @ExceptionHandler 하고 붙어있던 코드들을 분리했다.
@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e){
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e){
log.error("[exceptionHandler] 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 exHandler(Exception e){
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
}
@ControllerAdvice 를 붙여 따로 분리해 뒀다.
이렇게 하면 예외가 발생하면 저 ControllerAdvice를 뒤져서 저기 중 받을 수 있는 예외가 있는 지 조회해 보고, 받을 수 있는 예외가 있다면 그 로직을 수행하는 듯 하다.
결과도 똑같이 나온다.
@ControllerAdvice는 대상으로 지정한 컨트롤러 들에게 @ExceptionHandler, @InitBinder 기능을 부여해 주는 역할을 한다.
말 그대로 컨트롤러 어드바이스이다.
참고로 위는 대상을 따로 지정해두진 않았는데, 저러면 글로벌 적용이 되는거다.
대상을 지정하는 방법은 총 3가지 방법이 있다.
애노테이션마다, 아예 패키지경로를, 아예 컨트롤러 클래스를.(아예 클래스를)
@ControllerAdvice(annotations = RestController.class) RestController 애노테이션을 가진 컨트롤러를 지정
@ControllerAdvice(basePackages = "org.example.controllers") 패키지 자체를 지정. 상품관리 패키지나 유저관리 패키지 따로 지정하면 유용할 듯
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) 아예 컨트롤러 클래스 자체를 지정. 컨트롤러 뿐 아니라 아예 클래스 자체도 될거임. 인터페이스나 컨트롤러.
@Slf4j
@RestControllerAdvice(basePackages = "hello.exception.api")
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e){
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e){
log.error("[exceptionHandler] 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 exHandler(Exception e){
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
}
이런 식으로.
클래스 자체를 ControllerAdvice로 등록하고, 대상 컨트롤러 지정해주고(혹은 글로벌)
거기에 @ExceptionHandler나 @InitBinder 를 정의해준다.
'스프링 > 4. 스프링 MVC-2' 카테고리의 다른 글
76. 스프링 타입컨버터 (0) | 2023.09.22 |
---|---|
75. 스프링의 자동 타입 변환 (0) | 2023.09.22 |
73. @ExceptionHandler API 예외 (0) | 2023.09.16 |
72. 스프링 내부에서 발생하는 예외를 처리해주는 Resolver (0) | 2023.09.15 |
71. 스프링이 자동등록해주는 ExceptionResolver (0) | 2023.09.15 |