add generic reflect type for collection,map,array json (#12732)

This commit is contained in:
suncairong163 2023-07-17 13:28:59 +08:00 committed by GitHub
parent 630e14ae40
commit 2932f09b28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 173 additions and 40 deletions

View File

@ -16,6 +16,7 @@
*/
package org.apache.dubbo.metadata.rest;
import org.apache.dubbo.metadata.rest.jaxrs.JAXRSServiceRestMetadataResolver;
import org.apache.dubbo.metadata.rest.media.MediaType;
import java.lang.reflect.Parameter;
@ -39,8 +40,16 @@ public abstract class AbstractNoAnnotatedParameterProcessor implements NoAnnotat
private boolean contentTypeSupport(RestMethodMetadata restMethodMetadata, MediaType mediaType, Class paramType) {
// @RequestParam String,number param
if (mediaType.equals(MediaType.ALL_VALUE) && (String.class == paramType || paramType.isPrimitive() || Number.class.isAssignableFrom(paramType))) {
return true;
if (mediaType.equals(MediaType.ALL_VALUE)) {
// jaxrs no annotation param is from http body
if (JAXRSServiceRestMetadataResolver.class.equals(restMethodMetadata.getCodeStyle())) {
return true;
}
// spring mvc no annotation param only is used by text data(string,number)
if (String.class == paramType || paramType.isPrimitive() || Number.class.isAssignableFrom(paramType)) {
return true;
}
}
Set<String> consumes = restMethodMetadata.getRequest().getConsumes();

View File

@ -18,9 +18,10 @@ package org.apache.dubbo.metadata.rest;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
/**
* description of service method args info
* description of service method args info
*/
public class ArgInfo {
/**
@ -42,6 +43,11 @@ public class ArgInfo {
*/
private Class paramType;
/**
* param actual Type(collection,map,array)
*/
private Type actualType;
/**
* param name
*/
@ -64,6 +70,7 @@ public class ArgInfo {
public ArgInfo(int index, Parameter parameter) {
this(index, parameter.getName(), parameter.getType());
this.actualType = parameter.getParameterizedType();
}
public ArgInfo() {
@ -152,6 +159,10 @@ public class ArgInfo {
return this;
}
public Type actualReflectType() {
return actualType;
}
@Override
public String toString() {
return "ArgInfo{" +

View File

@ -40,6 +40,7 @@ import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodecManager;
import org.apache.dubbo.rpc.protocol.rest.util.HttpHeaderUtil;
import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@ -100,9 +101,10 @@ public class RestInvoker<T> extends AbstractInvoker<T> {
} else if (responseCode >= 500) {
responseFuture.completeExceptionally(new RemoteServerInternalException(r.getMessage()));
} else if (responseCode < 400) {
mediaType = MediaTypeUtil.convertMediaType(restMethodMetadata.getReflectMethod().getReturnType(), r.getContentType());
Method reflectMethod = restMethodMetadata.getReflectMethod();
mediaType = MediaTypeUtil.convertMediaType(reflectMethod.getReturnType(), r.getContentType());
Object value = HttpMessageCodecManager.httpMessageDecode(r.getBody(),
restMethodMetadata.getReflectMethod().getReturnType(), mediaType);
reflectMethod.getReturnType(), reflectMethod.getGenericReturnType(), mediaType);
appResponse.setValue(value);
// resolve response attribute & attachment
HttpHeaderUtil.parseResponseHeader(appResponse, r);

View File

@ -28,7 +28,6 @@ import org.apache.dubbo.rpc.protocol.rest.request.RequestFacade;
import org.apache.dubbo.rpc.protocol.rest.util.MediaTypeUtil;
/**
* body param parse
*/
@ -43,7 +42,7 @@ public class BodyProviderParamParser extends ProviderParamParser {
try {
String contentType = parseContext.getRequestFacade().getHeader(RestHeaderEnum.CONTENT_TYPE.getHeader());
MediaType mediaType = MediaTypeUtil.convertMediaType(argInfo.getParamType(), contentType);
Object param = HttpMessageCodecManager.httpMessageDecode(request.getInputStream(), argInfo.getParamType(), mediaType);
Object param = HttpMessageCodecManager.httpMessageDecode(request.getInputStream(), argInfo.getParamType(), argInfo.actualReflectType(), mediaType);
parseContext.setValueByIndex(argInfo.getIndex(), param);
} catch (Throwable e) {
throw new ParamParseException("dubbo rest protocol provider body param parser error: " + e.getMessage());

View File

@ -23,6 +23,7 @@ import org.apache.dubbo.rpc.protocol.rest.exception.UnSupportContentTypeExceptio
import org.apache.dubbo.rpc.protocol.rest.pair.MessageCodecResultPair;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.Set;
public class HttpMessageCodecManager {
@ -30,14 +31,14 @@ public class HttpMessageCodecManager {
FrameworkModel.defaultModel().getExtensionLoader(HttpMessageCodec.class).getSupportedExtensionInstances();
public static Object httpMessageDecode(byte[] body, Class<?> type, MediaType mediaType) throws Exception {
public static Object httpMessageDecode(byte[] body, Class<?> type, Type actualType, MediaType mediaType) throws Exception {
if (body == null || body.length == 0) {
return null;
}
for (HttpMessageCodec httpMessageCodec : httpMessageCodecs) {
if (httpMessageCodec.contentTypeSupport(mediaType, type) || typeJudge(mediaType, type, httpMessageCodec)) {
return httpMessageCodec.decode(body, type);
return httpMessageCodec.decode(body, type,actualType);
}
}
throw new UnSupportContentTypeException("UnSupport content-type :" + mediaType.value);

View File

@ -17,8 +17,11 @@
package org.apache.dubbo.rpc.protocol.rest.message;
public interface HttpMessageDecode<InputStream>{
import java.lang.reflect.Type;
public interface HttpMessageDecode<InputStream> {
Object decode(InputStream body, Class<?> targetType,Type actualTYpe) throws Exception;
Object decode(InputStream body, Class<?> targetType) throws Exception;
}

View File

@ -22,6 +22,7 @@ import org.apache.dubbo.metadata.rest.media.MediaType;
import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec;
import java.io.OutputStream;
import java.lang.reflect.Type;
/**
* body type is byte array
@ -31,7 +32,7 @@ public class ByteArrayCodec implements HttpMessageCodec<byte[], OutputStream> {
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
public Object decode(byte[] body, Class<?> targetType, Type type) throws Exception {
return body;
}

View File

@ -23,14 +23,15 @@ import org.apache.dubbo.metadata.rest.media.MediaType;
import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec;
import org.apache.dubbo.rpc.protocol.rest.message.MediaTypeMatcher;
import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils;
import javax.ws.rs.core.Response;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
/**
* body is json
* body is json
*/
@Activate("json")
public class JsonCodec implements HttpMessageCodec<byte[], OutputStream> {
@ -39,12 +40,15 @@ public class JsonCodec implements HttpMessageCodec<byte[], OutputStream> {
static {
unSupportClasses.add(byte[].class);
unSupportClasses.add(String.class);
unSupportClasses.add(Response.class);
}
public static void addUnSupportClass(Class<?> unSupportClass) {
unSupportClasses.add(unSupportClass);
}
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
return DataParseUtils.jsonConvert(targetType, body);
public Object decode(byte[] body, Class<?> targetType, Type actualType) throws Exception {
return DataParseUtils.jsonConvert(actualType, body);
}
@Override

View File

@ -26,6 +26,7 @@ import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -39,8 +40,7 @@ public class MultiValueCodec implements HttpMessageCodec<byte[], OutputStream> {
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
// TODO java bean get set convert
public Object decode(byte[] body, Class<?> targetType, Type type) throws Exception {
Object map = DataParseUtils.multipartFormConvert(body,targetType);
Map valuesMap = (Map) map;
if (Map.class.isAssignableFrom(targetType)) {

View File

@ -26,15 +26,18 @@ import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
@Activate(onClass="javax.ws.rs.core.Response")
@Activate(onClass = "javax.ws.rs.core.Response")
public class ResteasyResponseCodec implements HttpMessageCodec<byte[], OutputStream> {
private Class<?> responseClass;
public ResteasyResponseCodec(){
public ResteasyResponseCodec() {
try {
responseClass = ClassUtils.forName("javax.ws.rs.core.Response");
JsonCodec.addUnSupportClass(responseClass);
} catch (Exception exception) {
responseClass = null;
}
@ -56,7 +59,7 @@ public class ResteasyResponseCodec implements HttpMessageCodec<byte[], OutputStr
}
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
public Object decode(byte[] body, Class<?> targetType, Type type) throws Exception {
if (null == body || body.length == 0) {
return null;
}

View File

@ -22,6 +22,7 @@ import org.apache.dubbo.metadata.rest.media.MediaType;
import org.apache.dubbo.rpc.protocol.rest.message.HttpMessageCodec;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
/**
@ -32,7 +33,7 @@ public class StringCodec implements HttpMessageCodec<byte[], OutputStream> {
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
public Object decode(byte[] body, Class<?> targetType, Type type) throws Exception {
if (body == null || body.length == 0) {
return null;
}

View File

@ -24,6 +24,7 @@ import org.apache.dubbo.rpc.protocol.rest.message.MediaTypeMatcher;
import org.apache.dubbo.rpc.protocol.rest.util.DataParseUtils;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
/**
@ -33,7 +34,7 @@ import java.nio.charset.StandardCharsets;
public class TextCodec implements HttpMessageCodec<byte[], OutputStream> {
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
public Object decode(byte[] body, Class<?> targetType, Type type) throws Exception {
return DataParseUtils.stringTypeConvert(targetType, new String(body, StandardCharsets.UTF_8));
}

View File

@ -32,6 +32,7 @@ import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.OutputStream;
import java.io.StringReader;
import java.lang.reflect.Type;
/**
* body content-type is xml
@ -41,7 +42,7 @@ public class XMLCodec implements HttpMessageCodec<byte[], OutputStream> {
@Override
public Object decode(byte[] body, Class<?> targetType) throws Exception {
public Object decode(byte[] body, Class<?> targetType, Type type) throws Exception {
SAXParserFactory spf = SAXParserFactory.newInstance();

View File

@ -22,6 +22,7 @@ import org.apache.dubbo.common.utils.StringUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
@ -151,7 +152,7 @@ public class DataParseUtils {
}
public static Object jsonConvert(Class targetType, byte[] body) throws Exception {
public static Object jsonConvert(Type targetType, byte[] body) throws Exception {
return JsonUtils.toJavaObject(new String(body, StandardCharsets.UTF_8), targetType);
}

View File

@ -31,6 +31,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Path("/demoService")
public interface DemoService {
@ -128,4 +129,24 @@ public interface DemoService {
@Consumes({MediaType.APPLICATION_JSON})
User noBodyArg(User user);
@POST
@Path("/list")
List<User> list(List<User> users);
@POST
@Path("/set")
Set<User> set(Set<User> users);
@POST
@Path("/array")
User[] array(User[] users);
@POST
@Path("/stringMap")
Map<String, User> stringMap(Map<String,User> userMap);
@POST
@Path("/map")
Map<User, User> userMap(Map<User,User> userMap);
}

View File

@ -30,6 +30,7 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Path("/demoService")
public class DemoServiceImpl implements DemoService {
@ -91,7 +92,7 @@ public class DemoServiceImpl implements DemoService {
}
@Override
public List<String> testMapForm(MultivaluedMap<String,String> params) {
public List<String> testMapForm(MultivaluedMap<String, String> params) {
return params.get("form");
}
@ -120,7 +121,7 @@ public class DemoServiceImpl implements DemoService {
@Path("/noIntHeader")
@Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN})
@Override
public int noIntHeader(@HeaderParam("header")int header) {
public int noIntHeader(@HeaderParam("header") int header) {
return header;
}
@ -128,7 +129,7 @@ public class DemoServiceImpl implements DemoService {
@Path("/noIntParam")
@Consumes({javax.ws.rs.core.MediaType.TEXT_PLAIN})
@Override
public int noIntParam(@QueryParam("header")int header) {
public int noIntParam(@QueryParam("header") int header) {
return header;
}
@ -156,4 +157,31 @@ public class DemoServiceImpl implements DemoService {
public static Map<String, Object> getAttachments() {
return context;
}
@Override
public List<User> list(List<User> users) {
return users;
}
@Override
public Set<User> set(Set<User> users) {
return users;
}
@Override
public User[] array(User[] users) {
return users;
}
@Override
public Map<String, User> stringMap(Map<String, User> userMap) {
return userMap;
}
@Override
public Map<User, User> userMap(Map<User, User> userMap) {
return userMap;
}
}

View File

@ -37,7 +37,7 @@ public class HttpMessageCodecManagerTest {
HttpMessageCodecManager.httpMessageEncode(byteArrayOutputStream,
registrationResult, null, MediaType.TEXT_XML, null);
Object o = HttpMessageCodecManager.httpMessageDecode(byteArrayOutputStream.toByteArray(), RegistrationResult.class, MediaType.TEXT_XML);
Object o = HttpMessageCodecManager.httpMessageDecode(byteArrayOutputStream.toByteArray(), RegistrationResult.class, RegistrationResult.class, MediaType.TEXT_XML);
Assertions.assertEquals(registrationResult, o);

View File

@ -59,8 +59,10 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import static org.apache.dubbo.remoting.Constants.SERVER_KEY;
@ -616,23 +618,23 @@ class JaxrsRestProtocolTest {
@Test
void testGetInvoker() {
Assertions.assertDoesNotThrow(()->{
URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.rest.TestGetInvokerService");
Assertions.assertDoesNotThrow(() -> {
URL exportUrl = URL.valueOf("rest://127.0.0.1:" + availablePort + "/rest?interface=org.apache.dubbo.rpc.protocol.rest.rest.TestGetInvokerService");
TestGetInvokerService server = new TestGetInvokerServiceImpl();
TestGetInvokerService server = new TestGetInvokerServiceImpl();
URL url = this.registerProvider(exportUrl, server, DemoService.class);
URL url = this.registerProvider(exportUrl, server, DemoService.class);
Exporter<TestGetInvokerService> exporter = protocol.export(proxy.getInvoker(server, TestGetInvokerService.class, url));
Exporter<TestGetInvokerService> exporter = protocol.export(proxy.getInvoker(server, TestGetInvokerService.class, url));
TestGetInvokerService invokerService = this.proxy.getProxy(protocol.refer(TestGetInvokerService.class, url));
TestGetInvokerService invokerService = this.proxy.getProxy(protocol.refer(TestGetInvokerService.class, url));
String invoker = invokerService.getInvoker();
Assertions.assertEquals("success", invoker);
String invoker = invokerService.getInvoker();
Assertions.assertEquals("success", invoker);
exporter.unexport();
});
exporter.unexport();
});
}
@Test
@ -689,6 +691,37 @@ class JaxrsRestProtocolTest {
exporter.unexport();
}
@Test
void testCollectionResult() {
DemoService server = new DemoServiceImpl();
URL url = this.registerProvider(exportUrl, server, DemoService.class);
URL nettyUrl = url.addParameter(SERVER_KEY, "netty");
Exporter<DemoService> exporter = protocol.export(proxy.getInvoker(server, DemoService.class, nettyUrl));
DemoService demoService = this.proxy.getProxy(protocol.refer(DemoService.class, nettyUrl));
Assertions.assertEquals(User.getInstance(), demoService.list(Arrays.asList(User.getInstance())).get(0));
HashSet<User> objects = new HashSet<>();
objects.add(User.getInstance());
Assertions.assertEquals(User.getInstance(), new ArrayList<>(demoService.set(objects)).get(0));
Assertions.assertEquals(User.getInstance(), demoService.array(objects.toArray(new User[0]))[0]);
Map<String, User> map = new HashMap<>();
map.put("map", User.getInstance());
Assertions.assertEquals(User.getInstance(), demoService.stringMap(map).get("map"));
Map<User, User> maps = new HashMap<>();
maps.put(User.getInstance(), User.getInstance());
Assertions.assertEquals(User.getInstance(), demoService.userMap(maps).get(User.getInstance()));
exporter.unexport();
}
private URL registerProvider(URL url, Object impl, Class<?> interfaceClass) {
ServiceDescriptor serviceDescriptor = repository.registerService(interfaceClass);
ProviderModel providerModel = new ProviderModel(

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.protocol.rest;
import java.io.Serializable;
import java.util.Objects;
/**
* User Entity
@ -63,6 +64,19 @@ public class User implements Serializable {
return user;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}';