Exception
μμΈ μ²λ¦¬μ μ€λ₯ νμ΄μ§
μλΈλ¦Ώ μμΈ μ²λ¦¬
μμ μλΈλ¦Ώ 컨ν
μ΄λμ μμΈ μ²λ¦¬ μ§μ λ°©μ
.
Exception
μλ° μ§μ μ€ν μ μμΈ λ°μ
μλ° λ©μΈ λ©μλλ₯Ό μ§μ μ€νν κ²½μ° main μ°λ λ μ€ν
main μ°λ λ μ€ν λμ€μ μμΈλ₯Ό μ‘μ§ λͺ»νκ³ μμΈκ° λμ Έμ§λ©΄, μμΈ μ 보λ₯Ό λ¨κΈ°κ³ ν΄λΉ μ°λ λ μ’
λ£
μΉ μ ν리μΌμ΄μ
μμ μμΈ λ°μ
Copy @GetMapping("/error-ex")
public void errorEx() {
throw new RuntimeException("μμΈ λ°μ!");
}
μ¬μ©μ μμ²λ³λ‘ λ³λμ μ°λ λκ° ν λΉλκ³ , μλΈλ¦Ώ 컨ν
μ΄λ μμμ μ€ν
μ ν리μΌμ΄μ
μμ μμΈ λ°μ μ, try~catch λ‘ μμΈλ₯Ό μ‘μμ μ²λ¦¬νλ©΄ λ¬Έμ κ° μμ.
νμ§λ§, μμΈλ₯Ό μ‘μ§ λͺ»νκ³ , μλΈλ¦Ώ λ°μΌλ‘ μμΈκ° μ λ¬λ κ²½μ° WAS(tomcat)κΉμ§ μμΈ μ λ¬
Copy WAS <- νν° <- μλΈλ¦Ώ <- μΈν°μ
ν° <- 컨νΈλ‘€λ¬(μμΈλ°μ)
WAS λ Exception μμΈκ° μ¬λΌμ€λ©΄ μλ² λ΄λΆμμ μ²λ¦¬ν μ μλ μ€λ₯κ° λ°μν κ²μΌλ‘ μΈμ§νκ³ HTTP Status 500 β Internal Server Error λ°ν
Copy # μ€νλ§ λΆνΈκ° μ 곡νλ κΈ°λ³Έ μμΈ νμ΄μ§ OFF
server.error.whitelabel.enabled=false
.
response.sendError(HTTP μν μ½λ, μ€λ₯ λ©μμ§)
Copy @GetMapping("/error-404")
public void error404(HttpServletResponse response) throws IOException {
response.sendError(404, "404 μ€λ₯!"); // HTTP μν μ½λμ μ€λ₯ λ©μμ§ μΆκ° κ°λ₯
}
μ€λ₯ λ°μ μ HttpServletResponse.sendError
λ©μλ μ¬μ© κ°λ₯
sendError νΈμΆ μ λ°λ‘ μμΈκ° λ°μνλ κ²μ μλμ§λ§, response λ΄λΆμ μ€λ₯ λ°μ μνλ₯Ό μ μ₯
νμ¬ WAS(Servlet container) μκ² μ λ¬
WAS(Servlet container) λ ν΄λΌμ΄μΈνΈμκ² μλ΅ μ μ response μ sendError() νΈμΆ κΈ°λ‘ νμΈ
ν, νΈμΆλμλ€λ©΄ μ€μ ν μ€λ₯ μ½λμ λ§λ κΈ°λ³Έ μ€λ₯ νμ΄μ§ μΆλ ₯
sendError νλ¦
Copy WAS(sendError νΈμΆ κΈ°λ‘ νμΈ) <- νν° <- μλΈλ¦Ώ <- μΈν°μ
ν° <- 컨νΈλ‘€λ¬
μλΈλ¦Ώ μ€λ₯ νμ΄μ§
μλΈλ¦Ώμ Exception
μ΄ μλΈλ¦Ώ λ°μΌλ‘ μ λ¬λκ±°λ response.sendError()
νΈμΆ μ κ° μν©μ λ§μΆ μ€λ₯ μ²λ¦¬ κΈ°λ₯ μ 곡
μλΈλ¦Ώ μ€λ₯ νμ΄μ§ λ±λ‘
Copy @Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404"); // response.sendError(404)
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500"); // response.sendError(500)
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class, "/error-page/500"); // RuntimeException λλ κ·Έ μμ νμ
μ μμΈ
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
μ€λ₯ νμ΄μ§λ μμΈλ₯Ό λ€λ£° λ ν΄λΉ μμΈμ κ·Έ μμ νμ
μ μ€λ₯λ₯Ό ν¨κ» μ²λ¦¬
μ€λ₯ μ²λ¦¬ 컨νΈλ‘€λ¬
Copy @Slf4j
@Controller
public class ErrorPageController {
@RequestMapping("/error-page/404")
public String errorPage404(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 404");
return "error-page/404";
}
@RequestMapping("/error-page/500")
public String errorPage500(HttpServletRequest request, HttpServletResponse response) {
log.info("errorPage 500");
return "error-page/500";
}
}
μ€λ₯ νμ΄μ§ μμ² νλ¦
Copy # μμΈ λ°μ νλ¦
컨νΈλ‘€λ¬(μμΈ λ°μ) β μ€νλ§ μΈν°μ
ν° β μλΈλ¦Ώ β νν° β WAS
# sendError νλ¦
컨νΈλ‘€λ¬(response.sendError()) β μ€νλ§ μΈν°μ
ν° β μλΈλ¦Ώ β νν° β WAS(sendError νΈμΆ κΈ°λ‘ νμΈ)
---
# μ€λ₯ νμ΄μ§ μμ² νλ¦
WAS '/error-page/500' μμ² β νν° β μλΈλ¦Ώ β μ€νλ§ μΈν°μ
ν° β 컨νΈλ‘€λ¬(/error-page/500) β View
WAS κΉμ§ μμΈκ° μ νλ κ²½μ°, WAS λ ν΄λΉ μμΈλ₯Ό μ²λ¦¬νλ μ€λ₯ νμ΄μ§ μ 보
νμΈ ν μ€λ₯ νμ΄μ§λ₯Ό λ€μ μμ²
WAS λ μ€λ₯ νμ΄μ§ μμ² μ μ€λ₯ μ 보λ₯Ό request.attribute μ μΆκ°ν΄μ μ λ¬
Copy private void printErrorInfo(HttpServletRequest request) {
log.info("ERROR_EXCEPTION: ex=", request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
log.info("ERROR_EXCEPTION_TYPE: {}", request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE));
log.info("ERROR_MESSAGE: {}", request.getAttribute(RequestDispatcher.ERROR_MESSAGE)); // exμ κ²½μ° NestedServletException μ€νλ§μ΄ νλ² κ°μΈμ λ°ν
log.info("ERROR_REQUEST_URI: {}", request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI));
log.info("ERROR_SERVLET_NAME: {}", request.getAttribute(RequestDispatcher.ERROR_SERVLET_NAME));
log.info("ERROR_STATUS_CODE: {}", request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
log.info("dispatchType={}", request.getDispatcherType());
}
DispatcherType π
ν΄λΌμ΄μΈνΈλ‘ λΆν° λ°μν μ μ μμ²μΈμ§, μ€λ₯ νμ΄μ§λ₯Ό μΆλ ₯νκΈ° μν λ΄λΆ μμ²μΈμ§ ꡬλΆνκΈ° μν΄ μλΈλ¦Ώμ DispatcherType
μ 보 μ 곡
μ€λ₯ λ°μ μ μ€λ₯ νμ΄μ§ μΆλ ₯μ μν΄ WAS λ΄λΆμμ νν°, μλΈλ¦Ώ, μΈν°μ
ν°λ₯Ό λ€μ νΈμΆ
μ΄λ―Έ μ΄κΈ° μμ²μμ κ²μ¦μ΄ μλ£λ λΆλΆμ μ¬νΈμΆ λ κ²½μ° λΉν¨μ¨μ
.
DispatcherType
μλΈλ¦Ώ μ€νμ μ€μ κ³ κ°μ΄ μμ²ν κ²μΈμ§, μλ²κ° λ΄λΆμμ μ€λ₯ νμ΄μ§λ₯Ό μμ²νλ κ²μΈμ§ DispatcherType
μΌλ‘ ꡬλΆν μ μλ λ°©λ²μ μ 곡
Copy public enum DispatcherType {
FORWARD, // λ€λ₯Έ μλΈλ¦Ώμ΄λ JSP νΈμΆ β RequestDispatcher.forward(request, response)
INCLUDE, // λ€λ₯Έ μλΈλ¦Ώμ΄λ JSP κ²°κ³Ό ν¬ν¨ β RequestDispatcher.include(request, response)
REQUEST, // ν΄λΌμ΄μΈνΈ μμ²
ASYNC, // μλΈλ¦Ώ λΉλκΈ° νΈμΆ
ERROR // μ€λ₯ μμ²
}
.
νν°μ DispatcherType
DispatcherType λ‘κ·Έ νν°
Copy @Slf4j
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
String uuid = UUID.randomUUID().toString();
try {
log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
λ‘κ·Έ νν° λ±λ‘
Copy @Configuration
public class DispatcherTypeWebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
// default: REQUEST. ν΄λΌμ΄μΈνΈ μμ² μμλ§ νν° μ μ©
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
return filterRegistrationBean;
}
}
.
νν°μ Interceptor
DispatcherType λ‘κ·Έ μΈν°μ
ν°
Copy @Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
log.info("REQUEST [{}][{}][{}][{}]", uuid, request.getDispatcherType(), requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String) request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}][{}]", logId, request.getDispatcherType(), requestURI);
if (ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
λ‘κ·Έ μΈν°μ
ν° λ±λ‘
Copy @Configuration
public class DispatcherTypeWebConfig implements WebMvcConfigurer {
/**
* νν°λ νν° λ±λ‘ μ νΉμ DispatcherType μΈ κ²½μ° νν°κ° μ μ©λλλ‘ μ€μ μ΄ κ°λ₯νμ§λ§,
* μΈν°μ
ν°λ μ€νλ§μ΄ μ 곡νλ κΈ°λ₯μ΄λΌμ DispatcherType μ 무κ΄νκ² νμ νΈμΆ
*
* λμ μΈν°μ
ν°μ excludePathPatterns λ₯Ό μ¬μ©ν΄μ νΉμ κ²½λ‘ μ μΈ κ°λ₯
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns(
"/css/**", "/*.ico"
, "/error", "/error-page/**" //μ€λ₯ νμ΄μ§ κ²½λ‘
);
}
}
.
DispatcherType νλ¦
Copy 1. WAS(/error-ex, dispatchType=REQUEST) β νν° β μλΈλ¦Ώ β μΈν°μ
ν° β 컨νΈλ‘€λ¬
2. 컨νΈλ‘€λ¬(μμΈλ°μ) β μΈν°μ
ν° β μλΈλ¦Ώ β νν° β WAS
3. WAS μ€λ₯ νμ΄μ§ νμΈ
4. WAS(/error-page/500, dispatchType=ERROR) β νν°(x) β μλΈλ¦Ώ β μΈν°μ
ν°(x) β
컨νΈλ‘€λ¬(/error-page/500) β View
μ€νλ§ λΆνΈ μ€λ₯ νμ΄μ§ π
μ€νλ§ λΆνΈλ μλΈλ¦Ώ μ€λ₯ νμ΄μ§ νΈμΆμ νμνλ μλ 볡μ‘ν κ³Όμ μ κΈ°λ³ΈμΌλ‘ μ 곡
WebServerCustomizer λ§λ€κΈ°
μμΈ μ’
λ₯μ λ°λΌ ErrorPage μΆκ° β ErrorPage μλ λ±λ‘
/error
κ²½λ‘λ₯Ό κΈ°λ³Έ μ€λ₯ νμ΄μ§λ‘ μ€μ
μλΈλ¦Ώ λ°μΌλ‘ μμΈκ° λμ Έμ§κ±°λ, response.sendError(...)
νΈμΆ μ λͺ¨λ μ€λ₯λ /error
νΈμΆ
ErrorMvcAutoConfiguration
ν΄λμ€κ° μ€λ₯ νμ΄μ§λ₯Ό μλμΌλ‘ λ±λ‘νλ μν
μμΈ μ²λ¦¬μ© 컨νΈλ‘€λ¬(ErrorPageController) μμ± β μλ λ±λ‘(BasicErrorController
)
ErrorPage μμ λ±λ‘ν /error λ₯Ό 맀νν΄μ μ²λ¦¬νλ 컨νΈλ‘€λ¬
.
BasicErrorController
κΈ°λ³Έμ μΈ μ€λ₯ νμ΄μ§ λ‘μ§μ΄ λͺ¨λ ꡬν
κ°λ°μλ μ€λ₯ νμ΄μ§ νλ©΄λ§ BasicErrorController κ° μ 곡νλ λ£°κ³Ό μ°μ μμμ λ°λΌμ λ±λ‘
μ μ HTML μΌ κ²½μ° μ μ 리μμ€, λ·° ν
νλ¦Ώμ μ¬μ©ν λμ μ€λ₯ νλ©΄μΌ κ²½μ° λ·° ν
νλ¦Ώ κ²½λ‘μ μ€λ₯ νμ΄μ§ νμΌ μμ±
.
BasicErrorController View μ ν μ°μ μμ
.1. λ·° ν
νλ¦Ώ
resources/templates/error/500.html
resources/templates/error/5xx.html
.2. μ μ 리μμ€(static, public)
resources/static/error/400.html
resources/static/error/404.html
resources/static/error/4xx.html
.3. μ μ© λμμ΄ μμ λ λ·° μ΄λ¦(error)
resources/templates/error.html
ν΄λΉ κ²½λ‘ μμΉμ HTTP μν μ½λ μ΄λ¦μ λ·° νμΌμ λ£μ΄λμ.
λ·° ν
νλ¦Ώμ΄ μ μ 리μμ€λ³΄λ€ μ°μ μμκ° λκ³ ,
404, 500 μ²λΌ ꡬ체μ μΈ κ²μ΄ 5xxμ²λΌ λ ꡬ체μ μΈ κ² λ³΄λ€ μ°μ μμκ° λλ€.
.
BasicErrorController μ 곡 κΈ°λ³Έ μ 보
κΈ°λ³Έ μ 보λ₯Ό model μ λ΄μ View μ μ λ¬
Copy timestamp: Fri Feb 05 00:00:00 KST 2021
path: `/hello` (Client μμ² κ²½λ‘ )
status: 400
message: Validation failed for object='data'. Error count: 1
error: Bad Request
exception: org.springframework.validation.BindException
errors: Errors(BindingResult)
trace: μμΈ trace
message
, exception
, errors
, trace
μ 보λ 보μμ default λ‘ ν¬ν¨μ΄ λμ΄μμ§ μμ
properties μ€μ μ ν΅ν΄ μ€λ₯ μ 보λ₯Ό model μ ν¬ν¨ν μ§ μ¬λΆ μ ν
Copy # exception ν¬ν¨ μ¬λΆ(true, false)
server.error.include-exception=true
# message ν¬ν¨ μ¬λΆ
server.error.include-message=always
# trace ν¬ν¨ μ¬λΆ
server.error.include-stacktrace=always
# errors ν¬ν¨ μ¬λΆ
server.error.include-binding-errors=always
never
: μ¬μ©νμ§ μμ
on_param
: νλΌλ―Έν°κ° μμ λ ν΄λΉ μ 보 λ
ΈμΆ
HTTP μμ²μ νλΌλ―Έν°(?message=&errorsa=&trace=)λ₯Ό μ λ¬νλ©΄ ν΄λΉ μ 보λ€μ΄ model μ λ΄κ²¨ λ·° ν
νλ¦Ώμ μΆλ ₯
μ΄μ μλ²μμλ λΉκΆμ₯
μ€λ¬΄μμλ μ΄ μ 보λ€μ λ
ΈμΆνλ©΄ μλλ€.
μ¬μ©μμκ²λ κΉλν μ€λ₯ νμ΄μ§μ κ³ κ°μ΄ μ΄ν΄ν μ μλ κ°λ¨ν μ€λ₯ λ©μμ§λ₯Ό 보μ¬μ£Όκ³ ,
μ€λ₯λ μλ²μ λ‘κ·Έλ‘ λ¨κ²¨μ λ‘κ·Έλ‘ νμΈνμ.
.
μ€νλ§ λΆνΈ μ€λ₯ κ΄λ ¨ μ΅μ
Copy # μ€λ₯ μ²λ¦¬ νλ©΄μ λͺ» μ°Ύμ κ²½μ°, μ€νλ§ whitelabel μ€λ₯ νμ΄μ§ μ μ©
server.error.whitelabel.enabled=true
# μ€λ₯ νμ΄μ§ κ²½λ‘
# μ€νλ§μ΄ μλ λ±λ‘νλ μλΈλ¦Ώ κΈλ‘λ² μ€λ₯ νμ΄μ§ κ²½λ‘μ BasicErrorController μ€λ₯ 컨νΈλ‘€λ¬ κ²½λ‘μ ν¨κ» μ¬μ©
server.error.path=/error
νμ₯ ν¬μΈνΈ
μλ¬ κ³΅ν΅ μ²λ¦¬ 컨νΈλ‘€λ¬μ κΈ°λ₯μ λ³κ²½νκ³ μΆμ κ²½μ° ErrorController μΈν°νμ΄μ€λ₯Ό μμ λ°μμ ꡬννκ±°λ, BasicErrorController λ₯Ό μμ λ°μμ κΈ°λ₯μ μΆκ°ν΄λ³΄μ.
μ€νλ§ λΆνΈ κΈ°λ³Έ μ 곡 μ€λ₯ νμ΄μ§λ₯Ό νμ©νλ©΄ μ€λ₯ νμ΄μ§ κ΄λ ¨ λλΆλΆμ λ¬Έμ λ μμ½κ² ν΄κ²° κ°λ₯νλ€.
API μμΈ μ²λ¦¬
μ€λ₯ νμ΄μ§
λ λ¨μν κ³ κ°μκ² μ€λ₯ νλ©΄μ 보μ¬μ£Όλ©΄ λμ§λ§, API
λ κ° μ€λ₯ μν©μ λ§λ μ€λ₯ μλ΅ μ€νμ μ νκ³ , JSONμΌλ‘ λ°μ΄ν°λ₯Ό μλ΅ν΄ μ£Όμ΄μΌ νλ€.
produces μ€μ
Copy @RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorPage500Api(HttpServletRequest request, HttpServletResponse response) {
// ...
return new ResponseEntity(result, HttpStatus.valueOf(statusCode));
}
produces = MediaType.APPLICATION_JSON_VALUE
ν΄λΌμ΄μΈνΈκ° μμ²νλ HTTP Header Accept κ°μ΄ application/json μΌ λ ν΄λΉ λ©μλ νΈμΆ
ResponseEntity λ λ©μμ§ μ»¨λ²ν°κ° λμνλ©΄μ ν΄λΌμ΄μΈνΈμκ² JSON κ΅¬μ‘°λ‘ λ³ννμ¬ λ°ν
Spring Boot κΈ°λ³Έ μ€λ₯ μ²λ¦¬
API μμΈ μ²λ¦¬λ μ€νλ§ λΆνΈκ° μ 곡νλ κΈ°λ³Έ μ€λ₯ λ°©μμ μ¬μ©
BasicErrorController
μ½λ μΌλΆ
Copy // ν΄λΌμ΄μΈνΈ μμ²μ Accept ν΄λ κ°μ΄ text/html μΈ κ²½μ° νΈμΆ(view μ 곡)
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { ... }
// κ·ΈμΈ κ²½μ°μ νΈμΆ(ResponseEntity λ‘ HTTP Body μ JSON λ°μ΄ν° λ°ν)
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { ... }
μ€νλ§ λΆνΈμ κΈ°λ³Έ μ€μ μ μ€λ₯ λ°μμ /error
λ₯Ό μ€λ₯ νμ΄μ§λ‘ μμ²
BasicErrorController
λ /error
κ²½λ‘λ₯Ό κΈ°λ³ΈμΌλ‘ λ°μ.(server.error.path
λ‘ μμ κ°λ₯
BasicErrorController
λ HTML μ€λ₯ νμ΄μ§λ₯Ό μ 곡νλ κ²½μ° λ§€μ° νΈλ¦¬
λ¨, API λ κ° μ»¨νΈλ‘€λ¬λ μμΈλ§λ€ μλ‘ λ€λ₯Έ μλ΅ κ²°κ³Όλ₯Ό μΆλ ₯ν΄μΌ νλ―λ‘ @ExceptionHandler
μ¬μ© κΆμ₯
ExceptionResolver
HandlerExceptionResolver
Copy public interface HandlerExceptionResolver {
ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler, // νΈλ€λ¬(컨νΈλ‘€λ¬) μ 보
Exception ex // νΈλ€λ¬(컨νΈλ‘€λ¬)μμ λ°μν μμΈ
);
}
μ€νλ§ MVC λ 컨νΈλ‘€λ¬(νΈλ€λ¬) λ°μΌλ‘ μμΈκ° λμ Έμ§ κ²½μ° μμΈλ₯Ό ν΄κ²°νκ³ , λμμ μλ‘ μ μν μ μλ HandlerExceptionResolver
μ 곡
ExceptionResolver μ μ© μ μμΈμ²λ¦¬
ExceptionResolver μ μ© ν μμΈμ²λ¦¬
μ°Έκ³ . ExceptionResolver λ‘ μμΈλ₯Ό ν΄κ²°ν΄λ postHandle() νΈμΆ X
HandlerExceptionResolver Interface ꡬν
Copy @Slf4j
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
/**
* ModelAndView λ₯Ό λ°ννλ μ΄μ λ try-catch μ²λΌ Exception μ μ²λ¦¬ν΄μ μ μ νλ¦μ²λΌ λ³κ²½νμ¬ μμΈλ₯Ό ν΄κ²°νλ κ²μ΄ λͺ©μ
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
// IllegalArgumentException λ°μ μ μμΈλ₯Ό HTTP μν μ½λ 400μΌλ‘ μ λ¬
if (ex instanceof IllegalArgumentException) {
log.info("IllegalArgumentException resolver to 400");
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
//1. λΉ ModelAndView λ°ν μ λ·° λ λλ§μ νμ§ μκ³ μ μ νλ¦μΌλ‘ μλΈλ¦Ώ λ°ν
//2. ModelAndView μ View, Model λ±μ μ 보λ₯Ό μ§μ νμ¬ λ°ννλ©΄ λ·° λ λλ§
return new ModelAndView();
}
} catch (IOException e) {
log.error("resolver ex", e);
}
//3. null λ°ν μ λ€μ ExceptionResolver μ°Ύμμ μ€ν
//μ²λ¦¬ κ°λ₯ν ExceptionResolver κ° μμ κ²½μ° κΈ°μ‘΄ λ°μν μμΈλ₯Ό μλΈλ¦Ώ λ°μΌλ‘ μ λ¬
return null;
}
}
.
HandlerExceptionResolver λ°ν κ°μ λ°λ₯Έ DispatcherServlet λμ λ°©μ
λΉ ModelAndView
new ModelAndView() μ²λΌ λΉ ModelAndView λ₯Ό λ°ννλ©΄ λ·°λ₯Ό λ λλ§ νμ§μκ³ , μ μ νλ¦μΌλ‘ μλΈλ¦Ώμ΄ 리ν΄
ModelAndView μ§μ
ModelAndView μ View, Model λ±μ μ 보λ₯Ό μ§μ ν΄μ λ°ννλ©΄ λ·°λ₯Ό λ λλ§
nul
null μ λ°ννλ©΄, λ€μ ExceptionResolver λ₯Ό μ°Ύμμ μ€ν
λ§μ½ μ²λ¦¬ν μ μλ ExceptionResolver κ° μμΌλ©΄ μμΈ μ²λ¦¬κ° μλκ³ , κΈ°μ‘΄μ λ°μν μμΈλ₯Ό μλΈλ¦Ώ λ°μΌλ‘ λμ§
.
ExceptionResolver νμ©
μμΈ μν μ½λ λ³ν
μμΈλ₯Ό response.sendError(..) νΈμΆλ‘ λ³κ²½ν΄μ μλΈλ¦Ώμμ μν μ½λμ λ°λ₯Έ μ€λ₯λ₯Ό μ²λ¦¬νλλ‘ μμ
μ΄ν WAS λ μλΈλ¦Ώ μ€λ₯ νμ΄μ§λ₯Ό μ°Ύμμ λ΄λΆ νΈμΆ(default. /error)
ex. μ€μ μλ²μμλ 500 μλ¬κ° λ°μνμμ§λ§ Client μκ²λ 4xx μ½λ μ λ¬
λ·° ν
νλ¦Ώ μ²λ¦¬
ModelAndView μ κ°μ μ±μμ μμΈμ λ°λ₯Έ μλ‘μ΄ μ€λ₯ νλ©΄μ λ·° λ λλ§μ ν΅ν΄ κ³ κ°μκ² μ 곡
return new ModelAndView("error/400");
API μλ΅ μ²λ¦¬
response.getWriter().println("hello"); μ²λΌ HTTP μλ΅ λ°λμ μ§μ λ°μ΄ν°λ₯Ό λ£μ΄μ μ λ¬
μ¬κΈ°μ JSON μΌλ‘ μλ΅νλ©΄ API μλ΅ μ²λ¦¬
.
WebMvcConfigurer μ λ±λ‘
Copy @Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
}
configureHandlerExceptionResolvers
μ¬μ© μ μ€νλ§μ΄ κΈ°λ³ΈμΌλ‘ λ±λ‘νλ ExceptionResolver
κ° μ κ±°λλ―λ‘ extendHandlerExceptionResolvers
λ₯Ό μ¬μ©
ExceptionResolver μ μ©
Exception
Copy public class UserException extends RuntimeException {
public UserException() {
super();
}
public UserException(String message) {
super(message);
}
public UserException(String message, Throwable cause) {
super(message, cause);
}
public UserException(Throwable cause) {
super(cause);
}
protected UserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
...
// μμΈ μμ±
throw new UserException("μ¬μ©μ μ€λ₯");
HandlerExceptionResolver ꡬν
Copy @Slf4j
public class UserHandlerExceptionResolver implements HandlerExceptionResolver {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof UserException) {
log.info("UserException resolver to 400");
String acceptHeader = request.getHeader("accept");
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
// HTTP μμ² ν΄λμ ACCEPT κ°μ΄ application/json μΌ κ²½μ° JSON μλ΅
if ("application/json".equals(acceptHeader)) {
Map<String, Object> errorResult = new HashMap<>();
errorResult.put("ex", ex.getClass());
errorResult.put("message", ex.getMessage());
String result = objectMapper.writeValueAsString(errorResult);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().write(result);
return new ModelAndView();
} else { // κ·Έ μΈμ κ²½μ°μλ TEXT/HTML μ€λ₯ νμ΄μ§ λ
ΈμΆ
return new ModelAndView("error/500");
}
}
} catch (IOException e) {
log.error("resolver ex", e);
}
return null;
}
}
WebConfig μ HandlerExceptionResolver λ±λ‘
Copy @Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
resolvers.add(new MyHandlerExceptionResolver());
resolvers.add(new UserHandlerExceptionResolver());
}
μ 리.
컨νΈλ‘€λ¬μμ μμΈκ° λ°μν΄λ ExceptionResolver μμ μμΈλ₯Ό μ²λ¦¬
μμΈκ° λ°μν΄λ μλΈλ¦Ώ 컨ν
μ΄λκΉμ§ μμΈκ° μ λ¬λμ§ μκ³ (κ²°κ³Όμ μΌλ‘ WAS μ
μ₯μμλ μ μ μ²λ¦¬)
μ€νλ§ MVC μμ μμΈ μ²λ¦¬λ μ’
λ£
Spring ExceptionResolver
Spring Boot κΈ°λ³Έμ μΌλ‘ μ 곡νλ ExceptionResolver
HandlerExceptionResolverComposite
μ μλ μμλ‘ λ±λ‘
ExceptionHandlerExceptionResolver
API μμΈ μ²λ¦¬λ λλΆλΆ μ΄ κΈ°λ₯μΌλ‘ ν΄κ²°
ResponseStatusExceptionResolver
HTTP μν μ½λ λ³κ²½
DefaultHandlerExceptionResolver
μ€νλ§ λ΄λΆ κΈ°λ³Έ μμΈ μ²λ¦¬
ExceptionHandler π
ExceptionHandlerExceptionResolver
μ€νλ§μ API μμΈ μ²λ¦¬ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ @ExceptionHandler
λ₯Ό μ¬μ©ν νΈλ¦¬ν μμΈ μ²λ¦¬ κΈ°λ₯ μ 곡
κ° μμ€ν
λ§λ€ λ€λ₯Έ μλ΅ λͺ¨μκ³Ό μ€ν
μμΈμ λ°λ₯Έ κ°κΈ° λ€λ₯Έ λ°μ΄ν° μλ΅
컨νΈλ‘€λ¬μ λ°λΌ λ€λ₯Έ μμΈ μλ΅
ModelAndView κ° μλ Json ννλ‘ λ°ν
λ±.. μΈλ°ν μ μ΄ νμ
.
@ExceptionHandler
μμΈ μ²λ¦¬ λ°©λ²
@ExceptionHandler μ μΈ ν ν΄λΉ 컨νΈλ‘€λ¬μμ μ²λ¦¬νκ³ μΆμ μμΈ μ§μ
ν΄λΉ 컨νΈλ‘€λ¬μμ μμΈ λ°μ μ ν΄λΉ λ©μλκ° νΈμΆ
μ§μ ν μμΈ λλ νμ μμ ν΄λμ€ λͺ¨λ μ²λ¦¬
Copy /**
* λΆλͺ¨, μμ ν΄λμ€ λͺ¨λ μ§μ λμ΄ μμ κ²½μ° μμΈν κ²μ΄ μ°μ κΆ
*/
@ExceptionHandler(λΆλͺ¨μμΈ.class)
public String λΆλͺ¨μμΈμ²λ¦¬()(λΆλͺ¨μμΈ e) {}
@ExceptionHandler(μμμμΈ.class)
public String μμμμΈμ²λ¦¬()(μμμμΈ e) {}
λ€μν μμΈλ₯Ό ν λ²μ μ²λ¦¬ κ°λ₯
Copy @ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
log.info("exception e", e);
}
μμΈ μλ΅
Copy /**
* μμΈ μλ΅ μ λ©μλ νλΌλ―Έν°μ μμΈ(UserException)κ° μ§μ
*/
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {}
νλΌλ―Έν°μ μλ΅
λ€μν νλΌλ―Έν°μ μλ΅ μ§μ κ°λ₯
.
@ExceptionHandler
μ€ν νλ¦
Copy throw new IllegalArgumentException("μλͺ»λ μ
λ ₯ κ°");
...
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
return new ErrorResult("BAD", e.getMessage());
}
컨νΈλ‘€λ¬ νΈμΆ κ²°κ³Όλ‘ μμΈ(IllegalArgumentException)κ° μ»¨νΈλ‘€λ¬ λ°μΌλ‘ λμ Έμ§
DispatcherServlet μ κ±°μ³ μμΈ λ°μμΌλ‘ ExceptionResolver
μλ
κ°μ₯ μ°μ μμκ° λμ ExceptionHandlerExceptionResolver
μ€ν
ExceptionHandlerExceptionResolver
λ ν΄λΉ 컨νΈλ‘€λ¬μ IllegalArgumentException
μ μ²λ¦¬ν μ μλ @ExceptionHandler
κ° μλμ§ νμΈ
@ExceptionHandler
μ μΈ λ©μλ μ€ν
@RestController μ΄λ―λ‘ @ResponseBody μ μ© β HTTP 컨λ²ν°κ° μ¬μ©λκ³ JSON μλ΅
@ResponseStatus(HttpStatus.BAD_REQUEST) λ₯Ό μ§μ νμΌλ―λ‘ HTTP μν μ½λ 400 μλ΅
μλΈλ¦Ώ 컨ν
μ΄λκΉμ§ λ΄λ €κ°μ§ μκ³ μ μ νλ¦μΌλ‘ λ°ν
.
μν©μ λ°λ₯Έ @ExceptionHandler
νμ©
Copy /**
* μμΈ μ²λ¦¬μ© ν΄λμ€λ₯Ό λ§λ€μ΄μ μ¬μ©νλ κ²½μ°
* νμ¬ Controller μμ IllegalArgumentException λ°μ μ νΈμΆ
*
* @ResponseStatus λ μ λ
Έν
μ΄μ
μ΄λ―λ‘ HTTP μλ΅ μ½λλ₯Ό λμ μΌλ‘ λ³κ²½ λΆκ°
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
return new ErrorResult("BAD", e.getMessage());
}
/**
* νμ¬ Controller μμ UserException λ°μ μ νΈμΆ
*
* ResponseEntity λ₯Ό μ¬μ©ν΄μ HTTP λ©μμ§ λ°λμ μ§μ μλ΅(HTTP 컨λ²ν° μ¬μ©)
* HTTP μλ΅ μ½λλ₯Ό νλ‘κ·Έλλ°ν΄μ λμ μΌλ‘ λ³κ²½ κ°λ₯
*/
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
/**
* νμ¬ Controller μμ RuntimeException(Exception μ μμ ν΄λμ€) λ°μ μ νΈμΆ
*
* μ²λ¦¬λμ§ λͺ»ν λ¨μ μμΈλ₯Ό μ²λ¦¬
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
return new ErrorResult("EX", "λ΄λΆ μ€λ₯");
}
/**
* ModelAndView λ₯Ό μ¬μ©ν΄μ μ€λ₯ νλ©΄(HTML) μλ΅
*/
@ExceptionHandler(ViewException.class)
public ModelAndView ex(ViewException e) {
return new ModelAndView("error");
}
@ControllerAdvice π
μ¬λ¬ 컨νΈλ‘€λ¬μμ λ°μνλ μ€λ₯λ₯Ό λͺ¨μμ μ²λ¦¬
@ExceptionHandler
λ₯Ό μ¬μ©ν΄μ μμΈλ₯Ό κΉλνκ² μ²λ¦¬κ° κ°λ₯νμ§λ§, μ μ μ½λμ μμΈ μ²λ¦¬ μ½λκ° νλμ 컨νΈλ‘€λ¬μ μμ¬ μλ λ¨μ μ΄ μ‘΄μ¬
@ControllerAdvice
λλ @RestControllerAdvice
λ₯Ό μ¬μ©ν΄μ λΆλ¦¬ν΄ 보μ.
@ControllerAdvice
Copy @RestControllerAdvice
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
return new ErrorResult("BAD", e.getMessage());
}
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
return new ErrorResult("EX", "λ΄λΆ μ€λ₯");
}
}
λμμΌλ‘ μ§μ ν μ¬λ¬ 컨νΈλ‘€λ¬μ @ExceptionHandler
, @InitBinder
κΈ°λ₯μ λΆμ¬
λμ 컨νΈλ‘€λ¬ μ§μ β λ³΄ν΅ ν¨ν€μ§λͺ
μ λ μ§μ
Copy // Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
@ControllerAdvice μ λμμ μ§μ νμ§ μμΌλ©΄ λͺ¨λ 컨νΈλ‘€λ¬μ μ μ©(κΈλ‘λ² μ μ©)
@RestControllerAdvice
λ @ControllerAdvice μ λμΌνκ³ , @ResponseBody κ° μΆκ°
@Controller, @RestController μ°¨μ΄μ λμΌ
@ExceptionHandler μ @ControllerAdvice λ₯Ό μ‘°ν©νλ©΄ μμΈλ₯Ό κΉλνκ² ν΄κ²° κ°λ₯
ResponseStatusExceptionResolver
μμΈμ λ°λΌ HTTP μν μ½λ μ§μ
@ResponseStatus
κ° λ¬λ €μλ μμΈ
ResponseStatusException
μμΈ
.
@ResponseStatus μμΈ
Copy @ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "μλͺ»λ μμ² μ€λ₯")
public class BadRequestException extends RuntimeException { }
...
throw new BadRequestException();
ν΄λΉ μμΈκ° 컨νΈλ‘€λ¬ λ°μΌλ‘ λμ΄κ°λ©΄ ResponseStatusExceptionResolver μμΈκ° ν΄λΉ μ λ
Έν
μ΄μ
μ νμΈν΄μ HTTP μν μ½λλ₯Ό λ³κ²½
(HttpStatus.BAD_REQUEST(400))νκ³ , λ©μμ§ ν¬ν¨
ResponseStatusExceptionResolver μμ response.sendError(statusCode, resolvedReason) νΈμΆ
sendError(400) νΈμΆλ‘ WAS μμ λ€μ μ€λ₯ νμ΄μ§
(/error) λ΄λΆ μμ²
reason μ MessageSource μμ μ°Ύλ λ©μΈμ§ κΈ°λ₯
μ 곡 β reason = "error.bad"
`
ResponseStatusException μμΈ
Copy throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.bad", new IllegalArgumentException());
μ§μ λ³κ²½ν μ μλ μμΈμ ResponseStatusException
μ μ©
@ResponseStatus λ μ λ
Έν
μ΄μ
μ μ¬μ©νλ―λ‘ μ§μ (쑰건μ λ°λΌ λμ μΌλ‘)λ³κ²½ν μ μλ μμΈμλ μ μ© λΆκ°
DefaultHandlerExceptionResolver
μ€νλ§ λ΄λΆμμ λ°μνλ μ€νλ§ μμΈ μ²λ¦¬
μ€νλ§ λ΄λΆ μ€λ₯λ₯Ό μ΄λ»κ² μ²λ¦¬ν μ§μ λν λ§μ λ΄μ©μ΄ μ μ
TypeMismatchException
μΌλ‘ λ°μνλ 500 μ€λ₯λ₯Ό DefaultHandlerExceptionResolver
κ° 400 μ€λ₯λ‘ λ³κ²½
DefaultHandlerExceptionResolver.handleTypeMismatch
Copy protected ModelAndView handleTypeMismatch(TypeMismatchException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
// response.sendError() λ₯Ό ν΅ν΄ λ¬Έμ ν΄κ²°.
// sendError(400) λ₯Ό νΈμΆνμΌλ―λ‘ WAS μμ λ€μ μ€λ₯ νμ΄μ§(/error) λ΄λΆ μμ²
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return new ModelAndView();
}