163 lines
6.8 KiB
Markdown
163 lines
6.8 KiB
Markdown
### 스프링 부트에서 예외 처리
|
|
|
|
스프링 부트에서 예외 처리는 애플리케이션의 안정성과 사용자 경험을 개선하는 데 중요한 역할을 합니다. REST API나 MVC 기반 웹 애플리케이션에서 발생하는 예외를 효과적으로 관리하면, 클라이언트에게 일관된 응답을 제공하고 개발자가 문제를 빠르게 파악할 수 있습니다. 스프링 부트는 다양한 예외 처리 방법을 제공하며, 이를 컨트롤러 단위 또는 전역적으로 설정할 수 있습니다. 아래에서는 주요 개념과 구현 방법을 설명합니다.
|
|
|
|
---
|
|
|
|
#### 1. 기본 예외 처리
|
|
스프링 부트는 기본적으로 `BasicErrorController`를 제공하여 예외가 발생했을 때 표준화된 오류 응답을 반환합니다. 예를 들어, 잘못된 URL로 요청하거나 서버에서 예외가 발생하면 JSON 형식(REST API)이나 HTML 페이지(MVC)로 오류 정보를 반환합니다.
|
|
|
|
- **기본 응답 예시 (JSON)**:
|
|
```json
|
|
{
|
|
"timestamp": "2025-03-15T12:00:00Z",
|
|
"status": 404,
|
|
"error": "Not Found",
|
|
"message": "No message available",
|
|
"path": "/wrong/path"
|
|
}
|
|
```
|
|
- **설정**: `application.yaml`에서 `server.error` 속성으로 기본 동작을 커스터마이징할 수 있습니다.
|
|
```yaml
|
|
server:
|
|
error:
|
|
include-stacktrace: on_param # 스택 트레이스 포함 여부 (never, on_param, always)
|
|
include-message: always # 오류 메시지 포함
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. 컨트롤러 내 예외 처리 (@ExceptionHandler)
|
|
컨트롤러 내에서 특정 예외를 처리하려면 `@ExceptionHandler` 어노테이션을 사용합니다. 이는 특정 컨트롤러에 국한된 예외 처리를 정의할 때 유용합니다.
|
|
|
|
- **예시**:
|
|
```java
|
|
@RestController
|
|
@RequestMapping("/api")
|
|
public class MyController {
|
|
|
|
@GetMapping("/user/{id}")
|
|
public String getUser(@PathVariable Long id) {
|
|
if (id <= 0) {
|
|
throw new IllegalArgumentException("ID는 0보다 커야 합니다.");
|
|
}
|
|
return "User " + id;
|
|
}
|
|
|
|
@ExceptionHandler(IllegalArgumentException.class)
|
|
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
|
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
|
|
}
|
|
}
|
|
```
|
|
- **동작**: `/api/user/0` 요청 시 `400 Bad Request`와 메시지가 반환됩니다.
|
|
|
|
---
|
|
|
|
#### 3. 전역 예외 처리 (@ControllerAdvice)
|
|
애플리케이션 전체에 걸친 예외 처리를 위해 `@ControllerAdvice`와 `@ExceptionHandler`를 조합하여 사용합니다. 이를 통해 모든 컨트롤러에서 발생하는 예외를 일관되게 처리할 수 있습니다.
|
|
|
|
- **예시**:
|
|
```java
|
|
@RestControllerAdvice
|
|
public class GlobalExceptionHandler {
|
|
|
|
@ExceptionHandler(IllegalArgumentException.class)
|
|
public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException ex) {
|
|
ErrorResponse response = new ErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage());
|
|
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
|
|
}
|
|
|
|
@ExceptionHandler(Exception.class)
|
|
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
|
|
ErrorResponse response = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류 발생");
|
|
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
|
|
// 커스텀 오류 응답 객체
|
|
public record ErrorResponse(HttpStatus status, String message) {}
|
|
```
|
|
- **결과**:
|
|
- `IllegalArgumentException`: `400 Bad Request`와 메시지 반환.
|
|
- 기타 예외: `500 Internal Server Error`와 기본 메시지 반환.
|
|
|
|
---
|
|
|
|
#### 4. 커스텀 예외 정의
|
|
비즈니스 로직에 맞는 예외를 정의하고 이를 처리하는 것도 일반적입니다.
|
|
|
|
- **커스텀 예외**:
|
|
```java
|
|
public class UserNotFoundException extends RuntimeException {
|
|
public UserNotFoundException(String message) {
|
|
super(message);
|
|
}
|
|
}
|
|
```
|
|
- **컨트롤러에서 사용**:
|
|
```java
|
|
@GetMapping("/user/{id}")
|
|
public String getUser(@PathVariable Long id) {
|
|
if (id == null || id <= 0) {
|
|
throw new UserNotFoundException("사용자를 찾을 수 없습니다: " + id);
|
|
}
|
|
return "User " + id;
|
|
}
|
|
```
|
|
- **전역 처리**:
|
|
```java
|
|
@RestControllerAdvice
|
|
public class GlobalExceptionHandler {
|
|
@ExceptionHandler(UserNotFoundException.class)
|
|
public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
|
|
return new ResponseEntity<>(new ErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage()), HttpStatus.NOT_FOUND);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### 5. 응답 형식 커스터마이징
|
|
REST API에서 클라이언트가 기대하는 오류 응답 형식을 맞추기 위해 `ResponseEntity`나 커스텀 객체를 사용합니다. 예를 들어:
|
|
|
|
- **응답 형식**:
|
|
```json
|
|
{
|
|
"status": 404,
|
|
"error": "Not Found",
|
|
"message": "사용자를 찾을 수 없습니다: 0"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### 6. 주요 어노테이션 및 클래스
|
|
- `@ExceptionHandler`: 특정 예외를 처리하는 메서드 정의.
|
|
- `@ControllerAdvice`: 전역 예외 처리 클래스 정의.
|
|
- `@RestControllerAdvice`: `@ControllerAdvice` + `@ResponseBody` 조합.
|
|
- `ResponseEntity`: HTTP 상태 코드, 헤더, 본문을 포함한 응답 생성.
|
|
- `HttpStatus`: HTTP 상태 코드 열거형 (예: `HttpStatus.NOT_FOUND`).
|
|
|
|
---
|
|
|
|
#### 7. 예외 처리 모범 사례
|
|
- **일관성 유지**: 모든 오류 응답 형식을 표준화하여 클라이언트가 쉽게 파싱하도록 합니다.
|
|
- **세부 정보 제한**: 프로덕션 환경에서는 스택 트레이스나 민감한 정보를 노출시키지 않습니다.
|
|
- **로그 기록**: 예외 발생 시 로깅(`SLF4J` 등)을 통해 디버깅 정보를 남깁니다.
|
|
```java
|
|
@Slf4j
|
|
@RestControllerAdvice
|
|
public class GlobalExceptionHandler {
|
|
@ExceptionHandler(Exception.class)
|
|
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
|
|
log.error("Unhandled exception occurred", ex);
|
|
return new ResponseEntity<>(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류"), HttpStatus.INTERNAL_SERVER_ERROR);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 결론
|
|
스프링 부트의 예외 처리는 `@ExceptionHandler`로 국부적인 처리를, `@ControllerAdvice`로 전역 처리를 구현하며, 커스텀 예외와 응답 형식을 통해 유연성을 확보할 수 있습니다. 이를 통해 개발자는 환경별 요구사항에 맞춰 안정적이고 유지보수 가능한 애플리케이션을 설계할 수 있습니다. 추가적으로, 스프링 부트의 기본 오류 처리 기능을 활용하면 최소한의 설정으로도 기본적인 예외 처리가 가능하다는 점도 큰 장점입니다. |