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()
该方法主要完成两个功能:(下面代码)
- 找到被***类在spring中配置的
id
并赋值给targetBeanName
。 - 使用找到的
id
从spring
容器中找到具体被***的类,并赋值给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
方法,
执行 被***filter
的 doFilter
方法,具体实现,请看下面源码:
@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);
}