实例
实现一个简单的过滤器只需要两步
1,实现Filter接口写一个过滤器实现类
public class DemoFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("[DemoFilter-before]doFilter");
chain.doFilter(request, response);
System.out.println("[DemoFilter-after]doFilter");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("[DemoFilter-before]init");
}
@Override
public void destroy() {
System.out.println("[DemoFilter-before]destroy");
}
}
2,web.xml文件中新增相关filter配置
<filter>
<filter-name>DemoFilter</filter-name>
<filter-class>com.ryan.springtest.filter.DemoFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>DemoFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
输出
此时启动tomcat访问任一url,即可看到相应的输出信息
[DemoFilter-before]doFilter
[DemoFilter-after]doFilter
注:filterChain为过滤器链,表示执行完这个过滤器之后接着执行下一个过滤器
原理
过滤器的具体实现依赖于容器,本文的源码分析是基于Tomcat的实现。
要完成过滤器的实现,Tomcat首先需要加载我们定义的过滤器,接着针对每一次请求找到对应的过滤器,最后是执行过滤器中的doFilter,触发过滤器链的执行,下面将按照这个逻辑对源码进行简单的分析。
过滤器加载
过滤器的加载是在Tomcat启动的时候完成的,Tomcat启动的时候,会加载web.xml中的配置信息,filter的加载具体是在ContextConfig类的configureContext方法中,关键代码如下
for (FilterDef filter : webxml.getFilters().values()) {
if (filter.getAsyncSupported() == null) {
filter.setAsyncSupported("false");
}
context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
context.addFilterMap(filterMap);
}
此时分别加载filter和filterMap相关信息,并保存在上下文环境中
加载完相关配置信息后,还需对具体的filter进行初始化,这一步在StandardContext类的startInternal方法中完成,关键代码如下
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
public boolean filterStart() {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Starting filters");
}
// Instantiate and record a FilterConfig for each defined filter
boolean ok = true;
synchronized (filterConfigs) {
filterConfigs.clear();
for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
String name = entry.getKey();
if (getLogger().isDebugEnabled()) {
getLogger().debug(" Starting filter '" + name + "'");
}
try {
ApplicationFilterConfig filterConfig =
new ApplicationFilterConfig(this, entry.getValue());
filterConfigs.put(name, filterConfig);
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
getLogger().error(sm.getString(
"standardContext.filterStart", name), t);
ok = false;
}
}
}
return ok;
}
遍历刚刚从web.xml解析出来的filter配置信息,并调用ApplicationFilterConfig构造方法进行初始化,保存在filterConfigs中并存到上下文环境中。
过滤器链生成
当请求进入tomcat的时候,会被匹配的过滤器过滤,多个匹配的过滤器组成一个过滤器链,并按照我们在web.xml中定义的filter-mapping的顺序执行。
被tomcat处理的请求,最终会被StandardWrapperValve类的invoke方法处理,对应的过滤器链也是在此时生成的,关键代码如下
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
@SuppressWarnings("deprecation")
public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) {
ApplicationFilterChain filterChain = null;
if (request instanceof Request) {
//略
} else {
// Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setSupport(((StandardWrapper)wrapper).getInstanceSupport());
// Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps();
// If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0))
return (filterChain);
// Acquire the information we will need to match filter mappings
String servletName = wrapper.getName();
// Add the relevant path-mapped filters to this filter chain
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersURL(filterMaps[i], requestPath))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
//略
} else {
filterChain.addFilter(filterConfig);
}
}
// Add filters that match on servlet name second
for (int i = 0; i < filterMaps.length; i++) {
if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
continue;
}
if (!matchFiltersServlet(filterMaps[i], servletName))
continue;
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMaps[i].getFilterName());
if (filterConfig == null) {
// FIXME - log configuration problem
continue;
}
//略
} else {
filterChain.addFilter(filterConfig);
}
}
// Return the completed filter chain
return (filterChain);
}
上述的代码比较长,主要的逻辑有
- 每个请求需要生成对应的ApplicationFilterChain,invoke方法中调用ApplicationFilterFactory类的createFilterChain方法创建一个ApplicationFilterChain,其中包含了目标servlet以及对应的过滤器链。
- createFilterChain方法中,首先设置了目标servlet,
filterChain.setServlet(servlet);
。 - 接着从上下文环境中取出之前解析的filterMaps信息,
FilterMap filterMaps[] = context.findFilterMaps();
。 - 遍历filterMaps,判断当前的请求是否符合拦截条件,若符合则将filterConfig放进filterChain中,从这里可以看出,实际决定过滤器执行顺序的是filter-mapping在web.xml中的配置顺序。
至此一个ApplicationFilterChain便构建好了,包含一个目标servlet和我们想要的过滤器链。
过滤器链执行
获取到过滤器链之后,接下来就是过滤器链的具体执行,回到上一步分析开始的StandardWrapperValve类的invoke方法中,现在我们拿到的ApplicationFilterChain,便可以继续向下分析了。
try {
if ((servlet != null) && (filterChain != null)) {
// Swallow output if needed
if (context.getSwallowOutput()) {
try {
SystemLogHandler.startCapture();
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter(request.getRequest(), response.getResponse());
}
} finally {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
context.getLogger().info(log);
}
}
} else {
if (request.isAsyncDispatching()) {
request.getAsyncContextInternal().doInternalDispatch();
} else if (comet) {
filterChain.doFilterEvent(request.getEvent());
} else {
filterChain.doFilter(request.getRequest(), response.getResponse());
}
}
}
//略
上述代码中,我们关注的是filterChain.doFilter
方法,在这里将会触发过滤器链的执行,继续跟踪源码
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
//略
} else {
internalDoFilter(request,response);
}
}
最终实际的处理方法是internalDoFilter
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response);
//略
if( Globals.IS_SECURITY_ENABLED ) {
//略
} else {
filter.doFilter(request, response, this);
}
support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request, response);
} catch (IOException | ServletException | RuntimeException e) {
//略
} catch (Throwable e) {
//略
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
//略
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
if( Globals.IS_SECURITY_ENABLED ) {
//略
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
servlet, request, response);
} catch (IOException e) {
//略
} finally {
if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
lastServicedRequest.set(null);
lastServicedResponse.set(null);
}
}
}
上面只列出我们关注的关键代码
-
ApplicationFilterConfig filterConfig = filters[pos++];
此处取出当前要执行的filter,并把pos加1。 - 执行
filter.doFilter
方法,并将当前的filterChain传入过滤器中。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("[DemoFilter-before]doFilter");
chain.doFilter(request, response);
System.out.println("[DemoFilter-after]doFilter");
}
- 上面是我们定义的filter,当我们调用chain.doFilter的时候,最终又回到上面的internalDoFilter方法中,取出过滤器链中的下一个过滤器进行执行。
- 当过滤器链执行完成后,便会执行servlet.service方法。
- 最后internalDoFilter执行完成后,便会回到上一个过滤器的doFilter中,继续执行chain.doFilter之后的代码,直到执行完所有匹配的过滤器。
至此,过滤器链的执行便完成了。
过滤器关键类与接口
- Filter:实现一个过滤器可以实现该接口
- ContextConfig:加载web.xml中的配置信息,并保存到上下文环境中
- StandardContext:对具体的filter进行初始化,并保存到上下文环境中
- StandardWrapperValve:将请求映射到ApplicationFilterChain,并负责过滤器的执行。
- ApplicationFilterChain:负责过滤器链的递归调用
过滤器应用示例
编码设置:设置请求及相应的编码
日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面。
通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用。