Controller Based Exception Handling

정리공간·2022년 6월 1일

전역 REST Api 예외 처리
restApi 에러는 일반적인 에러가 아닌 미리 정해진 스펙으로 httpStatus code와 함께 에러를 표현해야 하므로
일반적인 에러표현과 구분되어야 한다

에러처리를 위한 DTO 생성

@Getter
@NoArgsConstructor
public class ErrorResponse {
    private int errorCode;
    private String errorMessage;
    private String detailMessage;
 
    public ErrorResponse(int errorCode, String errorMessage, String detailMessage) {
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
        this.detailMessage = detailMessage;
    }
}

추상 exception 생성

public abstract class AbstractRestException extends RuntimeException {
 
    AbstractRestException(String msg) {
        super(msg);
    }
    AbstractRestException(Throwable throwable) {
        super(throwable);
    }
 
    AbstractRestException(String msg, Throwable throwable) {
        super(msg, throwable);
    }
 
    public abstract HttpStatus getHttpStatus();
}

AbstractRestException 를 상속받은 excetion 구현.


public class ConflictException extends AbstractRestException {
    @Override
    public HttpStatus getHttpStatus() {
        return HttpStatus.CONFLICT; // 알맞은 httpStatusCode 선택
    }
 
    public ConflictException(String msg, Throwable throwable) {
        super(msg, throwable);
    }
    public ConflictException(String msg) {
        super(msg);
    }
    public ConflictException(Throwable throwable) {
        super(throwable);
    }
}

나머지 인증인가 등의 exception 추가

서비스 로직에서 생성한 exception을 던져줌

public DocnoDto saveDocno(DocnoDto docnoDto) {
    if(Boolean.TRUE.equals(this.isExistsSaveDocno(docnoDto))) {
        throw new ConflictException("The data you requested already exists in the system.");
    }
    Docno docno = docnoMapper.toEntity(docnoDto);
    return docnoMapper.toDto(docnoRepository.save(docno));
}

exception을 받아줄 controller생성

@ControllerAdvice("platform.docno.app.controller")
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ExceptionHandlerController {
 
    @ExceptionHandler(AbstractRestException.class)
    protected ResponseEntity<ErrorResponse> handleApiException(AbstractRestException e) {
        return ResponseEntity
                .status(e.getHttpStatus())
                .body(new ErrorResponse(
                        e.getHttpStatus().value(),
                        e.getHttpStatus().getReasonPhrase(),
                        e.getMessage())
                );
    }
 
    @ExceptionHandler(Exception.class)
    protected ResponseEntity<ErrorResponse> exception(Exception e) {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(
                        new ErrorResponse(
                                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                                HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
                                e.getLocalizedMessage()
                        )
                );
    }
}

@ControllerAdvice : @Controller Bean을 도와주게 한다. value값으로 범위를 선정할 수 있음.
@Order : @ControllerAdvice가 붙은 빈이 여러개일 때 우선순위를 정한다.
@Exceptionhandler : value에 해당하는 exception을 해당 메소드가 받아 처리

Controller.java

@PutMapping
public ResponseEntity<DocnoDto> saveDocno(@RequestBody DocnoDto docnoDto){
    DocnoDto rtnDto = docnoService.saveDocno(docnoDto);
    return ResponseEntity.status(HttpStatus.CREATED).body(rtnDto);
}

테스트 코드

@Test
void A01_문서번호_저장_성공() throws Exception {
    DocnoDto docnoDto = DocnoDto.builder()
            .code("TEST1")
            .name("테스트1")
            .iniCycle("mon")
            .currentNumber(1)
            .startNumber(1)
            .endNumber(9999)
            .expression("#yyyyMM#@SEQ@")
            .isUse(true)
            .tenantId(tenantId)
            .build();
 
    mockMvcPerform(put("/docnos")
            .content(new ObjectMapper().writeValueAsString(docnoDto)))
            .andExpect(status().isCreated());
}

0개의 댓글