add arthas-grpc-services, support ObjectService

This commit is contained in:
hengyunabc 2023-10-10 17:32:17 +08:00
parent 8d9af06592
commit e5f2050f33
6 changed files with 890 additions and 0 deletions

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>arthas-all</artifactId>
<groupId>com.taobao.arthas</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>arthas-grpc-services</artifactId>
<name>arthas-grpc-services</name>
<url>https://github.com/alibaba/arthas</url>
<properties>
<java.version>1.8</java.version>
<grpc.version>1.46.0</grpc.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.taobao.arthas</groupId>
<artifactId>arthas-vmtool</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.arthas</groupId>
<artifactId>arthas-repackage-logger</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-services</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>mac</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<properties>
<os.detected.classifier>osx-x86_64</os.detected.classifier>
</properties>
</profile>
</profiles>
<build>
<finalName>${project.artifactId}</finalName>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
<protocArtifact>com.google.protobuf:protoc:3.11.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.28.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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<JavaField> 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;
}
}

View File

@ -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<ObjectQueryResult> 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<Class<?>> 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);
}
}

View File

@ -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);
}

View File

@ -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<String> 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<String, Integer> 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<String, JavaField> 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<String> collection;
private int[] arrayValue;
private NestedObject nestedObject;
public int getBasicValue() {
return basicValue;
}
public void setBasicValue(int basicValue) {
this.basicValue = basicValue;
}
public Collection<String> getCollection() {
return collection;
}
public void setCollection(Collection<String> 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<String, Object> 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());
}
}

View File

@ -76,6 +76,7 @@
<module>site</module>
<module>packaging</module>
<module>arthas-grpc-web-proxy</module>
<module>arthas-grpc-services</module>
</modules>
<properties>