diff --git a/core/src/main/java/com/taobao/arthas/core/mcp/util/McpObjectVOFilter.java b/core/src/main/java/com/taobao/arthas/core/mcp/util/McpObjectVOFilter.java index a669f5211a2..7a1ff38581e 100644 --- a/core/src/main/java/com/taobao/arthas/core/mcp/util/McpObjectVOFilter.java +++ b/core/src/main/java/com/taobao/arthas/core/mcp/util/McpObjectVOFilter.java @@ -1,8 +1,5 @@ package com.taobao.arthas.core.mcp.util; -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONFactory; -import com.alibaba.fastjson2.JSONWriter; import com.alibaba.fastjson2.filter.ValueFilter; import com.taobao.arthas.core.GlobalOptions; import com.taobao.arthas.core.command.model.ObjectVO; @@ -93,13 +90,7 @@ private String drawObjectView(ObjectVO objectVO) { */ private String drawJsonView(Object object) { try { - JSONWriter.Context context = JSONFactory.createWriteContext(); - context.setMaxLevel(4097); - context.config(JSONWriter.Feature.IgnoreErrorGetter, true); - context.config(JSONWriter.Feature.ReferenceDetection, true); - context.config(JSONWriter.Feature.IgnoreNonFieldGetter, true); - context.config(JSONWriter.Feature.WriteNonStringKeyAsString, true); - return JSON.toJSONString(object, context); + return ObjectView.toJsonString(object); } catch (Exception e) { logger.debug("ObjectView-style serialization failed, using toString: {}", e.getMessage()); return objectToString(object); diff --git a/core/src/main/java/com/taobao/arthas/core/view/ObjectView.java b/core/src/main/java/com/taobao/arthas/core/view/ObjectView.java index 6d7f6bf8cd6..5e7d1ebbed0 100644 --- a/core/src/main/java/com/taobao/arthas/core/view/ObjectView.java +++ b/core/src/main/java/com/taobao/arthas/core/view/ObjectView.java @@ -4,6 +4,9 @@ import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.writer.FieldWriter; +import com.alibaba.fastjson2.writer.ObjectWriterCreator; +import com.alibaba.fastjson2.writer.ObjectWriterProvider; import com.taobao.arthas.common.ArthasConstants; import com.taobao.arthas.core.GlobalOptions; import com.taobao.arthas.core.command.model.ObjectVO; @@ -25,6 +28,25 @@ public class ObjectView implements View { public static final int MAX_DEEP = 4; private static final Logger logger = LoggerFactory.getLogger(ObjectView.class); private final static int MAX_OBJECT_LENGTH = ArthasConstants.MAX_HTTP_CONTENT_LENGTH; + private static final ObjectWriterProvider JSON_OBJECT_WRITER_PROVIDER = new ObjectWriterProvider( + new ObjectWriterCreator() { + @Override + protected void setDefaultValue(List fieldWriters, Class objectClass) { + // fastjson2 默认会通过无参构造函数创建一个对象来提取字段默认值(用于 NotWriteDefaultValue 等能力), + // 这可能触发目标对象的构造逻辑(比如单例守卫、资源初始化等),在 Arthas 里属于不可接受的副作用。 + // 这里直接禁用该行为,只基于现有对象进行序列化。 + } + }); + + public static String toJsonString(Object object) { + JSONWriter.Context context = new JSONWriter.Context(JSON_OBJECT_WRITER_PROVIDER); + context.setMaxLevel(4097); + context.config(JSONWriter.Feature.IgnoreErrorGetter, + JSONWriter.Feature.ReferenceDetection, + JSONWriter.Feature.IgnoreNonFieldGetter, + JSONWriter.Feature.WriteNonStringKeyAsString); + return JSON.toJSONString(object, context); + } private final Object object; private final int deep; @@ -54,7 +76,7 @@ public String draw() { StringBuilder buf = new StringBuilder(); try { if (GlobalOptions.isUsingJson) { - return JSON.toJSONString(object, JSONWriter.Feature.IgnoreErrorGetter); + return toJsonString(object); } renderObject(object, 0, deep, buf); return buf.toString(); diff --git a/core/src/test/java/com/taobao/arthas/core/view/ObjectViewTest.java b/core/src/test/java/com/taobao/arthas/core/view/ObjectViewTest.java index cda9a6eaf4c..5efb1008c87 100644 --- a/core/src/test/java/com/taobao/arthas/core/view/ObjectViewTest.java +++ b/core/src/test/java/com/taobao/arthas/core/view/ObjectViewTest.java @@ -1,5 +1,6 @@ package com.taobao.arthas.core.view; +import com.taobao.arthas.core.GlobalOptions; import org.junit.Assert; import org.junit.Test; @@ -206,6 +207,22 @@ public void testEnumList() { Assert.assertEquals(expected, objectView.draw()); } + @Test + public void testJsonFormatDoNotCreateNewInstance() { + boolean old = GlobalOptions.isUsingJson; + try { + JsonFormatSingleton singleton = JsonFormatSingleton.getInstance(); + GlobalOptions.isUsingJson = true; + + ObjectView objectView = new ObjectView(new Object[] { singleton }, 3); + String output = objectView.draw(); + Assert.assertFalse(output.startsWith("ERROR DATA!!!")); + Assert.assertEquals(1, JsonFormatSingleton.constructorCalls); + } finally { + GlobalOptions.isUsingJson = old; + } + } + @Test public void testDate() { Date d = new Date(1531204354961L - TimeZone.getDefault().getRawOffset() @@ -324,4 +341,24 @@ public void setJ(String j) { public enum EnumDemo { DEMO; } + + public static class JsonFormatSingleton { + private static final JsonFormatSingleton INSTANCE; + private static volatile int constructorCalls = 0; + + static { + INSTANCE = new JsonFormatSingleton(); + } + + private JsonFormatSingleton() { + constructorCalls++; + if (constructorCalls > 1) { + throw new IllegalStateException("JsonFormatSingleton is created!"); + } + } + + public static JsonFormatSingleton getInstance() { + return INSTANCE; + } + } }