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
스프링 MVC는 Controller(Handler) 밖으로 Exception가 던져진 경우 Exception을 해결하고 동작을 새로 정의할 수 있는 방법을 제공한다. Controller 밖으로 던져진 Exception을 해결하고 동작 방식을 변경하고 싶으면 HandlerExceptionResolver를 사용하면 된다.
하지만 HandlerExceptionResolver는 ModelAndView를 반환해야한다. 이건 API 응답에 필요하지 않다.
@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("사용자 오류");
}
}
IllegalArgumentException
예외가 컨트롤러 밖으로 던져진다. 예외가 발생했으므로 ExceptionResolver
가 작동하고 가장 우선 순위가 높은 ExceptionHandlerExceptionResolver
= @ExceptionHandler
가 실행된다.ExceptionHandlerExceptionResolver
는 해당 컨트롤러에 해당 Exception(illegalArgumentException) 을 처리할 수 있는 @ExceptionHandler
가 있는지 확인한다.illegalExHandle()
를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다.@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에 @ExceptionHandler, @InitBinder 기능 부여
@ControllerAdvice에 대상을 지정하지 않으면 모든 Controller에 적용됨
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann- controller-advice (스프링 공식 문서 참고)
권한에 대한 검증을 서비스에서 한다면 다른 컨트롤러나 서비스에서 권한 문제를 통과하지 못할 수 도 있다. 반드시 권한있는 사용자가 그 서비스에 접근한다는 보장은 없다. 그래서 컨트롤러에서 권한 예외처리를 진행하여 권한을 넘겨주는 것이 적절하다.