[Annotation]@RestControllerAdivce / 스프링 예외 처리
- -
스프링에서는 예외 처리를 전역으로 할 수 있는 기능을 제공합니다.
오늘 포스팅할 내용은 스프링 어노테이션 중 하나인 @RestContollerAdvice를 이용한 예외 처리에 관한 것입니다.
※ 테스트 환경
- JDK 1.8
- Spring 4.3.14
- Tomcat 9.0
@RestControllerAdvice
@RestControllerAdvice는 @ControllerAdvice와 @ResponseBody가 더해진 어노테이션이다.
@ControllerAdvice는 예외 처리를 View로 응답하는 경우 사용할 수 있고 REST 요청에 대한 처리가 필요한 경우(일반적으로 JSON 형식의 데이터) @ResponseBody가 더해진 @RestControllerAdvice를 사용하면 된다.
예외를 잡아내기 위해서 @RestControllerAdivce 내에 @ExceptionHandler라는 어노테이션을 선언한 메소드를 사용한다. @ExceptionHandler는 @Controller, @RestController가 적용된 Bean에서 발생한 예외를 잡아 하나의 메소드에서 처리하는 역할을 한다. @Service에서의 예외는 잡지 못한다.
이제 테스트를 해보자.
두 가지 상황을 테스트해봤다. 첫 번째는 Get 방식의 Model을 리턴하고 두 번째는 Post 방식의 JSON 형식의 데이터를 리턴하는 상황이다.
1. Get 방식
테스트 페이지를 하나 만들고 Y 입력시에 예외가 발생하도록 했다.
ex01.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Exception 예제 01. @Restcontrolleradvice(Get방식)</title>
<%@ include file="/WEB-INF/views/include/source.jsp" %>
<script>
function sendMsg() {
var msg = $("#inputMsg").val();
location.href="/exception/sqlExceptionGet.do?msg=" + msg;
}
$(document).ready(function() {
});
</script>
</head>
<body>
<form id="testForm">
<div class="container">
<div class="row border-bottom mb-3">
<div class="col-12">
<p class="h1">EX01. @RestControllerAdvice - Get</p>
</div>
</div>
<div class="row">
<div class="col-4">
<div class="row">
<div class="col-2">
<label for="inputLabel" class="visually-hidden"></label>
<input type="text" readonly class="form-control-plaintext" id=inputLabel value="메세지">
</div>
<div class="col-auto">
<label for="inputMsg" class="visually-hidden"></label>
<input type="text" class="form-control" id="inputMsg" placeholder="Y 입력시 예외 발생">
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary mb-3" onclick="sendMsg()">전송</button>
</div>
</div>
</div>
</div>
</div>
<%@ include file="/WEB-INF/views/common/bottom.jsp" %>
</form>
</body>
</html>
먼저 공통으로 예외를 핸들링할 클래스 CusomExceptionHandler 클래스를 만들었다.
@RestControllerAdvice(annotations = ExceptionController.class)
annotations 속성에 예외 처리를 하고 싶은 클래스를 따로 지정할 수 있는데 지정하지 않는다면 전역적으로 작동된다.
CustomExceptionHandler.java
package com.study.exception;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
@RestControllerAdvice
public class CustomExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
@ExceptionHandler(CustomSqlException.class)
private ModelAndView customSqlException(HttpServletRequest request, CustomSqlException ex) {
String msg = ex.getMessage();
logger.error("### CustomSqlException");
logger.error("### 메세지 : " + msg);
if(isAjaxRequest(request)) {
return createJsonView(msg);
}
ModelAndView mav = new ModelAndView();
mav.addObject("msg", msg);
mav.setViewName("/common/message");
return mav;
}
private boolean isAjaxRequest(HttpServletRequest request) {
String header = request.getHeader("x-requested-with");
logger.info("### Request Header : " + request.getHeader("x-requested-with"));
if("XMLHttpRequest".equals(header)) {
return true;
}
return false;
}
private ModelAndView createJsonView(String msg) {
ModelAndView mav = new ModelAndView("jsonView");
mav.addObject("msg", msg);
return mav;
}
@Bean
public MappingJackson2JsonView jsonView() {
return new MappingJackson2JsonView();
}
}
CustomSqlException 예외가 발생하는 시점에 @ExceptionHandler가 등록된 메소드가 작동한다.
25번 라인에 isAjaxRequest 메소드는 두 번째 상황에서 Post 방식 ajax를 사용할 계획이었기에 요청이 ajax인지 구분하기 위해 만들었다.
ajax 요청시에는 Request 헤더에 "XMLHttpRequest" 값이 들어오는 것을 이용했다. 그리고 Model에 저장된 객체를 JSON 형식으로 변환해주는 MappingJackson2JsonView 클래스를 사용해 JSON으로 리턴하게 된다.(CreateJsonView)
MappingJackson2JsonView 클래스를 사용하기 위해서는 Maven에 Jackson 라이브러리가 등록되어야 하고 Bean을 생성해야 한다.
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.8</version>
</dependency>
servlet-context.xml
<beans:bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView" id="jsonView">
<beans:property name="contentType" value="application/json;charset=UTF-8"/>
</beans:bean>
예외 발생시 원하는 메세지를 받을 수 있게 CustomSqlException이라는 클래스를 만들었다.
CustomSqlException.java
package com.study.exception;
@SuppressWarnings("serial")
public class CustomSqlException extends Exception {
public CustomSqlException(String msg) {
super(msg);
}
}
super함수를 통해 msg를 전달하면 Exception 클래스의 내부 생성자를 통해 입력받은 메세지로 새로운 예외를 생성하게 된다. Exception 클래스는 Throwable 클래스를 상속받는다.
Exception.class
/**
* Constructs a new exception with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public Exception(String message) {
super(message);
}
그리고 컨트롤러에서 Y를 입력받을 때 "예외 발생"이라는 메세지를 보내고 예외를 발생시켰다.
ExceptionController.java
@RequestMapping(value = "/exception/sqlExceptionGet.do", method = { RequestMethod.GET })
public ModelAndView sqlExceptionGet(HttpServletRequest reqest) throws Exception {
ModelAndView mav = new ModelAndView();
String msg = reqest.getParameter("msg");
if("Y".equals(msg)) {
throw new CustomSqlException("예외 발생");
} else {
msg = "통과";
}
mav.addObject("msg", msg);
mav.setViewName("/common/message");
return mav;
}
[결과]
2. Post 방식
Post 방식도 Y 입력시 예외가 발생하도록 했다. Get 방식과 다른 것은 ajax 비동기 방식이다.
ex02.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Exception 예제 02 : @Restcontrolleradvice(Post방식)</title>
<%@ include file="/WEB-INF/views/include/source.jsp" %>
<script>
function sendMsg() {
var params = {
msg : $("#inputMsg").val()
};
$.ajax({
type : "POST",
async : false,
url : "/exception/sqlExceptionPost.do",
cache : false,
dataType : "json",
data : JSON.stringify(params),
contentType: "application/json; charset=utf-8",
success: function(data) {
alert(data.msg);
},
error : function(request, status, error) {
alert("code : " + request.status + "\n" + "message : " + request.responseText + "\n" + "error : " + error);
}
});
}
$(document).ready(function() {
});
</script>
</head>
<body>
<form id="testForm">
<div class="container">
<div class="row border-bottom mb-3">
<div class="col-12">
<p class="h1">EX02. @RestControllerAdvice - Post</p>
</div>
</div>
<div class="row">
<div class="col-4">
<div class="row">
<div class="col-2">
<label for="inputLabel" class="visually-hidden"></label>
<input type="text" readonly class="form-control-plaintext" id=inputLabel value="메세지">
</div>
<div class="col-auto">
<label for="inputMsg" class="visually-hidden"></label>
<input type="text" class="form-control" id="inputMsg" placeholder="Y 입력시 예외 발생">
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary mb-3" onclick="sendMsg()">전송</button>
</div>
</div>
</div>
</div>
</div>
<%@ include file="/WEB-INF/views/common/bottom.jsp" %>
</form>
</body>
</html>
ExceptionController.java
@RequestMapping(value = "/exception/sqlExceptionPost.do", method = { RequestMethod.POST })
public @ResponseBody Map<String, Object> sqlExceptionPost(HttpServletRequest reqest, @RequestBody String jsonParam) throws Exception {
JSONObject result = new JSONObject();
try {
JSONObject json = (JSONObject) JSONValue.parse(jsonParam);
String msg = (String) json.get("msg");
if("Y".equals(msg)) {
throw new Exception();
} else {
result.put("msg", "통과");
}
} catch (Exception e) {
// TODO: handle exception
throw new CustomSqlException("예외 발생");
}
return result;
}
11번 라인에서 Y 데이터를 받으면 예외를 발생시켰다.
디버깅을 걸어 순서를 따라가보면 예외가 발생하고 catch 문으로 빠져 CustomSqlException이 실행되면서 "예외 발생"이라는 메세지가 전달된다.
[결과]
참고자료
예외 처리를 분리하여 관리하는 이점은 비즈니스 로직에만 집중할 수 있도록 하는 것입니다. 여러분들은 어떻게 예외 처리를 하고 계신가요? 전체 소스는 GitHub에서 확인하실 수 있습니다.
+피드백은 언제나 환영입니다 :)
'Framework > Spring' 카테고리의 다른 글
[AOP]Transaction 설정 (0) | 2021.02.17 |
---|---|
[Spring Framework]Lucy-xss-filter-servlet 적용하기 (1) | 2021.02.09 |
[Spring Boot]Apache POI를 이용한 엑셀 다루기 (0) | 2021.02.01 |
[Error]Multipart 사용시 참고(404 에러) (1) | 2020.12.09 |
[Spring Framework]MyBatis 서버 재실행없이 XML 적용하기(RefreshableSqlSessionFactoryBean) (1) | 2020.12.02 |
소중한 공감 감사합니다.