[英]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。我假设这是真的。
因此,解决问题的一种方法是为有问题的类编写自定义序列化程序,请参阅本教程。 通过调整映射器配置也可以避免一些序列化异常。
另一种方法是避免完全序列化(或“json-ising”)这些对象,而是将一些虚拟值或toString()
的结果写入数据库,无论如何。 这是你问的吗? 然后你可以
try
/ catch
块并添加 Jackson 未能序列化到列表的类,下次避免对相同的 class 进行序列化,或者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
,而是一个空数组。 所以这个检查是没有必要的。arg0
、 arg1
等代理项。因此,在实现这样的解决方案之前,您宁愿非常确定代码是以正确的方式编译的。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.