정상 응답과 오류 응답
@RestController
public class ApiExceptionController {
@GetMapping("/api/members/{id}")
public MemberDto getMember(@PathVariable("id") String id){
if(id.equals("ex")){
throw new RuntimeException("잘못된 사용자");
}
if(id.equals("bad")){
throw new IllegalArgumentException("잘못된 입력 값");
}
if(id.equals("user-ex")){
throw new UserException("사용자 오류");
}
return new MemberDto(id,"hello " + id);
}
@Data
@AllArgsConstructor
static class MemberDto{
private String memberId;
private String name;
}
}
- 정상 호출 시 JSON 형식으로 데이터가 정상 반환
- 그러나 예외 발생 호출 시 JSON 형식이 아닌 오류 페이지 HTML이 반환
오류 응답 변환
@Slf4j
@Controller
public class ErrorPageController {
...
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response){
log.info("errorPage 500");
return "error-page/500";
}
// 추가
@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE) // 해당 타입인 경우 url이 같으면 해당 메소드가 우선순위
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일 때 해당 Method 호출
- HTTP Header의 Accept의 값이 application/json이 아닌 경우 기존 오류 응답인 HTML 반환
- ResponseEntity를 사용하여 JSON 반환
HandlerExceptionResolver
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
@Nullable Object handler,
Exception ex);
}
- 컨트롤러 밖으로 던져진 예외를 해결하고, 동작 방식을 변경하도록 Spring MVC에서 제공하는 인터페이스
- 줄여서 ExceptionResolver
- handler : 핸들러(컨트롤러) 정보
- ex : 핸들러(컨트롤러)에서 발생한 예외
사용 예시
@Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception 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");
}
return null;
}
}
...
@Slf4j
public class UserHandlerExceptionResolver implements HandlerExceptionResolver {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try{
if(ex instanceof UserException){
log.info("UserException resolver to 400");
String acceptHeader = request.getHeader("accept");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
if("application/json".equals(acceptHeader)){
Map<String, Object> errorResult = new HashMap<>();
errorResult.put("ex", ex.getClass());
errorResult.put("message", ex.getMessage());
String result = objectMapper.writeValueAsString(errorResult); // Json to String
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().write(result);
return new ModelAndView();
} else{
// TEXT, HTML 등
return new ModelAndView("error/500");
}
}
} catch(IOException e){
log.error("resolver ex", e);
}
return null;
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
resolvers.add(new UserHandlerExceptionResolver());
}
}
- IllegalArgumentException이 발생 시
- HandlerExceptionResolver 적용 전 : HTTP 상태 코드 500 반환
- HandlerExceptionResolver 적용 후 : response.sendError(400)을 호출해서 HTTP 상태 코드를 400으로 지정하고 빈 ModelAndView를 반환
- ModelAndView를 반환하는 이유는 try - catch를 하듯이 Exception을 처리해서 정상 흐름처럼 변경
- 빈 ModelAndView 반환 시 : View를 렌더링 하지 않고 정상 흐름으로 서블릿이 return
- ModelAndView 반환 시 : View를 렌더링
- null 반환 시 : 다음 ExceptionResolver를 찾아서 실행. 처리할 수 있는 ExceptionResolver가 없으면 예외 처리가 안되고, 기존에 발생한 예외를 서블릿 밖으로 던짐
- 활용 예시
- 예외를 response.sendError(xxx) 호출로 변경해서 서블릿에서 상태 코드에 따른 오류를 처리하도록 위임
- ModelAndView에 값을 채워서 예외에 따른 새로운 오류 화면 View 렌더링 제공
- HTTP 요청 헤더의 ACCEPT 값에 따라서 오류 지정 및 오류 페이지 지정 가능
- response.getWrite().println(xxx) 처럼 HTTP 응답 Body에 직접 데이터를 넣어주어 API 응답 처리 가능
- ExceptionResolver로 예외를 해결해도 postHandle()은 호출되지 않음
'Spring > MVC' 카테고리의 다른 글
파일 업로드 (0) | 2024.08.05 |
---|---|
스프링 API 예외 처리 (0) | 2024.07.21 |
HttpEntity (0) | 2024.07.21 |
서블릿 예외 처리 (0) | 2024.07.20 |
스프링 인터셉터 (0) | 2024.07.11 |