최근 프로젝트에서 SessionFilter를 GenericFilterBean나 Filter가 아닌 OncePerRequestFilter를 extends하여 구현하게 되었다.
왜 OncePerRequestFilter를 사용했는지 이 글에서 다뤄보고자 한다.
우선 위 그림을 통해 Filter의 동작 순서에 대해 알아볼 수 있다.
Filter는 DispatcherServlet 실행 전 후에 호출되는데, Filter를 사용하다보면 Filter가 중복으로 호출되는 경우가 발생한다.
Filter가 중복 호출되는 Case
Filter는 Servlet마다 호출되며, Servlet은 요청마다 Servlet을 생성해서 메모리에 저장한 뒤 같은 클라이언트의 요청이 들어오면 생성해 둔 서블릿 객체를 재활용한다.
하지만, 위 그림과 같이 하나의 서블릿에서 내부적으로 다른 서블릿이 호출되게 되면 해당 요청은 다시 Filter Chain을 통해 Filter1과 Filter2를 한번 더 호출한다.
이렇게, 요청을 다른 서블릿으로 전달하는 과정을 dispatch라고 하며, RequestDispatcher 클래스를 통해서 이루어진다.
만약, Filter1, 2가 무거운 인증 필터라면 똑같은 절차를 2번씩이나 거쳐야 하므로 많은 부분에서 손해가 발생하게 된다.
이 부분은 커스텀 필터를 만들때 Filter Interface를 implements하거나 GenericFilterBean Abstract class를 extends 하거나 동일한 문제가 발생한다.
GenericFilterBean는 기존 Filter 인터페이스에서 얻어올 수 없는 정보였던 Spring의 Config 정보를 가져올 수 있게 만들어 놓은 Abstract Class이다.
OncePerRequestFilter가 중복호출을 방지하는 방법
아래 코드는 OncePerRequestFilter의 원본 소스이다.
doFilter() 내부의 alreadyFilteredAttributeName 변수부터 확인해보면 이미 필터가 실행되었는지 확인하고, 이미 실행된 필터라면 다시 실행하지 않도록 검사를 진행한다.
public abstract class OncePerRequestFilter extends GenericFilterBean {
/**
* Suffix that gets appended to the filter name for the
* "already filtered" request attribute.
* @see #getAlreadyFilteredAttributeName
*/
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
/**
* This {@code doFilter} implementation stores a request attribute for
* "already filtered", proceeding without filtering again if the
* attribute is already there.
* @see #getAlreadyFilteredAttributeName
* @see #shouldNotFilter
* @see #doFilterInternal
*/
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!((request instanceof HttpServletRequest httpRequest) && (response instanceof HttpServletResponse httpResponse))) {
throw new ServletException("OncePerRequestFilter only supports HTTP requests");
}
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else {
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
이러한 중복 체크 로직을 통해서 커스텀 Filter가 중복 호출되는 것을 방지할 수 있으므로, Client의 1번의 요청 당 오직 1번만 실행되는 Filter를 만들 수 있게 된다.
결국, Redirect가 일어날 Case가 있을지 없을지는 모르지만 추후에 오동작을 막기 위해서는 모든 Servlet에 일관된 요청을 처리하기 위해 만들어진 OncePerRequestFilter를 사용하는 것이 좋다고 생각한다.
'🌐 Backend > 🍃 Spring' 카테고리의 다른 글
JNDI를 이용하여 Datasource를 설정한 이유 (0) | 2024.04.03 |
---|---|
Spring Boot 1.x -> 2.x Mirgration 진행하면서 (0) | 2024.03.26 |
쿠키, 세션, 토큰, Hybrid 토큰 인증 방식의 차이 (0) | 2023.09.11 |
Spring Cloud #1 (0) | 2023.05.28 |
파일 업로드 하는 방법(Form, Spring) (0) | 2022.09.12 |