filter在Tomcat的实现(转)
from https://sanwen8.cn/p/45268RX.html
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的执行的!