Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter
通过Filter技术,可以实现在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。
分析源码主要是主要目的是什么?了解FilterChain的创建、在代码里具体是怎么调用Servlet、filter是如何通过doFilter()这个方法实现递归的调用
springboot的相关依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
在解析之前先看两张图,了解filter的执行时机
添加filter之前:
添加filter之后:
要说明一下的是过滤链不是责任链的设计模式,因为一个request可以被链条上的多个filter处理。但是,对于责任链来说,一个request只能被链条中的一个handler处理。
1、过滤链的创建
主要看这个方法org.apache.catalina.core.ApplicationFilterFactory#createFilterChain
return null;
} else {
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
Request req = (Request)request;
if (Globals.IS_SECURITY_ENABLED) {
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain)req.getFilterChain();
if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else {
filterChain = new ApplicationFilterChain();
}
//上面执行的语句都是为创建一个空的FilterChain对象
filterChain.setServlet(servlet);//将被调用的servlet设置进去
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
StandardContext context = (StandardContext)wrapper.getParent();
FilterMap[] filterMaps = context.findFilterMaps();//获取所有的filter的映射对象,保存的是各个filter的元数据信息
if (filterMaps != null && filterMaps.length != 0) {
......
FilterMap filterMap;
ApplicationFilterConfig filterConfig;//这个对象中保存者filter的bean引用,是要被filterChain所引用
for(var12 = 0; var12 < var11; ++var12) {
filterMap = var10[var12];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {//请求路径和filter设置的路径匹配模式进行匹配
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) {
filterChain.addFilter(filterConfig);
}
}
}
......
for(var12 = 0; var12 < var11; ++var12) {
filterMap = var10[var12];
if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {//执行的servlet和filter的selvetNames进行匹配
filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
if (filterConfig != null) {
filterChain.addFilter(filterConfig);
}
}
}
return filterChain;//filterChain创建完成返回
} else {
return filterChain;
}
}
}
2、接下来看如何去调用执行的servlet和形成多个filter形成套娃式的递归调用的
org/apache/catalina/core/StandardWrapperValve.class:153 这个是filterChain的调用入口了
其主要执行的话是下面这个方法
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.pos < this.n) {//filtechain就保存filterConfig的长度-》n和当前执行到的filter下标-》post
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
//下面两种方式来调用filter重写的filter方法,参数就是filterChian、request和response,然后在filter我们又调用了filterChain的dofilter(),所以就形成了这么个套娃式的递归调用
if (Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (ServletException | RuntimeException | IOException var15) {
throw var15;
} catch (Throwable var16) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
} else {
try {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !this.servletSupportsAsync) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
Principal principal = ((HttpServletRequest)request).getUserPrincipal();
Object[] args = new Object[]{request, response};
SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
} else {
this.servlet.service(request, response);//当所有filter都被调用之后就执行了servlet,servlet之后就逐渐的递归返回了
}
} catch (ServletException | RuntimeException | IOException var17) {
throw var17;
} catch (Throwable var18) {
Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set((Object)null);
lastServicedResponse.set((Object)null);
}
}
}
}
整个请求中过滤链的组装和调用过程就是这样了。
进行springboot整合过滤器的过程中可能会遇到过滤器执行两次的问题,针对这个问题可能出现的一种原因就是因为在请求执行完之后浏览器会再发一次请求.ico的请求,具体内容可以参考: