将HttpServletRequest转换为JSON

时间:2015-11-24 17:44:59

标签: java json spring gson

在我的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不应该这样做因序列化问题而成为应用程序故障点。

谢谢。

2 个答案:

答案 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"
}