본문 바로가기
Back-End/🌱 Spring Boot (java)

[Spring] 서블릿 예외 처리 시 필터 재호출 해결법

by 코딩하는 동현😎 2024. 7. 28.

이전 포스트에서 서블릿 예외 처리를 구현했습니다. (저번 포스트에서 코드 그대로 활용합니다.)

 

[Spring] 서블릿 예외 처리와 오류 페이지

오류 페이지의 view는 Thymeleaf 템플릿 엔진으로 작성했습니다!Dependencies: Spring Web, Lombok , Thymeleaf, Validation build.gradleplugins { id 'org.springframework.boot' version '2.5.1' id 'io.spring.dependency-management' version

konkukcodekat.tistory.com

 

저번 포스트에서 마지막에 동작 과정을 보면 아래와 같습니다.

1. WebServerCustomizer <- WAS(sendError 호출 기록 확인) <- 필터 <- 서블릿 <- 인터셉터 <- ServletExController(sendError 호출)

2. WAS (/error-page/500 다시 요청) -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/error-page/500) -> 타임리프 View(클라이언트에게 보여주는 페이지)

 

오류가 발생하면 오류 페이지를 출력하기 위해서 2번과 같이 WAS내부에서 호출이 다시 한번 발생하고, 그 과정에서 필터, 인터셉터등이 모두 호출됩니다.

로그인 인증 체크 같은 경우, 이미 한번 필터나 인터셉터에서 로그인 체크를 완료한 상태이기 때문에, 서버 내부에서 오류 페이지를 호출하면서 똑같은 필터나 인터셉터가 한번 더 호출되는 것은 비효율적입니다.

결국 클라이언트로 부터 발생한 정상 요청인지, 아니면 오류 페이지를 출력하기 위해서 내부 요청인지 구분해야 다시 호출되는 것을 막을 수 있습니다.

서블릿은 이런 문제를 해결하기 위해 DispatcherType 이라는 추가 정보를 제공합니다.


DispatcherType

DispatcherType은 아래와 같습니다. 고객이 처음 요청하면 dispatcherType=REQUEST 이고, 오류 페이지 호출을 위해 내부에서 호출하면 dispatcherType=ERROR입니다.

public enum DispatcherType {
 FORWARD,
 INCLUDE,
 REQUEST,
 ASYNC,
 ERROR
}

 


LogFilter에 DispatcherType 추가

코드는 이전 서플릿 필터 포스트에 코드를 가져와서 수정하겠습니다. (logfilter, webconfig)

 

[Spring] 서블릿 필터 개념과 로그인 필터 구현

저번 포스트에 이어 로그인 기능을 구현했습니다. 그러나 로그인 기능을 구현하고 로그인된 사용자에 맞춰서 기능을 구현하는 것 까진 잘되는데, 특정 페이지에 접근하는 것을 막는것은 불가능

konkukcodekat.tistory.com

 

filter/LogFilter

package hello.exception.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;

@Slf4j
public class LogFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("log filter init");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        String uuid = UUID.randomUUID().toString();

        try {
            log.info("REQUEST  [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
            chain.doFilter(request, response);
        } catch (Exception e) {
            log.info("EXCEPTION {}", e.getMessage());
            throw e;
        } finally {
            log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
        }

    }

    @Override
    public void destroy() {
        log.info("log filter destroy");
    }
}

 

아래와 같이 DispatcherType도 같이 로그에 나오도록  수정했습니다.

log.info("REQUEST [{}][{}]", uuid, requestURI);

-->

log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);


실행 결과

로그에 DispatcherType이 같이 필터를 통해 출력되는 것이 보입니다.

저희 목표는 ERROR 요청에는 필터가 실행되지 않도록 하는것입니다.


WebConfig에 DispatcherType 추가

Logfilter를 적용시켜주는 파일 WebConfig에서 filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);를 추가해서 오류 호출이 아닌 사용자 호출에만 로그 필터를 적용하도록 할 수 있습니다.

 

WebConfig

package hello.exception;

import hello.exception.filter.LogFilter;
import hello.exception.interceptor.LogInterceptor;
import hello.exception.resolver.MyHandlerExceptionResolver;
import hello.exception.resolver.UserHandlerExceptionResolver;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
	// 인터셉터 부분
    // @Override
    // public void addInterceptors(InterceptorRegistry registry) {
    //     registry.addInterceptor(new LogInterceptor())
    //             .order(1)
    //             .addPathPatterns("/**")
    //             .excludePathPatterns("/css/**", "*.ico", "/error", "/error-page/**");//오류 페이지 경로
    // }

    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
        resolvers.add(new MyHandlerExceptionResolver());
        resolvers.add(new UserHandlerExceptionResolver());
    }

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        // REQUEST과 ERROR일때 필터 적용
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistrationBean;
    }
}

실행결과

이전과 다르게 ERROR 요청에 필터가 적용이 안되고, REQUEST요청에만 필터가 적용되는것을 볼 수 있습니다.

반응형

댓글