[Spring] @ExceptionHandler | @ControllerAdvice 예외 처리

Jeini·2023년 5월 23일
0

🍃  Spring

목록 보기
11/33
post-thumbnail

💡 Spring에서 예외 처리 요약


✔️ Controller 메서드 내에서 try-catch로 처리

✔️ Controller에 @ExceptionHandler 메서드가 처리

✔️ @ControllerAdivice 클래스의 @ExceptionHandler 메서드가 처리

✔️ 예외 종류별로 뷰 지정: SimpleMappingExceptionResolver

✔️ 응답 상태 코드별로 뷰 지정: <error-page>

💡 예외 처리 해보기


✏️ ExceptionController

✔️ try-catch 하기 전 ➡️ Server 에러

package kr.ac.jipark09;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {
	@RequestMapping("/ex/")
	public void main() throws Exception {
		throw new Exception();
	}

}

✔️ try-catch를 한 후 ➡️ Client 에러

package kr.ac.jipark09;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {
	@RequestMapping("/ex")
	public void main() throws Exception {
		try {
			throw new Exception("예외가 발생했습니다.");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

  • 예외는 발생해서 view를 찾을 수 없어서 404가 뜸
    ➡️ catch 블럭에 error.jsp를 바로 리턴하자

✔️ catch 블럭에 error를 리턴

package kr.ac.jipark09;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {
	@RequestMapping("/ex")
	public String main() throws Exception {
		try {
			throw new Exception("예외가 발생했습니다.");
		} catch (Exception e) {
			return "error";
		}
	}
}

✏️ 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>

💡 @ExceptionHandler


✔️ Controller계층에서 발생하는 에러를 잡아서 메서드로 처리해주는 기능

✏️ @ExceptionHandler 사용

package kr.ac.jipark09;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {
	
	// NullPotinerException
	@ExceptionHandler(NullPointerException.class)
	public String catcher2(NullPointerException e,  Model model) {
		model.addAttribute("ex", e);
		return "error";
	}
	
	// Exception
	@ExceptionHandler(Exception.class)
	public String catcher(Exception e, Model model) {
		model.addAttribute("ex", e);
		return "error";
	}
	
	@RequestMapping("/ex")
	public String main() throws Exception {
		throw new Exception("예외가 발생하였습니다.");
	}
	
	@RequestMapping("/ex2")
	public String main2() throws Exception {
		throw new NullPointerException("예외가 발생하였습니다.");
	}
}
  • @ExceptionHandler 어노테이션을 쓰고 인자로 캐치하고 싶은 예외클래스를 등록하면 된다.

  • Controller내에 있는 메서드에서 예외가 발생하면, catcher 메서드가 받는다
    ➡️ catch블럭으로 인식해도 좋다.

❗️ 만약 @RequestMapping에 들어가서 처리할 예외가 없으면 최고조상인 Exception이 받는다.

💡 @ControllerAdvice


✔️ 모든 Controller에서 사용할 수 있게 예외 처리

  • 전역 예외 처리 클래스 작성 가능 (패키지 지정 가능)

  • 예외 처리 메서드가 중복된 경우, Controller 내의 예외 처리 메서드가 우선

  • 예외 처리하는 메서드들을 별도의 클래스에 넣어놓고 그 위에 다가 @ControllerAdive 어노테이션을 붙인다.
    ➡️ 이 메서드들이 모든 Controller에서 발생하는 예외를 처리하게 된다.

✏️ GlobalCatcher: @ControllerAdvice를 통한 모든 Controller 예외 처리

package kr.ac.jipark09;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalCatcher {
	// NullPotinerException
		@ExceptionHandler(NullPointerException.class)
		public String catcher2(NullPointerException e,  Model model) {
			model.addAttribute("ex", e);
			return "error";
		}
		
		// Exception
		@ExceptionHandler(Exception.class)
		public String catcher(Exception e, Model model) {
			model.addAttribute("ex", e);
			return "error";
		}

}
  • ExceptionController에 @ExceptionHandler 를 사용해주지 않아도 GlobalCatcher 클래스에서 예외를 처리해 주고 있기 때문에 error.jsp가 잘 나온다.

💡 @ExceptionHandler 메서드의 model VS @RequestMapping 메서드의 model


✔️ 이 둘의 model 객체는 다르다.

✏️ 둘의 model 객체가 같은지 다른지 test

package kr.ac.jipark09;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ExceptionController {
	
	// NullPotinerException
	@ExceptionHandler(NullPointerException.class)
	public String catcher2(NullPointerException e,  Model model) {
		model.addAttribute("ex", e);
		return "error";
	}
	
	// Exception
	@ExceptionHandler(Exception.class)
	public String catcher(Exception e, Model model) {
		System.out.println("model=" + model);
		model.addAttribute("ex", e);
		return "error";
	}
	
	@RequestMapping("/ex")
	public String main(Model model) throws Exception {
		model.addAttribute("msg", "message from ExceptionController.main()");
		throw new Exception("예외가 발생하였습니다.");
	}
	
	@RequestMapping("/ex2")
	public String main2() throws NullPointerException {
		throw new NullPointerException("예외가 발생하였습니다.");
	}
}
[console]

model={}
  • 이 둘의 model 객체가 같다면 콘솔에 main 메서드에서 저장했던 model 객체가 나왔어야 했다.
    ➡️ 나오지 않음 즉, 이 둘은 같은 model 객체가 아니다.

  • ❗️ Controller 메서드에서 예외를 처리하는 메서드한테 데이터를 전달하는 것이 아님

💡 @ResponseStatus


✔️ 응답 메시지의 상태 코드를 변경할 때 사용

@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) // 405 method not allowed
@ExceptionHandler({NullPointerException.class, ClassCastException.class})
public String catcher(Exception ex, Model m) {
	m.addAttribute("ex", ex) {
    	return "error"
    }
}
  1. 예외 처리 메서드에다가 처리

  2. 이때 error.jsp view를 처리하게 되는데, 응답코드가 200이다. ➡️ 요청 처리 성공
    : 하지만 예외가 떴는데 요청을 성공적으로 처리했다는 것을 인식주어서는 안된다.
    : 200 대신 400번대나 500번을 바꿔줘야 한다.

  3. METHOD_NOT_ALLOWED: 405

✔️ 사용자정의 예외 클래스

@ResponseStatus(HttpStatus.BAD_REQUEST) // 400 Bad Request.
class MyException extends RuntimeException {
	MyException(String msg) {
    	super(msg);
    }
    
    MyException() {
    	this("");
    }
}
  • @ResponseStatus 이거 안쓰면 500번 뜬다.

✏️ error.jsp

✔️ <%@page isErrorPage = "true"> 를 사용하면 해당 페이지는 에러페이지가 됨

<%@ page contentType="text/html;charset=utf-8" isErrorPage="true"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
	<title>error.jsp</title>
</head>
<body>
<h1>예외가 발생했습니다.</h1>
발생한 예외 : ${pageContext.exception}<br>
예외 메시지 : ${pageContext.exception.message}<br>
<ol>
<c:forEach items="${pageContext.exception.stackTrace}" var="i">
	<li>${i.toString()}</li>
</c:forEach>
</ol>
</body>
</html>
  • pageContext.exception 객체 사용

  • <%@page isErrorPage = "true"> 로 해 놓으면 상태코드를 바꿔도 항상 500번대로 바뀌게 됨

💡 <error-page>: web.xml


✔️ 상태 코드 별 View 맵핑

✏️ ExceptionController2

package kr.ac.jipark09;

import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST) // 500 -> 400
class MyException extends RuntimeException {
	public MyException(String msg) {
		super(msg);
	}
	public MyException() {}
}

@Controller
public class ExceptionController2 {

	@RequestMapping("/ex3")
	public String main() throws Exception {
		throw new MyException("예외가 발생하였습니다.");
	}
	
	@RequestMapping("/ex4")
	public String main2() throws NullPointerException {
		throw new NullPointerException("예외가 발생하였습니다.");
	}
}

✏️ web.xml <error-page> 등록

💡 SimpleMappingExceptionResolver


✔️ 예외 종류별 View 맵핑에 사용. serlvet-context.xml에 등록

  • 예외 종류 - 에러 뷰

  • 에러 뷰에 <%@page isErrorPage = "true"> 가 설정되어 있으면 항상 500번 대로 뜨게 된다.
    ➡️ false 로 바꾸면 404로 설정한 상태코드가 잘 나오게 된다.

💡 ExceptionResolver


✔️ Spring의 예외 처리 기본 전략

  • 위 예제 코드에 try-catch가 되어있지 않다. 그러면 예외가 자신을 호출한 쪽으로 전달하게 되고 죽는다.

  • DispatcherServlet 은 이 예외를 처리할려고 handlerExceptionResolvers 에 가서 등록되어 있는 것들을 본다. 이 예외를 처리할 수 있는지 순서대로 훑어보게 된다.

✏️ ExceptionHandlerExceptionResolver

  • NullPointerException이 뜨면, 첫 번째, ExceptionHandlerExceptionResolver 가 이 예외를 처리할 수 있는 @ExceptionHandler 메서드가 있는지 찾는다.
    ➡️ Controller ➡️ @ControllerAdvice / @ExceptionHandler ➡️ "error" 반환

  • 첫번째 리졸버가 찾지 못하면 2, 3 순으로 찾는다.

✏️ ResponseStatusExceptionResolver

  • 500 ➡️ 400 으로 바꿔주는 역할

  • web.xml에 가서 400번에 해당하는 뷰가 있는지 확인한다. ➡️ /error400.jsp 반환

✏️ DefalutHandlerExceptionResolver

  • 스프링에 정의된 예외의 상태코드를 기본 500에서 상황에 따라 400이나 500으로 바꿔줌

📄 DispatcherServlet.properties

✔️ DispatcherServlet이 사용하는 기본전략을 적어놓은 곳

  • 여기서 예외를 처리하게 된다.

Reference
:https://fastcampus.co.kr/dev_academy_nks

profile
Fill in my own colorful colors🎨

0개의 댓글