OncePerRequestFilter
而Spring的OncePerRequestFilter类实际上是一个实现了Filter接口的抽象类。spring对Filter进行了一些封装处理
OncePerRequestFilter,顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢,往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,因此,为了兼容各种不同运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择
源码说明:
public abstract class OncePerRequestFilter extends GenericFilterBean {
//一个标记,后面会用到
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
@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;
//这里获取一个名称,该名称后面会被用于放到request当作key
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...
// 在当前请求里面设置一个标记,key就是前面拼接的那个变量,value是true,这个标记如果在request存在则在前面会被检测到并改变hasAlreadyFilteredAttribute的值
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 这个方法是一个抽象方法需要子类去实现具体的过滤逻辑
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
// 执行完毕之后移除该标记
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
//其余代码略
}
自定义代码案例:
@Component
@Order(-1)
@Slf4j
public class SessionContextFilter extends OncePerRequestFilter implements Ordered {
/**
* 请求拦截器
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Thread thread = Thread.currentThread();
Long userId = request.getHeader("userId") == null ? 0L : Long.valueOf(request.getHeader("userId"));
String userName = request.getHeader("userName") == null ? "admin" : request.getHeader("userName");
ContextHolder.setContext(new OperationVO(userId,userName));
try {
OperationVO operationVO = ContextHolder.getContext();
// 除这行代码外,其他均为自定义业务代码
filterChain.doFilter(request,response);
} finally {
ContextHolder.clearContext();
OperationVO operationVO = ContextHolder.getContext();
}
}
@Override
public int getOrder() {
return 0;
}
}
filterChain.doFilter
实现filterchain的dofilter方法采用 责任链设计模式,把自身接收到的请求request对象和response对象和自身对象即filterchain
作为下一个过滤器的dofilter的三个形参传递过去,这样才能使得过滤器传递下去,当然这个方法中还存在一些判断if等机制
用来判断现在的这个过滤器是不是最后一个,是的话就可以把请求和响应对象传递给浏览器请求的页面
第一个疑问是该过滤器链里面的过滤器源于哪里?
答案是该类里面包含了一个ApplicationFilterConfig对象,而该对象则是个filter容器
当web容器启动是ApplicationFilterConfig自动实例化,它会从该web工程的web.xml文件中读取配置的filter信息,然后装进该容器
下个疑问是它如何执行该过滤器容器里面的filter呢?
答案是通过pos它来标识当前ApplicationFilterChain(当前过滤器链)执行到哪个过滤器
ApplicationFilterChain采用责任链设计模式达到对不同过滤器的执行
首先ApplicationFilterChain 会调用它重写FilterChain的doFilter方法,然后doFilter里面会调用
internalDoFilter(request,response)方法;该方法使过滤器容器拿到每个过滤器,然后调用它们重写Filter接口里面的dofilter方法
以下是ApplicationFilterChain 里面重写FilterChain里面的doFilter方法的描述
/**
* Invoke the next filter in this chain, passing the specified request
* and response. If there are no more filters in this chain, invoke
* the <code>service()</code> method of the servlet itself.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception occurs
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response)
以下是internalDoFilter的部分代码
// Call the next filter if there is one
if (pos < n) {
//先拿到下个过滤器,将指针向下移动一位
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
//获取当前指向的filter实例
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
filter, request, response);
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege
("doFilter", filter, classType, args, principal);
} else {
//filter调用doFilter(request, response, this)方法
//ApplicationFilterChain里面的filter都实现了filter
filter.doFilter(request, response, this);
}
}
}
以下是Filter接口doFilter定义如下
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
过滤器链里面的filter在调用dofilter完成后,会继续调用chain.doFilter(request,response)方法,而这个chain其实就是applicationfilterchain,所以调用过程又回到了上面调用dofilter和调用internalDoFilter方法,这样执行直到里面的过滤器全部执行
当filte都调用完成后,它就会初始化相应的servlet,(例如jsp资源,默认它会开启一个 JspServlet对象)
// We fell off the end of the chain -- call the servlet instance
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
servlet, request, response);