apache / shenyu

Apache ShenYu is a Java native API Gateway for service proxy, protocol conversion and API governance.
https://shenyu.apache.org/
Apache License 2.0
8.43k stars 2.93k forks source link

[Question] does it necessary to upgrade dubbo to `3.1.x` ? #4122

Closed loongs-zhang closed 2 years ago

loongs-zhang commented 2 years ago

Question

see https://github.com/apache/dubbo/issues/9973

JooKS-me commented 2 years ago

+1, But I suggest to upgrade to 3.1

BTW, if we want to proxy triple, it is also need to upgrade to 3.*

moremind commented 2 years ago

this issue have submitted, please reference: https://github.com/apache/shenyu/pull/3621

loongs-zhang commented 2 years ago

this issue have submitted, please reference: #3621

copy that, how about just upgrade dubbo in shenyu-plugin-apache-dubbo, and not upgrade dubbo in client? @yu199195 @moremind @JooKS-me

JooKS-me commented 2 years ago

this issue have submitted, please reference: #3621

copy that, how about just upgrade dubbo in shenyu-plugin-apache-dubbo, and not upgrade dubbo in client?

Why not update the client? Are there any additional considerations?

loongs-zhang commented 2 years ago

this issue have submitted, please reference: #3621

copy that, how about just upgrade dubbo in shenyu-plugin-apache-dubbo, and not upgrade dubbo in client?

Why not update the client? Are there any additional considerations?

The client can also be upgraded, but we need to be compatible because the classes in dubbo has changed.

loongs-zhang commented 2 years ago

I'm not sure that the performance will be better after the upgrade. Stress testing is required.

loongs-zhang commented 2 years ago

For dubbo 2.7.15, we conducted a stress test.

https://github.com/apache/dubbo/issues/9973 not fixed:

summary +    530 in 00:00:05 =  113.1/s Avg:   108 Min:    22 Max:  1360 Err:     4 (0.75%) Active: 46 Started: 46 Finished: 0
summary +   4116 in 00:00:29 =  139.6/s Avg:   682 Min:    22 Max:  2479 Err:   343 (8.33%) Active: 100 Started: 100 Finished: 0
summary =   4646 in 00:00:34 =  136.0/s Avg:   617 Min:    22 Max:  2479 Err:   347 (7.47%)
summary +   4009 in 00:00:30 =  133.2/s Avg:   731 Min:    22 Max:  2582 Err:   311 (7.76%) Active: 100 Started: 100 Finished: 0
summary =   8655 in 00:01:04 =  134.7/s Avg:   670 Min:    22 Max:  2582 Err:   658 (7.60%)
summary +   3960 in 00:00:30 =  132.3/s Avg:   773 Min:    21 Max:  2668 Err:   319 (8.06%) Active: 100 Started: 100 Finished: 0
summary =  12615 in 00:01:34 =  133.9/s Avg:   702 Min:    21 Max:  2668 Err:   977 (7.74%)
summary +   4177 in 00:00:30 =  139.2/s Avg:   721 Min:    22 Max:  2604 Err:   447 (10.70%) Active: 100 Started: 100 Finished: 0
summary =  16792 in 00:02:04 =  135.2/s Avg:   707 Min:    21 Max:  2668 Err:  1424 (8.48%)
summary +   4251 in 00:00:30 =  141.1/s Avg:   694 Min:    22 Max:  2513 Err:   315 (7.41%) Active: 100 Started: 100 Finished: 0
summary =  21043 in 00:02:34 =  136.3/s Avg:   704 Min:    21 Max:  2668 Err:  1739 (8.26%)
summary +   4109 in 00:00:31 =  133.8/s Avg:   739 Min:    22 Max:  2597 Err:   307 (7.47%) Active: 100 Started: 100 Finished: 0
summary =  25152 in 00:03:05 =  135.9/s Avg:   710 Min:    21 Max:  2668 Err:  2046 (8.13%)
summary +   3987 in 00:00:29 =  136.7/s Avg:   746 Min:    22 Max:  2571 Err:   344 (8.63%) Active: 100 Started: 100 Finished: 0
summary =  29139 in 00:03:34 =  136.0/s Avg:   715 Min:    21 Max:  2668 Err:  2390 (8.20%)
summary +   4244 in 00:00:30 =  140.7/s Avg:   704 Min:    22 Max:  2665 Err:   413 (9.73%) Active: 100 Started: 100 Finished: 0
summary =  33383 in 00:04:04 =  136.6/s Avg:   714 Min:    21 Max:  2668 Err:  2803 (8.40%)
summary +   4073 in 00:00:30 =  136.4/s Avg:   718 Min:    22 Max:  2569 Err:   309 (7.59%) Active: 100 Started: 100 Finished: 0
summary =  37456 in 00:04:34 =  136.6/s Avg:   714 Min:    21 Max:  2668 Err:  3112 (8.31%)
summary +   3662 in 00:00:27 =  134.5/s Avg:   780 Min:    22 Max:  2586 Err:   283 (7.73%) Active: 0 Started: 100 Finished: 100
summary =  41118 in 00:05:01 =  136.4/s Avg:   720 Min:    21 Max:  2668 Err:  3395 (8.26%)

fixed https://github.com/apache/dubbo/issues/9973 in our internal branch:

summary +  72613 in 00:00:21 = 3433.4/s Avg:    21 Min:    11 Max:   737 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary + 173187 in 00:00:30 = 5776.8/s Avg:    17 Min:    11 Max:   177 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 245800 in 00:00:51 = 4807.4/s Avg:    18 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 172352 in 00:00:30 = 5745.1/s Avg:    17 Min:    11 Max:   271 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 418152 in 00:01:21 = 5154.2/s Avg:    18 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 175115 in 00:00:30 = 5837.2/s Avg:    17 Min:    11 Max:   331 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 593267 in 00:01:51 = 5338.5/s Avg:    17 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 174405 in 00:00:30 = 5813.5/s Avg:    17 Min:    11 Max:   376 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 767672 in 00:02:21 = 5439.5/s Avg:    17 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 175538 in 00:00:30 = 5849.7/s Avg:    17 Min:    11 Max:   352 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 943210 in 00:02:51 = 5511.4/s Avg:    17 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 175693 in 00:00:30 = 5858.0/s Avg:    17 Min:    11 Max:   370 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 1118903 in 00:03:21 = 5563.1/s Avg:    17 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 175683 in 00:00:30 = 5855.3/s Avg:    17 Min:    11 Max:   343 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 1294586 in 00:03:51 = 5601.0/s Avg:    17 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 118795 in 00:00:30 = 3960.2/s Avg:    25 Min:    11 Max:   460 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 1413381 in 00:04:21 = 5412.5/s Avg:    18 Min:    11 Max:   737 Err:     0 (0.00%)
summary + 121851 in 00:00:30 = 4062.1/s Avg:    24 Min:    11 Max:   143 Err:     0 (0.00%) Active: 100 Started: 100 Finished: 0
summary = 1535232 in 00:04:51 = 5273.4/s Avg:    18 Min:    11 Max:   737 Err:     0 (0.00%)
summary +  36433 in 00:00:09 = 4034.2/s Avg:    24 Min:    11 Max:   105 Err:     0 (0.00%) Active: 0 Started: 100 Finished: 100
summary = 1571665 in 00:05:00 = 5236.1/s Avg:    18 Min:    11 Max:   737 Err:     0 (0.00%)

The new org.apache.dubbo.common.utils.PojoUtils is here(we just modified PojoUtils):

/*
 * 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.utils;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.TreeMap;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static org.apache.dubbo.common.utils.ClassUtils.isAssignableFrom;

/**
 * PojoUtils. Travel object deeply, and convert complex type to simple type.
 * <p/>
 * Simple type below will be remained:
 * <ul>
 * <li> Primitive Type, also include <b>String</b>, <b>Number</b>(Integer, Long), <b>Date</b>
 * <li> Array of Primitive Type
 * <li> Collection, eg: List, Map, Set etc.
 * </ul>
 * <p/>
 * Other type will be covert to a map which contains the attributes and value pair of object.
 */
public class PojoUtils {

    private static final Logger logger = LoggerFactory.getLogger(PojoUtils.class);
    private static final ConcurrentMap<String, Method> NAME_METHODS_CACHE = new ConcurrentHashMap<String, Method>();
    private static final ConcurrentMap<Class<?>, ConcurrentMap<String, Field>> CLASS_FIELD_CACHE = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, Field>>();
    private static final boolean GENERIC_WITH_CLZ = Boolean.parseBoolean(ConfigUtils.getProperty(CommonConstants.GENERIC_WITH_CLZ_KEY, "true"));
    private static final List<Class<?>> CLASS_CAN_BE_STRING = Arrays.asList(Byte.class, Short.class, Integer.class,
            Long.class, Float.class, Double.class, Boolean.class, Character.class);
    private static final ConcurrentMap<String, Object> CLASS_NOT_FOUND_CACHE = new ConcurrentHashMap<String, Object>();
    private static final Object NOT_FOUND_VALUE = new Object();

    public static Object[] generalize(Object[] objs) {
        Object[] dests = new Object[objs.length];
        for (int i = 0; i < objs.length; i++) {
            dests[i] = generalize(objs[i]);
        }
        return dests;
    }

    public static Object[] realize(Object[] objs, Class<?>[] types) {
        if (objs.length != types.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]);
        }

        return dests;
    }

    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 generalize(Object pojo) {
        return generalize(pojo, new IdentityHashMap<Object, Object>());
    }

    @SuppressWarnings("unchecked")
    private static Object generalize(Object pojo, Map<Object, Object> history) {
        if (pojo == null) {
            return null;
        }

        if (pojo instanceof Enum<?>) {
            return ((Enum<?>) pojo).name();
        }
        if (pojo.getClass().isArray() && Enum.class.isAssignableFrom(pojo.getClass().getComponentType())) {
            int len = Array.getLength(pojo);
            String[] values = new String[len];
            for (int i = 0; i < len; i++) {
                values[i] = ((Enum<?>) Array.get(pojo, i)).name();
            }
            return values;
        }

        if (ReflectUtils.isPrimitives(pojo.getClass())) {
            return pojo;
        }

        if (pojo instanceof Class) {
            return ((Class) pojo).getName();
        }

        Object o = history.get(pojo);
        if (o != null) {
            return o;
        }
        history.put(pojo, pojo);

        if (pojo.getClass().isArray()) {
            int len = Array.getLength(pojo);
            Object[] dest = new Object[len];
            history.put(pojo, dest);
            for (int i = 0; i < len; i++) {
                Object obj = Array.get(pojo, i);
                dest[i] = generalize(obj, history);
            }
            return dest;
        }
        if (pojo instanceof Collection<?>) {
            Collection<Object> src = (Collection<Object>) pojo;
            int len = src.size();
            Collection<Object> dest = (pojo instanceof List<?>) ? new ArrayList<Object>(len) : new HashSet<Object>(len);
            history.put(pojo, dest);
            for (Object obj : src) {
                dest.add(generalize(obj, history));
            }
            return dest;
        }
        if (pojo instanceof Map<?, ?>) {
            Map<Object, Object> src = (Map<Object, Object>) pojo;
            Map<Object, Object> dest = createMap(src);
            history.put(pojo, dest);
            for (Map.Entry<Object, Object> obj : src.entrySet()) {
                dest.put(generalize(obj.getKey(), history), generalize(obj.getValue(), history));
            }
            return dest;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        history.put(pojo, map);
        if (GENERIC_WITH_CLZ) {
            map.put("class", pojo.getClass().getName());
        }
        for (Method method : pojo.getClass().getMethods()) {
            if (ReflectUtils.isBeanPropertyReadMethod(method)) {
                ReflectUtils.makeAccessible(method);
                try {
                    map.put(ReflectUtils.getPropertyNameFromBeanReadMethod(method), generalize(method.invoke(pojo), history));
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        }
        // public field
        for (Field field : pojo.getClass().getFields()) {
            if (ReflectUtils.isPublicInstanceField(field)) {
                try {
                    Object fieldValue = field.get(pojo);
                    if (history.containsKey(pojo)) {
                        Object pojoGeneralizedValue = history.get(pojo);
                        if (pojoGeneralizedValue instanceof Map
                                && ((Map) pojoGeneralizedValue).containsKey(field.getName())) {
                            continue;
                        }
                    }
                    if (fieldValue != null) {
                        map.put(field.getName(), generalize(fieldValue, history));
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        }
        return map;
    }

    public static Object realize(Object pojo, Class<?> type) {
        return realize0(pojo, type, null, new IdentityHashMap<Object, Object>());
    }

    public static Object realize(Object pojo, Class<?> type, Type genericType) {
        return realize0(pojo, type, genericType, new IdentityHashMap<Object, Object>());
    }

    private static class PojoInvocationHandler implements InvocationHandler {

        private Map<Object, Object> map;

        public PojoInvocationHandler(Map<Object, Object> 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((Map<String, Object>) value, method.getReturnType(), null, new IdentityHashMap<Object, Object>());
            }
            return value;
        }
    }

    @SuppressWarnings("unchecked")
    private static Collection<Object> createCollection(Class<?> type, int len) {
        if (type.isAssignableFrom(ArrayList.class)) {
            return new ArrayList<Object>(len);
        }
        if (type.isAssignableFrom(HashSet.class)) {
            return new HashSet<Object>(Math.max((int) (len/.75f) + 1, 16));
        }
        if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
            try {
                return (Collection<Object>) type.newInstance();
            } catch (Exception e) {
                // ignore
            }
        }
        return new ArrayList<Object>(len);
    }

    private static Map createMap(Map src) {
        Class<? extends Map> cl = src.getClass();
        int size = src.size();
        Map result = null;
        if (HashMap.class == cl) {
            result = new HashMap(Math.max((int) (size/.75f) + 1, 16));
        } else if (Hashtable.class == cl) {
            result = new Hashtable(Math.max((int) (size/.75f) + 1, 16));
        } else if (IdentityHashMap.class == cl) {
            result = new IdentityHashMap((int)(1 + size * 1.1));
        } 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(Math.max((int) (size/.75f) + 1, 16));
        } else if (ConcurrentHashMap.class == cl) {
            result = new ConcurrentHashMap(Math.max((int) (size/.75f) + 1, 16));
        } else if (ConcurrentSkipListMap.class == cl) {
            result = new ConcurrentSkipListMap();
        } else {
            try {
                result = cl.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<Object, Object>(Math.max((int) (size/.75f) + 1, 16));
        }

        return result;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private static Object realize0(Object pojo, Class<?> type, Type genericType, final Map<Object, Object> history) {
        if (pojo == null) {
            return null;
        }

        if (type != null && type.isEnum() && pojo.getClass() == String.class) {
            return Enum.valueOf((Class<Enum>) type, (String) pojo);
        }

        if (ReflectUtils.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);

        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 = realize0(obj, ctype, null, 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 = realize0(obj, ctype, null, history);
                    Array.set(dest, i, value);
                }
                return dest;
            }
        }

        if (pojo instanceof Collection<?>) {
            if (type.isArray()) {
                Class<?> ctype = type.getComponentType();
                Collection<Object> src = (Collection<Object>) pojo;
                int len = src.size();
                Object dest = Array.newInstance(ctype, len);
                history.put(pojo, dest);
                int i = 0;
                for (Object obj : src) {
                    Object value = realize0(obj, ctype, null, history);
                    Array.set(dest, i, value);
                    i++;
                }
                return dest;
            } else {
                Collection<Object> src = (Collection<Object>) pojo;
                int len = src.size();
                Collection<Object> 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 = realize0(obj, keyClazz, keyType, history);
                    dest.add(value);
                }
                return dest;
            }
        }

        if (pojo instanceof Map<?, ?> && type != null) {
            Map<Object, Object> map = (Map<Object, Object>) pojo;
            Object className = ((Map<Object, Object>) pojo).get("class");
            if (className instanceof String) {
                SerializeClassChecker.getInstance().validateClass((String) className);
//                try {
//                    type = ClassUtils.forName((String) className);
//                    if (GENERIC_WITH_CLZ) {
//                        map.remove("class");
//                    }
//                } catch (ClassNotFoundException e) {
//                    // ignore
//                }
                if (!CLASS_NOT_FOUND_CACHE.containsKey(className)) {
                    try {
                        type = ClassUtils.forName((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<Object, Object>) pojo).get("name");
                if (name != null) {
                    if (!(name instanceof String)) {
                        throw new IllegalArgumentException("`name` filed should be string!");
                    } else {
                        return Enum.valueOf((Class<Enum>) type, (String) name);
                    }
                }
            }

            if (Map.class.isAssignableFrom(type) || type == Object.class) {
                final Map<Object, Object> 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<Object, Object> 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() : realize0(entry.getKey(), keyClazz, keyType, history);
                    Object value = valueClazz == null ? entry.getValue() : realize0(entry.getValue(), valueClazz, valueType, 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 = newInstance(type);
                history.put(pojo, dest);
                for (Map.Entry<Object, Object> 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());
                            if (method != null) {
                                Type ptype = method.getGenericParameterTypes()[0];
                                value = realize0(value, method.getParameterTypes()[0], ptype, 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();
                                    logger.error(exceptionDescription, e);
                                    throw new RuntimeException(exceptionDescription, e);
                                }
                            } else {
                                Field field = getField(dest.getClass(), name);
                                if (field != null) {
                                    value = realize0(value, field.getType(), field.getGenericType(), history);
                                    try {
                                        field.set(dest, value);
                                    } catch (IllegalAccessException e) {
                                        String exceptionDescription = "Failed to set field " + name + " of pojo " + dest.getClass().getName() + " : " + e.getMessage();
                                        throw new RuntimeException(exceptionDescription, e);
                                    }
                                }
                            }
                        }
                    }
                }
                if (dest instanceof Throwable) {
                    Object message = map.get("message");
                    if (message instanceof String) {
                        try {
                            Field field = Throwable.class.getDeclaredField("detailMessage");
                            ReflectUtils.makeAccessible(field);
                            field.set(dest, message);
                        } catch (Exception e) {
                        }
                    }
                }
                return dest;
            }
        }
        return pojo;
    }

    /**
     * Get key type for {@link Map} directly implemented by {@code clazz}.
     * If {@code clazz} does not implement {@link Map} directly, return {@code null}.
     *
     * @param clazz {@link Class}
     * @return Return String.class for {@link com.alibaba.fastjson.JSONObject}
     */
    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;
    }

    /**
     * Get parameterized type
     *
     * @param genericType generic type
     * @param index       index of the target parameterized type
     * @return Return Person.class for List<Person>, return Person.class for Map<String, Person> when index=0
     */
    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 Object newInstance(Class<?> cls) {
        try {
            return cls.newInstance();
        } catch (Throwable t) {
            try {
                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());
                }
                Constructor<?> constructor = constructors[0];
                if (constructor.getParameterTypes().length > 0) {
                    for (Constructor<?> c : constructors) {
                        if (c.getParameterTypes().length < constructor.getParameterTypes().length) {
                            constructor = c;
                            if (constructor.getParameterTypes().length == 0) {
                                break;
                            }
                        }
                    }
                }
                ReflectUtils.makeAccessible(constructor);
                Object[] parameters = Arrays.stream(constructor.getParameterTypes()).map(PojoUtils::getDefaultValue).toArray();
                return constructor.newInstance(parameters);
            } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    /**
     * return init value
     *
     * @param parameterType
     * @return
     */
    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 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 (ReflectUtils.isBeanPropertyWriteMethod(m) && m.getName().equals(name)) {
                        method = m;
                        break;
                    }
                }
            }
            if (method != null) {
                ReflectUtils.makeAccessible(method);
                NAME_METHODS_CACHE.put(cls.getName() + "." + name + "(" + valueCls.getName() + ")", method);
            }
        }
        return method;
    }

    private static Field getField(Class<?> cls, String fieldName) {
        Field result = null;
        if (CLASS_FIELD_CACHE.containsKey(cls) && CLASS_FIELD_CACHE.get(cls).containsKey(fieldName)) {
            return CLASS_FIELD_CACHE.get(cls).get(fieldName);
        }
        try {
            result = cls.getDeclaredField(fieldName);
            ReflectUtils.makeAccessible(result);
        } catch (NoSuchFieldException e) {
            for (Field field : cls.getFields()) {
                if (fieldName.equals(field.getName()) && ReflectUtils.isPublicInstanceField(field)) {
                    result = field;
                    break;
                }
            }
        }
        if (result != null) {
            ConcurrentMap<String, Field> fields = CLASS_FIELD_CACHE.computeIfAbsent(cls, k -> new ConcurrentHashMap<>());
            fields.putIfAbsent(fieldName, result);
        }
        return result;
    }

    public static boolean isPojo(Class<?> cls) {
        return !ReflectUtils.isPrimitives(cls)
                && !Collection.class.isAssignableFrom(cls)
                && !Map.class.isAssignableFrom(cls);
    }

    /**
     * Update the property if absent
     *
     * @param getterMethod the getter method
     * @param setterMethod the setter method
     * @param newValue     the new value
     * @param <T>          the value type
     * @since 2.7.8
     */
    public static <T> void updatePropertyIfAbsent(Supplier<T> getterMethod, Consumer<T> setterMethod, T newValue) {
        if (newValue != null && getterMethod.get() == null) {
            setterMethod.accept(newValue);
        }
    }

    /**
     * convert map to a specific class instance
     *
     * @param map map wait for convert
     * @param cls the specified class
     * @param <T> the type of {@code cls}
     * @return class instance declare in param {@code cls}
     * @throws ReflectiveOperationException if the instance creation is failed
     * @since 2.7.10
     */
    public static <T> T mapToPojo(Map<String, Object> map, Class<T> cls) throws ReflectiveOperationException {
        T instance = cls.getDeclaredConstructor().newInstance();
        Map<String, Field> beanPropertyFields = ReflectUtils.getBeanPropertyFields(cls);
        for (Map.Entry<String, Field> entry : beanPropertyFields.entrySet()) {
            String name = entry.getKey();
            Field field = entry.getValue();
            Object mapObject = map.get(name);
            if (mapObject == null) {
                continue;
            }

            Type type = field.getGenericType();
            Object fieldObject = getFieldObject(mapObject, type);
            field.set(instance, fieldObject);
        }

        return instance;
    }

    private static Object getFieldObject(Object mapObject, Type fieldType) throws ReflectiveOperationException {
        if (fieldType instanceof Class<?>) {
            return convertClassType(mapObject, (Class<?>) fieldType);
        } else if (fieldType instanceof ParameterizedType) {
            return convertParameterizedType(mapObject, (ParameterizedType) fieldType);
        } else if (fieldType instanceof GenericArrayType || fieldType instanceof TypeVariable<?> || fieldType instanceof WildcardType) {
            // ignore these type currently
            return null;
        } else {
            throw new IllegalArgumentException("Unrecognized Type: " + fieldType.toString());
        }
    }

    @SuppressWarnings("unchecked")
    private static Object convertClassType(Object mapObject, Class<?> type) throws ReflectiveOperationException {
        if (type.isPrimitive() || isAssignableFrom(type, mapObject.getClass())) {
            return mapObject;
        } else if (Objects.equals(type, String.class) && CLASS_CAN_BE_STRING.contains(mapObject.getClass())) {
            // auto convert specified type to string
            return mapObject.toString();
        } else if (mapObject instanceof Map) {
            return mapToPojo((Map<String, Object>) mapObject, type);
        } else {
            // type didn't match and mapObject is not another Map struct.
            // we just ignore this situation.
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private static Object convertParameterizedType(Object mapObject, ParameterizedType type) throws ReflectiveOperationException {
        Type rawType = type.getRawType();
        if (!isAssignableFrom((Class<?>) rawType, mapObject.getClass())) {
            return null;
        }

        Type[] actualTypeArguments = type.getActualTypeArguments();
        if (isAssignableFrom(Map.class, (Class<?>) rawType)) {
            Map<Object, Object> map = (Map<Object, Object>) mapObject.getClass().getDeclaredConstructor().newInstance();
            for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) mapObject).entrySet()) {
                Object key = getFieldObject(entry.getKey(), actualTypeArguments[0]);
                Object value = getFieldObject(entry.getValue(), actualTypeArguments[1]);
                map.put(key, value);
            }

            return map;
        } else if (isAssignableFrom(Collection.class, (Class<?>) rawType)) {
            Collection<Object> collection = (Collection<Object>) mapObject.getClass().getDeclaredConstructor().newInstance();
            for (Object m : (Iterable<?>) mapObject) {
                Object ele = getFieldObject(m, actualTypeArguments[0]);
                collection.add(ele);
            }

            return collection;
        } else {
            // ignore other type currently
            return null;
        }
    }

}
loongs-zhang commented 2 years ago

Thus I think we must upgrade dubbo to fix this performance problem.