相信很多人都会对Http的Filter的url-pattern不支持正则而烦恼吧。就比如,在web项目中Struts,当我们想要对所有的或者某一个Action进行过滤,而不过滤其他的Action,尤其是不想过滤静态资源,如果你的Struts配置了Action后缀可能会好一些,很可惜我的项目就没有设置后缀,所有没法使用url-pattern仅支持的几种规则,所以就自己实现了一个抽象类,用来支持可配置需过滤以及不需过滤的url规则,且支持正则,具体源码可见下方。
import java.io.IOException; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; /** * <p> * 支持使用正则配置过滤指定URL * * web.xml配置时加入init-params: include:配置需要过滤的url规则,支持正则,多个之间已','分割 * exclude:配置不需要过滤的url规则,支持正则,多个之间已','分割 * </p> * * @author Vicky * @date 2015-5-13 */ public abstract class PatternFilter implements Filter { protected Pattern[] includePattern = null; protected Pattern[] excludePattern = null; public final void init(FilterConfig filterconfig) throws ServletException { String include = filterconfig.getInitParameter("include"); String exclude = filterconfig.getInitParameter("exclude"); if (null != include && !"".equals(include)) { String[] arr = include.split(","); includePattern = new Pattern[arr.length]; for (int i = 0; i < arr.length; i++) { includePattern[i] = Pattern.compile(arr[i]); } } if (null != exclude && !"".equals(exclude)) { String[] arr = exclude.split(","); excludePattern = new Pattern[arr.length]; for (int i = 0; i < arr.length; i++) { excludePattern[i] = Pattern.compile(arr[i]); } } innerInit(filterconfig); } /** * 子类进行初始化方法 * * @param filterconfig */ public abstract void innerInit(FilterConfig filterconfig) throws ServletException; public void destroy() { // TODO Auto-generated method stub } /** * filter过滤方法,final子类不可覆盖,实现正则匹配规则,子类覆盖innerDoFilter */ public final void doFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException { String url = ((HttpServletRequest) servletrequest).getServletPath(); if (checkExclude(url) || !checkInclude(url)) {// 无需过滤该请求,则pass filterchain.doFilter(servletrequest, servletresponse); return; } // 调用innerDoFilter进行过滤 innerDoFilter(servletrequest, servletresponse, filterchain); return; } /** * 需子类覆盖,实现过滤逻辑 * * @param servletrequest * @param servletresponse * @param filterchain */ public abstract void innerDoFilter(ServletRequest servletrequest, ServletResponse servletresponse, FilterChain filterchain) throws IOException, ServletException; /** * 检验访问请求是否在include列表中 * * @param requestUrl * @return */ public final boolean checkInclude(String requestUrl) { boolean flag = true; if (null == includePattern || includePattern.length == 0) { return flag; } for (Pattern pat : includePattern) { if (flag = pat.matcher(requestUrl).matches()) break; } return flag; } /** * 检验访问请求是否在exclude列表中 * * @param requestUrl * @return */ public final boolean checkExclude(String requestUrl) { boolean flag = false; if (null == excludePattern || excludePattern.length == 0) { return flag; } for (Pattern pat : excludePattern) { if (flag = pat.matcher(requestUrl).matches()) break; } return flag; } }
源码其实很简单,仅有基础需要注意的地方:java的Matcher类是线程不安全的,所有在Filter中肯定是不能共享的,但是Pattern可以,所以我们将指定的规则初始化成Pattern缓存起来,避免每次调用的时候都来初始化Pattern,提高效率;通过使用final修改方法以防止子类覆盖doFilter的实现逻辑,并通过调用抽象方法innerDoFilter()来调用子类的过滤逻辑。
下面就来看看如何使用该过滤器。很简单,首先写一个Filter继承自该类即可,实现innerDoFilter()以及innerInit()方法即可,然后在web.xml中添加该filter的配置,配置的时候就可以指定两个init-param了,具体配置见下面。
filter> <filter-name>loginFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <description>指定Spring管理Filter生命周期</description> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <init-param> <description>配置需要过滤的url规则,需使用正则,多个之间已','分割</description> <param-name>include</param-name> <param-value></param-value> </init-param> <init-param> <description>配置不需要过滤的url规则,需使用正则,多个之间已','分割</description> <param-name>exclude</param-name> <param-value>/css/.*,/js/.*,/images/.*,/fonts/.*,/plugin/.*</param-value> </init-param> </filter> <filter-mapping> <filter-name>loginFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
从上面的配置文件可以看见,添加了两个初始化参数,含义配置中有详细描述。不过我的配置可能跟普通的filter配置不同,是因为我使用了Spring来管理Filter,所以这里的fliter-class是Spring的一个类(org.springframework.web.filter.DelegatingFilterProxy),这个类默认会根据filter-name来查找对应的bean来初始化成filter。下面说一下Spring的这个类DelegatingFilterProxy。
从PatternFilter的代码可以看出需要调用init()方法,但是如果使用Spring的DelegatingFilterProxy类来管理Filter,默认情况下是不会调用Filter的init()方法的,这是因为默认情况下Spring是不会管理Filter的生命周期的,所以不会调用init()以及destory()方法。下面来简单分析下DelegatingFilterProxy这个类的源码,看看哪里是解决这个问题的关键。
首先打开DelegatingFilterProxy类的源码,可以看出继承自GenericFilterBean,而GenericFilterBean实现了Filter,并实现了init()方法。
public final void init(FilterConfig filterConfig) throws ServletException { Assert.notNull(filterConfig, "FilterConfig must not be null"); if (logger.isDebugEnabled()) { logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'"); } this.filterConfig = filterConfig; // Set bean properties from init parameters. try { PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment)); 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 successfully"); } }
从上面的代码可以看出GenericFilterBean的init()方法中通过调用initFilterBean()来调用子类的init()方法,下面我们来看下DelegatingFilterProxy的initFilterBean()方法。
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) { 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) { this.delegate = initDelegate(wac); } } } }
该方法内部调用了initDelegate()方法。
protected Filter initDelegate(WebApplicationContext wac) throws ServletException { Filter delegate = wac.getBean(getTargetBeanName(), Filter.class); if (isTargetFilterLifecycle()) { delegate.init(getFilterConfig()); } return delegate; }
该方法内部调用了被代理的类(即我们自己的filter)init()方法。但是,注意下在调用的时候有一个if判断,即判断是否允许Spring管理filter的生命周期(isTargetFilterLifecycle()),看看这个方法内部时如果进行判断的。
/** * Return whether to invoke the {@code Filter.init} and * {@code Filter.destroy} lifecycle methods on the target bean. */ protected boolean isTargetFilterLifecycle() { return this.targetFilterLifecycle; }
方法内部很简单,只是判断一个变量值,该变量值决定了是否允许Spring管理filter的生命周期,默认是false,所以当找到这里的时候就已经知道是什么原因引起的了。所以很自然的就是想看看是否存在一个设置该变量值的方法,当然,肯定是存在的(setTargetFilterLifecycle()),于是就找这个方法在哪里被调用了,结果发现没有任何地方显示调用了该方法,注意是显示,所以暂且没辙了,于是很自然的想到了继承该类以此来满足自己的需求,但是总感觉不对,于是看了下这个类的注释,结果很高兴的发现了注释中提到了这个变量值如果修改。
* <p><b>NOTE:</b> The lifecycle methods defined by the Servlet Filter interface * will by default <i>not</i> be delegated to the target bean, relying on the * Spring application context to manage the lifecycle of that bean. Specifying * the "targetFilterLifecycle" filter init-param as "true" will enforce invocation * of the {@code Filter.init} and {@code Filter.destroy} lifecycle methods * on the target bean, letting the servlet container manage the filter lifecycle.
很简单,就是在配置filter的时候在init-param中添加一个参数,名为targetFilterLifecycle,值为true即可,于是尝试下,结果OK了。虽然问题解决了,但是还是想看一下到底是哪里调用了setTargetFilterLifecycle()这个方法,毕竟没有地方显示调用,于是又回去翻源码。
我们回到GenericFilterBean这个类的init()方法,可以看到其中有一个地方很想设置变量值的地方:bw.setPropertyValues(pvs, true);。结果深入看了下,就是这个地方设置的setTargetFilterLifecycle值,具体做法是获取filter中所有的init-param参数,然后根据参数名通过反射获取对应的set方法,并调用赋值,其中具体代码有很多不解之处,不过不影响理解这个问题。根据以上的逻辑,DelegatingFilterProxy类中的所有变量只有存在对应的set方法应该都是可以通过init-param指定。
以上就是我自己实现的可配置规则的Filter类以及Spring管理Filter遇到的问题。