스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 15

Spring 정리 2022. 7. 16. 16:44

인프런 강의 53일차.

 - 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 1 (김영한 강사님)

 - 1편에서 배운 MVC를 활용할 수 있는 기술 습득

 - 타입 컨버터, 파일 업로드, 활용, 쿠키, 세션, 필터, 인터셉터, 예외 처리, 타임리프, 메시지, 국제화, 검증 등등

 

 8.1 예외 처리와 오류 페이지 - 프로젝트 생성

  - MVC1 편에서 개발한 상품 관리 프로젝트를 약간 다듬어서 form-start 라는 프로젝트로 변환.

 

  - https://start.spring.io

  - 프로젝트 선택 Project : Gradle Project

  - Language : Java

  - Spring Boot : 2.7.0

  - Group : hello

  - Artifact : exception

  - Name : exception

  - Package name : hello.exception

  - Packaging : Jar

  - Java : 11

  - Dependencies : Spring Web, Thymeleaf, Lombok, Validation

* gradle.build 실행 후 아래 오류 발생 시 gradle-wrapper.properties에서 gradle 버전을 6.9 이하로 변경해서 다운로드하자. (gradle-6.9-all.zip)

 > Unable to find method 'org.gradle.api.artifacts.result.ComponentSelectionReason.getDescription()Ljava/lang/String;'. Possible causes for this unexpected error include: Gradle's dependency cache may be corrupt (this sometimes occurs after a network connection timeout.) Re-download dependencies and sync project (requires network)

 

 8.2 예외 처리와 오류 페이지 - 서블릿 예외 처리 - 시작

  - 스프링이 아닌 순수 서블릿 컨테이너는 예외를 어떻게 처리하는지 알아보자.

  - 서블릿은 다음 2가지 방식으로 예외 처리를 지원한다.

    > Exception (예외)

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

 

 * Exception(예외) 처리 방식

  - 자바 직접 실행

    > 자바의 메인 메서드를 직접 실행하는 경우 main 이라는 이름의 쓰레드가 실행된다.

    > 실행 도중에 예외를 잡지 못하고 처음 실행한 main() 메서드를 넘어서 예외가 던져지면, 예외 정보를 남기고 해당 쓰레드는 종료된다.

 - 웹 애플리케이션

    > 웹 애플리케이션은 사용자 요청별로 별도의 쓰레드가 할당되고, 서블릿 컨테이너 안에서 실행된다.

    > 애플리케이션에서 예외가 발생했는데, 어디선가 try ~ catch로 예외를 잡아서 처리하면 아무런 문제가 없다.

    > 그런데 만약에 애플리케이션에서 예외를 잡지 못하고, 서블릿 밖으로 까지 예외가 전달되면 어떻게 동작할까?

웹 어플리케이션 exception 흐름

    > 최종적으로 WAS 까지 예외가 전달된다. WAS는 예외가 올라오면 어떻게 처리 할까?

server.error.whitelabel.enabled=false

 - application.properties

 - 스프링에서 기본적으로 제공하는 whitelabel 에러 페이지 표시 제외하도록 추가

package hello.exception.servlet;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Slf4j
@Controller
public class ServletExController {

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

}

 - hello/exception/servlet/ServletExController.java

 - 익셉션 발생시키는 로직 추가

 - 실행 시 tomcat이 기본적으로 제공하는 HTTP Status 500 - Internal Server Error가 표시된다.

    > 웹 브라우저에서 개발자 모드로 확인해보면 HTTP 상태 코드가 500으로 보인다.

    > Exception 의 경우 서버 내부에서 처리할 수 없는 오류가 발생한 것으로 생각해서 HTTP 상태 코드 500을 반환한다.

 

 * response.sendError(HTTP 상태 코드, 오류 메시지) 처리 방식

  - 오류가 발생했을 때 HttpServletResponse 가 제공하는 sendError 라는 메서드를 사용해도 된다.

  - 이것을 호출한다고 당장 예외가 발생하는 것은 아니지만, 서블릿 컨테이너에게 오류가 발생했다는 점을 전달할 수 있다.

  - 이 메서드를 사용하면 HTTP 상태 코드와 오류 메시지도 추가할 수 있다.

    > response.sendError(HTTP 상태 코드)

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

 

@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);
}

 - hello/exception/servlet/ServletExController.java

 - response.sendError로 상태코드 세팅 리턴하는 메소드 추가

sendError 흐름

 - sendError 처리 흐름

    > response.sendError() 를 호출하면 response 내부에는 오류가 발생했다는 상태를 저장해둔다.

    > 그리고 서블릿 컨테이너는 고객에게 응답 전에 response 에 sendError() 가 호출되었는지 확인한다.

    > 그리고 호출되었다면 설정한 오류 코드에 맞추어 기본 오류 페이지를 보여준다

 

 8.3 예외 처리와 오류 페이지 - 서블릿 예외 처리 - 오류 화면 제공

  - 서블릿 컨테이너가 제공하는 기본 예외 처리 화면은 고객 친화적이지 않으니 서블릿이 제공하는 기능을 사용해보자. 

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

 

 * 에러 페이지 호출 흐름

<web-app>
    <error-page>
        <error-code>404</error-code>
        <location>/error-page/404.html</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/error-page/500.html</location>
    </error-page>
    <error-page>
        <exception-type>java.lang.RuntimeException</exception-type>
        <location>/error-page/500.html</location>
    </error-page>
</web-app>

 - 과거에는 web.xml에 각 에러 타입, 코드별 호출 페이지를 지정해서 처리했었다.

 - 지금은 스프링 부트를 통해서 서블릿 컨테이너를 실행하기 때문에, 스프링 부트가 제공하는 기능을 사용해서 서블릿 오류 페이지를 등록하면 된다.

 

 * 서블릿 오류 페이지 등록

package hello.exception;

import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {

    @Override
    public void customize(ConfigurableWebServerFactory factory) {
        //Springboot에서 제공해주는 ErrorPage 클래스는 사용해 에러 정의
        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);     //에러 페이지 등록
    }
}

 - hello/exception/WebServerCustomizer.java

 - Servlet 오류를 처리하기 위해 스프링부트가 제공하는 ErrorPage 기능을 사용하였다.

   > response.sendError(404) : errorPage404 호출

   > response.sendError(500) : errorPage500 호출

   > RuntimeException 또는 그 자식 타입의 예외: errorPageEx 호출

 - 오류 페이지는 예외를 다룰 때 해당 예외와 그 자식 타입의 오류를 함께 처리한다.

   > RuntimeException 은 물론이고 RuntimeException 의 자식도 함께 처리한다.

 

 * 오류를 처리할 수 있는 컨트롤러 구현

package hello.exception.servlet;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@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";
    }
}

 - hello/exception/servlet/ErrorPageController.java

 - 서블릿에서 전달한 페이지를 호출하는 컨트롤러 구현

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>404 오류 화면</h2>
    </div>
    <div>
        <p>오류 화면 입니다.</p>
    </div>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

 - templates/error-page/404.html

 - 오류처리 404 호출 View

 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>500 오류 화면</h2>
    </div>
    <div>
        <p>오류 화면 입니다.</p>
    </div>
    <hr class="my-4">
</div> <!-- /container -->
</body>
</html>

 - templates/error-page/500.html

 - 오류처리 500 호출 View