토비의 스프링 Vol 1 스프링의 이해와 원리 및 https://mangkyu.tistory.com/204 을 참고하였습니다
예외란?
예외는 프로그램의 정상적인 실행 흐름을 방해하는 이벤트로, 런타임(실행 시간) 중에 발생합니다. 프로그램이 처리할 수 없거나 예상하지 못한 상황에 직면했을 때 생성됩니다.
예외의 종류와 특징
자바 개발자들 사이에서 가장 큰 이슈는 체크 예외(checked exception) 라고 불리는 명시적 처리가 필요한 예외를 사용하고 다루는 방법이다. 자바에서 throw를 통해 발생시킬 수 있는 예외는 크게 세 가지가 있다.
1. java.lang.Error
시스템 자체의 비 정상적인 상황이 발생하는 경우이다. 이는 주로 Java Virtual Machine에서 발생하고, 애플리케이션 레벨에서 잡으려고 하면 단된다. 따라서 이 Error들은 신경쓰지 않아도 된다(나아가 신경쓰면 안된다고 볼 수도 있다. 다만 이는 상황에 따라서 달라서 주의 요함).
2. java.lang.Exception과 체크 예외
Exception과 그 서브클래스로 정의되는 예외들은 에러와 달리 개발자들이 만든 애플리케이션에서 발생한 예외에 사용된다. Exception클래스는 체크 예외와 언체크 예외(unchecked exception) 로 나뉜다. RuntimeException을 상속한 클래스들을 언체크 예외라고 부른다.

체크 예외가 발생할 수 있는 메서드를 사용할 경우 반드시 예외를 처리하는 코드를 함께 작성해야 한다. 사용할 메서드가 체크 예외를 던진다면 이를 catch문으로 잡던지, 아니면 다시 throws를 정의해서 메서드 밖으로 던저야 한다. 그렇지 않으면 컴파일 에러가 발생한다.
3. RuntimeException과 언체크/런타임 예외
java.lang.RuntimeException 클래스를 상속한 예외들은 명시적인 예외처리를 강제하지 않기에 언체크 예외라고 불린다. 또한 대표 클래스 이름을 본따 런타임 예외라고도 불린다. 런타임 예외는 보통 프로그램의 오류가 있을 때 발생하도록 의도된 것이다. 예를 들어 초기화하지 않은 레퍼런스 변수를 사용할 때 발생하는 NullpointerException, 허용되지 않는 값을 사용하는 IllegalArgumentException 등이 있다.
피할 수 있지만 개발자가 부주의해서 발생할 수 있는 경우에 발생하도록 만든 것이 런타임 예외이다. 따라서 예상치 못한 예외상황 발생이 아니기 때문에 굳이 catch나 throws를 사용하지 않아도 되도록 만든 것이다.
자바 언어와 JDK의 초기 설계자들은 체크 예외를 발생 가능한 예외에 모두 적용 하려고 했던 것 같다. 그래서 lOException이나 SQLException을 비롯해서 예외적인 상황에서 던져질 가능성이 있는 것들 대부분이 체크 예외로 만들어져 있다.
그러나 이런 의도는 현실과 잘 맞지 않았고, 심지어는 비난의 대상이 되기도 했다. 체크 예외가 예외처리를 강제하는 것 때문에 무한 throws 같은 코드가 남발됐다. 최근에 새로 등장하는 자바 스펙의 API들은 대부분 체크 예외로 만들지 않는 경향이 있다.
4. 예외 처리 방법
4.1. 예외 복구
예외상황을 파악하고 문제를 해결해서 정상 상태로 되돌려놓는 것
예를 들어 파일을 읽으려 하는데 파일이 없는 상황을 생각해보자. 이때 IOException라는 체크 예외가 발생한다. 이때 사용자에게 상황을 알려주고 다른 파일을 이용하도록 한내해서 예외상황을 해결할 수 있다. 다른 작업 흐름으로 자연스럽게 유도해주는 것이다. 이런 경우 예외상황은 다시 정상으로 돌아오고 예외를 복구했다고 볼 수 있다. 하지만, IOException 달랑 던져주는 건 예외복구라고 할 수 없다.
예외처리를 강제하는 체크 예외들은 이렇게 예외를 어떤 식으로든 복구할 가능성이 있는 경우에 사용된다.
4.2. 예외처리 회피
두 번째 방법은 예외처리를 자신이 담당하지 않고 자신을 호출한 쪽으로 던져버리는 것이다. 아래 두 예시를 보자.
p니blic void add() throws SQLException {
// JDBC API
}
public void add() throws SQLException {
try {
// ODBC API
}
catch(SQLException e) {}
// 로그 출력
throw e;
}
}
JdbcContext나 JdbcTemplate이 사용하는 콜백 오브젝트는 모든 SQLException을 템플릿으로 던져버린다. 이는 SQLException을 처리하는 일은 콜백 오브젝트의 역할이 아니라고 보기 때문이다. 하지만 콜백과 템플릿정도의 긴밀한 관계가 아니라면, 예외를 throws하는 것은 신중해야 할 수도 있다. 만일 DAO에서 SQLException을 던진다면, 이를 서비스나 컨트롤러 레이어에서 잘 처리할 수 있을까? 코드의 품질은?
예외를 회피하는 것은 예외를 복구하는 것처럼 의도가 분명해야 한다. 콜백/템플릿처럼 긴밀한 관계에 있는 다른 오브젝트에게 예외처리 책임을 분명히 지게 하거나, 자신을 사용하는 쪽에서 예외를 다루는 게 최선 이라는 확신이 있어야 한다.
4.3. 예외 전환
마지막 방법은 예외를 전환하는 것이다. 예외 회피와 비슷해보이지만, 발생한 예외를 그대로 넘기는 것이 아니라 적절한 예외로 전환해서 던진다는 차이가 있다. 예외 전환은 다음 두 가지 목적이 있다.
4.3.1. 예외 의미 추가 필요
내부에서 발생한 예외를 그대로 던지는 것이 그 예외상황에 대한 적절한 의미를 부여해주지 못하는 경우에, 의미를 분명하게 해줄 수 있는 예외로 바꿔주기 위해서다.
예를 들어 새로운 사용자 등록 과정에서 아이디 중복으로 실패하는 경우, 단순히 SQLException만 던진다면 외부에선 어떤 이유로 발생하였는 지 알 수가 없다. 이럴때는 DAO 등에서 DuplicatieUserIdException 등으로 바꿔서 던지는 것이 복구 가능성(다른 ID로 사용자 등록 시도)들을 높여준다.
이때 중첩 예외로 만들어서 던지는 것이 좋다. 중첩 예외는 getCause()메서드를 이용해서 처음 발생한 예외가 무엇인지 확인할 수 있고 initCause를 통해서 중첩 예외를 만들 수 있다. 자세한 예시는 아래 코드를 보자.
// 사용자 정의 예외 클래스
class DatabaseException extends Exception {
public DatabaseException(String message) {
super(message);
}
public DatabaseException(String message, Throwable cause) {
super(message, cause);
}
}
class ServiceException extends Exception {
public ServiceException(String message) {
super(message);
}
public ServiceException(String message, Throwable cause) {
super(message, cause);
}
}
// 데이터베이스 접근 클래스
class DatabaseService {
public void connectToDatabase() throws DatabaseException {
try {
// 데이터베이스 연결 시뮬레이션 - 실패 상황
throw new RuntimeException("네트워크 연결 실패");
} catch (RuntimeException e) {
// 원본 예외를 원인으로 하는 새로운 예외 생성
throw new DatabaseException("데이터베이스 연결에 실패했습니다", e);
}
}
public String getData() throws DatabaseException {
connectToDatabase();
return "데이터";
}
}
// 비즈니스 로직 클래스
class UserService {
private DatabaseService dbService = new DatabaseService();
public String getUserInfo(int userId) throws ServiceException {
try {
return dbService.getData();
} catch (DatabaseException e) {
// 하위 계층의 예외를 잡아서 상위 계층의 예외로 변환
throw new ServiceException("사용자 정보를 가져올 수 없습니다: " + userId, e);
}
}
}
public class NestedExceptionExample {
public static void main(String[] args) {
UserService userService = new UserService();
try {
userService.getUserInfo(123);
} catch (ServiceException e) {
System.out.println("=== 예외 정보 출력 ===");
System.out.println("최상위 예외: " + e.getMessage());
// 중첩된 예외들을 순서대로 출력
Throwable cause = e.getCause();
int level = 1;
while (cause != null) {
System.out.println("원인 예외 " + level + ": " + cause.getMessage());
System.out.println("예외 타입: " + cause.getClass().getSimpleName());
cause = cause.getCause();
level++;
}
System.out.println("\n=== 전체 스택 트레이스 ===");
e.printStackTrace();
}
// initCause() 메서드를 사용한 예외 체이닝 예시
demonstrateInitCause();
}
public static void demonstrateInitCause() {
System.out.println("\n=== initCause() 메서드 사용 예시 ===");
try {
Exception originalException = new RuntimeException("원본 예외");
Exception wrappedException = new Exception("래핑된 예외");
Exception wrappedException2 = new Exception("2래핑된 예외");
// initCause()를 사용하여 원인 예외 설정
wrappedException.initCause(originalException);
wrappedException2.initCause(wrappedException);
throw wrappedException2;
} catch (Exception e) {
System.out.println("예외 메시지: " + e.getMessage());
System.out.println("1차 원인 예외: " + e.getCause().getMessage());
System.out.println("2차 원인 예외: " + e.getCause().getCause().getMessage());
// 예외 체인 확인
System.out.println("예외 체인 존재: " + (e.getCause() != null));
}
}
}
현재 2가지를 한번에 테스트하고 있다. 첫 번째는 실제 UserService를 사용할때 오류가 발생한다면 사용자 정의 예외(DatabaseException)로 바꾸면서 이를 중첩 예외처리하는지 확인한다.
두 번째는 Exception 속 Exception 속 RuntimeException 구조를 만들어 테스트한다. 이러한 구조를 통해 사용자 예외를 사용하는 것과 동시에 실제로 어떤 예외사항이 발생했는지 직접 확인하는 두 가지를 동시에 만족시킬 수 있다.
4.3.2. 예외 처리 단순화
두 번째 전환 목적은 예외 처리 단순화에 있다. 중첩 예외를 이용해 새로운 예외를 만들고, 원인이 되는 예외를 내부에 담아서 던지는 방식은 같다. 여기서는 의미를 명확하게 하려는 목적이 아니라 예외처리를 강제하는 체크 예외를 언체크 예외인 런타임 예외로 바꾸는 경우에 사용한다.
5. 예외 처리 전략
5.1. 런타임 예외의 보편화
이 글을 보는 대부분은 자바라는 언어를 스프링 생태계를 사용하기 위한 수단으로 사용할 것이다. 따라서 중요한 문제가 생겼다고 해도 이를 체크 예외로 처리해서 서버(시스템) 전체를 멈춰버리게 하는것은 굉장히 큰 리스크이기도 하고, 서버를 일시 중지하고 관리자가 예외 사항을 곧바로 고치는 것도 거의 불가능한 상황이다.
따라서 '차라리 애플리케이션 차원에서 예외상황을 미리 파악하고, 예외가 발생하지 않도록 차단하자'라는 생각을 하게 되었고, 이를 위해 런타임 예외를 보편적으로 적용하는 방식으로 바뀌었다.
따라서 위에서 언급했다시피 API에서 언체크 예외를 발생시키는 추세로 바뀌고 있다.
5.2. 애플리케이션 예외
런타임 예외 중심의 전략은 굳이 이름을 붙이자면 '낙관적인 예외처리 기법' 이라고 할 수 있다. 오류가 발생해도 처리할 수 있겠지... 서비스에 문제 없겠지... 하는 낙관적인 시선에서 비롯되었기 때문이다.
반면 시스템 또는 외부의 예외상황이 원인이 아니라 애플리케이션 자체 로직에 의한 의도적이고 반드시 catch해서 무엇인가 조치를 취하도록 요구하는 예외도 있다. 이런 예외들을 일반적으로 애플리케이션 예외라고 한다. 예를들어 은행 프로그램에서 잔고가 출금 요청 금액보다 적을 때 발생하는 오류 등이 이에 해당한다. 이때는 출금 작업을 중단하고, 사용자에게 적절한 경고를 반환해야 한다.
이런 기능을 담은 메서드를 설계하는 방법이 2가지 있다.
- 정상적인 출금처리를 했을 경우와 잔고 부족이 발생했을 경우 각각 다른 종류의 리턴값을 돌려주는 것.
예를들어 정상 처리인 경우엔 요청 금액을 반환하고, 잔고 부족인 경우 0 또는 -1등 특정 값을 리턴하는 것이다. 하지만 이러한 특수한 내용을 추가하는 것은 지속가능한 코드 관점에서 굉장히 위험하다. 추후에 수정/개선/보완 작업을 할 때, 이부분에서 혼란이 올 수 있는 등... 여러 문제가 많다. - 정상적인 흐름을 따르는 코드는 그대로 두고, 잔고 부족과 같은 예외상황에서는 비즈니스적 의미를 띄는 예외를 만드는 것.
예를 들어, 잔고 부족인 경우 InsufficientBalanceException을 반환하는 것이다. 이때 사용하는 예외는 의도적으로 체크 예외로 만든다. 그래서 개발자들이 잊지 않고 잔고 부족처럼 자주 발생 가능한 예외상황에 대한 로직을 구현하도록 강제해주는 게 좋다.
6. 그래서 SQLException은??
이제 SQLException을 다시 살펴보자.
6.1 복구가 가능한 오류인가?
99% 확률로 SQLException은 코드 레벨에서 복구가 불가능하다. 발생하는 이유는 프로그램의 오류 또는 개발자 부주의 또는 통제할 수 없는 외부상황(인터넷 접속 불안정 등)이다. 시스템 예외라면 당연히 애플리케이션 레벨에서 복구할 방법이 존재하지 않는다.
6.2. 스프링의 JdbcTemplate
스프링의 JdbcTemplate은 이런 전략을 사용하고 있다. 모든 SQLException을 런타임 예외인 DataAccessException으로 포장해서 던진다. 그래서 DAO에서 throws SQLException이 모두 사라진 것이다. 그 밖에도 스프링 API 메서드에 정의되어 있는 대부분의 예외는 런타임이다. 따라서 이를 처리하도록 강제하지 않는다.
7. 스프링의 예외 처리 방법
Spring Boot는 다음과 같은 순서로 에러를 처리한다
예외 발생 →
DispatcherServlet→HandlerExceptionResolver→ErrorController→ 에러 응답
7.1. 기본 예시를 통한 처리 방법 익히기
다음 Controller를 보고, 어느 부분에서 에러를 처리하는지 확인해보자.
@RestController
public class MainController {
@GetMapping("/e")
public String e() throws Exception {
throw new Exception();
}
@GetMapping("/e2")
public String e2() throws Exception {
throw new RuntimeException();
}
@GetMapping("/e3")
public String e3() throws Exception {
throw new ResponseStatusException
(HttpStatus.FORBIDDEN, "Item Not Found");
}
@GetMapping("/e4")
public String e4() throws Exception, NoSuchElementFoundException {
throw new NoSuchElementFoundException();
}
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException
(NoSuchElementFoundException exception) {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
.body(exception.getMessage()+1234);
}
}
HandlerExceptionResolver 체인
Spring Boot는 여러 ExceptionResolver를 순서대로 실행한다:
- ExceptionHandlerExceptionResolver
@ExceptionHandler어노테이션이 붙은 메서드를 찾아 실행(/e4에 해당한다)- 컨트롤러 내부 또는
@ControllerAdvice에 정의된 핸들러
- ResponseStatusExceptionResolver
@ResponseStatus어노테이션이나ResponseStatusException처리
- DefaultHandlerExceptionResolver
- Spring MVC의 기본 예외들을 HTTP 상태 코드로 변환
ErrorController 체인
/e,/e2,/e3에 해당한다
Spring Boot는 기본적으로 BasicErrorController를 제공한다:
/error엔드포인트로 모든 에러를 처리- JSON 또는 HTML 응답 지원
error.html템플릿이나error.json응답 생성
여기서 생성되어 반환되는 View가 다음 화면이다
![[Pasted image 20250907211717.png]]
이에 반해,
/e4의 경우 텍스트만 반환된다.

엔드포인트들에 대한 처리 흐름을 다시한번 정리하면 다음과 같다.
/e, /e2, /e3 - Spring Boot 기본 처리
처리 흐름:
1. Exception/RuntimeException/ResponseStatusException 발생
2. 해당 예외에 대한 @ExceptionHandler 없음 확인
3. Spring Boot의 BasicErrorController 작동
4. /error 경로로 내부 포워딩
5. ErrorViewResolver가 뷰 생성
6. Whitelabel Error Page 렌더링
/e4 - @ExceptionHandler로 처리되는 경우
처리 흐름:
1. NoSuchElementFoundException 발생
2. Spring이 @ExceptionHandler 스캔
3. handleNoSuchElementFoundException() 메서드 실행
4. ResponseEntity<String> 직접 반환
5. 끝 (기본 오류 처리 거치지 않음)
7.2. HandlerExceptionResolver 구현체들
예외가 던져지면 디스패처 서블릿까지 전달되는데, 적합한 예외 처리를 위해 HandlerExceptionResolver 구현체들을 빈으로 등록해서 관리한다. 그리고 적용 가능한 구현체를 찾아 예외 처리를 하는데, 우선순위대로 아래의 4가지 구현체들이 빈으로 등록되어 있다.
- DefaultErrorAttributes: 에러 속성을 저장하며 직접 예외를 처리하지는 않는다.
ExceptionHandlerExceptionResolver: 에러 응답을 위한Controller나ControllerAdvice에 있는ExceptionHandler를 처리함ResponseStatusExceptionResolver: Http 상태 코드를 지정하는@ResponseStatus또는ResponseStatusException를 처리함DefaultHandlerExceptionResolver: 스프링 내부의 기본 예외들을 처리한다.
Spring은 아래와 같은 도구들로 ExceptionResolver를 동작시켜 에러를 처리할 수 있다
@ResponseStatus
에러 HTTP의 상태를 변경하도록 도와주는 어노테이션이다. @ResponseStatus는 다음과 같은 경우들에 적용할 수 있다.
- Exception 클래스 자체
- 메소드에 @ExceptionHandler와 함께
- 클래스에 @RestControllerAdvice와 함께
위 예시 코드에서, /e4의 커스텀 예외 대해 다음과 같이 수정하고 재접속시 Http 상태가 바뀐 걸 볼 수 있다.
@ResponseStatus(value = HttpStatus.REQUEST_TIMEOUT)
public class NoSuchElementFoundException extends Exception {
}
@GetMapping("/e4")
public String e4() throws Exception{
throw new NoSuchElementFoundException();
}
하지만 이러한 방식에는 한계가 있다. 에러 응답에서 볼 수 있듯이 이는 BasicErrorController에 의한 응답이다. 즉, @ResponseStatus를 처리하는 ResponseStatusExceptionResolver는 WAS까지 예외를 전달시키며, 복잡한 WAS의 에러 요청 전달이 진행되는 것이다. 이러한 @ResponseStatus는 다음과 같은 한계점들을 가지고 있다.
- 에러 응답의 내용(Payload)를 수정할 수 없음(DefaultErrorAttributes를 수정하면 가능하긴 함)
- 예와 클래스와 강하게 결합되어 같은 예외는 같은 상태와 에러 메세지를 반환함
- 별도의 응답 상태가 필요하다면 예외 클래스를 추가해야 됨
- WAS까지 예외가 전달되고, WAS의 에러 요청 전달이 진행됨
- 외부에서 정의한 Exception 클래스에는 @ResponseStatus를 붙여줄 수 없음
ResponseStatusException
@ResponseStatus의 프로그래밍적 대안으로써 손쉽게 에러를 반환할 수 있는 ResponseStatusException가 추가되었다. ResponseStatusException는 HttpStatus와 함께 선택적으로 reason과 cause를 추가할 수 있고, 언체크 예외을 상속받고 있어 명시적으로 에러를 처리해주지 않아도 된다.
@GetMapping("/e5")
public String e5(){
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Item Not Found");
}
ResponseStatusExceptionResolver가 처리하게 된다. 또한 @ResponseStatus 대비 장점은 다음과 같다.
- 신속한 기본적인 예외 처리
- HttpStatus를 직접 설정하여 예외 클래스와의 결합도 저하
- 불필요하게 많은 별도의 예외 클래스 만들지 않아도 됨
하지만 그럼에도 불구하고, 직접 예외를 처리한다는 점. 예외 처리가 중복될 수 있다는 점. WAS까지 예외가 도달하긴 한다는 점에서 단점이 명확하다.
@ExceptionHandler
//(지금은) Controller 내부
@GetMapping("/e4")
public String e4() throws Exception{
throw new NoSuchElementFoundException();
}
@ExceptionHandler(NoSuchElementFoundException.class)
public ResponseEntity<String> handleNoSuchElementFoundException
(NoSuchElementFoundException exception) {
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
.body(exception.getMessage()+1234);
}
위와 같은 상황에서, e4에서 발생시키는 사용자 예외에 대한 Handler를 ExceptionHandler로 받은 모습이다. ExceptionHandlerExceptionResolver가 @ExceptionHandler 를 찾아 실행한다.ExceptionHandlerExceptionResolver가 ResponseStatusExceptionResolver보다 우선순위가 높기 때문에,@ExceptionHandler가 앞선 두 방식(@ResponseStatus, ResponseStatusException)보다 우선 적용된다.
추가적인 장점들은 다음과 같다.
- ExceptionHandler는 @ResponseStatus와 달리 에러 응답(payload)을 자유롭게 다룰 수 있다는 점에서 유연하다. 다음과 같은 내용을 쉽게 담을 수 있다.
- code: 어떠한 종류의 에러가 발생하는지에 대한 에러 코드
- message: 왜 에러가 발생했는지에 대한 설명
- erros: 어느 값이 잘못되어 @Valid에 의한 검증이 실패한 것인지를 위한 에러 목록
- 외부 로그 시스템 등에 통합이 용이하다.
- 복잡한 로직 테스트에 용이하다.
Spring은 예외 발생 시 가장 구체적인 예외 핸들러를 먼저 찾고, 없으면 부모 예외 핸들러를 찾는다.
@(Rest)ControllerAdvice
@ControllerAdvice는 Spring Framework에서 전역적인 컨트롤러 설정을 위해 사용되는 어노테이션이다.
1. 전역 예외 처리
- 애플리케이션 전체에서 발생하는 예외를 한 곳에서 처리
@ExceptionHandler와 함께 사용
2. 데이터 바인딩 설정
@InitBinder로 전역 데이터 바인딩 규칙 정의
3. 모델 속성 추가
@ModelAttribute로 모든 컨트롤러에 공통 모델 데이터 추가
이중 1.전역 예외 처리 특성을 활용해, 예외처리에 대해 관리할 수 있다.
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementFoundException.class)
protected ResponseEntity<?> handleNoSuchElementFoundException(NoSuchElementFoundException e) {
final ErrorResponse errorResponse = ErrorResponse.builder()
.code("Item Not Found")
.message(e.getMessage()).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}
장점은 다음과 같다.
- 중앙집중식 예외 처리: 코드 중복 제거
- 일관성: 애플리케이션 전체에서 동일한 예외 처리 방식
- 유지보수성: 예외 처리 로직 변경 시 한 곳만 수정
- 관심사 분리: 비즈니스 로직과 예외 처리 분리
스프링 예외에는 대표적으로 잘못된 URI를 호출하여 발생하는 NoHandlerFoundException 등이 있다. Spring은 스프링 예외를 미리 처리해둔 ResponseEntityExceptionHandler를 추상 클래스로 제공하고 있다. ResponseEntityExceptionHandler에는 스프링 예외에 대한 ExceptionHandler가 모두 구현되어 있으므로 ControllerAdvice 클래스가 이를 상속받게 하면 된다.
만약 이 추상 클래스를 상속받지 않는다면 스프링 예외들은 DefaultHandlerExceptionResolver가 처리하게 되는데, 그러면 예외 처리기가 달라지므로 클라이언트가 일관되지 못한 에러 응답을 받지 못하므로 ResponseEntityExceptionHandler를 상속시키는 것이 좋다. 또한 이는 기본적으로 에러 메세지를 반환하지 않으므로, 스프링 예외에 대한 에러 응답을 보내려면 아래 메서드를 오버라이딩 해야 한다.
public abstract class ResponseEntityExceptionHandler {
...
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
...
}
}
ControllerAdvice는 스프링 전역(물론 특정 패키지에 제한하고 싶다면 옵션을 걸 수 있다)에 적용되기 때문에, 아래 조건을 지키며 사용하는 것을 추천한다.
- 한 프로젝트당 하나의 ControllerAdvice만 관리하는 것이 좋다.
- 만약 여러 ControllerAdvice가 필요하다면 basePackages나 annotations 등을 지정해야 한다.
- 직접 구현한 Exception 클래스들은 한 공간에서 관리한다.
'Backend > Spring' 카테고리의 다른 글
| AOP(Aspect Oriented Programming) (0) | 2025.09.24 |
|---|---|
| Spring에서의 테스트 (0) | 2025.09.16 |
| [Security]Spring 소셜로그인 및 JWT 토큰 (0) | 2025.04.27 |
| 스프링 Security (0) | 2025.04.12 |
| [Spring] HTTP 통신구조 기초 이해 (0) | 2025.03.14 |