瀏覽代碼

优化bean复制

zhou-hao 7 年之前
父節點
當前提交
a5a0758b6c

+ 6 - 5
hsweb-boost/hsweb-boost-compiler/src/main/java/org/hswebframework/web/boost/Compiler.java

@@ -12,15 +12,16 @@ import java.util.concurrent.atomic.AtomicLong;
  * @since 3.0
  */
 public class Compiler<I> {
-    public ClassPool  classPool = new ClassPool(true);
-    public static final AtomicLong counter   = new AtomicLong(1);
+    public ClassPool classPool = new ClassPool(true);
+    public static final AtomicLong counter = new AtomicLong(1);
 
-    private CtClass  ctClass;
+    private CtClass ctClass;
+    @Getter
     private Class<I> superClass;
     @Getter
-    private String   className;
+    private String className;
     @Getter
-    private String   classFullName;
+    private String classFullName;
 
     private Class<I> targetClass;
 

+ 6 - 0
hsweb-boost/hsweb-boost-compiler/src/main/java/org/hswebframework/web/boost/bean/Copier.java

@@ -1,8 +1,14 @@
 package org.hswebframework.web.boost.bean;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Set;
 
 public interface Copier {
     void copy(Object source, Object target, Set<String> ignore, Converter converter);
+
+    default void copy(Object source, Object target, String ...ignore){
+        copy(source,target,new HashSet<>(Arrays.asList(ignore)),FastBeanCopier.DEFAULT_CONVERT);
+    }
 }
 

+ 293 - 134
hsweb-boost/hsweb-boost-compiler/src/main/java/org/hswebframework/web/boost/bean/FastBeanCopier.java

@@ -1,17 +1,24 @@
 package org.hswebframework.web.boost.bean;
 
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.commons.beanutils.BeanUtilsBean;
 import org.apache.commons.beanutils.PropertyUtilsBean;
 import org.hswebframework.web.boost.Compiler;
 import org.springframework.util.ClassUtils;
 
+import java.beans.BeanDescriptor;
 import java.beans.FeatureDescriptor;
 import java.beans.PropertyDescriptor;
 import java.lang.reflect.Method;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -20,9 +27,7 @@ import java.util.stream.Stream;
  * @since 3.0
  */
 public class FastBeanCopier {
-    private static final Map<String, Copier> CACHE = new HashMap<>();
-
-    private static final Map<String, Map<String, String>> PROPERTY_MAPPING = new HashMap<>();
+    private static final Map<CacheKey, Copier> CACHE = new HashMap<>();
 
     private static final PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance().getPropertyUtils();
 
@@ -41,14 +46,26 @@ public class FastBeanCopier {
         wrapperClassMapping.put(long.class, Long.class);
     }
 
-    public static void copy(Object source, Object target, String... ignore) {
-        copy(source, target, DEFAULT_CONVERT, ignore);
+    public static <T, S> T copy(S source, T target, String... ignore) {
+        return copy(source, target, DEFAULT_CONVERT, ignore);
+    }
+
+    public static <T, S> T copy(S source, Supplier<T> target, String... ignore) {
+        return copy(source, target.get(), DEFAULT_CONVERT, ignore);
+    }
+
+    public static <T, S> T copy(S source, Class<T> target, String... ignore) {
+        try {
+            return copy(source, target.newInstance(), DEFAULT_CONVERT, ignore);
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
     }
 
     public static Copier getCopier(Object source, Object target, boolean autoCreate) {
         Class sourceType = ClassUtils.getUserClass(source);
         Class targetType = ClassUtils.getUserClass(target);
-        String key = createCacheKey(sourceType, targetType);
+        CacheKey key = createCacheKey(sourceType, targetType);
         if (autoCreate) {
             return CACHE.computeIfAbsent(key, k -> createCopier(sourceType, targetType));
         } else {
@@ -57,83 +74,165 @@ public class FastBeanCopier {
 
     }
 
-    public static void copy(Object source, Object target, Converter converter, String... ignore) {
+    public static <T, S> T copy(S source, T target, Converter converter, String... ignore) {
+        if (source instanceof Map && target instanceof Map) {
+            ((Map) target).putAll(((Map) source));
+            return target;
+        }
         getCopier(source, target, true)
                 .copy(source, target, (ignore == null || ignore.length == 0) ? new HashSet<>() : new HashSet<>(Arrays.asList(ignore)), converter);
+        return target;
     }
 
-    private static String createCacheKey(Class source, Class target) {
-        return source.getName().concat("=>").concat(target.getName());
+    private static CacheKey createCacheKey(Class source, Class target) {
+        return new CacheKey(source, target);
     }
 
     public static Copier createCopier(Class source, Class target) {
         String method = "public void copy(Object s, Object t, java.util.Set ignore, " +
                 "org.hswebframework.web.boost.bean.Converter converter){\n" +
-                source.getName() + " source=(" + source.getName() + ")s;\n" +
-                target.getName() + " target=(" + target.getName() + ")t;\n" +
+                "try{\n\t" +
+                source.getName() + " source=(" + source.getName() + ")s;\n\t" +
+                target.getName() + " target=(" + target.getName() + ")t;\n\t" +
                 createCopierCode(source, target) +
+                "}catch(Exception e){\n" +
+                "\tthrow new RuntimeException(e.getMessage(),e);" +
+                "\n}\n" +
                 "\n}";
-        System.out.println(method);
         return Compiler.create(Copier.class)
                 .addMethod(method)
                 .newInstance();
     }
 
-    private static String createFieldCopierCode(PropertyDescriptor source, PropertyDescriptor target) {
-        Method sourceRead = source.getReadMethod();
-        if (sourceRead == null) {
-            //源对象的get方法不存在
-            return "";
-        }
-        Method targetWrite = target.getWriteMethod();
-        if (targetWrite == null) {
-            return "";
-        }
+    private static Map<String, ClassProperty> createProperty(Class type) {
+        return Stream.of(propertyUtils.getPropertyDescriptors(type))
+                .filter(property -> !property.getName().equals("class") && property.getReadMethod() != null && property.getWriteMethod() != null)
+                .map(BeanClassProperty::new)
+                .collect(Collectors.toMap(ClassProperty::getName, Function.identity()));
 
-        PropertyCopierGenerator generator = new PropertyCopierGenerator();
-        generator.target = target;
-        generator.source = source;
+    }
 
-        return generator.generate();
+    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()));
     }
 
     private static String createCopierCode(Class source, Class target) {
-        Map<String, PropertyDescriptor> sourceCache = Stream.of(propertyUtils.getPropertyDescriptors(source))
-                .collect(Collectors.toMap(FeatureDescriptor::getName, Function.identity()));
-        StringBuilder builder = new StringBuilder();
-
-        Arrays.asList(propertyUtils.getPropertyDescriptors(target))
-                .forEach((targetField) -> {
-                    PropertyDescriptor sourceField = sourceCache.get(targetField.getName());
-                    if (null != sourceField) {
-                        builder.append(createFieldCopierCode(sourceField, targetField))
-                                .append("\n");
-                    } else {
-                        //源字段不存
-                    }
-                });
+        Map<String, ClassProperty> sourceProperties = null;
+
+        Map<String, ClassProperty> targetProperties = null;
+
+        //源类型为Map
+        if (Map.class.isAssignableFrom(source)) {
+            if (!Map.class.isAssignableFrom(target)) {
+                sourceProperties = createProperty(source);
+                targetProperties = createMapProperty(sourceProperties);
+            }
+        } else if (Map.class.isAssignableFrom(target)) {
+            if (!Map.class.isAssignableFrom(source)) {
+                sourceProperties = createProperty(source);
+                targetProperties = createMapProperty(sourceProperties);
+
+            }
+        } else {
+            targetProperties = createProperty(target);
+            sourceProperties = createProperty(source);
+        }
+        if (sourceProperties == null || targetProperties == null) {
+            throw new UnsupportedOperationException("不支持的类型,source:" + source + " target:" + target);
+        }
+        StringBuilder code = new StringBuilder();
 
-        return builder.toString();
+        for (ClassProperty sourceProperty : sourceProperties.values()) {
+            ClassProperty targetProperty = targetProperties.get(sourceProperty.getName());
+            if (targetProperty == null) {
+                continue;
+            }
+
+            code.append("if(!ignore.contains(\"").append(sourceProperty.getName()).append("\")){\n\t")
+                    .append(targetProperty.generateVar(targetProperty.getName())).append("=").append(sourceProperty.generateGetter(targetProperty.getType()))
+                    .append(";\n");
+            if (!sourceProperty.isPrimitive()) {
+                code.append("\tif(").append(sourceProperty.getName()).append("!=null){\n");
+            }
+            code.append("\ttarget.").append(targetProperty.generateSetter(targetProperty.getType(), sourceProperty.getName())).append(";\n");
+            if (!sourceProperty.isPrimitive()) {
+                code.append("\t}\n");
+            }
+            code.append("}\n");
+        }
+//
+//        Map<String, PropertyDescriptor> sourceCache = Stream.of(propertyUtils.getPropertyDescriptors(source))
+//                .collect(Collectors.toMap(FeatureDescriptor::getName, Function.identity()));
+//        StringBuilder builder = new StringBuilder();
+//
+//        Arrays.asList(propertyUtils.getPropertyDescriptors(target))
+//                .forEach((targetField) -> {
+//                    PropertyDescriptor sourceField = sourceCache.get(targetField.getName());
+//                    if (null != sourceField) {
+//                        builder.append(createFieldCopierCode(sourceField, targetField))
+//                                .append("\n");
+//                    } else {
+//                        //源字段不存在
+//                    }
+//                });
+
+        return code.toString();
     }
 
-    static class PropertyCopierGenerator {
-        PropertyDescriptor source;
-        PropertyDescriptor target;
-        List<String> lines = new ArrayList<>();
+    static abstract class ClassProperty {
+
+        @Getter
+        protected String name;
+
+        @Getter
+        protected String readMethodName;
+
+        @Getter
+        protected String writeMethodName;
+
+        @Getter
+        protected Function<Class, String> getter;
+
+        @Getter
+        protected BiFunction<Class, String, String> setter;
+
+        @Getter
+        protected Class type;
 
-        private boolean targetIsPrimitive() {
-            return target.getPropertyType().isPrimitive();
+        public String getReadMethod() {
+            return readMethodName + "()";
         }
 
-        private boolean sourceIsPrimitive() {
-            return source.getPropertyType().isPrimitive();
+        public String generateVar(String name) {
+            return getTypeName().concat(" ").concat(name);
         }
 
-        private boolean typeIsWrapper(Class type) {
+        public String getTypeName() {
+            String targetTypeName = type.getName();
+            if (type.isArray()) {
+                targetTypeName = type.getComponentType().getName() + "[]";
+            }
+            return targetTypeName;
+        }
+
+        public boolean isPrimitive() {
+            return isPrimitive(getType());
+        }
+
+        public boolean isPrimitive(Class type) {
+            return type.isPrimitive();
+        }
+
+        public boolean isWrapper() {
+            return isWrapper(getType());
+        }
+
+        public boolean isWrapper(Class type) {
             return wrapperClassMapping.values().contains(type);
         }
 
-        private Class getPrimitiveType(Class type) {
+        protected Class getPrimitiveType(Class type) {
             return wrapperClassMapping.entrySet().stream()
                     .filter(entry -> entry.getValue() == type)
                     .map(Map.Entry::getKey)
@@ -141,100 +240,134 @@ public class FastBeanCopier {
                     .orElse(null);
         }
 
-        private String getReadSourceObjectValueCode() {
-            if (sourceIsPrimitive()) {
-                Class wrapperClass = wrapperClassMapping.get(source.getPropertyType());
-                return wrapperClass.getName() + ".valueOf(source." + source.getReadMethod().getName() + "())";
-            }
-
-            return "source." + source.getReadMethod().getName() + "()";
+        protected Class getWrapperType() {
+            return wrapperClassMapping.get(type);
         }
 
-        private boolean notNull() {
-            return !sourceIsPrimitive();
+        protected String castWrapper(String getter) {
+            return getWrapperType().getSimpleName().concat(".valueOf(").concat(getter).concat(")");
         }
 
-        private void generateConvert() {
-            StringBuilder convertCode = new StringBuilder();
-            convertCode.append("if(!ignore.contains(\"").append(target.getName())
-                    .append("\")");
-            if (notNull()) {
-                convertCode.append("&&source.")
-                        .append(source.getReadMethod().getName())
-                        .append("()!=null");
-            }
-            String targetTypeName = target.getPropertyType().getName();
-            if (target.getPropertyType().isArray()) {
-                targetTypeName = target.getPropertyType().getComponentType().getName() + "[]";
-            }
-            convertCode.append("){\n");
-            convertCode.append(targetTypeName)
-                    .append(" ")
-                    .append(target.getName()).append("=");
-            String convert = "converter.convert((Object)(" + getReadSourceObjectValueCode() + "),"
-                    + target.getPropertyType().getName() + ".class)";
-            if (source.getPropertyType() != target.getPropertyType()) {
-                if (targetIsPrimitive()) {
-                    boolean sourceIsWrapper = typeIsWrapper(source.getPropertyType());
-                    Class targetWrapperClass = wrapperClassMapping.get(target.getPropertyType());
-
-                    Class sourcePrimitive = getPrimitiveType(source.getPropertyType());
-                    if (sourceIsWrapper) {
-                        convertCode.append(target.getPropertyType().getName())
-                                .append(".valueOf(")
-                                .append(getReadSourceObjectValueCode())
-                                .append(".")
-                                .append(sourcePrimitive.getName())
-                                .append("Value());");
-                    } else {
-                        convertCode.append("((").append(targetWrapperClass.getName())
-                                .append(")")
-                                .append(convert)
-                                .append(").")
-//                                .append(target.getPropertyType().getName())
-//                                .append("Value()).")
-                                .append(target.getPropertyType().getName())
-                                .append("Value();");
-                    }
-//                    convertCode.append(getReadSourceObjectValueCode())
-//                            .append(".")
-//                            .append(target.getPropertyType().getName()).append("Value();");
-
-                } else if (sourceIsPrimitive()) {
-                    boolean targetIsWrapper = typeIsWrapper(target.getPropertyType());
-                    Class targetPrimitive = getPrimitiveType(target.getPropertyType());
-                    if (targetIsWrapper) {
-                        convertCode.append(target.getPropertyType().getName())
-                                .append(".valueOf(")
-                                .append(getReadSourceObjectValueCode())
-                                .append(".")
-                                .append(targetPrimitive.getName())
-                                .append("Value());");
+        public Function<Class, String> createGetterFunction() {
+
+            return (targetType) -> {
+                String getterCode = "source." + getReadMethod();
+
+                String convert = "converter.convert((Object)(" + (isPrimitive() ? castWrapper(getterCode) : getterCode) + "),"
+                        + targetType.getName() + ".class)";
+                StringBuilder convertCode = new StringBuilder();
+
+                if (targetType != getType()) {
+                    if (isPrimitive(targetType)) {
+                        boolean sourceIsWrapper = isWrapper(getType());
+                        Class targetWrapperClass = wrapperClassMapping.get(targetType);
+
+                        Class sourcePrimitive = getPrimitiveType(getType());
+                        //目标字段是基本数据类型,源字段是包装器类型
+                        // Integer.valueOf(source.getField()).intValue();
+                        if (sourceIsWrapper) {
+                            convertCode.append(targetType.getName())
+                                    .append(".valueOf(")
+                                    .append(getterCode)
+                                    .append(".")
+                                    .append(sourcePrimitive.getName())
+                                    .append("Value())");
+                        } else {
+                            //类型不一致,调用convert转换
+                            convertCode.append("((").append(targetWrapperClass.getName())
+                                    .append(")")
+                                    .append(convert)
+                                    .append(").")
+                                    .append(targetType.getName())
+                                    .append("Value()");
+                        }
+
+                    } else if (isPrimitive()) {
+                        boolean targetIsWrapper = isWrapper(targetType);
+                        //源字段类型为基本数据类型,目标字段为包装器类型
+                        if (targetIsWrapper) {
+                            Class targetPrimitive = getPrimitiveType(targetType);
+                            convertCode.append(targetType.getName())
+                                    .append(".valueOf(")
+                                    .append(getterCode)
+                                    .append(".")
+                                    .append(targetPrimitive.getName())
+                                    .append("Value())");
+                        } else {
+                            convertCode.append("(").append(targetType.getName())
+                                    .append(")(")
+                                    .append(convert)
+                                    .append(")");
+                        }
                     } else {
-                        convertCode.append("(").append(target.getPropertyType().getName())
+                        convertCode.append("(").append(targetType.getName())
                                 .append(")(")
                                 .append(convert)
-                                .append(");");
+                                .append(")");
                     }
                 } else {
-                    convertCode.append("(").append(target.getPropertyType().getName())
-                            .append(")(")
-                            .append(convert)
-                            .append(");");
+
+                    if (Cloneable.class.isAssignableFrom(targetType)) {
+                        try {
+                            targetType.getMethod("clone");
+                            convertCode.append("(" + getTypeName() + ")").append(getterCode).append(".clone();");
+                        } catch (NoSuchMethodException e) {
+                            convertCode.append(getterCode);
+                        }
+                    } else {
+                        convertCode.append(getterCode);
+                    }
+
                 }
-            } else {
-                convertCode.append("source.")
-                        .append(source.getReadMethod().getName())
-                        .append("();");
-            }
-            convertCode.append("\ntarget.").append(target.getWriteMethod().getName()).append("(").append(target.getName()).append(");");
-            convertCode.append("\n}");
-            lines.add(convertCode.toString());
+//                if (!isPrimitive()) {
+//                    return getterCode + "!=null?" + convertCode.toString() + ":null";
+//                }
+                return convertCode.toString();
+            };
+        }
+
+        public BiFunction<Class, String, String> createSetterFunction(Function<String, String> settingNameSupplier) {
+            return (sourceType, paramGetter) -> {
+
+                return settingNameSupplier.apply(paramGetter);
+            };
+        }
+
+        public String generateGetter(Class targetType) {
+            return getGetter().apply(targetType);
+        }
+
+        public String generateSetter(Class targetType, String getter) {
+            return getSetter().apply(targetType, getter);
+        }
+    }
+
+    static class BeanClassProperty extends ClassProperty {
+        public BeanClassProperty(PropertyDescriptor descriptor) {
+            type = descriptor.getPropertyType();
+            readMethodName = descriptor.getReadMethod().getName();
+            writeMethodName = descriptor.getWriteMethod().getName();
+
+            getter = createGetterFunction();
+            setter = createSetterFunction(paramGetter -> writeMethodName + "(" + paramGetter + ")");
+            name = descriptor.getName();
+        }
+    }
+
+    static class MapClassProperty extends ClassProperty {
+        public MapClassProperty(String name) {
+            type = Object.class;
+            this.name = name;
+            this.readMethodName = "get";
+            this.writeMethodName = "put";
+
+            this.getter = createGetterFunction();
+            this.setter = createSetterFunction(paramGetter -> "put(\"" + name + "\"," + paramGetter + ")");
         }
 
-        public String generate() {
-            generateConvert();
-            return String.join("\n", lines.toArray(new String[lines.size()]));
+        @Override
+        public String getReadMethodName() {
+            return "get(\"" + name + "\")";
         }
     }
 
@@ -248,7 +381,9 @@ public class FastBeanCopier {
             if (targetClass == String.class) {
                 return (T) String.valueOf(source);
             }
-
+            if (targetClass == Object.class) {
+                return (T) source;
+            }
             org.apache.commons.beanutils.Converter converter = BeanUtilsBean
                     .getInstance()
                     .getConvertUtils()
@@ -261,9 +396,33 @@ public class FastBeanCopier {
                 copy(source, newTarget);
                 return newTarget;
             } catch (Exception e) {
+
                 e.printStackTrace();
             }
             return null;
         }
     }
+
+    @AllArgsConstructor
+    public static class CacheKey {
+
+        private Class targetType;
+
+        private Class sourceType;
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof CacheKey)) {
+                return false;
+            }
+            CacheKey target = ((CacheKey) obj);
+            return target.targetType == targetType && target.sourceType == sourceType;
+        }
+
+        public int hashCode() {
+            int result = this.targetType != null ? this.targetType.hashCode() : 0;
+            result = 31 * result + (this.sourceType != null ? this.sourceType.hashCode() : 0);
+            return result;
+        }
+    }
 }

+ 28 - 2
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/FastBeanCopierTest.java

@@ -9,7 +9,9 @@ import org.junit.Test;
 import java.lang.reflect.InvocationTargetException;
 import java.math.BigDecimal;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 
 /**
  * TODO 完成注释
@@ -29,17 +31,24 @@ public class FastBeanCopierTest {
         nestObject.setAge(10);
         nestObject.setName("测试2");
         source.setNestObject(nestObject);
+
+
         Target target = new Target();
-        Copier copier = FastBeanCopier.createCopier(Source.class, Target.class);
         FastBeanCopier.copy(source, target);
+
         long t = System.currentTimeMillis();
         for (int i = 100_0000; i > 0; i--) {
-            copier.copy(source, target, new HashSet<>(), FastBeanCopier.DEFAULT_CONVERT);
+            FastBeanCopier.copy(source, target);
         }
         System.out.println(System.currentTimeMillis() - t);
 
         System.out.println(target);
         System.out.println(target.getNestObject() == source.getNestObject());
+//        Source source1=new Source();
+
+//        FastBeanCopier.copy(source,source1);
+
+//        System.out.println(source1);
 //
 //        t = System.currentTimeMillis();
 //
@@ -56,4 +65,21 @@ public class FastBeanCopierTest {
     }
 
 
+    @Test
+    public void testCopyToMap() {
+        Source source = new Source();
+        source.setAge(100);
+        source.setName("测试");
+        source.setIds(new String[]{"1", "2", "3"});
+        NestObject nestObject = new NestObject();
+        nestObject.setAge(10);
+        nestObject.setName("测试2");
+        source.setNestObject(nestObject);
+        Map<String,Object> target = new HashMap<>();
+        FastBeanCopier.copy(source, target);
+
+        System.out.println(target);
+    }
+
+
 }

+ 6 - 1
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/NestObject.java

@@ -7,9 +7,14 @@ import lombok.Data;
  * @since
  */
 @Data
-public class NestObject {
+public class NestObject implements Cloneable {
     private String name;
 
     private int age;
 
+
+    @Override
+    public NestObject clone() throws CloneNotSupportedException {
+        return (NestObject)super.clone();
+    }
 }