Spring boot

Bank App 만들기 - Exception Handler 처리(HTTP 상태 코드)

ryeonng 2024. 8. 7. 12:14

 

@ControllerAdvice, @RestControllerAdvice 

HTTP 통신을 통해 예외 상황을 클라이언트에게 알려주는 방법은 여러 가지가 있으며, 이들을 적절히 사용하는 것이 중요하다.

 

@ControllerAdvice와 @RestControllerAdvice는 Spring Framework에서 제공하는 어노테이션들로, 애플리케이션 전역에 걸쳐 발생하는 예외를 효과적으로 관리하고 처리하는데 사용된다. 이들은 일종의 "예외 처리의 중앙 집중화"를 가능하게 해주며, 애플리케이션 내 여러 컨트롤러나 서비스에서 공통적으로 발생할 수 있는 예외를 한 곳에서 관리할 수 있게 해준다.

 

 

 

@ControllerAdvice와 @RestControllerAdvice의 차이점

  • @ControllerAdvice : 이 어노테이션은 주로 @Controller 또는 @RequestMapping 어노테이션이 적용된 클래스(즉, MVC 컨트롤러)에서 발생하는 예외를 처리하기 위해 사용된다. HTML 뷰를 반환하는 전통적인 웹 애플리케이션에서 주로 사용된다.
  • @RestControllerAdvice@ControllerAdvice와 유사한 기능을 제공하지만, @RestController에서 발생하는 예외를 처리하는데 특화되어 있다. 즉, RESTful 웹 서비스에서  JSON이나 XML 같은 응답을 반환할 때 사용된다. 사실상, @RestControllerAdvice는 @ControllerAdvice에 @ResponseBody를 추가한 것과 동일한 효과를 제공하여, 직접 데이터를 매핑할 수 있다.

 

예시코드 확인 - 1

ControllerAdvice 에서 데이터를 반환하는 예시 코드

@ControllerAdvice
public clasls GlobalExceptionHander {

    @ExceptionHandler(value = ResourceNotFoundException.class)
    @ResponseBody
    public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
        // 에러 메시지와 함께 404 상태 코드를 반환
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

 

예시코드 확인 - 2

RestControllerAdvice 에서 반환하는 예시 코드

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = ResourceNotFoundException.class)
		// @ResponseBody 사용 안해도 됨(데이터를 반환 처리 함) 
    public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
        // 에러 메시지와 함께 404 상태 코드를 반환
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

 

 

사용자 정의 예외 클래스 만들기

RuntimeException은 프로그램의 실행 도중 발생하는 예외를 나타낸다. 컴파일 시 예외가 아닌 실행 시 예외로 분류되며, 따로 try-catch 블록으로 처리하지 않아도 컴파일러가 오류로 인식하지 않는다.

 

예시로는 NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException 등이 있다. 여기서는 RuntimeException을 확장해서 사용자 정의 예외 클래스를 만들것이다.

위와 같이 패키지와 자바 파일들을 생성하자 (handler/GlobalControllerAdvice.java 파일 생성)

  • handler/GlobalControllerAdvice 파일 생성
  • handler/exception 패키지 생성
  • handler/exception/UnAuthorizedException.java 파일 생성

: UnAuthorizedException 클래스는 인증이 되지 않은 사용자가 인증이 필요한 서비스에 접근 요청을 할 때 예외를 발생 시킬 사용자 정의 예외 클래스를 설계한다.

 

package com.tenco.bank.handler.exception;
import org.springframework.http.HttpStatus;
import lombok.Getter;

@Getter
public class UnAuthorizedException extends RuntimeException  {
	
	private HttpStatus status; 
	
	// throw new UnAuthorizedException(   ,  ) 
	public UnAuthorizedException(String message, HttpStatus status) {
		super(message);
		this.status = status;
	}
}
package com.tenco.bank.handler.exception;
import org.springframework.http.HttpStatus;
import lombok.Getter;

// 에러 발생시에 여러 페이지로 이동 시킬 때 사용 예정 

@Getter
public class RedirectException extends RuntimeException {
	
	private HttpStatus status;
	
	// throw new RedirectException(???, ???);
	public RedirectException(String message, HttpStatus status) {
		super(message);
		this.status = status;
	}
}
package com.tenco.bank.handler.exception;
import org.springframework.http.HttpStatus;
import lombok.Getter;

@Getter
public class DataDeliveryException extends RuntimeException {
	
	private HttpStatus status;
	
	public DataDeliveryException(String message, HttpStatus status) {
		super(message);
		this.status = status;
	}
}

 

 

 

@ControllerAdvice 구현

package com.tenco.bank.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.RedirectException;
import com.tenco.bank.handler.exception.UnAuthorizedException;

@ControllerAdvice // IoC의 대상이 된다. (싱글톤 패턴으로 관리 됨) -> HTML 렌더링 예외에 많이 사용한다.
public class GlobalControllerAdvice {

	/**
	 * 개발 시 많이 활용
	 * 모든 예외 클래스를 알 수 없기 때문에, 로깅으로 확인할 수 있도록 설정
	 * 로깅 처리 - 동기적 방식(System.out.println), @slf4j 라이브러리(비동기 로딩 처리)
	 */
	
	@ExceptionHandler(Exception.class)
	public void exception(Exception e) {
		System.out.println("------------------------");
		System.out.println(e.getClass().getName()); // 어떤 예외클래스 발생했는지 찍어보기
		System.out.println(e.getMessage()); // 발생한 예외클래스 로깅처리
		System.out.println("------------------------");
	}
	
	/**
	 * Data로 예외를 내려주는 방법
	 * @responseBody 활용
	 * - 브라우저에서 자바스크립트 코드로 동작하게 된다.
	 */
	
	// * 예외를 내릴 때 데이터를 내리고 싶다면,
	// 1. @RestControllerAdvice를 사용하면 된다.
	// 단, @ControllerAdvice를 사용하고 있다면 @ResponseBody를 붙여서 사용하면 된다.
	@ResponseBody
	@ExceptionHandler(DataDeliveryException.class)
	public String dataDeliveryException(DataDeliveryException e) {
		StringBuffer sb = new StringBuffer();
		sb.append("<script>");
		sb.append("alert('"+ e.getMessage()  +"')");
		sb.append("window.history.back();");
		sb.append("</script>");
		return sb.toString();
	}
	
	@ResponseBody
	@ExceptionHandler(UnAuthorizedException.class)
	public String unAuthorizedException(UnAuthorizedException e) {
		StringBuffer sb = new StringBuffer();
		sb.append("<script>");
		sb.append("alert('"+ e.getMessage()  +"')");
		sb.append("window.history.back();");
		sb.append("</script>");
		return sb.toString();
	}
	
	/**
	 * 에러 페이지로 이동 처리
	 * JSP로 이동 시 데이터를 담아서 보내는 방법
	 * ModelAndView, Model 사용 가능
	 * throw new RedirectException('페이지를 찾을 수 없습니다.', 404);
	 */
	@ExceptionHandler(RedirectException.class)
	public ModelAndView redirectException(RedirectException e) {
		
		ModelAndView modelAndView = new ModelAndView("errorPage");
		modelAndView.addObject("statusCode", e.getStatus().value());
		modelAndView.addObject("message", e.getMessage());
		return modelAndView; // 페이지 반환 + 데이터 내려줌
	} 
	
}


 

에러 페이지 생성 (errorPage.jsp)

샘플 화면

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${statusCode} Error - Page Not Found</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css">
<style type="text/css">
	body {
		display: flex;
		justify-content: center;
		align-items: center;
		height: 100vh;
	}
	
	.error--message {
		text-align: center;
		margin-bottom: 20px;
	}
	
</style>
</head>
<body>
	<div class="container">
		<div class="text-center">
			<h1>${statusCode}</h1>
			<p class="error--message"> Page Not Found </p>
			<p>${message}</p>
			<a href="/index" class="btn btn-primary">Go to Home Page</a>
		</div>
	</div>
</body>
</html>

 

 

직업 예외 발생 해보기

package com.tenco.bank.controller;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import com.tenco.bank.handler.exception.DataDeliveryException;
import com.tenco.bank.handler.exception.RedirectException;
import com.tenco.bank.handler.exception.UnAuthorizedException;

@Controller // Ioc의 대상(싱글톤 패턴으로 관리된다.) -- 제어의 역전
public class MainController {

	// REST API 기반으로 주소를 설계할 수 있다.
	
	// 주소설계
	// http:localhost:8080/main-page
	
	@GetMapping({"/main-page", "/index"})
	// @ResponseBody // 데이터를 반환시킨다.
	public String mainPage() {
		System.out.println("mainPage() 메서드 호출 확인");
		// [JSP 파일 찾기 (yml 설정)] - 뷰 리졸버 (JSP 파일을 찾아주는 요소)
		// prefix : /WEB-INF/view
		//			/main 
		// suffix : .jsp
		return "/main";
	}
	
	// TODO - 삭제예정
	// 주소 설계
	// http://localhost:8080/error-test1/true
	// http://localhost:8080/error-test1/false
	
	@GetMapping("/error-test1")
	public String errorPage() {
		if(true) {
			throw new RedirectException("잘못된 요청입니다.", HttpStatus.NOT_FOUND); // 404
		}
		return "main"; // 에러가 없다면, main 페이지로 이동
	}
	
	// http://localhost:8080/error-test2
	@GetMapping("/error-test2")
	public String errorData2() {
		if(true) {
			throw new DataDeliveryException("잘못된 데이터 입니다.", HttpStatus.BAD_REQUEST);
		}
		return "main"; 
	}
	
	// http://localhost:8080/error-test3
	@GetMapping("/error-test3")
	public String errorData3() {
		if(true) {
			throw new UnAuthorizedException("인증되지 않은 사용자 입니다.", HttpStatus.UNAUTHORIZED);
		}
		return "main"; 
	}
	
}

 


HTTP 상태 코드
HTTP (Hypertext Transfer Protocol)는 웹 서버와 웹 클라이언트 사이에서 데이터를 주고받기 위해 사용하는 통신 방식으로 TCP/IP 프로토콜 위에서 동작한다. 즉, 우리가 웹을 이용하려면 웹 서버와 웹 클라이언트는 각각 TCP/IP 동작에 필수적인 IP 주소를 가져야 한다는 의미이다.

HTTP란 이름대로라면 하이퍼텍스트(Hypertext)만 전송할 수 있어 보이지만, 실제로는 HTML이나 XML과 같은 하이퍼텍스트 뿐 아니라 이미지, 음성, 동영상, JavaScript, PDF와 각종 문서 파일 등 컴퓨터에서 다룰 수 있는 데이터라면 무엇이든 전송할 수 있다.

우리가 웹 브라우저의 주소창에 https://www.naver.com을 입력하고 Enter를 누르면 웹 클라이언트와 웹 서버 사이에 HTTP 연결이 맺어지고 웹 클라이언트는 웹 서버에 HTTP 요청 메시지를 보낸다. 웹 서버는 요청에 따른 처리를 진행한 후에 그 결과를 웹 클라이언트에 HTTP 응답 메시지로 보낸다.

 

 

100 ~ 500번대 까지의 상태 코드 정의

  • 1XX : Infomational(정보제공) - 임시 응답으로 현재 클라이언트의 요청까지는 처리되었으니 계속 진행하라는 의미. HTTP 1.1 버전부터 추가되었다.
  • 2XX : Success(성공) - 클라이언트의 요청이 서버에서 성공적으로 처리되었다는 의미
  • 3XX : Redirection(리다이렉션) - 완전한 처리를 위해 추가 동작이 필요한 경우. 주로 서버의 주소 또는 요청한 URI의 웹 문서가 이동되었으니 그 주소로 다시 시도하라는 의미
  • 4XX : Client Error(클라이언트 에러) - 없는 페이지를 요청하는 등 클라이언트의 요청 메시지 내용이 잘못된 경우를 의미
  • 5XX : Server Error(서버 에러) - 서버 사정으로 메시지 처리에 문제가 발생한 경우. 서버의 부하, DB처리 과정 오류, 서버에서 익셉션이 발생하는 경우

 

주요 4xx 상태코드 확인 (클라이언트 에러) 클라이언트의 요청에 오류가 있다.

상태코드 상태 텍스트 한국어 뜻 서버 측면에서의 의미
400 Bad Request 잘못된 요청 요청 구문, 유효하지 않은 요청 메시지 또는 디코딩할 수 없는 내용으로인해 서버가 요청을 이해할 수 없음
401 Unauthorized 권한 없음 요청이 인증을 필요로 하며, 클라이언트가 해당 인증을 제공하지 않았음. www.Authenticate 헤더를 통해 인증 방식을 지정.
403 Forbidden 금지됨 서버가 요청을 이해했으나, 권한 없음 또는 기타 사유로 인해 요청을 수행 거부.
404 Not Found 찾을 수 없음 서버가 요청한 리소스를 찾을 수 없음. 주소 오타 또는 리소스 이동/삭제 등이 원인일 수 있음.

 

 

주요 5xx 상태코드 확인 (서버 에러) 클라이언트의 요청은 유효하나, 서버가 처리에 실패하였다.

코드 상태 텍스트 한국어 뜻 서버 측면에서의 의미
500 Internal Server Error 내부 서버 오류 서버 내부에 오류가 발생하여 요청을 처리할 수 없음. 일반적으로 서버 측에서 예상치 못한 상황이 발생했을 때 반환됨
501 Not Implemented 구현되지 않음 서버가 요청된 메소드를 지원하지 않거나 구현하지 않음. 예를 들어, 서버가 PUT 메소드를 지원하지 않는 경우에 반환됨
502 Bad Gateway 불량 게이트웨이 게이트웨이나 프록시 역할을 하는 서버가 뒤쪽 서버로부터 잘못된 응답을 받음. 대개 다른 서버로의 요청 중 문제가 발생했을 때 사용됨
503 Service Unavaliable 서비스 제공불가 서버가 일시적으로 요청을 처리할 수 없는 상태. 일반적으로 서버 과부하나 유지 보수로 인해 일시적으로 서비스를 제공할 수 없을 때 반환됨