上一章简单介绍了拦截器的使用源码demo,拦截器中获取get参数不会出现任何异常,但是在拦截器中读取了输入流中body内容后,在controller中@RequestBody注解参数无法获取到内容
下面介绍解决这一问题的源码实现.
测试源码
springboot 项目interceptor_demo2 的目录结构如下:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>interceptor_demo2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>interceptor_demo2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件application.properties,只配置web server 端口
server.port=8090
自定义拦截器 TestInterceptor
package com.example.interceptor_demo2.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("uri="+request.getRequestURI());
try {
RequestWrapper requestWrapper = new RequestWrapper(request);
String body = requestWrapper.getBody();
System.out.println("拦截器获取到body内容:"+body);
}catch (Exception e){
e.printStackTrace();
}
return true;
}
}
注册拦截器 WebConfigurer
package com.example.interceptor_demo2.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private TestInterceptor loginInterceptor;
/**
* 这个方法是用来配置静态资源的,比如html,js,css,等等
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns("/**") 表示拦截所有的请求,
// addPathPatterns("/test/**") 表示拦截/test/ 下的所有路径请求,
// addPathPatterns("/test/*") 表示拦截/test/abc,拦截/test/aaa , 不拦截 /test/abc/def
// addPathPatterns("/test/**").excludePathPatterns("/test/login", "/test/register") 表示拦截/test/ 下的所有路径请求,但不拦截 /test/login 和 /test/register
registry.addInterceptor(loginInterceptor).addPathPatterns("/**");
}
}
RequestWrapper
package com.example.interceptor_demo2.interceptor;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
public RequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body = stringBuilder.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody() {
return this.body;
}
}
过滤器 ChannelFilter
package com.example.interceptor_demo2.interceptor;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(urlPatterns = "/*",filterName = "channelFilter")
public class ChannelFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
if(requestWrapper == null) {
System.out.println("servletRequest");
filterChain.doFilter(servletRequest, servletResponse);
} else {
System.out.println("requestWrapper");
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {
}
}
测试类 TestController
package com.example.interceptor_demo2.controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/test")
public String test(@RequestBody String paramsJson){
System.out.println("paramsJson"+paramsJson);
return "controller@RequestBody获取到参数内容:" + paramsJson;
}
}
启动类 InterceptorDemoApplication
package com.example.interceptor_demo2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
//注册过滤器注解
@ServletComponentScan
public class InterceptorDemo2Application {
public static void main(String[] args) {
SpringApplication.run(InterceptorDemo2Application.class, args);
}
}
测试
使用postman 测试,发送post请求body为json内容 {“key”:“123”} ,如果没有安装postman且没其他测试post请求工具 点击这里下载
源码
链接:https://pan.baidu.com/s/1knR0rWTR3c3dP7CDJjYG3A
提取码:7x0j
最后,需要注意的地方
上面介绍的 通过 自定义Filter 和 HttpServletRequestWrapper 实现拦截器读取body 且在controller方法中也能读取到body内容,实现原理是通过 Filter 过滤所有接口 读取body内容存储到HttpServletRequestWrapper中字符串变量body中,后面n次读取body内容都是获取HttpServletRequestWrapper中的body内容。
注意:上面这种方法在读取字符串参数body时 是没有问题的,但是如果客户端post请求的body内容不是字符串而是字节流话,上面方法就会出问题:controller方法读取到的字节流参数(HttpServletRequestWrapper的body) 的大小会比实际上传的字节流数据 大了接近一倍。
我的解决方法是:在自定义的过滤器和拦截器中 判断request的URI,不要让接收字节流参数的接口 new RequestWrapper ,比如下面自定义Filter中的doFilter方法, /xxxxxx 是一个接收字节流的接口
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(servletRequest instanceof HttpServletRequest) {
if(!((HttpServletRequest) servletRequest).getRequestURI().startsWith("/xxxxxx")){
requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
}
}
if(requestWrapper == null) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
同时在Interceptor中 也要像在自定义 Filter中一样 通过请求URI判断,如果是 接收字节流的接口 不要去读取body值,那么这样就不会 使用到HttpServletRequestWrapper, 在Controller 方法中参数读取到字节流就完全是来自客户端请求的字节流内容了。