责任链模式 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 方法,这之后就是我们上面说的过滤器链的执行逻辑。