@RequestMapping(value = "/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response) {
log.info("API errorPage 500");
Map<String, Object> result = new HashMap<>();
Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);
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));
}
produces = MediaType.APPLICATION_JSON_VALUE를 통해 HTTP Header의 Accept값이 application/json일 때 해당 메서드가 호출된다.Map을 만들고, status, mesaage에 값을 할당 했다.Jackson라이브러리는 Map을 JSON 구조로 변환할 수 있다.ResponseEntity로 응답하기 때문에 메시지 컨버터가 동작하여 JSON이 반환된다.스프링 부트가 제공하는
BasicErrorController에 기본 API 예외처리도 제공한다.
{
"timestamp": "2021-04-28T00:00:00.000+00:00",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"trace": "java.lang.RuntimeException: 잘못된 사용자\n\tat hello.exception.web.api.ApiExceptionController.getMember(ApiExceptionController.java:19...,"
"message": "잘못된 사용자"
,
"path": "/api/members/ex"
}
HandlerExceptionResolver 인터페이스를 상속받아 개발한다.public interface HandlerExceptionResolver { ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
hanlder: 핸들러(컨트롤러) 정보Exception ex: 핸들러(컨트롤러)에서 발생한 예외
package hello.exception.resolver;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import java.io.IOException;
@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
log.info("call resolver", ex);
try {
if (ex instanceof IllegalArgumentException) {
log.info("IllegalArgumentException resolver to 400");
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
} catch (IOException e) {
log.error("resolver ex", e);
e.printStackTrace();
}
return null;
}
}
ExceptionResolver가 ModelAndView를 반환하는 이유는 try-catch를 하듯이 Exception을 처리하여 정상흐름처럼이 변경하는 것이 목적이기 때문이다.ModelAndView를 반환하면 뷰를 렌더링 하지 않고, 정상 흐름으로 서블릿이 리턴.ModelAndView에 View, Model등의 정보를 지정해서 반환하면 뷰를 렌더링.ExceptionResolver를 찾아서 실행. 만약 처리할 수 있는 ExceptionResolver가 없으면 예외처리가 안되고, 기존에 발생한 예외를 서블릿 밖으로 던진다.response.sendError(xxx)호출로 변경해서 서빌릿에서 상태 코드에 따른 오류처리를 하도록 위임./error호출)ModelAndView에 값을 채워 예외에 따른 오류하면 뷰 렌더링response.getWriter().println("hello")처럼 HTTP 응답 바이데 직접 데이터를 넣어 처리 가능. (JSON응답시 API응답 처리 가능)ExceptionResolver 사용을 위해 WebMvcConfigurer 통해 등록.extendHandlerExceptionResolvers 를 통해 등록.configureHandlerExceptionResolvers(..) 를 사용하면 스프링이 기본으로 등록하는 ExceptionResolver 가 제거@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
resolvers.add(new UserHandlerExceptionResolver());
}
.
.
.
}
스프링이 제공하는 API예외 처리를 위한 애노테이션이다.
스프링은ExceptionHandlerExceptionResolver를 기본제공하고,ExceptionResolver중에서 우선순위도 가장 높다.
package hello.exception.exhandler;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ErrorResult {
private String code;
private String message;
}
@ExceptionHandler 애노테이션을 설정하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면된다. @ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalArgumentException(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e) {
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
@ControllerAdvice, @RestControllerAdvice를 사용하면 분리할 수 있다.package hello.exception.exhandler.davice;
import hello.exception.exception.UserException;
import hello.exception.exhandler.ErrorResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalArgumentException(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e) {
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandler(Exception e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "내부 오류");
}
}
@ExceptionHandler, @InitBinder기능을 부여해주는 역할을 한다.// 애노테이션 지정
@ControllerAdvice(annotations = RestController.class)
// 패키지 지정
@ControllerAdvice("org.example.controllers")
// 클래스 지정
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})