simDev1234
심플하고 차분하게
simDev1234
전체 방문자
오늘
어제
  • 분류 전체보기
    • Computer Science
      • Basic Math
      • Data Structure
      • Algorithm
      • Database
      • OS
    • Language
      • Java
      • Kotlin
      • SQL
    • Framework
      • Spring
      • Orm&Mapper
      • 프로젝트로 스프링 이해하기
      • 스프링 라이브러리
    • Infra
      • Cloud
      • Docker
      • Redis
      • AWS, Azure
      • Device
    • Etc
      • CleanCoding
    • Git,Github

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 스프링
  • 404
  • 참조타입
  • 자바프로그램
  • 자바
  • controllerTest
  • JVM메모리구조
  • 컨트롤러
  • scanner #next() #nextLine()
  • 참조변수
  • 자바프로그래밍
  • null
  • 자바메모리구조

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
simDev1234

심플하고 차분하게

Framework/Spring

[스프링] 스프링 MVC - 예외처리

2022. 9. 11. 11:53

|  예외처리

- 예외 : 프로그램이 예상하지 못한 상황을 만났을 때 오류를 처리하는 것

- 자바에서는 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
    'Framework/Spring' 카테고리의 다른 글
    • [스프링] 개발을 시작하기 전에 - 요구 사항 분석, 기본 구조 잡기
    • [스프링] 프로젝트 전 꼭 알아두면 좋은 것들
    • [스프링] 스프링 MVC - 필터, 인터셉터
    • [스프링] 스프링MVC - HTTP 요청 및 응답
    simDev1234
    simDev1234
    TIL용 블로그. * 저작권 이슈가 있는 부분이 있다면 댓글 부탁드립니다.

    티스토리툴바