Spring에서는 전체 예외 처리에 대한 추상화를 제공하고 몇 가지 annotation만으로 예외 처리를 손쉽게 할 수 있다.
이번 포스팅에서는 Spring REST 서비스에서 예외를 처리하는 방법과 HTTP 응답 상태코드를 반환하는 방법에 대해서 기록한다.
1. 예외 수동 처리
다음 Controller 코드는 HttpStatus와 함께 응답 body 페이로드를 포함하는 ResponseEntity를 반환한다.
- 예외가 발생하지 않으면 200 코드와 함께 body 페이로드로 Member를 응답한다.
- ResourceNotFoundException 예외가 발생하는 경우 empty body와 404 코드를 응답한다.
- MemberServiceException 예외가 발생하는 경우 empty body 와 500 코드를 응답한다.
예외 클래스는 RuntimeException을 확장한 ResourceNotFoundException과 MemberServiceException이다.
package com.example.jpa.exception;
public class ResourceNotFoundException extends RuntimeException{
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String message) {
super(message);
}
}
package com.example.jpa.exception;
public class MemberServiceException extends RuntimeException{
public MemberServiceException() {
super();
}
public MemberServiceException(String message) {
super(message);
}
}
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/{memberId}")
public ResponseEntity<Member> getMember(@PathVariable Long memberId) {
Member member;
try {
member = memberService.getMember(memberId);
} catch (MemberServiceException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
} catch (ResourceNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
return ResponseEntity.ok(member);
}
}
컨트롤러의 endpoint 메서드 내에서 직접 예외 처리를 하고 있는데 이 방식의 문제점은 중복이다.
DELETE, POST를 처리하는 다른 endpoint에서도 중복되는 예외 처리 코드가 필요할 수 있다.
2. 예외 클래스에 ResponseStatus annotation 사용
두 번째 방법은 예외 클래스에 @ResponseStatus annotation을 지정하여 해당 예외가 발생했을 때 지정된 ResponseStatus 코드로 자동 응답 하도록 할 수 있다.
package com.example.jpa.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String message) {
super(message);
}
}
package com.example.jpa.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class MemberServiceException extends RuntimeException{
public MemberServiceException() {
super();
}
public MemberServiceException(String message) {
super(message);
}
}
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/{memberId}")
public ResponseEntity<Member> getMember(@PathVariable Long memberId) {
Member member = memberService.getMember(memberId);
return ResponseEntity.ok(member);
}
}
memberService.getMember 메서드 내부에서 MemberServiceException, ResourceNotFoundException이 발생하는 경우 각 예외 클래스에 @ResponseStatus에 지정된 응답 코드로 응답을 한다.
예외가 발생한 경우 다음과 같이 응답 payload도 함께 전송된다.
{
"timestamp": "2023-11-09 00:05:56",
"status": 404,
"error": "Not Found",
"path": "/members/1"
}
3. Controller Advice 클래스 (@ControllerAdvice)
Spring은 @ControllerAdvice 클래스 annotation 이 지정된 클래스를 통해서 중앙 집중적으로 모든 예외에 대한 처리를 할 수 있도록 지원한다.
@ControllerAdvice
public class ControllerAdviser {
@ExceptionHandler({RuntimeException.class})
public ResponseEntity<String> runtimeException(RuntimeException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
@ExceptionHandler({ResourceNotFoundException.class})
public ResponseEntity<String> resourceNotFoundException(ResourceNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
@ExceptionHandler({MemberServiceException.class})
public ResponseEntity<String> memberServiceException(MemberServiceException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
@ExceptionHandler annotation에 지정된 예외를 처리하는 handler 메서드를 등록하여 처리한다.
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping("/{memberId}")
public ResponseEntity<Member> getMember(@PathVariable Long memberId) {
Member member = memberService.getMember(memberId);
return ResponseEntity.ok(member);
}
}
@ExceptionHandler annotation 이 지정된 handler 메서드에 @ResponseStatus annotation을 지정할 수 있다.
@ControllerAdvice
public class ControllerAdviser {
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({RuntimeException.class})
public void runtimeException() {}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler({ResourceNotFoundException.class})
public void resourceNotFoundException() {}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler({MemberServiceException.class})
public void memberServiceException() {}
}
위와 같이 지정된 경우에는 body payload 없이 코드만 응답한다.
예외 클래스에 @ResponseStatus 어노테이션이 적용되어 있고 @ControllerAdvice 클래스의 예외 핸들러에 @ResponseStatus 어노테이션이 적용되어 있는 경우 @ControllerAdvice 클래스의 예외 핸들러가 동작한다.
'스프링부트' 카테고리의 다른 글
spring scheduler task에 shedlock + redis 적용방법 (0) | 2023.11.20 |
---|---|
spring multi module 프로젝트 만들기 (0) | 2023.11.19 |
Spring Boot Embedded Tomcat 서버 설정 (0) | 2023.11.03 |
Spring Data JPA 에서 공간 정보(Spatial Data) 사용하기 (0) | 2023.11.01 |
Spring REST 컨트롤러에서 HTTP 헤더 읽기 (0) | 2023.10.29 |
댓글