GenericFilterBean

soomin·2021년 8월 18일
1

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/GenericFilterBean.html

Simple base implementation of Filter which treats its config parameters (init-param entries within the filter tag in web.xml) as bean properties.

A handy superclass for any type of filter. Type conversion of config parameters is automatic, with the corresponding setter method getting invoked with the converted value. It is also possible for subclasses to specify required properties. Parameters without matching bean property setter will simply be ignored.

This filter leaves actual filtering to subclasses, which have to implement the Filter.doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) method.

This generic filter base class has no dependency on the Spring ApplicationContext concept. Filters usually don't load their own context but rather access service beans from the Spring root application context, accessible via the filter's ServletContext (see WebApplicationContextUtils).

설명을 보면 Spring의 ApplicationContext 에 종속되는 것이 아니라, GenericFilterBean 이 필드로가지고 있는 ServletContext를 가지고 SpringApplicationContext 내부 서비스 빈에 접근한다고 하고 있음.
[WebApplicationContextUtils 클래스를 통해 ApplicationContext 를 얻어냄.]



확인


  • WebApplicationContextUtils 클래스의 함수에 ServletContext 를 인자로 주면 등록된 ApplicationContext 정보를 얻어 서비스 빈에 엑세스할 수 있다는 의미로 보임.
  • 즉, ServletContext 영역의 Filter 에서 ApplicationContext 영역에 진입할 수 있다는 이야기인 듯
  • 크게 대단한 점이 있는가..어떻게든 가져오면 되는데...할 수 있겠지만.., 의존주입 + 생애주기 관리 면에 있어서 굉장히 편리해졌고, "javax.servlet.Filter도 Spring 프로세스에 통합되는구나"하는 생각이 든다.
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    private ServletContext servletContext;

    @Override
    public void run(ApplicationArguments args) throws Exception {

	WebApplicationContext webApplicationContext = 
    		WebApplicationContextUtils.findWebApplicationContext(servletContext);

        TestDao bean = webApplicationContext.getBean(TestDao.class);

        bean.countAllTestRecord();

        /**
         * jdbc.resultsettable                      : |------|
         * jdbc.resultsettable                      : |count |
         * jdbc.resultsettable                      : |------|
         * jdbc.resultsettable                      : |19    |
         * jdbc.resultsettable                      : |------|
         */

    }
}



내부 까보기


  • 내부는 javax.servlet.Filter 및 SpringBean 에 필요한 인터페이스들을 구현 중
  • 정확히 영역이 어디인지 모르겠으니, 구현된 소스인 DelegatingFilterProxy 도 까봄

인젝션에 대한 이야기는 들어있지 않고, 필드에 객체를 저장해서 사용하는데, 위임도 targetBean 의 이름[정확히 이야기하면 FilterChainProxy 빈의 이름]을 이용하며, 이를 위해서 ApplicationContext 를 필드에 저장하고 있음


ApplicationContext 는 다음과 같이 주입


#doFilter() 에서 #initDelegate() 함수 호출


#initDelegate() 에서는 ApplicationContext 를 가지고 빈객체를 빼와서 조건에 따라 Filter#init() 를 호출 시킴


#doFilter() 의 마지막라인, #invokeDelegate() 함수 호출로 필터 객체의 #doFilter() 를 실행

여기서 호출하는 Filter 구현체 delegate 는 ApplicationContext 에 속한 녀석 이므로, SpringBean 인 Filter 에게 위임한다고 볼 수 있음. 하지만, FilterChainProxyDelegatingFilterProxy 둘 다 GenericFilterBean 을 상속하고 있다는 점이 중요.



이렇게 보면, DelegatingFilterProxyServletFilter 으로 동작하는 것이 확실 한 듯, 앞 선에서 요청을 받아서 스프링 FilterChainProxy 에게 요청을 위임한다는 프로세스도 정확히 지키고 있음.



실험


GenericFilterBean 구현체를 등록 후, ApplicationContext 에 등록된 Filter 객체를 가져와서 각 객체의 HashCode 를 찍어보기

public class PagingFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("PagingFilter : "+System.identityHashCode(this));
        chain.doFilter(request,response);
    }

}

@RestController
public class TestController {

    @Autowired
    public ApplicationContext applicationContext;

    @GetMapping("/test")
    public String test(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        FilterRegistrationBean pagingFilter = applicationContext.getBean("pagingFilter", FilterRegistrationBean.class);

        ServletRequest sr = (ServletRequest) request;
        ServletResponse srr = (ServletResponse) response;

        pagingFilter.getFilter().doFilter(sr, srr, new FilterChain() {
            @Override
            public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
                System.out.println();
            }
        });

        return "Hello World";
    }
}

결과



GenericFilterBean


GenericFilterBean 추상클래스의 구현정보와 필드정보, 이 중 ServletContext 도 필드로 두고 있다.


#init() 함수에서 ServletConfig 정보를 가지고 Bean 정보로 취합하는 듯하며, 이를 통해 Bean 으로 Wrapping하고 있다.



결론


GenericFilterBean 클래스를 보고, 이건 스프링이야! 서블릿이야! 하는 행동이 무의미하다는 것을 깨달았다. 다형성 관점으로 넓혀 해석하면, 작성 소스에 따라서 어디든지 속할 수도 있겠다 싶다. 스프링에서 꺼내다 쓰던, Servlet 영역에 있는 필터를 꺼내쓰던 같은 객체인 것 같고.. 물론, 작동 영역을 알고 있는 것은 도움이 되는 부분인 것은 확실하다.

엄밀히 말하면 레퍼런스 설명과 동작 시점은 ServletFilter 시점이기 때문에 ServletContext 에 존재한다.
하지만 동시에 SpringContainer 에서도 꺼내서 찾을 수 있다. 이 시점에서 정말 이게 중요한건가 싶은 생각이 든다.

SpringBean 등록 후 후속 함수에서 ServletContext Bean 에 접근하여 필터로 추가할 수도 있는 일이다.

	@Override
    public void run(ApplicationArguments args) throws Exception {
        WebApplicationContext context = WebApplicationContextUtils.findWebApplicationContext(servletContext);

        context.getServletContext().getFilterRegistrations()
                .entrySet().stream().forEach(e -> System.out.printf("Filter Registration Info : %s\n", e.getValue().getClassName()));
        Object pagingFilter = context.getBean("pagingFilter");
        System.out.println(pagingFilter);
    }
    
/**
Filter Registration Info : org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter
Filter Registration Info : org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter
Filter Registration Info : org.apache.tomcat.websocket.server.WsFilter
Filter Registration Info : com.study.filter.core.filter.PagingFilter
Filter Registration Info : org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter
Filter Registration Info : org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean$1
Filter Registration Info : org.springframework.boot.web.servlet.filter.OrderedFormContentFilter

filterRegistrationBean urls=[/*] order=-90
*/

Spring Boot 환경에서 내장 톰캣처럼, web.xml 로 분리되는 설정들이 Java 소스로 변화되고 스프링 설정내부로 들어오면서 Spring 이 ServletContext 정보를 가지게 되었다.(ServletContext Bean)

ServletContext 의 설정도, SpringContainer 의 설정도 소스로 설정되며, 이 흐름은 더 이상 영역을 분리하여 설정하는 것이 아닌, 영역 간 통합의 흐름과 Spring 의 메커니즘에서 동작하는 방식으로 통합되는 것을 의미하고 있다.

큰 줄기로 보면, 이 흐름에 맞추어 Filter 이면서도 Spring 에서 객체를 주입받거나 생애주기를 관리 받을 수 있는 객체는 필요할 수 밖에 없는 것이다.

물론 논리적으로 분할 된 공간이므로 나눌 수 있으나 통합되는 흐름에 맞추어 Filter 들도 GenericFilterBean처럼, SpringContainer 에 포함되면서도 논리적으로는 Servlet Filter 처럼 작동하는 통합의 흐름을 보이고 있다.

캡슐화에 맞추어 해당 소스는 Spring 내부에 감춰지고, 스스로 web.xml 에 기술하던 설정들도 어느세 SpringBoot 내장 톰캣 메커니즘에 의해 자동화되고 소스를 파악하지않거나, 설정을 하지않아도 동작하게 되었다.

각각의 영역을 확실히 알아내는 것도 성장에 도움을 주지만, 해당 Filter 가 언제 동작하는지 & 왜 만들었는지 & 어떤 소스와 같이 맞물리는 지 에 대한 실효적인 사고의 흐름이 필요하다. 이번에 정보를 알아보고 질문하여 답을 얻으므로서 세부적인 것에 대한 사고는 도움을 줄 수 있으나, 경험이 부족한 상태로 세세한 것에 몰두하다보면 변화 흐름을 볼 수 없게 만든다는 답을 얻었다.

profile
블로그 유목민

0개의 댓글