重新认识javax.servlet.Filter过滤器

在web容器中, 最先加载的是 Listener, 其次是 Filter 最后是 Servlet.
Filter是一个特殊的 Servlet, 其生命周期分为以下三部分:

  • init(FilterConfig filterConfig) // 为Filter初始化 提供支持
  • doFilter(ServletRequest request, ServletResponse response, FilterChain chain) //这个是 Filter的核心方法被拦截请求在这里被处理
  • destroy() // Filter 实例销毁前的准备工作 //Called by the web container to indicate to a filter that it is being taken out of service.

通常我们将自定义的Filter配置在web容器的web.xml中, 大致分为两个部分来配置这个Filter.

//声明filter
<filter>
    <filter-name>filter-name</filter-name>
    <filter-class>com.xx.FilterClass</filter-class>
//初始化参数
    <init-param>
        <param-name>key</param-name>
        <param-value>value</param-value>
    </init-param>
</filter>

//filter 拦截规则
<filter-mapping>
  <filter-mapping>
  <filter-name>filter-name</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

和servlet不同的是, 如果配置多个filter, 则其声明的先后顺序是区别的, servlet在匹配到后流程至此终止, 而 filter不一定, 他是可以向下传递下去进入下一个filter. 这里能够使 Filter形成链式调用关键点在于 doFilter方法参数FilterChain {doFilter(ServletRequest request, ServletResponse response, FilterChain chain)}

FilterChain 是一个接口, 他只有一个方法 doFilter(ServletRequest var1, ServletResponse var2). 在我们希望将 #request和 #response传递给下一个 Filter处理的时候就会使用 FilterChain. 如:

filterChain.doFilter(request, response) ;

这样就会进入下一个Filter的处理逻辑. 如此形成有名的Filter职责链. 类似链表结构. 链表是持有自己的引用, 但是FilterChain不是, 他有点类似代理和链表的结合体. 经过查看 tomcat源码, 才知道, 这个职责链是由web容器管理的. 不同的容器实现FilterChain接口方法 #doFilter 就可以了. 例如在tomcat中, 实现类叫ApplicationFilterChain 这个类包含我们声明的全部Filter, 并且有序, 然后记录当前Filter在Filter链中的索引值, 获取该索引值的Filter然后调用doFilter同时将自己传给这个Filter, 代码如下:

filter.doFilter(request, response, this);

如果全部Filter执行完毕, 则将流程交给对应的servlet实例执行具体的业务.

servlet.service(request, response);

至此全部流程完毕.

我接触到的这种职责链模式很多地方有使用, spring#CompositeFilter就是职责链很经典的使用而且也重新实现了一个简单的职责链管理器. 在 spring-web模块中可以看见:

private static class VirtualFilterChain implements FilterChain {

   private final FilterChain originalChain;

   private final List<? extends Filter> additionalFilters;

   private int currentPosition = 0;

   public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) {
      this.originalChain = chain;
      this.additionalFilters = additionalFilters;
   }

   @Override
   public void doFilter(final ServletRequest request, final ServletResponse response)
         throws IOException, ServletException {

      if (this.currentPosition == this.additionalFilters.size()) {
         this.originalChain.doFilter(request, response);
      }
      else {
         this.currentPosition++;
         Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
         nextFilter.doFilter(request, response, this);
      }
   }
}

根据描述, CompositeFilter通常需要结合 FIlter代理类#DelegatingFilterProxy

/**
 * A generic composite servlet {@link Filter} that just delegates its behavior
 * to a chain (list) of user-supplied filters, achieving the functionality of a
 * {@link FilterChain}, but conveniently using only {@link Filter} instances.
 *
 * <p>This is useful for filters that require dependency injection, and can
 * therefore be set up in a Spring application context. Typically, this
 * composite would be used in conjunction with {@link DelegatingFilterProxy},
 * so that it can be declared in Spring but applied to a servlet context.
 *
 * @author Dave Syer
 * @since 3.1
 */

这个类我们配置在web.xml文件中, 作为对外唯一可见的一个Filter. 如:

<filter>
    <filter-name>compositeFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>compositeFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

查看 DelegatingFilterProxy源码, DelegatingFilterProxy会在spring容器bean初始化完成之后初始化被代理的Filter.

public class DelegatingFilterProxy extends GenericFilterBean{

@Override
protected void initFilterBean() throws ServletException {
   synchronized (this.delegateMonitor) {
      if (this.delegate == null) {
         // If no target bean name specified, use filter name.
         if (this.targetBeanName == null) {
            this.targetBeanName = getFilterName();
         }
         // Fetch Spring root application context and initialize the delegate early,
         // if possible. If the root application context will be started after this
         // filter proxy, we'll have to resort to lazy initialization.
         WebApplicationContext wac = findWebApplicationContext();
         if (wac != null) {
            this.delegate = initDelegate(wac);
         }
      }
   }
}
}

public abstract class GenericFilterBean implements
      Filter, BeanNameAware, EnvironmentAware, ServletContextAware, InitializingBean, DisposableBean {

/**
 * Calls the {@code initFilterBean()} method that might
 * contain custom initialization of a subclass.
 * <p>Only relevant in case of initialization as bean, where the
 * standard {@code init(FilterConfig)} method won't be called.
 * @see #initFilterBean()
 * @see #init(javax.servlet.FilterConfig)
 */
@Override
public void afterPropertiesSet() throws ServletException {
   initFilterBean(); //这是一个空方法, 需要子类实现
}
}

这样就形成了Filter代理 和Filter职责链.
使用DelegatingFilterProxy的目的多用于我们在自定义业务FIlter的时候常常需要依赖spring管理的bean. 但是因为在web容器中, Filter首先被加载, 这之后spring容器才会被加载(这里还没有代码验证过),所以如果在Filter中依赖spring管理bean则会出现空指针引用, 无法使用. 当使用DelegatingFilterProxy后, 这种情况就改变了. 根据这个类的实现. 容器加载DelegatingFilterProxy这个Filter后, 会等待spring容器初始化全部bean完成后#afterPropertiesSet方法被调用的时候才会初始化DelegatingFilterProxy这个Filter代理的全部Filter, 这时候被代理Filter就能够拿到spring容器里的全部bean.

上面的说法, 我觉得不对, DelegatingFilterProxy 首先可以将多个Filter串联在一起, 多个Filter可以以Spring Bean的形式的定义在spring配置文件中, 而不用全部声明到web.xml文件中. 然后这些Filter初始化都委托给DelegatingFilterProxy. 我觉得这是这个类本来的职责.
按照web容器规范, Listener/FIlter/Servlet他们在web容器中的的初始化顺序是 Listener -> FIlter -> Servlet ; 如果引入spring框架, 一般, 我们都是使用 Spring#ContextLoaderListener来加载spring以及初始化Spring IOC容器, 所以 这个先后顺序没有问题, 只是Filter中如果依赖了spring bean拿不到bean. 无法注入, 因为web.xml中无法注入spring bean, Filter也拿不到ApplicationContext, 所以没有办法拿到spring#bean, 只有把 Filter也纳入spring管理这样才可以拿到spring bean.

注意: 以下配置节点targetFilterLifecycle

<filter>
    <filter-name>compositeFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

targetFilterLifecycle为 true代表spring管理filter的初始化, 即 init和 destroy方法在适当的时候会被spring调用. 如果为false则这两个方法无效.具体代码如下:

/**
 * Initialize the Filter delegate, defined as bean the given Spring
 * application context.
 * <p>The default implementation fetches the bean from the application context
 * and calls the standard {@code Filter.init} method on it, passing
 * in the FilterConfig of this Filter proxy.
 * @param wac the root application context
 * @return the initialized delegate Filter
 * @throws ServletException if thrown by the Filter
 * @see #getTargetBeanName()
 * @see #isTargetFilterLifecycle()
 * @see #getFilterConfig()
 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
 */
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
   Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
   if (isTargetFilterLifecycle()) {
      delegate.init(getFilterConfig());
   }
   return delegate;
}
/**
 * Destroy the Filter delegate.
 * Default implementation simply calls {@code Filter.destroy} on it.
 * @param delegate the Filter delegate (never {@code null})
 * @see #isTargetFilterLifecycle()
 * @see javax.servlet.Filter#destroy()
 */
protected void destroyDelegate(Filter delegate) {
   if (isTargetFilterLifecycle()) {
      delegate.destroy();
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266