网站开发环境和运行环境,河北做网站的,正安县网站seo优化排名,个人建站赚钱文章目录 前情提要解决方案自定义 HttpServletRequest 包装类 RequestWrapper自定义 HttpServletResponse 包装类 ResponseWrapper自定义过滤器 MiddlewareFilter配置过滤器注解配置类 编写 Controller 测试 前情提要
在项目中需要使用过滤器 在请求调用 Controller 方法前修改… 文章目录 前情提要解决方案自定义 HttpServletRequest 包装类 RequestWrapper自定义 HttpServletResponse 包装类 ResponseWrapper自定义过滤器 MiddlewareFilter配置过滤器注解配置类 编写 Controller 测试 前情提要
在项目中需要使用过滤器 在请求调用 Controller 方法前修改请求参数和在结果返回之前修改返回结果。
在 Controller 中定义如下接口
PostMapping(/hello)
public JSONObject hello(RequestBody MapString, Object params) {return JSONObject.parseObject(JSON.toJSONString(params));
}定义的过滤器如下
public class ServNoFilter extends OncePerRequestFilter {Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 获取请求体内容String requestBody getRequestBody(httpServletRequest);// 业务处理......// 放行filterChain.doFilter(httpServletRequest, httpServletResponse);}private String getRequestBody(HttpServletRequest request) throws IOException {BufferedReader reader new BufferedReader(request.getReader());StringBuilder sb new StringBuilder();String line;while ((line reader.readLine()) ! null) {sb.append(line);}return sb.toString();}
}此时启动项目访问接口则会在控制台打印如下异常信息Request processing failed; nested exception is java.lang.IllegalStateException: getReader() has already been called for this request。
表示在过滤器中已经通过 request.getReader() 方法将请求流读取。
如果在过滤器中将 getReader() 换成 getInputStream() 就会报请求体为空异常org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing。
这是因为在 Servlet 中请求对象的输入流只能被读取一次。而在第一次读取请求体时Servlet 容器会将请求体保存在内存中并将其解析成相应的请求参数和请求头信息。如果在后续的处理中再次读取请求体就可能会导致数据错误或异常。
解决方案
自定义 HttpServletRequest 包装类 RequestWrapper
在 Servlet 中原始的 HttpServletRequest 对象中的请求流即请求体只能读取一次。这是因为 HTTP 协议是基于流的协议服务器在读取请求流时会将其消耗掉一旦读取完毕就无法再次读取
当 Servlet 容器读取完请求流后会将请求的内容解析并储存在相应的属性中如请求参数、请求头信息等。在后续的处理过程中Servlet 可以从这些属性中获取请求内容而不必再次读取请求流。
因此我们需要自定义 RequestWrapper 将请求流保存下来并提供方法来多次读取请求体的内容。
自定义 HttpServletRequest 包装类 RequestWrapper 如下
/*** HttpServletRequest 包装类允许在 Servlet 中多次读取请求体内容* 重写了 getInputStream()方法和 getReader() 方法返回可以多次读取的流。*/
public class RequestWrapper extends HttpServletRequestWrapper {private final byte[] body;/*** 构造 RequestWrapper 对象** param request 原始 HttpServletRequest 对象* param context 请求体内容*/public RequestWrapper(HttpServletRequest request, String context) {super(request);this.body context.getBytes(StandardCharsets.UTF_8);}/*** 重写 getInputStream 方法返回经过包装后的 ServletInputStream 对象** return 经过包装后的 ServletInputStream 对象*/Overridepublic ServletInputStream getInputStream() {return new ServletInputStreamWrapper(new ByteArrayInputStream(body));}/*** 重写 getReader 方法返回经过包装后的 BufferedReader 对象** return 经过包装后的 BufferedReader 对象*/Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));}/*** 私有内部类用于包装 ServletInputStream 对象*/private static class ServletInputStreamWrapper extends ServletInputStream {private final ByteArrayInputStream inputStream;/*** 构造函数传入待包装的 ByteArrayInputStream 对象** param inputStream 待包装的 ByteArrayInputStream 对象*/public ServletInputStreamWrapper(ByteArrayInputStream inputStream) {this.inputStream inputStream;}/*** 重写 read 方法读取流中的下一个字节** return 读取到的下一个字节如果已达到流的末尾则返回-1*/Overridepublic int read() {return inputStream.read();}/*** 覆盖 isFinished 方法指示流是否已完成读取数据** return 始终返回 false表示流未完成读取数据*/Overridepublic boolean isFinished() {return false;}/*** 重写 isReady 方法指示流是否准备好进行读取操作** return 始终返回 false表示流未准备好进行读取操作*/Overridepublic boolean isReady() {return false;}/*** 重写 setReadListener 方法设置读取监听器** param readListener 读取监听器*/Overridepublic void setReadListener(ReadListener readListener) {}}
}自定义 HttpServletResponse 包装类 ResponseWrapper
与请求流即请求体一样原始的 HttpServletResponse 对象中的响应流即响应体只能写入一次。当服务器在向客户端发送响应时会将响应流写入到网络传输通道中一旦写入完毕就无法再次修改或写入。
因此我们需要通过自定义 ResponseWrapper 包装原始的 HttpServletResponse 对象并重写其输出流或者输出写方法从而实现对响应流的修改和控制。
自定义 HttpServletResponse 包装类 ResponseWrapper 如下
/*** HttpServletResponse 包装类对提供对响应数据的处理和操作。*/
public class ResponseWrapper extends HttpServletResponseWrapper {private final ByteArrayOutputStream outputStream;private ServletOutputStream servletOutputStream;private PrintWriter writer;/*** 构造函数传入原始的 HttpServletResponse 对象** param response 原始的 HttpServletResponse 对象*/public ResponseWrapper(HttpServletResponse response) {super(response);this.outputStream new ByteArrayOutputStream();}/*** 重写 getOutputStream 方法返回经过包装后的 ServletOutputStream 对象** return 经过包装后的 ServletOutputStream 对象*/Overridepublic ServletOutputStream getOutputStream() {if (servletOutputStream null) {servletOutputStream new ServletOutputStreamWrapper(outputStream);}return servletOutputStream;}/*** 重写 getWriter 方法返回经过包装后的 PrintWriter 对象** return 经过包装后的 PrintWriter 对象*/Overridepublic PrintWriter getWriter() {if (writer null) {writer new PrintWriter(getOutputStream());}return writer;}/*** 获取响应数据并指定字符集** param charsetName 字符集名称* return 响应数据字符串*/public String getResponseData(String charsetName) {Charset charset Charset.forName(charsetName);byte[] bytes outputStream.toByteArray();return new String(bytes, charset);}/*** 设置响应数据并指定字符集** param responseData 响应数据字符串* param charsetName 字符集名称*/public void setResponseData(String responseData, String charsetName) {Charset charset Charset.forName(charsetName);byte[] bytes responseData.getBytes(charset);outputStream.reset();try {outputStream.write(bytes);} catch (IOException e) {// 处理异常}setCharacterEncoding(charsetName);}/*** 私有内部类用于包装 ServletOutputStream 对象*/private static class ServletOutputStreamWrapper extends ServletOutputStream {private final ByteArrayOutputStream outputStream;/*** 构造函数传入待包装的 ByteArrayOutputStream 对象** param outputStream 待包装的 ByteArrayOutputStream 对象*/public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream) {this.outputStream outputStream;}/*** 重写 write 方法将指定字节写入输出流** param b 字节*/Overridepublic void write(int b) {outputStream.write(b);}/*** 重写 isReady 方法指示输出流是否准备好接收写入操作** return 始终返回 false表示输出流未准备好接收写入操作*/Overridepublic boolean isReady() {return false;}/*** 重写 setWriteListener 方法设置写入监听器** param writeListener 写入监听器*/Overridepublic void setWriteListener(WriteListener writeListener) {}}
}自定义过滤器 MiddlewareFilter
我们的需求是在请求到达服务器之前对请求参数进行修改在响应返回之前对响应结果进行处理。
对于这样的需求我们可以通过自定义过滤器来实现。大致实现思路如下 修改请求参数请求体我们可以 获取请求体内容。修改请求体内容。将修改后的请求对象替换原来的请求对象以便后续获取修改后的参数。 修改响应结果响应体我们可以 获取响应数据。对响应数据进行处理。将修改后的数据作为最终结果返回。
同时为了确保每个请求在请求时只会被过滤一次我们可以继承 OncePerRequestFilter 来定义自己的过滤器。
最终自定义过滤器如下
public class MiddlewareFilter extends OncePerRequestFilter {Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 1. 从 HttpServletRequest 对象中获取请求体内容String requestBody getRequestBody(httpServletRequest);// 2. 解析请求体内容为JSON对象JSONObject jsonBody JSONObject.parseObject(requestBody);// 3. 修改请求体内容jsonBody.put(paramKey,paramValue);// 4. 包装 HttpServletRequest 对象为自定义的 RequestWrapper 对象以便后续的处理RequestWrapper requestWrapper new RequestWrapper(httpServletRequest, jsonBody.toJSONString());// 5. 包装 HttpServletResponse 对象为自定义的 ResponseWrapper 对象以便后续的处理ResponseWrapper responseWrapper new ResponseWrapper(httpServletResponse);// 6. 调用下一个过滤器或 ServletfilterChain.doFilter(requestWrapper, responseWrapper);// 7. 获取响应数据String responseData responseWrapper.getResponseData(StandardCharsets.UTF_8.name());// 8. 解析响应数据为JSON对象JSONObject jsonData JSONObject.parseObject(responseData);// 9. 在这里可以对响应数据进行处理jsonData.put(responseKey, responseValue);// 10. 将修改后的 JSON 对象转换为字符串responseData jsonData.toJSONString();// 11. 将修改后的 JSON 对象设置为最终的响应数据responseWrapper.setResponseData(responseData, StandardCharsets.UTF_8.name());// 12. 将响应数据写入原始的响应对象解决响应数据无法被多个过滤器处理问题OutputStream outputStream httpServletResponse.getOutputStream();outputStream.write(responseData.getBytes(StandardCharsets.UTF_8));outputStream.flush();}/*** 获取请求体内容。** param request HttpServletRequest对象* return 请求体内容* throws IOException 如果读取请求体内容时发生I/O异常*/private String getRequestBody(HttpServletRequest request) throws IOException {BufferedReader reader new BufferedReader(new InputStreamReader(request.getInputStream()));StringBuilder sb new StringBuilder();String line;while ((line reader.readLine()) ! null) {sb.append(line);}return sb.toString();}
}配置过滤器
注解
通过 Java Servlet 3.0 规范中引入的 WebFilter 注解配置过滤器。
WebFilter 注解可以应用在实现了 Filter 接口或继承自 OncePerRequestFilter 的类上标识该类为过滤器并指定过滤器的相关配置包括拦截的 URL 路径、执行顺序以及初始化参数等。
我们可以在 MiddlewareFilter 过滤器上使用 WebFilter 注解注册该过滤器并指定执行该过滤器执行的顺序和拦截的 URL
WebFilter(value 1000, urlPatterns /hello)
public class MiddlewareFilter extends OncePerRequestFilter {......
}value设置过滤器的执行顺序数字越小优先级越高。urlPatterns指定要拦截的 URL 路径允许指定多个 URL 路径urlPatterns {/hello,/hello1}。
还需要再启动类上使用ServletComponentScan注解扫描和注册带有 WebServlet、WebFilter 和 WebListener 注解的组件
ServletComponentScan
SpringBootApplication
public class Demo1Application {public static void main(String[] args) {SpringApplication.run(Demo1Application.class, args);}}配置类
除了注解的形式配置过滤器我们还可以通过配置类的形式进行配置。
创建 FilterConfig 类用于配置需要注册的过滤器同时在类上添加 Configuration 注解标识该类为配置类在项目启动时 Spring 会自动扫描该类中的 Bean 定义并将其加载到容器中
Configuration
public class FilterConfig {Beanpublic FilterRegistrationBeanMiddlewareFilter middlewareFilter() {FilterRegistrationBeanMiddlewareFilter registration new FilterRegistrationBean();registration.setFilter(new MiddlewareFilter()); // 设置过滤器实例registration.addUrlPatterns(/hello); // 拦截的 URL 路径registration.setOrder(1000); // 设置过滤器执行顺序数字越小越先执行return registration;}}在类中我们定义了名为 middlewareFilter 的方法用于注册我们自定义的 MiddlewareFilter 过滤器。
在 方法中创建了一个 FilterRegistrationBean 对象用于注册和配置过滤器并设置 MiddlewareFilter 对象作为过滤器实例指定了过滤器要拦截的 URL 路径滤器执行顺序。
最后将 FilterRegistrationBean 对象返回以便 Spring 自动进行注册和管理。
编写 Controller 测试
创建两个接口同样的逻辑接收一个请求体参数 params再将接收的参数以 JSON 格式返回
RestController
public class BasicController {/*** 处理 /hello 请求的方法* param params 请求体参数以键值对的形式传递* return 经过转换后的 JSONObject 对象*/PostMapping(/hello)public JSONObject hello(RequestBody MapString, Object params) {return JSONObject.parseObject(JSON.toJSONString(params));}PostMapping(/hello1)public JSONObject hello1(RequestBody MapString,Object params) {return JSONObject.parseObject(JSON.toJSONString(params));}
}启动项目在 ApiFox 中分别以同样的请求参数发送 POST 请求调用 /hello、/hello1 接口 请求参数 {name: hello,age: 20
}/hello 接口返回结果 {paramKey: paramValue,responseKey: responseValue,name: hello,age: 20
}/hello1 接口返回结果 {name: hello,age: 20
}复制多个 MiddlewareFilter 过滤器模拟多层过滤器修改请求体参数和返回结果测试结果如下
{paramKey: paramValue, //过滤器1responseKey2: responseValue2, //过滤器2responseKey: responseValue, //过滤器2paramKey2: paramValue2, //过滤器1name: hello,age: 20
}
文章转载自: http://www.morning.hdrsr.cn.gov.cn.hdrsr.cn http://www.morning.ljbpk.cn.gov.cn.ljbpk.cn http://www.morning.ndcjq.cn.gov.cn.ndcjq.cn http://www.morning.slnz.cn.gov.cn.slnz.cn http://www.morning.rltsx.cn.gov.cn.rltsx.cn http://www.morning.pmjw.cn.gov.cn.pmjw.cn http://www.morning.ysskn.cn.gov.cn.ysskn.cn http://www.morning.dschz.cn.gov.cn.dschz.cn http://www.morning.wqsjx.cn.gov.cn.wqsjx.cn http://www.morning.gtjkh.cn.gov.cn.gtjkh.cn http://www.morning.ptwrz.cn.gov.cn.ptwrz.cn http://www.morning.djxnn.cn.gov.cn.djxnn.cn http://www.morning.kghhl.cn.gov.cn.kghhl.cn http://www.morning.tfbpz.cn.gov.cn.tfbpz.cn http://www.morning.xqbbc.cn.gov.cn.xqbbc.cn http://www.morning.ymwcs.cn.gov.cn.ymwcs.cn http://www.morning.btcgq.cn.gov.cn.btcgq.cn http://www.morning.mwwnz.cn.gov.cn.mwwnz.cn http://www.morning.nkmw.cn.gov.cn.nkmw.cn http://www.morning.pmbcr.cn.gov.cn.pmbcr.cn http://www.morning.hsklc.cn.gov.cn.hsklc.cn http://www.morning.tkzrh.cn.gov.cn.tkzrh.cn http://www.morning.dbhnx.cn.gov.cn.dbhnx.cn http://www.morning.lmjtp.cn.gov.cn.lmjtp.cn http://www.morning.nyqzz.cn.gov.cn.nyqzz.cn http://www.morning.kpcxj.cn.gov.cn.kpcxj.cn http://www.morning.ymqrc.cn.gov.cn.ymqrc.cn http://www.morning.cdlewan.com.gov.cn.cdlewan.com http://www.morning.tmrjb.cn.gov.cn.tmrjb.cn http://www.morning.hpnhl.cn.gov.cn.hpnhl.cn http://www.morning.rbkml.cn.gov.cn.rbkml.cn http://www.morning.fxzw.cn.gov.cn.fxzw.cn http://www.morning.rnfwx.cn.gov.cn.rnfwx.cn http://www.morning.srltq.cn.gov.cn.srltq.cn http://www.morning.jzmqk.cn.gov.cn.jzmqk.cn http://www.morning.yszrk.cn.gov.cn.yszrk.cn http://www.morning.qbdqc.cn.gov.cn.qbdqc.cn http://www.morning.kxscs.cn.gov.cn.kxscs.cn http://www.morning.tfgkq.cn.gov.cn.tfgkq.cn http://www.morning.zcckq.cn.gov.cn.zcckq.cn http://www.morning.xxwhz.cn.gov.cn.xxwhz.cn http://www.morning.bhpsz.cn.gov.cn.bhpsz.cn http://www.morning.ysmw.cn.gov.cn.ysmw.cn http://www.morning.xxfxxf.cn.gov.cn.xxfxxf.cn http://www.morning.wfzlt.cn.gov.cn.wfzlt.cn http://www.morning.kqlrl.cn.gov.cn.kqlrl.cn http://www.morning.blqsr.cn.gov.cn.blqsr.cn http://www.morning.cldgh.cn.gov.cn.cldgh.cn http://www.morning.xrftt.cn.gov.cn.xrftt.cn http://www.morning.dyzbt.cn.gov.cn.dyzbt.cn http://www.morning.sjjtz.cn.gov.cn.sjjtz.cn http://www.morning.tqjwx.cn.gov.cn.tqjwx.cn http://www.morning.jnbsx.cn.gov.cn.jnbsx.cn http://www.morning.rqhdt.cn.gov.cn.rqhdt.cn http://www.morning.bpcf.cn.gov.cn.bpcf.cn http://www.morning.pmdzd.cn.gov.cn.pmdzd.cn http://www.morning.lbjdx.cn.gov.cn.lbjdx.cn http://www.morning.sjqml.cn.gov.cn.sjqml.cn http://www.morning.lqljj.cn.gov.cn.lqljj.cn http://www.morning.nsrlb.cn.gov.cn.nsrlb.cn http://www.morning.gxfpk.cn.gov.cn.gxfpk.cn http://www.morning.rqjl.cn.gov.cn.rqjl.cn http://www.morning.ztmkg.cn.gov.cn.ztmkg.cn http://www.morning.cxnyg.cn.gov.cn.cxnyg.cn http://www.morning.knqck.cn.gov.cn.knqck.cn http://www.morning.mmplj.cn.gov.cn.mmplj.cn http://www.morning.kryxk.cn.gov.cn.kryxk.cn http://www.morning.tgcw.cn.gov.cn.tgcw.cn http://www.morning.kxwsn.cn.gov.cn.kxwsn.cn http://www.morning.qttg.cn.gov.cn.qttg.cn http://www.morning.pshtf.cn.gov.cn.pshtf.cn http://www.morning.lsnnq.cn.gov.cn.lsnnq.cn http://www.morning.xxrwp.cn.gov.cn.xxrwp.cn http://www.morning.rkfwr.cn.gov.cn.rkfwr.cn http://www.morning.fbrshjf.com.gov.cn.fbrshjf.com http://www.morning.mtsgx.cn.gov.cn.mtsgx.cn http://www.morning.cjqqj.cn.gov.cn.cjqqj.cn http://www.morning.pkwwq.cn.gov.cn.pkwwq.cn http://www.morning.xfxqj.cn.gov.cn.xfxqj.cn http://www.morning.fksrg.cn.gov.cn.fksrg.cn