본문 바로가기
Spring Framework/SPRING MVC

예외처리 (API)

by 거북이의 기술블로그 2024. 10. 7.
예외처리... HTTP API 응답

 

요약 정리

  • HTML / TEXT 형식의 예외 처리
    • BasicErrorController() 사용
  • API 형식의 예외처리
    • ExceptionHandlerExceptionResovler 사용 (@ExceptionHandler)

 

사전 준비

@Component
 public class MyCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
     
     @Override
     public void customize(ConfigurableWebServerFactory factory) {
         
         ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
         ErrorPage errorPageException = new ErrorPage(RuntimeException.class, "/error-page/500");
         
         factory.addErrorPages(errorPage404, errorPageException);
     }
     
     
 }
 
 
 
 @Slf4j
 @RestController
 public class ApiExceptionController {
      
      
      @GetMapping("/api/exception/{id}")
      public String apiExceptionTest(@PathVariable("id") String id){
           
           if (id.equals("ex"){
               throw new RuntimeException("잘못된 사용자"); // 예외 던짐 (RuntimeException)
           }
           
           return new MemberDto(id, "hello" + id);
       }
}

@DATA
@AllArgsConstructors
public class MemberDto{
     
     private String memberId;
     private String name;
}
  • Customizing을 통해서 Error 발생시 error-page 경로 설정
  • RuntimeException 예외 던짐

 

예외 API 응답 처리

 

1. 직접 Error 제어 Controller 추가

  • produces = MediaType.APPLICATION_JSON_VALUE 를 이용하여 api 요청을 Mapping 함
  • ResponseEntity 를 사용하므로 , 메시지 컨버터를 이용하여 json 형식으로 반환됨
@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500(HttpServletRequest request, HttpServletResponse response) {
     
     Map<String, Object> result = new HashMap<>();
     Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);
     
     // result에 api 응답을 담음
     result.put("status", request.getAttribute(ERROR_STATUS_CODE));
     result.put("message", ex.getMessage());
     
     Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
     
     return new ResponseEntity(result, HttpStatus.valueOf(statusCode));
 }

 

2. Spring boot 기본 제공 Error 제어 (BasicErrorController)

  • 기본값으로, /templates/error/ 하위에 에러페이지를 생성하면 Mapping 시켜준다.
    • application.properties 에 "server.error.path"부분을 추가해주면 경로를 변경할 수 있다
  • errorHtml()의 경우 accept헤더 값이 "text/html"인 경우에 호출된다. (view 페이지 제공)
  • error()의 경우 accept헤더 값이 "application/json"인 경우에 호출된다 (API 제공)
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {

    //...
    
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }
    
    //...
}
  • application properties 설정을 통해 더 자세한 오류 결과값 출력 가능
[application.properties]
server.error.include-binding-errors=always
server.error.include-exception=true
server.error.include-message=always
server.error.include-stacktrace=always

 

 

 3. Handler Exception Resolver

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
                                  @Nullable Object handler, Exception ex);
}

 

  • resolveException()메서드를 이용해서, exception 발생시 원하는 응답코드와 메시지를 ModelAndView형태로 반환이 가능
    • 반환 형태
      • null : null을 반환할경우, 다음 resolveException을 찾고 없으면 servlet 밖으로 예외처리가 됨
      • ModelAndView 빈 값 :  view 를 반환하지않고, 서블릿에 정상흐름으로 반환 됨
      • ModelAndView 값 : view를 렌더링한다. 
public class MyHandlerException implements HandlerExceptionResolver {
    
    @Override
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
                                  @Nullable Object handler, Exception ex){
    
    try{
        if (ex instanceOf IllegalArgumentException){
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); // 400번대 오류발생 (Illegal 예외시)
            return new ModelAndView();
        }
    }catch (IoException e){
        log.error("error");
    }
    
    return null; // 다음 resovleException
}

 

  • HandlerExceptionResolver 등록 방법 (WebConfig)
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        
        resolvers.add(new MyHandlerExceptionResolver());
    
    }
}

 

 

4. spring 제공 Exception Resolver

  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver
  • ExcetpionHandlerExceptionResolver -> 우선순위가 가장 높고, 자주 사용됨

 

ResponseStatusExceptionResolver

  • 역할
    • HTTP 상태코드 지정
  • 처리방법
    • @ResoposeStatus 예외
      • 직접 Exception을 만들때에 사용이 가능
      • 개발자가 직접 추가를 해줘야하는 부분이므로, 직접 구현이 어렵다면 ResponseStatusException을 활용
      • 추가적으로, reason에 messages.properties를 이용하여 작성이 가능
    • ResponseStatusException 예외
      • ResponseStatusException클래스를 이용하여, 직접 추가하지 못한 @ResponseStatus 대신하여 상태코드 지정
/*

** @ResponseStatus 사용 **

*/


//messages.properties
error.bad="잘못된 요청 오류"


// BadRequestException.class
//@ResponseStatus( code = HttpStatus.BAD_REQUEST, reason = "잘못된 요청 오류")
@ResponseStatus( code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException{
}


// TestController.class
@Controller
public class TestController{
     
     @GetMapping("/api/responseStatus")
     public String testException(){
           throw new BadRequestException;
     }
}

 

/*

** ResponseStatusException 사용 **

*/

//messages.properties
error.bad="잘못된 요청 오류"


//ResponseExceptionResolver
//ResponseResovlerController

@Controller
public class ResponseResolverController{
     
     @GetMapping("/api/responseResolver")
     public String testException(){
         throw new ResponseStatusException(HttpStatus.NOT_FOUND,"error.bad", new IllegalArgumentException());
     }
}

 

 

DefaultHandlerExceptionResolver

@Controller
public class DefaultController{

    @GetMapping("/api/default-handler-ex")
    public String defaultException(@RequestParam Integer data) {
    //data -> typeMismatch ( data ==> String )
        return "ok";
    }
}
  • 스프링 내부적으로 자동으로 발생시켜주는 error Resolver
  • 주로, TypeMismatch 로 인해 발생한다
[응답결과]
{ "status": 400, "error": "Bad Request", "exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException", "message": "Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'; nested exception is java.lang.NumberFormatException: For input string: \"hello\"", "path": "/api/default-handler-ex" }

 

 

 

ExceptionHandlerExceptionResolver

  • HttpServletResponse에 .sendError() 적용할 필요 없음
  • ExceptionResolver를 이용하여 ModelAndView를 반환했어야 했지만, 그럴 필요가 없어짐
  • 컨트롤러마다 다르게 예외 처리 방식 적용이 가능 ( 기존에는 RuntimeException을 예외처리 해놓으면 어떠한 컨트롤러이든지 같은예외 처리가 이루어짐)

 


@RestControllerAdvice
public class ExControllerAdvice {
    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public ErrorResult illegalExHandle(IllegalArgumentException e) {
        return new ErrorResult("BAD", e.getMessage());
    }
     
    @ExceptionHandler
    public ResponseEntity<ErrorResult> userExHandle(UserException e) {
        ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
        return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
    }
    
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler
    public ErrorResult exHandle(Exception e) {
        return new ErrorResult("EX", "내부 오류"); 
    }
}
  • @RestControllerAdvice를 이용하여, 기존의 Controller와 분리하여 관리가 가능해짐
    • @RestControllerAdvice 또는 @ControllerAdvice 사용 가능
    • @RestControllerAdvice는 @ResponseBody가 더 추가 된 것임
  • @ResponseStatus를 이용하여 HttpStatus 상태코드 지정 가능
  • @ExceptionHandler를 사용하여, 예외 처리 지정이 가능
    • 예외처리 지정을 하지 않았을 경우, 파라미터를 기준으로 적용됨
    • @ExceptionHandler의 경우 여러 Exception들을 적용할 수 있음
    • ( ex @ExceptionHandler(@ExceptionHandler.class, @BadRequestException.class) )
  • ResponseEntity를 사용하여 errorResult 및 Http 상태코드를 지정 가능

 

대상 컨트롤러 지정 방법

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
 
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
 
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

 

'Spring Framework > SPRING MVC' 카테고리의 다른 글

Formatter (포맷터)  (0) 2024.10.08
Spring Type Converter (타입 형변환)  (0) 2024.10.08
예외처리 (web page)  (0) 2024.10.07
Spring Intercept  (0) 2024.10.07