在我的Spring rest应用程序中,我在方面记录每个api端点参数。
@Aspect
@Component
public class EndpointsAspect {
@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * *(..))")
public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
Map<String, Object> log = new HashMap<>();
String[] parameterNames = methodSignature.getParameterNames();
Object[] parameterValues = joinPoint.getArgs();
Map<String, Object> arguments = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
arguments.put(parameterNames[i], parameterValues[i]);
}
log.put("Method arguments", arguments);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(log);
...
Object retVal = joinPoint.proceed();
}
}
它工作正常,直到adviced方法的一个参数有HttpServletRequest类型的参数
@RequestMapping("/info")
public String index(HttpServletRequest request) {
return "Info";
}
在这种情况下会引发java.lang.StackOverflowError。
我知道这与HttpServlterRequest变量(可能是一些不定式循环)有某种关系,但是如何解决这个问题呢?
何以限制gson深度?
我已经查看了一些解决方案(注释应该转换为带有注释的json的字段或类),但它不适合我,这应该是适用于所有类和案例的通用解决方案(例如,我用一些注释来注释HttpServletRequest,或者将它包含在gson排除策略中,因为谁将这些类转换为json),我需要将日志数据作为json,但是logger不应该这样做因序列化问题而成为应用程序故障点。
谢谢。
答案 0 :(得分:1)
我找到了一些解决方案并回答了我的问题。
对于json序列化程序,我使用flexjson库(http://flexjson.sourceforge.net/)。它支持具有双向关系的类的序列化,无需任何注释和其他额外工作。
在HttpServletRequest的情况下,我在try catch块中包装序列化并记录,例如,参数“request”无法序列化。
@Around("execution(@org.springframework.web.bind.annotation.RequestMapping * *(..))")
public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Map<String, Object> log = new HashMap<>();
log.put("Method", joinPoint.getSignature().toString());
String[] parameterNames = methodSignature.getParameterNames();
Object[] parameterValues = joinPoint.getArgs();
Map<String, Object> arguments = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
// Check if argument can be serialized and put arguments to argument list if possible. For example, HttpServletRequest cannot be serialized
try {
JSONSerializer serializer = new JSONSerializer();
String json = serializer.prettyPrint(true).deepSerialize(parameterValues[i]);
arguments.put(parameterNames[i], parameterValues[i]);
}
catch (Exception serializerException) {
arguments.put(parameterNames[i], "Couldn't serialize argument. "+serializerException.getMessage());
}
}
log.put("Method arguments", arguments);
...
try {
JSONSerializer serializer = new JSONSerializer();
String json = serializer.prettyPrint(true).deepSerialize(log);
logger.info(json);
}
catch (Exception e) {
logger.error("Could not serialize data. "+e.getMessage());
}
...
}
日志看起来像这样:
{
"Path": "/v1/users/1",
"Http Status": "200 OK",
"Method arguments": {
"request": "Couldn't serialize argument. Error trying to deepSerialize",
"id": 1
},
"Headers": {
"accept-language": "en-US,en;q=0.8",
"cookie": "lbannounce=; ServiceTransactionSuccess=false; ServiceTransactionAmount=false; ServiceTransactionWidgetType=false; ServiceTransactionDomain=false; _ga=GA1.1.1011235846.1448355706",
"host": "localhost:8080",
"connection": "keep-alive",
"accept-encoding": "gzip, deflate, sdch",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"user-agent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36"
},
"Return Value": {
"class": "api.domain.User",
"email": "user@mail.com",
"id": 1,
"username": "user111"
},
"Ip": "0:0:0:0:0:0:0:1",
"Http Method": "GET",
"Method": "User api.web.UserController.user(int,HttpServletRequest)"
}
答案 1 :(得分:0)
我不知道为了更深入地记录每个参数值有多重要,但是一个解决方法可能是为每个参数而不是对象添加字符串表示:
for (int i = 0; i < parameterNames.length; i++) {
arguments.put(parameterNames[i], parameterValues[i].toString());
}
如果是HttpServletRequest
你可能会得到一个糟糕的日志,但这是一个日志!对于你自己的类,你可以覆盖toString()方法来定义你想要的东西(顺便说一下,这不是一个糟糕的做法)。
如果它不适合您,您可以为外部类(不在您的包根目录中)添加一个hack,例如:
for (int i = 0; i < parameterNames.length; i++) {
Object value = parameterValues[i];
if (!"com.mypackage.foo.bar".equals(value.getClass().getPackage().getName()) {
value = value.toString();
}
arguments.put(parameterNames[i], value);
}
这是一个黑客,但它可能会有所帮助。
但是如果你不喜欢这个简单的解决方案,那么漂亮的可能是使用Gson序列化器(在这里作为expianed:https://sites.google.com/site/gson/gson-user-guide#TOC-Using-Gson),并且你负责实现热的HttpServletRequest将被序列化
GsonBuilder gson = new GsonBuilder();
gson.registerTypeAdapter(HttpServletRequest.class, new MyCustonSerializer());
...
class MyCustonSerializer implements JsonSerializer<HttpServletRequest> {
public JsonElement serialize(HttpServletRequest src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
编辑: - 添加一个完整的示例 -
public class GsonTest {
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(Integer.class, new MyCustonHttpServletRequestSerializer());
//gb.registerTypeAdapter(Object.class, null);
Map<String, Object> map = new HashMap<String, Object>();
map.put("a", "String a");
map.put("b", new Integer(3));
map.put("c", new Person("John", "Doe"));
Gson gson = gb.setPrettyPrinting().create();
String json = gson.toJson(map);
System.out.println(json);
}
}
class MyCustonHttpServletRequestSerializer implements JsonSerializer<Integer> {
@Override
public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive("Number " + src);
}
}
class Person {
private String name;
private String lastName;
public Person(String name, String lastName) {
this.name = name;
this.lastName = lastName;
}
}
并输出
{
"b": "Number 3",
"c": {
"name": "John",
"lastName": "Doe"
},
"a": "String a"
}