Spring Boot (+ RESTful)

RESTful 웹 서비스 구축 - @ControllerAdvice / @ExceptionHandler

wy-family 2025. 1. 2. 22:07

간단한 REST API 를 만들어봤었다.

추가적인 REST API 들을 만들어보기 전에, 예외 처리와 관련된 공부를 하고 가자.

프로그램 개발과 예외 처리

프로그램 개발 과정에서 **예외(Exception)**는 예상치 못한 상황에서 자주 발생합니다. 이러한 예외를 처리하는 방법은 다양합니다:

  1. try-catch를 이용한 예외 처리
    개별 코드 블록에서 발생할 수 있는 예외를 직접 처리합니다.
try {
    // 예외 발생 가능성 있는 코드
} catch (Exception ex) {
    // 예외 처리 코드
}
  1. 예외를 호출한 곳으로 전달 (throws)
    특정 메서드에서 발생한 예외를 호출한 메서드로 던질 수 있습니다.
    이를 위해 throws 키워드를 사용합니다. 예외를 직접 처리하지 않고 위임하는 방식입니다.
public void someMethod() throws IOException {
    // IOException 발생 가능
}
  1. 서버(예: Tomcat)에 예외 위임
    Spring MVC에서는 컨트롤러에서 처리되지 않은 예외가 톰캣 서버와 같은 웹 서버로 전달되며, 서버가 해당 예외를 처리하거나 에러 페이지를 반환합니다.

글로벌 예외 처리의 필요성

프로그램 개발 과정에서 각각의 메서드마다 예외를 처리하는 코드를 작성하면 코드가 중복되고 복잡해질 수 있습니다.
이 문제를 해결하기 위해 예외를 한 곳에서 처리하는 전담 처리 메커니즘이 필요합니다.

Spring Boot에서는 이를 위해 글로벌 예외 처리를 제공하며, 이를 구현하는 방법이 바로 @ControllerAdvice입니다.


@ControllerAdvice의 역할

  • 글로벌 예외 처리 컨트롤러
    @ControllerAdvice는 애플리케이션 전반에 걸쳐 발생하는 예외를 처리하기 위한 전용 클래스를 정의할 수 있도록 도와줍니다.
    컨트롤러에서 발생한 예외를 잡아 처리하는 역할을 수행합니다.
  • 컨트롤러의 예외 처리 위임
    특정 컨트롤러에서 발생한 예외를 직접 처리하지 않고, @ControllerAdvice가 적용된 클래스에 위임할 수 있습니다.
    이를 통해 코드 중복을 줄이고 유지보수를 간소화할 수 있습니다.

@ExceptionHandler의 역할

  • @ExceptionHandler는 @ControllerAdvice 클래스 내부에서 사용되며, 특정 예외를 처리하는 메서드를 정의할 때 사용합니다.
  • 하나의 @ControllerAdvice 클래스 안에 여러 개의 @ExceptionHandler 메서드를 정의할 수 있습니다.
    각 메서드는 처리하려는 특정 예외 타입에 대해 동작합니다.

예제: @ControllerAdvice와 @ExceptionHandler

@ControllerAdvice
public class GlobalExceptionHandler {

    // NullPointerException 처리
    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<String> handleNullPointerException(NullPointerException ex) {
        return new ResponseEntity<>("Null pointer exception occurred: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    // IllegalArgumentException 처리
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("Illegal argument: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    // 모든 기타 예외 처리
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleAllExceptions(Exception ex) {
        return new ResponseEntity<>("An error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

핵심 개념 요약

  1. @ControllerAdvice
    • 글로벌 예외 처리 클래스에 사용됩니다.
    • 컨트롤러 계층에서 발생한 예외를 한 곳에서 처리할 수 있도록 지원합니다.
  2. @ExceptionHandler
    • @ControllerAdvice 클래스 안에서 특정 예외를 처리하는 메서드에 붙입니다.
    • 처리하려는 예외 유형에 따라 다양한 메서드를 정의할 수 있습니다.
  3. 코드 중복 감소와 유지보수성 향상
    @ControllerAdvice와 @ExceptionHandler를 사용하면 각 컨트롤러마다 중복 코드를 작성하지 않아도 되며, 예외 처리 로직을 중앙 집중화할 수 있습니다.

보완된 내용: 예외 처리 흐름 이해

  1. 예외 발생
    컨트롤러에서 예외가 발생하면, Spring은 이를 @ControllerAdvice로 전달합니다.
  2. 핸들러 메서드 실행
    발생한 예외의 타입에 따라 매핑된 @ExceptionHandler 메서드가 호출됩니다.
  3. 응답 반환
    @ExceptionHandler 메서드는 클라이언트로 반환할 응답을 생성합니다.

추가적으로 알아두면 좋은 내용

  • 전역 데이터 추가 (@ModelAttribute)
    @ControllerAdvice 클래스에서 공통 데이터를 모델에 추가할 수 있습니다.
@ControllerAdvice
public class GlobalModelAdvice {

    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("appName", "My Spring Application");
    }
}
  • 특정 컨트롤러에만 적용
    @ControllerAdvice는 특정 컨트롤러 또는 패키지에만 적용되도록 제한할 수 있습니다.
@ControllerAdvice(assignableTypes = {UserController.class})
public class SpecificControllerAdvice {
    // 특정 컨트롤러만 처리
}

 

프로그램 개발을 하다보면 예외가 발생하곤 한다.
예외가 발생하면 처리하는 방식은 여러가지가 있다.
try - catch 를 이용해서 예외처리도 가능하고,
어떤 특정 메소드를 실행하면서 에러가 발생하면 쓰루쓰(? 이게 뭔 말이지?)를 이용해서 예외를 톰캣 서버에다가 던져가지고 서버가 예외를 처리를 해주는 방식도 있다.
개발을 하다보면은 각각의 메소드에서 예외들이 발생을 하면은 그 예외를 한 곳에서 별도로 처리를 해주면 좋을 것이다.
예외를 위임받는 무언가가 있으면 좋다. 예외를 처리하는 걸 전담으로 하는 무언가가 있으면 좋다.
메소드가 처리를 할 때 예외가 발생하면 그 예외를 어드바이스(Advice)해줄 수 있는, 위임받아서 처리를 해줄 수 있는 그런 방법이 Spring Boot 에서는 @ControllerAdvice 라는 어노테이션이 기능해준다. 컨트롤러에서 오류가 발생하면 @ControllerAdvice 가 작성되어 있는 클래스가 예외를 처리를 해준다는 것. 일종의 예외를 전역적으로 처리를 해주는 글로벌 예외 전담 컨트롤러인 셈이다.
@ControllerAdvice 가 붙어있는 클래스가 있을 것인데, 예외라는 건 상당히 다양하다. 예외 객체는 상당히 다양하게 존재한다. @ControllerAdvice 가 붙어있는 예외를 처리하는 클래스에서 독립적으로, 개별적으로 예외들을 처리할 수 있도록 하는 것이 @ExceptionHandler 라는 어노테이션이다.
@ControllerAdvice 가 붙어있는 예외를 처리하는 클래스 안에는 여러가지 Methods 가 있을 것이다. 그 Methods 는 각각 '어떤 예외가 발생하면 처리하는 Method' 라는 걸 나타낼 수 있는데 그걸 @ExceptionHandler 가 기능을 해준다. Handler 라는 건, 뭔가를 처리를 해주는 것을 의미한다. 사건이 발생했을 때 해결해주는 걸 Handler 라고 할 수 있다.

 

Spring Framework에서 @ControllerAdvice는 전역 예외 처리, 데이터 바인딩 처리, 모델 객체 조작 등을 제공하기 위해 사용되는 어노테이션입니다. Spring MVC의 컨트롤러 계층과 연동하여 전역적인 설정과 처리를 가능하게 합니다. 이 어노테이션은 특정 컨트롤러에만 국한되지 않고, 애플리케이션 전체 또는 특정 조건에 맞는 컨트롤러에 적용될 수 있습니다.


@ControllerAdvice의 주요 기능

  1. 전역 예외 처리
    특정 예외가 발생했을 때 이를 처리하기 위한 전역 핸들러를 설정할 수 있습니다. 이를 통해 각 컨트롤러에서 반복적으로 예외 처리를 하지 않아도 됩니다.
  2. 전역 데이터 바인딩
    모델에 공통 데이터를 추가하거나 데이터 바인딩 설정을 전역적으로 정의할 수 있습니다.
  3. 전역 모델 조작
    모든 요청에 대해 공통적으로 사용할 모델 데이터를 추가하거나 수정할 수 있습니다.

주요 어노테이션

@ControllerAdvice 내부에서 자주 사용하는 어노테이션은 다음과 같습니다:

1. @ExceptionHandler

특정 예외를 처리하기 위한 메서드를 정의합니다. 컨트롤러 메서드에서 발생하는 예외를 전역적으로 처리할 수 있습니다.

2. @ModelAttribute

모든 컨트롤러 요청에서 공통적으로 사용할 모델 데이터를 정의할 수 있습니다.

3. @InitBinder

데이터 바인딩 및 데이터 유효성 검사를 위한 설정을 제공합니다.


세부적인 동작 원리

  1. 스프링 컨텍스트 내 빈으로 등록
    @ControllerAdvice를 붙인 클래스는 Spring의 Application Context에 빈으로 등록됩니다.
  2. AOP 기반 적용
    @ControllerAdvice는 AOP(Aspect-Oriented Programming)를 활용하여 각 컨트롤러 요청 이전 또는 이후에 동작합니다.
  3. 스코프 제한 가능
    @ControllerAdvice는 특정 컨트롤러 또는 특정 요청 경로에 대해서만 동작하도록 제한할 수 있습니다.

실무에서의 활용

  1. 전역적인 에러 응답 처리
    예를 들어, REST API에서는 전역적으로 JSON 형태의 에러 메시지를 반환하도록 구현할 수 있습니다.
@ControllerAdvice
public class RestExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<Map<String, String>> handleResourceNotFound(ResourceNotFoundException ex) {
        Map<String, String> errorDetails = new HashMap<>();
        errorDetails.put("message", ex.getMessage());
        errorDetails.put("timestamp", LocalDateTime.now().toString());
        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }
}
  1. 모델 데이터의 전역 설정
    템플릿 엔진(예: Thymeleaf)에서 공통적으로 사용되는 데이터를 추가할 때 유용합니다.
  2. 데이터 변환 및 유효성 검사
    사용자 입력 데이터를 특정 형식으로 변환하거나 기본적인 유효성 검사를 전역적으로 수행할 수 있습니다.

장점

  • 코드 중복 감소: 컨트롤러마다 중복된 예외 처리 코드를 줄일 수 있습니다.
  • 유지보수성 향상: 전역적으로 설정된 공통 로직은 수정이 쉽고 관리가 용이합니다.
  • 모듈화: 특정 관심사를 전역 핸들러로 분리하여 코드의 가독성과 구조를 개선합니다.

주의사항

  1. 적용 범위의 명확성
    너무 많은 로직을 전역 핸들러에 추가하면 코드가 복잡해질 수 있으므로 필요한 범위에서만 사용해야 합니다.
  2. 비즈니스 로직 분리
    @ControllerAdvice는 비즈니스 로직이 아닌 예외 처리나 공통 로직에만 초점을 맞춰야 합니다.
  3. 성능 고려
    불필요하게 모든 요청에 대해 동작하도록 설정하면 성능에 영향을 미칠 수 있습니다.

결론

@ControllerAdvice는 Spring MVC 애플리케이션에서 전역적인 처리 로직을 캡슐화하고, 코드 중복을 제거하며, 유지보수성을 높이는 데 매우 유용합니다. 그러나 지나친 사용은 오히려 복잡성을 증가시킬 수 있으므로, 특정 요구사항에 맞춰 적절히 활용하는 것이 중요합니다.


강의에서는, @ControllerAdvice 를 붙인 클래스에 @RestController 도 같이 붙여서,
return 을 ResponseEntity 로 한다고 나와있는데,
@RestController 를 붙여야지만 return 을 ResponseEntity 타입으로 할 수 있는건가?
너가 알려준 예시 코드에는 @RestController 가 없어서 말이야.
아닙니다! @RestController를 붙이지 않아도 @ControllerAdvice 클래스에서 ResponseEntity를 반환할 수 있습니다. ResponseEntity는 Spring에서 HTTP 응답 데이터를 표현하기 위해 제공되는 객체이며, 이 타입은 MVC 구조에서 컨트롤러가 반환하는 데이터의 형식을 결정할 뿐 특정 어노테이션(@RestController)에 의존하지 않습니다.

@RestController와 @ControllerAdvice의 차이점

  • @RestController:
    • @Controller와 @ResponseBody를 결합한 어노테이션입니다.
    • 주로 RESTful API를 작성할 때 사용되며, 메서드의 반환값이 HTTP 응답 바디에 직렬화됩니다.
  • @ControllerAdvice:
    • 전역적인 예외 처리, 데이터 바인딩, 모델 조작 등을 위한 전용 어노테이션입니다.
    • 주로 예외 처리와 관련된 로직을 작성하며, 반환 타입으로 ResponseEntity를 사용할 수 있습니다.

ResponseEntity와 관련된 동작 원리

ResponseEntity는 다음과 같은 이유로 @RestController와 무관하게 사용할 수 있습니다:

  1. Spring MVC의 기본 동작
    ResponseEntity는 HTTP 응답의 상태 코드, 헤더, 바디를 명시적으로 설정할 수 있는 객체로, DispatcherServlet에서 처리됩니다.
    @ControllerAdvice의 메서드가 ResponseEntity를 반환하면, 이를 HTTP 응답으로 변환하는 작업은 Spring MVC가 담당합니다.
  2. @ResponseBody의 자동 적용
    @ControllerAdvice 내의 예외 처리 메서드에 ResponseEntity를 반환하면, Spring은 암묵적으로 @ResponseBody가 적용된 것처럼 동작하여 반환값을 HTTP 응답 바디로 직렬화합니다.

예제: @RestController 없이도 작동하는 코드

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("Illegal argument: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

위 코드에서 @RestController는 없어도 ResponseEntity는 정상적으로 반환됩니다.


왜 강의에서 @RestController를 붙였을까?

강의에서 @ControllerAdvice에 @RestController를 추가한 이유는 일관성을 유지하고 간단한 구현을 위해서일 가능성이 큽니다.
@RestController를 붙이면 별도의 @ResponseBody 선언 없이 모든 메서드가 HTTP 응답 바디를 반환하게 되어 편리할 수 있습니다.

예를 들어, 다음처럼 @RestController를 붙이는 경우:

@RestController
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return ResponseEntity.badRequest().body("Illegal argument: " + ex.getMessage());
    }
}

위 코드는 @ResponseBody를 암묵적으로 포함하기 때문에, 추가적인 어노테이션 없이도 모든 메서드가 RESTful 응답으로 처리됩니다.


결론

  • @RestController는 반드시 필요하지 않습니다.
    @ControllerAdvice만 사용해도 ResponseEntity를 반환하여 HTTP 응답을 생성할 수 있습니다.
  • @RestController는 단순히 편리함을 위해 추가될 수 있습니다.
    모든 메서드에 RESTful 응답을 일관되게 적용하려는 경우에 사용합니다.

따라서, 강의에서 사용한 @RestController는 필수가 아닌 선택사항이며, @ControllerAdvice만으로도 충분히 동작합니다.

질문 1. 직렬화 한다는 게 뭔 말이야? HTTP 응답 바디에 직렬화된다고 했는데 무슨 뜻이야?
HTTP 응답 바디로 직렬화한다고 했네.

질문 2. @RestController를 붙이면 별도의 @ResponseBody 선언 없이
모든 메서드가 HTTP 응답 바디를 반환하게 되어 편리할 수 있다고 했는데,
다르게 말하면 @RestController를 붙이지 않으면
별도의 @ResponseBody 선언이 있어야 한다는 이야기처럼 들리는데
왜 어디에도 @ResponseBody 선언이 보이지 않는거야?
질문 1: 직렬화란 무엇인가?

**직렬화(Serialization)**란 객체(Object) 또는 데이터 구조를 특정한 형식으로 변환하여 저장하거나 전송할 수 있는 상태로 만드는 과정을 의미합니다.

HTTP 응답 바디로 직렬화한다는 뜻

  • Spring MVC에서 메서드의 반환값이 객체라면, 이 객체를 클라이언트가 이해할 수 있는 형식으로 변환(직렬화)해야 합니다.
  • 보통 RESTful API에서는 JSON이나 XML 형식으로 객체를 직렬화합니다.
  • 예를 들어:
@RestController
public class ExampleController {
    @GetMapping("/example")
    public User getUser() {
        return new User("John", "Doe");
    }
}

위 코드에서 getUser() 메서드가 반환하는 User 객체는 **JSON 형식으로 변환(직렬화)**되어 HTTP 응답 바디에 담깁니다:

{
  "firstName": "John",
  "lastName": "Doe"
}

Spring은 내부적으로 Jackson 라이브러리를 사용해 Java 객체를 JSON으로 직렬화하거나 JSON을 Java 객체로 역직렬화합니다.


질문 2: @RestController 없이 @ResponseBody가 필요한가?

1. @RestController와 @ResponseBody의 관계

  • @RestController는 @Controller와 @ResponseBody를 합친 기능을 제공합니다.
  • 따라서 @RestController를 사용하면 메서드의 반환값이 자동으로 HTTP 응답 바디에 직렬화됩니다.
  • 반면, @Controller를 사용하는 경우 메서드가 반환한 값이 뷰 이름으로 처리됩니다. 즉, 반환값을 HTTP 응답 바디에 넣고 싶다면 @ResponseBody를 추가로 선언해야 합니다.

2. 왜 @ResponseBody가 보이지 않는가?

Spring에서 @ControllerAdvice와 @ExceptionHandler를 사용할 때는 @ResponseBody가 암묵적으로 적용됩니다.
즉, @ExceptionHandler 메서드가 반환하는 값은 기본적으로 HTTP 응답 바디에 포함되도록 동작합니다.

예제 코드 분석

다음 코드에서는 @ResponseBody를 명시적으로 선언하지 않았지만, 반환값이 HTTP 응답 바디로 들어갑니다:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
        return new ResponseEntity<>("Illegal argument: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

이는 @ControllerAdvice와 @ExceptionHandler의 동작 방식 때문입니다.
Spring은 예외 처리 메서드에서 반환되는 데이터를 자동으로 HTTP 응답 바디로 직렬화하도록 설정합니다.


결론

  1. 직렬화란 무엇인가?
    • Java 객체를 JSON이나 XML 같은 형식으로 변환하여 클라이언트가 이해할 수 있도록 만드는 과정입니다.
    • Spring에서는 이 과정을 통해 HTTP 응답 바디에 데이터를 담습니다.
  2. 왜 @ResponseBody가 보이지 않는가?
    • @ControllerAdvice와 @ExceptionHandler는 암묵적으로 @ResponseBody를 포함하여 동작합니다.
    • 따라서 예외 처리 메서드에서 반환된 값은 별도의 선언 없이도 HTTP 응답 바디로 직렬화됩니다.
 
그러면 정말로 @RestController 라고 붙여준건,
순전히 함께 일하는 사람들이나 또는 코드를 개발하고 있는 개발자 본인이
나중에 다시 보더라도 이해하기 위해서 붙여놓은 걸로 이해를 하면 되겠네?
근데 안 붙여놓으면 이해가 어려운 건가?
그리고 붙여놓으면 어떤 이해를 하는데에 도움을 준다는 걸까?
네, @RestController를 붙이는 것은 코드의 의도를 명확히 드러내기 위한 선택적 장치로 이해할 수 있습니다. 꼭 붙이지 않아도 기술적으로는 문제없이 작동하지만, 개발자 본인이나 함께 작업하는 팀원들에게 코드의 역할과 동작 방식을 더 잘 전달할 수 있습니다.

@RestController를 붙이는 이유

  1. 의도 명확화
    • @RestController는 "이 클래스는 RESTful API와 관련된 작업을 수행한다"는 의도를 명시적으로 표현합니다.
    • 예외 처리 클래스에 @RestController를 붙이면, 해당 클래스가 단순히 전역 예외를 처리하면서 응답을 RESTful 방식으로 반환한다는 점을 더 쉽게 이해할 수 있습니다.
    • 특히 팀 프로젝트나 코드 리뷰에서 다른 개발자가 해당 클래스를 볼 때 "이 클래스는 RESTful HTTP 응답을 반환하는 역할을 한다"는 것을 즉시 파악할 수 있습니다.
  2. @ResponseBody를 명시하지 않아도 되는 장점
    • @RestController를 사용하면 모든 메서드에서 자동으로 @ResponseBody가 적용됩니다.
    • 이를 통해 개발자가 "이 클래스에서 반환하는 값은 항상 HTTP 응답 바디로 직렬화된다"는 것을 더 명확하게 알 수 있습니다.
  3. 표준화와 일관성
    • RESTful API를 작성하는 컨트롤러라면 @RestController를 붙이는 것이 일반적인 관례입니다.
    • 예외 처리 클래스도 RESTful 방식으로 응답을 반환한다면, @RestController를 붙이는 것이 코드 스타일의 일관성을 유지하는 데 도움이 됩니다.

@RestController를 안 붙였을 때 이해가 어려운가?

1. 이해하기 어려운 경우

  • 초보 개발자나 Spring에 익숙하지 않은 개발자가 코드를 보면 혼란스러울 수 있습니다.
  • @ControllerAdvice만 보고 "이 클래스가 반환하는 값이 HTTP 응답 바디로 직렬화되는지" 확신하지 못할 수 있습니다.

2. 이해하기 쉬운 경우

  • Spring MVC의 동작 방식(예: @ControllerAdvice와 @ExceptionHandler가 암묵적으로 @ResponseBody를 포함한다는 사실)을 알고 있는 개발자라면, @RestController가 없어도 쉽게 이해할 수 있습니다.

@RestController를 붙이면 이해를 돕는 점

  1. 역할을 명확히 표현
    • 클래스의 목적이 "RESTful 응답을 반환하는 전역 예외 처리"임을 명확히 전달합니다.
  2. 직렬화 방식을 암묵적으로 알려줌
    • 이 클래스의 메서드들이 @ResponseBody를 사용하지 않아도 직렬화된 응답 바디를 반환한다는 점을 직관적으로 이해할 수 있습니다.
  3. 초보 개발자에게 친절한 코드
    • Spring의 세부 동작 원리를 몰라도, @RestController만 보고 "이 클래스는 RESTful 응답을 반환하는 클래스구나"라고 바로 알 수 있습니다.

언제 붙이고 언제 생략할까?

  1. 붙이는 것이 적합한 경우
    • 프로젝트 내에서 RESTful API 표준을 엄격히 준수하고, 모든 응답이 JSON 같은 직렬화된 형식으로 반환되는 경우.
    • 팀원 간 코드 이해와 일관성을 중시하는 경우.
  2. 생략해도 괜찮은 경우
    • 팀 내에서 Spring MVC 동작 원리에 대한 이해가 충분하고, @ControllerAdvice와 @ExceptionHandler의 기본 동작을 모두 알고 있는 경우.
    • 코드의 단순화를 위해 불필요한 어노테이션을 줄이고 싶을 때.

결론

@RestController를 붙이는 것은 개발자의 의도와 코드의 목적을 명확히 전달하기 위한 선택적인 스타일 요소입니다.
안 붙여도 기술적으로는 문제가 없지만, 붙임으로써 다음과 같은 장점이 있습니다:

  • 코드의 역할이 직관적으로 드러남.
  • 초보 개발자나 팀원들이 코드를 더 쉽게 이해할 수 있음.
  • RESTful API와 관련된 코드라는 것을 명시적으로 알림.

따라서 팀의 코드 스타일, 프로젝트 관례, 함께 작업하는 개발자의 경험 수준에 따라 @RestController를 붙일지 여부를 결정하면 됩니다.


@ControllerAdvice 가 붙어있으면, 그 클래스는 ComponentScan에 의해서 메모리에 올라가는게 맞아?

네, 맞습니다! @ControllerAdvice가 붙은 클래스는 Spring의 컴포넌트 스캔(ComponentScan) 메커니즘에 의해 자동으로 메모리에 등록(Bean으로 등록)됩니다. 이 과정은 다른 @Component 계열 어노테이션(@Controller, @Service, @Repository)과 유사합니다.


왜 @ControllerAdvice가 ComponentScan의 대상인가?

  1. @ControllerAdvice는 @Component를 포함한다
    • @ControllerAdvice는 내부적으로 @Component 어노테이션을 가지고 있습니다.
    • @Component는 Spring에서 해당 클래스를 스캔 대상(Bean 등록 대상)으로 삼는 가장 기본적인 어노테이션입니다.
    • 따라서, @ControllerAdvice가 붙은 클래스는 @Component로 간주되어 Spring Application Context에 자동으로 등록됩니다.
     
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    // 생략된 내부 코드
}
  1. 스프링이 자동으로 예외 처리 컨트롤러로 인식
    • @ControllerAdvice가 붙은 클래스는 Spring MVC의 ExceptionResolver 메커니즘에 의해 예외 처리 담당자로 인식됩니다.
    • Application Context에 등록된 이후, 해당 클래스는 전역적으로 예외를 처리하거나 모델 데이터를 조작할 수 있도록 동작합니다.

Spring의 ComponentScan과 메모리 등록 과정

  1. ComponentScan
    • Spring Boot는 기본적으로 **기본 패키지(애플리케이션의 메인 클래스가 위치한 패키지)**를 기준으로 하위 패키지를 스캔합니다.
    • 스캔 과정에서 @ControllerAdvice가 붙은 클래스는 자동으로 스프링 빈으로 등록됩니다.
  2. Application Context에 등록
    • 스캔된 클래스는 Spring의 Application Context에 Bean으로 등록되며, 전역적으로 접근 가능합니다.
    • @ControllerAdvice 클래스는 특별히 MVC의 Exception Handling 또는 Model Binding 역할을 수행하는 전용 컴포넌트로 동작합니다.

추가로 알아두면 좋은 점

  1. @ControllerAdvice를 특정 조건에만 적용하기
    • @ControllerAdvice는 기본적으로 전역적으로 동작하지만, 조건에 따라 특정 컨트롤러 또는 패키지에만 적용할 수 있습니다.
     
@ControllerAdvice(assignableTypes = {UserController.class})
public class SpecificControllerAdvice {
    // UserController에만 적용
}
  1. ComponentScan의 범위 조정
    • @ControllerAdvice 클래스가 스캔되지 않거나 특정 위치에서만 동작하도록 하려면, @ComponentScan의 범위를 조정할 수 있습니다.
     
@SpringBootApplication
@ComponentScan(basePackages = "com.example.advice")
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

결론

@ControllerAdvice가 붙어 있는 클래스는 기본적으로 Spring의 ComponentScan 메커니즘에 의해 메모리에 올라가고, Application Context에 등록됩니다. 이는 @ControllerAdvice가 내부적으로 @Component를 포함하기 때문입니다.

따라서 특별히 추가 설정을 하지 않아도, 해당 클래스는 전역 예외 처리 및 모델 데이터 조작 등의 역할을 수행하도록 자동으로 Spring에 의해 관리됩니다.

그러면 @Component 말고 또 내부적으로 포함하고 있는 어노테이션은 뭐뭐가 있어?

@ControllerAdvice는 내부적으로 직접 포함하고 있는 어노테이션은 @Component뿐입니다.
그러나 @ControllerAdvice의 역할과 동작은 Spring MVC의 여러 기능과 통합되어 있으며, 다음과 같은 관련 어노테이션들과 밀접한 관계가 있습니다.

아래는 @ControllerAdvice의 동작에 영향을 미치는 주요 어노테이션 및 관련된 동작 방식입니다.


1. 포함된 어노테이션

@Component

  • @ControllerAdvice는 내부적으로 @Component를 포함하고 있습니다.
  • 이로 인해 @ControllerAdvice가 붙은 클래스는 Spring의 ComponentScan 메커니즘에 의해 자동으로 스프링 빈으로 등록됩니다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    // 내부적으로 @Component를 포함
}

2. 관련 어노테이션

@ControllerAdvice 클래스에서 자주 사용되는 어노테이션은 직접 포함되어 있지는 않지만, 이 클래스와 밀접하게 작동합니다:

@ExceptionHandler

  • 특정 예외를 처리하기 위한 메서드를 정의할 때 사용됩니다.
  • @ControllerAdvice와 함께 동작하며, 메서드가 처리할 예외 유형을 지정할 수 있습니다.

@InitBinder

  • 특정 컨트롤러에서 데이터 바인딩 설정을 전역적으로 적용할 수 있습니다.
  • 주로 사용자 입력값을 특정 형식으로 변환하거나 유효성 검사를 설정할 때 사용됩니다.

@ModelAttribute

  • 모든 컨트롤러에 공통적으로 추가할 모델 데이터를 정의할 때 사용됩니다.
  • @ControllerAdvice와 함께 사용하면, 모든 요청에 대해 지정한 데이터를 모델에 추가할 수 있습니다.

@ResponseBody

  • @ControllerAdvice와 함께 사용하지 않더라도 암묵적으로 포함된 것처럼 동작합니다.
  • 예외 처리 메서드(@ExceptionHandler)가 반환하는 값이 HTTP 응답 바디에 포함되도록 합니다.
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(NullPointerException.class)
    @ResponseBody // 명시적으로 붙일 필요는 없음
    public String handleNullPointerException(NullPointerException ex) {
        return "Null Pointer Exception occurred!";
    }
}

3. 동작 원리를 지원하는 내부 어노테이션은?

@ControllerAdvice가 Spring MVC와 통합된 기능을 제공하는 이유는 Spring 내부에서 동작을 처리하는 메커니즘 덕분입니다. 다음은 내부적으로 영향을 미치는 Spring의 처리 과정입니다:

  1. @RestControllerAdvice
    • @ControllerAdvice와 @ResponseBody를 결합한 어노테이션입니다.
    • @ControllerAdvice의 모든 기능을 제공하면서, 자동으로 메서드의 반환값이 HTTP 응답 바디에 포함되도록 설정합니다.
  2. Spring의 ExceptionResolver
    • Spring MVC의 HandlerExceptionResolver 메커니즘이 @ControllerAdvice와 @ExceptionHandler를 탐지하여 예외 처리를 위임합니다.

결론

@ControllerAdvice는 내부적으로 **@Component**만 포함하고 있습니다.
그러나 @ExceptionHandler, @InitBinder, @ModelAttribute 같은 어노테이션과 함께 동작하며, Spring MVC와의 통합을 통해 강력한 전역 처리 메커니즘을 제공합니다.

이와 함께 @RestControllerAdvice는 @ControllerAdvice와 @ResponseBody를 결합한 버전으로, RESTful API 개발 시 더욱 편리하게 사용할 수 있는 선택지입니다.

 
그러면 이건 무슨 의미로 써준거야?
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    // 내부적으로 @Component를 포함
}

이 코드는 @ControllerAdvice 어노테이션의 정의를 보여주는 것으로, @ControllerAdvice가 어떻게 구성되어 있는지를 설명하기 위해 작성된 것입니다. Spring Framework의 소스 코드에서 실제로 이렇게 정의되어 있습니다.


코드가 의미하는 것

1. @Target({ElementType.TYPE})

  • 이 어노테이션이 적용될 수 있는 대상(Target)을 지정합니다.
  • ElementType.TYPE은 클래스, 인터페이스(애너테이션 포함), 열거형에 어노테이션을 적용할 수 있음을 의미합니다.
  • 즉, @ControllerAdvice는 클래스에만 적용 가능합니다.

2. @Retention(RetentionPolicy.RUNTIME)

  • 이 어노테이션이 유지되는 기간을 지정합니다.
  • RetentionPolicy.RUNTIME은 런타임 동안에도 이 어노테이션이 유지되어 Reflection API를 통해 참조할 수 있음을 의미합니다.
  • Spring은 런타임에 @ControllerAdvice를 탐지하고 처리하기 때문에, 이 설정이 필요합니다.

3. @Documented

  • 이 어노테이션이 JavaDoc과 같은 문서화 툴에 포함되도록 설정합니다.
  • 즉, @ControllerAdvice를 사용하는 클래스에 대한 설명을 문서화할 때, 해당 어노테이션이 표시됩니다.

4. @Component

  • 이 어노테이션이 Spring의 컴포넌트 스캔 대상임을 나타냅니다.
  • @Component가 포함되어 있기 때문에, @ControllerAdvice를 붙인 클래스는 Spring Application Context에 **빈(Bean)**으로 등록됩니다.

5. public @interface ControllerAdvice

  • @ControllerAdvice 자체가 어노테이션임을 나타냅니다.
  • Java에서 커스텀 어노테이션을 정의할 때 사용되는 문법입니다.

주석: // 내부적으로 @Component를 포함

  • 이 주석은 @ControllerAdvice 어노테이션이 실제로는 @Component 어노테이션을 포함하고 있다는 사실을 설명하기 위해 작성된 것입니다.
  • @Component를 포함하고 있기 때문에, @ControllerAdvice는 컴포넌트 스캔 대상이 됩니다.
  • 즉, @ControllerAdvice를 붙인 클래스는 Spring이 자동으로 탐지하고 관리합니다.

@ControllerAdvice가 @Component를 포함하는 이유

@ControllerAdvice를 Spring의 컴포넌트 스캔 메커니즘에 의해 자동으로 등록되도록 하기 위함입니다.
@Component가 없다면, 개발자가 직접 @Bean 등록을 해야 하므로 불편합니다. 이를 방지하기 위해 Spring이 @Component를 포함시켜 자동 등록을 지원합니다.


정리

  • 이 코드는 @ControllerAdvice 어노테이션의 구성 요소를 보여주며, Spring이 이 어노테이션을 사용해 전역 예외 처리 클래스를 탐지하고 관리할 수 있도록 만든 동작 방식을 설명합니다.
  • 핵심은 @ControllerAdvice가 내부적으로 @Component를 포함하고 있어 Spring이 자동으로 빈으로 등록한다는 점입니다.