Browse Source

优化FastBeanCopier性能

zhou-hao 3 years ago
parent
commit
f7ab9f9226

+ 11 - 0
hsweb-core/pom.xml

@@ -106,5 +106,16 @@
             <artifactId>reactor-extra</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <artifactId>jctools-core</artifactId>
+            <groupId>org.jctools</groupId>
+            <version>2.1.2</version>
+        </dependency>
+
     </dependencies>
 </project>

+ 27 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/ClassDescription.java

@@ -0,0 +1,27 @@
+package org.hswebframework.web.bean;
+
+import lombok.Getter;
+import org.hswebframework.web.dict.EnumDict;
+
+import java.util.Collection;
+
+@Getter
+public class ClassDescription {
+    private final Class<?> type;
+
+    private final boolean collectionType;
+    private final boolean arrayType;
+    private final boolean enumType;
+    private final boolean enumDict;
+    private final int fieldSize;
+
+    public ClassDescription(Class<?> type) {
+        this.type = type;
+        collectionType = Collection.class.isAssignableFrom(type);
+        enumDict = EnumDict.class.isAssignableFrom(type);
+        arrayType = type.isArray();
+        enumType = type.isEnum();
+        fieldSize = type.getDeclaredFields().length;
+    }
+
+}

+ 16 - 0
hsweb-core/src/main/java/org/hswebframework/web/bean/ClassDescriptions.java

@@ -0,0 +1,16 @@
+package org.hswebframework.web.bean;
+
+import org.jctools.maps.NonBlockingHashMap;
+
+import java.util.Map;
+
+public class ClassDescriptions {
+
+    private static final Map<Class<?>, ClassDescription> CACHE = new NonBlockingHashMap<>();
+
+    public static ClassDescription getDescription(Class<?> type) {
+        return CACHE.computeIfAbsent(type, ClassDescription::new);
+    }
+
+
+}

+ 71 - 48
hsweb-core/src/main/java/org/hswebframework/web/bean/FastBeanCopier.java

@@ -1,14 +1,17 @@
 package org.hswebframework.web.bean;
 
+import com.google.common.collect.Maps;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.beanutils.BeanUtilsBean;
+import org.apache.commons.beanutils.ConvertUtilsBean;
 import org.apache.commons.beanutils.PropertyUtilsBean;
 import org.hswebframework.utils.time.DateFormatter;
 import org.hswebframework.web.dict.EnumDict;
 import org.hswebframework.web.proxy.Proxy;
+import org.jctools.maps.NonBlockingHashMap;
 import org.springframework.core.ResolvableType;
 import org.springframework.util.ClassUtils;
 import org.springframework.util.ReflectionUtils;
@@ -30,10 +33,12 @@ import java.util.stream.Stream;
  */
 @Slf4j
 public final class FastBeanCopier {
-    private static final Map<CacheKey, Copier> CACHE = new ConcurrentHashMap<>();
+    private static final Map<CacheKey, Copier> CACHE = new NonBlockingHashMap<>();
 
     private static final PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance().getPropertyUtils();
 
+    private static final ConvertUtilsBean convertUtils = BeanUtilsBean.getInstance().getConvertUtils();
+
     private static final Map<Class<?>, Class<?>> wrapperClassMapping = new HashMap<>();
 
     @SuppressWarnings("all")
@@ -123,7 +128,7 @@ public final class FastBeanCopier {
         Class<?> type = ClassUtils.getUserClass(object);
 
         if (java.lang.reflect.Proxy.isProxyClass(type)) {
-            Class<?>[] interfaces= type.getInterfaces();
+            Class<?>[] interfaces = type.getInterfaces();
             return interfaces[0];
         }
 
@@ -167,8 +172,8 @@ public final class FastBeanCopier {
                 "\n}";
         try {
             return Proxy.create(Copier.class)
-                    .addMethod(method)
-                    .newInstance();
+                        .addMethod(method)
+                        .newInstance();
         } catch (Exception e) {
             log.error("创建bean copy 代理对象失败:\n{}", method, e);
             throw new UnsupportedOperationException(e.getMessage(), e);
@@ -178,20 +183,22 @@ public final class FastBeanCopier {
     private static Map<String, ClassProperty> createProperty(Class<?> type) {
 
         List<String> fieldNames = Arrays.stream(type.getDeclaredFields())
-                .map(Field::getName).collect(Collectors.toList());
+                                        .map(Field::getName).collect(Collectors.toList());
 
         return Stream.of(propertyUtils.getPropertyDescriptors(type))
-                .filter(property -> !property.getName().equals("class") && property.getReadMethod() != null && property.getWriteMethod() != null)
-                .map(BeanClassProperty::new)
-                //让字段有序
-                .sorted(Comparator.comparing(property -> fieldNames.indexOf(property.name)))
-                .collect(Collectors.toMap(ClassProperty::getName, Function.identity(), (k, k2) -> k, LinkedHashMap::new));
+                     .filter(property -> !property
+                             .getName()
+                             .equals("class") && property.getReadMethod() != null && property.getWriteMethod() != null)
+                     .map(BeanClassProperty::new)
+                     //让字段有序
+                     .sorted(Comparator.comparing(property -> fieldNames.indexOf(property.name)))
+                     .collect(Collectors.toMap(ClassProperty::getName, Function.identity(), (k, k2) -> k, LinkedHashMap::new));
 
     }
 
     private static Map<String, ClassProperty> createMapProperty(Map<String, ClassProperty> template) {
         return template.values().stream().map(classProperty -> new MapClassProperty(classProperty.name))
-                .collect(Collectors.toMap(ClassProperty::getName, Function.identity(), (k, k2) -> k, LinkedHashMap::new));
+                       .collect(Collectors.toMap(ClassProperty::getName, Function.identity(), (k, k2) -> k, LinkedHashMap::new));
     }
 
     private static String createCopierCode(Class<?> source, Class<?> target) {
@@ -231,13 +238,16 @@ public final class FastBeanCopier {
                 code.append("if($$__source.").append(sourceProperty.getReadMethod()).append("!=null){\n");
             }
             code.append(targetProperty.generateVar(targetProperty.getName())).append("=")
-                    .append(sourceProperty.generateGetter(target, targetProperty.getType()))
-                    .append(";\n");
+                .append(sourceProperty.generateGetter(target, targetProperty.getType()))
+                .append(";\n");
 
             if (!targetProperty.isPrimitive()) {
                 code.append("\tif(").append(sourceProperty.getName()).append("!=null){\n");
             }
-            code.append("\t$$__target.").append(targetProperty.generateSetter(targetProperty.getType(), sourceProperty.getName())).append(";\n");
+            code
+                    .append("\t$$__target.")
+                    .append(targetProperty.generateSetter(targetProperty.getType(), sourceProperty.getName()))
+                    .append(";\n");
             if (!targetProperty.isPrimitive()) {
                 code.append("\t}\n");
             }
@@ -310,10 +320,10 @@ public final class FastBeanCopier {
 
         protected Class<?> getPrimitiveType(Class<?> type) {
             return wrapperClassMapping.entrySet().stream()
-                    .filter(entry -> entry.getValue() == type)
-                    .map(Map.Entry::getKey)
-                    .findFirst()
-                    .orElse(null);
+                                      .filter(entry -> entry.getValue() == type)
+                                      .map(Map.Entry::getKey)
+                                      .findFirst()
+                                      .orElse(null);
         }
 
         protected Class<?> getWrapperType() {
@@ -334,11 +344,11 @@ public final class FastBeanCopier {
                 boolean hasGeneric = false;
                 if (field != null) {
                     String[] arr = Arrays.stream(ResolvableType.forField(field)
-                            .getGenerics())
-                            .map(ResolvableType::getRawClass)
-                            .filter(Objects::nonNull)
-                            .map(t -> t.getName().concat(".class"))
-                            .toArray(String[]::new);
+                                                               .getGenerics())
+                                         .map(ResolvableType::getRawClass)
+                                         .filter(Objects::nonNull)
+                                         .map(t -> t.getName().concat(".class"))
+                                         .toArray(String[]::new);
                     if (arr.length > 0) {
                         generic = "new Class[]{" + String.join(",", arr) + "}";
                         hasGeneric = true;
@@ -365,11 +375,11 @@ public final class FastBeanCopier {
                         } else {
                             //类型不一致,调用convert转换
                             convertCode.append("((").append(targetWrapperClass.getName())
-                                    .append(")")
-                                    .append(convert)
-                                    .append(").")
-                                    .append(targetType.getName())
-                                    .append("Value()");
+                                       .append(")")
+                                       .append(convert)
+                                       .append(").")
+                                       .append(targetType.getName())
+                                       .append("Value()");
                         }
 
                     } else if (isPrimitive()) {
@@ -377,25 +387,30 @@ public final class FastBeanCopier {
                         //源字段类型为基本数据类型,目标字段为包装器类型
                         if (targetIsWrapper) {
                             convertCode.append(targetType.getName())
-                                    .append(".valueOf(")
-                                    .append(getterCode)
-                                    .append(")");
+                                       .append(".valueOf(")
+                                       .append(getterCode)
+                                       .append(")");
                         } else {
                             convertCode.append("(").append(targetType.getName())
-                                    .append(")(")
-                                    .append(convert)
-                                    .append(")");
+                                       .append(")(")
+                                       .append(convert)
+                                       .append(")");
                         }
                     } else {
                         convertCode.append("(").append(getTypeName(targetType))
-                                .append(")(")
-                                .append(convert)
-                                .append(")");
+                                   .append(")(")
+                                   .append(convert)
+                                   .append(")");
                     }
                 } else {
                     if (Cloneable.class.isAssignableFrom(targetType)) {
                         try {
-                            convertCode.append("(").append(getTypeName()).append(")").append(getterCode).append(".clone()");
+                            convertCode
+                                    .append("(")
+                                    .append(getTypeName())
+                                    .append(")")
+                                    .append(getterCode)
+                                    .append(".clone()");
                         } catch (Exception e) {
                             convertCode.append(getterCode);
                         }
@@ -499,7 +514,9 @@ public final class FastBeanCopier {
             if (source == null) {
                 return null;
             }
-            if (source.getClass().isEnum()) {
+            ClassDescription target = ClassDescriptions.getDescription(targetClass);
+
+            if (target.isEnumType()) {
                 if (source instanceof EnumDict) {
                     Object val = (T) ((EnumDict) source).getValue();
                     if (targetClass.isInstance(val)) {
@@ -555,8 +572,8 @@ public final class FastBeanCopier {
                 return (T) collection;
             }
 
-            if (targetClass.isEnum()) {
-                if (EnumDict.class.isAssignableFrom(targetClass)) {
+            if (target.isEnumType()){
+                if (target.isEnumDict()) {
                     String strVal = String.valueOf(source);
 
                     Object val = EnumDict.find((Class) targetClass, e -> {
@@ -567,10 +584,10 @@ public final class FastBeanCopier {
                     }
                     return convert(val, targetClass, genericType);
                 }
-                String strSource=String.valueOf(source);
+                String strSource = String.valueOf(source);
                 for (T t : targetClass.getEnumConstants()) {
                     if (((Enum) t).name().equalsIgnoreCase(strSource)
-                            ||Objects.equals(String.valueOf(((Enum<?>) t).ordinal()),strSource)) {
+                            || Objects.equals(String.valueOf(((Enum<?>) t).ordinal()), strSource)) {
                         return t;
                     }
                 }
@@ -579,21 +596,27 @@ public final class FastBeanCopier {
                 return null;
             }
             //转换为数组
-            if (targetClass.isArray()) {
+            if (target.isArrayType()) {
                 Class<?> componentType = targetClass.getComponentType();
                 List<?> val = convert(source, List.class, new Class[]{componentType});
                 return (T) val.toArray((Object[]) Array.newInstance(componentType, val.size()));
             }
 
             try {
-                org.apache.commons.beanutils.Converter converter = BeanUtilsBean
-                        .getInstance()
-                        .getConvertUtils()
-                        .lookup(targetClass);
+                org.apache.commons.beanutils.Converter converter = convertUtils.lookup(targetClass);
                 if (null != converter) {
                     return converter.convert(targetClass, source);
                 }
 
+                //快速复制map
+                if (targetClass == Map.class) {
+                    if(source instanceof Map) {
+                        return (T) new HashMap(((Map<?, ?>) source));
+                    }
+                    ClassDescription sourType = ClassDescriptions.getDescription(source.getClass());
+                    return (T)copy(source, Maps.newHashMapWithExpectedSize(sourType.getFieldSize()));
+                }
+
                 return copy(source, beanFactory.newInstance(targetClass), this);
             } catch (Exception e) {
                 log.warn("复制类型{}->{}失败", source, targetClass, e);