업무 중 spring interceptor의 prehandle 부분에서 특수문자 유효성 체크를 하는데 이 부분에서 걸리면 throw new exception(메시지)으로 예외를 내지만 이 예외에 대한 메시지가 아닌 다른 메시지(시스템 공통 메시지)가 화면 얼럿창에 표시되었다.
interceptor의 prehandle에서 예외가 나면 컨트롤러로 가지 않기 때문에, 앞단에서 발생한 예외 메시지를 화면 얼럿창에 표시하기 위한 방법을 찾다가
try catch로 우선 exception을 잡고 prehandle의 인자인 HttpServletResponse response에 메시지를 담아 내보내는 방법을 썼었다.
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
try{
//유효성 체크 로직 함수 진행
} catch (Exception e) {
response.setContentType("text/html; charset=utf-8");
PrintWriter out = response.getWriter();
String element = e.getMessage();
out.print(element);
out.flush();
out.close();
}
}
이 방법으로도 화면의 얼럿창에 의도한 메시지가 잘 출력되기 했지만, Spring이 제공하는 예외 처리 기능들을 활용하면 좀 더 효과적으로 예외를 핸들링할 수 있다는 점을 알게되었다. (찾아보니 기존 코드들도 ExceptionHandler를 통해 처리되고 있던 것이었음..)
이 김에 Spring Interceptor와 예외 처리 기능들 중 @ExceptionHandler와 @ControllerAdvice에 대해서 자세히 알아보도록 하자.
Spring이 제공하는 기술로써, 디스패처 서블릿(Dispatcher Servlet)이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공한다.
개발자는 특정 Controller의 핸들러가 실행되기 전이나 후에 추가적인 작업을 원할 때 Interceptor를 사용한다. (추가적인 작업으로는 로그인체크, 권한 체크 등이 있다.)
인터셉터 적용의 유무 기준이 되는 url을 servlet-context.xml에 설정해주게 되면 스프링에서 일괄적으로 해당 url 경로의 핸들러에 인터셉터를 적용해주기 때문에 누락에 대한 위험이 상당히 줄게되고 코드의 중복을 제거해준다.
스프링에서 제공하는 org.springframework.web.servlet.HandlerInterceptor
인터페이스를 구현하거나, org.springframework.web.servlet.handler.HandlerInterceptorAdapter
추상클래스를 오버라이딩 함으로써 자신만의 인터셉터를 만들 수 있다. HandlerInterceptorAdapter
추상클래스 경우 HandlerInterceptor
인터페이스를 상속받아 구현되었다.
스프링이 제공해주는 HandlerInterceptor
인터페이스와 HandlerInterceptorAdapter
추상클래스에 정의되어 있는
메서드는 preHandle(), postHandle(), afterCompletion() 3가지이다.
1) HandlerInterceptor 혹은 HandlerInterceptorAdapter 를 상속받아서 자신만의 Interceptor 클래스를 생성한다.
2) servlet-context.xml에 우리가 작성한 Interceptor 클래스를 빈(bean)으로 등록해주고, Interceptor를 적용할 url을 작성
public class CustomInterceptor extends HandlerInterceptorAdapter{
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// TODO Auto-generated method stub
return false;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// TODO Auto-generated method stub
}
}
@ExceptionHandler 어노테이션을 사용하면 value로 원하는 예외를 지정하고 이를 핸들링할 수 있다.
@ExceptionHandler는 value 속성으로 지정한 예외 뿐 아니라 예외의 자식 클래스도 전부 캐치해 지정된 응답을 반환하게 된다. (value 속성을 지정하지 않는다면 메서드의 파라미터에 있는 예외가 자동으로 지정된다.)
(만약 같은 예외에 대해 여러 컨트롤러에서 같은 처리를 하고 싶다면 컨트롤러마다 같은 메서드를 작성해 주어야만 한다. - 코드 중복 발생)
@RestController
public class SimpleController {
@ExceptionHandler(value = IllegalArgumentException.class)
public ResponseEntity<String> invokeError(IllegalArgumentException e) {
...
return new ResponseEntity<>("부모 클래스", HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(value = IllegalArgumentExtendsException.class)
public ResponseEntity<String> invokeError(IllegalArgumentException e) {
...
return new ResponseEntity<>("자식 클래스", HttpStatus.BAD_REQUEST);
}
}
@ControllerAdvice는 @Component 어노테이션의 특수한 케이스로, 스프링 부트 애플리케이션에서 전역적으로 예외를 핸들링할 수 있게 해주는 어노테이션이다.
이를 통해 코드의 중복을 해결할 수 있다.
또한, 하나의 클래스 내에서 정상 동작 시 호출되는 코드와 예외를 처리하는 코드를 분리할 수 있다.
@ControllerAdvice
public class SimpleControllerAdvice {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> IllegalArgumentException() {
return ResponseEntity.badRequest().build();
}
}
위 처럼 코드를 작성하게 되면 애플리케이션 내의 모든 컨트롤러에서 발생하는 IllegalArgumentException을 해당 메서드가 처리하게 된다.
주의)
여러 ControllerAdvice가 있을 때 @Order어노테이션으로 순서를 지정하지 않는다면 Spring은 ControllerAdvice를 임의의 순서로 호출한다. 즉, 사용자가 예상하지 못한 예외 처리가 발생할 수 있다.