Filter和HttpServletRequest代理类实现Request参数值更改

前几日写代码,发现没有过滤搜索框输入的特殊字符,比如输入“_”会导致所有数据都被搜索出来(数据库为MySQL),这显然是不正确的,所以决定将搜索关键词中的“_”替换成”\_”,但是我突然想到个问题,这个搜索值获取是在每个Controller中获取的,我必须要找到所有的Controller中获取该参数的代码位置,然后进行修改,然而我并不想这么做…

我想到将request中该参数的值在被Controller获取前更改,然后就不用修改之前的代码就可以实现,但是后面我发现ServletRequest并没有提供修改参数值的方法… 后面经过思考,想到可以制造一个ServletRequest代理类,在代理类中将getParameter重写,即返回需要的值,再将代理类的实例传给Controller使用,这个方法最后验证是可行的。

下面将记录代码,这里使用到Filter,作为请求到达Controller前的拦截器,对request进行代理:

1,先看一下web.xml中的配置,配置了一个filter和一个servlet,filter配置了参数,分别是替换字符对和分隔符的设置,还配置了一个servlet,仅作为测试用:

<filter>
	<filter-name>RequestParamValueReplaceFilter</filter-name>
	<filter-class>proxy_mode.filter.RequestParamValueReplaceFilter</filter-class>
	<init-param>
		<param-name>replacerPairs</param-name>
		<param-value>_,\\_;\[,\\\[</param-value> <!-- 将“_”替换为“\_”,将“[”替换为“\[”,由于“[”是正则中的关键词,需要多一个“\” -->
	</init-param>
	<init-param>
		<param-name>repalcerSeperator</param-name>
		<param-value>;</param-value>
	</init-param>
	<init-param>
		<param-name>keyValueSeperator</param-name>
		<param-value>,</param-value>
	</init-param>
</filter>
<filter-mapping>
	<filter-name>RequestParamValueReplaceFilter</filter-name>
	<url-pattern>/porxy_mode</url-pattern>
</filter-mapping>

<servlet>
	<servlet-name>replacerTestServlet</servlet-name>
	<servlet-class>proxy_mode.servlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>replacerTestServlet</servlet-name>
	<url-pattern>/porxy_mode</url-pattern>
</servlet-mapping>

2,servlet:

public class TestServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
		String name = CharacterConvertUtil.convert(req.getParameter("name"), "utf-8");
		System.out.println("servlet中打印name: " + name);
		
		resp.setContentType("gbk");
		resp.getWriter().println("servlet中打印name: " + name);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req, resp);
	}
}

3,Filter,filter中除了初始化参数的代码外,就是配置代理类和替换request的方法,配置代理类生成器InvocationHandler,以及代理类调用器Proxy可以参看API,写的挺详细的。

这里需要注意的是,制造代理类只能使用HttpServletRequest,不能使用ServletRequest,否则会报错,估计是后面的程序使用的均是HttpServletRequest,所以当传入ServletRequest的实现类实例就会报错。

public class RequestParamValueReplaceFilter implements Filter {

	@Override
	public void init(FilterConfig paramFilterConfig) throws ServletException {

		initParam(paramFilterConfig);
	}

	private void initParam(FilterConfig cfg) {
		ReplacerUtil.replacerPairs = cfg.getInitParameter("replacerPairs");
		ReplacerUtil.repalcerSeperator = cfg.getInitParameter("repalcerSeperator");
		ReplacerUtil.keyValueSeperator = cfg.getInitParameter("keyValueSeperator");
		ReplacerUtil.initReplacerMap();
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {

		String name = CharacterConvertUtil.convert(req.getParameter("name"), "utf-8");
		System.out.println("filter打印name原始值:" + name);
		
		resp.setCharacterEncoding("gbk");
		resp.getWriter().println("filter打印name原始值:" + name);

		// 使用HttpServletRequest代理类,将req中name参数替换
		InvocationHandler reqHandler = new HttpServletRequestInvocationHandler(req);
		ServletRequest newReq = (ServletRequest) Proxy.newProxyInstance(
				HttpServletRequestInvocationHandler.class.getClassLoader(), new Class[] { HttpServletRequest.class },
				reqHandler);

		chain.doFilter(newReq, resp);
	}

	@Override
	public void destroy() {
		System.out.println("filter destroy......");
	}

}

4,InvocationHandler,配置代理类,具体方法的解释参见API,很详细的,下面已经记录用法:

public class HttpServletRequestInvocationHandler implements InvocationHandler {

	/**
	 * invoke方法中只有代理类的实例,而被代理类的实例需要自行注入
	 */
	private ServletRequest req;
	
	public HttpServletRequestInvocationHandler(ServletRequest req) {
		this.req = req;
	}
	
	/**
	 * proxy为代理类实例
	 * method为调用的方法
	 * args为传递到方法的参数值(实际值)
	 * 返回值为方法method执行后的返回值
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

		// 拦截getParameter方法,当参数为“name”时进行替换
		if ("getParameter".equals(method.getName()) && args.length > 0 && "name".equals(String.valueOf(args[0]))) {
			String name = req.getParameter("name");
			String newName = ReplacerUtil.replace(name);
			return newName;
		}

		// 不需要替换的参数就返回原始值
		return method.invoke(req, args);
	}
	
}

5,替换特殊字符的Util

public class ReplacerUtil {
	
	public static String replacerPairs;
	public static String repalcerSeperator;
	public static String keyValueSeperator;
	public static Map<String,String> replacerMap = new HashMap<String,String>(); 
	
	public static void initReplacerMap() {
		
		try {
			String[] replacerPairsArr = replacerPairs.split(repalcerSeperator);
			for (String replacerPair : replacerPairsArr) {
				String[] replacerPairArr = replacerPair.split(keyValueSeperator);
				replacerMap.put(replacerPairArr[0], replacerPairArr[1]);
			}
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
		
		System.out.println("replacerMap: " + replacerMap);
		
	}
	
	public static String replace(String original) {
		
		if (original == null) return original;
		for (Entry<String,String> entry : replacerMap.entrySet()) {
			original = original.replaceAll(entry.getKey(), entry.getValue());
		}
		
		return original;
	}
}

PS:这篇文章其实只是一个案例,是记录通过思考,得出解决方案,最后推敲是否可行的,也用于记录如何使用Filter,InvocationHandler以及Proxy的。其实文中方法并不推荐使用,因为如果真这么做,会对参数值造成污染,导致代码被入侵,所以,并不建议这么做,如果真的要使用,那么必须严格规定传入参数的参数名称,否则会污染其他代码,这样想来,ServletRequest也是没有提供修改参数值的方法的确是有原因的,也许java压根就不会提供,道理也很显然的了。

关于编码,还有些疑问,上面代码有个方法CharacterConvertUtil.convert(),是将iso8859-1转换为utf-8,必须这么做,否则控制台和网页上中文都会显示乱码,我估计是因为网络传输没有指定编码时默认使用iso8859-1,然后java默认是unicode,所以需要将iso8859-1转换为utf-8,仅仅是猜测,还有待研究…

本文《Filter和HttpServletRequest代理类实现Request参数值更改》来自 www.juwends.com ,欢迎转载或CV操作,但请注明出处,谢谢!