상세 컨텐츠

본문 제목

Spring 블로그 만들기 - 7. 예외 처리

개발/Spring 블로그 만들기

by 똘똘이박사 2019. 2. 22. 05:33

본문



이 포스팅의 샘플 게시판 개발 환경은 MAC OS, STS, OpenJDK11 입니다.


예외처리 포스팅은 아래와 같은 내용을 정리 하였습니다.

Http Status 에 따른 예외처리(404 에러 예외처리)

@ExceptionHandler을 이용한 예외처리

@ControllerAdvice를 이용한 예외처리

@ResponseStatus를 이용한 예외처리



블로그 만들기 - 7. 예외 처리


예외처리는 의도하지 않은 잘 못된 결과들에 대한 처리를 말합니다.

예를 들어 URL 주소를 잘못 입력 했을 경우 아래 화면과 같은 문제가 발생합니다.



이런 종류의 에러 관련 페이지는 사이트를 방문 하는 사람들이 보게 된다면 그다지 좋지 않을 것입니다.

따라서, 위와 같이 에러 문구를 그대로 출력하지 말고, 별도의 페이지를 만들어 안내를 하는 것이 바람직 합니다.


(이미지 출처 : 네이버)


위와 같이 페이지를 찾을 수 없거나 전달되는 파라미터가 잘 못된 경우 등 문제가 발생할 수 있는 가능성이 많이 있습니다.

아래는 주로 사용되는 값들을 정리한 표 입니다.


코드

설명

 200

OK 

OK 

 400

BAD_REQUEST

Bad Request 

 404NOT_FOUND Not Found 

 500

INTERNAL_SERVER_ERROR

Internal Server Error 


더 많은 내용은 아래의 사이트를 참조하시기 바랍니다.

위와 같은 http 상태에 따른 예외처리는 스프링에서 처리 하는 것보다는 

web.xml에서 처리 하는게 더 나은 방법일 수 있습니다.

이중 404 같이 잘 못된 주소로 인해 발생하는 에러는 web.xml에서 처리 하는 방법이 더 빠르므로 이부분에 대한

예외 처리는 web.xml을 통해 해결 하는 방법을 우선 알아 봅니다.


Http Status 상태에 따른 예외처리 (404 Not Found 예외처리)

web.xml을 열고 아래와 같은 구문을 추가 합니다.


  <!-- Error Page -->

<error-page>

<error-code>404</error-code>

<location>/WEB-INF/views/error/404error.jsp</location>

</error-page>


위 구문의 내용은 에러코드가 404 일때

/WEB-INF/views/error/404error.jsp 페이지로 이동 하라는 내용입니다.

이제 views 디렉토리 아래에 'error' 디렉토리를 만들고, 404error.jsp 페이지를 만들어 넣습니다.



404error.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>


<%@ include file="/WEB-INF/views/layout/header.jsp"%>


<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">


<title>board</title>


</head>

<body>

<article>

<div class="container">

잘못된 URL 입니다.

</div>

</article>

</body>

</html>


다시 위에서 테스트했던 것과 동일한 방법으로 URL 주소를 의도적으로 잘 못 입력해 봅니다.



사실 위의 경우와 같이 비즈니스 로직을 처리하기 전에 발생하는 문제는

스프링 예외처리 방법을 적용하는 것이 더 복잡할 수 있습니다.

하지만 비즈니스 로직을 처리하는 과정에서 발생하는 문제에 대해서 위와 같은 방식으로 무리가 있습니다.

비즈니스 로직을 처리하는 과정에서 발생하는 문제의 원인이 위의 주소입력 문제처럼 단순하지 않고

여러 가지 원인이 있을 수 있기 때문입니다.

따라서 비즈니스 로직을 처리 할 때 발생하는 문제점은 스프링에서 제공하는 예외처리 방법을 적용해 처리하는 것이 좋습니다.


스프링에서 예외처리를 하는 방법에는 3가지 방법이 있습니다.

  • @ExceptionHandler을 이용한 방법

  • @ControllerAdvice를 이용한 방법

  • @ResponseStatus를 이용한 방법

예외처리 테스트를 위해 비즈니스 로직 단계에서 의도적으로 말도 안되는 상황을 연출해 에러를 낼 예정입니다.

게시판 상세내역 조회 부분을 약간 수정하여 게시판 리스트의 제목을 클릭하면

게시글의 조회수를 업데이트 하고, 글의 내용을 불러 오는 것이 아니라

글의 카테고리 컬럼에 저장 할 수 없는 긴 문자열을 저장해 보도록 하겠습니다.

따라서 인의적인 에러를 만들기 위해 BoardServiceImpl.java 의 getBoardContent() 메소드를 아래와 같이 수정합니다.


BoardServiceImpl.java getBoardContent() 수정

@Override

public BoardVO getBoardContent(int bid) throws Exception{

BoardVO boardVO = new BoardVO();

boardDAO.updateViewCnt(bid);

//       boardVO = boardDAO.getBoardContent(bid);            <= 기존 상세내역 조회 부분은 주석 처리 합니다.

// Cate_cd에 컬럼에서 저장할 수 있는 크기보다 큰 문자열을 저장하도록 셋팅을 하고 게시물 수정 로직을 호출 합니다.

  // 따라서 수정 SQL 문 처리시 문제가 발생하게 됩니다.

    boardVO.setBid(bid);

boardVO.setCate_cd("1111111111111111111111111111111111111");   

boardDAO.updateBoard(boardVO);

 

return boardVO;

}



코드를 수정하고 게시판의 제목을 클릭하게 되면 아래와 같은 에러 화면이 출력됩니다.



위의 문제는 cate_cd 에 큰 데이터가 들어가서 발생하는 문제(Data too long for column 'cate_cd' at row 1) 입니다.

이제 위와 같이 비즈니스 로직 처리 시 발생하는 문제에 대해 예외처리를 하는 방법에 대해 정리해 보겠습니다.



@ExceptionHandler을 이용한 예외처리


@ExceptionHandler을 이용한 예외처리 방법은 

기존의 Controller에 @ExceptionHandler 어노테이션을 사용하는 메소드를 추가하여 예외처리를 하는 방식입니다.

BoardController.java에 다음과 같은 메소드를 추가 합니다.


BoardController.java 추가 내용

@ExceptionHandler(RuntimeException.class)

public String exceptionHandler(Model model, Exception e){

logger.info("exception : " + e.getMessage());

model.addAttribute("exception", e);

return "error/exception";

}


@ExceptionHandler 어노테이션을 활용한 방법은 기존의 Controller를 작성하는 방법과 상당히 유사 합니다.

@RequestMapping 어노테이션 대신 @ExceptionHandler을 사용하기만 하면 됩니다.

@ExceptionHandler 어노테이션 속성으로 사용된 RuntimeException 은 데이터베이스에처 발생하는 에러를 처리하기 위해 사용합니다. RuntimeException 이외에도 많은 속성이 있지만 다음 기회에 정리 하도록 하겠습니다.


위 구문에서 인자를 2개 사용합니다. Model 과 Exception 타입을 인자로 받는데 Model은 에러 데이터 객체를 View를 전달합니다. Exception은 try.. catch 구문에서 사용하는 것과 같이 에러를 받기 위해 사용됩니다.

따라서 위 구문은 화면에 "exception" 이라는 이름으로 에러 객체를 전달하게 됩니다.

그리고 화면의 이름은 error 디렉토리 아래 있는 exception.jsp 라는 파일입니다.


이제 에러를 출력해줄 View 화면을 만들어 봅니다.

아래 그림과 같이 error 디렉토리 아래 exception.jsp 라는 파일을 만들고 아래의 코드를 입력 합니다.




exception.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>


<%@ include file="/WEB-INF/views/layout/header.jsp"%>


<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">


<title>board</title>


</head>

<body>

<article>

<div class="container">

<p>데이터를 처리 하는 과정에서 문제가 발생하였습니다.</p>

<p>관리자에게 문의하여 주십시오.</p>

</div>

</article>

</body>

</html>


위와 같이 입력 후 게시판의 제목을 클릭해 보면 아래와 같이

준비된 에러 페이지로 이동을 할 수 있습니다.



하지만 관리자가 위의 화면을 가지고 에러를 처리 하기에는 많은 어려움이 있습니다.

따라서 exceptionHandler 에서 화면에 전달하는 Exception 객체의 정보를 화면에 출력해

조금 더 많은 정보를 얻을 수 있습니다.


아래와 같이 내용을 조금 더 추가해 봅니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>


<%@ include file="/WEB-INF/views/layout/header.jsp"%>


<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">


<title>board</title>


</head>

<body>

<article>

<div class="container">

<p>데이터를 처리 하는 과정에서 문제가 발생하였습니다.</p>

<p>관리자에게 문의하여 주십시오.</p>

<h3>${exception.getMessage()}</h3>

<ul>

<c:forEach items="${exception.getStackTrace()}" var="stack">

<li>${stack.toString()}</li>

</c:forEach>

</ul>

</div>

</article>

</body>

</html>


붉은색으로 표시된 부분이 추가된 부분 입니다.

Controller 에서 "exception" 이라는 이름으로 보낸 Exception 객체를 JSTL forEach문을 사용하기 위해 

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 부분을 추가 하였고

에러 메시지 출력을 위해 ${exception.getMessage()} 을 사용하였습니다.

그리고 <ul><li> 태그를 사용하여 에러 내용을 출력 하였습니다.

아래 그림은 실행 결과 입니다.



이와 같은 방법은 각 컨트롤러에 예외처리를 위한 메소드를 추가해 주는 방식입니다.

따라서 예외를 적용할 모든 컨트롤러에는 위와 같은 메소드가 추가되어야 한다는 이야기가 됩니다.

컨트롤러의 수가 많아지면 그만큼 작업해야 할 양이 많아 지게 됩니다.

위와 같이 사이트 전체에 공통으로 적용해야할 컨트롤러에는 @ControllerAdvice를 사용한 예외처리 방법이 효율적입니다.


@ControllerAdvice를 이용한 예외처리 방법

@ControllerAdvice를 이용한 예외처리 방법은 예외처리를 할 '예외처리 전용 컨트롤러'를  만드는 것입니다.

아래와 같이 예외처리를 위한 전용 컨트롤러를 위해 새로운 패키지(com.freehoon.web.error.controller)를 생성합니다.



그리고 아래와 같이 예외전용 컨트롤러를 만듭니다.


CommonExceptionAdvice.java 추가

package com.freehoon.web.error.controller;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;


@ControllerAdvice

public class CommonExceptionAdvice {


private static final Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);

@ExceptionHandler(RuntimeException.class)

public String errorException(Model model, Exception e) {

logger.info("@ControllerAdvice 방식 \n###exeption : " + e.getMessage());

model.addAttribute("exception", e);

return "error/exception";

}

}


위에서 사용했던 예외처리와 비교 하였을때 달라진 점이라면

예외처리 메소드를 별도의 컨트롤러로 분리 하였다는 점과 @Controller 어노테이션 대신에 @ControllerAdvice 어노테이션을 사용하였다는 점 뿐입니다.


기존 BoardController.java에 있던 handleException() 부분은 모두 주석 처리 하고 새로운 예외처리를 테스트해 봅니다.



@ResponseStatus를 이용한 예외처리 방법

예외처리를 시작하기 전 에러화면을 보면 상태코드 값이 '500' 입니다.

이 값은 서버에서 문제가 생겼을때 통상적으로 반환되는 상태코드 값이므로 문제에 대한 정확한 원인을 파악하기 힘듭니다.

따라서, 이 상태코드를 의미가 있는 코드로 변환하여 돌려주는 것이 문제 해결에 도움이 됩니다.

이럴때 사용하는 것이 @ResponseStatus 어노테이션 입니다.

@ResponseStatus 사용을 위해 아래와 같이 NotFoundException.java 라는 새로운 클래스를 생성합니다.




NotFoundException.java 추가

package com.freehoon.web.error.controller;


import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ResponseStatus;


@ResponseStatus(HttpStatus.NOT_FOUND)

public class NotFoundException extends RuntimeException {


}


그리고 이전에 만들었던

CommonExceptionAdvice 클래스에서 @ControllerAdvice 어노테이션을 주석 처리 합니다.

예외처리 우선 순위에서 @ResponseStatus 보다 @ControllerAdvice가 우선 하기 때문에 NotFoundException 클랙스가

작동하지 않기 때문입니다.


마지막으로 에러 발생시 예외를 처리하기 위해

BoardServiceImpl.java 를 아래와 같이 수정합니다.


BoardServiceImpl.java 수정

@Override

public BoardVO getBoardContent(int bid) throws Exception{

BoardVO boardVO = new BoardVO();

    


boardDAO.updateViewCnt(bid);

// boardVO = boardDAO.getBoardContent(bid);

try {

boardVO.setBid(bid);

boardVO.setCate_cd("1111111111111111111111111111111111111");

boardDAO.updateBoard(boardVO);

} catch (RuntimeException e) {

throw new NotFoundException();

}

return boardVO;

}


의도적으로 에러를 발생시키기 위한 부분을 try..catch.. 로 감쌌습니다.

주위 깊게 보아야 할 부분은 catch 부분 입니다.

예외를 처리하기 위해 catch 조건에 RuntimeException 을 파라미터 타입으로 받고 있고

처리 부분에 throw new NotFoundException() 을 호출하고 있습니다.


이제 다시 게시판 리스트에서 제목을 클릭해 봅니다.

그럼 가장 처음 web.xml에 예외처리를 했던 아래와 같은 화면이 열리는 것을 확인 할 수 있습니다.

이것은 @ResponseStatus 어노테이션을 사용하여 상태코드를 'Not Found' 값으로 셋팅하였기 때문입니다.










※ 포스팅에 오타나 잘못된 부분, 추가적으로 더 알고 싶은 부분이 있으면 댓글 주세요~



반응형

관련글 더보기