Introduction to Intercepting Filter Pattern in Java – Java中拦截过滤模式的介绍

最后修改: 2016年 11月 5日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to introduce the Intercepting Filter Pattern presentation-tier Core J2EE Pattern.

在本教程中,我们将介绍拦截式过滤器模式演示层核心J2EE模式。

This is the second tutorial in our Pattern Series and a follow-up to the Front Controller Pattern guide which can be found here.

这是我们的模式系列中的第二个教程,也是前台控制器模式指南的后续,可以在这里找到。

Intercepting Filters are filters that trigger actions before or after an incoming request is processed by a handler.

拦截过滤器是在处理程序处理传入的请求之前或之后触发行动的过滤器。

Intercepting filters represents centralized components in a web application, common to all requests and extensible without affecting existing handlers.

拦截过滤器代表了网络应用中的集中式组件,对所有的请求都是通用的,并且可以在不影响现有处理程序的情况下进行扩展。

2. Use Cases

2.使用案例

Let’s extend the example from the previous guide and implement an authentication mechanism, request logging, and a visitor counter. In addition, we want the ability to deliver our pages in various different encoding.

让我们扩展上一篇指南中的例子,并实现认证机制、请求日志和访客计数器。此外,我们希望能够以各种不同的编码来交付我们的页面

All these are use cases for intercepting filters because they are common to all requests and should be independent of the handlers.

所有这些都是拦截过滤器的用例,因为它们是所有请求所共有的,应该独立于处理程序。

3. Filter Strategies

3.过滤策略

Let us introduce different filter strategies and exemplary use cases. To run the code with Jetty Servlet container, simply execute:

让我们介绍一下不同的过滤策略和示范性的使用案例。要用Jetty Servlet容器运行代码,只需执行。

$> mvn install jetty:run

3.1. Custom Filter Strategy

3.1.自定义过滤器策略

The custom filter strategy is used in every use case that requires an ordered processing of requests, in the meaning of one filter is based on the results of a previous filter in an execution chain.

自定义过滤器策略用于每个需要有序处理请求的用例中,即一个过滤器是基于执行链中前一个过滤器的结果

These chains will be created by implementing the FilterChain interface and registering various Filter classes with it.

这些链将通过实现FilterChain接口和用它注册各种Filter类来创建。

When using multiple filter chains with different concerns, you can join them together in a filter manager:

当使用具有不同关注点的多个过滤器链时,你可以在一个过滤器管理器中把它们连接起来。

intercepting filter-custom strategy

 

In our example, the visitor counter is working by counting unique usernames from logged-in users, which means it’s based on the result of the authentication filter, therefore, both filters have to be chained.

在我们的例子中,访客计数器是通过计算登录用户的唯一用户名来工作的,这意味着它是基于认证过滤器的结果,因此,这两个过滤器必须被链起来。

Let’s implement this filter chain.

让我们来实现这个过滤器链。

First, we’ll create an authentication filter which checks if the session exists for a set ‘username’ attribute and issue a login procedure if not:

首先,我们将创建一个认证过滤器,检查所设定的 “用户名 “属性的会话是否存在,如果不存在就发出一个登录程序。

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);
        }
    }
    
    ...
}

Now let’s create the visitor counter. This filter maintains a HashSet of unique usernames and adds a ‘counter’ attribute to the request:

现在让我们来创建访客计数器。这个过滤器维护一个唯一用户名的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);
    }

    ...
}

Next, we’ll implement a FilterChain that iterates registered filters and executes doFilter method:

接下来,我们将实现一个FilterChain,迭代注册的过滤器并执行doFilter方法。

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);
        }
    }
}

To wire our components together, let’s create a simple static manager which is responsible for instantiating filter chains, registering its filters, and initiate it:

为了把我们的组件连在一起,让我们创建一个简单的静态管理器,它负责实例化过滤器链,注册其过滤器,并启动它。

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);
    }
}

As the last step we’ll have to call our FilterManager as common part of the request processing sequence from within our FrontCommand:

作为最后一步,我们必须从FrontCommand中调用我们的FilterManager作为请求处理序列的共同部分。

public abstract class FrontCommand {
    ...

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

    ...
}

3.2. Base Filter Strategy

3.2.基础过滤策略

In this section, we’ll present the Base Filter Strategy, with which a common superclass is used for all implemented filters.

在这一节中,我们将介绍基础过滤器策略,所有实现的过滤器都使用一个共同的超类。

This strategy plays nicely together with the custom strategy from the previous section or with the Standard Filter Strategy that we’ll introduce in the next section.

这个策略与上一节的自定义策略或下一节将介绍的标准过滤策略一起使用,效果很好。

The abstract base class can be used to apply custom behavior that belongs to a filter chain. We’ll use it in our example to reduce boilerplate code related to filter configuration and debug logging:

这个抽象的基类可以用来应用属于过滤器链的自定义行为。我们将在我们的例子中使用它来减少与过滤器配置和调试日志有关的模板代码。

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());
    }
}

Let’s extend this base class to create a request logging filter, which will be integrated into the next section:

让我们扩展这个基类,创建一个请求记录过滤器,它将被整合到下一节。

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. Standard Filter Strategy

3.3.标准过滤策略

A more flexible way of applying filters is to implement the Standard Filter Strategy. This can be done by declaring filters in a deployment descriptor or, since Servlet specification 3.0, by annotation.

应用过滤器的一个更灵活的方法是实现标准过滤器策略。这可以通过在部署描述符中声明过滤器来实现,或者从Servlet规范3.0开始,通过注解来实现。

The standard filter strategy allows to plug-in new filters into a default chain without having an explicitly defined filter manager:

标准的过滤器策略允许将新的过滤器插入到默认链中,而不需要明确定义过滤器管理器。

intercepting filter-standard strategy

 

Note that the order, in which the filters get applied, cannot be specified via annotation. If you need an ordered execution, you have to stick with a deployment descriptor or implement a custom filter strategy.

请注意,不能通过注解来指定应用过滤器的顺序。如果你需要一个有序的执行,你必须坚持使用部署描述符或实现一个自定义的过滤器策略。

Let’s implement an annotation driven encoding filter that also uses the base filter strategy:

让我们来实现一个注释驱动的编码过滤器,它也使用基础过滤器策略。

@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);
    }
}

In a Servlet scenario with having a deployment descriptor, our web.xml would contain these extra declarations:

在拥有部署描述符的Servlet场景中,我们的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>

Let’s pick-up our logging filter and annotate it too, in order to get used by the Servlet:

让我们拿起我们的日志过滤器并对其进行注释,以便被Servlet使用。

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

3.4. Template Filter Strategy

3.4.模板过滤策略

The Template Filter Strategy is pretty much the same as the base filter strategy, except that it uses template methods declared in the base class that must be overridden in implementations:

模板过滤策略与基类过滤策略基本相同,只是它使用基类中声明的模板方法,这些方法必须在实现中被重载。

intercepting filter-template strategy

 

Let’s create a base filter class with two abstract filter methods that get called before and after further processing.

让我们创建一个基础过滤器类,其中有两个抽象的过滤器方法,在进一步处理之前和之后被调用。

Since this strategy is less common and we don’t use it in our example, a concrete implementation and use case is up to your imagination:

由于这种策略不太常见,而且我们的例子中也没有使用它,所以具体的实现和用例就看你的想象力了。

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. Conclusion

4.结论

The Intercepting Filter Pattern captures cross-cutting concerns that can evolve independently of the business logic. From the perspective of business operations, filters are executed as a chain of pre or post actions.

拦截式过滤器模式捕获了可以独立于业务逻辑发展的交叉关注。从业务操作的角度来看,过滤器是作为一连串的前或后动作来执行的。

As we’ve seen so far, the Intercepting Filter Pattern can be implemented using different strategies. In a ‘real world’ applications these different approaches can be combined.

正如我们到目前为止所看到的,拦截过滤器模式可以使用不同的策略来实现。在 “真实世界 “的应用中,这些不同的方法可以被结合起来。

As usual, you’ll find the sources on GitHub.

像往常一样,你会发现源代码在GitHub上。