上一章简单介绍了拦截器的使用源码demo,拦截器中获取get参数不会出现任何异常,但是在拦截器中读取了输入流中body内容后,在controller中@RequestBody注解参数无法获取到内容

下面介绍解决这一问题的源码实现.

测试源码

springboot 项目interceptor_demo2 的目录结构如下:

springboot JoinPoint 获取参数 springboot获取body参数_拦截器


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请求工具 点击这里下载

springboot JoinPoint 获取参数 springboot获取body参数_源码demo_02

源码

链接: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 方法中参数读取到字节流就完全是来自客户端请求的字节流内容了。