diff --git a/motan-core/src/main/java/com/weibo/api/motan/proxy/CommonClient.java b/motan-core/src/main/java/com/weibo/api/motan/proxy/CommonClient.java index 7b7d52332..f800f000d 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/proxy/CommonClient.java +++ b/motan-core/src/main/java/com/weibo/api/motan/proxy/CommonClient.java @@ -59,4 +59,5 @@ public interface CommonClient { * build request with interfaceName, methodName and arguments */ Request buildRequest(String interfaceName, String methodName, Object[] arguments); + } diff --git a/motan-core/src/main/java/com/weibo/api/motan/proxy/MeshClientRefererInvocationHandler.java b/motan-core/src/main/java/com/weibo/api/motan/proxy/MeshClientRefererInvocationHandler.java index 2db7be619..17b285850 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/proxy/MeshClientRefererInvocationHandler.java +++ b/motan-core/src/main/java/com/weibo/api/motan/proxy/MeshClientRefererInvocationHandler.java @@ -133,4 +133,5 @@ public Request buildRequest(String methodName, Object[] arguments) { public Request buildRequest(String interfaceName, String methodName, Object[] arguments) { return MotanClientUtil.buildRequest(interfaceName, methodName, arguments); } + } diff --git a/motan-core/src/main/java/com/weibo/api/motan/proxy/RefererCommonHandler.java b/motan-core/src/main/java/com/weibo/api/motan/proxy/RefererCommonHandler.java index cedadacc5..b5bfcc4df 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/proxy/RefererCommonHandler.java +++ b/motan-core/src/main/java/com/weibo/api/motan/proxy/RefererCommonHandler.java @@ -46,4 +46,9 @@ public Request buildRequest(String methodName, Object[] arguments) { public Request buildRequest(String interfaceName, String methodName, Object[] arguments) { return MotanClientUtil.buildRequest(interfaceName, methodName, arguments); } + + public Request buildRequestV1(String interfaceName, String methodName, Object[] arguments,String parametersDesc) { + return MotanClientUtil.buildRequestV1(interfaceName, methodName, arguments,parametersDesc); + } + } diff --git a/motan-core/src/main/java/com/weibo/api/motan/rpc/DefaultProvider.java b/motan-core/src/main/java/com/weibo/api/motan/rpc/DefaultProvider.java index bfd54186f..bc155ecb4 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/rpc/DefaultProvider.java +++ b/motan-core/src/main/java/com/weibo/api/motan/rpc/DefaultProvider.java @@ -23,6 +23,7 @@ import com.weibo.api.motan.exception.MotanServiceException; import com.weibo.api.motan.util.ExceptionUtil; import com.weibo.api.motan.util.LoggerUtil; +import com.weibo.api.motan.util.PojoUtils; import java.lang.reflect.Method; @@ -62,7 +63,10 @@ public Response invoke(Request request) { boolean defaultThrowExceptionStack = URLParamType.transExceptionStack.getBooleanValue(); try { - Object value = method.invoke(proxyImpl, request.getArguments()); + //如果是List做参数,在泛化调用的情况下,这里需要做特殊处理 + //TODO 这里默认无法感知到是不是泛化调用过来的请求 + Object[] arguments = PojoUtils.realize(request.getArguments(),method.getParameterTypes(),method.getGenericParameterTypes()); + Object value = method.invoke(proxyImpl,arguments); response.setValue(value); } catch (Exception e) { if (e.getCause() != null) { diff --git a/motan-core/src/main/java/com/weibo/api/motan/transport/ProviderMessageRouter.java b/motan-core/src/main/java/com/weibo/api/motan/transport/ProviderMessageRouter.java index 400bbb43a..befb71245 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/transport/ProviderMessageRouter.java +++ b/motan-core/src/main/java/com/weibo/api/motan/transport/ProviderMessageRouter.java @@ -120,6 +120,7 @@ private void processLazyDeserialize(Request request, Method method) { && request.getArguments()[0] instanceof DeserializableObject && request instanceof DefaultRequest) { try { + //反序列化操作 Object[] args = ((DeserializableObject) request.getArguments()[0]).deserializeMulti(method.getParameterTypes()); ((DefaultRequest) request).setArguments(args); } catch (IOException e) { diff --git a/motan-core/src/main/java/com/weibo/api/motan/util/ClassUtils.java b/motan-core/src/main/java/com/weibo/api/motan/util/ClassUtils.java new file mode 100644 index 000000000..a6eb35d14 --- /dev/null +++ b/motan-core/src/main/java/com/weibo/api/motan/util/ClassUtils.java @@ -0,0 +1,53 @@ +package com.weibo.api.motan.util; + +/** + * @author dinglang + * @since 2023/9/4 + */ +public class ClassUtils { + public static ClassLoader getClassLoader(Class clazz) { + ClassLoader cl = null; + if (!clazz.getName().startsWith("org.apache.dubbo")) { + cl = clazz.getClassLoader(); + } + if (cl == null) { + try { + cl = Thread.currentThread().getContextClassLoader(); + } catch (Exception ignored) { + // Cannot access thread context ClassLoader - falling back to system class loader... + } + if (cl == null) { + // No thread context class loader -> use class loader of this class. + cl = clazz.getClassLoader(); + if (cl == null) { + // getClassLoader() returning null indicates the bootstrap ClassLoader + try { + cl = ClassLoader.getSystemClassLoader(); + } catch (Exception ignored) { + // Cannot access system ClassLoader - oh well, maybe the caller can live with null... + } + } + } + } + + return cl; + } + + /** + * Return the default ClassLoader to use: typically the thread context + * ClassLoader, if available; the ClassLoader that loaded the ClassUtils + * class will be used as fallback. + *

+ * Call this method if you intend to use the thread context ClassLoader in a + * scenario where you absolutely need a non-null ClassLoader reference: for + * example, for class path resource loading (but not necessarily for + * Class.forName, which accepts a null ClassLoader + * reference as well). + * + * @return the default ClassLoader (never null) + * @see java.lang.Thread#getContextClassLoader() + */ + public static ClassLoader getClassLoader() { + return getClassLoader(ClassUtils.class); + } +} diff --git a/motan-core/src/main/java/com/weibo/api/motan/util/CompatibleTypeUtils.java b/motan-core/src/main/java/com/weibo/api/motan/util/CompatibleTypeUtils.java new file mode 100644 index 000000000..c242999a7 --- /dev/null +++ b/motan-core/src/main/java/com/weibo/api/motan/util/CompatibleTypeUtils.java @@ -0,0 +1,223 @@ + +package com.weibo.api.motan.util; + +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.*; + +public class CompatibleTypeUtils { + + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** + * the text to parse such as "2007-12-03T10:15:30" + */ + private static final int ISO_LOCAL_DATE_TIME_MIN_LEN = 19; + + private CompatibleTypeUtils() { + } + + /** + * Compatible type convert. Null value is allowed to pass in. If no conversion is needed, then the original value + * will be returned. + *

+ * Supported compatible type conversions include (primary types and corresponding wrappers are not listed): + *

+ */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public static Object compatibleTypeConvert(Object value, Class type) { + if (value == null || type == null || type.isAssignableFrom(value.getClass())) { + return value; + } + + if (value instanceof String) { + String string = (String) value; + if (char.class.equals(type) || Character.class.equals(type)) { + if (string.length() != 1) { + throw new IllegalArgumentException(String.format("CAN NOT convert String(%s) to char!" + + " when convert String to char, the String MUST only 1 char.", string)); + } + return string.charAt(0); + } + if (type.isEnum()) { + return Enum.valueOf((Class) type, string); + } + if (type == BigInteger.class) { + return new BigInteger(string); + } + if (type == BigDecimal.class) { + return new BigDecimal(string); + } + if (type == Short.class || type == short.class) { + return new Short(string); + } + if (type == Integer.class || type == int.class) { + return new Integer(string); + } + if (type == Long.class || type == long.class) { + return new Long(string); + } + if (type == Double.class || type == double.class) { + return new Double(string); + } + if (type == Float.class || type == float.class) { + return new Float(string); + } + if (type == Byte.class || type == byte.class) { + return new Byte(string); + } + if (type == Boolean.class || type == boolean.class) { + return Boolean.valueOf(string); + } + if (type == Date.class || type == java.sql.Date.class || type == java.sql.Timestamp.class + || type == java.sql.Time.class) { + try { + Date date = new SimpleDateFormat(DATE_FORMAT).parse(string); + if (type == java.sql.Date.class) { + return new java.sql.Date(date.getTime()); + } + if (type == java.sql.Timestamp.class) { + return new java.sql.Timestamp(date.getTime()); + } + if (type == java.sql.Time.class) { + return new java.sql.Time(date.getTime()); + } + return date; + } catch (ParseException e) { + throw new IllegalStateException("Failed to parse date " + value + " by format " + + DATE_FORMAT + ", cause: " + e.getMessage(), e); + } + } + if (type == LocalDateTime.class) { + if (StringUtils.isEmpty(string)) { + return null; + } + return LocalDateTime.parse(string); + } + if (type == LocalDate.class) { + if (StringUtils.isEmpty(string)) { + return null; + } + return LocalDate.parse(string); + } + if (type == LocalTime.class) { + if (StringUtils.isEmpty(string)) { + return null; + } + + if (string.length() >= ISO_LOCAL_DATE_TIME_MIN_LEN) { + return LocalDateTime.parse(string).toLocalTime(); + } else { + return LocalTime.parse(string); + } + } + if (type == Class.class) { + try { + return ReflectUtil.name2class(string); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + if (char[].class.equals(type)) { + // Process string to char array for generic invoke + // See + // - https://github.com/apache/dubbo/issues/2003 + int len = string.length(); + char[] chars = new char[len]; + string.getChars(0, len, chars, 0); + return chars; + } + } + if (value instanceof Number) { + Number number = (Number) value; + if (type == byte.class || type == Byte.class) { + return number.byteValue(); + } + if (type == short.class || type == Short.class) { + return number.shortValue(); + } + if (type == int.class || type == Integer.class) { + return number.intValue(); + } + if (type == long.class || type == Long.class) { + return number.longValue(); + } + if (type == float.class || type == Float.class) { + return number.floatValue(); + } + if (type == double.class || type == Double.class) { + return number.doubleValue(); + } + if (type == BigInteger.class) { + return BigInteger.valueOf(number.longValue()); + } + if (type == BigDecimal.class) { + return new BigDecimal(number.toString()); + } + if (type == Date.class) { + return new Date(number.longValue()); + } + if (type == boolean.class || type == Boolean.class) { + return 0 != number.intValue(); + } + } + if (value instanceof Collection) { + Collection collection = (Collection) value; + if (type.isArray()) { + int length = collection.size(); + Object array = Array.newInstance(type.getComponentType(), length); + int i = 0; + for (Object item : collection) { + Array.set(array, i++, item); + } + return array; + } + if (!type.isInterface()) { + try { + Collection result = (Collection) type.getDeclaredConstructor().newInstance(); + result.addAll(collection); + return result; + } catch (Throwable ignored) { + } + } + if (type == List.class) { + return new ArrayList(collection); + } + if (type == Set.class) { + return new HashSet(collection); + } + } + if (value.getClass().isArray() && Collection.class.isAssignableFrom(type)) { + int length = Array.getLength(value); + Collection collection; + if (!type.isInterface()) { + try { + collection = (Collection) type.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + collection = new ArrayList(length); + } + } else if (type == Set.class) { + collection = new HashSet(Math.max((int) (length/.75f) + 1, 16)); + } else { + collection = new ArrayList(length); + } + for (int i = 0; i < length; i++) { + collection.add(Array.get(value, i)); + } + return collection; + } + return value; + } +} diff --git a/motan-core/src/main/java/com/weibo/api/motan/util/MotanClientUtil.java b/motan-core/src/main/java/com/weibo/api/motan/util/MotanClientUtil.java index 73823eedc..c0d88ea42 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/util/MotanClientUtil.java +++ b/motan-core/src/main/java/com/weibo/api/motan/util/MotanClientUtil.java @@ -35,6 +35,10 @@ public static Request buildRequest(String interfaceName, String methodName, Obje return buildRequest(interfaceName, methodName, arguments, null); } + public static Request buildRequestV1(String interfaceName, String methodName, Object[] arguments,String parametersDesc) { + return buildRequest(interfaceName, methodName, parametersDesc,arguments, null); + } + public static Request buildRequest(String interfaceName, String methodName, Object[] arguments, Map attachments) { return buildRequest(interfaceName, methodName, null, arguments, attachments); } diff --git a/motan-core/src/main/java/com/weibo/api/motan/util/PojoUtils.java b/motan-core/src/main/java/com/weibo/api/motan/util/PojoUtils.java new file mode 100644 index 000000000..9637031fb --- /dev/null +++ b/motan-core/src/main/java/com/weibo/api/motan/util/PojoUtils.java @@ -0,0 +1,520 @@ +package com.weibo.api.motan.util; + +import org.apache.commons.lang3.ArrayUtils; + +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListMap; + +/** + * @author dinglang + * @since 2023/9/4 + */ +public class PojoUtils { + + private static final ConcurrentMap NAME_METHODS_CACHE = new ConcurrentHashMap<>(); + + private static final ConcurrentMap, ConcurrentMap> CLASS_FIELD_CACHE = new ConcurrentHashMap<>(); + + private static final ConcurrentMap CLASS_NOT_FOUND_CACHE = new ConcurrentHashMap<>(); + + private static final Object NOT_FOUND_VALUE = new Object(); + + public static Object[] realize(Object[] objs, Class[] types, Type[] gtypes) { + if (objs.length != types.length || objs.length != gtypes.length) { + throw new IllegalArgumentException("args.length != types.length"); + } + Object[] dests = new Object[objs.length]; + for (int i = 0; i < objs.length; i++) { + dests[i] = realize(objs[i], types[i], gtypes[i]); + } + return dests; + } + + public static Object realize(Object pojo, Class type, Type genericType) { + return realize0(pojo, type, genericType, new IdentityHashMap<>()); + } + + private static Object realize0(Object pojo, Class type, Type genericType, final Map history) { + return realize1(pojo, type, genericType, new HashMap<>(8), history); + } + + private static Object realize1(Object pojo, Class type, Type genericType, final Map mapParent, final Map history) { + if (pojo == null) { + return null; + } + + if (type != null && type.isEnum() && pojo.getClass() == String.class) { + return Enum.valueOf((Class) type, (String) pojo); + } + + if (ReflectUtil.isPrimitives(pojo.getClass()) + && !(type != null && type.isArray() + && type.getComponentType().isEnum() + && pojo.getClass() == String[].class)) { + return CompatibleTypeUtils.compatibleTypeConvert(pojo, type); + } + + Object o = history.get(pojo); + + if (o != null) { + return o; + } + + history.put(pojo, pojo); + + Map mapGeneric = new HashMap<>(8); + mapGeneric.putAll(mapParent); + TypeVariable>[] typeParameters = type.getTypeParameters(); + if (genericType instanceof ParameterizedType && typeParameters.length > 0) { + ParameterizedType parameterizedType = (ParameterizedType) genericType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + for (int i = 0; i < typeParameters.length; i++) { + if (!(actualTypeArguments[i] instanceof TypeVariable)) { + mapGeneric.put(typeParameters[i].getTypeName(), actualTypeArguments[i]); + } + } + } + + if (pojo.getClass().isArray()) { + if (Collection.class.isAssignableFrom(type)) { + Class ctype = pojo.getClass().getComponentType(); + int len = Array.getLength(pojo); + Collection dest = createCollection(type, len); + history.put(pojo, dest); + for (int i = 0; i < len; i++) { + Object obj = Array.get(pojo, i); + Object value = realize1(obj, ctype, null, mapGeneric, history); + dest.add(value); + } + return dest; + } else { + Class ctype = (type != null && type.isArray() ? type.getComponentType() : pojo.getClass().getComponentType()); + int len = Array.getLength(pojo); + Object dest = Array.newInstance(ctype, len); + history.put(pojo, dest); + for (int i = 0; i < len; i++) { + Object obj = Array.get(pojo, i); + Object value = realize1(obj, ctype, null, mapGeneric, history); + Array.set(dest, i, value); + } + return dest; + } + } + + if (pojo instanceof Collection) { + if (type.isArray()) { + Class ctype = type.getComponentType(); + Collection src = (Collection) pojo; + int len = src.size(); + Object dest = Array.newInstance(ctype, len); + history.put(pojo, dest); + int i = 0; + for (Object obj : src) { + Object value = realize1(obj, ctype, null, mapGeneric, history); + Array.set(dest, i, value); + i++; + } + return dest; + } else { + Collection src = (Collection) pojo; + int len = src.size(); + Collection dest = createCollection(type, len); + history.put(pojo, dest); + for (Object obj : src) { + Type keyType = getGenericClassByIndex(genericType, 0); + Class keyClazz = obj == null ? null : obj.getClass(); + if (keyType instanceof Class) { + keyClazz = (Class) keyType; + } + Object value = realize1(obj, keyClazz, keyType, mapGeneric, history); + dest.add(value); + } + return dest; + } + } + + if (pojo instanceof Map && type != null) { + Object className = ((Map) pojo).get("class"); + if (className instanceof String) { + if (!CLASS_NOT_FOUND_CACHE.containsKey(className)) { + //TODO 这里先注释,暂时不支持这种 + /* try { + type = DefaultSerializeClassChecker.getInstance().loadClass(ClassUtils.getClassLoader(), (String) className); + } catch (ClassNotFoundException e) { + CLASS_NOT_FOUND_CACHE.put((String) className, NOT_FOUND_VALUE); + }*/ + } + } + + // special logic for enum + if (type.isEnum()) { + Object name = ((Map) pojo).get("name"); + if (name != null) { + if (!(name instanceof String)) { + throw new IllegalArgumentException("`name` filed should be string!"); + } else { + return Enum.valueOf((Class) type, (String) name); + } + } + } + Map map; + // when return type is not the subclass of return type from the signature and not an interface + if (!type.isInterface() && !type.isAssignableFrom(pojo.getClass())) { + try { + map = (Map) type.getDeclaredConstructor().newInstance(); + Map mapPojo = (Map) pojo; + map.putAll(mapPojo); + //if (GENERIC_WITH_CLZ) { + map.remove("class"); + //} + } catch (Exception e) { + //ignore error + map = (Map) pojo; + } + } else { + map = (Map) pojo; + } + + if (Map.class.isAssignableFrom(type) || type == Object.class) { + final Map result; + // fix issue#5939 + Type mapKeyType = getKeyTypeForMap(map.getClass()); + Type typeKeyType = getGenericClassByIndex(genericType, 0); + boolean typeMismatch = mapKeyType instanceof Class + && typeKeyType instanceof Class + && !typeKeyType.getTypeName().equals(mapKeyType.getTypeName()); + if (typeMismatch) { + result = createMap(new HashMap(0)); + } else { + result = createMap(map); + } + + history.put(pojo, result); + for (Map.Entry entry : map.entrySet()) { + Type keyType = getGenericClassByIndex(genericType, 0); + Type valueType = getGenericClassByIndex(genericType, 1); + Class keyClazz; + if (keyType instanceof Class) { + keyClazz = (Class) keyType; + } else if (keyType instanceof ParameterizedType) { + keyClazz = (Class) ((ParameterizedType) keyType).getRawType(); + } else { + keyClazz = entry.getKey() == null ? null : entry.getKey().getClass(); + } + Class valueClazz; + if (valueType instanceof Class) { + valueClazz = (Class) valueType; + } else if (valueType instanceof ParameterizedType) { + valueClazz = (Class) ((ParameterizedType) valueType).getRawType(); + } else { + valueClazz = entry.getValue() == null ? null : entry.getValue().getClass(); + } + + Object key = keyClazz == null ? entry.getKey() : realize1(entry.getKey(), keyClazz, keyType, mapGeneric, history); + Object value = valueClazz == null ? entry.getValue() : realize1(entry.getValue(), valueClazz, valueType, mapGeneric, history); + result.put(key, value); + } + return result; + } else if (type.isInterface()) { + Object dest = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{type}, new PojoInvocationHandler(map)); + history.put(pojo, dest); + return dest; + } else { + Object dest; + if (Throwable.class.isAssignableFrom(type)) { + Object message = map.get("message"); + if (message instanceof String) { + dest = newThrowableInstance(type, (String) message); + } else { + dest = newInstance(type); + } + } else { + dest = newInstance(type); + } + + history.put(pojo, dest); + + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + if (key instanceof String) { + String name = (String) key; + Object value = entry.getValue(); + if (value != null) { + Method method = getSetterMethod(dest.getClass(), name, value.getClass()); + Field field = getAndCacheField(dest.getClass(), name); + if (method != null) { + if (!method.isAccessible()) { + method.setAccessible(true); + } + Type containType = Optional.ofNullable(field) + .map(Field::getGenericType) + .map(Type::getTypeName) + .map(mapGeneric::get) + .orElse(null); + if (containType != null) { + //is generic + if (containType instanceof ParameterizedType) { + value = realize1(value, (Class) ((ParameterizedType) containType).getRawType(), containType, mapGeneric, history); + } else if (containType instanceof Class) { + value = realize1(value, (Class) containType, containType, mapGeneric, history); + } else { + Type ptype = method.getGenericParameterTypes()[0]; + value = realize1(value, method.getParameterTypes()[0], ptype, mapGeneric, history); + } + } else { + Type ptype = method.getGenericParameterTypes()[0]; + value = realize1(value, method.getParameterTypes()[0], ptype, mapGeneric, history); + } + try { + method.invoke(dest, value); + } catch (Exception e) { + String exceptionDescription = "Failed to set pojo " + dest.getClass().getSimpleName() + " property " + name + + " value " + value.getClass() + ", cause: " + e.getMessage(); + + throw new RuntimeException(exceptionDescription, e); + } + } else if (field != null) { + value = realize1(value, field.getType(), field.getGenericType(), mapGeneric, history); + try { + field.set(dest, value); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to set field " + name + " of pojo " + dest.getClass().getName() + " : " + e.getMessage(), e); + } + } + } + } + } + return dest; + } + } + return pojo; + } + private static Collection createCollection(Class type, int len) { + if (type.isAssignableFrom(ArrayList.class)) { + return new ArrayList<>(len); + } + if (type.isAssignableFrom(HashSet.class)) { + return new HashSet<>(len); + } + if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { + try { + return (Collection) type.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + // ignore + } + } + return new ArrayList<>(); + } + + private static Type getGenericClassByIndex(Type genericType, int index) { + Type clazz = null; + // find parameterized type + if (genericType instanceof ParameterizedType) { + ParameterizedType t = (ParameterizedType) genericType; + Type[] types = t.getActualTypeArguments(); + clazz = types[index]; + } + return clazz; + } + + private static Type getKeyTypeForMap(Class clazz) { + Type[] interfaces = clazz.getGenericInterfaces(); + if (!ArrayUtils.isEmpty(interfaces)) { + for (Type type : interfaces) { + if (type instanceof ParameterizedType) { + ParameterizedType t = (ParameterizedType) type; + if ("java.util.Map".equals(t.getRawType().getTypeName())) { + return t.getActualTypeArguments()[0]; + } + } + } + } + return null; + } + + private static Method getSetterMethod(Class cls, String property, Class valueCls) { + String name = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); + Method method = NAME_METHODS_CACHE.get(cls.getName() + "." + name + "(" + valueCls.getName() + ")"); + if (method == null) { + try { + method = cls.getMethod(name, valueCls); + } catch (NoSuchMethodException e) { + for (Method m : cls.getMethods()) { + if (ReflectUtil.isBeanPropertyWriteMethod(m) && m.getName().equals(name)) { + method = m; + break; + } + } + } + if (method != null) { + NAME_METHODS_CACHE.put(cls.getName() + "." + name + "(" + valueCls.getName() + ")", method); + } + } + return method; + } + + private static Object newInstance(Class cls) { + try { + return cls.getDeclaredConstructor().newInstance(); + } catch (Exception t) { + Constructor[] constructors = cls.getDeclaredConstructors(); + /* + From Javadoc java.lang.Class#getDeclaredConstructors + This method returns an array of Constructor objects reflecting all the constructors + declared by the class represented by this Class object. + This method returns an array of length 0, + if this Class object represents an interface, a primitive type, an array class, or void. + */ + if (constructors.length == 0) { + throw new RuntimeException("Illegal constructor: " + cls.getName()); + } + Throwable lastError = null; + Arrays.sort(constructors, Comparator.comparingInt(a -> a.getParameterTypes().length)); + for (Constructor constructor : constructors) { + try { + constructor.setAccessible(true); + Object[] parameters = Arrays.stream(constructor.getParameterTypes()).map(PojoUtils::getDefaultValue).toArray(); + return constructor.newInstance(parameters); + } catch (Exception e) { + lastError = e; + } + } + throw new RuntimeException(lastError.getMessage(), lastError); + } + } + private static Object getDefaultValue(Class parameterType) { + if ("char".equals(parameterType.getName())) { + return Character.MIN_VALUE; + } + if ("boolean".equals(parameterType.getName())) { + return false; + } + if ("byte".equals(parameterType.getName())) { + return (byte) 0; + } + if ("short".equals(parameterType.getName())) { + return (short) 0; + } + return parameterType.isPrimitive() ? 0 : null; + } + + private static Map createMap(Map src) { + Class cl = src.getClass(); + Map result = null; + if (HashMap.class == cl) { + result = new HashMap(); + } else if (Hashtable.class == cl) { + result = new Hashtable(); + } else if (IdentityHashMap.class == cl) { + result = new IdentityHashMap(); + } else if (LinkedHashMap.class == cl) { + result = new LinkedHashMap(); + } else if (Properties.class == cl) { + result = new Properties(); + } else if (TreeMap.class == cl) { + result = new TreeMap(); + } else if (WeakHashMap.class == cl) { + return new WeakHashMap(); + } else if (ConcurrentHashMap.class == cl) { + result = new ConcurrentHashMap(); + } else if (ConcurrentSkipListMap.class == cl) { + result = new ConcurrentSkipListMap(); + } else { + try { + result = cl.getDeclaredConstructor().newInstance(); + } catch (Exception e) { /* ignore */ } + + if (result == null) { + try { + Constructor constructor = cl.getConstructor(Map.class); + result = (Map) constructor.newInstance(Collections.EMPTY_MAP); + } catch (Exception e) { /* ignore */ } + } + } + + if (result == null) { + result = new HashMap<>(); + } + + return result; + } + + private static Object newThrowableInstance(Class cls, String message) { + try { + Constructor messagedConstructor = cls.getDeclaredConstructor(String.class); + return messagedConstructor.newInstance(message); + } catch (Exception t) { + return newInstance(cls); + } + } + + private static Field getAndCacheField(Class cls, String fieldName) { + Field result; + if (CLASS_FIELD_CACHE.containsKey(cls) && CLASS_FIELD_CACHE.get(cls).containsKey(fieldName)) { + return CLASS_FIELD_CACHE.get(cls).get(fieldName); + } + + result = getField(cls, fieldName); + + if (result != null) { + ConcurrentMap fields = CLASS_FIELD_CACHE.computeIfAbsent(cls, k -> new ConcurrentHashMap<>()); + fields.putIfAbsent(fieldName, result); + } + return result; + } + + private static Field getField(Class cls, String fieldName) { + Field result = null; + for (Class acls = cls; acls != null; acls = acls.getSuperclass()) { + try { + result = acls.getDeclaredField(fieldName); + if (!Modifier.isPublic(result.getModifiers())) { + result.setAccessible(true); + } + } catch (NoSuchFieldException e) { + } + } + if (result == null && cls != null) { + for (Field field : cls.getFields()) { + if (fieldName.equals(field.getName()) && ReflectUtil.isPublicInstanceField(field)) { + result = field; + break; + } + } + } + return result; + } + + private static class PojoInvocationHandler implements InvocationHandler { + + private final Map map; + + public PojoInvocationHandler(Map map) { + this.map = map; + } + + @Override + @SuppressWarnings("unchecked") + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getDeclaringClass() == Object.class) { + return method.invoke(map, args); + } + String methodName = method.getName(); + Object value = null; + if (methodName.length() > 3 && methodName.startsWith("get")) { + value = map.get(methodName.substring(3, 4).toLowerCase() + methodName.substring(4)); + } else if (methodName.length() > 2 && methodName.startsWith("is")) { + value = map.get(methodName.substring(2, 3).toLowerCase() + methodName.substring(3)); + } else { + value = map.get(methodName.substring(0, 1).toLowerCase() + methodName.substring(1)); + } + if (value instanceof Map && !Map.class.isAssignableFrom(method.getReturnType())) { + value = realize0(value, method.getReturnType(), null, new IdentityHashMap<>()); + } + return value; + } + } +} diff --git a/motan-core/src/main/java/com/weibo/api/motan/util/ReflectUtil.java b/motan-core/src/main/java/com/weibo/api/motan/util/ReflectUtil.java index 574002585..12439638f 100644 --- a/motan-core/src/main/java/com/weibo/api/motan/util/ReflectUtil.java +++ b/motan-core/src/main/java/com/weibo/api/motan/util/ReflectUtil.java @@ -32,6 +32,50 @@ * */ public class ReflectUtil { + /** + * void(V). + */ + public static final char JVM_VOID = 'V'; + + /** + * boolean(Z). + */ + public static final char JVM_BOOLEAN = 'Z'; + + /** + * byte(B). + */ + public static final char JVM_BYTE = 'B'; + + /** + * char(C). + */ + public static final char JVM_CHAR = 'C'; + + /** + * double(D). + */ + public static final char JVM_DOUBLE = 'D'; + + /** + * float(F). + */ + public static final char JVM_FLOAT = 'F'; + + /** + * int(I). + */ + public static final char JVM_INT = 'I'; + + /** + * long(J). + */ + public static final char JVM_LONG = 'J'; + + /** + * short(S). + */ + public static final char JVM_SHORT = 'S'; public static final String PARAM_CLASS_SPLIT = ","; public static final String EMPTY_PARAM = "void"; private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; @@ -302,4 +346,118 @@ private static Object getEmptyObject(Class returnType, Map, Object> } } + public static boolean isPrimitives(Class cls) { + while (cls.isArray()) { + cls = cls.getComponentType(); + } + return isPrimitive(cls); + } + + public static boolean isPrimitive(Class cls) { + return cls.isPrimitive() || cls == String.class || cls == Boolean.class || cls == Character.class + || Number.class.isAssignableFrom(cls) || Date.class.isAssignableFrom(cls); + } + + public static boolean isPublicInstanceField(Field field) { + return Modifier.isPublic(field.getModifiers()) + && !Modifier.isStatic(field.getModifiers()) + && !Modifier.isFinal(field.getModifiers()) + && !field.isSynthetic(); + } + + public static boolean isBeanPropertyWriteMethod(Method method) { + return method != null + && Modifier.isPublic(method.getModifiers()) + && !Modifier.isStatic(method.getModifiers()) + && method.getDeclaringClass() != Object.class + && method.getParameterTypes().length == 1 + && method.getName().startsWith("set") + && method.getName().length() > 3; + } + + public static Class name2class(String name) throws ClassNotFoundException { + return name2class(ClassUtils.getClassLoader(), name); + } + + /** + * name to class. + * "boolean" => boolean.class + * "java.util.Map[][]" => java.util.Map[][].class + * + * @param cl ClassLoader instance. + * @param name name. + * @return Class instance. + */ + private static Class name2class(ClassLoader cl, String name) throws ClassNotFoundException { + int c = 0, index = name.indexOf('['); + if (index > 0) { + c = (name.length() - index) / 2; + name = name.substring(0, index); + } + if (c > 0) { + StringBuilder sb = new StringBuilder(); + while (c-- > 0) { + sb.append('['); + } + + if ("void".equals(name)) { + sb.append(JVM_VOID); + } else if ("boolean".equals(name)) { + sb.append(JVM_BOOLEAN); + } else if ("byte".equals(name)) { + sb.append(JVM_BYTE); + } else if ("char".equals(name)) { + sb.append(JVM_CHAR); + } else if ("double".equals(name)) { + sb.append(JVM_DOUBLE); + } else if ("float".equals(name)) { + sb.append(JVM_FLOAT); + } else if ("int".equals(name)) { + sb.append(JVM_INT); + } else if ("long".equals(name)) { + sb.append(JVM_LONG); + } else if ("short".equals(name)) { + sb.append(JVM_SHORT); + } else { + // "java.lang.Object" ==> "Ljava.lang.Object;" + sb.append('L').append(name).append(';'); + } + name = sb.toString(); + } else { + if ("void".equals(name)) { + return void.class; + } + if ("boolean".equals(name)) { + return boolean.class; + } + if ("byte".equals(name)) { + return byte.class; + } + if ("char".equals(name)) { + return char.class; + } + if ("double".equals(name)) { + return double.class; + } + if ("float".equals(name)) { + return float.class; + } + if ("int".equals(name)) { + return int.class; + } + if ("long".equals(name)) { + return long.class; + } + if ("short".equals(name)) { + return short.class; + } + } + + if (cl == null) { + cl = ClassUtils.getClassLoader(); + } + return Class.forName(name, true, cl); + } + + } diff --git a/motan-demo/motan-demo-api/src/main/java/com/weibo/motan/demo/service/MotanDemoService.java b/motan-demo/motan-demo-api/src/main/java/com/weibo/motan/demo/service/MotanDemoService.java index a28c2ef7a..c78a5df0c 100644 --- a/motan-demo/motan-demo-api/src/main/java/com/weibo/motan/demo/service/MotanDemoService.java +++ b/motan-demo/motan-demo-api/src/main/java/com/weibo/motan/demo/service/MotanDemoService.java @@ -19,10 +19,16 @@ import com.weibo.api.motan.transport.async.MotanAsync; import com.weibo.motan.demo.service.model.User; +import java.util.List; + @MotanAsync public interface MotanDemoService { String hello(String name); User rename(User user, String name) throws Exception; + User batchSave(List userList); + + List getUsers(List ids); + } diff --git a/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan1RpcClient.java b/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan1RpcClient.java new file mode 100644 index 000000000..90299194c --- /dev/null +++ b/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan1RpcClient.java @@ -0,0 +1,82 @@ +/* + * + * Copyright 2009-2016 Weibo, Inc. + * + * Licensed 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 com.weibo.motan.demo.client; + +import com.weibo.api.motan.config.ProtocolConfig; +import com.weibo.api.motan.config.RefererConfig; +import com.weibo.api.motan.config.RegistryConfig; +import com.weibo.api.motan.proxy.CommonClient; +import com.weibo.api.motan.rpc.Request; +import com.weibo.api.motan.rpc.ResponseFuture; +import com.weibo.api.motan.util.MotanClientUtil; +import com.weibo.motan.demo.service.MotanDemoService; +import com.weibo.motan.demo.service.PbParamService; +import com.weibo.motan.demo.service.model.User; +import io.grpc.examples.routeguide.Point; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class Motan1RpcClient { + + public static void main(String[] args) throws Throwable { + //motan(V1)协议使用CommonClient实现泛化调用 + RefererConfig motanDemoServiceReferer = new RefererConfig(); + + // 设置接口及实现类 + motanDemoServiceReferer.setInterface(CommonClient.class); + motanDemoServiceReferer.setServiceInterface("com.weibo.motan.demo.service.MotanDemoService"); + + + // 配置服务的group以及版本号 + motanDemoServiceReferer.setGroup("motan-demo-rpc"); + motanDemoServiceReferer.setVersion("1.0"); + motanDemoServiceReferer.setRequestTimeout(1000); + motanDemoServiceReferer.setAsyncInitConnection(false); + + // 配置注册中心直连调用 + RegistryConfig registry = new RegistryConfig(); + + //use direct registry + registry.setRegProtocol("direct"); + registry.setAddress("127.0.0.1:8001"); + + // use ZooKeeper registry +// registry.setRegProtocol("zk"); +// registry.setAddress("127.0.0.1:2181"); + motanDemoServiceReferer.setRegistry(registry); + + // 配置RPC协议 + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setId("motan"); + protocol.setName("motan"); + motanDemoServiceReferer.setProtocol(protocol); + // motanDemoServiceReferer.setDirectUrl("localhost:8002"); // 注册中心直连调用需添加此配置 + + // 使用服务 + CommonClient service = motanDemoServiceReferer.getRef(); + + //motan v1协议时,需要用这种方式构建request对象后进行泛化调用,否则会导致报错 + Request request = MotanClientUtil.buildRequest("com.weibo.motan.demo.service.MotanDemoService", + "hello", "java.lang.String", + new Object[]{"EEE"},null); + + System.out.println(service.call(request, Object.class)); + } + +} diff --git a/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan2RpcClient.java b/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan2RpcClient.java index 84d06c818..78ebfecc3 100644 --- a/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan2RpcClient.java +++ b/motan-demo/motan-demo-client/src/main/java/com/weibo/motan/demo/client/Motan2RpcClient.java @@ -24,6 +24,7 @@ import com.weibo.api.motan.proxy.CommonClient; import com.weibo.api.motan.rpc.Request; import com.weibo.api.motan.rpc.ResponseFuture; +import com.weibo.api.motan.util.MotanClientUtil; import com.weibo.motan.demo.service.MotanDemoService; import com.weibo.motan.demo.service.PbParamService; import com.weibo.motan.demo.service.model.User; @@ -31,9 +32,19 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class Motan2RpcClient { public static void main(String[] args) throws Throwable { + motan2ApiCommonClientListDemo(); + motan2ApiCommonClientPojoDemo(); + motan2ApiCommonClientGenericParameterTypesDemo(); + + ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath:motan2_demo_client.xml"}); MotanDemoService service; // hessian @@ -123,4 +134,130 @@ public static void motan2ApiCommonClientDemo() throws Throwable { System.out.println(client.call("hello", new Object[]{"a"}, String.class)); } + public static void motan2ApiCommonClientPojoDemo() throws Throwable { + RefererConfig referer = new RefererConfig<>(); + + // 设置服务端接口 + referer.setInterface(CommonClient.class); + referer.setServiceInterface("com.weibo.motan.demo.service.MotanDemoService"); + + // 配置服务的group以及版本号 + referer.setGroup("motan-demo-rpc"); + referer.setVersion("1.0"); + referer.setRequestTimeout(1000); + referer.setAsyncInitConnection(false); + + // 配置注册中心直连调用 + RegistryConfig registry = new RegistryConfig(); + registry.setRegProtocol("direct"); + registry.setAddress("127.0.0.1:8001"); + referer.setRegistry(registry); + + // 配置RPC协议 + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setId("motan2"); + protocol.setName("motan2"); + referer.setProtocol(protocol); + + // 使用服务 + CommonClient client = referer.getRef(); + + //使用Map代替POJO进行真正的泛化调用 + Map map = new HashMap<>(); + map.put("id", 1999); + map.put("name", "dinglang"); + + Request request = MotanClientUtil.buildRequest("com.weibo.motan.demo.service.MotanDemoService", + "rename", "com.weibo.motan.demo.service.model.User,java.lang.String", + new Object[]{map, "EEE"}, null); + System.out.println(client.call(request, Object.class)); + } + + public static void motan2ApiCommonClientListDemo() throws Throwable { + RefererConfig referer = new RefererConfig<>(); + + // 设置服务端接口 + referer.setInterface(CommonClient.class); + referer.setServiceInterface("com.weibo.motan.demo.service.MotanDemoService"); + + // 配置服务的group以及版本号 + referer.setGroup("motan-demo-rpc"); + referer.setVersion("1.0"); + referer.setRequestTimeout(1000); + referer.setAsyncInitConnection(false); + + // 配置注册中心直连调用 + RegistryConfig registry = new RegistryConfig(); + registry.setRegProtocol("direct"); + registry.setAddress("127.0.0.1:8001"); + referer.setRegistry(registry); + + // 配置RPC协议 + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setId("motan2"); + protocol.setName("motan2"); + referer.setProtocol(protocol); + + // 使用服务 + CommonClient client = referer.getRef(); + + + List integerList = new ArrayList<>(); + integerList.add(1); + integerList.add(2); + integerList.add(3); + integerList.add(4); + + Request request = MotanClientUtil.buildRequest("com.weibo.motan.demo.service.MotanDemoService", + "getUsers", "java.util.List", + new Object[]{integerList}, null); + System.out.println(client.call(request, Object.class)); + } + + public static void motan2ApiCommonClientGenericParameterTypesDemo() throws Throwable { + RefererConfig referer = new RefererConfig<>(); + + // 设置服务端接口 + referer.setInterface(CommonClient.class); + referer.setServiceInterface("com.weibo.motan.demo.service.MotanDemoService"); + + // 配置服务的group以及版本号 + referer.setGroup("motan-demo-rpc"); + referer.setVersion("1.0"); + referer.setRequestTimeout(100000); + referer.setAsyncInitConnection(false); + + // 配置注册中心直连调用 + RegistryConfig registry = new RegistryConfig(); + registry.setRegProtocol("direct"); + registry.setAddress("127.0.0.1:8001"); + referer.setRegistry(registry); + + // 配置RPC协议 + ProtocolConfig protocol = new ProtocolConfig(); + protocol.setId("motan2"); + protocol.setName("motan2"); + referer.setProtocol(protocol); + + // 使用服务 + CommonClient client = referer.getRef(); + + Map map = new HashMap<>(); + map.put("id", 1999); + map.put("name", "dinglang"); + + Map user = new HashMap<>(); + user.put("id", 1998); + user.put("name", "dylan"); + + List list = new ArrayList<>(); + list.add(map); + list.add(user); + + Request request = MotanClientUtil.buildRequest("com.weibo.motan.demo.service.MotanDemoService", + "batchSave", "java.util.List", + new Object[]{list}, null); + System.out.println(client.call(request, Object.class)); + } + } diff --git a/motan-demo/motan-demo-server/src/main/java/com/weibo/motan/demo/server/MotanDemoServiceImpl.java b/motan-demo/motan-demo-server/src/main/java/com/weibo/motan/demo/server/MotanDemoServiceImpl.java index d44ab3e7f..0521f2f90 100644 --- a/motan-demo/motan-demo-server/src/main/java/com/weibo/motan/demo/server/MotanDemoServiceImpl.java +++ b/motan-demo/motan-demo-server/src/main/java/com/weibo/motan/demo/server/MotanDemoServiceImpl.java @@ -19,8 +19,14 @@ import com.weibo.api.motan.config.springsupport.annotation.MotanService; import com.weibo.motan.demo.service.MotanDemoService; import com.weibo.motan.demo.service.model.User; +import jdk.nashorn.internal.runtime.ListAdapter; +import org.springframework.lang.NonNull; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; @MotanService(export = "demoMotan:8002") public class MotanDemoServiceImpl implements MotanDemoService { @@ -39,4 +45,30 @@ public User rename(User user, String name) throws Exception { return user; } + @Override + public User batchSave(List userList) { + return new User(9999, + "hello world batchSave :" + join(userList, User::getName)); + } + + @Override + public List getUsers(List ids) { + String result = ids.stream().map(String::valueOf).collect(Collectors.joining(",")); + System.out.println(result); + List userList = new ArrayList(); + for (Integer id : ids) { + User user = new User(id, "hello" + id); + userList.add(user); + } + + return userList; + } + + private String join(final @NonNull List list, final Function mapper) { + return list.stream() + .map(mapper) + .collect(Collectors.joining("-")); + } + + }