博客
关于我
Java Web 中对 ServletRequest 的一些非常规操作解决方案
阅读量:423 次
发布时间:2019-03-06

本文共 4345 字,大约阅读时间需要 14 分钟。

文章目录

1. 前言

ServletRequest 是我们搞 Java Web 经常接触的 Servlet Api 。有些时候我们要经常对其进行一些操作。这里列举一些经常的难点操作。

2. 提取 body 中的数据

前后端交互我们会在 body 中传递数据。我们如何从 body 中提取数据。通常我们会通过 IO 操作:

/**      * obtain request body      *      * @param request the ServletRequest      * @return body string   it maybe is   null      */     public static String obtainBody(ServletRequest request) {                  BufferedReader br = null;         StringBuilder sb = new StringBuilder();          try {             br = request.getReader();             String str;             while ((str = br.readLine()) != null) {                 sb.append(str);             }             br.close();         } catch (IOException e) {             log.error(" requestBody read error");         } finally {             if (null != br) {                 try {                     br.close();                 } catch (IOException e) {                     log.error(" close io error");                 }             }         }         return sb.toString();      }

看起来比较凌乱,各种异常处理,IO 开关操作,很不优雅。 如果你使用了 Java 8 你可以这样简化这种操作:

String body = request.getReader().lines().collect(Collectors.joining());

BufferedReader 提供了获取 流的方法 lines() ,我们可以通过以上方法非常方便的获取 ServletRequest 中的 body

3. ServletRequest 中的流是一次性的

不要以为上面的读取 body 操作是完美无瑕的,这里有一个坑。如果按照上面的操作 ServletRequest 中的 body 只能读取一次。 我们传输的数据都是通过流来传输的。ServletRequest 中我们实际上都是通过:

ServletInputStream inputStream = request.getInputStream()

来获取输入流,然后通过 read 系列方法来读取。Java 中的 InputStream read 方法内部有一个postion, **它的作用是标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,read 方法会返回 -1,标志已经读取完了,如果想再次读取,可以调用 reset 方法,position 就会移动到上次调用 mark 的位置,mark 默认是 0,所以就能从头再读了。

能否 reset 是有条件的,它取决于 markSupported(),markSupported() 方法返回是否可以进行 mark/reset

我们再回头看 ServletInputStream ,其实现并没有重写 reset 方法并不支持 mark/reset 。所以ServletRequest 中的 IO流 只能读取一次 。

4. 可重复读取 ServletRequest 中的流

如果我们使用了个多个 Servlet Filter 进行链式调用并多次操作 ServletRequest 中的流应该怎么做? 我们可以通过 Servlet Api 提供的 javax.servlet.http.HttpServletRequestWrapper 来对其进行包装。 通过继承 HttpServletRequestWrapper :

public class ReaderRequest extends HttpServletRequestWrapper {   private String body;   public ReaderRequest(HttpServletRequest request) throws IOException {       super(request);       body = request.getReader().lines().collect(Collectors.joining());   }   @Override   public BufferedReader getReader() throws IOException {       final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());       InputStreamReader inputStreamReader = new InputStreamReader(byteArrayInputStream);       return new BufferedReader(inputStreamReader);   }}

以下是在一个 Servlet Filter 中的标准范例:

public class TestFilter extends GenericFilterBean {    @Override    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {         // 包装        ReaderRequest cachingRequestWrapper=new ReaderRequest((HttpServletRequest) servletRequest);        // 直接从包装读取        String collect = cachingRequestWrapper.getReader().lines().collect(Collectors.joining());        // 传递包装        filterChain.doFilter(cachingRequestWrapper, servletResponse);    }}

5. 如何对 ServletRequest 进行 setParameter()

从前台传入数据的时候、后台通过 HttpServletRequest 中的 getParameter(String name) 方法对数据进行获取。 如果后台想将数据放进去,下次请求或者其他请求时使用,只能通过setAttribute(String name, Object o) 放入然后从 getAttribute(String name) 获取, 无法通过 getParameter(String name) 获取。我在 就遇到了这个问题

首先说一下getParameter(String name) 是在数据从客户端到服务端之后才有效的,而 则是服务端内部的事情,只有在服务端调用了 setAttribute(String name, Object o) 之后,并且没有重定向(redirect),在没有到客户端之前 getAttribute(String name) 才有效。

如果希望在服务端中转过程中使用 setParameter() ,我们可以通过 getParameter(String name) 委托给 getAttribute(String name) 来执行。相关实现依然通过 javax.servlet.http.HttpServletRequestWrapper 来实现。

package cn.felord.spring.security.filter;    import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletRequestWrapper;    /**   * @author Felordcn   * @since 2019/10/17 22:09   */  public class ParameterRequestWrapper extends HttpServletRequestWrapper {          public ParameterRequestWrapper(HttpServletRequest request ) {          super(request);        }        @Override      public String getParameter(String name) {         return (String) super.getAttribute(name);      }  }

你也可借鉴思路实现其它你需要的功能。

6. 总结

今天我们对 ServletRequest 的一些常用的操作进行了讲解。也是我们经常在实际开发中遇到的一些问题。当然你也可以使用一些第三方包来解决这些问题。

关注公众号:Felordcn获取更多资讯

转载地址:http://vpjuz.baihongyu.com/

你可能感兴趣的文章
ngModelController
查看>>
ngrok | 内网穿透,支持 HTTPS、国内访问、静态域名
查看>>
ngrok内网穿透可以实现资源共享吗?快解析更加简洁
查看>>
NHibernate学习[1]
查看>>
NHibernate异常:No persister for的解决办法
查看>>
NIFI1.21.0/NIFI1.22.0/NIFI1.24.0/NIFI1.26.0_2024-06-11最新版本安装_采用HTTP方式_搭建集群_实际操作---大数据之Nifi工作笔记0050
查看>>
NIFI1.21.0_java.net.SocketException:_Too many open files 打开的文件太多_实际操作---大数据之Nifi工作笔记0051
查看>>
NIFI1.21.0_Mysql到Mysql增量CDC同步中_日期类型_以及null数据同步处理补充---大数据之Nifi工作笔记0057
查看>>
NIFI1.21.0_Mysql到Mysql增量CDC同步中_补充_更新时如果目标表中不存在记录就改为插入数据_Postgresql_Hbase也适用---大数据之Nifi工作笔记0059
查看>>
NIFI1.21.0_NIFI和hadoop蹦了_200G集群磁盘又满了_Jps看不到进程了_Unable to write in /tmp. Aborting----大数据之Nifi工作笔记0052
查看>>
NIFI1.21.0_Postgresql和Mysql同时指定库_指定多表_全量同步到Mysql数据库以及Hbase数据库中---大数据之Nifi工作笔记0060
查看>>
NIFI1.21.0最新版本安装_连接phoenix_单机版_Https登录_什么都没改换了最新版本的NIFI可以连接了_气人_实现插入数据到Hbase_实际操作---大数据之Nifi工作笔记0050
查看>>
NIFI1.21.0最新版本安装_配置使用HTTP登录_默认是用HTTPS登录的_Https登录需要输入用户名密码_HTTP不需要---大数据之Nifi工作笔记0051
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表多表增量同步_增删改数据分发及删除数据实时同步_通过分页解决变更记录过大问题_02----大数据之Nifi工作笔记0054
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表多表增量同步_增加修改实时同步_使用JsonPath及自定义Python脚本_03---大数据之Nifi工作笔记0055
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表多表增量同步_插入修改删除增量数据实时同步_通过分页解决变更记录过大问题_01----大数据之Nifi工作笔记0053
查看>>
NIFI1.21.0通过Postgresql11的CDC逻辑复制槽实现_指定表或全表增量同步_实现指定整库同步_或指定数据表同步配置_04---大数据之Nifi工作笔记0056
查看>>
NIFI1.23.2_最新版_性能优化通用_技巧积累_使用NIFI表达式过滤表_随时更新---大数据之Nifi工作笔记0063
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_根据binlog实现数据实时delete同步_实际操作04---大数据之Nifi工作笔记0043
查看>>
NIFI从MySql中增量同步数据_通过Mysql的binlog功能_实时同步mysql数据_配置binlog_使用处理器抓取binlog数据_实际操作01---大数据之Nifi工作笔记0040
查看>>