(1) Exception Handling

2021. 3. 22. 13:10개발공부/Spring


예외 처리(Exception Handling)은 Controller에서 바로 처리(handling)하지 않는 것이 좋습니다. 중복이 발생해 클린하지 않은 코드가 되기 때문입니다. 횡단 관심사(Aspect)로 두어 한 곳에서 처리하는 것이 바람직합니다. 처리할 수 있는 방법은 3가지가 있습니다.


- Http Status Return 방법 : 예외 클래스를 만들어 @ResponseStatus 어노테이션을 클래스에 부여합니다. Controller 클래스에서 예외가 발생할 것 같은 지점에 해당 클래스 객체를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Exception
 @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
 public class OrderNotFoundException extends RuntimeException {
     // ...
 }
 
//Controller
 @RequestMapping(value="/orders/{id}", method=GET)
 public String showOrder(@PathVariable("id"long id, Model model) {
     Order order = orderRepository.findOrderById(id);
 
     if (order == nullthrow new OrderNotFoundException(id);
 
     model.addAttribute(order);
     return "orderDetail";
 }
 
cs

- Controller Based 방법 : @ExceptionHandler 어노테이션을 이용해 Controller 클래스에 예외 처리 메서드를 추가하는 방법입니다.

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
32
33
34
35
36
37
38
39
40
@Controller
public class ExceptionHandlingController {
 
  // @RequestHandler methods
  ...
  
  // Exception handling methods
  
  // Convert a predefined exception to an HTTP Status code
  @ResponseStatus(value=HttpStatus.CONFLICT,
                  reason="Data integrity violation")  // 409
  @ExceptionHandler(DataIntegrityViolationException.class)
  public void conflict() {
    // Nothing to do
  }
  
  // Specify name of a specific view that will be used to display the error:
  @ExceptionHandler({SQLException.class,DataAccessException.class})
  public String databaseError() {
    // Nothing to do.  Returns the logical view name of an error page, passed
    // to the view-resolver(s) in usual way.
    // Note that the exception is NOT available to this view (it is not added
    // to the model) but see "Extending ExceptionHandlerExceptionResolver"
    // below.
    return "databaseError";
  }
 
  // Total control - setup a model and return the view name yourself. Or
  // consider subclassing ExceptionHandlerExceptionResolver (see below).
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception ex) {
    logger.error("Request: " + req.getRequestURL() + " raised " + ex);
 
    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", ex);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }
}
cs

- Global Exception 방법 : 애플리케이션 전체에 걸쳐 동일한 예외 처리를 사용할 수 있도록 해줍니다. @ControllerAdvice를 사용해줍니다. 어노테이션이 부여된 클래스를 Spring 프레임워크가 자동으로 @Controller 클래스들에 적용시킵니다. 부여된 클래스에서 @ExceptionHandler로 각각의 예외들을 메서드로 만들어 줍니다. 그리고 @Controller에서 예외 발생 시 대응하는 메서드가 실행됩니다.(210606 : 내용추가중 이어서 작성)

1
2
3
4
5
6
7
8
9
10
11
12
@ControllerAdvice @RequestMapping(produces = "application/vnd.error+json"public class PersonControllerAdvice {
    @ExceptionHandler(PersonNotFoundException.classpublic ResponseEntity < VndErrors > notFoundException(final PersonNotFoundException e) {
        return error(e, HttpStatus.NOT_FOUND, e.getId().toString());
    }
    private ResponseEntity < VndErrors > error(final Exception exception, final HttpStatus httpStatus, final String logRef) {
        final String message = Optional.of(exception.getMessage()).orElse(exception.getClass().getSimpleName());
        return new ResponseEntity < > (new VndErrors(logRef, message), httpStatus);
    }
    @ExceptionHandler(IllegalArgumentException.classpublic ResponseEntity < VndErrors > assertionException(final IllegalArgumentException e) {
        return error(e, HttpStatus.NOT_FOUND, e.getLocalizedMessage());
    }
}
cs

 

This class provides @ExceptionHandler methods globally to all controllers, as (which you can’t see from this code alone) there are multiple controllers that throw PersonNotFoundException, which need handling. The RequestMapping annotation here is used to set the content type that is returned by the ResponseEntity. These could be added to the methods themselves instead of the different types needed to be returned. Each instance of @ExceptionHandler marks an exception that it is in charge of dealing with. The methods in this example simply catch the exception and take its error message and combine it with an appropriate response code.

Without this code, when PersonNotFoundException is thrown, the following output is produced (along with a stacktrace in your log).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
    "timestamp": "2017-09-12T13:33:40.136+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Person could not be found with id: 1",
    "path": "/people/1"
}
 
 
[
    {
        "logref": "1",
        "message": "Person could not be found with id: 1",
        "links": []
    }
]
cs

 

참고자료 :

DZone - Global Exception Handling with ControllerAdvice

Spring blog - Exception Handling in Spring MVC