Joy,如果您提出问题,请尽量提供完整的MCVE,不要让想帮助您猜测的志愿者猜测。在这种情况下,您在序列化数据时遇到了问题,但您既没有提到您使用哪种序列化技术或工具,也没有从您的代码中识别出它,因为方面建议使用了一个对象 mapper,而您没有展示它是如何声明的。我不明白为什么这么多开发人员选择简洁而不是清晰。
在mapper.writeValueAsString(..) 上进行了一些谷歌搜索后,我发现您可能使用的是 Jackson。我会假设这是真的。
-
因此,解决问题的一种方法是为有问题的类编写自定义序列化程序,请参阅this tutorial。通过调整映射器配置也可以避免一些序列化异常。
-
另一种方法是完全避免序列化(或“json-ising”)这些对象,而是将一些虚拟值或toString() 的结果写入数据库,无论如何。这是你问的吗?那你可以
- 只需在您的方面保留一个静态的排除类列表或
- 构建一个动态列表,使用
try/catch 块并将 Jackson 无法序列化的类添加到列表中,下次避免对同一类进行序列化,或者
- 始终使用
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 等代理名称。因此,在实施这样的解决方案之前,您宁愿非常确定代码是否以正确的方式编译。
- 在已经包含 JSON 对象的地图上调用
mapper.writeValueAsString(argumentNameValueMap) 会导致像 "foo" 这样的字符串再次像 "\"foo\"" 这样被双引号括起来,这可能不是您想要的。确保每个对象只序列化一次。
这是我的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));
}
}
将连接点、参数和返回值记录到控制台的第一块只是为了帮助您了解方面在做什么。
控制台日志:
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"}