责任链模式 Tomcat 采用“责任链模式”设计了过滤器链。该模式的含义是,一个请求进入后会经过一系列处理逻辑,这些处理逻辑可以分别写在不同的地方,每个逻辑处理完后将请求传给下一个处理逻辑。
这样做的好处是:处理逻辑之间解耦,每个逻辑只管做好自己的事,不需要知道自己的前后的处理逻辑;可以快速在这个逻辑链条中加入新的逻辑,不影响原有的逻辑和执行顺序。
过滤器链的简单实现 先定义一个过滤器接口:
1 2 3 4 5 6 public  interface  Filter   {    void  doFilter (Request request, Response response, FilterChain filterChain)  ; } 
 
其中的 Request、Response 是对HTTP请求、响应的模拟:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public  class  Request   {    private  String requestStr;     public  String getRequestStr ()   {         return  requestStr;     }     public  void  setRequestStr (String requestStr)   {         this .requestStr = requestStr;     } } public  class  Response   {    private  String responseStr;     public  String getResponseStr ()   {         return  responseStr;     }     public  void  setResponseStr (String responseStr)   {         this .responseStr = responseStr;     } } 
 
最重要的 FilterChain 实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public  class  FilterChain  implements  Filter   {    private  List<Filter> list = new  ArrayList<>();     private  int  index = 0 ;     @Override      public  void  doFilter (Request request, Response response, FilterChain filterChain)   {         if  (index == list.size()) {             return ;         }         Filter f = list.get(index);         index++;         f.doFilter(request, response, filterChain);     }     public  void  addFilter (Filter filter)   {         list.add(filter);     } } 
 
index初始化为0,此过滤器链的 doFilter 方法第一次被调用时,取出了过滤器链 list 中第一个过滤器,index 自增1,然后执行第一个过滤器的 doFilter 方法,过滤器链对象自身作为方法参数。此过滤器链的 doFilter 方法第二次被调用时,因为 index 已经自增,所以从 list 中取到的是第二个过滤器,也就顺理成章地执行第二个过滤器的 doFilter 方法。当所有过滤器执行完后,
其中一个过滤器实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 public  class  FilterA  implements  Filter   {    @Override      public  void  doFilter (Request request, Response response, FilterChain filterChain)   {         String requestStr = request.getRequestStr();         System.out.println("==============================请求经过FilterA" );         filterChain.doFilter(request, response, filterChain);         System.out.println("==============================响应经过FilterA" );         if  (response.getResponseStr() == null ) {             response.setResponseStr(requestStr);         }         response.setResponseStr(response.getResponseStr() + "-FA" );     } } 
 
下面结合过滤器 FilterA 的实现和过滤器链 FilterChain 的实现来理解过滤器链工作的原理。
因为要遍历所有过滤器,所以请求和响应对象首先进入filterChain.doFilter(request, response, filterChain)方法,参数包括 filterChain 对象自身。
因为index初始化为0,此 filterChain 对象的 doFilter 方法第一次被调用时,取出了过滤器链 list 中第一个过滤器,index 自增1,然后执行第一个过滤器的 doFilter 方法,filterChain 对象自身作为方法参数。
在过滤器 filterA.doFilter() 方法中,处理完请求后就调用 filterChain.doFilter() 方法,所以 filterChain.doFilter() 方法可以看作是一个递归方法。
在 filterChain.doFilter() 方法中,每次调用就取 index 下标对应的过滤器,调用过滤器的 doFilter 方法之前先将 index 自增1。下次进入这个方法时,取出的过滤器就是下一个过滤器。当过滤器全部执行完,即 index == list.size() 时就 return。
因为除了第一次调用,后续调用 filterChain.doFilter() 方法都是在每个过滤器的 doFilter 方法内,所以从 filterChain.doFilter() 方法 return 后就回到过滤器的 doFilter 方法,所以过滤器中的后续处理响应的逻辑会被执行,过滤器的 doFilter 执行完 return 又到了 filterChain.doFilter() 方法。以此类推,每个过滤器处理响应的逻辑都会被执行。
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  class  Main   {    public  static  void  main (String[] args)   {         Request request = new  Request();         request.setRequestStr("请求内容" );         Response response = new  Response();         FilterChain filterChain = new  FilterChain();         filterChain.addFilter(new  FilterA());         filterChain.addFilter(new  FilterB());         filterChain.doFilter(request, response, filterChain);         System.out.println("最终响应:"  + response.getResponseStr());     } } 
 
输出内容:
1 2 3 4 5 ==============================请求经过FilterA ==============================请求经过FilterB ==============================响应经过FilterB ==============================响应经过FilterA 最终响应:请求内容-FB-FA 
 
以上是对过滤器链的简单实现,与 Tomcat 的过滤器原理大概类似。Toomcat 是分别定义了一个 FilterChain 接口和 Filter 接口,FilterChain 接口 Tomcat自己实现了,我们自定义过滤器是实现 Filter 接口。
ApplicationFilterChain ApplicationFilterChain是 Tomcat 实现的 FilterChain,而且 Tomcat 只有这一个实现类。
ApplicationFilterChain 中保存过滤器使用的是 ApplicationFilterConfig 数组。ApplicationFilterConfig类中的成员就是Filter,源码如下:
1 2 3 4 5 6 7 public  final  class  ApplicationFilterChain  implements  FilterChain   {	private  ApplicationFilterConfig[] filters = new  ApplicationFilterConfig[0 ]; } public  final  class  ApplicationFilterConfig  implements  FilterConfig , Serializable   {	private  transient  Filter filter = null ; } 
 
ApplicationFilterChain 的 doFilter 方法调用到自己的 internalDoFilter 方法,在这个方法内,可以看到我们上一节实现的逻辑:把过滤器下标+1,取过滤器,调用过滤器doFilter方法,入参为请求+响应+过滤器链自身。并且,在这一系列操作前先判断过滤器下标,即是否执行完所有过滤器了,如果执行完了,调用 servlet.service() 方法。servlet 方法返回后,整个过程就结束了。
ApplicationFilterChain 的 internalDoFilter 方法部分源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private  void  internalDoFilter (ServletRequest request, 							  ServletResponse response) 	throws  IOException, ServletException  {	if  (pos < n) { 		ApplicationFilterConfig filterConfig = filters[pos++];  		try  { 			Filter filter = filterConfig.getFilter(); 			 			filter.doFilter(request, response, this ); 			 			 		} catch  (IOException | ServletException | RuntimeException e) { 			throw  e; 		} catch  (Throwable e) { 			e = ExceptionUtils.unwrapInvocationTargetException(e); 			ExceptionUtils.handleThrowable(e); 			throw  new  ServletException(sm.getString("filterChain.filter" ), e); 		} 		return ;  	} 	 	 	servlet.service(request, response); 	 	 } 
 
配置 Tomcat 过滤器用的是 web.xml 配置文件,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32       <filter >           <filter-name > encodingFilter</filter-name >           <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class >           <async-supported > true</async-supported >           <init-param >               <param-name > encoding</param-name >               <param-value > UTF-8</param-value >           </init-param >       </filter >       <filter-mapping >           <filter-name > encodingFilter</filter-name >           <url-pattern > /*</url-pattern >       </filter-mapping >  	 <filter > 	<filter-name > LoginFilter</filter-name >  	<filter-class > com.imooc.filter.LoginFilter</filter-class >  	<init-param >  		<param-name > noLoginPaths</param-name >  		<param-value > login.jsp;fail.jsp;Login</param-value > 不会被过滤掉的页面 	</init-param >  	<init-param >  		<param-name > charset</param-name >  		<param-value > UTF-8</param-value >  	</init-param >  </filter > <filter-mapping > 	<filter-name > LoginFilter</filter-name >  	<url-pattern > /*</url-pattern >  </filter-mapping > 
 
这些过滤器的执行顺序遵守以下规则:
以url-pattern方式配置的filter运行时肯定先于以servlet-name方式配置的filter  
以url-partern方式配置的filter中,如果有多个与当前请求匹配,则按web.xml中filter-mapping出现的顺序来运行  
 
SpringBoot对Tomcat过滤器的自动配置 如果项目是 Servlet+JSP 或 SpringMVC,就需要手动在 web.xml 中配置过滤器,但在 SpringBoot 实现起来就简单了,我们只需要实现将过滤器类像下面这样注册为 Bean,SpringBoot 就会帮我们自动配置。
这里的 Tomcat 版本是9.0.35
 
自定义过滤器示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Component @WebFilter (urlPatterns = {"/*" }) public  class  CustomFilter  implements  Filter   {	@Override  	public  void  init (FilterConfig filterConfig)  throws  ServletException  { 		System.out.println("==========CustomFilter===============init" ); 	} 	@Override  	public  void  doFilter (ServletRequest request, ServletResponse response, FilterChain chain)  			throws  IOException, ServletException  {		HttpServletRequest httpReq = (HttpServletRequest) request; 		System.out.println("==========CustomFilter===============doFilter" ); 		chain.doFilter(request, response); 	} 	@Override  	public  void  destroy ()   { 		 	} } 
 
项目启动后,调试一下 FilterChain 对象的中的过滤器,会看到:
1 2 3 4 5 0 = {ApplicationFilterConfig@5065} "ApplicationFilterConfig[name=characterEncodingFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter]" 1 = {ApplicationFilterConfig@5066} "ApplicationFilterConfig[name=formContentFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedFormContentFilter]" 2 = {ApplicationFilterConfig@5067} "ApplicationFilterConfig[name=requestContextFilter, filterClass=org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter]" 3 = {ApplicationFilterConfig@5068} "ApplicationFilterConfig[name=customFilter, filterClass=com.example.demo.config.CustomFilter]" 4 = {ApplicationFilterConfig@5069} "ApplicationFilterConfig[name=Tomcat WebSocket (JSR356) Filter, filterClass=org.apache.tomcat.websocket.server.WsFilter]" 
 
这里的原理简单说是,容器启动时,会从 beanFactory=DefaultListableBeanFactory 中取出所有实现了javax.servlet.Filter接口的 Bean(除了我们自定义的过滤器,其他是 SpringBoot 提供的),进行一个升序 排序,序号来自于它们使用的@Order注解或PriorityOrdered接口。如果没有使用,序号就是 Integer.MAX。上面的前3个过滤器的序号都是负数,所以位置靠前。
这部分逻辑在下面这个方法(在ServletContextInitializerBeans类):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private  <T> List<Entry<String, T>> getOrderedBeansOfType(ListableBeanFactory beanFactory, Class<T> type,		Set<?> excludes) { 	String[] names = beanFactory.getBeanNamesForType(type, true , false );  	Map<String, T> map = new  LinkedHashMap<>(); 	for  (String name : names) { 		if  (!excludes.contains(name) && !ScopedProxyUtils.isScopedTarget(name)) { 			T bean = beanFactory.getBean(name, type);  			if  (!excludes.contains(bean)) { 				map.put(name, bean); 			} 		} 	} 	List<Entry<String, T>> beans = new  ArrayList<>(map.entrySet()); 	beans.sort((o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getValue(), o2.getValue()));  	return  beans; } 
 
过滤器链 ApplicationFilterChain 是在每次请求来时被创建的,负责创建工作的是 ApplicationFilterFactory.createFilterChain() 方法。在这个方法中,遍历StandardContext的成员Map<String, ApplicationFilterConfig> filterConfigs(其实是遍历 StandardContext 的成员 filterMap,这个 Map 中保存了所有 filterName,再根据每个 filterName 从 filterConfigs 取 ApplicationFilterConfig 对象),把这个成员内的所有 ApplicationFilterConfig 对象添加到 ApplicationFilterChain 中。而 StandardContext 的成员 filterConfigs 的元素也是来自于上面 SpringBoot 容器中的过滤器 Bean。
创建好 ApplicationFilterChain 之后,就是调用它的 doFilter 方法,这之后就是我们上面说的过滤器链的执行逻辑。