DelegatingFilterProxy
这是一个filter的代理类,什么意思呢? 我们知道:当我们实现Filter接口并配置到web.xml中这个filter就会生效,但是在这个filter中我们是无法使用自动注入功能的,因为这个filter并没有被spring管理,那么如果我们想实现一个自定义的filter并且可以进行自动注入spring管理的bean那该多好,spring给我们提供了DelegatingFilterProxy类,注意这是一个过滤器,实现了Filter接口,怎么使用呢?
- 我们需要在web.xml文件中配置delegatingFilterProxy过滤器
<filter>
<filter-name>myDelegatingFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!--这个参数的意思是:是否执行被代理的filter的声明周期函数(init,destory)-->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>myDelegatingFilter</filter-name>
<servlet-name>springmvc</servlet-name>
</filter-mapping>
注意上面的filter-name需要设置为我们接下来自定义filter的bean的名字,或者也可以配置targetBeanName init-param参数指定被代理filter的名字
2. 自定义我们的filter,也就是需要被代理的filter
package com.fuyi.learn.springmvc.filter;
import com.fuyi.learn.springmvc.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.*;
import java.io.IOException;
@Component
public class MyDelegatingFilter extends GenericFilterBean {
@Autowired
IUserService userService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyDelegatingFilter:");
userService.sayHello();
filterChain.doFilter(servletRequest,servletResponse);
}
}
这个filter不需要配置到web.xml
实现原理分析
在DelegatingFilterProxy中,
- 当第一次执行到该代理filter的doFilter方法时,会先调用findWebApplicationContext()方法查找WebApplicationContext, 该方法内部是调用WebApplicationContextUtils.findWebApplicationContext(getServletContext())查找的
doFilter代码:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 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;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
- 查找到WebApplicationContext之后,调用initDelegate()方法,该方法内部时从WebApplicationContext容器中查找我们自定义的filter(我们已经交给spring管理),拿到我们自定义的filter之后,赋值给该代理filter的delegateToUse属性中,以便下次执行不需要再从spring容器中获取,
- 从spring容器中拿到我们定义的filter bean之后就直接调用invokeDelegate()方法,内部就调用了我们filter的doFilter方法,
invokeDelegate方法:
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
这样就实现了我们自定义filter中实现spring的自动注入功能,总结就是
通过spring提供的代理filter来代理 我们自己的filter,然后在代理filter(DelegatingFilterProxy)中执行我们自定义的filter(已经交给spring容器管理)
GenericFilterBean
springmvc提供的过滤器的简单基本实现,它将其过滤器的配置参数(web.xml中过滤器的init-param指定的参数)作为bean属性。
实现原理分析:
- GenericFilterBean实现了Filter接口的init方法,在init方法中读取我们配置的init-param参数并设置到我们自定义bean的setter方法
- 之后会调用initFilterBean(),这个方法可以自类去实现,写一些自己的想做的事情
init()方法:
@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, 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);
}
}
// Let subclasses do whatever initialization they like.
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
}
}
OncePerRequestFilter
该过滤器能够使request在单次请求中只过滤一次,什么意思呢?我们知道过滤器可以配置拦截多个请求路径的,假如我们的一次请求经过了该过滤器然后到达servlet,但是servlet又进行了转发,这时转发的路径又要被该过滤器过滤,那么我们的过滤器就会在用户的一次请求中执行两次,有时这并不是我们想要的。这时我们就可以继承OncePerRequestFilter过滤器并实现doFilterInternal方法,在这个方法里写我们自己的连接器的处理逻辑。
我们以一个请求为例,来进行原理分析:
- 首先我们的请求第一次进入OncePerRequestFilter的doFilter方法,在doFilter方法中会调用request.setAttribute()方法向请求中设置一个标识属性,下面代码
:request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
标识我们的请求已经执行过一次,然后执行我们的doFilterInternal方法,第一次请求就继续往后执行 - 当请求被后续的servlet转发过来,那么又会经过我们的filter,这时doFilter方法调用下面代码判断请求中有没有设置alreadyFilteredAttributeName标识,其实每次请求都会调用下面的代码判断,只是第一次请求的时候request中没有该字段罢了,相关代码:
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
这时从request中获取到了该标识,那就表明我们这次request已经执行了一次了,就会直接调用filterChain.doFilter(request, response);方法继续往后执行,不会调用我们的doFilterInternal方法
其实这里逻辑很简单看下源码就知道了,附上doFilter方法
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else if (hasAlreadyFilteredAttribute) {
if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
return;
}
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else {
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}