diff --git a/arthas-grpc-services/pom.xml b/arthas-grpc-services/pom.xml
new file mode 100644
index 000000000..971ae270d
--- /dev/null
+++ b/arthas-grpc-services/pom.xml
@@ -0,0 +1,145 @@
+
+
+
+ arthas-all
+ com.taobao.arthas
+ ${revision}
+ ../pom.xml
+
+ 4.0.0
+ arthas-grpc-services
+ arthas-grpc-services
+ https://github.com/alibaba/arthas
+
+ 1.8
+ 1.46.0
+
+
+
+
+ io.grpc
+ grpc-bom
+ ${grpc.version}
+ pom
+ import
+
+
+
+
+
+
+
+ com.taobao.arthas
+ arthas-common
+ ${project.version}
+
+
+ com.taobao.arthas
+ arthas-vmtool
+ ${project.version}
+
+
+ io.netty
+ netty-codec-http
+
+
+ com.alibaba.arthas
+ arthas-repackage-logger
+
+
+ io.grpc
+ grpc-netty
+ provided
+
+
+ io.grpc
+ grpc-services
+ provided
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+ provided
+ true
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
+ ch.qos.logback
+ logback-core
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.apache.httpcomponents
+ httpmime
+ 4.5.2
+ test
+
+
+
+
+
+ mac
+
+
+ mac
+
+
+
+ osx-x86_64
+
+
+
+
+ ${project.artifactId}
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.6.2
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ 0.6.1
+
+ ${basedir}/src/main/proto
+ com.google.protobuf:protoc:3.11.0:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:1.28.0:exe:${os.detected.classifier}
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/arthas-grpc-services/src/main/java/io/arthas/services/JavaObjectConverter.java b/arthas-grpc-services/src/main/java/io/arthas/services/JavaObjectConverter.java
new file mode 100644
index 000000000..933062304
--- /dev/null
+++ b/arthas-grpc-services/src/main/java/io/arthas/services/JavaObjectConverter.java
@@ -0,0 +1,198 @@
+package io.arthas.services;
+
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import io.arthas.api.ArthasServices.ArrayElement;
+import io.arthas.api.ArthasServices.ArrayValue;
+import io.arthas.api.ArthasServices.BasicValue;
+import io.arthas.api.ArthasServices.CollectionValue;
+import io.arthas.api.ArthasServices.CollectionValue.Builder;
+import io.arthas.api.ArthasServices.JavaField;
+import io.arthas.api.ArthasServices.JavaFields;
+import io.arthas.api.ArthasServices.JavaObject;
+import io.arthas.api.ArthasServices.MapEntry;
+import io.arthas.api.ArthasServices.MapValue;
+import io.arthas.api.ArthasServices.NullValue;
+import io.arthas.api.ArthasServices.UnexpandedObject;
+
+
+
+public class JavaObjectConverter {
+ private static final int MAX_DEPTH = 5;
+
+ public static JavaObject toJavaObject(Object obj) {
+ return toJavaObject(obj, 0);
+ }
+
+ public static JavaObject toJavaObject(Object obj, int depth) {
+ if (depth >= MAX_DEPTH) {
+ return null;
+ }
+
+ if (obj == null) {
+ return JavaObject.newBuilder().setNullValue(NullValue.getDefaultInstance()).build();
+ }
+
+ JavaObject.Builder objectBuilder = JavaObject.newBuilder();
+ Class extends Object> objClazz = obj.getClass();
+ objectBuilder.setClassName(objClazz.getName());
+
+ // 基础类型
+ if (isBasicType(objClazz)) {
+ return objectBuilder.setBasicValue(createBasicValue(obj)).build();
+ } else if (obj instanceof Collection) { // 集合
+ return objectBuilder.setCollection(createCollectionValue((Collection>) obj, depth)).build();
+ } else if (obj instanceof Map) { // map
+ return objectBuilder.setMap(createMapValue((Map, ?>) obj, depth)).build();
+ } else if (objClazz.isArray()) {
+ return objectBuilder.setArrayValue(toArrayValue(obj, depth)).build();
+ }
+
+ Field[] fields = objClazz.getDeclaredFields();
+ List javaFields = new ArrayList<>();
+
+ for (Field field : fields) {
+ field.setAccessible(true);
+ JavaField.Builder fieldBuilder = JavaField.newBuilder();
+ fieldBuilder.setName(field.getName());
+
+ try {
+ Object fieldValue = field.get(obj);
+ Class> fieldType = field.getType();
+
+ if (fieldValue == null) {
+ fieldBuilder.setNullValue(NullValue.newBuilder().setClassName(fieldType.getName()).build());
+ } else if (fieldType.isArray()) {
+ ArrayValue arrayValue = toArrayValue(fieldValue, depth + 1);
+ if (arrayValue != null) {
+ fieldBuilder.setArrayValue(arrayValue);
+ } else {
+ fieldBuilder.setUnexpandedObject(
+ UnexpandedObject.newBuilder().setClassName(fieldType.getName()).build());
+ }
+ } else if (fieldType.isPrimitive() || isBasicType(fieldType)) {
+ BasicValue basicValue = createBasicValue(fieldValue);
+ fieldBuilder.setBasicValue(basicValue);
+ } else if (fieldValue instanceof Collection) { // 集合
+ fieldBuilder.setCollection(createCollectionValue((Collection>) fieldValue, depth));
+ } else if (fieldValue instanceof Map) { // map
+ fieldBuilder.setMap(createMapValue((Map, ?>) fieldValue, depth));
+ } else {
+ JavaObject nestedObject = toJavaObject(fieldValue, depth + 1);
+ if (nestedObject != null) {
+ fieldBuilder.setObjectValue(nestedObject);
+ } else {
+ fieldBuilder.setUnexpandedObject(
+ UnexpandedObject.newBuilder().setClassName(fieldType.getName()).build());
+ }
+ }
+ } catch (IllegalAccessException e) {
+ // TODO ignore ?
+ }
+ javaFields.add(fieldBuilder.build());
+ }
+
+ objectBuilder.setFields(JavaFields.newBuilder().addAllFields(javaFields).build());
+ return objectBuilder.build();
+ }
+
+ private static ArrayValue toArrayValue(Object array, int depth) {
+ if (array == null || depth >= MAX_DEPTH) {
+ return null;
+ }
+
+ ArrayValue.Builder arrayBuilder = ArrayValue.newBuilder();
+ Class> componentType = array.getClass().getComponentType();
+
+ arrayBuilder.setClassName(componentType.getName());
+
+ int length = Array.getLength(array);
+ for (int i = 0; i < length; i++) {
+ Object element = Array.get(array, i);
+
+ if (element != null) {
+ if (componentType.isArray()) {
+ ArrayValue nestedArrayValue = toArrayValue(element, depth + 1);
+ if (nestedArrayValue == null) {
+ arrayBuilder.addElements(ArrayElement.newBuilder().setArrayValue(nestedArrayValue));
+ } else {
+ arrayBuilder.addElements(ArrayElement.newBuilder().setUnexpandedObject(
+ UnexpandedObject.newBuilder().setClassName(element.getClass().getName()).build()));
+ }
+
+ } else if (componentType.isPrimitive() || isBasicType(componentType)) {
+ BasicValue basicValue = createBasicValue(element);
+ arrayBuilder.addElements(ArrayElement.newBuilder().setBasicValue(basicValue));
+ } else {
+ JavaObject nestedObject = toJavaObject(element, depth + 1);
+ if (nestedObject != null) {
+ arrayBuilder.addElements(ArrayElement.newBuilder().setObjectValue(nestedObject));
+ } else {
+ arrayBuilder.addElements(ArrayElement.newBuilder().setUnexpandedObject(
+ UnexpandedObject.newBuilder().setClassName(element.getClass().getName()).build()));
+ }
+
+ }
+ } else {
+ arrayBuilder.addElements(ArrayElement.newBuilder()
+ .setNullValue(NullValue.newBuilder().setClassName(componentType.getName()).build()));
+ }
+ }
+
+ return arrayBuilder.build();
+ }
+
+ private static MapValue createMapValue(Map, ?> map, int depth) {
+ MapValue.Builder builder = MapValue.newBuilder();
+
+ for (Entry, ?> entry : map.entrySet()) {
+ MapEntry mapEntry = MapEntry.newBuilder().setKey(toJavaObject(entry.getKey(), depth))
+ .setValue(toJavaObject(entry.getValue(), depth)).build();
+ builder.addEntries(mapEntry);
+ }
+ return builder.build();
+ }
+
+ private static CollectionValue createCollectionValue(Collection> collection, int depth) {
+ Builder builder = CollectionValue.newBuilder();
+ for (Object o : collection) {
+ builder.addElements(toJavaObject(o, depth));
+ }
+ return builder.build();
+ }
+
+ private static BasicValue createBasicValue(Object value) {
+ BasicValue.Builder builder = BasicValue.newBuilder();
+
+ if (value instanceof Integer) {
+ builder.setInt((int) value);
+ } else if (value instanceof Long) {
+ builder.setLong((long) value);
+ } else if (value instanceof Float) {
+ builder.setFloat((float) value);
+ } else if (value instanceof Double) {
+ builder.setDouble((double) value);
+ } else if (value instanceof Boolean) {
+ builder.setBoolean((boolean) value);
+ } else if (value instanceof String) {
+ builder.setString((String) value);
+ }
+
+ return builder.build();
+ }
+
+ private static boolean isBasicType(Class> clazz) {
+ if (String.class.equals(clazz) || Integer.class.equals(clazz) || Long.class.equals(clazz)
+ || Float.class.equals(clazz) || Double.class.equals(clazz) || Boolean.class.equals(clazz)) {
+ return true;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/arthas-grpc-services/src/main/java/io/arthas/services/ObjectServiceImpl.java b/arthas-grpc-services/src/main/java/io/arthas/services/ObjectServiceImpl.java
new file mode 100644
index 000000000..aaba1c8b4
--- /dev/null
+++ b/arthas-grpc-services/src/main/java/io/arthas/services/ObjectServiceImpl.java
@@ -0,0 +1,139 @@
+package io.arthas.services;
+
+
+import java.lang.instrument.Instrumentation;
+import java.lang.invoke.MethodHandles;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.taobao.arthas.common.VmToolUtils;
+
+import arthas.VmTool;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import io.arthas.api.ObjectServiceGrpc.ObjectServiceImplBase;
+import io.arthas.api.ArthasServices.JavaObject;
+import io.arthas.api.ArthasServices.ObjectQuery;
+import io.arthas.api.ArthasServices.ObjectQueryResult;
+import io.arthas.api.ArthasServices.ObjectQueryResult.Builder;
+
+public class ObjectServiceImpl extends ObjectServiceImplBase {
+ private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass().getName());
+
+ private VmTool vmTool;
+ private Instrumentation inst;
+
+ public ObjectServiceImpl(Instrumentation inst, String libDir) {
+ this.inst = inst;
+
+ try {
+ String detectLibName = VmToolUtils.detectLibName();
+ String vmToolLibPath = Paths.get(libDir, detectLibName).toString();
+
+ logger.info("vmtool lib path: {}", vmToolLibPath);
+
+ vmTool = VmTool.getInstance(vmToolLibPath);
+ } catch (Throwable e) {
+ logger.error("init vmtool error", e);
+ }
+ }
+
+ @Override
+ public void query(ObjectQuery query, StreamObserver responseObserver) {
+ if (vmTool == null) {
+ throw Status.UNAVAILABLE.withDescription("vmtool can not work").asRuntimeException();
+ }
+
+ String className = query.getClassName();
+ String classLoaderHash = query.getClassLoaderHash();
+ String classLoaderClass = query.getClassLoaderClass();
+ int limit = query.getLimit();
+ int depth = query.getDepth();
+
+ // 如果只传递了 class name 参数,则jvm 里可能有多个同名的 class,需要全部查找
+ if (isEmpty(classLoaderHash) && isEmpty(classLoaderClass)) {
+ List> foundClassList = new ArrayList<>();
+ for (Class> clazz : inst.getAllLoadedClasses()) {
+ if (clazz.getName().equals(className)) {
+ foundClassList.add(clazz);
+ }
+ }
+
+ // 没找到
+ if (foundClassList.size() == 0) {
+ responseObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false)
+ .setMessage("can not find class: " + className).build());
+ responseObserver.onCompleted();
+ return;
+ } else if (foundClassList.size() > 1) {
+ String message = "found more than one class: " + className;
+ responseObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false).setMessage(message).build());
+ responseObserver.onCompleted();
+ return;
+ } else { // 找到了指定的 类
+ Object[] instances = vmTool.getInstances(foundClassList.get(0), limit);
+ Builder builder = ObjectQueryResult.newBuilder().setSuccess(true);
+ for (Object obj : instances) {
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(obj, depth);
+ builder.addObjects(javaObject);
+ }
+ responseObserver.onNext(builder.build());
+ responseObserver.onCompleted();
+ return;
+ }
+ }
+
+ // 有指定 classloader hash 或者 classloader className
+
+ Class> foundClass = null;
+
+ for (Class> clazz : inst.getAllLoadedClasses()) {
+ if (!clazz.getName().equals(className)) {
+ continue;
+ }
+
+ ClassLoader classLoader = clazz.getClassLoader();
+
+ if (classLoader == null) {
+ continue;
+ }
+
+ if (!isEmpty(classLoaderHash)) {
+ String hex = Integer.toHexString(classLoader.hashCode());
+ if (classLoaderHash.equals(hex)) {
+ foundClass = clazz;
+ break;
+ }
+ }
+
+ if (!isEmpty(classLoaderClass) && classLoaderClass.equals(classLoader.getClass().getName())) {
+ foundClass = clazz;
+ break;
+ }
+ }
+ // 没找到类
+ if (foundClass == null) {
+ responseObserver.onNext(ObjectQueryResult.newBuilder().setSuccess(false)
+ .setMessage("can not find class: " + className).build());
+ responseObserver.onCompleted();
+ return;
+ }
+
+ Object[] instances = vmTool.getInstances(foundClass, limit);
+ Builder builder = ObjectQueryResult.newBuilder().setSuccess(true);
+ for (Object obj : instances) {
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(obj, depth);
+ builder.addObjects(javaObject);
+ }
+ responseObserver.onNext(builder.build());
+ responseObserver.onCompleted();
+ }
+
+ public static boolean isEmpty(Object str) {
+ return str == null || "".equals(str);
+ }
+}
diff --git a/arthas-grpc-services/src/main/proto/ArthasServices.proto b/arthas-grpc-services/src/main/proto/ArthasServices.proto
new file mode 100644
index 000000000..c3dd7ba0d
--- /dev/null
+++ b/arthas-grpc-services/src/main/proto/ArthasServices.proto
@@ -0,0 +1,106 @@
+syntax = "proto3";
+
+package io.arthas.api;
+
+message BasicValue {
+ oneof value {
+ int32 int = 1;
+ int64 long = 2;
+ float float = 3;
+ double double = 4;
+ bool boolean = 5;
+ string string = 6;
+ }
+}
+
+message ArrayElement {
+ oneof element {
+ BasicValue basicValue = 1;
+ JavaObject objectValue = 2;
+ ArrayValue arrayValue = 3;
+ NullValue nullValue = 4;
+ UnexpandedObject unexpandedObject = 5;
+ }
+}
+
+message ArrayValue {
+ string className = 1;
+ repeated ArrayElement elements = 2;
+}
+
+message NullValue {
+ string className = 1;
+}
+
+message UnexpandedObject {
+ string className = 1;
+}
+
+message CollectionValue {
+ string className = 1;
+ repeated JavaObject elements = 2;
+}
+
+message MapEntry {
+ JavaObject key = 1;
+ JavaObject value = 2;
+}
+
+message MapValue {
+ string className = 1;
+ repeated MapEntry entries = 2;
+}
+
+message JavaField {
+ string name = 1;
+
+ oneof value {
+ JavaObject objectValue = 2;
+ BasicValue basicValue = 3;
+ ArrayValue arrayValue = 4;
+ NullValue nullValue = 5;
+ CollectionValue collection = 6;
+ MapValue map = 7;
+ UnexpandedObject unexpandedObject = 8;
+ }
+}
+
+
+message JavaFields {
+ repeated JavaField fields = 1;
+}
+
+message JavaObject {
+ string className = 1;
+
+ oneof value {
+ JavaObject objectValue = 2;
+ BasicValue basicValue = 3;
+ ArrayValue arrayValue = 4;
+ NullValue nullValue = 5;
+ CollectionValue collection = 6;
+ MapValue map = 7;
+ UnexpandedObject unexpandedObject = 8;
+ JavaFields fields = 9;
+ }
+}
+
+
+message ObjectQuery {
+ string className = 1;
+ string express = 2;
+ string ClassLoaderHash = 3;
+ string classLoaderClass = 4;
+ int32 limit = 5;
+ int32 depth = 6;
+}
+
+message ObjectQueryResult {
+ bool success = 1;
+ string message = 2;
+ repeated JavaObject objects = 3;
+}
+
+service ObjectService {
+ rpc query(ObjectQuery) returns (ObjectQueryResult);
+}
\ No newline at end of file
diff --git a/arthas-grpc-services/src/test/java/io/arthas/services/JavaObjectConverterTest.java b/arthas-grpc-services/src/test/java/io/arthas/services/JavaObjectConverterTest.java
new file mode 100644
index 000000000..5988b15ce
--- /dev/null
+++ b/arthas-grpc-services/src/test/java/io/arthas/services/JavaObjectConverterTest.java
@@ -0,0 +1,301 @@
+package io.arthas.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import io.arthas.api.ArthasServices.ArrayElement;
+import io.arthas.api.ArthasServices.ArrayValue;
+import io.arthas.api.ArthasServices.BasicValue;
+import io.arthas.api.ArthasServices.CollectionValue;
+import io.arthas.api.ArthasServices.JavaField;
+import io.arthas.api.ArthasServices.JavaFields;
+import io.arthas.api.ArthasServices.JavaObject;
+import io.arthas.api.ArthasServices.MapEntry;
+import io.arthas.api.ArthasServices.MapValue;
+import io.arthas.api.ArthasServices.NullValue;
+
+public class JavaObjectConverterTest {
+
+ @Test
+ public void testString() {
+ JavaObject javaObject = JavaObjectConverter.toJavaObject("sss");
+ System.err.println(javaObject);
+ assertNotNull(javaObject);
+ assertEquals("java.lang.String", javaObject.getClassName());
+ assertTrue(javaObject.hasBasicValue());
+ assertEquals("sss", javaObject.getBasicValue().getString());
+ }
+
+ @Test
+ public void testToJavaObjectWithBasicType() {
+ int intValue = 123;
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(intValue);
+ assertNotNull(javaObject);
+ assertEquals("java.lang.Integer", javaObject.getClassName());
+ assertTrue(javaObject.hasBasicValue());
+ assertEquals(intValue, javaObject.getBasicValue().getInt());
+ }
+
+ @Test
+ public void testToJavaObjectWithArray() {
+ int[] intArray = { 1, 2, 3 };
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(intArray);
+ assertNotNull(javaObject);
+ assertEquals("[I", javaObject.getClassName());
+ assertTrue(javaObject.hasArrayValue());
+ ArrayValue arrayValue = javaObject.getArrayValue();
+ assertNotNull(arrayValue);
+ assertEquals("int", arrayValue.getClassName());
+ assertEquals(3, arrayValue.getElementsCount());
+
+ ArrayElement element = arrayValue.getElements(1);
+ assertEquals(2, element.getBasicValue().getInt());
+ }
+
+ @Test
+ public void testToJavaObjectWithCollection() {
+ List stringList = new ArrayList<>();
+ stringList.add("foo");
+ stringList.add("bar");
+ stringList.add("baz");
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(stringList);
+ assertNotNull(javaObject);
+ assertEquals("java.util.ArrayList", javaObject.getClassName());
+ assertTrue(javaObject.hasCollection());
+ CollectionValue collectionValue = javaObject.getCollection();
+ assertNotNull(collectionValue);
+ assertEquals(3, collectionValue.getElementsCount());
+
+ JavaObject object3 = collectionValue.getElements(2);
+ assertEquals("baz", object3.getBasicValue().getString());
+ }
+
+ @Test
+ public void testToJavaObjectWithMap() {
+ Map stringIntegerMap = new HashMap<>();
+ stringIntegerMap.put("one", 1);
+ stringIntegerMap.put("two", 2);
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(stringIntegerMap);
+ assertNotNull(javaObject);
+ assertEquals("java.util.HashMap", javaObject.getClassName());
+ assertTrue(javaObject.hasMap());
+ MapValue mapValue = javaObject.getMap();
+ assertNotNull(mapValue);
+ assertEquals(2, mapValue.getEntriesCount());
+
+ MapEntry mapEntry = mapValue.getEntries(0);
+
+ JavaObject key = mapEntry.getKey();
+ assertEquals("one", key.getBasicValue().getString());
+ JavaObject value = mapEntry.getValue();
+ assertEquals(1, value.getBasicValue().getInt());
+ }
+
+ @Test
+ public void testToJavaObject() {
+ // 创建一个复杂的 Object
+ ComplexObject complexObject = createComplexObject();
+
+ // 转换为 JavaObject
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(complexObject);
+
+ // 对转换后的 JavaObject 进行断言,验证各个 field 的值是否一致
+ Assert.assertNotNull(javaObject);
+ Assert.assertEquals(ComplexObject.class.getName(), javaObject.getClassName());
+
+ JavaFields fields = javaObject.getFields();
+
+ Map fieldMap = fields.getFieldsList().stream()
+ .collect(Collectors.toMap(JavaField::getName, field -> field));
+
+ // 验证基础类型字段
+ BasicValue basicValue = fieldMap.get("basicValue").getBasicValue();
+ Assert.assertEquals(5, basicValue.getInt());
+
+ // 验证集合字段
+ JavaField collection = fieldMap.get("collection");
+ CollectionValue collectionValue = collection.getCollection();
+
+ Assert.assertEquals(2, collectionValue.getElementsCount());
+
+ // 验证数组字段
+ JavaField array = fieldMap.get("arrayValue");
+ ArrayValue arrayValue = array.getArrayValue();
+ Assert.assertEquals(2, arrayValue.getElementsCount());
+
+ // 验证嵌套对象字段
+ JavaField nestedObject = fieldMap.get("nestedObject");
+ JavaObject nestedJavaObject = nestedObject.getObjectValue();
+ JavaFields nestedObjectFields = nestedJavaObject.getFields();
+ Assert.assertEquals(1, nestedObjectFields.getFieldsCount());
+ JavaField nestedObjectField = nestedObjectFields.getFields(0);
+ Assert.assertEquals("stringValue", nestedObjectField.getName());
+ Assert.assertEquals("nestedValue", nestedObjectField.getBasicValue().getString());
+ }
+
+ private ComplexObject createComplexObject() {
+ ComplexObject complexObject = new ComplexObject();
+ complexObject.setBasicValue(5);
+ complexObject.setCollection(Arrays.asList("element1", "element2"));
+ complexObject.setArrayValue(new int[] { 1, 2 });
+ complexObject.setNestedObject(new NestedObject("nestedValue"));
+ return complexObject;
+ }
+
+ private static class ComplexObject {
+ private int basicValue;
+ private Collection collection;
+ private int[] arrayValue;
+ private NestedObject nestedObject;
+
+ public int getBasicValue() {
+ return basicValue;
+ }
+
+ public void setBasicValue(int basicValue) {
+ this.basicValue = basicValue;
+ }
+
+ public Collection getCollection() {
+ return collection;
+ }
+
+ public void setCollection(Collection collection) {
+ this.collection = collection;
+ }
+
+ public int[] getArrayValue() {
+ return arrayValue;
+ }
+
+ public void setArrayValue(int[] arrayValue) {
+ this.arrayValue = arrayValue;
+ }
+
+ public NestedObject getNestedObject() {
+ return nestedObject;
+ }
+
+ public void setNestedObject(NestedObject nestedObject) {
+ this.nestedObject = nestedObject;
+ }
+ }
+
+ private static class NestedObject {
+ private String stringValue;
+
+ public NestedObject(String stringValue) {
+ this.stringValue = stringValue;
+ }
+
+ public String getStringValue() {
+ return stringValue;
+ }
+
+ public void setStringValue(String stringValue) {
+ this.stringValue = stringValue;
+ }
+ }
+
+ private static class TestObject {
+ private Double[] doubleArray;
+
+ public Double[] getDoubleArray() {
+ return doubleArray;
+ }
+
+ public void setDoubleArray(Double[] doubleArray) {
+ this.doubleArray = doubleArray;
+ }
+ }
+
+ @Test
+ public void testObjectWithDubboArrayField() {
+ // 创建测试对象
+ TestObject testObject = new TestObject();
+ testObject.setDoubleArray(new Double[] { 1.0, 2.0, 3.0 });
+
+ // 转换为JavaObject
+ JavaObject javaObject = JavaObjectConverter.toJavaObject(testObject);
+
+ // 检查各个field的值是否一致
+ for (int i = 0; i < testObject.getDoubleArray().length; i++) {
+ Double expectedValue = testObject.getDoubleArray()[i];
+ ArrayValue arrayValue = javaObject.getFields().getFields(0).getArrayValue();
+ ArrayElement arrayElement = arrayValue.getElements(i);
+ Double actualValue = arrayElement.getBasicValue().getDouble();
+ Assert.assertEquals(expectedValue, actualValue);
+ }
+ }
+
+ @Test
+ public void testToJavaObjectWithNullValue() {
+ JavaObject result = JavaObjectConverter.toJavaObject(null);
+ assertNotNull(result);
+ assertTrue(result.hasNullValue());
+ assertEquals(NullValue.getDefaultInstance(), result.getNullValue());
+ }
+
+ @Test
+ public void testToJavaObjectWithNullKeyInMap() {
+ Map map = new HashMap<>();
+ map.put(null, "value");
+
+ JavaObject result = JavaObjectConverter.toJavaObject(map);
+ assertNotNull(result);
+ assertTrue(result.hasMap());
+ MapValue mapValue = result.getMap();
+ assertEquals(1, mapValue.getEntriesCount());
+
+ MapEntry entry = mapValue.getEntries(0);
+ assertNotNull(entry.getKey());
+ assertTrue(entry.getKey().hasNullValue());
+ assertEquals(NullValue.getDefaultInstance(), entry.getKey().getNullValue());
+
+ assertNotNull(entry.getValue());
+ assertTrue(entry.getValue().hasBasicValue());
+
+ assertEquals("value", entry.getValue().getBasicValue().getString());
+ }
+
+ @Test
+ public void testToJavaObjectWithNullValueInArray() {
+ Object[] array = new Object[3];
+ array[0] = "value";
+ array[1] = null;
+ array[2] = 123;
+
+ JavaObject result = JavaObjectConverter.toJavaObject(array);
+ assertNotNull(result);
+ assertTrue(result.hasArrayValue());
+ ArrayValue arrayValue = result.getArrayValue();
+ assertEquals(3, arrayValue.getElementsCount());
+
+ ArrayElement element1 = arrayValue.getElements(0);
+ assertTrue(element1.hasObjectValue());
+ JavaObject objectValue1 = element1.getObjectValue();
+ assertTrue(objectValue1.hasBasicValue());
+ assertEquals("value", objectValue1.getBasicValue().getString());
+
+ ArrayElement element2 = arrayValue.getElements(1);
+ assertNotNull(element2.getNullValue());
+
+ ArrayElement element3 = arrayValue.getElements(2);
+ assertTrue(element3.hasObjectValue());
+ JavaObject objectValue3 = element3.getObjectValue();
+ assertTrue(objectValue3.hasBasicValue());
+ assertEquals(123, objectValue3.getBasicValue().getInt());
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2ab112686..9cffbd9ff 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,7 @@
site
packaging
arthas-grpc-web-proxy
+ arthas-grpc-services