Springboot를 공부해나가고 부트캠프에서 예외처리 관련 과제를 받아 진행을 하다보니 throw를 통한 예외처리가 아닌 직접 커스텀하여 중복되는 코드를 줄여나갈 수 있는 방법을 기록하려 합니다.
이 글을 Spring 공식 페이지를 기반으로 작성하였습니다.
(하단에 나와있는 코드는 Spring 공식 홈페이지의 코드입니다.)
가장 처음 작성해볼 방법은 예외 클래스나 컨트롤러 메서드에 HTTP 상태 코드를 명시적으로 지정할 수 있도록 해주는 어노테이션 @ResponseStatus를 통해서 예외처리를 하는 방법입니다.
1. @ResponseStatus
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
public class OrderNotFoundException extends RuntimeException {
// …
}
@RequestMapping(value="/orders/{id}", method=GET)
public String showOrder(@PathVariable("id") long id, Model model) {
Order order = orderRepository.findOrderById(id);
if (order == null) throw new OrderNotFoundException(id);
model.addAttribute(order);
return "orderDetail";
}
일반적으로 웹 요청 처리 중 처리되지 않은 예외가 발생하게 되면 서버는 HTTP 500 응답을 반환하기 때문에 이를 통해 구분하여 사용자에게 반환하고 서버에 로그를 남길 수 있을 것입니다. 위와 같은 코드에서는 예외 상황에서 404에러를 반환하게 될 것입니다.
1. RuntimeException을 상속받은 예외 클래스는 내부적으로 생성자 내부에서 super("~예외입니다."); 이런식으로 처리를 하기 때문에 고정되어 있는 응답이라는 단점이라면 단점이 존재합니다.
2. @ExceptionHandler
@Controller
public class ExceptionHandlingController {
// @RequestHandler methods
...
// Exception handling methods
// Convert a predefined exception to an HTTP Status code
@ResponseStatus(value=HttpStatus.CONFLICT,
reason="Data integrity violation") // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// Specify name of a specific view that will be used to display the error:
@ExceptionHandler({SQLException.class,DataAccessException.class})
public String databaseError() {
// Nothing to do. Returns the logical view name of an error page, passed
// to the view-resolver(s) in usual way.
// Note that the exception is NOT available to this view (it is not added
// to the model) but see "Extending ExceptionHandlerExceptionResolver"
// below.
return "databaseError";
}
// Total control - setup a model and return the view name yourself. Or
// consider subclassing ExceptionHandlerExceptionResolver (see below).
@ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
위 방법은 @ExceptionHandler를 통한 예외처리입니다.
사실 커스텀해서 나타내기 위해서는 예외클래스를 만들어야 하는 것은 동일합니다. 그렇지만 @ExceptionHandler({CustomSecondException1.class, CustomFirstException.class}) 이런식으로 동시에 일어날 수 있는 예외를 구분할 수 있는 것도 가능합니다.
1. Controller단에서만 작동하며 주로 Controller 내부에서 비즈니스 로직은 Service를 통해서 분리되어 있을텐데 그 로직 또한 Controller 내부에서 발생한 것이기 때문에 서비스 내부에서 발생한 예외 또한 잡아줍니다.
2. ReturnType free
3. @ExceptionHandler가 적용된 메서드를 파라미터를 통해 받아올 수 있습니다.
4. 코드량이 조금 더 많습니다.
3. ContollerAdvice
결국 그럼 Controller단 하나하나 에러/예외를 처리해야 하는 이 불편함을 어떻게 해결해야 할까요? Spring은 그 부분도 제시합니다.
@ControllerAdvice
class GlobalControllerExceptionHandler {
@ResponseStatus(HttpStatus.CONFLICT) // 409
@ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
위와 같이 @ControllerAdivce 어노테이션을 사용하여 내부에 위 형식으로 코드를 작성해준다면 전역적으로 에러를 컨트롤할 수 있습니다. 이 부분을 Enum과 예외 커스텀 클래스를 만들어서 사용해보겠습니다.
3-1 ErrorEnum
package com.hyun.scheduler.enums;
import org.springframework.http.HttpStatus;
public enum ErrorEnum {
PASSWORD_MISMATCH(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."),
SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "일정 조회에 실패했습니다.");
private final HttpStatus httpStatus;
private final String errorMessage;
ErrorEnum(HttpStatus httpStatus, String errorMessage) {
this.httpStatus = httpStatus;
this.errorMessage = errorMessage;
}
public HttpStatus getHttpStatus() {
return this.httpStatus;
}
public String getErrorMessage() {
return this.errorMessage;
}
}
3-2 ExceptionClass
package com.hyun.scheduler.domain.dto;
import com.hyun.scheduler.enums.ErrorEnum;
import org.springframework.http.HttpStatus;
public class MyException extends RuntimeException {
private final HttpStatus httpStatus;
public MyException(ErrorEnum errorEnum) {
super(errorEnum.getErrorMessage());
this.httpStatus = errorEnum.getHttpStatus();
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
}
3-3 GlobalExceptionHandler
package com.hyun.scheduler.exception;
import com.hyun.scheduler.domain.dto.MyException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MyException.class)
public ResponseEntity<String> handleMyException(MyException ex) {
return ResponseEntity.status(ex.getHttpStatus()).body(ex.getMessage());
}
}
위와 같이 진행하는 과제 프로젝트에서는 진행을 할 수 있었습니다. Enum을 통해 예외 HttpStatus와 errorMessage를 관리하고 이를 ExceptionClass에서 반환하고 GlobalExceptionHandler에서 전역적으로 예외를 처리해줄 수 있는 로직입니다. 추후 프로젝트에서도 이 방법을 통해서 예외를 가독성 좋게 처리할 수 있다는 생각이 듭니다.
Reference - https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
'SpringBoot' 카테고리의 다른 글
SpringBoot 대용량 데이터 조회 (Index) (1) | 2025.07.03 |
---|---|
AOP 탐구 (0) | 2025.07.01 |
JPA 영속성 파헤치기 (0) | 2025.06.28 |
SpringEvent / EDA (2) | 2025.06.24 |
Spring Security 없이 로그인을 하는 방법 (Session) (0) | 2025.05.07 |