繁体   English   中英

在 Spring 引导应用程序中序列化 API 请求参数的问题

[英]Issues in serializing API request argument in Spring boot application

我写了一个方面来序列化Spring启动应用程序中API的请求arguments,在DB中如下:

  @Pointcut("within(com.tm.web.rest.*)")
  public void applicationResourcePointcut() {
    // Method is empty as this is just a Pointcut, the implementations are in the advices.
  }


  /**
   * Advice that logs when a method is returned.
   *
   * @param joinPoint join point for advice
   */
  @AfterReturning(value = ("applicationResourcePointcut()"),
      returning = "returnValue")


public void capturePayloadWhileReturning(JoinPoint joinPoint, Object returnValue)  {
    
    CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();

    Map<String, Object> argumentNameValueMap = new HashMap<>();

    if (codeSignature.getParameterNames() == null) {
      return mapper.writeValueAsString(argumentNameValueMap);
    }

    for (int i = 0; i < codeSignature.getParameterNames().length; i++) {
      String argumentName = codeSignature.getParameterNames()[i];
      Object argumentValue = joinPoint.getArgs()[i];
      argumentNameValueMap.put(argumentName, mapper.convertValue(argumentValue, Map.class));
    }
    String s = mapper.writeValueAsString(argumentNameValueMap); 
}

如果我们将HttpServletRequest / ByteStream作为请求参数,上面的代码片段就会失败。

例如,对于字节 stream,我得到以下异常:

java.lang.IllegalArgumentException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile["inputStream"])
  

对于HttpServletRequest的请求类型,我收到 StackOverflow 错误。

实际上,我想避免使用这些类型的 arguments。但是我想不出任何正确处理这个问题的方法。

有人可以在这里帮忙吗?

Joy,如果你提出问题,尽量提供完整的MCVE ,不要让想帮你猜的志愿者继续猜测。 在这种情况下,您在序列化数据方面遇到了问题,但您既没有提及您使用的是哪种序列化技术或工具,也没有从您的代码中识别出它,因为方面建议使用 object mapper而您没有显示它是如何声明的。 我不明白为什么这么多开发人员选择简洁而不是清晰。

mapper.writeValueAsString(..)上进行一些谷歌搜索后,我发现您可能使用 Jackson。我假设这是真的。

  1. 因此,解决问题的一种方法是为有问题的类编写自定义序列化程序,请参阅本教程 通过调整映射器配置也可以避免一些序列化异常。

  2. 另一种方法是避免完全序列化(或“json-ising”)这些对象,而是将一些虚拟值或toString()的结果写入数据库,无论如何。 这是你问的吗? 然后你可以

    1. 只需保留 static 排除您方面的类列表或
    2. 构建一个动态列表,使用try / catch块并添加 Jackson 未能序列化到列表的类,下次避免对相同的 class 进行序列化,或者
    3. 总是使用try / catch ,回退到toString()

我认为 #1 总体上会更好,但因为你的问题是关于 AOP 而不是 Jackson(也根据你选择的标签),我将向你展示 #2.3。

进一步查看您的示例代码,它看起来有点奇怪:

  • 例如,由于void方法中的return mapper.writeValueAsString(..)语句,它永远不会像这样编译。
  • 您绑定returnValue但从不使用它。
  • 您在三个不同的地方调用codeSignature.getParameterNames() ,其中一个在循环中,而不是将值缓存在局部变量中。 那应该简化。
  • 您可以将签名转换为MethodSignature而不是更通用的CodeSignature 然后您就可以访问该方法的返回类型。 Spring AOP 无论如何都不支持拦截构造函数,只有 AspectJ 支持。 假设你使用 Spring AOP,你唯一可以拦截的就是方法。
  • 我不明白为什么你在每个方法参数值上调用mapper.convertValue(..) ,试图将它转换成Map 为什么不直接使用writeValueAsString(..)呢?
  • 您检查getParameterNames()是否为null ,但它永远不会返回null ,而是一个空数组。 所以这个检查是没有必要的。
  • 另请注意,只有在使用调试信息编译 class 时,您存储参数名称的整个想法才有效。 否则就不会有任何真正的参数名称,只有arg0arg1等代理项。因此,在实现这样的解决方案之前,您宁愿非常确定代码是以正确的方式编译的。
  • 在已经包含 JSON 对象的 map 上调用mapper.writeValueAsString(argumentNameValueMap)会导致像"foo"这样的字符串再次被双引号括起来,如"\"foo\"" ,这可能不是你想要的。 确保每个 object 只序列化一次。

这是我的MCVE

示例组件:

package de.scrum_master.spring.q64782403;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;

@Component
public class MyComponent {
  public void doSomething() {
    System.out.println("Doing something");
  }

  public int add(int a, int b) {
    System.out.println("Adding");
    return a+b;
  }

  public void someRequest(HttpServletRequest request, String parameter) {
    System.out.println("Handling request");
  }
  public void someByteStream(int index, ByteArrayInputStream stream) {
    System.out.println("Handling byte array input stream");
  }
  public String concatenate(String a, String b) {
    System.out.println("Concatenating");
    return a + " " + b;
  }
}

司机申请:

package de.scrum_master.spring.q64782403;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;

import java.io.ByteArrayInputStream;

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    try (ConfigurableApplicationContext context = SpringApplication.run(Application.class, args)) {
      doStuff(context);
    }
  }

  private static void doStuff(ConfigurableApplicationContext context) {
    MyComponent myComponent = context.getBean(MyComponent.class);
    myComponent.doSomething();
    myComponent.add(4, 5);
    myComponent.someByteStream(11, new ByteArrayInputStream(new byte[1024]));
    myComponent.someRequest(new MockHttpServletRequest("GET", "/my/request"), "foo");
    myComponent.concatenate("Hello", "world");
  }
}

请注意,对于这个虚拟应用程序,我只使用MockHttpServletRequest ,所以如果你想编译它,你需要添加org.springframework:spring-test作为编译依赖项。

方面:

package de.scrum_master.spring.q64782403;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Component
@Aspect
public class SerialiserAspect {
  ObjectMapper mapper = new ObjectMapper();

  @AfterReturning(
    value = "within(de.scrum_master.spring.q64782403..*)",
    returning = "returnValue"
  )
  public void capturePayloadWhileReturning(JoinPoint joinPoint, Object returnValue)
    throws JsonProcessingException
  {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    String[] argumentNames = signature.getParameterNames();
    Object[] argumentValues = joinPoint.getArgs();
    assert argumentNames.length == argumentValues.length;

    System.out.println(joinPoint);
    System.out.println("  Argument names  = " + Arrays.deepToString(argumentNames));
    System.out.println("  Argument types  = " + Arrays.deepToString(signature.getParameterTypes()));
    System.out.println("  Argument values = " + Arrays.deepToString(argumentValues));
    System.out.println("  Return type     = " + signature.getReturnType());
    System.out.println("  Return value    = " + returnValue);

    Map<String, Object> arguments = new HashMap<>();
    for (int i = 0; i < argumentNames.length; i++) {
      String argumentName = argumentNames[i];
      Object argumentValue = argumentValues[i];
      try {
        mapper.writeValueAsString(argumentValue);
      }
      catch (JsonProcessingException e) {
        argumentValue = argumentValue.toString();
        System.out.println("Serialisation problem, falling back to toString():\n  " + e);
      }
      arguments.put(argumentName, argumentValue);
    }
    System.out.println(mapper.writeValueAsString(arguments));
  }
}

记录连接点 arguments 和返回值到控制台的第一个块只是为了帮助您了解方面在做什么。

控制台日志:

2020-11-12 10:04:39.522  INFO 19704 --- [           main] d.s.spring.q64782403.Application         : Started Application in 4.49 seconds (JVM running for 6.085)
Doing something
execution(void de.scrum_master.spring.q64782403.MyComponent.doSomething())
  Argument names  = []
  Argument types  = []
  Argument values = []
  Return type     = void
  Return value    = null
{}
Adding
execution(int de.scrum_master.spring.q64782403.MyComponent.add(int,int))
  Argument names  = [a, b]
  Argument types  = [int, int]
  Argument values = [4, 5]
  Return type     = int
  Return value    = 9
{"a":4,"b":5}
Handling byte array input stream
execution(void de.scrum_master.spring.q64782403.MyComponent.someByteStream(int,ByteArrayInputStream))
  Argument names  = [index, stream]
  Argument types  = [int, class java.io.ByteArrayInputStream]
  Argument values = [11, java.io.ByteArrayInputStream@1e3ff233]
  Return type     = void
  Return value    = null
Serialisation problem, falling back to toString():
  com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
{"stream":"java.io.ByteArrayInputStream@1e3ff233","index":11}
Handling request
execution(void de.scrum_master.spring.q64782403.MyComponent.someRequest(HttpServletRequest,String))
  Argument names  = [request, parameter]
  Argument types  = [interface javax.servlet.http.HttpServletRequest, class java.lang.String]
  Argument values = [org.springframework.mock.web.MockHttpServletRequest@9accff0, foo]
  Return type     = void
  Return value    = null
Serialisation problem, falling back to toString():
  com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.util.Collections$3 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.mock.web.MockHttpServletRequest["servletContext"]->org.springframework.mock.web.MockServletContext["servletNames"])
{"request":"org.springframework.mock.web.MockHttpServletRequest@9accff0","parameter":"foo"}
Concatenating
execution(String de.scrum_master.spring.q64782403.MyComponent.concatenate(String,String))
  Argument names  = [a, b]
  Argument types  = [class java.lang.String, class java.lang.String]
  Argument values = [Hello, world]
  Return type     = class java.lang.String
  Return value    = Hello world
{"a":"Hello","b":"world"}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM