[Spring] Interceptor와 예외 처리 기능들 (@ExceptionHandler, @ControllerAdvice)

szlee·2024년 2월 14일
0

Spring

목록 보기
13/15

업무 중 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();
                  }
}

(참고) PrintWriter out = response.getWriter(); 의 동작 원리

이 방법으로도 화면의 얼럿창에 의도한 메시지가 잘 출력되기 했지만, Spring이 제공하는 예외 처리 기능들을 활용하면 좀 더 효과적으로 예외를 핸들링할 수 있다는 점을 알게되었다. (찾아보니 기존 코드들도 ExceptionHandler를 통해 처리되고 있던 것이었음..)

이 김에 Spring Interceptor와 예외 처리 기능들 중 @ExceptionHandler와 @ControllerAdvice에 대해서 자세히 알아보도록 하자.






Spring Interceptor

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) preHandle()

  • 컨트롤러가 호출되기 전에 실행됨
  • 컨트롤러가 실행 이전에 처리해야 할 작업이 있는 경우 혹은 요청 정보를 가공하거나 추가하는 경우 사용
  • 실행되어야 할 '핸들러'에 대한 정보를 인자값으로 받기 때문에 '서블릿 필터'에 비해 세밀하게 로직을 구성할수 있음.
  • 리턴값이 boolean이다. 리턴이 true 일경우 preHandle() 실행 후 핸들러에 접근한다. false일 경우 작업을 중단하기 때문에 컨트롤러와 남은 인터셉터가 실행되지 않는다.

2) postHandle()

  • 핸들러가 실행은 완료 되었지만 아직 View가 생성되기 이전에 호출된다.
  • ModelAndView 타입의 정보를 인자값으로 받는다. 따라서 Controller에서 View 정보를 전달하기 위해 작업한 Model 객체의 정보를 참조하거나 조작할 수 있다.
  • preHandle() 에서 리턴값이 fasle인경우 실행되지 않음.
  • 적용중인 인터셉터가 여러개인경우, preHandle()는 역순으로 호출된다.
  • 비동기적 요청 처리 시에는 처리되지 않음.

3) afterCompletion()

  • 모든 View에서 최종 결과를 생성하는 일을 포함한 모든 작업이 완료된 후에 실행된다.
  • 요청 처리중에 사용한 리소스를 반환해주기 적당한 메서드이다.
  • preHandle() 에서 리턴값이 false인경우 실행되지 않는다.
  • 적용중인 인터셉터가 여러개인 경우 preHandle()는 역순으로 호출된다.
  • 비동기적 요청 처리시에 호출되지않음.

인터셉터 동작 위치 및 순서

  1. 사용자는 서버에 자신이 원하는 작업을 요청하기 위해 url을 통해 Request 객체를 보낸다.
  2. DispatcherServlet은 해당 Request 객체를 받아서 분석한뒤 '핸들러 매핑(HandlerMapping)' 에게 사용자의 요청을 처리할 핸들러를 찾도록 요청한다.
  3. 그 결과로 핸들러 실행체인(HandlerExecutionChain)이 동작하게 되는데, 이 핸들러 실행체인은 하나이상의 핸들러 인터셉터를 거쳐서 컨트롤러가 실행될수 있도록 구성되어 있다.
    (핸들러 인터셉터를 등록하지 않았다면, 곧바로 컨트롤러가 실행된다. 반대로 하나이상의 인터셉터가 지정되어 있다면 지정된 순서에 따라서 인터셉터를 거쳐서 컨트롤러를 실행한다)

구현 방법

1) HandlerInterceptor 혹은 HandlerInterceptorAdapter 를 상속받아서 자신만의 Interceptor 클래스를 생성한다.

  • HandlerInterceptor는 인터페이스이기 때문에 implements 키워드, HandlerInterceptorAdapter는 추상클래스이기 때문에 extends 키워드 사용한다.

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

        }

}


Spring 예외 처리 기능들

@ExceptionHandler

@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

@ControllerAdvice는 @Component 어노테이션의 특수한 케이스로, 스프링 부트 애플리케이션에서 전역적으로 예외를 핸들링할 수 있게 해주는 어노테이션이다.
이를 통해 코드의 중복을 해결할 수 있다.

또한, 하나의 클래스 내에서 정상 동작 시 호출되는 코드와 예외를 처리하는 코드를 분리할 수 있다.

@ControllerAdvice
public class SimpleControllerAdvice {

    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<String> IllegalArgumentException() {
        return ResponseEntity.badRequest().build();
    }
}

위 처럼 코드를 작성하게 되면 애플리케이션 내의 모든 컨트롤러에서 발생하는 IllegalArgumentException을 해당 메서드가 처리하게 된다.

주의)
여러 ControllerAdvice가 있을 때 @Order어노테이션으로 순서를 지정하지 않는다면 Spring은 ControllerAdvice를 임의의 순서로 호출한다. 즉, 사용자가 예상하지 못한 예외 처리가 발생할 수 있다.







refer
refer

profile
🌱

0개의 댓글