Contents

Java中拦截过滤模式的简介

1. 概述

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

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

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

2. 用例

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

3. 过滤策略

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

$> mvn install jetty:run

3.1.自定义过滤策略

自定义过滤策略用于需要对请求进行有序处理的每个用例,意思是一个过滤器基于执行链中前一个过滤器的结果。 这些链将通过实现FilterChain 接口并使用它注册各种Filter类来创建。 当使用具有不同关注点的多个过滤器链时,您可以将它们连接到一个过滤器管理器中:

/uploads/intercepting_filter_pattern_in_java/1.png

在我们的示例中,访问者计数器通过计算来自登录用户的唯一用户名来工作,这意味着它基于身份验证过滤器的结果,因此,两个过滤器必须链接。

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

首先,我们将创建一个身份验证过滤器,它检查会话是否存在设置“用户名”属性,如果不存在则发出登录过程:

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,并向请求添加一个“计数器”属性:

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

接下来,我们将实现一个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);
        }
    }
}

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

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. 标准过滤策略

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

标准过滤器策略允许将新过滤器插入默认链中,而无需明确定义过滤器管理器:

/uploads/intercepting_filter_pattern_in_java/3.png

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

让我们实现一个注解驱动的编码过滤器,它也使用基本过滤器策略:

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

在具有部署描述符的 Servlet 场景中,我们的web.xml将包含这些额外的声明:

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

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

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

3.4. 模板过滤策略

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

/uploads/intercepting_filter_pattern_in_java/5.png

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

由于这种策略不太常见,并且我们在示例中没有使用它,因此具体的实现和用例取决于您的想象:

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