1. 개요
이 예제에서는 Intercepting Filter Pattern 프리젠테이션 계층 Core J2EE 패턴을 소개합니다.
이것은 패턴 시리즈 의 두 번째 예제이며 여기 에서 찾을 수 있는 전면 컨트롤러 패턴 사용방법(예제) 에 대한 후속 조치 입니다.
가로채기 필터 는 들어오는 요청이 처리기에 의해 처리되기 전이나 후에 작업을 트리거하는 필터입니다.
가로채는 필터는 웹 응용 프로그램의 중앙 집중식 구성 요소를 나타내며 모든 요청에 공통이며 기존 처리기에 영향을 주지 않고 확장할 수 있습니다.
2. 사용 사례
이전 사용방법(예제) 의 예제를 확장하여 인증 메커니즘, 요청 로깅 및 방문자 카운터를 구현해 보겠습니다 . 또한 다양한 인코딩 으로 페이지를 전달할 수 있기를 원합니다 .
이 모든 것은 필터를 가로채기 위한 사용 사례입니다. 이는 모든 요청에 공통적이며 핸들러와 독립적이어야 하기 때문입니다.
3. 필터 전략
다양한 필터 전략과 예시적인 사용 사례를 소개하겠습니다. Jetty Servlet 컨테이너로 코드를 실행하려면 다음을 실행하기만 하면 됩니다.
$> mvn install jetty:run
3.1. Custom 필터 전략
사용자 지정 필터 전략은 요청을 순서대로 처리해야 하는 모든 사용 사례에 사용되며, 하나의 필터는 실행 체인의 이전 필터 결과를 기반으로 한다는 의미입니다 .
이러한 체인은 FilterChain 인터페이스를 구현하고 여기에 다양한 필터 클래스를 등록하여 생성됩니다.
관심사가 다른 여러 필터 체인을 사용하는 경우 필터 관리자에서 함께 결합할 수 있습니다.
이 예에서 방문자 카운터는 로그인한 사용자의 고유한 사용자 이름을 계산하여 작동합니다. 즉, 인증 필터의 결과를 기반으로 하므로 두 필터를 모두 연결해야 합니다.
이 필터 체인을 구현해 보겠습니다.
먼저, 설정된 '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 에서 소스를 찾을 수 있습니다 .