GenericFilterBean与DelegatingFilterProxy

GenericFilterBean与DelegatingFilterProxy

上一篇文章

简要介绍了SpringMVC中的Filter两个基类GenericFilterBean与OncePerRequestFilter以及其他一些常用的Filter,这篇文章详细介绍一下GenericFilterBean与DelegatingFilterProxy的作用和使用场景,

讲这个之前需要先了解一点,从web.xml中初始化的Filter并不属于spring,它是Web体系的Filter,并不是bean,在spring容器中bean初始化之前创建,

Web体系的Filter怎么创建:

通常的做法是实现Filter:

public class MyFilter2 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println(Arrays.toString(filterConfig.getInitParameter("exceptPath")
                .replace(" ", "")
                .replace("   ", "")
                .replace("\n", "")
                .replace("\r\n", "")
                .split(",")));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        //在这里实现自己的过滤规则
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

然后在web.xml中配置,让自定义Filter能够初始化:

    <filter>
        <filter-name>myFilter2</filter-name>
        <filter-class>cn.myllxy.remusic.utils.MyFilter2</filter-class>
        <init-param>
            <!-- 添加一些例外的路径准备让过滤器放行 -->
            <param-name>exceptPath</param-name>
            <param-value>
                /,*.js,*.html,*.css,*.png,*.jpg,*.ico,*.gif,
                *login,*index.html
            </param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>myFilter2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

这是在SSM中使用Filter,在SpringBoot中自定义Filter会更简单:

GenericFilterBean实现了

javax.servlet.Filter、org.springframework.beans.factory.BeanNameAware、org.springframework.context.EnvironmentAware、org.springframework.web.context.ServletContextAware、org.springframework.beans.factory.InitializingBean、org.springframework.beans.factory.DisposableBean

目的为了对接Spring的一些功能,

好了,看源码,

GenericFilterBean#init():

	@Override
	public final void init(FilterConfig filterConfig) throws ServletException {
		Assert.notNull(filterConfig, "FilterConfig must not be null");
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
		}

		this.filterConfig = filterConfig;

		// 从init parameters将属性设置进bean中
		PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
                                // 将GenericFilterBean封装成BeanWrapper 
				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 successfully");
		}
	}

这个init的目的是将filterConfig中的属性通过beanWrapper设置到DelegatingFilterProxy这个bean中,

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

	@Override
	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);
				}
			}
		}
	}

让spring中的bean与web中的Filter产生联系,Filter delegate = wac.getBean(getTargetBeanName(),Filter.class):

	/**
	 * 在给定的上下文中将代理Filter初始化为Bean
	 */
	protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
		Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

自此之后DelegatingFilterProxy中的Filter就是从容器中获取得了,是一个bean,

到这里GenericFilterBean中的init方法就执行结束了,接下来就是调用DelegatingFilterProxy中的doFilter了:

	@Override
	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) {
				if (this.delegate == null) {
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: " +
								"no ContextLoaderListener or DispatcherServlet registered?");
					}
					this.delegate = initDelegate(wac);
				}
				delegateToUse = this.delegate;
			}
		}

		// 让代理Filter执行实际的doFilter方法
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

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

	protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		delegate.doFilter(request, response, filterChain);
	}

DelegatingFilterProxy的作用介绍完了,那么要怎么启用它呢:

    <filter>
        <filter-name>delegatingFilterProxy</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>myFilter</param-value>
        </init-param>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>delegatingFilterProxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

做到这一步,我的自定义Filter中就可以注入spring容器中的bean了,而在SpringBoot中直接在Filter上加上@Component,其他什么都不用管就可以实现了

注意这个targetFilterLifecycle默认是false,如果是true,代表会执行Filter的init方法,如果你的Filter是实现的顶层接口Filter,那么就必须实现init和destroy,

如果继承自GenericFilterBean,那么就不用,因为GenericFilterBean实现了init和destroy

看到这里,上篇文章中关于DelegatingFilterProxy的作用简介是不是就弄明白了:

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

编辑于 2021-12-22 14:23