| 예외처리
- 예외 : 프로그램이 예상하지 못한 상황을 만났을 때 오류를 처리하는 것
- 자바에서는 try-catch문 안에서 예외처리를 했었다.
try {
// 행동
} catch (Exception e) {
log.error("cetian exception");
}
- 스프링에서는 이러한 예외처리를 간편하게 할 수 있도록 하였다.
| 스프링의 예외처리 (REST API)
- 과거와는 달리 오늘날에는 프론트엔드와 백엔드 분업화가 잘 이루어져 있다.
- 백엔드에서는 보통 Rest API를 사용해 컨트롤러 기반의 예외처리를 주로 한다.
- 현장에서 예외처리를 할 때에는 enum과 예외객체를 통해 custom exception을 만들고,
@RestControllerAdvice를 사용해서 예외처리를 한다.
[ Rest API를 활용한 컨트롤러 기반 예외 처리 ]
(1) @ExceptionHandler
- 컨트롤러에 예외 발생 시, 이를 처리해주는 메소드를 만들고 @ExceptionHandler를 표시
- 리스트 방식으로 @ExceptionHandler(IllegalAccessException e1, CustomeException e2) 넣을 수도 있다.
(2) HTTP Status code 변경
@ResponseStatus | - http 상태 코드를 Annotation으로 직접 지정 ex.@ResponseStatus(httpStatus.FORBIDDEN) |
ResponseEntity | - ResponseEntity에 예외객체를 제너릭으로 넣어 사용 ex. ResponseEntity<CustomException> |
(3) 예외처리 우선순위
- 1. 해당 Exception이 정확히 지정된 Handler
- 2. 해당 Exception의 부모 예외 Handler
- 3. 또는 모든 예외의 부모 Exception
[ 어플리케이션의 전역적 예외 처리 - @RestControllerAdvice ]
- 방대한 양의 컨트롤러가 있는 프로그램에서 예외처리를 어떻게 해야할까?
- @RestControllerAdvice : Controller에게 사용되는 일종의 조언
- 기존의 ControllerAdvice는 view를 응답하는 방식이었다면,
- ResotControllerAdvice는 REST API용으로 객체를 응답하는 방식이다. (주로 JSON)
- 현재 개발자들에게 가장 많이 사용되고 있는 방식이라고 한다.
| 예외처리 실습
[1] Controller에 @ExceptionHandler로 예외처리 메소드를 만든다.
@ExceptionHandler(IllegalAccessException.class)
public String handleIllegalAccessException(
IllegalAccessException e){
log.error("IllegalAccessException is occured.", e);
return "INVALID_ACCESS";
}
>> 동일 Controller 클래스 내 HTTP 매핑 메소드
@GetMapping("/order/{orderId}")
public String getOrder(
@PathVariable("orderId") String orderId,
@RequestParam("orderAmount") Integer orderAmount) throws IllegalAccessException {
log.info("Get some order");
if ("500".equals(orderId)) {
throw new IllegalAccessException("500 is not valid orderId");
}
return "OrderId : " + orderId + ", orderAmount : " + orderAmount;
}
>> 요청 및 응답
http://localhost:8080/order/500?orderAmount=10000
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 14
Date: Sun, 11 Sep 2022 01:24:40 GMT
Keep-Alive: timeout=60
Connection: keep-alive
INVALID_ACCESS
[2] @ResponsStatus로 HTTP 상태 코드를 설정한다.
- 참고 : HTTP 상태 코드 의미
1xx | 정보 전달 : 요청 받았고, 작업 진행 중 |
2xx | 성공 : 작업을 성공받으로 받았다. |
3xx | 리다이렉션 필요 : 짧은 코드의 경우 301이나 302코드 전송, 또는 헤더의 location에 이동할 url를 전송 |
4xx | 클라이언트 오류 : 요청이 올바르지 않을 때 - 403 Forbidden (거부됨) : 관리자가 해당 사용자를 차단했거나 권한이 없거나, index.html이 없을 때 - 404 Not Found (찾을 수 없음) : 해당 리소스가 없음 - 410 Gone (사라짐) : 리소스가 영원히 사라진 경우 |
5xx | 서버 오류 : 서버가 응답할 수 없을 때 - 500 Internal Server Error (내부 서버 오류) : 설정 및 퍼미션 오류, 또는 JSP/서블릿 등 호출 시 오류 - 502 Bad Gateway (게이트웨이 불량) |
- 위를 보면 현재 페이지는 정상(HTTP/1.1 200)으로 나타나는데, 이를 변경하고 싶을 때 @ResponseStatus를 쓴다
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(IllegalAccessException.class)
public String handleIllegalAccessException(
IllegalAccessException e){
log.error("IllegalAccessException is occured.", e);
return "INVALID_ACCESS";
}
>> 요청 및 응답
http://localhost:8080/order/500?orderAmount=10000
HTTP/1.1 403
Content-Type: text/plain;charset=UTF-8
Content-Length: 14
Date: Sun, 11 Sep 2022 01:30:49 GMT
Keep-Alive: timeout=60
Connection: keep-alive
INVALID_ACCESS
[3] 예외객체를 만들어 JSON방식으로 예외 데이터를 전달한다.
- lombok을 활용하여 dto 안에 예외처리 객체를 만든다.
package com.example.websample.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class ErrorResponse {
private String errorCode;
private String message;
}
- 위에서 작성했던 예외처리 코드를 변경하여 객체로 묶는다.
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(IllegalAccessException.class)
public ErrorResponse handleIllegalAccessException(
IllegalAccessException e){
log.error("IllegalAccessException is occured.", e);
return new ErrorResponse("INVALID_ACCESS",
"IllegalAccessException is occured.");
}
>> 요청 및 응답
http://localhost:8080/order/500?orderAmount=10000
HTTP/1.1 403
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sun, 11 Sep 2022 01:37:26 GMT
Keep-Alive: timeout=60
Connection: keep-alive
{
"errorCode": "INVALID_ACCESS",
"message": "IllegalAccessException is occured."
}
Response file saved.
> 2022-09-11T103726.403.json
Response code: 403; Time: 513ms (513 ms); Content length: 77 bytes (77 B)
[4] ResponseEntinty를 사용하면 response에 헤더를 담을 수 있고, responseStatus를 지정할 수 있다.
@ExceptionHandler(IllegalAccessException.class)
public ResponseEntity<ErrorResponse> handleIllegalAccessException(
IllegalAccessException e){
log.error("IllegalAccessException is occured.", e);
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.header("newHeader", "some value")
.body(new ErrorResponse("INVALID_ACCESS",
"IllegalAccessException is occured."));
}
[5] 커스텀 예외처리 : enum과 예외객체를 사용하는 별도의 Custom Exception 클래스를 만든다.
- exception > ErrorCode (Enum)
package com.example.websample.exception;
public enum ErrorCode {
TOO_BIG_ID_ERROR,
TOO_SMALL_ID_ERROR
}
- dto > ErrorResponse (예외객체)
package com.example.websample.dto;
import com.example.websample.exception.ErrorCode;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class ErrorResponse {
private ErrorCode errorCode;
private String message;
}
- exception > WebSampleException (예외처리 클래스)
package com.example.websample.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor // constructor
@Data // getter/setter
public class WebSampleException extends RuntimeException{
private ErrorCode errorCode;
private String message;
}
>> 컨트롤러
@GetMapping("/order/{orderId}")
public String getOrder(
@PathVariable("orderId") String orderId,
@RequestParam("orderAmount") Integer orderAmount) throws WebSampleException {
log.info("Get some order");
if ("500".equals(orderId)) {
throw new WebSampleException(
ErrorCode.TOO_BIG_ID_ERROR,
"500 is too big orderId");
}
if ("3".equals(orderId)) {
throw new WebSampleException(
ErrorCode.TOO_SMALL_ID_ERROR,
"3 is too small orderId"
);
}
return "OrderId : " + orderId + ", orderAmount : " + orderAmount;
}
@ExceptionHandler(WebSampleException.class)
public ResponseEntity<ErrorResponse> handleWebSampleException(
WebSampleException e){
log.error("WebSampleException is occured.", e);
return ResponseEntity
.status(HttpStatus.INSUFFICIENT_STORAGE)
.body(new ErrorResponse(ErrorCode.TOO_BIG_ID_ERROR,
"WebSampleException is occured."));
}
[6] @RestControllerAdvice 를 넣은 별도의 예외처리 클래스를 만든다.
- 사용법은 간단하다. 클래스에 @RestControllerAdvice를 넣고, 예외처리 핸들러 메소드를 모두 가져오면 된다.
- 각 컨트롤러는 지정한 예외(ex.WebSampleException)에 맞는 ExceptionHandler를 찾는다.
- 만약 없다면, 자신의 부모 예외를 찾고, 그도 없을 시에는 Exception을 찾아간다.
package com.example.websample.exception;
import com.example.websample.dto.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalAccessException.class)
public ResponseEntity<ErrorResponse> handleIllegalAccessException(
IllegalAccessException e){
log.error("IllegalAccessException is occured.", e);
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body(new ErrorResponse(ErrorCode.TOO_BIG_ID_ERROR,
"IllegalAccessException is occured."));
}
@ExceptionHandler(WebSampleException.class)
public ResponseEntity<ErrorResponse> handleWebSampleException(
WebSampleException e){
log.error("WebSampleException is occured.", e);
return ResponseEntity
.status(HttpStatus.INSUFFICIENT_STORAGE)
.body(new ErrorResponse(ErrorCode.TOO_BIG_ID_ERROR,
"WebSampleException is occured."));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleWebSampleException(
Exception e){
log.error("WebSampleException is occured.", e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse(ErrorCode.TOO_BIG_ID_ERROR,
"Exception is occured."));
}
}
| 정리
- 오늘날의 개발 환경에서는 주로 REST API를 통한 예외처리 및 전역적인 예외처리를 하는 것이 일반적이다. - 개발자는 비즈니스로직에 맞는 커스텀 예외를 만들 수 있다.(Java의 다양한 예외를 상속) 1) 커스텀 예외 Enum -- ex. ErrorCode 2) 전달할 예외 데이터 객체 -- ex. ErrorResponse 3) 커스텀 에외 클래스 -- ex. WebSampleException extends RunTimeException - 어플리케이션의 전역적인 예외처리를 위해서는, @RestControllerAdvice를 만들고, 예외 핸들러 메소드들을(@ExceptionHandler) 넣어준다. |
[ 참고 및 출처 ]
- 부트캠프 수업을 들은 후 정리
- Http 응답 코드 https://namu.wiki/w/HTTP/%EC%9D%91%EB%8B%B5%20%EC%BD%94%EB%93%9C
'Framework > Spring' 카테고리의 다른 글
[스프링] 개발을 시작하기 전에 - 요구 사항 분석, 기본 구조 잡기 (1) | 2022.09.15 |
---|---|
[스프링] 프로젝트 전 꼭 알아두면 좋은 것들 (0) | 2022.09.13 |
[스프링] 스프링 MVC - 필터, 인터셉터 (1) | 2022.09.11 |
[스프링] 스프링MVC - HTTP 요청 및 응답 (1) | 2022.09.11 |
[스프링] 스프링의 주요기술 (0) | 2022.09.05 |