<aside> 1️⃣ 모든 예외처리를 한번에 모아서 하는방법
</aside>
@ControllerAdvice
어노테이션을 사용하는 것이 좋다. **@ControllerAdvice
**를 사용하면 여러 컨트롤러에서 발생하는 예외를 한 곳에서 처리할 수 있다.@ControllerAdvice
**와 **@ExceptionHandler
**를 사용하여 전역 예외 처리기(global exception handler)를 구현하는 방법이다.@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object> handleGenericException(Exception e) {
// 여기서 처리하려는 모든 예외 유형을 추가합니다.
// 예외에 대한 자세한 정보를 로깅합니다.
// ResponseEntity 객체를 생성하고 커스텀 오류 메시지와 함께 반환합니다.
// 일반적으로 오류 코드, 오류 메시지, 예외 유형 등을 포함하는 커스텀 오류 응답 클래스를 만들어 사용합니다.
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("message", e.getMessage());
return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 특정 예외 유형을 처리하려면 새로운 메소드를 만들고 @ExceptionHandler 어노테이션을 추가합니다.
@ExceptionHandler(value = {IllegalArgumentException.class})
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException e) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("message", e.getMessage());
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
}
GlobalExceptionHandler
클래스는 애플리케이션 내에서 발생하는 모든 예외를 처리한다. @ExceptionHandler
어노테이션을 사용하여 특정 예외 유형을 처리하는 메소드를 추가할 수 있다.Exception.class
**와 **IllegalArgumentException.class
**를 처리하였지만, 필요에 따라 추가적으로 처리하고 싶은 예외 유형에 대해 @ExceptionHandler
메소드를 추가하여 처리할 수 있다.<aside> 2️⃣ throws Exception 과 @Controller Advice 사용의 차이점
</aside>
**throws Exception
**을 사용하는 방식과 **@ControllerAdvice
**를 사용하는 전역 예외 처리 방식은 몇 가지 중요한 차이점이 있다.
try-catch
블록을 사용하여 예외를 처리하는 경우, 중복된 예외 처리 코드가 여러 곳에 퍼져있게 된다. **@ControllerAdvice
**를 사용하면, 공통 예외 처리 로직을 한 곳에서 관리하므로 코드 중복을 줄일 수 있다.@ControllerAdvice
**를 사용하면, 동일한 예외 유형에 대해 일관된 처리 방식과 응답을 제공할 수 있다.@ControllerAdvice
**를 사용하면, 비즈니스 로직과 예외 처리 로직을 분리할 수 있으므로 코드의 가독성이 향상된다. 또한, 예외 처리 로직이 한 곳에 모여 있으므로 유지보수가 더 쉽다.**@ControllerAdvice
**를 사용하여 특정 에러가 발생했을 때 사용자 지정 에러 코드를 반환하는 것 역시 가능하다. 이를 위해 먼저 사용자 지정 예외 클래스를 정의하고, 해당 예외가 발생했을 때 @ExceptionHandler
메소드에서 이를 처리하도록 설정할 수 있다.
CustomException
이 있다고 가정해 보자. 이 예외는 메시지와 함께 사용자 지정 에러 코드를 가질 수 있다.public class CustomException extends RuntimeException {
private int errorCode;
public CustomException(String message, int errorCode) {
super(message);
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
}
@ControllerAdvice
클래스에서 **CustomException
**을 처리하도록 설정할 수 있다.@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {CustomException.class})
public ResponseEntity<Object> handleCustomException(CustomException e) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("message", e.getMessage());
body.put("errorCode", e.getErrorCode());
return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST);
}
// 다른 예외 처리 메소드...
}
CustomException
**이 발생하면 사용자 지정 에러 코드와 함께 응답을 반환하도록 설정할 수 있다. 이는 클라이언트가 에러 코드를 통해 예외 상황을 더 정확하게 이해하고 적절하게 대응할 수 있도록 도와준다.<aside> 3️⃣ 중앙처리식으로 예외처리를하면 코드에서 try-catch, throws Exception을 빼도되는가?
</aside>
**'검사 예외(checked exception)'**와 **'비검사 예외(unchecked exception)'**라는 두 가지 유형의 예외를 다루는 방식에 따라 달라진다.
IOException
, SQLException
등과 같이 주로 복구 가능한 조건을 나타낸다. 이 예외들은 try-catch 블록으로 잡거나, 메소드가 throws
키워드를 사용해 해당 예외를 선언해야 한다.RuntimeException
클래스와 그 하위 클래스들(NullPointerException
, ArrayIndexOutOfBoundsException
등)이 비검사 예외에 속한다. 이 예외들은 코드의 실수나 문제로 인해 발생하므로, 이러한 예외를 방지하기 위한 적절한 코드 검사와 검증이 필요하다.NullPointerException
이 발생하고 이를 잡지 않았다.@RequestMapping("/uncheckedException")
public String uncheckedException() {
String str = null;
str.length(); // NullPointerException 발생
return "success";
}
FileNotFoundException
이 발생합니다.@RequestMapping("/checkedException")
public String checkedException() throws Exception {
FileInputStream in = new FileInputStream("non_existing_file.txt"); // FileNotFoundException 발생
return "success";
}
@ControllerAdvice
**에 의해 처리될 수 있다.@ControllerAdvice
로 전달하지만, 검사 예외는 반드시 throws
키워드로 선언하거나 try-catch
로 잡아야 한다.try-catch
**의 사용을 줄이고 예외 처리를 명확하게 할 수 있다. 이런 방식으로 코드를 작성하면, **@ControllerAdvice
**에서 비검사 예외를 잡아 일관되게 처리할 수 있다.<aside> 4️⃣ 검사 예외를 비검사 예외로 감싸서 던진다는것은 무엇인가?
</aside>
try-catch
블록을 사용하거나 메서드 선언에 **throws
**를 사용해 해당 예외를 던질 수 있다는 것을 명시해야 한다.@ControllerAdvice
등)에서 집중적으로 관리할 수 있게 된다.public void someMethod() {
try {
// 검사 예외를 던지는 메서드
methodThatThrowsCheckedException();
} catch (CheckedException e) {
throw new RuntimeException(e); // 비검사 예외로 변환하여 던짐
}
}
methodThatThrowsCheckedException()
**는 검사 예외를 던지는 메서드이다. someMethod()
내에서 이 메서드를 호출할 때는 try-catch
블록으로 예외를 처리하고 있다. 그러나 catch
블록에서는 단순히 예외를 처리하는 대신, 발생한 검사 예외를 새로운 **RuntimeException
**으로 감싸서 다시 던지고 있다.@ControllerAdvice
**에서 잡아서 일관되게 처리할 수 있다.<aside> 5️⃣ 검사 예외를 비검사 예외로 변환해서 던졌는데 @ControllerAdvice를 안쓴다면 어떻게될까?
</aside>
@ControllerAdvice
**를 통해 전역적으로 예외를 처리할 수 있다. **@ControllerAdvice
**가 없으면, 비검사 예외는 Spring에서 기본적으로 제공하는 예외 처리 메커니즘에 의해 처리된다. 이는 대부분의 경우에 있어서 유용하지만, 보다 세밀한 예외 처리가 필요한 경우에는 부족할 수 있다.@ControllerAdvice
없이 비검사 예외를 던져도 Spring 프레임워크는 해당 예외를 처리하게 된다. 그러나 이는 Spring의 기본적인 예외 처리 메커니즘이 적용되며, 이는 일반적으로 HTTP 500 (Internal Server Error) 응답을 반환하게 된다.@ControllerAdvice
**를 사용하면, 예외 처리를 한 곳에서 중앙 집중적으로 관리하면서 더 세밀한 제어를 할 수 있다.@ControllerAdvice
**를 통해 HTTP 400 (Bad Request)와 함께 상세 메시지를 클라이언트에게 보내는 등의 상황에 유용하게 사용할 수 있다.@ControllerAdvice
**를 사용하지 않더라도 Spring 프레임워크는 비검사 예외를 잡아 처리하므로, 애플리케이션 자체가 중단되지는 않는다. 다만 이 경우, 예외에 대한 세밀한 처리가 어려울 수 있다.@ControllerAdvice
**를 사용하는 것은, 예외 처리를 더욱 세밀하게 제어하고, 에러 응답을 사용자 정의하며, 예외 처리 코드를 중앙화하여 관리하기 용이하게 하는 등의 장점이 있다.<aside> 6️⃣ 그럼 500에러를 안띄우도록 하려면 어떻게 해야할까?
</aside>
500 Internal Server Error
**를 보내는 대신 다른 동작이 필요하다면, **@ControllerAdvice
**를 이용해서 그 예외를 처리하도록 할 수 있다. 이런 방식을 통해 예외 처리를 세밀하게 제어하고, 사용자 정의한 응답을 반환할 수 있다.@ControllerAdvice
안에서 예외를 처리하게 되면, 예외에 따라 다른 HTTP 응답 코드를 반환하거나, 추가적인 에러 메시지를 제공하거나, 특정 페이지로 리다이렉트하거나 하는 등의 커스텀 동작을 정의할 수 있다.@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = { SomeBusinessException.class })
public ResponseEntity<Object> handleSomeBusinessException(SomeBusinessException ex) {
// 여기서 상태 코드를 정의하고 메시지를 설정하고, 필요한 추가 정보를 제공할 수 있습니다.
// 이 경우에는 400 Bad Request와 함께 에러 메시지를 보냅니다.
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), "Some business rule has been violated");
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
SomeBusinessException
**이 발생하면 항상 400 Bad Request와 함께 지정한 에러 메시지가 반환된다. 이렇게 하면 원하는 대로 에러 처리를 할 수 있으며, 500 Internal Server Error를 방지할 수 있다.<aside> 7️⃣
만약 500에러가 난다면 내가 지정한 커스텀 에러페이지(jsp, html)로 어떻게 보낼까?
</aside>
@ControllerAdvice
**를 이용해 예외 발생 시에 특정 페이지를 보여주는 것이 가능하다. 예외가 발생했을 때 특정 뷰로 리다이렉트하거나 포워드하는 등의 작업을 수행할 수 있다.error
"라는 이름의 뷰를 반환하도록 할 수 있다@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = { Exception.class })
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
Exception.class
가 발생하면 "error
"라는 이름의 뷰를 보여주도록 설정하였다. 이때 **error
**는 실제로 뷰 리졸버가 찾을 수 있는 뷰의 이름이어야 한다. (예를 들어, Thymeleaf를 사용한다면 src/main/resources/templates/error.html
파일이 존재해야 한다.)<aside> 8️⃣ SQL관련 오류(예외)가 발생했을때 적용가능한 @ControllerAdvice
</aside>
SQLException
**이 발생하면, 해당 예외를 처리하는 @ExceptionHandler
메서드를 통해 처리할 수 있다.SQLException
**은 **검사 예외(Checked Exception)**이다. 따라서 일반적으로 이를 직접 던지지는 않는다. 대신에 Spring은 **SQLException
**을 **DataAccessException
**으로 변환하여 던지게 된다. 이는 Spring JDBC에서 일관된 예외 계층구조를 제공하기 위함이다.SQLException
**을 처리하고자 한다면, DataAccessException
또는 그 하위 클래스를 처리하는 @ExceptionHandler
메서드를 작성해야 한다@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = { DataAccessException.class })
public ModelAndView handleSQLException(HttpServletRequest req, DataAccessException e) {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
SQLException
**이 발생하여 **DataAccessException
**으로 변환되었을 때, 해당 예외를 처리하는 @ExceptionHandler
메서드가 호출되어 예외 처리를 수행하게 된다.<aside> 9️⃣ SQL관련 에러가 발생했을때 어떤 계층에서 예외를 잡아서 던져주는게 좋을까?
</aside>
DAO
**에서 SQLException
같은 검사 예외가 발생하면 해당 예외를 비검사 예외로 감싸서 상위 레이어로 던질 것이다.Service
레이어를 거쳐 **Controller
**까지 전파될 것이며, 이 과정에서 추가적인 예외 처리 로직이 없다면 변경 없이 그대로 전달될것이다.Controller
**에서는 **@ControllerAdvice
**가 적용된 클래스에 의해 이 예외가 캐치되고 처리된다. 이렇게 중앙 집중식 예외 처리를 수행하면, 애플리케이션 전체에서 일관된 예외 처리를 할 수 있어 유지보수성이 좋아지며, 코드 중복도 줄일 수 있다.자세한 설명
DAO
**에서 비검사 예외로 변환하여 던진 예외는 그대로 Service
레이어를 거쳐 **Controller
**까지 전파된다. 이 과정에서 **Service
**나 **Controller
**에서 추가적으로 try-catch나 throws Exception을 작성하지 않아도 된다.
Service
**나 **Controller
**에서 특별히 예외를 처리해야 하는 로직이 없다면, 예외를 잡아서 다시 던질 필요는 없다. 비검사 예외는 자동으로 호출 스택을 올라가면서 최상위까지 전파되기 때문에, 특별히 예외를 처리하거나 전달하는 로직을 추가로 작성할 필요가 없다.@ControllerAdvice
**를 사용하는 중앙 집중식 예외 처리의 이점 중 하나이다.@ControllerAdvice
**가 적용된 클래스는 스프링 프레임워크에 의해 자동으로 예외를 처리하는 역할을 하게 된다. 따라서 **Controller
**에서는 별도로 @ControllerAdvice
클래스로 예외를 전달하는 로직을 작성할 필요가 없다.Controller
**에서 발생한 예외는 스프링 프레임워크에 의해 자동으로 적절한 @ExceptionHandler
메서드를 가진 @ControllerAdvice
클래스로 전달된다. 이 과정에서 Controller
클래스는 @ControllerAdvice
클래스와는 독립적으로 작동하며, 서로 간에 직접적인 연결이나 호출은 발생하지 않는다.Controller
**에서 예외를 잡아서 별도의 처리를 하거나 다른 예외로 변환하여 던질 수는 있다. 이렇게 되면 @ControllerAdvice
클래스는 **Controller
**에서 던진 새로운 예외를 받게 된다. 그러나 일반적인 경우에는 이러한 추가적인 처리 없이 **Controller
**에서 발생한 예외를 그대로 @ControllerAdvice
클래스가 받아서 처리하게 된다.