반응형
- [Spring Boot] Exception 처리 - 에러 페이지 적용(화면), 에러 코드 적용(API) 여기서 ErrorCode와 ExceptionDto를 활용하여 Exception을 출력하는 방법에 대해서 정리했었음
- 이 방식은 Response의 status와 body를 직접 만들어서 응답해주는 방식
- 하지만 이 방식은 실제로 프로젝트에 적용하기에는 어려움이 있음
- 이 방식을 사용하기 위해서는 모든 Return Type을 맞춰줘야 하는 어려움이라던지, Service 단에서 에러가 발생하는 경우 에러 처리가 힘들다는 등의 단점이 존재
- 따라서 Response를 수정해서 응답하는 방식이 아닌 exception이 발생한 지점에서 throw를 통해 exception을 던지고 나중에 이를 받아 처리하는 작업이 필요
- @ExceptionHandler, @ControllerAdvice, @RestControllerAdvice 사용
ErrorCode, MyException 생성
ErrorCode (enum)
- 발생할 수 있을만한 에러들을 몇 개 정리해서 만들어 봄
@Getter
@AllArgsConstructor
public enum ErrorCode {
USERNAME_NOT_FOUND(HttpStatus.NOT_FOUND, "유저를 찾을 수 없습니다"),
INVALID_PERMISSION(HttpStatus.UNAUTHORIZED, "권한이 없습니다"),
DUPLICATED_USER_NAME(HttpStatus.CONFLICT, "유저명이 중복됩니다"),
DATABASE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "DB에러가 발생하였습니다");
private HttpStatus status;
private String message;
}
MyException
- ExceptionDto 와는 달리 RuntimeException을 상속 받음
- 단순히 출력용이 아닌 Exception 처리를 하기 위함
@Getter
public class MyException extends RuntimeException{
private String result;
private ErrorCode errorCode;
private String message;
public MyException(ErrorCode errorCode) {
this.result = "ERROR";
this.errorCode = errorCode;
this.message = errorCode.getMessage();
}
}
ExceptionRestController, ExceptionService 생성
ExceptionRestController
- Controller가 아닌 Service에서 exception이 발생한 상황을 가정
@RestController
@RequestMapping("/exception-example")
@RequiredArgsConstructor
public class ExceptionRestController {
private final ExceptionService exceptionService;
@GetMapping("/throw-my-exception/1")
public void throwMyException1() {
// ex) 로그인 시 username에 해당하는 User가 없는 경우
exceptionService.login();
}
@GetMapping("/throw-my-exception/2")
public void throwMyException2() {
// ex) 로그인 하지 않은 유저가 댓글을 작성하려는 경우
exceptionService.writeComment();
}
@GetMapping("/throw-my-exception/3")
public void throwMyException3() {
// ex) 회원가입 시 username이 중복되는 경우
exceptionService.join();
}
@GetMapping("/throw-my-exception/4")
public void throwMyException4() {
// ex) 댓글 추가 시 DB 에러가 발생한 경우
exceptionService.editComment();
}
}
ExceptionService
@Service
public class ExceptionService {
public void login() {
throw new MyException(ErrorCode.USERNAME_NOT_FOUND);
}
public void writeComment() {
throw new MyException(ErrorCode.INVALID_PERMISSION);
}
public void join() {
throw new MyException(ErrorCode.DUPLICATED_USER_NAME);
}
public void editComment() {
throw new MyException(ErrorCode.DATABASE_ERROR);
}
}
여기까지의 결과
- log에는 원하는 exception이 제대로 발생하지만, response를 확인해보면 HttpStatus가 500인 것을 확인할 수 있음
- 원하는 결과는 HttpStatus.NOT_FOUND(404) 에러가 출력되는 것임
@ExceptionHandler 적용
- 위에서 작성한 ExceptionRestController에 아래 코드 추가
- ExceptionRestController에서 MyException이 발생하면 myExceptionHandler가 실행되는 방식
- MyException.class가 아닌 RuntimeException.class, IOException.class 등과 같이 기본 exception들을 처리할 수도 있음
@ExceptionHandler(MyException.class)
public ResponseEntity<?> myExceptionHandler(MyException e) {
e.printStackTrace();
return ResponseEntity.status(e.getErrorCode().getStatus())
.body(new ExceptionDto(e.getErrorCode()));
}
결과
- log 뿐만 아닌, response에도 원하는 HttpStatus가 출력되는 것을 확인할 수 있음
@ControllerAdvice, @RestControllerAdvice
- 위에서 적용한 @ExceptionHandler는 메소드가 속한 Controller나 RestController에서만 적용됨
- Controller, RestController가 여러개이고 Controller마다 굳이 따로 excetpion 처리를 하지 않고, 모두 동일한 방식으로 처리하려는 상황에서 @ExceptionHandler는 비효율적임
- 이런 상황에서는 @ControllerAdvice, @RestControllerAdvice 사용
- @ControllerAdivce는 @Controller에, @RestControllerAdivce는 @RestController에만 적용되는 것이 아닌 @RestController = @Controller + @ResponseBody와 같은 차이
@RestControllerAdvice 적용 예제
- 일단 ExceptionRestController에 추가했던 myExceptionHandler 삭제 후 진행
- annotations를 사용해 특정 어노테이션에만 적용하거나, basePackages를 사용해 특정 패키지에만 적용 시키는 등의 방법으로 적용 범위를 지정할 수 있음
- 만약 MyException이 발생한다면 myExceptionHandler에서 해당 exception 처리
- runtimeExceptionHandler에서는 @ResponseStatus를 통해 HttpStatus를 설정하고 body는 String 타입으로 return
//@RestControllerAdvice(annotations = Controller.class)
@RestControllerAdvice(basePackages = "com.study.hellospring.exception_example")
public class ExceptionManager {
@ExceptionHandler(MyException.class)
public ResponseEntity<?> myExceptionHandler(MyException e) {
e.printStackTrace();
return ResponseEntity.status(e.getErrorCode().getStatus())
.body(new ExceptionDto(e.getErrorCode()));
}
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String runtimeExceptionHandler(RuntimeException e) {
e.printStackTrace();
return "Runtime Exception 발생!";
}
}
결과
- ExceptionRestController 뿐만 아닌 해당 패키지의 다른 Controller에서 발생한 runtimeException도 잘 처리하는 것을 확인할 수 있음
(추가) @ModelAttribute, @InitBinder
- @ControllerAdvice, @RestControllerAdvice를 보통 exception 처리에 많이 활용하는데 @ModelAttribute, @InitBinder와 같이 사용하여 더 다양하게 활용할 수 있음
- ex) @ControllerAdvice, @RestControllerAdvice의 범위에 포함되는 요청 시, 매번 입력되는 자료를 변환 및 Model 추가
@ModelAttribute
- @ModelAttribute가 적용되는 Controller의 메서드가 실행될 때 모든 요청마다 Model에 값을 담아 넘길 수 있음
@Slf4j
@Controller
public class TestController {
@GetMapping("/model-attribute")
public String home() {
log.info("home 실행");
return "home.html";
}
@ModelAttribute
public void addModel(Model model) {
log.info("Model Attribute 실행");
model.addAttribute("nowTime", LocalDateTime.now());
}
}
결과
- home.html 화면을 return 할 때, home() 메서드에서는 model을 추가하지 않았지만 아래와 같이 값이 잘 넘어오는 것을 확인할 수 있음
@InitBinder
- 주로 검증이나 자료형 변환 등에 사용
- 문자열을 입력받으면 이를 날짜로 변환하는 작업을 @InitBinder을 통해 변환하는 예제 코드
@Slf4j
@Controller
public class TestController {
@Data
private static class InitBinderDto {
private String name;
private Date createdAt;
}
@PostMapping("/init-binder")
@ResponseBody
public String test(@RequestBody InitBinderDto dto) {
log.info("name : {}", dto.getName());
log.info("createdAt : {}", dto.getCreatedAt());
return dto.toString();
}
@InitBinder()
public void initBinder(WebDataBinder dataBinder) {
log.info("Init Binder 실행");
// String -> Date 변환
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dataBinder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}
}
결과
- 문자열 "2023-01-10"을 입력했지만 Date Type으로 변환된 것을 확인할 수 있음
반응형
'Spring Boot > 문법 정리' 카테고리의 다른 글
[Spring Boot] JpaRepository를 활용한 페이징 기능 구현 + 정렬, 검색, 알림창 띄우기를 활용한 예제 (0) | 2023.01.14 |
---|---|
[Spring Boot] JSON을 활용한 API 통신 예제 + JSON 형변환 (Gson, ObjectMapper, JSONParser) (0) | 2023.01.11 |
[Spring Boot] Exception 처리 - 에러 페이지 적용(화면), 에러 코드 적용(API) (0) | 2023.01.09 |
[Spring Boot] Front-End 없이 Jwt 화면 로그인 구현 (1) | 2023.01.07 |
[Spring Boot] Spring Security를 사용한 Jwt Token 로그인 구현 (1) | 2023.01.07 |