【问题标题】:How to modify request body before reaching controller in spring boot如何在 Spring Boot 中到达控制器之前修改请求正文
【发布时间】:2018-11-28 15:50:50
【问题描述】:

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

【问题讨论】:

标签: java spring spring-boot


【解决方案1】:

简答题
是的,但不容易。

详情
我知道更改请求正文的三个选项 “之前”它到达控制器中的处理程序方法;

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

既然你已经在使用 spring-boot, 选项3,自定义Spring HandlerInterceptor, 似乎是您的最佳选择。

这是一个指向 Baeldung Article 的链接,涵盖 spring HandlerInterceptors。

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

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

包装类

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

如何包装请求

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

【讨论】:

  • 谢谢DwB 我会努力更新状态
  • DwB它的抛出无法读取HTTP消息:org.springframework.http.converter.HttpMessageNotReadableException:读取输入消息时出现I/O错误;嵌套异常是 java.io.IOException: Stream closed 。我该如何解决这个问题。
  • 您只能从 http 消息流中读取一次,永远。这就是为什么您需要在自定义 http 请求包装器中包装“真实”http 请求的原因。
  • 似乎 HttpServletResponse 是不可变的。我做了修改身体。如何返回流?因为方法“preHandle(HttpServletRequest request, final HttpServletResponse response, final Object handler)”返回布尔值。我什至尝试将自定义 HttpServletRequest 分配给原始 HttpServletRequest。但它不起作用。
  • @DwB 你打算如何传播你包装好的httpServletRequest? HandlerInterceptor##preHandle 只返回一个布尔值。您不能包装然后将包装的对象传播到调度链的下方。如果您计划包装请求对象,我认为过滤器可能是一个更好的解决方案,因为它们具有 chain.doFilter(request, response);您可以通过这种方式传递包装好的请求/响应。
【解决方案2】:

另一种选择是向 HttpServletRequest 对象添加一个属性。之后,您可以使用 @RequestAttribute 注释在 Controller 类中读取该属性。

在拦截器中

@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;
        }

    }

在控制器中

@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";
    }
}

这具有不修改请求正文的优点。

【讨论】:

  • 您不需要在SampleController 中指定SimpleInterceptor 吗?如果你定义了多个拦截器呢?
【解决方案3】:

我的回答是使用 HTTP 过滤器

RequestFilter.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() {

    }

}

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()));
        }
}

【讨论】:

    【解决方案4】:

    一种方法是通过反思。 ProceedingJoinPoint 包含传递给方法的 args 对象

    @Aspect
    @Component
    public class AopInterceptor {
    
        @Around(value = "@annotation(xyz.rpolnx.spring.web.poc.annotation.AopIntercepExample)")
        public Object handler(final ProceedingJoinPoint joinPoint) throws Throwable {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    
            Object[] args = joinPoint.getArgs();
    
            Class<?> someClass = args[0].getClass();
    
            Field field = someClass.getDeclaredField("custom");
            field.setAccessible(true);
            field.set(args[0], "custom");
            field.setAccessible(false);
    
            return joinPoint.proceed();
        }
    }
    
    @RestController
    public class SimpleController {
    
        @PostMapping("/aop")
        @AopIntercepExample
        public Person handleAopIntercept(@RequestBody Person nodes) {
            return nodes;
        }
    }
    
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AopIntercepExample {
    }
    
    public class Person {
        private String name;
        private String id;
        private String custom;
    }
    

    【讨论】:

      【解决方案5】:

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

      1. 创建一个实现 RequestBodyAdvice 的类并使用@ControllerAdvice 对其进行注解
      @ControllerAdvice
      public class CustomRequestBodyAdvice implements RequestBodyAdvice {
      
      1. 您必须实现 4 种方法:

      一个。支持:在这里,您可以通过指定请求主体的类型来控制您定位的控制器,以及更好的请求主体

      @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();
          }
      

      b. beforeBodyReady

      <!-- 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;
      }
      

      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;
      }
      

      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;
      }
      

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

      【讨论】:

      • type.getTypeName() == AuthorDTO.class.getTypeName() 这是正确的吗?我认为你应该使用String#equals
      • @PEdroArthur 都是正确的。这里的目标是针对您希望的请求正文类型。
      • 我知道了,但我担心使用== 来测试字符串。
      • 建议删除“return methodParameter.getContainingClass() == AuthorController.class && type.getTypeName() == AuthorDTO.class.getTypeName();”。这实际上是一个“compileTime 测试”,而不是您希望在每个请求中都运行的测试。
      • 如何处理没有@requestBody注解的请求
      猜你喜欢
      • 2020-08-09
      • 1970-01-01
      • 2020-01-22
      • 1970-01-01
      • 1970-01-01
      • 2019-06-02
      • 2014-03-25
      • 1970-01-01
      • 2019-09-03
      相关资源
      最近更新 更多