반응형
Spring Interceptor 구현
목적
interceptor 활용 및 filter 차이점
filter 정리글 : https://devhj.tistory.com/59
예제소스
https://github.com/devHjlee/devHjBlog/tree/main/filter-interceptor-aop
Interceptor
Interceptor(인터셉터)는 스프링 프레임워크에서 제공하는 기능으로, 웹 애플리케이션의 요청 처리 과정에서 컨트롤러 호출 전후에 추가적인 작업을 수행할 수 있도록 해줍니다. Interceptor는 주로 요청의 전/후 처리, 인증 및 권한 검사, 로깅, 캐싱 등의 공통적인 기능을 구현하기 위해 사용됩니다.
Interceptor의 주요 특징은 다음과 같습니다:
- HandlerInterceptor 인터페이스 구현:
- Interceptor는
org.springframework.web.servlet.HandlerInterceptor
인터페이스를 구현하여 작성됩니다. - HandlerInterceptor 인터페이스는 preHandle(), postHandle(), afterCompletion() 세 가지 메서드를 제공합니다.
- preHandle() 메서드는 컨트롤러 실행 전에 호출되며, postHandle() 메서드는 컨트롤러 실행 후에 호출되고, afterCompletion() 메서드는 뷰가 렌더링 된 후에 호출됩니다.
- Interceptor는
- 스프링 MVC와 통합:
- Interceptor는 스프링 MVC 프레임워크와 긴밀하게 통합됩니다.
- 스프링 MVC의 인터셉터 체인을 구성하여 여러 개의 인터셉터를 적용할 수 있습니다.
- 인터셉터 체인은 설정된 순서대로 실행되며, 각각의 인터셉터는 다음 인터셉터로 요청을 전달하거나 컨트롤러 호출을 중단할 수 있습니다.
- 전역 및 지역 설정:
- Interceptor는 전역적으로 설정할 수도 있고, 특정 URL 패턴에 대해 지역적으로 설정할 수도 있습니다.
- 전역적으로 설정된 인터셉터는 모든 요청에 대해 동작하며, 지역적으로 설정된 인터셉터는 특정 URL 패턴에 대해서만 동작합니다.
Interceptor를 사용하면 다음과 같은 작업을 수행할 수 있습니다:
- 요청 전/후에 공통적으로 처리해야 할 작업, 예를 들어 인증 및 권한 검사, 세션 관리 등을 수행할 수 있습니다.
- 컨트롤러 실행 전/후에 로깅, 성능 측정, 트랜잭션 관리 등의 부가적인 작업을 수행할 수 있습니다.
- 컨트롤러의 결과를 가공하거나 추가 데이터를 주입할 수 있습니다.
Interceptor 주요 메서드
preHandle()
:- 컨트롤러 실행 전에 호출되는 메서드입니다.
- 주로 요청 전에 수행해야 하는 사전 처리 작업을 구현합니다.
- 예를 들어, 인증/인가 체크, 로깅, 요청 파라미터 검증 등의 작업을 수행할 수 있습니다.
- 만약
preHandle()
에서false
를 반환하면, 이후의 인터셉터와 컨트롤러가 실행되지 않고 요청 처리가 중단됩니다.
postHandle()
:- 컨트롤러 실행 후 뷰가 렌더링되기 전에 호출되는 메서드입니다.
- 컨트롤러가 실행된 이후에 추가적인 처리 작업을 수행할 수 있습니다.
- 주로 컨트롤러의 실행 결과를 가공하거나, 모델에 추가적인 데이터를 넣는 등의 작업을 수행할 수 있습니다.
ModelAndView
객체를 조작하여 뷰에 전달할 데이터를 수정할 수도 있습니다.
afterCompletion()
:- 뷰가 렌더링된 후에 호출되는 메서드입니다.
- 요청의 완료 후에 처리해야 하는 작업을 구현할 수 있습니다.
- 주로 리소스 정리, 로깅 등의 작업을 수행합니다.
postHandle()
에서 예외가 발생한 경우에도afterCompletion()
이 호출되며, 예외 처리에 대한 로직을 추가할 수 있습니다.
필터 관련글 : https://devhj.tistory.com/59
Filter 와 차이점
- 실행 시점:
- Filter: Filter는 서블릿 컨테이너에서 Servlet의 수명 주기 내에서 동작합니다. Filter는 요청이 서블릿에 도달하기 전(pre-processing)과 응답이 클라이언트로 전송되기 전(post-processing)에 실행됩니다.
- Interceptor: Interceptor는 Spring MVC 프레임워크에서 동작하며, 컨트롤러에 진입하기 전(preHandle), 컨트롤러가 실행된 후(postHandle), 응답이 클라이언트로 전송된 후(afterCompletion)에 실행됩니다.
- 구성 방식:
- Filter: Filter는 웹 애플리케이션의
web.xml
파일 또는@WebFilter
어노테이션을 사용하여 구성됩니다. Filter는 서블릿 컨테이너에 의해 관리되며, URL 패턴 또는 서블릿 이름을 기반으로 등록되고 순서를 지정할 수 있습니다. - Interceptor: Interceptor는 Spring MVC의 구성 파일(XML 또는 Java Config)에서
WebMvcConfigurer
인터페이스를 구현하여 설정됩니다. Interceptor는 Spring 프레임워크에 의해 관리되며, 경로 패턴과 순서를 지정하여 등록할 수 있습니다.
- Filter: Filter는 웹 애플리케이션의
- 영향 범위:
- Filter: Filter는 서블릿 컨테이너에서 동작하므로, 웹 애플리케이션 전체에 대해 적용됩니다. 따라서 Filter는 모든 요청과 응답에 대해 적용됩니다.
- Interceptor: Interceptor는 Spring MVC 프레임워크에서 동작하므로, Spring MVC 컨텍스트에만 영향을 미칩니다. 즉, 요청이 Spring MVC 컨트롤러로 라우팅될 때만 Interceptor가 적용됩니다.
- Spring 컨텍스트의 사용:
- Filter: Filter는 서블릿 컨테이너의 라이프사이클에 의해 관리되므로, Filter 내에서 Spring의 DI(Dependency Injection)와 같은 Spring 컨텍스트의 기능을 직접적으로 사용할 수 없습니다. (DelegatingFilterProxy, FilterRegistrationBean 을 통해 스프링빈을 사용은 가능)
- Filter에서 스프링 빈에 접근하려면 별도의 메커니즘을 통해 스프링 컨텍스트와의 연결 및 의존성 주입을 구현해야 합니다. 이는 복잡성을 증가시킬 수 있으며, 필요한 경우에만 사용하는 것이 좋습니다.
- Interceptor: Interceptor는 Spring MVC 프레임워크에서 동작하므로, Spring의 DI 및 다른 Spring 기능을 활용할 수 있습니다. Interceptor는 Spring 컨텍스트에서 관리되기 때문에 스프링 빈을 주입하거나 다른 컴포넌트와 상호작용할 수 있습니다.
개발환경
- IDE : IntelliJ
- Jdk : OpenJdk 11
- gradle
- spring boot : 2.7.11
- 프로젝트 구조 (초록색이 Interceptor 에 관련된 소스)
WebConfig
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
CustomInterceptor customInterceptor = new CustomInterceptor();
registry.addInterceptor(customInterceptor)
.addPathPatterns("/**");
}
}
CustomInterceptor
@Slf4j
@Component
public class CustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws IOException {
log.info("preHandle Start");
// 컨트롤러 실행 전에 수행될 로직을 구현합니다.
// 예시로 filterData 일때 인증예외를 발생시킵니다.
if(request.getRequestURI().equals("/filterData")) {
throw new AuthFailException("TOKEN NULL");
}
// true를 반환하면 컨트롤러가 실행되고, false를 반환하면 컨트롤러를 실행하지 않습니다.
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 컨트롤러 실행 후에 수행될 로직을 구현합니다.
log.info("postHandle Start");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
// 뷰가 렌더링된 후에 수행될 로직을 구현합니다.
log.info("afterCompletion Start");
}
}
그외 테스트를 위해 구현 한 소스들 (ControllerAdvice 관련글: https://devhj.tistory.com/57)
HelloController
@Slf4j
@RestController
public class HelloController {
@GetMapping("/filterData")
public ResponseEntity<Map<String, Object>> filterData(HttpServletRequest request, @RequestParam String name, @RequestParam int age) {
Map<String, Object> resMap = new HashMap<>();
resMap.put("name", name);
resMap.put("age", age);
log.info(request.getServerName());
return new ResponseEntity<Map<String,Object>>(resMap, HttpStatus.OK);
}
@GetMapping("/noFilterData")
public ResponseEntity<Map<String, Object>> noFilterData(@RequestParam String name, @RequestParam int age) {
Map<String, Object> resMap = new HashMap<>();
resMap.put("name", name);
resMap.put("age", age);
return new ResponseEntity<Map<String,Object>>(resMap, HttpStatus.OK);
}
}
GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthFailException.class)
public ResponseEntity<ErrorResponse> authFailException(AuthFailException ex) {
log.error(ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.ERROR_002);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
log.error(ex.getMessage());
ErrorResponse errorResponse = new ErrorResponse(ErrorCode.ERROR_001);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}
AuthFailException
public class AuthFailException extends RuntimeException{
public AuthFailException(String message) {
super(message);
}
}
ErrorCode
public enum ErrorCode {
ERROR_001("An unexpected error occurred."),
ERROR_002("AUTH FAIL."),
// 추가로 필요한 에러코드를 여기에 선언합니다.
;
private final String message;
ErrorCode(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
ErrorResponse
public class ErrorResponse {
private final ErrorCode errorCode;
private final String errorMessage;
public ErrorResponse(ErrorCode errorCode) {
this.errorCode = errorCode;
this.errorMessage = errorCode.getMessage();
}
public ErrorCode getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
}
실행 결과
- /filterData?name=a&age=1 호출하면 CumstomInterceptor 에서 Auth Exception 을 발생 시키고 RestControllerAdvice 를 통해 Exception 에 관련된 {"errorCode": "ERROR_002","errorMessage": "AUTH FAIL."} 응답값 을 전달
- filter 포스팅관련해서 만들어 두었던 CustomResponseFilter 에 의해 기존 응답값에 Use 를 추가하게 되어 최종적으로 {"errorCode": "ERROR_002","errorMessage": "AUTH FAIL.","use": "Y"} 리턴
/noFilterData?name=a&age=1 호출하면 정상적인 응답값 리턴
참고
- Chat GPT
반응형
댓글