实现一个支持正则匹配的Filter以及Spring管理Filter遇到的问题

相信很多人都会对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遇到的问题。

时间: 2024-12-31 03:57:40

实现一个支持正则匹配的Filter以及Spring管理Filter遇到的问题的相关文章

Spring管理Filter和Servlet

Spring管理filter和servlet 在使用spring容器的web应用中,业务对象间的依赖关系都可以用context.xml文件来配置,并且由spring容器来负责依赖对象  的创建.如果要在filter或者servlet中使用spring容器管理业务对象,通常需要使用 WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext())来获得WebApplicationContext,然后调用

Spring 管理Filter和Servlet

本文转载自:http://www.open-open.com/lib/view/open1417248512252.html 在使用spring容器的web应用中,业务对象间的依赖关系都可以用context.xml文件来配置,并且由spring容器来负责依赖对象的创建.如果要在filter或者servlet中使用spring容器管理业务对象,通常需要使用WebApplicationContextUtils.getRequiredWebApplicationContext(getServletCo

配置DelegatingFilterProxy使用Spring管理filter

项目环境:JDK7 + Maven3.04 0. 项目使用springmvc作为controller层 1. 引入spring-security <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.0.1.RELEASE</version> &

正则匹配闭合HTML标签(支持嵌套)

任何复杂的正则表达式都是由简单的子表达式组成的,要想写出复杂的正则来,一方面需要有化繁为简的功底,另外一方面,我们需要从正则引擎的角度去思考问题.关于正则引擎的原理,推荐<Mastering Regular Expression>中文名叫<精通正则表达式>.挺不错的一本书. OK,先确定我们要解决的问题——从一段Html文本中找出特定id的标签的innerHTML. 这里面最大的难点就是,Html标签是支持嵌套的,怎么能够找到指定标签相对应的闭合标签呢? 我们可以这样想,先匹配最前

day6 反射,hashlib模块,正则匹配,冒泡,选择,插入排序

一.反射(自省) 首先通过一个例子来看一下本文中可能用到的对象和相关概念. import sys # 模块,sys指向这个模块对象import inspectdef foo(): pass # 函数,foo指向这个函数对象 class Cat(object): # 类,Cat指向这个类对象 def __init__(self, name='kitty'): self.name = name def sayHi(self): # 实例方法,sayHi指向这个方法对象,使用类或实例.sayHi访问

pcre库编写的正则匹配程序

使用的是Philip Hazel的Perl-Compatible Regular Expression库,参考: http://see.xidian.edu.cn/cpp/html/1428.html 执行匹配的时: gcc myreg.c ip.pat 内容: ip.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ip.txt 内容: 192.168.1.1 测试: ./a.out ip.pat ip.txt 下面是myreg.c源代码 /*  myreg.c  */ #inc

关于JAVA正则匹配空白字符的问题(全角空格与半角空格)

今天遇到一个字符串,怎么匹配空格都不成功!!! 我把空格复制到test.properties文件 显示“\u3000” ,这是什么? 这是全角空格!!! 查了一下    \s    不支持全角 1."\s"匹配的是哪一种空格? 正则表达式中\s匹配任何空白字符,包括空格.制表符.换页符等等:中文全角空格 \s 并不能匹配中文全角空格. \s 只能匹配下面六种字符(详见 java.util.regex.Pattern 的 API DOC): 半角空格( ) 水平制表符(\t) 竖直制表符

python 爬蟲 解析/正则匹配/乱码问题整理

今日爬取一听/扬天音乐都遇到了某些问题,现在对爬取过程中遇到的问题,做对于自己而言较为系统的补充与解释.主要问题有一下几点: 一:beautiful,urllib等库进行网页解析时,对于目标下的东西无法进行解析与显示 二:正则匹配虽然看过许多,但实际使用时仍然不够熟练,需要大量参考,故而,打算重新整理 三:对于乱码问题,曾在建mysql数据库时,头疼多次,现打算对于网页解析的乱码处理方法做些整理 这次目标是爬取扬天音乐"http://up.mcyt.net/",需要获取的内容有:歌曲名

re模块 正则匹配

import re re.M 多行模式 位或的意思 parrterm就是正则表达式的字符串,flags是选项,表达式需要被编译,通过语法.策划.分析后卫其编译为一种格式,与字符串之间进行转换 re模块 主要为了提速,re的其他方法为了提高效率都调用了编译方法,就是为了提速 re的方法 单次匹配 re.compile 和 re.match def compile(pattern, flags=0): return _compile(pattern, flags) 可看到,re最后返回的是_comp