这篇文章仅仅用来参考,本身不想写,request之所以不想让你修改parameter的值,就是因为这个东西一般不然改,有人问我为什么不让改,表面上说我只能说这属于篡改数据,因为这个使用户的请求数据,如果被篡改就相当于篡改消息,如果你一天给别人发消息发的是:你好,而对方收到的是:fuck you!,你会怎么想,呵呵!当然它主要是怕不安全把参数数据该乱了,因为程序员毕竟是自己写程序,尤其是在公共程序里面写,后台程序员发现自己的数据不对,也找不到原因;一般WEB应用会提供一个attribute来提供自己的参数设置,这样就OK了,但是有些人就是那么变态说为啥就不能改呢,面向对象不是相互的么,有get应该有set的呀,我只能说,面向对象来自于生活现实,生活现实中每天逛大街,街上有很多形形色色如花似玉的,但是又可能你只能看,不能摸,更不能XX,呵呵,否则一个异常就出来了:臭流氓!

呵呵,不过就技术的角度来讲,能实现吗,当然可以,没有不可以实现的,源码之下,了无秘密,这是一个大牛说的,那么我们先来思考下有那些实现的方式:

1、我自己new一个request,然后放到容器里头,放那呢?等会来说,先记录下。

2、如果我能改掉request里面的值,那就好了呗,好的,先记录下,等会来想怎么改。

先说第一种方式,我自己new一个,呵呵,怎么new,怎么让其他的程序知道。

new的两种方式之一(开始思考的起源):

先说new的方式,在不知道具体的容器怎么实现HttpSevletRequest的时候,很简单,我自己写个类,implements HttpServletRequest呵呵,这个貌似很简单,OK,继承下试一试:

public class HttpServletRequestExtend implements HttpServletRequest {.......实现代码
}

此时提示需要有N多方法需要被实现,例如:

getParameter、getAttribute、getAttributeNames、getCharacterEncoding、getContentLength、getContentType。。。。。。

等等几十个方法,呵呵;

当然,你可以再构造方法里面将实际的request对象传递进来,如果是相同的方法,就这个request来实现,如果需要自己处理的方法,就按照自己的方式来处理,这种包装貌似简单

自己定义parameter,就用一个

private Map<String , String[]>paramterMap = new HashMap<String , String[]>();

就可以简单搞定,自己再搞个addParameter方法等等,就可以实现自己的功能。

不过写起来挺费劲的,因为意味着你所有的方法都要去实现下,除非你其他的方法都不用,只用其中几个方法而已,这就体现出一些接口的不足了。

但是这种方式是可行的,至少可以这样说,只是很费劲而已,因为感觉冗余很厉害,也体现出接口的不足,和抽象类的价值,我们想要的只是重载那些我们想要重载的,原有的还是按照它原有的处理思路,此时,有一个叫HttpServletRequestWrapper的出现了;

new方式2:

继承HttpServletRequestWrapper,其实就是上面那种方法多了一层继承,将你的重复工作交予了它,你也可以这样做,

全名为:javax.servlet.http.HttpServletRequestWrapper,看来也是一个扩展的通用接口,也就是会对request做一次包装,OK;跟着进去发现它可以处理类似request一样的差不多的内容,在这个基础上做了一次包装,你可以认为他就是对你自己new的那个,多了一层简单扩展实现,而你再这个基础上,可以继续继承和重写。

OK,此时你要重写如何重写呢,比如我们要重写一个getParameter方法和getParameterValues方法,其余的方法保持和原来一致,我们在子类中,自己定义一个Map用来放参数,结合request本身的参数,加上外部其他自定义的参数,做成一个新的参数表。

如下所示:

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.HashMap;
import java.util.Map;
public class ParameterRequestWrapper extends HttpServletRequestWrapper {private Map<String , String[]> params = new HashMap<String, String[]>();@SuppressWarnings("unchecked")public ParameterRequestWrapper(HttpServletRequest request) {// 将request交给父类,以便于调用对应方法的时候,将其输出,其实父亲类的实现方式和第一种new的方式类似super(request);//将参数表,赋予给当前的Map以便于持有request中的参数this.params.putAll(request.getParameterMap());}//重载一个构造方法public ParameterRequestWrapper(HttpServletRequest request , Map<String , Object> extendParams) {this(request);addAllParameters(extendObject);//这里将扩展参数写入参数表}@Overridepublic String getParameter(String name) {//重写getParameter,代表参数从当前类中的map获取String[]values = params.get(name);if(values == null || values.length == 0) {return null;}return values[0];}public String[] getParameterValues(String name) {//同上return params.get(name);}public void addAllParameters(Map<String , Object>otherParams) {//增加多个参数for(Map.Entry<String , Object>entry : otherParams.entrySet()) {addParameter(entry.getKey() , entry.getValue());}}public void addParameter(String name , Object value) {//增加参数if(value != null) {if(value instanceof String[]) {params.put(name , (String[])value);}else if(value instanceof String) {params.put(name , new String[] {(String)value});}else {params.put(name , new String[] {String.valueOf(value)});}}}
}

好了,两种new的方式都有了,我们推荐那种?一般来说推荐第二种方式,至少他给你提供好了一些东西,不过怎么说呢,你要明白是怎么回事,第一种方式到第二种方式的演变是需要知道的,至少你要知道,效果是一样的就是了,第一种方式里面有大量的方法需要重写,第二种不需要,这属于设计模式的知识,我们这不详细探讨了。

接下来我们说下将new出来的request如何使用,以及【让业务层使用到】,以及我们要说的,这种方式的【缺陷是什么】,如何做没有这种缺陷。

让业务层知道的方式很简单,最简单的方式是:

你写一个过滤器,在filter这个地方new了这个自己定义的request后,然后将在doFilter的时候,给的request就不是传入的request,而是你自己new出来的,接下来所有的request都是你new出来的了,如下所示:

ParameterRequestWrapper requestWrapper = new ParameterRequestWrapper((HttpServletRequest)request);
requestWrapper.addParameter("fff" , "我靠");
filterChain.doFilter(requestWrapper, servletResponse);

接下来,应用使用到的request对象,通过getParameter方法就能得到一个字符串叫:“我靠”,呵呵;注意,这个Fiter一定要在类似struts或者spring MVC之前处理。

还有什么方式呢,在传入业务层之前你还可以做AOP,如果业务层的入口方法是传入request的;还有些特殊自理,如struts2里面的request对象是通过:ServletActionContext.getRequest()来获取的,而不是直接入参的,你只需要,在业务代码调用前,调用代码:

ServletActionContext.setRequest(HttpServletRequest request),参数是你自己new出来的这个request就可以了,简单吧。方法多多,任意你选。

好,开心了一会,回到正题,有缺陷没有,有的,肯定有的。是什么,是什么,是什么?

刚才重载方法的时候,Map是自己写的,getParameter方法、getParameterValues方法是重写了,但是,其他的方法呢?回答是其他方法还是用request以前的值,是的,是以前的值,但是子类的Map数据有增加,request实际没增加,当你获取getParameterMap、getParameterNames这些方法的时候,参数就又有问题了,会不一致,这个可以自己测试,当然,最直接的解决方法是将这些方法也给换掉,也没问题,只要你愿意写,呵呵!

接下来,我们介绍第二种方法,我不推荐使用,但是从技术角度,不得不说是一种方法,只是这种方法是让java的安全机制在你面前裸奔,变得一丝不挂。

可能说到这里,很多人已经知道我要说啥了,因为可以让他变得一丝不挂的东西,没几样,在这个层面,一般说的就是“反射”,是的,request既然不让我改,那么我又想修改,那么我就用反射。

那么用反射的条件是什么?熟悉源码,是的,你必须看懂request怎么获取参数的,看源码容易走入误区,虽然是错误的,但是我还是先说下我走入的那些个误区,然后再来说怎么实际的改东西。

我走入的误区,但是也跟踪了源码,因祸得福:

首先通过以下方式找到request的实例来自于哪里,是那个类(因为HttpServletRequest是一个接口),那个jar包:

request.getClass() 就获取到是那个类,在tomcat下,看到是:org.apache.catalina.connector.RequestFacade这个类,其实看package就基本知道jar包的名称是啥了

不过可以通过程序看下是啥:

request.getClass().getResource("").getPath() 

可以得到request所在的jar包的源文件文件路径。

或者这样也可以:

request.getClass().getResource("/org/apache/catalina/connector/RequestFacade.class").getPath()

一样可以获取到,主要要加第一个反斜杠哦,否则会认为是当前class的相对路径的,第一个为长度为0的字符串""就是指当前路径了。

可以得到是tomcat下面的lib目录下的catalina.jar这个包。

这些可以反编译,也可以到官方下载源码,我们下面来看看源码:

我当时第一理解是getParameterMap获取的map和getParameter时获取参数的位置是一样的,然后,我就想尝试去修改这个Map,可惜当然获取到这个map的时候,发生put、remove这些操作的时候,直接抛出异常:

IllegalStateException内容里面会提示:parameterMap.locked这样的字样在里面,为啥呢,我们进去看看:

先看看getParameterMap这个方法:

那么这个request是什么呢?看到定义:

protected Request request = null;

发现上面没有import,那就应该是同一层包下面的Request类(我没有直接跟踪进去就是想要让大家知道虽然简单,但是容易混淆,在tomcat源码中,不止有一个类叫Request,而且存在相互调用)

这个类的全名就是:

org.apache.catalina.connector.Request

跟踪进去看看他的getParameterMap方法:

可以看到如果map被lock,直接返回,若没有,则将里面做了一个填充的操作,然后再设置为Lock,很简单吧。这个Lock貌似就和上面的异常有点关系了。

我们到这个parameterMap看看是什么类型,里面发生了什么:

protected ParameterMap parameterMap = new ParameterMap();

那么ParameterMap 是什么定义的呢:

public final class ParameterMap extends HashMap {.....
}

有点意思了,貌似找到组织了,竟然是HashMap的儿子,还有搞不定的嘛,眼看就要一切拨开云雾见青天了。

在看看里面的lock到底做了啥,找个put方法:

乖乖,终于找到凶手了,再看看其他的clear方法都做了类似操作,要修改这个怎么办?简单想办法把这个Map拿到,然后setLock(false)然后就可以操作了,然后操作完再setLock(true)呵呵,怎么获取到这个Map呢?

getParameterMap其实就是返回了他,将他强制类型转换为ParameterMap,貌似不靠谱,因为这个Class不在你的应用内存里面,引用不到,不过可以做的是什么反射?

呵呵!简单来说,获取到这个Map后,假如被命名为map

Filed  lockedField = map.getClass().getDeclaredField("locked");
lockedField.setAccessible(true);//打开访问权限,让他裸奔,private类型照样玩他
lockedField.setBoolean(map, false);//将lock参数设置为false了,就是可以修改了
这下子爽了,可以调用map.put了
map.put("newNode" , new String[] {"阿拉拉拉"});
....
调用完了,记得:
lockedField.setBoolean(map, true);

否则看上述代码,发现lock是false,会重新初始化,你的设置就悲剧了。

OK,这个时候发现,request.getParameterMap对了,可是其他的貌似不对,getParameter、getParameterValues、getParameterNames这几个都不对;

最后我发现我走错了,下面开始纠正错误了:

跟踪另外几个方法进去:

发现也是在这个request里面,进去看看:

发现又出来一个coyoteRequest,又是哪里冒出来的:看定义:

protected org.apache.coyote.Request coyoteRequest;

这个类竟然也叫Request,而且是包装在现在Request类里面的,这就是为什么开始我要说全名(org.apache.catalina.connector.Request)了继续跟踪到后面这个Reuqestorg.apache.coyote.Request)里面去过后,看这个里面的getParameters方法,因为可以看出Parameters获取到,后面就是键值对了。

进去看下:

原来是一个属性,看下属性定义:

private Parameters parameters = new Parameters();

这个Parameters到底是啥东西,我能修改么?

public final class Parameters extends MultiMap {
....
}

没开始那么兴奋,貌似没见过MultiMap 是什么。

跟踪进去,尽然没发现父类,正在我纳闷的时候,翻看这个Parameters的源码的时候,发现没用集成,用了下组合,呵呵:

private Hashtable<String,String[]> paramHashStringArray =new Hashtable<String,String[]>();

和我想想的差不多,再看看方法,有个addParameterValues,估计它就是用这个方法来设置参数的:

再确认下,发现getParameter、getParameterValues、getParameterNames都间接会直接调用这个hashtable;

这下笑了,因为找到了,就可以让他裸奔。

要么找到这个parameter对象,然后调用方法addParam、addParameterValues这些方法,不过貌似要remove不行,还有,这个addParam通过上图可以看到,如果同一个Key,存在参数,不是替换,而是将结果的数组扩大,要替换还是不行,所以拿到paramHashStringArray这个值就可以干任何事情了,呵呵!

好,我们简单写个测试代码,放在一个filter里面:

try {Class clazz = request.getClass();Field requestField = clazz.getDeclaredField("request");requestField.setAccessible(true);Object innerRequest = requestField.get(request);//获取到request对象//设置尚未初始化 (否则在获取一些参数的时候,可能会导致不一致)Field field = innerRequest.getClass().getDeclaredField("parametersParsed");field.setAccessible(true);field.setBoolean(innerRequest , false);Field coyoteRequestField = innerRequest.getClass().getDeclaredField("coyoteRequest");coyoteRequestField.setAccessible(true);Object coyoteRequestObject = coyoteRequestField.get(innerRequest);//获取到coyoteRequest对象Field parametersField = coyoteRequestObject.getClass().getDeclaredField("parameters");parametersField.setAccessible(true);Object parameterObject = parametersField.get(coyoteRequestObject);//获取到parameter的对象
//获取hashtable来完成对参数变量的修改Field hashTabArrField = parameterObject.getClass().getDeclaredField("paramHashStringArray");hashTabArrField.setAccessible(true);@SuppressWarnings("unchecked")Map<String,String[]> map = (Map<String,String[]>)hashTabArrField.get(parameterObject);map.put("fuck" , new String[] {"fuck you"});
//也可以通过下面的方法,不过下面的方法只能添加参数,如果有相同的key,会追加参数,即,同一个key的结果集会有多个
//            Method method = parameterObject.getClass().getDeclaredMethod("addParameterValues" , String.class , String[].class);
//            method.invoke(parameterObject , "fuck" , new String[] {"fuck you!" , "sssss"});} catch (Exception e) {e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.}System.out.println(request.getParameter("fuck"));

此时getParameter就能获取到写进去的值了哦,getParameterValues也是可以的;这种方式改掉的参数,所有的getParameterMap(还没调用过这个方法之前执行上面的代码)、getParameterNames全部都会被改掉。

测试OK了,发现上面的代码有点乱,整理下,至少初始化的时候可以省掉很多反射的代码:

定义几个静态变量,初始化的时候,暂时先别做任何动作:

    private static Field requestField;private static Field parametersParsedField;private static Field coyoteRequestField;private static Field parametersField;private static Field hashTabArrField;

在static或放在filter的init方法中去执行:

try {Class clazz = Class.forName("org.apache.catalina.connector.RequestFacade");requestField = clazz.getDeclaredField("request");requestField.setAccessible(true);parametersParsedField = requestField.getType().getDeclaredField("parametersParsed");parametersParsedField.setAccessible(true);coyoteRequestField = requestField.getType().getDeclaredField("coyoteRequest");coyoteRequestField.setAccessible(true);parametersField = coyoteRequestField.getType().getDeclaredField("parameters");parametersField.setAccessible(true);hashTabArrField = parametersField.getType().getDeclaredField("paramHashStringArray");hashTabArrField.setAccessible(true);} catch (Exception e) {e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.}


这段代码执行后,反射的很多代码就省下来了;

OK,生下来就是调用了,调用的时候,如果放在Utils里面,就提供静态方法,放在Filter里面随你,反正filter是单例的:

我们就想得到那个Map,所以就提供一个:getRequestMap方法就O了:

@SuppressWarnings("unchecked")private Map<String , String[]> getRequestMap(ServletRequest request) {try {Object innerRequest = requestField.get(request);parametersParsedField.setBoolean(innerRequest, true);Object coyoteRequestObject = coyoteRequestField.get(innerRequest);Object parameterObject = parametersField.get(coyoteRequestObject);return (Map<String,String[]>)hashTabArrField.get(parameterObject);} catch (IllegalAccessException e) {e.printStackTrace();return Collections.emptyMap();}}


doFilter的时候,调用下:

Map<String , String[]> map = getRequestMap(request);if(map != null) {map.put("fuck" , new String[] {"fuck you!"});}

你就可以疯狂设置你的参数了,呵呵,这个程序开始裸奔了,你clear掉,后台的人疯了,小心被枪毙,在一些特殊应用中,你可以尝试去修改一些值达到一些特殊的目的,所以裸奔还是有意义的,呵呵!

最后再补充一种方式是:MockHttpServletRequest,全名为:org.springframework.mock.web.MockHttpServletRequest,是spring提供的,前提是你用了spring 2.5或更高的版本,另外需要注意的是,这个spring仅仅提供一个模拟的request,所以里面有些东西可能获取的内容并不是你特别想要的,他实现的方式和第一中方式类似,通常用在测试框架中。


如何修改request的parameter的几种方式相关推荐

  1. Action中取得request,session的四种方式

    Action中取得request,session的四种方式 在Struts2中,从Action中取得request,session的对象进行应用是开发中的必需步骤,那么如何从Action中取得这些对象 ...

  2. struts2的action从request获取参数值的几种方式

    使用jquery框架的ajax能够方便的向后台传递参数,以$.post为例,参数有2种方式字符串和键值对:$.post(url, "name=aty&age=25")和$. ...

  3. python3 request模块 post请求四种方式

    HTTP 协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式.常见的四种编码方式如下: 1.application/x-www-for ...

  4. MySQL数据库修改用户登录密码的三种方式

    文章目录 一.更新 mysql.user 表 二.用 set password 命令 三.使用 mysqladmin 命令 提醒:MYSQL5.7 版本后不再支持password()函数和passwo ...

  5. oracle修改某个数据类型,Oracle 修改某个字段的数据类型三种方式

    1.将该列设置为null,再修改其类型(这样会丢失数据) 2.最简单的方法: 假设你的表名为 tab_target create table test  as select * from tab_ta ...

  6. php+oracle新增数据类型,Oracle 修改某个字段的数据类型三种方式

    1.将该列设置为null,再修改其类型(这样会丢失数据) 2.最简单的方法: 假设你的表名为 tab_target create table test as select 1.将该列设置为null,再 ...

  7. Android数据存储五种方式总结

    1 使用SharedPreferences存储数据     2 文件存储数据       3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 下面详细讲解 ...

  8. Windows注册服务的两种方式,并设置服务开机自启

    目录 第一种方式: 下载instsrv.exe和srvany.exe 用管理员身份运行"命令提示符"工具 用"注册表编辑器"将服务替换为自己需要的功能 修改完注 ...

  9. android mysql储存动态数据_Android数据存储五种方式总结

    本文介绍Android平台进行数据存储的五大方式,分别如下: 下面详细讲解这五种方式的特点 第一种: 使用SharedPreferences存储数据 适用范围:保存少量的数据,且这些数据的格式非常简单 ...

  10. Linux 开机自动执行脚本的两种方式

    前言 很多情况下,我们都希望服务重启之后,很多应用都能自动启动,那么除了linux 提供的自启动配置之外,我们也可以在开机之后,通过指定 一些脚本的具体路径,或者是某个服务的启动命令具体路径,来进行服 ...

最新文章

  1. 正则表达式判断号码靓号类型
  2. mysql od函数_Mysql数学函数不求人
  3. python实现冒泡排序算法的非递归版本_python排序算法速度比较:快速排序,归并排序,冒泡排序...
  4. 【学习笔记】13、标准数据类型—元组
  5. django 类取消csrf_Django之Form组件详解、图片上传及定制
  6. android 编译之后黑屏_抖音BoostMultiDex:Android低版本上首次启动时间减少80%(一)...
  7. 维特比算法Viterbi Algorithm
  8. Elasticsearch安装之安装Java环境
  9. c语言换行后多空格,关于文件操作,碰到空格就换行
  10. oracle怎么给表建同义词_oracle同义词创建(synonym)
  11. 对物理量“角速度”与“波矢”的理解
  12. 阿里云OSS上传报错:InvalidAccessKeyIdError
  13. 用c语言实现窗口化,c怎么窗口化只能开挂吗
  14. sizeof(long)
  15. Excel获取Sheet名称公式
  16. 高通WCD9370音频编解码器模块芯片介绍
  17. 如何查看公众号文章的排版格式字体大小
  18. PTA 6-9 递归计算P函数 (10分)
  19. php 的pre,pre标签的作用是什么
  20. JointJS 拖动画布

热门文章

  1. 【美港探案】百度Q2:需要重新定义
  2. 什么是CSR以及CSR的作用和生成方法
  3. 排列组合 C语言函数,排列组合c怎么算 计算方法是什么
  4. 利用Eigen库实现最小二乘拟合平面
  5. 财路网每日原创推送:区块链的“三围”了解
  6. Mac电脑进入恢复模式详细教程
  7. 奥斯卡大乌龙,然而人工智能也预测《爱乐之城》为最佳影片
  8. 毕业一年一点不平凡经历,不平常的感受,有了不平常的自信
  9. sre_constants.error: unbalanced parenthesis
  10. Transform.rotation所见非所得