티스토리 뷰

JsonInclude

@JsonInclude(JsonInclude.Include.NON_NULL) 어노테이션을 붙여주면 null 인 결과는 response 에 나타나지 않는다. 

@JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonProperty("errors")
    private List<CustomFieldError> customFieldErrors;

Exception Handler 구현

현재 참여하고 있는 프로젝트에서 일관성 있는 오류를 응답으로 주고 try-catch 문을 간결하게 / 혹은 생략하기 위해서 Exception Handler 를 구현하게 됐다. 

 

< 기존에 Errorcode 는 존재하고 있는 상태였다 >

 

* 진행 상황

- InvalidParameter Error (구현 중)

- DataNotFoundException (구현 완료)

- user 관련 오류 처리 (계획 중)

 

ExceptionHandler 에는

 

package com.onjung.onjung.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.xml.crypto.Data;
import java.util.NoSuchElementException;

@ControllerAdvice
public class ControllerExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    protected ResponseEntity<ErrorResponse> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        logger.error("handleHttpRequestMethodNotSupportedException", e);

        final ErrorResponse response
                = ErrorResponse
                .create()
                .status(HttpStatus.METHOD_NOT_ALLOWED.value())
                .message(e.getMessage());

        return new ResponseEntity<>(response, HttpStatus.METHOD_NOT_ALLOWED);
    }

    @ExceptionHandler(DataNotFoundException.class)
    protected ResponseEntity<ErrorResponse> handleDataNotFoundException(DataNotFoundException e) {
        logger.error("handleDataNotFoundException", e);

        final ErrorResponse response
                = ErrorResponse
                .create()
                .status(HttpStatus.NOT_FOUND.value())
                .message(e.getMessage());

        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
    }

    // 파라미터 유효성 검사 통과 못한 경우 처리하기
    @ExceptionHandler({InvalidParameterException.class, MethodArgumentNotValidException.class})
    protected ResponseEntity<ErrorResponse> handleInvalidParameterException(InvalidParameterException e) {
        logger.error("handleInvalidParameterException", e);

        ErrorCode errorCode = e.getErrorCode();

        ErrorResponse response
                = ErrorResponse
                .create()
                .status(errorCode.getStatus())
                .message(e.toString());
//                .errors(e.getErrors());

        return new ResponseEntity<>(response, HttpStatus.resolve(errorCode.getStatus()));
    }


    // 나머지 오류 전부
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> handleException(Exception e) {
        logger.error("handleException", e);

        ErrorResponse response
                = ErrorResponse
                .create()
                .status(HttpStatus.INTERNAL_SERVER_ERROR.value())
                .message(e.toString());
        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
    }

}

위와 같이 해줬다.

e.getErrors 에 주석처리를 해둔 이유는 응답에서 Error 부분이 너무 길어서 오히려 가독성을 해치는 느낌이 있어서 뺄까 고려 중이다.

 

ErrorResponse

 

package com.onjung.onjung.exception;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
public class ErrorResponse {

    private LocalDateTime timestamp = LocalDateTime.now(); // 오류 발생 시점

    private String message; // 예외 메시지

    private int status; // 상태코드

    // 어떤 필드가 valid 를 통과하지 못했는지
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonProperty("errors")
    private List<CustomFieldError> customFieldErrors;

    public ErrorResponse(){
    }

    static public ErrorResponse create(){
        return new ErrorResponse();
    }

    public ErrorResponse message(String message){
        this.message = message;
        return this;
    }

    public ErrorResponse status(int status){
        this.status = status;
        return this;
    }

    public ErrorResponse errors(Errors errors) {
        setCustomFieldErrors(errors.getFieldErrors());
        return this;
    }


    public void setCustomFieldErrors(List<FieldError> fieldErrors){

        customFieldErrors = new ArrayList<>();

        fieldErrors.forEach(error -> {
            customFieldErrors.add(new CustomFieldError(
                    error.getCodes()[0],
                    error.getRejectedValue(),
                    error.getDefaultMessage()
            ));
        });

    }

    @Getter
    public static class CustomFieldError {

        private String field;
        private Object value;
        private String reason;

        public CustomFieldError(String field, Object value, String reason) {
            this.field = field;
            this.value = value;
            this.reason = reason;
        }
    }
}

이전과의 차이?

 

위를 기반으로 컨트롤러들을 수정해줬다

그 중 하나를 예로 들어보자면, 기존의 코드는 아래와 같았다.

 

 @GetMapping("{itemId}")
    public ResponseEntity readItem(@PathVariable("itemId") Long itemId){
        try{
            Optional<Item> item = itemService.readItem(itemId);
            return ResponseEntity.status(HttpStatus.OK).body(item);
        }catch (Exception e)
        {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Exception raised in ItemController/readItem");
        }
    }

 

이의 경우에는, DataNotFoundException 인 경우에만 해당되는 응답이라고 볼 수 있다. 

그러나 404가 아닌 여러 오류가 발생할 수 있고 body 에는 오직 문자열로 어디에서 오류가 발생했는지 담기기 때문에 코드가 길고 비효율적이라고 생각했다.

 

@GetMapping("{itemId}")
    public ResponseEntity readItem(@PathVariable("itemId") Long itemId) {
        try {
            Optional<Item> item = itemService.readItem(itemId);
            return ResponseEntity.status(HttpStatus.OK).body(item);
        } catch (InterruptedException e) {
            return ResponseEntity.status(HttpStatus.GATEWAY_TIMEOUT).body(e.getMessage());
        }
    }

그러나 위와 같이 수정하게 되면, InterruptedException 이 아닌 오류들은 전부 Exception Handler 에서 처리하게 되고,

DataNotFoundException 인지, 다른 예외처리인지 확인할 수 있다.

 

그러나 DataNotFoundException 과 같은 명시해서 좋을 예외들은 catch 문으로 작성해주고, throw 하는 것도 좋을 것 같다는 피드백이 있어서 이에 따라 추후에 수정해보고자 한다. 

 

오류 잡기

중간에 계속 오류가 났었는데, 그 오류는 다음과 같았다.

 

ExceptionHandlerExceptionResolver : Failure in @ExceptionHandler ..

 

이와 관련해서 구글링을 해보니, ErrorResponse 에 Getter 가 있어야 한다고 해서 이를 추가해줬더니 해결됐다.

 

Getter 를 붙여줘야 하는 이유는, Json 구조로 객체 형태를 반환하기 위해서 Jackson 라이브러리는 ObjectMapping API 를 사용하는데 이는 Getter / Setter 를 기준으로 작동하기 때문에 Getter 가 없는 ErrorResponse 를 Json 구조로 반환할 수 없어서 발생하는 오류였다. 

 

https://yuja-kong.tistory.com/entry/Spring-Boot-ExceptionHandler-%EC%A0%81%EC%9A%A9-%EC%8B%9C-HttpMediaTypeNotAcceptableException-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0

 

[SpringBoot] @ExceptionHandler 적용 시 HttpMediaTypeNotAcceptableException 에러 해결

에러를 처리하는 곳에서 에러가 났다...😱 (이것은.. 무한 에러 루프..?) Spring Boot에서 공통 익셉션을 처리하기 위해 @RestControllerAdvice, @ExceptionHandler를 사용하여 개발하고 있었다. 익셉션처리가 잘

yuja-kong.tistory.com

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함