Part 6. 스프링 MVC의 Controller
6.6 Controller의 Exception 처리
- Controller를 작성할 때 예외 상황을 고려하면 처리해야 하는 작업이 많이 늘어난다.
- 스프링 MVC에서는 이러한 작업을 다음과 같은 방식으로 처리할 수 있다.
- @ExceptionHandler와 @ControllerAdvice를 이용한 처리
- @ResponseEntity를 이용하는 예외 메시지 구성
6.6.1 @ControllerAdvice
- @ControllerAdivce는 뒤에서 배우게 되는 AOP를 이용하는 방식이다.
- AOP는 핵심적인 로직은 아니지만 프로그램에서 필요한 공통적인 관심사는 분리하자는 개념이다.
- Controller를 작성할 때는 메서드의 모든 예외사항을 전부 핸들링해야 한다면 중복적이고 많은 양의 코드를 작성해야 하지만, AOP 방식을 이용하면 공통적인 예외사항에 대해서는 별도로 @ControllerAdvice를 이용해서 분리하는 방식이다.
- 예제를 위해 프로젝트에 org.zerock.exception이라는 패키지를 생성하고, CommonExceptionAdvice 클래스를 생성한다.
- CommonExceptionAdvice는 @ControllerAdvice 어노테이션을 적용하지만 예외 처리를 목적으로 생성하는 클래스이므로 별도의 로직을 처리하지 않는다.
< CommonException Advice 클래스 >
package org.zerock.exception;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import lombok.extern.log4j.Log4j;
@ControllerAdvice
@Log4j
public class CommonExceptionAdvice {
@ExceptionHandler(Exception.class)
public String except(Exception ex, Model model) {
log.error("Exception........." + ex.getMessage());
model.addAttribute("exceptrion", ex);
log.error(model);
return "error_page";
}
}
- CommonExcptionAdvice 클래스에는 @ControllerAdvice라는 어노테이션과 @ExceptionHandler라는 어노테이션을 사용하고 있다.
- @ControllerAdvice는 해당 객체가 스프링의 컨틀롤러에서 발생하는 예외를 처리하는 존재임을 명시하는 용도로 사용하고, @ExceptionHandler는 해당 메서드가 () 들어가는 예외 타입을 처리한다는 것을 의미한다.
- @ExceptionHandler 어노테이션 속성으로는 Exception 클래스 타입을 지정할 수 있다.
- 위와 같은 경우 Exception.class를 지정하였으므로 모든 예외에 대한 처리가 except()만을 이용해 처리할 수 있다.
- 만일 특정한 타입의 예외를 다루고 싶다면 Exception.class 대신에 구체적인 예외의 클래스를 지정해야 한다.
- JSP 화면에서도 구체적인 메시지를 보고 싶다면 Model을 이용해서 전달하는 것이 좋다.
- org.zerock.exception 패키지는 servlet-context.xml에서 인식하지 않기 때문에 < component-scan >을 이용해서 해당 패키지의 내용을 조사하도록 해야 한다.
< servlet-context.xml >
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name ="defaultEncoding" value="utf-8"></beans:property>
<beans:property name="maxUploadSize" value="104857560"></beans:property>
<beans:property name="maxUploadSizePerFile" value="2097152"></beans:property>
<beans:property name="uploadTempDir" value="file:/D:/upload/tmp"></beans:property>
<beans:property name="maxInMemorySize" value="10485756"></beans:property>
</beans:bean>
<context:component-scan base-package="org.zerock.controller" />
<context:component-scan base-package="org.zerock.exception" />
- CommonExceptionAdvice의 except()의 리턴값은 문자열이므로 JSP 파일의 경로가 된다.
- JSP는 error_page.jsp이므로 /WEB-INF/views 폴더 내 작성해야 한다.
< error_page.jsp >
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" import="java.util.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4><c:out value="${exception.getMessage()}"></c:out></h4>
<ul>
<c:forEach items="${exception.getStackTrace() }" var="stack">
<li><c:out value="${stack}"></c:out></li>
</c:forEach>
</ul>
</body>
</html>
- 예외의 메시지가 정상적으로 출력되는지 확인해 보려면 고의로 숫자나 날짜 등의 파라미터 값을 변환에 문제 있게 만들어서 호출해 볼 수 있다.
- 예를들어, '/sample/ex04?name=aaa&age=11&page=9'와 호출해야 하는 URL에서 고의로 age 값을 숫자로 변환할 수 없게 다른 값을 전달해 보가나 page와 같은 파라미터를 생략하는 등의 작업을 통해 확인할 수 있다.
- 아래 화면은 고의로 age 값을 'bbb'와 같은 문자열로 전송하였을 때 보이는 화면이다.

Java 설정을 이용하는 경우
- ServletConfig 클래스에 'org.zerock.exception' 패키지를 인식해야 하므로 아래와 같이 'org.zerock'exception' 패키지를 추가해야 한다.
< ServletConfig 클래스 >
@EnableWebMvc
@ComponentScan(basePackages = { "org.zerock.controller", "org.zerock.exception" })
public class ServletConfig implements WebMvcConfigurer
6.6.2 404 에러 페이지
- WAS의 구동 중 가장 흔한 에러와 관련된 HTTP 상태 코드는 '404'와 '500' 에러코드다.
- 500 메시지는 'Internal Server Error'이므로 @ExceptionHandler를 이용해 처리되지만, 잘못된 URL을 호출할 때 보이는 404 에러 메시지의 경우는 조금 다르게 처리하는 것이 좋다.
- 서블릿이나 JSP를 이용했던 개발 시에는 web.xml을 이용해 별도의 에러 페이지를 지정할 수 있다.
- 에러 발생 시 추가적인 작업을 하기는 어렵기 때문에 스프링을 이용해 404와 같이 WAS 내부에서 발생하는 에러를 처리하는 방식을 알아두는 것이 좋다.
- 스프링 MVC의 모든 요청은 DispatcherServlet을 이용해 처리하므로 404 에러도 같이 처리할 수 있도록 web.xml을 수정한다.
< web.xml >
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
- org.zerock.exception.CommonExceptionAdvice에는 다음과 같이 메서드를 추가한다.
< CommonExceptionAdvice >
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handle40(NoHandlerFoundException ex) {
return "custom404";
}
- 에러 메시지는 custom404.jsp를 작성해 처리한다.
< custom404.jsp >
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC-"-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>해당 URL은 존재하지 않습니다.</h1>
</body>
</html>
- 브라우저에서 존재하지 않는 URL을 호출하면 custom404.jsp 페이지가 보이는 것을 학인할 수 있다.('/sample/..로 시작하는 URL의 경우에는 SampleController가 무조건 동작하므로 이를 제외한 경로로 테스트한다.)

Java 설정을 이용하는 경우
- web.xml에 설정한 throwExcptionIfNoHandlerFound를 설정하기 위해서는 서블릿 3.0 이상을 이용해야만 하고 WebConfig 클래스를 아래와 같이 수정해야 한다.
< WebConfig 클래스 >
package org.zerock.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {ServletConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamicregistration) { registration.setInitParameter("throwExceptionIfNoHandlerFound","true");
}
}