https://www.cnblogs.com/zhanhaitao/p/7857245.html

初衷

DelegatingFilterProxy 就是一个对于servlet filter的***

  • <mark>用这个类的好处主要是通过Spring容器来管理servlet filter的生命周期</mark>
  • <mark>还有就是如果filter中需要一些Spring容器的实例,可以通过spring直接注入</mark>
  • <mark>另外读取一些配置文件这些便利的操作都可以通过Spring来配置实现</mark>

DelegatingFilterProxy 类存在与 spring-web 包中,其作用就是一个 filter的***

其间接实现了Filter接口,但是在doFilter中其实调用的从Spring 容器中获取到的***Filter的实现类delegate。

有上图我们可以看到, DelegatingFilterProxy 类继承 GenericFilterBean ,间接实现了Filter这个接口,故而该类属于一个过滤器。

那么就会有实现Filter中init、doFilter、destroy三个方法。

***具体实现

# init

首先我们看 init 方法,我们知道 <mark>当filter初始化时会执行init方法</mark>

从源码中我们可以找到具体代码,该方法在GenericFilterBean类中实现,具体功能是:

  • 将该类封装成spring特有形式的类,方便spring维护
  • 并且调用initFilterBean方法,该方法放在子类(DelegatingFilterProxy)实现,该方法主要目的是,找到在spring中维护的目标filter,具体实现看下面代码:
	/** * Standard way of initializing this filter. * Map config parameters onto bean properties of this filter, and * invoke subclass initialization. * @param filterConfig the configuration for this filter * @throws ServletException if bean properties are invalid (or required * properties are missing), or if subclass initialization fails. * @see #initFilterBean */
	@Override
	public final void init(FilterConfig filterConfig) throws ServletException {
		Assert.notNull(filterConfig, "FilterConfig must not be null");

		this.filterConfig = filterConfig;

		!将该类封装成spring特有的bean形式,方便spring维护
				↓
		// 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, env));
				initBeanWrapper(bw);
				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 在子类 (DelegatingFilterProxy) 中实现
		我们可以到DelegatingFilterPoxy中去看看,具体完成了那些工作?
	  1、找到要***bean的id ==> targetBeanName
	  2、在spring,bean容器中找到具体被***的filter ==> delegate
			↓
		// Let subclasses do whatever initialization they like.
		initFilterBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
		}
	}

initFilterBean() 该方法主要完成两个功能:(下面代码)

  1. 找到被***类在spring中配置的 id 并赋值给 targetBeanName
  2. 使用找到的 idspring 容器中找到具体被***的类,并赋值给 delegate
@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) {
			
				!找到要被***的filter在spring中配置的id(name)this.targetBeanName = getFilterName();
			}
			// Fetch Spring root application context and initialize the delegate early,
			// if possible. If the root application context will be started after this
			// filter proxy, we'll have to resort to lazy initialization.
			WebApplicationContext wac = findWebApplicationContext();
			if (wac != null) {

				!找到具体被***的filter
					↓
				this.delegate = initDelegate(wac);
			}
		}
	}
}

getFilterName() 该方法的作用是,获取被***的 filter 在spring中配置的 id (下面)

	/** * Make the name of this filter available to subclasses. * Analogous to GenericServlet's {@code getServletName()}. * <p>Takes the FilterConfig's filter name by default. * If initialized as bean in a Spring application context, * it falls back to the bean name as defined in the bean factory. * @return the filter name, or {@code null} if none available * @see javax.servlet.GenericServlet#getServletName() * @see javax.servlet.FilterConfig#getFilterName() * @see #setBeanName */
	@Nullable
	protected String getFilterName() {
	
		!找到被***filter在spring中配置的id
			↓
		return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
	}

initDelegate() 该方法的作用是,从spring容器中获取到具体被***的 filter

	/** * Initialize the Filter delegate, defined as bean the given Spring * application context. * <p>The default implementation fetches the bean from the application context * and calls the standard {@code Filter.init} method on it, passing * in the FilterConfig of this Filter proxy. * @param wac the root application context * @return the initialized delegate Filter * @throws ServletException if thrown by the Filter * @see #getTargetBeanName() * @see #isTargetFilterLifecycle() * @see #getFilterConfig() * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) */
	protected Filter initDelegate(WebApplicationContext wac) throws ServletException {

		! 取出上面找到的 id
			↓
		String targetBeanName = getTargetBeanName();
		Assert.state(targetBeanName != null, "No target bean name set");
		
		! 找到被***的filter
			↓
		Filter delegate = wac.getBean(targetBeanName, Filter.class);
		if (isTargetFilterLifecycle()) {
			delegate.init(getFilterConfig());
		}
		return delegate;
	}

到这里我们可以看出来,我们要***的 filter 其实就是我们配置 filter 中的 filter-name 标签中的 filterName

<filter-name>filterName</filter-name>

# doFilter

我们在来看看 doFilter 方法具体实现,该方法主要是使用 被***的filter ,并调用 invokeDelegate 方法,
执行 被***filterdoFilter 方法,具体实现,请看下面源码:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

	! 得到被***的filter
		↓
	// Lazily initialize the delegate if necessary.
	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方法
		↓
	// Let the delegate perform the actual doFilter operation.
	invokeDelegate(delegateToUse, request, response, filterChain);
}

invokeDelegate 方法的作用就是执行被***filter的doFilter方法

/** * Actually invoke the delegate Filter with the given request and response. * @param delegate the delegate Filter * @param request the current HTTP request * @param response the current HTTP response * @param filterChain the current FilterChain * @throws ServletException if thrown by the Filter * @throws IOException if thrown by the Filter */
protected void invokeDelegate(
		Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

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

看到这里我相信大家都明白DelegatingFilterPoxy是怎么回事了吧。

下面我们看看spring+shiro是如何运用这个类的

运用( SpringBoot 环境)

需求

在springboot中 使用了filter, 并且注入了业务工具类(APIUtil ),但注入是null

所以问题就来了:springboot中使用filter时注入bean为null的解决办法

分析:其实Spring中,web应用启动的顺序是:

<mark>listener ⇒ filter ⇒ servlet</mark>

即,先初始化listener,然后再来就filter的初始化,再接着才到我们的dispathServlet的初始化

原因

但 <mark>过滤器是servlet规范中定义的,并不归spring容器管理,也无法直接注入spring中的bean(会报错)</mark>

# 解决方案 1

public class TokenAuthFilter implements Filter {
 
    private final static Logger log = LoggerFactory.getLogger(TokenAuthFilter.class);
 
    @Autowired
    private APIUtil apiUtil;
}

新增一个config类,用来手工创建filter的bean, 例如:

@Configuration
public class WebConfig {
 
  @Bean
    public Filter tokenAuthFilter() {
 
        return new TokenAuthFilter();
    }
    /** * 注册filter,统一处理api开头的请求 * @return FilterRegistrationBean */
    @Bean
    public FilterRegistrationBean tokenAuthFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        // DelegatingFilterProxy把servlet 容器中的filter同spring容器中的bean关联起来
        registration.setFilter(new DelegatingFilterProxy("tokenAuthFilter"));
        registration.addUrlPatterns("/api/*");
        registration.setName("tokenAuthFilter");
        registration.setOrder(1);
        return registration;
    }
 
}

# 解决方案 2 (高耦合)

初始化时通过spring上下文获取,进行bean的初始化:

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
    RedisTemplate demoBean = (RedisTemplate)context.getBean("redisTemplate");
    System.out.println(demoBean);
 }