Spring API Exception

정 승 연·2023년 1월 27일
0

목록 보기
6/9

보통 Repository,Service에서 발생한 오류는 Controller에서 처리하는것이 맞다?

try/catch를 이용한 예외 처리

Service에서 발생한 예외들을 Controller로 넘겨 각 다른 예외를 처리하도록 한다.

@Service
public class Service{

	@Transactional
	public createRes write(Long userId,createReq request) throws BaseException{
			
			if(writingSentence == ""){
	      throw new BaseException(POST_SENTENCE_EMPTY);
      }

      if(!isExist(writingSentence , writingWord)){
         throw new BaseException(POST_SENTENCE_NO_EXISTS_KEYWORD);
      }
	}
}

→ write메소드에 throw BaseException 추가 후 throw

@RestController
public class Controller {
	@ResponseBody
	@PostMapping("/write")
	public BaseResponse<createReq> write(@RequestBody createReq request) {
	
	    try{
            createRes write = sentenceService.write(userId,request);
            return new BaseResponse<>(write);
       }catch (BaseException e){
            return new BaseResponse<>(e.getStatus());
        }
}

→ catch에서 exception 잡아서 e.getStatus return

HandlerExceptionResolver(ExceptionResolver)

스프링 MVC는 Controller(Handler) 밖으로 Exception가 던져진 경우 Exception을 해결하고 동작을 새로 정의할 수 있는 방법을 제공한다. Controller 밖으로 던져진 Exception을 해결하고 동작 방식을 변경하고 싶으면 HandlerExceptionResolver를 사용하면 된다.

하지만 HandlerExceptionResolver는 ModelAndView를 반환해야한다. 이건 API 응답에 필요하지 않다.

@ExceptionHandler

ExceptionHandlerExceptionResolver

@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());

				// @RestController이므로 @ResponseBody가 적용된다.
				// return한 ErrorResult 객체값이 JSON으로 반환된다.
				// @ResponseStatus 상태코드도 지정하였으므로 http 상태코드도 응답한다.
    }

    @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("사용자 오류"); 
		}
}
  • /api2/members/bad
    • 컨트롤러를 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다. 예외가 발생했으므로 ExceptionResolver가 작동하고 가장 우선 순위가 높은 ExceptionHandlerExceptionResolver = @ExceptionHandler가 실행된다.
    • ExceptionHandlerExceptionResolver 는 해당 컨트롤러에 해당 Exception(illegalArgumentException) 을 처리할 수 있는 @ExceptionHandler가 있는지 확인한다.
    • illegalExHandle() 를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다.
    • @ResponseStatus(HttpStatus.BAD_REQUEST) 를 지정했으므로 HTTP 상태 코드 400으로 응답한다.

@RestControllerAdvice

@ExceptionHandler를 사용해 깔끔하게 처리할 수 있지만, 정상 코드와 예외 처리 코드가 섞여있다.

@ControllerAdvice 또는 @RestControllerAdvice를 이용해 분리할 수 있다.

다른 컨트롤러에도 이 Exception 을 사용하고 싶을 때도 사용할 수 있다.

@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());
      }
			.
			.
			.
}
  • Controller에서 발생하는 예외들을 @ResControllerAdvice 어노테이션이 달린 클래스에서 관리
  • Controller의 @ExceptionHandler 삭제

@ControllerAdvice

대상으로 지정한 여러 Controller에 @ExceptionHandler, @InitBinder 기능 부여

@ControllerAdvice에 대상을 지정하지 않으면 모든 Controller에 적용됨

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann- controller-advice (스프링 공식 문서 참고)

아니다. Controller에서 Exception 처리를 진행하면 작업량이 많아지고 중복이 된다. Spring에서 Exception을처리할 때 @ExceptionHandler 와 @ResponseEntity 를 사용한다.

➕ 권한에 대한 검증은 어디서 할까?

권한에 대한 검증을 서비스에서 한다면 다른 컨트롤러나 서비스에서 권한 문제를 통과하지 못할 수 도 있다. 반드시 권한있는 사용자가 그 서비스에 접근한다는 보장은 없다. 그래서 컨트롤러에서 권한 예외처리를 진행하여 권한을 넘겨주는 것이 적절하다.

➕ 로그 레벨의 의미

  • DEBUG
    • 말 그대로 디버깅을 위한 목적
    • 아직 DEBUG로 로그를 출력해야한다면 아직 안정화가 되지 않은 것이다.
  • INFO
    • 시스템 동작에 대한 정보를 제공
  • WARN
    • 현재 운영에는 문제가 없지만 앞으로 문제가 될 수 있는 사항
  • ERROR
    • 시스템 운영에 문제가 있을 만한 사항
    • 보통 예외를 잡아서 정상 처리한 경우
  • FATAL
    • 시스템 운영이 불가능한 경우
    • 보통 예외가 발생하고 정상 처리하지 못한 경우

0개의 댓글