SpringMVC - 对于如何配置 Filter 的深度剖析

概述

FilterServlet 提供支持的,用于 Web 环境,并不属于 Spring,所以 Sping 需要对 Filter 做一些处理,使之成为受 Spring 管理的 Bean,来融入 IoC 容器 中。

引入 spring-web 依赖:

<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-webartifactId>
  <version>*.*.*version>
dependency>

Spring使用版本:5.3.7) 中对 Filter 的实现在 org.springframework.web.filter 包中:

SpringMVC - 对于如何配置 Filter 的深度剖析_第1张图片

在这里插入图片描述

可以看到两个主要的抽象类 GenericFilterBeanOncePerRequestFilter,以及一些常见的 Filter 如:CorsFilterCharacterEncodingFilter

GenericFilterBean

GenericFilterBean 是一个抽象类,用于提供 FilterSpring IoC 容器 中接入的通用处理。

public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
		EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {

	...

	@Override
	public void destroy() {
	}

	@Override
	public final void init(FilterConfig filterConfig) throws ServletException {
		Assert.notNull(filterConfig, "FilterConfig must not be null");

		this.filterConfig = filterConfig;

		// Set bean properties from init parameters.
		PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
                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, (PropertyResolver)env));
                this.initBeanWrapper(bw);
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var6) {
                ...
            }
		}

		// Let subclasses do whatever initialization they like.
		initFilterBean();

		...
	}

	protected void initFilterBean() throws ServletException {
	}
	
	...
}

从源码中可以看到:GenericFilterBean 实现了 Filterinit()destroy() 方法,doFilter() 方法交给子类实现;其中 init() 方法主要是将用户自定义的一些配置(FilterConfig:用来配置 Filternameparam 等)映射到 BeanWrapper 中,然后调用 initFilterBean() 方法,这个方法也交给子类实现,目前实现这个方法的只有 DelegatingFilterProxy


接下来说一下继承自 GenericFilterBean 的两个重要的类 OncePerRequestFilterDelegatingFilterProxy

1.1、OncePerRequestFilter

public abstract class OncePerRequestFilter extends GenericFilterBean {
    public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";

    public OncePerRequestFilter() {
    }

    public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            HttpServletRequest httpRequest = (HttpServletRequest)request;
            HttpServletResponse httpResponse = (HttpServletResponse)response;
            String alreadyFilteredAttributeName = this.getAlreadyFilteredAttributeName();
            boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
            if (!this.skipDispatch(httpRequest) && !this.shouldNotFilter(httpRequest)) {
                if (hasAlreadyFilteredAttribute) {
                    if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
                        this.doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
                        return;
                    }

                    filterChain.doFilter(request, response);
                } else {
                    request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);

                    try {
                        this.doFilterInternal(httpRequest, httpResponse, filterChain);
                    } finally {
                        request.removeAttribute(alreadyFilteredAttributeName);
                    }
                }
            } else {
                filterChain.doFilter(request, response);
            }

        } else {
            throw new ServletException("OncePerRequestFilter just supports HTTP requests");
        }
    }

    protected abstract void doFilterInternal(HttpServletRequest var1, HttpServletResponse var2, FilterChain var3) throws ServletException, IOException;
}

源码注释中有这么一段话:

在这里插入图片描述

OncePerRequestFilter 是一个 Filter 基类,保证了每个请求只执行一次!!!,防止内部请求进行转发或重定向时再次执行这个过滤器(在 SpringSecurity 过滤器链中用的比较多)。

1.2、DelegatingFilterProxy

我们知道 FilterServlet 提供的组件,可以脱离 Spring 单独使用,但是我们一般是在 SpringMVC 环境下进行 Web 开发,所以 Spring 提供了 DelegatingFilterProxy 类将 Web 体系中的 FilterdoFilter() 指向一个从 spring 上下文获取的 bean,最终调用的是该 beandoFilter(),以后用的都是这个 bean 而不是原生 Web 体系的 Filter,也正是因为是一个 bean,所以才可以使用 @AutoWired 注入 spring bean。将我们自定义的 Filter 创建到 Spring 的上下文中,又能集成到 web 容器的 filterChain 上。

public class DelegatingFilterProxy extends GenericFilterBean {
	...
    @Nullable
    private WebApplicationContext webApplicationContext;
    @Nullable
    private String targetBeanName;
	...

    public DelegatingFilterProxy(String targetBeanName) {
        this(targetBeanName, (WebApplicationContext)null);
    }

    ...

    protected void initFilterBean() throws ServletException {
        synchronized(this.delegateMonitor) {
            if (this.delegate == null) {
                if (this.targetBeanName == null) {
                    this.targetBeanName = this.getFilterName();
                }

                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
                    this.delegate = this.initDelegate(wac);
                }
            }

        }
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
            synchronized(this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }

                    delegateToUse = this.initDelegate(wac);
                }

                this.delegate = delegateToUse;
            }
        }
		// 执行实际的 Filter 的 doFilter() 方法
        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }

    @Nullable
    protected WebApplicationContext findWebApplicationContext() {
    	// 获取 WebApplicationContext
        ...
    }

	// 从 Spring IoC 容器中获取名称为 targetBeanName,类型为 Filter 的 Bean
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
        String targetBeanName = this.getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
        if (this.isTargetFilterLifecycle()) {
        	// 这里的 targetFilterLifecycle 默认是 false,如果是 true,这里会执行 Filter 的 init 方法,如果我们自定义的 Filter 是实现的顶层接口 Filter,那么必须实现 init 和 destroy
        	// 如果是继承了 GenericFilterBean,那么就不用,因为 GenericFilterBean 实现了 init 和 destroy
            delegate.init(this.getFilterConfig());
        }

        return delegate;
    }
	...
}

具体使用方法见下面 拓展 中实际场景

牛刀小试

SpringMVC 环境下配置 Filter 有两种方式:

关于 Servlet 3.0,如果不了解可以先阅读 Java - servlet 3.0 这篇文章

1、使用 Servlet 3.0 之后提供的 @WebFilter 注解(Servlet 3.0 之前也可以通过 web.xml 配置的方式)

@WebFilter(filterName = "vloggerFilter", urlPatterns = "/*")
public class LoggerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init loggerFilter...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("loggerFilter before doFilter");
        chain.doFilter(request, response);
        System.out.println("loggerFilter after doFilter");
    }

    @Override
    public void destroy() {
        System.out.println("destroy loggerFilter...");
    }
}

servlet 容器启动之后,会扫描类路径下标注了 @WebFilter 注解的 Filter 并初始化。执行请求时,在匹配到指定的路径规则后执行 doFilter 方法

2、使用 Servlet 3.0 之后提供的插件化能力:定义一个配置类实现 WebApplicationInitializer 获取到 ServletContext 来注册 Filter、定义一个配置类继承 AbstractAnnotationConfigDispatcherServletInitializer 实现层级上下文

关于 SpringMVC 集成 Servlet 3.0,如果不了解可以先阅读 SpringMVC - 以 Servlet 3.0 的方式搭建 SSM 框架 这篇文章

首先定义一个 LoggerFilter

public class LoggerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        
    }
}
  • 2.1、通过实现 WebApplicationInitializer,重写 onStartUp() 方法,使用 servletContextFilterRegistration 来注册 Filter
@Configuration
public class MyConfig implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        FilterRegistration.Dynamic myFilter = servletContext.addFilter("myFilter", LoggerFilter.class);
        myFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
    }
}
  • 2.2、继承 AbstractAnnotationConfigDispatcherServletInitializer 后重写 getServletFilters() 方法来注册 Filter
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebMvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        LoggerFilter loggerFilter = new LoggerFilter();

        return new Filter[]{
                loggerFilter
        };
    }
}

拓展

问题描述

SpringMVC 环境下在自定义 Filter 中无法注入 Bean 的问题,因为 Filter 的初始化是在 Spring IoC 容器 初始化之前创建的,比如上面的 loggerFilter 中注入一个 Bean

@WebFilter(filterName = "loggerFilter", urlPatterns = "/")
public class LoggerFilter implements Filter {

    @Autowired
    private JdbcConsts jdbcConsts;	// null

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init loggerFilter...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("loggerFilter before doFilter");
        System.out.println(jdbcConsts);
        chain.doFilter(request, response);
        System.out.println("loggerFilter after doFilter");
    }

    @Override
    public void destroy() {
        System.out.println("destroy loggerFilter...");
    }
}

解决方案

使用 @WebFilter 配置的方式无法解决这个问题,所以在 SpringMVC 环境中如果要注入 Bean,就只能使用上面第二种方式:

// 必须加入到 IoC 容器中
@Component
// 不能使用 @WebFilter 注解
//@WebFilter(filterName = "loggerFilter", urlPatterns = "/")
public class LoggerFilter implements Filter {

    @Autowired
    private JdbcConsts jdbcConsts;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init loggerFilter...");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("loggerFilter before doFilter");
        System.out.println(jdbcConsts);
        chain.doFilter(request, response);
        System.out.println("loggerFilter after doFilter");
    }

    @Override
    public void destroy() {
        System.out.println("destroy loggerFilter...");
    }
}

使用 DelegatingFilterProxy 解决问题:

@Override
protected Filter[] getServletFilters() {
    DelegatingFilterProxy delegatingFilterProxy = new DelegatingFilterProxy();
    // 这里的 targerBeanName 就是上面 Filter 在 Ioc 容器中的 名称
    delegatingFilterProxy.setTargetBeanName("loggerFilter");
    delegatingFilterProxy.setTargetFilterLifecycle(true);

    

    return new Filter[]{
            delegatingFilterProxy
    };
}

你可能感兴趣的:(spring,学习,servlet,java,spring,Filter,SpringMVC)