diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java
index 2496d2dea311..881820729772 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java
@@ -32,6 +32,18 @@ public interface MethodDescriptor {
Class>[] getParameterClasses();
+ /**
+ * Retrieves the generic parameter types of the method.
+ *
+ * For parameterized parameters like {@code List}, this returns the
+ * {@link java.lang.reflect.ParameterizedType} instead of just the raw {@code Class}.
+ *
+ * @return the generic parameter types
+ */
+ default Type[] getGenericParameterTypes() {
+ return getParameterClasses();
+ }
+
Class> getReturnClass();
Type[] getReturnTypes();
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptor.java
index ea27aad53ca3..e90dd1132f4d 100644
--- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptor.java
+++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptor.java
@@ -41,6 +41,7 @@ public class ReflectionMethodDescriptor implements MethodDescriptor {
public final String methodName;
private final String[] compatibleParamSignatures;
private final Class>[] parameterClasses;
+ private final Type[] genericParameterTypes;
private final Class> returnClass;
private final Type[] returnTypes;
private final String paramDesc;
@@ -52,6 +53,7 @@ public ReflectionMethodDescriptor(Method method) {
this.method = method;
this.methodName = method.getName();
this.parameterClasses = method.getParameterTypes();
+ this.genericParameterTypes = method.getGenericParameterTypes();
this.returnClass = method.getReturnType();
Type[] returnTypesResult;
try {
@@ -122,6 +124,11 @@ public Class>[] getParameterClasses() {
return parameterClasses;
}
+ @Override
+ public Type[] getGenericParameterTypes() {
+ return genericParameterTypes;
+ }
+
@Override
public String getParamDesc() {
return paramDesc;
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptorTest.java b/dubbo-common/src/test/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptorTest.java
index 352b1ac645a3..b05ee89aeedf 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptorTest.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/rpc/model/ReflectionMethodDescriptorTest.java
@@ -20,7 +20,10 @@
import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType;
import org.apache.dubbo.rpc.support.DemoService;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -89,6 +92,47 @@ void addAttribute() {
Assertions.assertEquals(attr, method.getAttribute(attr));
}
+ @Test
+ void getGenericParameterTypes_nonGeneric() {
+ Type[] types = method.getGenericParameterTypes();
+ Assertions.assertEquals(1, types.length);
+ Assertions.assertEquals(String.class, types[0]);
+ }
+
+ @Test
+ void getGenericParameterTypes_withGenericParam() throws NoSuchMethodException {
+ ReflectionMethodDescriptor md =
+ new ReflectionMethodDescriptor(DemoService.class.getDeclaredMethod("processBytes", List.class));
+ Type[] genericTypes = md.getGenericParameterTypes();
+ Class>[] rawTypes = md.getParameterClasses();
+
+ Assertions.assertEquals(1, genericTypes.length);
+ Assertions.assertEquals(List.class, rawTypes[0]);
+ Assertions.assertInstanceOf(ParameterizedType.class, genericTypes[0]);
+
+ ParameterizedType pt = (ParameterizedType) genericTypes[0];
+ Assertions.assertEquals(List.class, pt.getRawType());
+ Assertions.assertEquals(Byte.class, pt.getActualTypeArguments()[0]);
+ }
+
+ @Test
+ void getGenericParameterTypes_mixedParams() throws NoSuchMethodException {
+ ReflectionMethodDescriptor md = new ReflectionMethodDescriptor(
+ DemoService.class.getDeclaredMethod("processMultiple", String.class, List.class, Map.class));
+ Type[] genericTypes = md.getGenericParameterTypes();
+ Class>[] rawTypes = md.getParameterClasses();
+
+ Assertions.assertEquals(3, genericTypes.length);
+ // String param: generic type == raw type
+ Assertions.assertSame(rawTypes[0], genericTypes[0]);
+ // List: generic type is ParameterizedType
+ Assertions.assertInstanceOf(ParameterizedType.class, genericTypes[1]);
+ Assertions.assertEquals(Short.class, ((ParameterizedType) genericTypes[1]).getActualTypeArguments()[0]);
+ // Map: generic type is ParameterizedType
+ Assertions.assertInstanceOf(ParameterizedType.class, genericTypes[2]);
+ Assertions.assertEquals(Byte.class, ((ParameterizedType) genericTypes[2]).getActualTypeArguments()[1]);
+ }
+
@Test
void testEquals() {
try {
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoService.java b/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoService.java
index 7a8e97b9c671..6e997357105f 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoService.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoService.java
@@ -16,6 +16,13 @@
*/
package org.apache.dubbo.rpc.support;
+import java.util.List;
+import java.util.Map;
+
public interface DemoService {
String sayHello(String name);
+
+ void processBytes(List data);
+
+ void processMultiple(String name, List values, Map mapping);
}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoServiceImpl.java b/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoServiceImpl.java
index cf81be8561a1..d3a222ab5949 100644
--- a/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoServiceImpl.java
+++ b/dubbo-common/src/test/java/org/apache/dubbo/rpc/support/DemoServiceImpl.java
@@ -16,9 +16,18 @@
*/
package org.apache.dubbo.rpc.support;
+import java.util.List;
+import java.util.Map;
+
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
+
+ @Override
+ public void processBytes(List data) {}
+
+ @Override
+ public void processMultiple(String name, List values, Map mapping) {}
}
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java
index 23ff9dee619f..4004147ecd1e 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DecodeableRpcInvocation.java
@@ -46,6 +46,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
@@ -79,6 +80,8 @@ public class DecodeableRpcInvocation extends RpcInvocation implements Codec, Dec
protected final FrameworkModel frameworkModel;
+ private Type[] genericParameterTypes;
+
protected final transient Supplier callbackServiceCodecFactory;
private static final boolean CHECK_SERIALIZATION =
@@ -236,6 +239,7 @@ protected Class>[] drawPts(String path, String version, String desc, Class>[
MethodDescriptor methodDescriptor = serviceDescriptor.getMethod(getMethodName(), desc);
if (methodDescriptor != null) {
pts = methodDescriptor.getParameterClasses();
+ this.genericParameterTypes = methodDescriptor.getGenericParameterTypes();
this.setReturnTypes(methodDescriptor.getReturnTypes());
// switch TCCL
@@ -270,10 +274,15 @@ protected Class>[] drawPts(String path, String version, String desc, Class>[
}
protected Object[] drawArgs(ObjectInput in, Class>[] pts) throws IOException, ClassNotFoundException {
- Object[] args;
- args = new Object[pts.length];
+ Object[] args = new Object[pts.length];
for (int i = 0; i < args.length; i++) {
- args[i] = in.readObject(pts[i]);
+ if (genericParameterTypes != null
+ && i < genericParameterTypes.length
+ && genericParameterTypes[i] != pts[i]) {
+ args[i] = in.readObject(pts[i], genericParameterTypes[i]);
+ } else {
+ args[i] = in.readObject(pts[i]);
+ }
}
return args;
}
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java
index 046410a20dfb..0d17ebc30882 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ReflectionPackableMethod.java
@@ -114,7 +114,8 @@ public ReflectionPackableMethod(
// server
this.responsePack = new WrapResponsePack(serialization, url, serializeName, actualResponseType);
- this.requestUnpack = new WrapRequestUnpack(serialization, url, allSerialize, actualRequestTypes);
+ this.requestUnpack = new WrapRequestUnpack(
+ serialization, url, allSerialize, actualRequestTypes, method.getGenericParameterTypes());
}
this.allSerialize = allSerialize;
}
@@ -442,16 +443,20 @@ private class WrapRequestUnpack implements WrapperUnPack {
private final Class>[] actualRequestTypes;
+ private final Type[] genericParameterTypes;
+
private final Collection allSerialize;
private WrapRequestUnpack(
MultipleSerialization serialization,
URL url,
Collection allSerialize,
- Class>[] actualRequestTypes) {
+ Class>[] actualRequestTypes,
+ Type[] genericParameterTypes) {
this.serialization = serialization;
this.url = url;
this.actualRequestTypes = actualRequestTypes;
+ this.genericParameterTypes = genericParameterTypes;
this.allSerialize = allSerialize;
}
@@ -467,7 +472,14 @@ public Object unpack(byte[] data, boolean isReturnTriException) throws IOExcepti
for (int i = 0; i < wrapper.getArgs().size(); i++) {
ByteArrayInputStream bais =
new ByteArrayInputStream(wrapper.getArgs().get(i));
- ret[i] = serialization.deserialize(url, wrapper.getSerializeType(), actualRequestTypes[i], bais);
+ if (genericParameterTypes != null
+ && i < genericParameterTypes.length
+ && genericParameterTypes[i] != actualRequestTypes[i]) {
+ ret[i] = serialization.deserialize(
+ url, wrapper.getSerializeType(), actualRequestTypes[i], genericParameterTypes[i], bais);
+ } else {
+ ret[i] = serialization.deserialize(url, wrapper.getSerializeType(), actualRequestTypes[i], bais);
+ }
}
return ret;
}
diff --git a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/DefaultMultipleSerialization.java b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/DefaultMultipleSerialization.java
index b0ccb0fd04b5..00ae86b6145b 100644
--- a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/DefaultMultipleSerialization.java
+++ b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/DefaultMultipleSerialization.java
@@ -21,6 +21,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.Type;
public class DefaultMultipleSerialization implements MultipleSerialization {
@@ -46,6 +47,17 @@ public Object deserialize(URL url, String serializeType, Class> clz, InputStre
return in.readObject(clz);
}
+ @Override
+ public Object deserialize(URL url, String serializeType, Class> clz, Type type, InputStream os)
+ throws IOException, ClassNotFoundException {
+ serializeType = convertHessian(serializeType);
+ final Serialization serialization = url.getOrDefaultFrameworkModel()
+ .getExtensionLoader(Serialization.class)
+ .getExtension(serializeType);
+ final ObjectInput in = serialization.deserialize(null, os);
+ return in.readObject(clz, type);
+ }
+
private String convertHessian(String ser) {
if (ser.equals("hessian4")) {
return "hessian2";
diff --git a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/MultipleSerialization.java b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/MultipleSerialization.java
index 3bafedfbca89..d7f3a4c316ca 100644
--- a/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/MultipleSerialization.java
+++ b/dubbo-serialization/dubbo-serialization-api/src/main/java/org/apache/dubbo/common/serialize/MultipleSerialization.java
@@ -23,6 +23,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.reflect.Type;
@SPI(scope = ExtensionScope.FRAMEWORK)
public interface MultipleSerialization {
@@ -31,4 +32,9 @@ public interface MultipleSerialization {
Object deserialize(URL url, String serializeType, Class> clz, InputStream os)
throws IOException, ClassNotFoundException;
+
+ default Object deserialize(URL url, String serializeType, Class> clz, Type type, InputStream os)
+ throws IOException, ClassNotFoundException {
+ return deserialize(url, serializeType, clz, os);
+ }
}
diff --git a/dubbo-serialization/dubbo-serialization-hessian2/src/main/java/org/apache/dubbo/common/serialize/hessian2/Hessian2ObjectInput.java b/dubbo-serialization/dubbo-serialization-hessian2/src/main/java/org/apache/dubbo/common/serialize/hessian2/Hessian2ObjectInput.java
index 8bb224d5452e..50847132bb50 100644
--- a/dubbo-serialization/dubbo-serialization-hessian2/src/main/java/org/apache/dubbo/common/serialize/hessian2/Hessian2ObjectInput.java
+++ b/dubbo-serialization/dubbo-serialization-hessian2/src/main/java/org/apache/dubbo/common/serialize/hessian2/Hessian2ObjectInput.java
@@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;
@@ -119,6 +120,7 @@ public T readObject(Class cls) throws IOException, ClassNotFoundException
}
@Override
+ @SuppressWarnings("unchecked")
public T readObject(Class cls, Type type) throws IOException, ClassNotFoundException {
if (!Objects.equals(
mH2i.getSerializerFactory().getClassLoader(),
@@ -126,9 +128,35 @@ public T readObject(Class cls, Type type) throws IOException, ClassNotFou
mH2i.setSerializerFactory(hessian2FactoryManager.getSerializerFactory(
Thread.currentThread().getContextClassLoader()));
}
+ if (type instanceof ParameterizedType) {
+ Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
+ Class>[] expectedTypes = new Class>[typeArgs.length];
+ boolean hasExpectedType = false;
+ for (int i = 0; i < typeArgs.length; i++) {
+ if (typeArgs[i] instanceof Class && isPrimitive((Class>) typeArgs[i])) {
+ expectedTypes[i] = (Class>) typeArgs[i];
+ hasExpectedType = true;
+ }
+ }
+ if (hasExpectedType) {
+ return (T) mH2i.readObject(cls, expectedTypes);
+ }
+ }
return readObject(cls);
}
+ private static boolean isPrimitive(Class> type) {
+ return type.isPrimitive()
+ || type == Boolean.class
+ || type == Character.class
+ || type == Byte.class
+ || type == Short.class
+ || type == Integer.class
+ || type == Long.class
+ || type == Float.class
+ || type == Double.class;
+ }
+
public InputStream readInputStream() throws IOException {
return mH2i.readInputStream();
}
diff --git a/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/Hessian2SerializationTest.java b/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/Hessian2SerializationTest.java
index 93dc9d1185e1..9e4fe59c9237 100644
--- a/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/Hessian2SerializationTest.java
+++ b/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/Hessian2SerializationTest.java
@@ -27,6 +27,9 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
@@ -507,6 +510,225 @@ void testReadObjectNotMatched() throws IOException, ClassNotFoundException {
frameworkModel.destroy();
}
+ @Test
+ void testReadObjectWithGenericType_ListByte() throws Exception {
+ FrameworkModel frameworkModel = new FrameworkModel();
+ Serialization serialization =
+ frameworkModel.getExtensionLoader(Serialization.class).getExtension("hessian2");
+ URL url = URL.valueOf("").setScopeModel(frameworkModel);
+
+ List original = Arrays.asList((byte) 1, (byte) 2, (byte) 127, (byte) -1);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutput objectOutput = serialization.serialize(url, outputStream);
+ objectOutput.writeObject(original);
+ objectOutput.flushBuffer();
+
+ byte[] bytes = outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInput objectInput = serialization.deserialize(url, inputStream);
+
+ Type listByteType = makeParameterizedType(List.class, Byte.class);
+ List> result = objectInput.readObject(List.class, listByteType);
+
+ Assertions.assertEquals(original.size(), result.size());
+ for (int i = 0; i < original.size(); i++) {
+ Assertions.assertInstanceOf(
+ Byte.class,
+ result.get(i),
+ "Element at index " + i + " should be Byte but was "
+ + result.get(i).getClass().getName());
+ Assertions.assertEquals(original.get(i), result.get(i));
+ }
+
+ frameworkModel.destroy();
+ }
+
+ @Test
+ void testReadObjectWithGenericType_ListShort() throws Exception {
+ FrameworkModel frameworkModel = new FrameworkModel();
+ Serialization serialization =
+ frameworkModel.getExtensionLoader(Serialization.class).getExtension("hessian2");
+ URL url = URL.valueOf("").setScopeModel(frameworkModel);
+
+ List original = Arrays.asList((short) 1, (short) 200, (short) -100);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutput objectOutput = serialization.serialize(url, outputStream);
+ objectOutput.writeObject(original);
+ objectOutput.flushBuffer();
+
+ byte[] bytes = outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInput objectInput = serialization.deserialize(url, inputStream);
+
+ Type listShortType = makeParameterizedType(List.class, Short.class);
+ List> result = objectInput.readObject(List.class, listShortType);
+
+ Assertions.assertEquals(original.size(), result.size());
+ for (int i = 0; i < original.size(); i++) {
+ Assertions.assertInstanceOf(Short.class, result.get(i));
+ Assertions.assertEquals(original.get(i), result.get(i));
+ }
+
+ frameworkModel.destroy();
+ }
+
+ @Test
+ void testReadObjectWithGenericType_ListFloat() throws Exception {
+ FrameworkModel frameworkModel = new FrameworkModel();
+ Serialization serialization =
+ frameworkModel.getExtensionLoader(Serialization.class).getExtension("hessian2");
+ URL url = URL.valueOf("").setScopeModel(frameworkModel);
+
+ List original = Arrays.asList(1.5f, 2.5f, -3.14f);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutput objectOutput = serialization.serialize(url, outputStream);
+ objectOutput.writeObject(original);
+ objectOutput.flushBuffer();
+
+ byte[] bytes = outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInput objectInput = serialization.deserialize(url, inputStream);
+
+ Type listFloatType = makeParameterizedType(List.class, Float.class);
+ List> result = objectInput.readObject(List.class, listFloatType);
+
+ Assertions.assertEquals(original.size(), result.size());
+ for (int i = 0; i < original.size(); i++) {
+ Assertions.assertInstanceOf(Float.class, result.get(i));
+ Assertions.assertEquals(original.get(i), result.get(i));
+ }
+
+ frameworkModel.destroy();
+ }
+
+ @Test
+ void testReadObjectWithGenericType_MapStringByte() throws Exception {
+ FrameworkModel frameworkModel = new FrameworkModel();
+ Serialization serialization =
+ frameworkModel.getExtensionLoader(Serialization.class).getExtension("hessian2");
+ URL url = URL.valueOf("").setScopeModel(frameworkModel);
+
+ Map original = new HashMap<>();
+ original.put("a", (byte) 1);
+ original.put("b", (byte) 2);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutput objectOutput = serialization.serialize(url, outputStream);
+ objectOutput.writeObject(original);
+ objectOutput.flushBuffer();
+
+ byte[] bytes = outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInput objectInput = serialization.deserialize(url, inputStream);
+
+ Type mapType = makeParameterizedType(Map.class, String.class, Byte.class);
+ Map, ?> result = objectInput.readObject(Map.class, mapType);
+
+ Assertions.assertEquals(original.size(), result.size());
+ for (Map.Entry, ?> entry : result.entrySet()) {
+ Assertions.assertInstanceOf(
+ Byte.class, entry.getValue(), "Value for key '" + entry.getKey() + "' should be Byte");
+ }
+ Assertions.assertEquals(original.get("a"), result.get("a"));
+ Assertions.assertEquals(original.get("b"), result.get("b"));
+
+ frameworkModel.destroy();
+ }
+
+ @Test
+ void testReadObjectWithGenericType_nonGenericUnchanged() throws Exception {
+ FrameworkModel frameworkModel = new FrameworkModel();
+ Serialization serialization =
+ frameworkModel.getExtensionLoader(Serialization.class).getExtension("hessian2");
+ URL url = URL.valueOf("").setScopeModel(frameworkModel);
+
+ String original = "hello";
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutput objectOutput = serialization.serialize(url, outputStream);
+ objectOutput.writeObject(original);
+ objectOutput.flushBuffer();
+
+ byte[] bytes = outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInput objectInput = serialization.deserialize(url, inputStream);
+ Assertions.assertEquals(original, objectInput.readObject(String.class, String.class));
+
+ frameworkModel.destroy();
+ }
+
+ private static ParameterizedType makeParameterizedType(Class> rawType, Type... typeArguments) {
+ return new ParameterizedType() {
+ @Override
+ public Type[] getActualTypeArguments() {
+ return typeArguments;
+ }
+
+ @Override
+ public Type getRawType() {
+ return rawType;
+ }
+
+ @Override
+ public Type getOwnerType() {
+ return null;
+ }
+ };
+ }
+
+ @Test
+ void testReadObjectWithGenericType_pojoWithNarrowNumberFields() throws Exception {
+ FrameworkModel frameworkModel = new FrameworkModel();
+ Serialization serialization =
+ frameworkModel.getExtensionLoader(Serialization.class).getExtension("hessian2");
+ URL url = URL.valueOf("").setScopeModel(frameworkModel);
+
+ List scores = Arrays.asList((byte) 90, (byte) 85);
+ Map attrs = new HashMap<>();
+ attrs.put("level", (byte) 5);
+ attrs.put("rank", (byte) 3);
+ NarrowNumberPojo original = new NarrowNumberPojo("Alice", (byte) 30, (short) 170, 12345.67f, scores, attrs);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ObjectOutput objectOutput = serialization.serialize(url, outputStream);
+ objectOutput.writeObject(original);
+ objectOutput.flushBuffer();
+
+ byte[] bytes = outputStream.toByteArray();
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
+ ObjectInput objectInput = serialization.deserialize(url, inputStream);
+
+ NarrowNumberPojo result = objectInput.readObject(NarrowNumberPojo.class, NarrowNumberPojo.class);
+
+ Assertions.assertEquals("Alice", result.getName());
+ Assertions.assertEquals((byte) 30, result.getAge());
+ Assertions.assertEquals((short) 170, result.getHeight());
+ Assertions.assertEquals(12345.67f, result.getSalary(), 0.01f);
+
+ Assertions.assertNotNull(result.getScores());
+ Assertions.assertEquals(2, result.getScores().size());
+ for (Object elem : result.getScores()) {
+ Assertions.assertInstanceOf(Byte.class, elem, "Score element should be Byte but was " + elem.getClass());
+ }
+ Assertions.assertEquals((byte) 90, result.getScores().get(0));
+ Assertions.assertEquals((byte) 85, result.getScores().get(1));
+
+ Assertions.assertNotNull(result.getAttributes());
+ for (Map.Entry entry : result.getAttributes().entrySet()) {
+ Assertions.assertInstanceOf(
+ Byte.class,
+ entry.getValue(),
+ "Attribute value should be Byte but was " + entry.getValue().getClass());
+ }
+ Assertions.assertEquals((byte) 5, result.getAttributes().get("level"));
+ Assertions.assertEquals((byte) 3, result.getAttributes().get("rank"));
+
+ frameworkModel.destroy();
+ }
+
@Test
void testLimit1() throws IOException, ClassNotFoundException {
FrameworkModel frameworkModel = new FrameworkModel();
diff --git a/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/NarrowNumberPojo.java b/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/NarrowNumberPojo.java
new file mode 100644
index 000000000000..633269f8421d
--- /dev/null
+++ b/dubbo-serialization/dubbo-serialization-hessian2/src/test/java/org/apache/dubbo/common/serialize/hessian2/NarrowNumberPojo.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.serialize.hessian2;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class NarrowNumberPojo implements Serializable {
+
+ private String name;
+ private byte age;
+ private short height;
+ private float salary;
+ private List scores;
+ private Map attributes;
+
+ public NarrowNumberPojo() {}
+
+ public NarrowNumberPojo(
+ String name, byte age, short height, float salary, List scores, Map attributes) {
+ this.name = name;
+ this.age = age;
+ this.height = height;
+ this.salary = salary;
+ this.scores = scores;
+ this.attributes = attributes;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public byte getAge() {
+ return age;
+ }
+
+ public void setAge(byte age) {
+ this.age = age;
+ }
+
+ public short getHeight() {
+ return height;
+ }
+
+ public void setHeight(short height) {
+ this.height = height;
+ }
+
+ public float getSalary() {
+ return salary;
+ }
+
+ public void setSalary(float salary) {
+ this.salary = salary;
+ }
+
+ public List getScores() {
+ return scores;
+ }
+
+ public void setScores(List scores) {
+ this.scores = scores;
+ }
+
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(Map attributes) {
+ this.attributes = attributes;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ NarrowNumberPojo that = (NarrowNumberPojo) o;
+ return age == that.age
+ && height == that.height
+ && Float.compare(that.salary, salary) == 0
+ && Objects.equals(name, that.name)
+ && Objects.equals(scores, that.scores)
+ && Objects.equals(attributes, that.attributes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, age, height, salary, scores, attributes);
+ }
+}