티스토리 뷰
작년 7월에 @ControllerAdvice로 같은 예외일 때 뷰나 데이터를 내려주는 분기처리를 할 수 없을까 고민했었다.
@ControllerAdvice로 같은 예외일 때 뷰나 데이터를 내려주는 분기처리를 할 수 없을까에 대한 고민
내내 저 고민을 하고 있었지만 이것저것 할 게 많아서 손을 못 대고 있던 찰나, 마침 내게... 예외에 따른 뷰와 객체 반환 분기처리를 하는 업무가 주어졌다.
다른 서버와 통신하는 API와 뷰를 다루는 비즈니스 로직이 있는데, API에 대해서는 따로 예외 처리가 되어 있지 않아 API 내부에서 예를 들어 RuntimeException을 던지면 API 응답 결과가 화면으로 반환되는 문제였다. 그냥 내부로직에서 Exception이 발생하면 에러페이지를 보이도록 해놓은 것 밖에 없는 것이 원인이었다.(...)
문제 해결을 더 어렵게 만드는 원인 하나가 더 있는데, 한 컨트롤러에서 뷰와 객체를 반환해주는 메서드들을 같이 사용하고 있다는 것이었다. 정말 예외 처리를 제대로 하려면 뷰를 다루는 컨트롤러와 객체를 반환하는 컨트롤러로 나누고 각자에 대해 예외 처리를 하는 게 맞지 않나 생각했다. 그런데 이미 소스는 한 덩이로 뭉쳐져 몇 년을 굴러왔으니 이제와서 나 혼자 손을 대기엔 너무 큰 부분이었다. (사실 꼭 나누지 않고, HTTP 헤더의 Content-Type이 text/html인지 Application/json인지에 따라 할 수도 있긴 하겠지만.)
이번에 요구받은 부분은 API 한정이므로 우선 요구받은 문제에 대해 해결하고, 한 컨트롤러에서 뷰와 객체를 반환해주는 문제는 새로운 프로젝트를 시작할 때 팀원들에게 코드를 분리해서 예외 처리를 용이하게 해보자고 제안해야겠다고 생각했다.
해결
결론부터 말하자면 뷰를 반환할 때는 HandlerExceptionResolver의 구현체를 사용하고, 객체를 반환할 때는 ExceptionHandler를 사용했다. (기존에 Exception을 전부 잡아 에러페이지로 던져주던 부분이 ExceptionHandler로 구현되어 있었는데, 이 부분을 ExceptionResolver로 변경했다.)
컨트롤러에서 예외가 던져지면 HandlerExceptionResolver의 구현체가 해결하고 ModelAndView를 반환한다. 참고로 ExceptionResolver가 예외처리를 하게 되면 재요청은 이루어지지 않아 인터셉터의 postHandle 은 호출되지 않는다고 한다.
https://velog.io/@kiwonkim/MVC2-9.-API-예외-처리
/**
* Interface to be implemented by objects that can resolve exceptions thrown during
* handler mapping or execution, in the typical case to error views. Implementors are
* typically registered as beans in the application context.
*
* <p>Error views are analogous to JSP error pages but can be used with any kind of
* exception including any checked exception, with potentially fine-grained mappings for
* specific handlers.
*
* @author Juergen Hoeller
* @since 22.11.2003
*/
public interface HandlerExceptionResolver {
/**
* Try to resolve the given exception that got thrown during handler execution,
* returning a {@link ModelAndView} that represents a specific error page if appropriate.
* <p>The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
* to indicate that the exception has been resolved successfully but that no view
* should be rendered, for instance by setting a status code.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding {@code ModelAndView} to forward to,
* or {@code null} for default processing in the resolution chain
*/
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
/**
* Annotation for handling exceptions in specific handler classes and/or
* handler methods.
*
* <p>Handler methods which are annotated with this annotation are allowed to
* have very flexible signatures. They may have parameters of the following
* types, in arbitrary order:
* <ul>
* <li>An exception argument: declared as a general Exception or as a more
* specific exception. This also serves as a mapping hint if the annotation
* itself does not narrow the exception types through its {@link #value()}.
* You may refer to a top-level exception being propagated or to a nested
* cause within a wrapper exception. As of 5.3, any cause level is being
* exposed, whereas previously only an immediate cause was considered.
* <li>Request and/or response objects (typically from the Servlet API).
* You may choose any specific request/response type, e.g.
* {@link javax.servlet.ServletRequest} / {@link javax.servlet.http.HttpServletRequest}.
* <li>Session object: typically {@link javax.servlet.http.HttpSession}.
* An argument of this type will enforce the presence of a corresponding session.
* As a consequence, such an argument will never be {@code null}.
* <i>Note that session access may not be thread-safe, in particular in a
* Servlet environment: Consider switching the
* {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setSynchronizeOnSession
* "synchronizeOnSession"} flag to "true" if multiple requests are allowed to
* access a session concurrently.</i>
* <li>{@link org.springframework.web.context.request.WebRequest} or
* {@link org.springframework.web.context.request.NativeWebRequest}.
* Allows for generic request parameter access as well as request/session
* attribute access, without ties to the native Servlet API.
* <li>{@link java.util.Locale} for the current request locale
* (determined by the most specific locale resolver available,
* i.e. the configured {@link org.springframework.web.servlet.LocaleResolver}
* in a Servlet environment).
* <li>{@link java.io.InputStream} / {@link java.io.Reader} for access
* to the request's content. This will be the raw InputStream/Reader as
* exposed by the Servlet API.
* <li>{@link java.io.OutputStream} / {@link java.io.Writer} for generating
* the response's content. This will be the raw OutputStream/Writer as
* exposed by the Servlet API.
* <li>{@link org.springframework.ui.Model} as an alternative to returning
* a model map from the handler method. Note that the provided model is not
* pre-populated with regular model attributes and therefore always empty,
* as a convenience for preparing the model for an exception-specific view.
* </ul>
*
* <p>The following return types are supported for handler methods:
* <ul>
* <li>A {@code ModelAndView} object (from Servlet MVC).
* <li>A {@link org.springframework.ui.Model} object, with the view name implicitly
* determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
* <li>A {@link java.util.Map} object for exposing a model,
* with the view name implicitly determined through a
* {@link org.springframework.web.servlet.RequestToViewNameTranslator}.
* <li>A {@link org.springframework.web.servlet.View} object.
* <li>A {@link String} value which is interpreted as view name.
* <li>{@link ResponseBody @ResponseBody} annotated methods (Servlet-only)
* to set the response content. The return value will be converted to the
* response stream using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}.
* <li>An {@link org.springframework.http.HttpEntity HttpEntity<?>} or
* {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object
* (Servlet-only) to set response headers and content. The ResponseEntity body
* will be converted and written to the response stream using
* {@linkplain org.springframework.http.converter.HttpMessageConverter message converters}.
* <li>{@code void} if the method handles the response itself (by
* writing the response content directly, declaring an argument of type
* {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse}
* for that purpose) or if the view name is supposed to be implicitly determined
* through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}
* (not declaring a response argument in the handler method signature).
* </ul>
*
* <p>You may combine the {@code ExceptionHandler} annotation with
* {@link ResponseStatus @ResponseStatus} for a specific HTTP error status.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
* @see ControllerAdvice
* @see org.springframework.web.context.request.WebRequest
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
'업무 경험 및 성과' 카테고리의 다른 글
산출물 취합에서 공동작업으로, 시간 절약 (0) | 2023.03.26 |
---|---|
메모리를 과도하게 사용하는 이유를 찾아보자 (0) | 2023.01.30 |
POI로 엑셀 데이터 유효성 검사(드롭다운) 기능 추가시 주의사항 (0) | 2022.12.13 |
AWS CodeDeploy에서 BeforeInstall단계 UnknownError 해결 (0) | 2022.11.13 |
AWS CodeDeploy에서 BeforeBlockTraffic단계 UnknownError 해결 (0) | 2022.11.13 |