如何在Spring Boot中到达控制器之前修改请求主体

Blu*_*oud 9 java spring spring-boot

我有一个Spring Boot应用程序。我更改每个发布请求的请求正文。是否可以在请求到达控制器之前修改请求主体。请列举一个例子。

Bij*_*ain 10

我的回答是使用HTTP Filter

请求过滤器.java

@Component
public class RequestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        RequestWrapper wrappedRequest = new RequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrappedRequest, response);
    }

    @Override
    public void destroy() {

    }

}
Run Code Online (Sandbox Code Playgroud)

RequestWrapper.java

 public class RequestWrapper extends HttpServletRequestWrapper {
        private final String body;
        private ObjectMapper objectMapper = new ObjectMapper();
    
        public RequestWrapper(HttpServletRequest request) throws IOException {
            // So that other request method behave just like before
            super(request);
    
            StringBuilder stringBuilder = new StringBuilder();
            BufferedReader bufferedReader = null;
            try {
                InputStream 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) {
                throw ex;
            } finally {
                if (bufferedReader != null) {
                    try {
                        bufferedReader.close();
                    } catch (IOException ex) {
                        throw ex;
                    }
                }
            }
            // Store request body content in 'requestBody' variable
            String requestBody = stringBuilder.toString();
            JsonNode jsonNode = objectMapper.readTree(requestBody);
            //TODO -- Update your request body here 
            //Sample
            ((ObjectNode) jsonNode).remove("key");
            // Finally store updated request body content in 'body' variable
            body = jsonNode.toString();
    
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
            ServletInputStream servletInputStream = new ServletInputStream() {
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
    
                @Override
                public boolean isFinished() {
                    return false;
                }
    
                @Override
                public boolean isReady() {
                    return false;
                }
    
                @Override
                public void setReadListener(ReadListener listener) {
    
                }
            };
            return servletInputStream;
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(this.getInputStream()));
        }
}
Run Code Online (Sandbox Code Playgroud)


Amy*_*oxy 8

以下是我使用 RequestBodyAdvice 实现它的方法:

  1. 创建一个实现RequestBodyAdvice的类,并使用@ControllerAdvice进行注释
@ControllerAdvice
public class CustomRequestBodyAdvice implements RequestBodyAdvice {
Run Code Online (Sandbox Code Playgroud)
  1. 您必须实现 4 种方法:

A。支持:在这里,您可以控制您的目标控制器,以及通过指定请求主体的类型更好地控制哪个请求主体

@Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        log.info("In supports() method of {}", getClass().getSimpleName());
        return methodParameter.getContainingClass() == AuthorController.class && type.getTypeName() == AuthorDTO.class.getTypeName();
    }
Run Code Online (Sandbox Code Playgroud)

b. 身体准备好之前

<!-- language: lang-js -->

@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
    log.info("In beforeBodyRead() method of {}", getClass().getSimpleName());
    return httpInputMessage;
}
Run Code Online (Sandbox Code Playgroud)

C。afterBodyRead:这里可以修改请求正文

<!-- language: lang-js -->

@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
    log.info("In afterBodyRead() method of {}", getClass().getSimpleName());
    if (body instanceof AuthorDTO) {
        AuthorDTO authorDTO = (AuthorDTO) body;
        authorDTO.setName("Test");
        return authorDTO;
    }

    return body;
}
Run Code Online (Sandbox Code Playgroud)

d. 处理空体

<!-- language: lang-js -->

@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
    log.info("In handleEmptyBody() method of {}", getClass().getSimpleName());
    return body;
}
Run Code Online (Sandbox Code Playgroud)

来源:http ://www.javabyexamples.com/quick-guide-to-requestbodyadvice-in-spring-mvc


小智 6

Another alternative would be adding an attribute to the HttpServletRequest object. And after that you can read that attribute in the Controller class with @RequestAttribute annotation.

In the Interceptor

@Component
    public class SimpleInterceptor extends HandlerInterceptorAdapter {

        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws ServletException, IOException {
            String parameter = request.getParameter("parameter");
            if (parameter == "somevalue") {
                request.setAttribute("customAttribute", "value");
            }

            return true;
        }

    }
Run Code Online (Sandbox Code Playgroud)

In the Controller

@RestController
@RequestMapping("")
public class SampleController {


    @RequestMapping(value = "/sample",method = RequestMethod.POST)
    public String work(@RequestBody SampleRequest sampleRequest, @RequestAttribute("customAttribute") String customAttribute) {
        System.out.println(customAttribute);
        return "This works";
    }
}
Run Code Online (Sandbox Code Playgroud)

This has advantage of not modifying the request body.


DwB*_*DwB 5

简短回答
是的,但并不容易。

详细信息
我知道三个选项可以在请求到达控制器中的处理程序方法“之前”更改它的主体;

  1. 在调用方法之前使用 AOP 更改请求。
  2. 创建 HTTP 过滤器。
  3. 创建自定义 Spring HandlerInterceptor。

由于您已经在使用 spring-boot,因此选项 3,自定义 Spring HandlerInterceptor 似乎是您的最佳选择。

这里是一个Baeldung 文章的链接,该文章涵盖了 spring HandlerInterceptors。

Baeldung 文章不是您问题的完整答案,因为您只能阅读一次InputStrem返回的内容HttpServletRequest

您将需要创建一个包装器类,该HttpServletRequest 包装器类在您的自定义 HandlerInterceptor 或自定义过滤器(过滤器可能是这里的方式)中扩展和包装您的包装器类中的每个请求。

包装类

  1. 读取HttpServletRequest包装类构造函数中的InputStream
  2. 根据您的要求修改主体。
  3. 将修改后的正文写入ByteArrayOutputStream.
  4. 用于从流中toByteArray检索实际值byte[]
  5. 关闭 ByteArrayOutputStream(try-with-resources 对此有好处)。
  6. 覆盖该getInputStream方法。
  7. byte[]每次getInputStream调用 时都将 包装在 ByteArrayInputStream 中。返回此流。

如何包装请求

  1. 在您的过滤器中,实例化您的包装类并传入原始请求(它是 doFilter 方法的参数)。
  2. 将包装器传递给 chain.doFilter 方法(不是原始请求)。

  • @DwB 你打算如何传播你的包装的 httpServletRequest ?HandlerInterceptor##preHandle 仅返回一个布尔值。您无法包装然后将包装的对象沿着调度链进一步传播。我认为如果您打算包装请求对象,过滤器可能是一个更好的解决方案,因为它们具有 chain.doFilter(request, response); 您可以通过这种方式传递封装的请求/响应。 (4认同)