Filter

117 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情

1. Filter

过滤器的配置比较简单,直接实现Filter接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。

以前是在javax.servlet包下

  • init():该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用。
  • doFilter() :容器中的每一次请求都会调用该方法, FilterChain用来调用下一个过滤器 Filter。
  • destroy(): 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次。

在Java中就使用了链式结构。把所有的过滤器都放在FilterChain里边,如果符合条件,就执行下一个过滤器(如果没有过滤器了,就执行目标资源)。

@Component
public class MyFilter implements Filter {
​
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
​
        System.out.println("Filter 前置");
    }
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
​
        System.out.println("Filter 处理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }
​
    @Override
    public void destroy() {
​
        System.out.println("Filter 后置");
    }
}

再在web.xml中配置

<filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.he.filter.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

可以看到,我们所以请求都可以输出doFilter里面我们配置的东西

1.1. 过滤器源码

好了我好奇,Filter在哪定义的呢,简要介绍了SpringMVC中的Filter两个基类GenericFilterBeanOncePerRequestFilter

GenericFilterBean中的init方法,

public final void init(FilterConfig filterConfig) throws ServletException {
   Assert.notNull(filterConfig, "FilterConfig must not be null");
​
   this.filterConfig = filterConfig;
​
   // 从init parameters将属性设置进bean中
   PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         // 从init parameters将属性设置进bean中
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
         Environment env = this.environment;
         if (env == null) {
            env = new StandardServletEnvironment();
         }
         bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
         // 此方法没做任何操作,主要用于扩展时使用为子类实现
         initBeanWrapper(bw);
         // 通过PropertyValue进行设置
         bw.setPropertyValues(pvs, true);
      }
      catch (BeansException ex) {
         String msg = "Failed to set bean properties on filter '" +
            filterConfig.getFilterName() + "': " + ex.getMessage();
         logger.error(msg, ex);
         throw new NestedServletException(msg, ex);
      }
   }
​
   // 子类去实现
   initFilterBean();
​
   if (logger.isDebugEnabled()) {
      logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
   }
}

我们来看一下initFilterBean()方法,只有DelegatingFilterProxy才重写了这个方法:

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) {
            // 父类GenericFilterBean中的getFilterName
            this.targetBeanName = getFilterName();
         }
         // 创建一个ApplicationContext
         WebApplicationContext wac = findWebApplicationContext();
         if (wac != null) {
            // 从spring上下文中获取beanName为this.targetBeanName的bean
            this.delegate = initDelegate(wac);
         }
      }
   }
}

进入initDelegate方法看看

// 给定的上下文中将代理Filter初始化为Bean
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
   String targetBeanName = getTargetBeanName();
   Assert.state(targetBeanName != null, "No target bean name set");
   Filter delegate = wac.getBean(targetBeanName, Filter.class);  // 让spring中的bean与web中的Filter产生联系
   if (isTargetFilterLifecycle()) {
      delegate.init(getFilterConfig());
   }
   return delegate;
}

让spring中的bean与web中的Filter产生联系

自此之后DelegatingFilterProxy中的Filter就是从容器中获取得了,是一个bean,到这里GenericFilterBean中的init方法就执行结束了,接下来就是调用DelegatingFilterProxy中的doFilter了:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
​
   // 这个时候的Filter全是spring容器中的bean了
   Filter delegateToUse = this.delegate;
   if (delegateToUse == null) {
      synchronized (this.delegateMonitor) {
         delegateToUse = this.delegate;
         if (delegateToUse == null) {
            WebApplicationContext wac = findWebApplicationContext();
            if (wac == null) {
               throw new IllegalStateException("No WebApplicationContext found: " +
                     "no ContextLoaderListener or DispatcherServlet registered?");
            }
            delegateToUse = initDelegate(wac);
         }
         this.delegate = delegateToUse;
      }
   }
​
   // 让代理Filter执行实际的doFilter方法
   invokeDelegate(delegateToUse, request, response, filterChain);
}

先前的doFilter只是判断当前代理Filter是否是null,是的话再从bean中取出Filter,接下来才是从spring容器中取出来的Filter执行doFilter的工作了,进入invokeDelegate中:

protected void invokeDelegate(
      Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
​
   delegate.doFilter(request, response, filterChain);
}

将Web体系中的Filter的doFilter()指向一个从spring上下文获取的bean,最终调用的是该bean的doFilter(),以后用的都是这个bean而不是原生Web体系的Filter,也正是因为是一个bean,所以才可以使用@AutoWired注入spring bean。将自己的Filter创建到Spring的上下文中,又能集成到web容器的filterChain上。

放一张别人总结的图

img

这里就是不知道为什么这个类打不了断点,所以都是看别人分析的

1.2. 拦截器和过滤器执行顺序

直接放图

在这里插入图片描述

这是别人的实践博客,我自己就不实践了,单纯结果还是很好理解的

对于拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别止步前行的博客-CSDN博客拦截器和过滤器的区别

HandlerInterceptor和Filter区别

HandlerInterceptorFilter
拦截器是框架中的对象过滤器是servlet中的对象
拦截器是实现HandlerInterceptor过滤器实现Filter接口的对象
拦截器是用来验证请求的,能截断请求过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的
过滤器是在拦截器之前执行的。
拦截器有三个执行时间点过滤器是一个执行时间点
拦截器是侧重拦截对controller的对象。如果你的请求不能被DispatcherServlet接收,这个请求不会执行拦截器内容过滤器可以处理jsp, js, html等等
拦截器拦截普通类方法执行过滤器过滤servlet请求响应