1. 개요

이 예제에서는 Intercepting Filter Pattern 프리젠테이션 계층 Core J2EE 패턴을 소개합니다.

이것은 패턴 시리즈 의 두 번째 예제이며 여기 에서 찾을 수 있는 전면 컨트롤러 패턴 사용방법(예제) 에 대한 후속 조치 입니다.

가로채기 필터 는 들어오는 요청이 처리기에 의해 처리되기 전이나 후에 작업을 트리거하는 필터입니다.

가로채는 필터는 웹 응용 프로그램의 중앙 집중식 구성 요소를 나타내며 모든 요청에 ​​공통이며 기존 처리기에 영향을 주지 않고 확장할 수 있습니다.

2. 사용 사례

이전 사용방법(예제) 의 예제를 확장하여 인증 메커니즘, 요청 로깅 및 방문자 카운터를 구현해 보겠습니다 . 또한 다양한 인코딩 으로 페이지를 전달할 수 있기를 원합니다 .

이 모든 것은 필터를 가로채기 위한 사용 사례입니다. 이는 모든 요청에 ​​공통적이며 핸들러와 독립적이어야 하기 때문입니다.

3. 필터 전략

다양한 필터 전략과 예시적인 사용 사례를 소개하겠습니다. Jetty Servlet 컨테이너로 코드를 실행하려면 다음을 실행하기만 하면 됩니다.

$> mvn install jetty:run

3.1. Custom 필터 전략

사용자 지정 필터 전략은 요청을 순서대로 처리해야 하는 모든 사용 사례에 사용되며, 하나의 필터는 실행 체인의 이전 필터 결과를 기반으로 한다는 의미입니다 .

이러한 체인은 FilterChain 인터페이스를 구현하고 여기에 다양한 필터 클래스를 등록하여 생성됩니다.

관심사가 다른 여러 필터 체인을 사용하는 경우 필터 관리자에서 함께 결합할 수 있습니다.

필터 Custom 전략 가로채기

 

이 예에서 방문자 카운터는 로그인한 사용자의 고유한 사용자 이름을 계산하여 작동합니다. 즉, 인증 필터의 결과를 기반으로 하므로 두 필터를 모두 연결해야 합니다.

이 필터 체인을 구현해 보겠습니다.

먼저, 설정된 'username' 속성에 대해 세션이 존재하는지 확인하는 인증 필터를 만들고 그렇지 않은 경우 로그인 절차를 실행합니다.

public class AuthenticationFilter implements Filter {
    ...
    @Override
    public void doFilter(
      ServletRequest request,
      ServletResponse response, 
      FilterChain chain) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        
        HttpSession session = httpServletRequest.getSession(false);
        if (session == null || session.getAttribute("username") == null) {
            FrontCommand command = new LoginCommand();
            command.init(httpServletRequest, httpServletResponse);
            command.process();
        } else {
            chain.doFilter(request, response);
        }
    }
    
    ...
}

이제 방문자 카운터를 만들어 보겠습니다. 이 필터는 고유한 사용자 이름의 HashSet 을 유지 하고 요청에 'counter' 속성을 추가합니다.

public class VisitorCounterFilter implements Filter {
    private static Set<String> users = new HashSet<>();

    ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) {
        HttpSession session = ((HttpServletRequest) request).getSession(false);
        Optional.ofNullable(session.getAttribute("username"))
          .map(Object::toString)
          .ifPresent(users::add);
        request.setAttribute("counter", users.size());
        chain.doFilter(request, response);
    }

    ...
}

다음으로 등록된 필터를 반복하고 doFilter 메서드 를 실행하는 FilterChain 을 구현합니다.

public class FilterChainImpl implements FilterChain {
    private Iterator<Filter> filters;

    public FilterChainImpl(Filter... filters) {
        this.filters = Arrays.asList(filters).iterator();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
        if (filters.hasNext()) {
            Filter filter = filters.next();
            filter.doFilter(request, response, this);
        }
    }
}

구성 요소를 함께 연결하기 위해 필터 체인을 인스턴스화하고 해당 필터를 등록하고 시작하는 역할을 하는 간단한 정적 관리자를 만들어 보겠습니다.

public class FilterManager {
    public static void process(HttpServletRequest request,
      HttpServletResponse response, OnIntercept callback) {
        FilterChain filterChain = new FilterChainImpl(
          new AuthenticationFilter(callback), new VisitorCounterFilter());
        filterChain.doFilter(request, response);
    }
}

마지막 단계 로 FrontCommand 내에서 요청 처리 시퀀스의 공통 부분으로 FilterManager 를 호출해야 합니다 .

public abstract class FrontCommand {
    ...

    public void process() {
        FilterManager.process(request, response);
    }

    ...
}

3.2. 기본 필터 전략

이 섹션에서는 구현된 모든 필터에 공통 수퍼클래스가 사용되는 기본 필터 전략을 제시합니다.

이 전략은 이전 섹션의 사용자 지정 전략 또는 다음 섹션에서 소개할 표준 필터 전략 과 잘 어울립니다 .

추상 기본 클래스를 사용하여 필터 체인에 속하는 사용자 지정 동작을 적용할 수 있습니다. 필터 구성 및 디버그 로깅과 관련된 상용구 코드를 줄이기 위해 예제에서 이를 사용할 것입니다.

public abstract class BaseFilter implements Filter {
    private Logger log = LoggerFactory.getLogger(BaseFilter.class);

    protected FilterConfig filterConfig;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("Initialize filter: {}", getClass().getSimpleName());
        this.filterConfig = filterConfig;
    }

    @Override
    public void destroy() {
        log.info("Destroy filter: {}", getClass().getSimpleName());
    }
}

이 기본 클래스를 확장하여 다음 섹션에 통합될 요청 로깅 필터를 생성해 보겠습니다.

public class LoggingFilter extends BaseFilter {
    private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response,
      FilterChain chain) {
        chain.doFilter(request, response);
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        
        String username = Optional
          .ofNullable(httpServletRequest.getAttribute("username"))
          .map(Object::toString)
          .orElse("guest");
        
        log.info(
          "Request from '{}@{}': {}?{}", 
          username, 
          request.getRemoteAddr(),
          httpServletRequest.getRequestURI(), 
          request.getParameterMap());
    }
}

3.3. 표준 필터 전략

필터를 적용하는 보다 유연한 방법은 표준 필터 전략 을 구현하는 것 입니다. 이는 배치 설명자에서 필터를 선언하거나 서블릿 사양 3.0부터 어노테이션을 통해 수행할 수 있습니다.

표준 필터 전략 을 사용하면 명시적으로 정의된 필터 관리자 없이 새 필터를 기본 체인에 플러그인할 수 있습니다.

필터 표준 전략 가로채기

 

필터가 적용되는 순서는 어노테이션을 통해 지정할 수 없습니다. 순서가 지정된 실행이 필요한 경우 배포 설명자를 고수하거나 사용자 지정 필터 전략을 구현해야 합니다.

기본 필터 전략도 사용하는 어노테이션 기반 인코딩 필터를 구현해 보겠습니다.

@WebFilter(servletNames = {"intercepting-filter"}, 
  initParams = {@WebInitParam(name = "encoding", value = "UTF-8")})
public class EncodingFilter extends BaseFilter {
    private String encoding;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        super.init(filterConfig);
        this.encoding = filterConfig.getInitParameter("encoding");
    }

    @Override
    public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) {
        String encoding = Optional
          .ofNullable(request.getParameter("encoding"))
          .orElse(this.encoding);
        response.setCharacterEncoding(encoding); 
        
        chain.doFilter(request, response);
    }
}

배포 설명자가 있는 서블릿 시나리오에서 web.xml 에는 다음과 같은 추가 선언이 포함됩니다.

<filter>
    <filter-name>encoding-filter</filter-name>
    <filter-class>
      com.baeldung.patterns.intercepting.filter.filters.EncodingFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>encoding-filter</filter-name>
    <servlet-name>intercepting-filter</servlet-name>
</filter-mapping>

서블릿에서 사용할 수 있도록 로깅 필터를 선택하고 어노테이션도 추가해 보겠습니다.

@WebFilter(servletNames = "intercepting-filter")
public class LoggingFilter extends BaseFilter {
    ...
}

3.4. 템플릿 필터 전략

템플릿 필터 전략구현에서 재정의해야 하는 기본 클래스에 선언된 템플릿 메서드를 사용한다는 점을 제외하면 기본 필터 전략과 거의 동일합니다.

필터 템플릿 전략 가로채기

 

추가 처리 전후에 호출되는 두 개의 추상 필터 메서드를 사용하여 기본 필터 클래스를 만들어 보겠습니다.

이 전략은 덜 일반적이고 이 예에서는 사용하지 않으므로 구체적인 구현 및 사용 사례는 상상에 달려 있습니다.

public abstract class TemplateFilter extends BaseFilter {
    protected abstract void preFilter(HttpServletRequest request,
      HttpServletResponse response);

    protected abstract void postFilter(HttpServletRequest request,
      HttpServletResponse response);

    @Override
    public void doFilter(ServletRequest request,
      ServletResponse response, FilterChain chain) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        
        preFilter(httpServletRequest, httpServletResponse);
        chain.doFilter(request, response);
        postFilter(httpServletRequest, httpServletResponse);
    }
}

4. 결론

Intercepting Filter Pattern은 비즈니스 로직과 독립적으로 발전할 수 있는 교차 문제를 포착합니다. 비즈니스 운영의 관점에서 필터는 사전 또는 사후 조치의 체인으로 실행됩니다.

지금까지 살펴본 것처럼 Intercepting Filter Pattern 은 다양한 전략을 사용하여 구현할 수 있습니다. '실제' 애플리케이션에서는 이러한 다양한 접근 방식을 결합할 수 있습니다.

평소와 같이 GitHub 에서 소스를 찾을 수 있습니다 .

Generic footer banner