예외처리

JIWOO YUN·2024년 3월 11일
0

SpringMVC2

목록 보기
22/26

서블릿 예외처리

  • 스프링이 아닌 순수 서블릿 컨테이너가 예외를 처리하는 방법

2가지 방식이 존재한다.

  • Exception(예외)
  • response.sendError(Http 상태 코드, 오류 메시지)

Exception

자바 직접 실행

  • 자바의 메인 메서드를 직접 실행하는 경우 main이라는 이름의 쓰레드가 실행
    • 실행도중 예외를 잡지 못하고 처음 실행한 main() 메서드를 넘어서 예외가 던져질 경우 -> 예외 정보를 남기고 해당 스레드 종료

웹 어플리케이션

  • 웹 애플리케이션의 경우 사용자 요청별로 별도의 스레드가 할당되고 서블릿 컨테이너 안에서 실행

    • 애플리케이션에서 예외가 발생했을 때 , try - catch 로 예외를 잡아서 처리하면 아무 문제가 없지만, 애플리케이션에서 예외를 잡지 못하고 서블릿 밖으로 까지 예외가 전달될 경우 어떻게 동작하는가?

    • WAS(여기까지 전파)  <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
    • WAS 까지 예외가 전달되는 문제가 발생한다.

서블릿 예외 컨트롤러 추가

@Slf4j
@Controller
public class ServletExController {
    
    @GetMapping("/error-ex")
    public void errorEx(){
        throw new RuntimeException("예외 발생");
    }
}
  • 톰캣이 기본으로 제공하는 오류화면을 볼 수있음.

response.sendError(Http 상태 코드, 오류 메시지)

  • 오류 발생시 HttpServletResponse 가 제공하는 sendError 라는 메서드를 사용해도 된다.
  • 이것을 호출한다고 당장 예외가 발생하는 것은 아니지만, 서블릿 컨테이너에서 오류가 발생했다는 점을 전달 할 수있음.
    • 구조
      • response.sendError(Http 상태코드)
      • response.sendError(Http 상태코드, 오류 메시지)

서블릿 예외 컨트롤러에 response.SendError 추가

@Slf4j
@Controller
public class ServletExController {
    
    @GetMapping("/error-ex")
    public void errorEx(){
        throw new RuntimeException("예외 발생");
    }

    @GetMapping("/error-404")
    public void error404(HttpServletResponse response) throws IOException{
        response.sendError(404,"404 오류");
    }

    @GetMapping("/error-500")
    public void error500(HttpServletResponse response) throws IOException{
        response.sendError(500,"500 오류");
    }
}

SendError 흐름

WAS(SendError 호출 확인) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(response.SendError)

response.SendError() 호출시 response 내부에는 오류가 발생했다는 상태를 저장.

  • 서블릿 컨테이너는 고객에게 응답전에 response에 sendError() 가 호출되었는지 확인 -> 호출되었다면 설정한 오류코드에 맞추어 기본 오류 페이지를 보여줌.

서블릿 예외 처리 - 오류 화면 제공

  • 서블릿은 Exception(예외) 가 발생해서 서블릿 밖으로 전달되거나 response.SendError() 가 호출 되었을 때 각각의 상황에 맞춘 오류 처리 기능을 제공

서블릿 오류 페이지 등록

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
    @Override
    public void customize(ConfigurableWebServerFactory factory) {

        ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
        ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
        ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500");
        factory.addErrorPages(errorPage404, errorPage500, errorPageEx);

    }
}
WebServerFactoryCustomizer<ConfigurableWebServerFactory>
  • 서블릿에서 제공하는 기능이기때문에 WebServerFactoryCustomizer를 사용해야함.

ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");

  • 404 에러가 발생시 errorPage404를 호출

해당 오류를 처리할 컨트롤러 추가


@Slf4j
@Controller
public class ErrorPageController {

    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response){
        log.info("errorPage 404");
        return "error-page/404";
    }

    @RequestMapping("/error-page/500")
    public String errorPage500(HttpServletRequest request, HttpServletResponse response){
        log.info("errorPage 500");
        return "error-page/500";
    }
}

서블릿 예외 처리 - 오류 페이지 작동 원리

  • 서블릿은 Exception이 발생해서 서블릿 밖으로 전달되거나 response.sendError 가 호출 되었을때 설정된 오류 페이지를 찾는다.

WAS 는 해당 예외 처리를 하는 오류 페이지 정보를 확인

new ErrorPage(RuntimeException.class,"/error-page/500");

500 에러가 발생시

  • RuntimeException 예외가 WAS 까지 전달 될경우 , WAS 가 오류 페이지 정보를 확인
    • 현재 RuntimeException의 오류페이지로 /error-page/500 이 지정되있음 -> WAS 는 오류페이지를 출력하기 위해서 /error-page/500을 다시 요청

중요점

  • 웹 브라우저는 서버 내부에서 이런 일이 일어나는 지모름 -> 오직 서버 내부에서 오류 페이지를 찾기 위해서 추가적인 호출을 하기 때문

오류 정보 추가하기

  • WAS는 오류 페이지를 단순히 다시 요청만 아니라 오류 정보를 request의 attribute에 추가해서 넘겨주기 때문에 필요할 경우 오류 페이지에서 전달된 오류 정보를 활용 할 수 있음.

오류 출력 추가

  • RequestDispatcher 상수로 정의 되어있는 거 가져와서 사용
  • 현재 버전은 javax 가 아니니 jakarta 로 변경해서 추가해주기
@Slf4j
@Controller
public class ErrorPageController {

    //RequestDispatcher 상수로 정의되어 있음
    public static final String ERROR_EXCEPTION = "jakarta.servlet.error.exception";
    public static final String ERROR_EXCEPTION_TYPE = "jakarta.servlet.error.exception_type";
    public static final String ERROR_MESSAGE = "jakarta.servlet.error.message";
    public static final String ERROR_REQUEST_URI = "jakarta.servlet.error.request_uri";
    public static final String ERROR_SERVLET_NAME = "jakarta.servlet.error.servlet_name";
    public static final String ERROR_STATUS_CODE = "jakarta.servlet.error.status_code";

    @RequestMapping("/error-page/404")
    public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 404");
        printErrorInfo(request);

        return "error-page/404";
    }

    @RequestMapping("/error-page/500")
    public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 500");

        printErrorInfo(request);
        return "error-page/500";
    }

    private void printErrorInfo(HttpServletRequest request) {

        log.info("ERROR_EXCEPTION: ex=", request.getAttribute(ERROR_EXCEPTION));
        log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(ERROR_EXCEPTION_TYPE));
        log.info("ERROR_MESSAGE: {}", request.getAttribute(ERROR_MESSAGE));
        //ex의 경우 NestedServletException 스프링이 한번 감싸서 반환
        log.info("ERROR_REQUEST_URI: {}", request.getAttribute(ERROR_REQUEST_URI));
        log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(ERROR_SERVLET_NAME));
        log.info("ERROR_STATUS_CODE: {}", request.getAttribute(ERROR_STATUS_CODE));
        log.info("dispatchType={}", request.getDispatcherType());
    }
}
profile
열심히하자

0개의 댓글