RESTful 웹 서비스 구축 - @ControllerAdvice / @ExceptionHandler
간단한 REST API 를 만들어봤었다.
추가적인 REST API 들을 만들어보기 전에, 예외 처리와 관련된 공부를 하고 가자.
프로그램 개발과 예외 처리
프로그램 개발 과정에서 **예외(Exception)**는 예상치 못한 상황에서 자주 발생합니다. 이러한 예외를 처리하는 방법은 다양합니다:
- try-catch를 이용한 예외 처리
개별 코드 블록에서 발생할 수 있는 예외를 직접 처리합니다.
try {
// 예외 발생 가능성 있는 코드
} catch (Exception ex) {
// 예외 처리 코드
}
- 예외를 호출한 곳으로 전달 (throws)
특정 메서드에서 발생한 예외를 호출한 메서드로 던질 수 있습니다.
이를 위해 throws 키워드를 사용합니다. 예외를 직접 처리하지 않고 위임하는 방식입니다.
public void someMethod() throws IOException {
// IOException 발생 가능
}
- 서버(예: 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);
}
}
핵심 개념 요약
- @ControllerAdvice
- 글로벌 예외 처리 클래스에 사용됩니다.
- 컨트롤러 계층에서 발생한 예외를 한 곳에서 처리할 수 있도록 지원합니다.
- @ExceptionHandler
- @ControllerAdvice 클래스 안에서 특정 예외를 처리하는 메서드에 붙입니다.
- 처리하려는 예외 유형에 따라 다양한 메서드를 정의할 수 있습니다.
- 코드 중복 감소와 유지보수성 향상
@ControllerAdvice와 @ExceptionHandler를 사용하면 각 컨트롤러마다 중복 코드를 작성하지 않아도 되며, 예외 처리 로직을 중앙 집중화할 수 있습니다.
보완된 내용: 예외 처리 흐름 이해
- 예외 발생
컨트롤러에서 예외가 발생하면, Spring은 이를 @ControllerAdvice로 전달합니다. - 핸들러 메서드 실행
발생한 예외의 타입에 따라 매핑된 @ExceptionHandler 메서드가 호출됩니다. - 응답 반환
@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의 주요 기능
- 전역 예외 처리
특정 예외가 발생했을 때 이를 처리하기 위한 전역 핸들러를 설정할 수 있습니다. 이를 통해 각 컨트롤러에서 반복적으로 예외 처리를 하지 않아도 됩니다. - 전역 데이터 바인딩
모델에 공통 데이터를 추가하거나 데이터 바인딩 설정을 전역적으로 정의할 수 있습니다. - 전역 모델 조작
모든 요청에 대해 공통적으로 사용할 모델 데이터를 추가하거나 수정할 수 있습니다.
주요 어노테이션
@ControllerAdvice 내부에서 자주 사용하는 어노테이션은 다음과 같습니다:
1. @ExceptionHandler
특정 예외를 처리하기 위한 메서드를 정의합니다. 컨트롤러 메서드에서 발생하는 예외를 전역적으로 처리할 수 있습니다.
2. @ModelAttribute
모든 컨트롤러 요청에서 공통적으로 사용할 모델 데이터를 정의할 수 있습니다.
3. @InitBinder
데이터 바인딩 및 데이터 유효성 검사를 위한 설정을 제공합니다.
세부적인 동작 원리
- 스프링 컨텍스트 내 빈으로 등록
@ControllerAdvice를 붙인 클래스는 Spring의 Application Context에 빈으로 등록됩니다. - AOP 기반 적용
@ControllerAdvice는 AOP(Aspect-Oriented Programming)를 활용하여 각 컨트롤러 요청 이전 또는 이후에 동작합니다. - 스코프 제한 가능
@ControllerAdvice는 특정 컨트롤러 또는 특정 요청 경로에 대해서만 동작하도록 제한할 수 있습니다.
실무에서의 활용
- 전역적인 에러 응답 처리
예를 들어, 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);
}
}
- 모델 데이터의 전역 설정
템플릿 엔진(예: Thymeleaf)에서 공통적으로 사용되는 데이터를 추가할 때 유용합니다. - 데이터 변환 및 유효성 검사
사용자 입력 데이터를 특정 형식으로 변환하거나 기본적인 유효성 검사를 전역적으로 수행할 수 있습니다.
장점
- 코드 중복 감소: 컨트롤러마다 중복된 예외 처리 코드를 줄일 수 있습니다.
- 유지보수성 향상: 전역적으로 설정된 공통 로직은 수정이 쉽고 관리가 용이합니다.
- 모듈화: 특정 관심사를 전역 핸들러로 분리하여 코드의 가독성과 구조를 개선합니다.
주의사항
- 적용 범위의 명확성
너무 많은 로직을 전역 핸들러에 추가하면 코드가 복잡해질 수 있으므로 필요한 범위에서만 사용해야 합니다. - 비즈니스 로직 분리
@ControllerAdvice는 비즈니스 로직이 아닌 예외 처리나 공통 로직에만 초점을 맞춰야 합니다. - 성능 고려
불필요하게 모든 요청에 대해 동작하도록 설정하면 성능에 영향을 미칠 수 있습니다.
결론
@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와 무관하게 사용할 수 있습니다:
- Spring MVC의 기본 동작
ResponseEntity는 HTTP 응답의 상태 코드, 헤더, 바디를 명시적으로 설정할 수 있는 객체로, DispatcherServlet에서 처리됩니다.
@ControllerAdvice의 메서드가 ResponseEntity를 반환하면, 이를 HTTP 응답으로 변환하는 작업은 Spring MVC가 담당합니다. - @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 선언이 보이지 않는거야?
**직렬화(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 응답 바디로 직렬화하도록 설정합니다.
결론
- 직렬화란 무엇인가?
- Java 객체를 JSON이나 XML 같은 형식으로 변환하여 클라이언트가 이해할 수 있도록 만드는 과정입니다.
- Spring에서는 이 과정을 통해 HTTP 응답 바디에 데이터를 담습니다.
- 왜 @ResponseBody가 보이지 않는가?
- @ControllerAdvice와 @ExceptionHandler는 암묵적으로 @ResponseBody를 포함하여 동작합니다.
- 따라서 예외 처리 메서드에서 반환된 값은 별도의 선언 없이도 HTTP 응답 바디로 직렬화됩니다.
그러면 정말로 @RestController 라고 붙여준건,
순전히 함께 일하는 사람들이나 또는 코드를 개발하고 있는 개발자 본인이
나중에 다시 보더라도 이해하기 위해서 붙여놓은 걸로 이해를 하면 되겠네?
근데 안 붙여놓으면 이해가 어려운 건가?
그리고 붙여놓으면 어떤 이해를 하는데에 도움을 준다는 걸까?
네, @RestController를 붙이는 것은 코드의 의도를 명확히 드러내기 위한 선택적 장치로 이해할 수 있습니다. 꼭 붙이지 않아도 기술적으로는 문제없이 작동하지만, 개발자 본인이나 함께 작업하는 팀원들에게 코드의 역할과 동작 방식을 더 잘 전달할 수 있습니다.
@RestController를 붙이는 이유
- 의도 명확화
- @RestController는 "이 클래스는 RESTful API와 관련된 작업을 수행한다"는 의도를 명시적으로 표현합니다.
- 예외 처리 클래스에 @RestController를 붙이면, 해당 클래스가 단순히 전역 예외를 처리하면서 응답을 RESTful 방식으로 반환한다는 점을 더 쉽게 이해할 수 있습니다.
- 특히 팀 프로젝트나 코드 리뷰에서 다른 개발자가 해당 클래스를 볼 때 "이 클래스는 RESTful HTTP 응답을 반환하는 역할을 한다"는 것을 즉시 파악할 수 있습니다.
- @ResponseBody를 명시하지 않아도 되는 장점
- @RestController를 사용하면 모든 메서드에서 자동으로 @ResponseBody가 적용됩니다.
- 이를 통해 개발자가 "이 클래스에서 반환하는 값은 항상 HTTP 응답 바디로 직렬화된다"는 것을 더 명확하게 알 수 있습니다.
- 표준화와 일관성
- RESTful API를 작성하는 컨트롤러라면 @RestController를 붙이는 것이 일반적인 관례입니다.
- 예외 처리 클래스도 RESTful 방식으로 응답을 반환한다면, @RestController를 붙이는 것이 코드 스타일의 일관성을 유지하는 데 도움이 됩니다.
@RestController를 안 붙였을 때 이해가 어려운가?
1. 이해하기 어려운 경우
- 초보 개발자나 Spring에 익숙하지 않은 개발자가 코드를 보면 혼란스러울 수 있습니다.
- @ControllerAdvice만 보고 "이 클래스가 반환하는 값이 HTTP 응답 바디로 직렬화되는지" 확신하지 못할 수 있습니다.
2. 이해하기 쉬운 경우
- Spring MVC의 동작 방식(예: @ControllerAdvice와 @ExceptionHandler가 암묵적으로 @ResponseBody를 포함한다는 사실)을 알고 있는 개발자라면, @RestController가 없어도 쉽게 이해할 수 있습니다.
@RestController를 붙이면 이해를 돕는 점
- 역할을 명확히 표현
- 클래스의 목적이 "RESTful 응답을 반환하는 전역 예외 처리"임을 명확히 전달합니다.
- 직렬화 방식을 암묵적으로 알려줌
- 이 클래스의 메서드들이 @ResponseBody를 사용하지 않아도 직렬화된 응답 바디를 반환한다는 점을 직관적으로 이해할 수 있습니다.
- 초보 개발자에게 친절한 코드
- Spring의 세부 동작 원리를 몰라도, @RestController만 보고 "이 클래스는 RESTful 응답을 반환하는 클래스구나"라고 바로 알 수 있습니다.
언제 붙이고 언제 생략할까?
- 붙이는 것이 적합한 경우
- 프로젝트 내에서 RESTful API 표준을 엄격히 준수하고, 모든 응답이 JSON 같은 직렬화된 형식으로 반환되는 경우.
- 팀원 간 코드 이해와 일관성을 중시하는 경우.
- 생략해도 괜찮은 경우
- 팀 내에서 Spring MVC 동작 원리에 대한 이해가 충분하고, @ControllerAdvice와 @ExceptionHandler의 기본 동작을 모두 알고 있는 경우.
- 코드의 단순화를 위해 불필요한 어노테이션을 줄이고 싶을 때.
결론
@RestController를 붙이는 것은 개발자의 의도와 코드의 목적을 명확히 전달하기 위한 선택적인 스타일 요소입니다.
안 붙여도 기술적으로는 문제가 없지만, 붙임으로써 다음과 같은 장점이 있습니다:
- 코드의 역할이 직관적으로 드러남.
- 초보 개발자나 팀원들이 코드를 더 쉽게 이해할 수 있음.
- RESTful API와 관련된 코드라는 것을 명시적으로 알림.
따라서 팀의 코드 스타일, 프로젝트 관례, 함께 작업하는 개발자의 경험 수준에 따라 @RestController를 붙일지 여부를 결정하면 됩니다.
@ControllerAdvice 가 붙어있으면, 그 클래스는 ComponentScan에 의해서 메모리에 올라가는게 맞아?
네, 맞습니다! @ControllerAdvice가 붙은 클래스는 Spring의 컴포넌트 스캔(ComponentScan) 메커니즘에 의해 자동으로 메모리에 등록(Bean으로 등록)됩니다. 이 과정은 다른 @Component 계열 어노테이션(@Controller, @Service, @Repository)과 유사합니다.
왜 @ControllerAdvice가 ComponentScan의 대상인가?
- @ControllerAdvice는 @Component를 포함한다
- @ControllerAdvice는 내부적으로 @Component 어노테이션을 가지고 있습니다.
- @Component는 Spring에서 해당 클래스를 스캔 대상(Bean 등록 대상)으로 삼는 가장 기본적인 어노테이션입니다.
- 따라서, @ControllerAdvice가 붙은 클래스는 @Component로 간주되어 Spring Application Context에 자동으로 등록됩니다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
// 생략된 내부 코드
}
- 스프링이 자동으로 예외 처리 컨트롤러로 인식
- @ControllerAdvice가 붙은 클래스는 Spring MVC의 ExceptionResolver 메커니즘에 의해 예외 처리 담당자로 인식됩니다.
- Application Context에 등록된 이후, 해당 클래스는 전역적으로 예외를 처리하거나 모델 데이터를 조작할 수 있도록 동작합니다.
Spring의 ComponentScan과 메모리 등록 과정
- ComponentScan
- Spring Boot는 기본적으로 **기본 패키지(애플리케이션의 메인 클래스가 위치한 패키지)**를 기준으로 하위 패키지를 스캔합니다.
- 스캔 과정에서 @ControllerAdvice가 붙은 클래스는 자동으로 스프링 빈으로 등록됩니다.
- Application Context에 등록
- 스캔된 클래스는 Spring의 Application Context에 Bean으로 등록되며, 전역적으로 접근 가능합니다.
- @ControllerAdvice 클래스는 특별히 MVC의 Exception Handling 또는 Model Binding 역할을 수행하는 전용 컴포넌트로 동작합니다.
추가로 알아두면 좋은 점
- @ControllerAdvice를 특정 조건에만 적용하기
- @ControllerAdvice는 기본적으로 전역적으로 동작하지만, 조건에 따라 특정 컨트롤러 또는 패키지에만 적용할 수 있습니다.
@ControllerAdvice(assignableTypes = {UserController.class})
public class SpecificControllerAdvice {
// UserController에만 적용
}
- 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의 처리 과정입니다:
- @RestControllerAdvice
- @ControllerAdvice와 @ResponseBody를 결합한 어노테이션입니다.
- @ControllerAdvice의 모든 기능을 제공하면서, 자동으로 메서드의 반환값이 HTTP 응답 바디에 포함되도록 설정합니다.
- 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이 자동으로 빈으로 등록한다는 점입니다.