본문 바로가기

백엔드 개발/SpringMVC

Spring에서 예외 처리하는 방법(1)

<개요>

컨트롤러의 매소드에서 예외가 발생 했을 시 어떻게 처리하는지에 관한 내용이다. 

예외처리 안 한 경우, 서버가 어떻게 응답하는지에 대해서 알아보고

예외 처리 방법에 대해서 알아본다.

예외 처리 방법에는 try-catch 문으로 처리, @ExceptionHandler 사용, @ControllerAdvice 사용이 있다. 

 

1. 예외처리 안 한 경우, 서버가 어떻게 응답하나? 

@Controller
public class ExceptionController2 {
	
	// 각 매소드에서 예외 발생 시킴.
	@RequestMapping("/ex3")
	public void main() throws Exception {
			throw new Exception("예외가 발생했습니다.");
	}
}

일부러 예외를 만들어 던져보았다. 그리고 이에 대한 아무런 처리를 하지 않은 경우 어떻게 나올까

에러코드 500이 뜬다. 이건 예외를 처리하지 않은 프로그래머의 잘못이기 때문이다. 

2. 예외처리를 try catch 문으로 처리

java에서 배운 try catch 문으로 처리해보자 .

	@RequestMapping("/ex3")
	public void main() throws Exception {
			try {
				throw new Exception("예외가 발생했습니다.");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

해당 매소드를 try catch 구문으로 묶었다.  그리고  localhost/ch2/ex3를 실행시켜보면,

에러코드 400이 난다. ex3.jsp를 찾을 수 없다는 메세지가 나온다. 그 이유는 해당 매소드의 반환 타입이 void이기 때문이다. 반환을 하지 않는 매소드를 사용 시, 컨트롤러는 매핑된 URL 주소 맨 끝단과 같은 이름의 view jsp를 찾는다. 

여기선 ex3.jsp를 만들어놓지 않았기 때문에 오류가 발생한다. 

그렇다면 에러가 발생하면 보여줄 view를 만들고, 반환형을 String으로 바꿔서 매소드 처리 후 해당 view가 보여지도록 해보자.

<!--error.jsp-->
<%@ page contentType="text/html;charset=utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
	<title>error.jsp</title>
</head>
<body>
<h1>예외가 발생했습니다.</h1>
<!-- 에러와 해당 에러의 메세지를 예외처리 컨트롤러의 모델에서 받아와 출력 -->
발생한 예외 : ${ex}<br>
예외 메시지 : ${ex.message}<br>
<ol>
<!-- 예외의 내용들을 모델에서 받아와 차례로 뽑는다. -->
<c:forEach items="${ex.stackTrace}" var="i">
	<li>${i.toString()}</li>
</c:forEach>
</ol>
</body>
</html>
//	매소드 String으로 형변환, error.jsp 가도록 return 값 설정
	@RequestMapping("/ex3")
	public String main() throws Exception {
			try {
				throw new Exception("예외가 발생했습니다.");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return "error";
			}
	}

근데 error.jsp에 적힌 에러 내용과 메세지를 보여주는 변수들이 작동을 안한다. 

그 이유는 컨트롤러와 view 사이에 모델로 값들을 받아 전달해주는 절차를 진행하지 않았기 때문이다.

	@RequestMapping("/ex3")
	public String main(Model m) throws Exception {
			try {
				throw new Exception("예외가 발생했습니다.");
			} catch (Exception e) {
				// 모델에 에러 객체를 넣어줌.
                // error.jsp에서 ex.message 등으로 객체의 변수들을 꺼내 씀.
				m.addAttribute("ex", e);
				e.printStackTrace();
				return "error";
			}
	}

하지만 이런 식으로 일 처리를 하면, 모든 매소드 내에 예외가 생길 수 있는 부분에 대해서 try catch문을 써주고 catch문 마다 모델에 에러를 넣어서 view로 보내줘야 한다. 중복코드가 너무 많고 비효율적이다.

해당 애로사항의 해결방법은 다음과 같다. 

@ExceptionHandelr 어노테이션을 통해 예외처리만 전문적으로 담당하는 매소드를 만들어서 한번에 처리해줄 수 있다. 

3. @ExceptionHandler 사용

@Controller
public class ExceptionController2 {
	
	//1. ExceptionHandler 생성 (무슨 예외에 대해 처리할 것인지 괄호 안에 적어줘야함.)
	@ExceptionHandler(Exception.class)
	
	//2. Handler와 매핑된 매소드 (예외 객체와 운반에 쓸 모델을 매개체로 받는다.)
	public String catcher(Exception ex, Model m) {
		
		// 이걸 진짜 사용하는지 보기 위한 프린트문
		System.out.println("ExceptionHandler 내부에서 일처리 했습니다.");
		//2-1 모델에 넣고, view 반환
		m.addAttribute("ex", ex);
		return "error";
	}
	

	@RequestMapping("/ex3")
	public String main(Model m) throws Exception {
				throw new Exception("예외가 발생했습니다.");

	}
	
	@RequestMapping("/ex4")
	public String main2() throws Exception {
			throw new FileNotFoundException("예외가 발생했습니다.");
	}
}

@ExceptionHandler(Exception.class) 어노테이션은 Catcher 구문과 같은 것이다. 

"/ex3", "/ex4"로 가는 일반 매소드들은 반환값이 없어도 에러가 뜨지 않는 걸 볼 수 있는데 이는 예외처리를 담당하는 Catcher 구문에서 예외를 잡아 반환 처리를 하기 때문이다. 

일반 매소드에서 예외를 던지면 @ExceptionHandler 달린 메소드에서 예외를 잡아 처리한다.

이는 일반 매소드에서 던진 예외와 예외처리 매소드에서 처리하겠다는 예외가 일치할 시 매핑되어 연결되기 때문이다.

해당 catcher 구문으로 일이 처리 되었음을 알 수 있다.

@ExceptionHandler(Exception.class) 어노테이션의 사용법은 catch와 매우 유사하다. 

만약 어떤 예외처리 매소드는 NullPointerException만, 나머지 예외는 다른 매소드에서 처리 하고 싶다면 이렇게 적으면 된다.

@ExceptionHandler(NullPointerException.class)
public String catcher1 (Exception ex, Model m) {
	m.addAttribute("ex", ex);
    return "error";
}

@ExceptionHandler(Exception.class)
public String catcher2 (Exception ex, Model m) {
	m.addAttribute("ex", ex);
    return "error";
}

만약 하나의 예외처리 매소드에서 NullPointer 예외와 파일을 못 찾은 예외를 처리하고 싶은 경우 어노테이션()안에 배열로 나열하면 된다.

@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})

해당 @ExceptionHandler가 붙은 예외처리 매소드는 해당 컨트롤러 내에서만 효력이 있다. 

다른 컨트롤러는 해당 매소드를 사용하지 못한다.

이렇게 되면 아까보단 낫지만, 모든 클래스에서 예외처리 매소드를 적어줘야 한다는 문제가 생긴다. 

이를 해결하기 위한 컨트롤러 어노테이션이 @ControllerAdivce 이다. 

이 어노테이션이 붙은 컨트롤러는 모든 컨트롤러에 대해서 예외처리가 가능하다.

4. @ControllerAdvice 사용

// 1. 범 컨트롤러적 어노테이션 
@ControllerAdvice
public class GlobalCatcher {
	
	//2. 밑의 내용은 그대로, 전문분야 정해서 처리
	@ExceptionHandler({NullPointerException.class, FileNotFoundException.class})
	public String catcher1(Exception ex, Model m) {
		
		m.addAttribute("ex", ex);
		return "error";
	}
	
	@ExceptionHandler(Exception.class)
	public String catcher2(Exception ex, Model m) {
		
		m.addAttribute("ex", ex);
		return "error";
	}
}

@ControllerAdvice 옆에 아무것도 안 적는다면, 모든 패키지에 관해 해당 @ControllerAdivce 붙은 클래스가 예외처리 도맡아 하겠다는 소리이다. 

우리는 이 녀석이 예외처리할 패키지를 정해줄 수 있다.

//com.fastcampus.ch2 패키지 내의 예외만 처리
@ControllerAdvice("com.fastcampus.ch2")
public class GlobalCatcher {
/...
}

** 우선순위 

만약 동일 컨트롤러 내에 예외처리 핸들러가 존재하고, @ControllerAdvice 클래스가 따로 존재할 경우 뭐가 먼저 일을 처리할까? 

동일 컨트롤러 내의 예외처리 핸들러가 우선순위가 높기에 일을 처리한다. 

따라서 @ControllerAdvice에 공용적으로 쓸 예외처리를 써두고, 만약 따로 특별한 예외처리를 해줘야할 컨트롤러가 있다면 그 안에 예외처리 핸들러를 써주면 되는 것이다.

 

 

<주의 사항>

예외처리 매소드에서 쓰는 Model과 일반 매소드에서 쓰이는 Model은 서로 다른 존재이다.

@Controller
public class ExceptionController {

	@ExceptionHandler(Exception.class)
	public String catcher(Exception ex, Model m) {
    	//2. 모델 속에 내용물이 있는지 확인
		System.out.println("m="+m);
		m.addAttribute("ex", ex);
		return "error";
	}
	
	// 1. 실험 예외 발생 전에 모델 안에 값을 넣음.
	//		만약 예외처리 매소드와 일반 매소드가 같은 모델을 쓴다면, 
	//		해당 내용이 예외처리 매소드의 모델에도 들어갔을 것임
	@RequestMapping("/ex")
	public String main(Model m) throws Exception {
		m.addAttribute("msg", "message from ExceptionController.main()");
			throw new Exception("예외가 발생했습니다.");
	}
}

모델 속에 내용물은 없었다. 

따라서 저 매소드 둘은 예외를 던지고 잡은 것에 의해 연결된 것 뿐이지 같은 모델을 쓰지 않음을 알 수 있다. 

예외처리 매소드의 모델은 에러 시 사용하는 view에 에러 객체를 가져다주는 역할만 한다.

'백엔드 개발 > SpringMVC' 카테고리의 다른 글

DispatcherServlet Source 까보기 (어떻게 돌아가는지)  (0) 2023.03.14
Spring에서 예외 처리하는 방법(2)  (0) 2023.03.12
세션(Session) 실습(2)  (0) 2023.03.10
Session 이용 실습(1)  (0) 2023.03.09
Session- 이론  (0) 2023.03.09