JUST WRITE

@ControllerAdvice Exception 처리 본문

Programing/Spring

@ControllerAdvice Exception 처리

천재보단범재 2022. 1. 3. 13:20

@ControllerAdvice

@ControllerAdvice

@ControllerAdvice를 통해 Application 내 모든 Controller에서 공유할 수 있는 Method를 정의할 수 있다.

@ExceptionHandler, @InitBinder, @ModelAttribute 등의 Annotation을 활용한 Method를 정의할 수 있다.

이번 글에서는 그중에서 @ControllerAdvice를 통해 Global하게 Exception을 처리하는 방법을 정리해보았다.

@ExceptionHandler

Spring 3.2부터 @ControllerAdvice로 @ExceptionHandler를 Global하게 사용하도록 지원해준다.

덕분에 흩어져 있던 @ExceptionHandler를 하나로 모을 수 있게 되었다.

추가적으로

  • Status Code뿐만 아니라 Response의 Body까지 control 가능
  • Exception 종류에 따라 1개의 Method로 Handle 가능
  • RESTful ReponseEntity Response 사용하기 수월

이제부터 Code를 통해서 알아보려 한다.

먼저 사용자 정의 Exception을 만든다.

package com.jsw.app.exception;

import org.springframework.http.HttpStatus;

public class CustomException extends RuntimeException {
	// Response HttpStatus
    private HttpStatus status;
    private String messageCode;

    public CustomException (HttpStatus status, String messageCode) {
        this.status = status;
        this.messageCode = messageCode;
    }

    public HttpStatus getStatus () {
        return this.status;
    }

    public void setStatus (HttpStatus status) {
        this.status = status;
    }

    public String getMessageCode() {
        return this.messageCode;
    }
    
}

RuntimeException을 상속해서 정의해보았다.

여기에 상황에 따라 Response에 넣을 Status와 Body에 담을 Message를 field로 추가하였다.

Message는 Spring의 MessageSource를 활용해 propeties로 관리하였다.

(Message 관리에 대한 부분은 추후에 글로 따로 정리)

 

@RestControllerAdvice를 통해 ExceptionHandler를 만든다.

package com.jsw.app.handler;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import com.jsw.app.exception.CustomException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler(value = { CustomException.class })
    protected ResponseEntity<Map<String, String>> handleCustomException(CustomException e) {
        String errMsg = messageSource.getMessage(e.getMessageCode(), null, Locale.getDefault());
        log.error("Throw Exception!!! HTTPStatus: {}, Message: {}", e.getStatus(), errMsg);
        
        Map<String, String> messageMap = new HashMap<>();
        messageMap.put("message", errMsg);
        
        return ResponseEntity.status(e.getStatus()).body(messageMap);
    }

}

@RestControllerAdvice@ControllerAdvice에서 @ResponseBody를 추가한 Annotation이다.

동일하게 수행하면서 @ResponseBody를 통해 객체를 Return 할 수 있다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {

...
...
...

}

위에 정의한 GlobalExceptionHandler를 다시 살펴본다.

@ExceptionHandler Annotation을 통해 CustomException이 발생하면 Method에서 처리하도록 정의하였다.

CustomException 내 field 값들로 ResponseEntity를 만들어 Return 한다.

 

Controller에서 따로 Exception 처리를 하지 않았다.

package com.jsw.app.controller;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import com.jsw.app.entity.Request;
import com.jsw.app.service.RequestService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TaxiController {

    @Autowired
    private RequestService requestService;

    @GetMapping("/taxi-requests")
    public ResponseEntity<List<Request>> getCallList () {
        return ResponseEntity.ok(requestService.getRequestList());
    }

    @PostMapping("/taxi-requests")
    public ResponseEntity<Request> callTaxi (HttpServletRequest servletRequest, @RequestParam("address") String address) {
        String header = servletRequest.getHeader("Authorization");
        return ResponseEntity.ok(requestService.makeRequet(address, header));
    }

    @PostMapping("/taxi-requests/{taxiRequestId}/accept")
    public ResponseEntity<Request> acceptCall (HttpServletRequest servletRequest, @PathVariable("taxiRequestId") Long taxiRequestId) {
        String header = servletRequest.getHeader("Authorization");
        return ResponseEntity.ok(requestService.acceptRequest(taxiRequestId, header));
    }

}

CustomException 발생 부분을 살펴보면...

// 이미 다른 기사에 의해 바차 요청이 수락된 경우
if (request.getStatus().equals(RequestStatus.ACCEPT) && request.getDriverId() == null) {
	throw new CustomException(HttpStatus.CONFLICT, "err.request.unacceptableRequest");
}

	Member member = memberWrapper.get();

if (member.getUserType() != UserRole.DRIVER) {
	throw new CustomException(HttpStatus.FORBIDDEN, "err.request.onlyAcceptDriver");
}

해당 CustomException을 발생시키면 위에서 정의한 GlobalExceptionHandler에서 처리한다.

@ControllerAdvice와 @ExceptionHandler를 통해 Global하게 Exception를 처리해 보았다.

@ControllerAdvice는 주로 이렇게 Exception 처리할 때 사용하지만 다른 방식으로도 사용한다.

추후에 다른 방식에 대해서도 정리해보려 한다.

728x90
반응형

'Programing > Spring' 카테고리의 다른 글

Spring Boot Configuration with Jasypt  (0) 2022.01.13
HTTP/2.0 적용  (0) 2022.01.11
BeanFactory vs ApplicationContext  (0) 2021.12.21
Bean Annotations  (0) 2021.12.20
@Autowired  (0) 2021.10.22
Comments