Project Winter
계획했던 기능 중 MVC 로직에 직접적인 영향을 주는 기능으로 마지막이었던 기능인 HandlerExceptionResolver
기능을 마무리했다.
스프링의 모든 ExceptionResolver
를 구현하진 못했지만 기본적인 HandlerExceptionResolver
인터페이스를 통해 예외를 핸들링하도록 구현했다.
HandlerExceptionResolver
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex);
}
개발자가 예외를 핸들링 로직을 정의할 인터페이스이다.
요청에 알맞은 핸들링을 하기위해 Http 요청/응답, 핸들러, 예외 를 전달해 상황에 맞는 핸들링을 할 수 있도록 정의했다.
WebMvcConfigurer
public interface WebMvcConfigurer {
default void addInterceptors(InterceptorRegistry registry) {}
default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
}
이용자가 정의한 HandlerExceptionResolver
를 추가할 수 있도록 WebMvcConfigurer
에 configureHandlerExceptionResolvers
메서드를 추가했다.
DispatcherServlet
private void doDispatch(HttpServletRequest req, HttpServletResponse res) {
HandlerExecutionChain mappedHandler = null;
Exception dispatchException = null;
try {
ModelAndView mv = null;
try {
mappedHandler = getHandler(req);
if (mappedHandler == null) {
noHandlerFound(req, res);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(req, res)) {
return ;
}
mv = ha.handle(req, res, mappedHandler.getHandler());
mappedHandler.applyPostHandle(req, res, mv);
} catch (Exception e) {
dispatchException = e;
}
processDispatchResult(req, res, mappedHandler, mv, dispatchException);
} catch (Exception e) {
triggerAfterCompletion(req, res, mappedHandler, e);
}
}
private void processDispatchResult(HttpServletRequest req, HttpServletResponse res, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception ex) throws Excep
if (ex != null) {
if (ex instanceof HandlerNotFoundException) {
mv = ((HandlerNotFoundException) ex).getModelAndView();
}
else mv = processHandlerException(req, res, mappedHandler, ex);
}
render(mv, req, res);
triggerAfterCompletion(req, res, mappedHandler, ex);
}
private ModelAndView processHandlerException(HttpServletRequest req, HttpServletResponse res, Object handler, Exception ex) throws Exception {
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (final HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(req, res, handler, ex);
if (exMv != null) return exMv;
}
}
throw ex;
}
private void triggerAfterCompletion(HttpServletRequest req, HttpServletResponse res, HandlerExecutionChain mappedHandler, Exception ex) {
if (mappedHandler != null) mappedHandler.triggerAfterCompletion(req, res, ex);
}
doDispatch()
메서드 로직을 손봤다.
일단 render()
메서드 호출 전 인터셉터의 postHandle()
메서드가 호출되어야 하는데 순서를 잘못 설정해 변경했다.
핸들러를 찾지 못했을때 기존에는 예외만 터트리던걸 응답 상태 코드 변경까지 하는 메서드로 분리시켰다.
handler
로직까지 마친 후 호출될 processDispatchResult()
메서드를 정의해 예외가 발생했을 경우 HandlerExceptionResolver
를 찾아 예외를 핸들링한 ModelAndView
를 render
해주도록 변경했다.
테스트
@WinterServerTest
public class HandlerExceptionResolverTest {
private static final Logger log = LoggerFactory.getLogger(HandlerExceptionResolverTest.class);
public static class HandlerExceptionResolverTestException extends RuntimeException {
}
@Controller
@RequestMapping("/test")
public static class HandlerExceptionResolverController {
@RequestMapping("/exception/resolver")
public String exceptionResolverHandler() {
log.debug("exceptionResolverHandler called.");
throw new HandlerExceptionResolverTestException();
}
}
public static class TestHandlerExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(final HttpServletRequest req, final HttpServletResponse res, final Object handler, final Exception ex) {
log.debug("resolveException called : {}", ex.getClass());
try {
if (ex instanceof HandlerExceptionResolverTestException) {
log.debug("HandlerExceptionResolverTestException resolve.");
res.setStatus(HttpStatus.NOT_FOUND.getCode());
return new ModelAndView("404");
}
} catch (Exception e) {
log.error("fail", e);
}
return null;
}
}
@Configuration
public static class HandlerExceptionResolverTestConfig implements WebMvcConfigurer {
@Override
public void configureHandlerExceptionResolvers(final List<HandlerExceptionResolver> resolvers) {
resolvers.add(new TestHandlerExceptionResolver());
}
}
@Test
public void test_controller_get_method_success() throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/test/exception/resolver"))
.build();
HttpResponse<String> result = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
assertEquals(result.statusCode(), 404);
assertTrue(result.body().contains("404 - Not Found"));
}
}
HandlerExceptionResolver
에 대한 테스트를 작성해 예외가 터졌을 때 정상적으로 핸들링 되는지 확인했다.
다음으로
이렇게 예외 핸들링 로직까지 완성했다.
마지막으로 DI 기능까지 구현하여 이 프로젝트를 마무리하려 한다.