- Spring REST 서비스 예외 처리 방법2023년 11월 09일
- 알쓸개잡
- 작성자
- 2023.11.09.:39
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 다음글이전글이전 글이 없습니다.댓글