filter在Tomcat的实现(转)

from  https://sanwen8.cn/p/45268RX.htm

filter是一个老生常谈的东西了,我们在前面研究Wrapper中已经说过,servlet能做的事情,filter也能做;其原因是filter和servlet都在一条请求调用链路中,Tomcat通过ApplicationFilterChain(Servlet规范的FilterChain的实现)在Valve执行之后,首先执行filter链,最后才执行Servlet,传入的参数都是httpRequest和httpResponse;

 

但是filter的定位不是Servlet的处理,它的任务是过滤,当出现不符合标准的情况之下,它直接就斩断当前的调用链路,直接退出;

 

看看下面的一个随便的filter的简单例子:

chain.doFilter的作用是通知FilterChain的链条继续执行下一个filter过滤器,当执行到这里,说明,当前的filter过滤通过了,如果身份验证失败,可以看到上述的程序中,直接就是res.senRedirect进行跳转了,容器的调用链路,后续的filter和servlet处理类都不执行了;

本文,通过源码的角度看看filter的创建,初始化,和整个的调用链路;

1.filter的解析与调用

FilterChain的实现是 ApplicationFilterChain,这个代表的是filter的链条:

其次,对于filterChain的创建是通过ApplicationFilterFactory来做的;

ApplicationFilterChain持有业务自定义的servlet的实例,用于最后执行;

在这之前,ApplicationFilterChain持有一系列的filterConfig,这个是filter的Config配置,该类中缓存对应的filter实例;

 

从上述的类的框图中,我们可以看得出来filter实例初始化的大概:

步骤一:创建filter之前,首先应该从配置文件中读取对应当前应用的filter配置,然后设置到filterConfig中来,并缓存到Context对象中;

ContextConfig是配置文件之后,将这两个filter的配置设置到当前应用的StandardContext中的两个属性中:

这两个属性实际就是web.xml中的filter和filter-mapping这两个节点的映射;

步骤二:当应用启动的时候,StandardContext的internalStart方法会调用filterStart方法,创建ApplicationFilterConfig,将上述的两个属性以这个ApplicationFilterConfig的形式存在:

 

步骤三:StandWrapperValve开始执行的时候,首先需要通过ApplicationFilterFactory工厂创建ApplicationFilterChain:

创建ApplicationFilterChain的过程很简单,就是new,主要是因为servlet的url是指定的,而filter-mapping也是可以配置的,那我们就需要只有当前的url匹配的filter,才应该加入到当前的filterChain中:

上述的两轮匹配,

一次通过matchFiltersURL,这个匹配是是精确匹配,直接通过包名+类名去匹配,这个没什么可说的;

第二次是matchFilterServlet,这个是模糊匹配,匹配规则和servlet的规则很类似:

首先,是精确匹配,url是什么就是什么,这个是最优先的;

其次,是/.../*

然后,是扩展匹配,也就是.xxxx

如果上述都不对,那么就匹配不上;

这段代码的规则在filter的规范中,和servlet的规则几乎是一样的;

步骤四:到这里,基于当前要执行的servlet,filterChain都已经准备了,下面就开始执行了,继续执行StandWrapperValve:

ApplicationChain执行doFilter的时候,会有两个操作,第一个操作是filter链条的执行,第二个是就是执行最终的servlet,当filter链条中有一个环节没有通过,直接就返回,而不会真正执行servlet,示意图如下:

代码如下,

前面已经缓存下来的ApplicationFilterConfig数组中的第一个元素,拿到第一个filter,然后开始执行:

后续的流程就是执行到filter中了,所以这就扣到前面最开始讲的,为什么每一个filter如果通过需要写filterChain.doFilter了,你需要人为的让这个filterChain继续下去;

 

上述filter流程从初始化到调用,流程很简单,没有什么可说的,

比较重要的一点需要引起重视,就是filter按照上述的逻辑,是有顺序的,而这个顺序其实就是从最开始ContextConfig配置文件解析的就有顺序了,filterConfig也是按照数组进行存储的,也是按照这个顺序的,从代码看到,第一个filter就是在web.xml中按照从上到下定义的第一个filter;

 

2.动态创建filter

在Servlet3.0版本以后,servlet和filter,甚至Listener都可以进行动态的创建,这里简单看看这个API特性:

在Tomcat已经启动时候,无法进行注册,一般这种动态注册在Tomcat容器启动的时候,有两种方法:

1.实现ServletContextListener接口,在contextInitialized方法中完成注册.

    public void contextInitialized(ServletContextEvent sce) { 

        ServletContext sc = sce.getServletContext(); 

        // Register Servlet 
        ServletRegistration sr = sc.addServlet("DynamicServlet", 
            "web.servlet.dynamicregistration_war.TestServlet"); 
        sr.setInitParameter("servletInitName", "servletInitValue"); 
        sr.addMapping("/*"); 

        // Register Filter 
        FilterRegistration fr = sc.addFilter("DynamicFilter", 
            "web.servlet.dynamicregistration_war.TestFilter"); 
        fr.setInitParameter("filterInitName", "filterInitValue"); 
        fr.addMappingForServletNames(EnumSet.of(DispatcherType.REQUEST), 
                                     true, "DynamicServlet"); 

        // Register ServletRequestListener 
        sc.addListener("web.servlet.dynamicregistration_war.TestServletRequestListener"); 

    }

2.在jar文件中放入实现ServletContainerInitializer接口的初始化器


在jar包中的META-INF/services/javax.servlet.ServletContainerInitializer文件,

文件内容为已经实现ServletContainerInitializer接口的类:

xxx.CustomServletContainerInitializer

该实现部分代码:

@HandlesTypes({ JarWelcomeServlet.class }) 
public class CustomServletContainerInitializer implements 
    ServletContainerInitializer { 
  private static final Log log = LogFactory 
      .getLog(CustomServletContainerInitializer.class); 

  private static final String JAR_HELLO_URL = "/jarhello"; 

  public void onStartup(Set<Class<?>> c, ServletContext servletContext) 
      throws ServletException { 
    log.info("CustomServletContainerInitializer is loaded here..."); 
    
    log.info("now ready to add servlet : " + JarWelcomeServlet.class.getName()); 
    
    ServletRegistration.Dynamic servlet = servletContext.addServlet( 
        JarWelcomeServlet.class.getSimpleName(), 
        JarWelcomeServlet.class); 
    servlet.addMapping(JAR_HELLO_URL); 


    log.info("now ready to add filter : " + JarWelcomeFilter.class.getName()); 
    FilterRegistration.Dynamic filter = servletContext.addFilter( 
        JarWelcomeFilter.class.getSimpleName(), JarWelcomeFilter.class); 


    EnumSet<DispatcherType> dispatcherTypes = EnumSet 
        .allOf(DispatcherType.class); 
    dispatcherTypes.add(DispatcherType.REQUEST); 
    dispatcherTypes.add(DispatcherType.FORWARD); 

    filter.addMappingForUrlPatterns(dispatcherTypes, true, JAR_HELLO_URL); 

    log.info("now ready to add listener : " + JarWelcomeListener.class.getName()); 
    servletContext.addListener(JarWelcomeListener.class); 
  } 
}

其中@HandlesTypes注解表示CustomServletContainerInitializer 可以处理的类,在onStartup 方法中,可以通过Set<Class<?>> c 获取得到。
jar文件中不但可以包含需要自定义注册的servlet,也可以包含应用注解的servlet,具体怎么做,视具体环境而定。
把处理某类事物的servlet组件打包成jar文件,有利于部署和传输,功能不要了,直接去除掉jar即可,方便至极!

ServletContextEvent是当前应用Servlet上下文启动的事件,这个事件可以获得ServletContext,在Tomcat中这个也就是ApplicationContext类,是整个应用的应用上下文的实现类;

 

该类中的属性比较重要:

而该类也提供了一系列的动态add方法:

以addFilter为例,其实也没有那么神秘,该方法还是攒一个filterRegistration,

然后,客户端调用addMappingForUrlPatterns,对StandContext的的filterMap进行修改:

这样在servlet执行的过程中,StandardWrapperValve会首先从这个StandContext的的filterMap,构造filterChain,而这个动态添加的filter在应用初始化后,就肯定包含在这个filterMap中了;

 

总结:

filter实现分为静态和动态,静态就是普通配置在web.xml或者通过@注释配置在类中的,动态是通过Servlet3.0规范以后Dynamic添加的;

无论静态还是动态的,都是通过解析StandardContext中的缓存构造ApplicationFilterConfig,进而生成当前应用请求链路的ApplicationFilterChain,并根据filter的顺序,一个filter一个filter的执行的!

posted on 2017-01-22 14:39  决心觉新  阅读(528)  评论(0编辑  收藏  举报