zhouhao преди 7 години
родител
ревизия
0654433016

+ 8 - 0
hsweb-boost/hsweb-boost-compiler/pom.xml

@@ -18,5 +18,13 @@
             <artifactId>javassist</artifactId>
             <version>3.22.0-GA</version>
         </dependency>
+        <dependency>
+            <groupId>commons-beanutils</groupId>
+            <artifactId>commons-beanutils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+        </dependency>
     </dependencies>
 </project>

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

@@ -0,0 +1,6 @@
+package org.hswebframework.web.boost.bean;
+
+@FunctionalInterface
+public interface Converter {
+    <T> T convert(Object source, Class<T> targetClass);
+}

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

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

+ 264 - 0
hsweb-boost/hsweb-boost-compiler/src/main/java/org/hswebframework/web/boost/bean/FastBeanCopier.java

@@ -0,0 +1,264 @@
+package org.hswebframework.web.boost.bean;
+
+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.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.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author zhouhao
+ * @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 PropertyUtilsBean propertyUtils = BeanUtilsBean.getInstance().getPropertyUtils();
+
+    private static final Map<Class, Class> wrapperClassMapping = new HashMap<>();
+
+    public static final DefaultConvert DEFAULT_CONVERT = new DefaultConvert();
+
+    static {
+        wrapperClassMapping.put(byte.class, Byte.class);
+        wrapperClassMapping.put(short.class, Short.class);
+        wrapperClassMapping.put(int.class, Integer.class);
+        wrapperClassMapping.put(float.class, Float.class);
+        wrapperClassMapping.put(double.class, Double.class);
+        wrapperClassMapping.put(char.class, Character.class);
+        wrapperClassMapping.put(boolean.class, Boolean.class);
+        wrapperClassMapping.put(long.class, Long.class);
+    }
+
+    public static void copy(Object source, Object target, String... ignore) {
+        copy(source, target, DEFAULT_CONVERT, ignore);
+    }
+
+    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);
+        if (autoCreate) {
+            return CACHE.computeIfAbsent(key, k -> createCopier(sourceType, targetType));
+        } else {
+            return CACHE.get(key);
+        }
+
+    }
+
+    public static void copy(Object source, Object target, Converter converter, String... ignore) {
+        getCopier(source, target, true).copy(source, target, (ignore == null || ignore.length == 0) ? new HashSet<>() : new HashSet<>(Arrays.asList(ignore)), converter);
+    }
+
+    private static String createCacheKey(Class source, Class target) {
+        return source.getName().concat("=>").concat(target.getName());
+    }
+
+    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" +
+                createCopierCode(source, target) +
+                "\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 "";
+        }
+
+        PropertyCopierGenerator generator = new PropertyCopierGenerator();
+        generator.target = target;
+        generator.source = source;
+
+        return generator.generate();
+    }
+
+    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 {
+                        //源字段不存
+                    }
+                });
+
+        return builder.toString();
+    }
+
+    static class PropertyCopierGenerator {
+        PropertyDescriptor source;
+        PropertyDescriptor target;
+        List<String> lines = new ArrayList<>();
+
+        private boolean targetIsPrimitive() {
+            return target.getPropertyType().isPrimitive();
+        }
+
+        private boolean sourceIsPrimitive() {
+            return source.getPropertyType().isPrimitive();
+        }
+
+        private boolean typeIsWrapper(Class type) {
+            return wrapperClassMapping.values().contains(type);
+        }
+
+        private Class getPrimitiveType(Class type) {
+            return wrapperClassMapping.entrySet().stream()
+                    .filter(entry -> entry.getValue() == type)
+                    .map(Map.Entry::getKey)
+                    .findFirst()
+                    .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() + "()";
+        }
+
+        private boolean notNull() {
+            return !sourceIsPrimitive();
+        }
+
+        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");
+            }
+            convertCode.append("){\n");
+            convertCode.append(target.getPropertyType().getName())
+                    .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());");
+                    } else {
+                        convertCode.append("(").append(target.getPropertyType().getName())
+                                .append(")(")
+                                .append(convert)
+                                .append(");");
+                    }
+                } else {
+                    convertCode.append("(").append(target.getPropertyType().getName())
+                            .append(")(")
+                            .append(convert)
+                            .append(");");
+                }
+            } 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());
+        }
+
+        public String generate() {
+            generateConvert();
+            return String.join("\n", lines.toArray(new String[lines.size()]));
+        }
+    }
+
+    static class DefaultConvert implements Converter {
+
+        @Override
+        public <T> T convert(Object source, Class<T> targetClass) {
+            if (source == null) {
+                return null;
+            }
+            if (targetClass == String.class) {
+                return (T) String.valueOf(source);
+            }
+
+            org.apache.commons.beanutils.Converter converter = BeanUtilsBean
+                    .getInstance()
+                    .getConvertUtils()
+                    .lookup(targetClass);
+            if (null != converter) {
+                return converter.convert(targetClass, source);
+            }
+            try {
+                T newTarget = targetClass.newInstance();
+                copy(source, newTarget);
+                return newTarget;
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return null;
+        }
+    }
+}

+ 0 - 33
hsweb-boost/hsweb-boost-compiler/src/test/java/JaninoTest.java

@@ -1,33 +0,0 @@
-import javassist.ClassClassPath;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.CtNewMethod;
-import org.junit.Test;
-
-
-/**
- * TODO 完成注释
- *
- * @author zhouhao
- * @since
- */
-public class JaninoTest {
-
-    @Test
-    public void Test() throws Exception {
-        ClassPool classPool = new ClassPool(true);
-        classPool.insertClassPath(new ClassClassPath(this.getClass()));
-
-        CtClass ctClass = classPool.makeClass("Test1");
-        ctClass.setInterfaces(new CtClass[]{classPool.get(TestApi.class.getName())});
-
-        ctClass.addMethod(CtNewMethod.make("public void hello(int i){System.out.println(i);}", ctClass));
-
-        TestApi api = (TestApi) ctClass.toClass().newInstance();
-        api.hello(1);
-    }
-
-    interface TestApi {
-        void hello(int i);
-    }
-}

+ 47 - 0
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/FastBeanCopierTest.java

@@ -0,0 +1,47 @@
+package org.hswebframework.web.boost;
+
+import org.hswebframework.web.boost.bean.Converter;
+import org.hswebframework.web.boost.bean.Copier;
+import org.hswebframework.web.boost.bean.FastBeanCopier;
+import org.junit.Test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashSet;
+
+/**
+ * TODO 完成注释
+ *
+ * @author zhouhao
+ * @since
+ */
+public class FastBeanCopierTest {
+
+    @Test
+    public void test() {
+        Source source = new Source();
+        source.setAge(100);
+        source.setName("测试");
+        NestObject nestObject = new NestObject();
+        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();
+        Test2 test2 = new Test2();
+
+        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());
+    }
+
+
+}

+ 14 - 0
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/NestObject.java

@@ -0,0 +1,14 @@
+package org.hswebframework.web.boost;
+
+import lombok.Data;
+
+/**
+ * @author zhouhao
+ * @since
+ */
+@Data
+public class NestObject {
+    private String name;
+
+    private int age;
+}

+ 14 - 0
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/Source.java

@@ -0,0 +1,14 @@
+package org.hswebframework.web.boost;
+
+import lombok.Data;
+
+@Data
+public class Source {
+    private String name;
+    private int    age;
+
+    private NestObject nestObject;
+
+}
+
+

+ 11 - 0
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/Target.java

@@ -0,0 +1,11 @@
+package org.hswebframework.web.boost;
+
+import lombok.Data;
+
+@Data
+public class Target {
+    private String name;
+    private int   age;
+    private NestObject nestObject;
+
+}

+ 32 - 0
hsweb-boost/hsweb-boost-compiler/src/test/java/org/hswebframework/web/boost/Test2.java

@@ -0,0 +1,32 @@
+package org.hswebframework.web.boost;
+
+import org.hswebframework.web.boost.bean.Converter;
+import org.hswebframework.web.boost.bean.Copier;
+
+import java.util.Set;
+
+/**
+ * TODO 完成注释
+ *
+ * @author zhouhao
+ * @since
+ */
+public class Test2 implements Copier{
+    public void copy(Object s, Object t, java.util.Set ignore, org.hswebframework.web.boost.bean.Converter converter){
+        org.hswebframework.web.boost.Source source=(org.hswebframework.web.boost.Source)s;
+        org.hswebframework.web.boost.Target target=(org.hswebframework.web.boost.Target)t;
+        if(!ignore.contains("name")&&source.getName()!=null){
+            java.lang.String name=source.getName();
+            target.setName(name);
+        }
+        if(!ignore.contains("nestObject")&&source.getNestObject()!=null){
+            org.hswebframework.web.boost.NestObject nestObject=source.getNestObject();
+            target.setNestObject(nestObject);
+        }
+
+        if(!ignore.contains("age")){
+            int age=source.getAge();
+            target.setAge(age);
+        }
+    }
+}

+ 1 - 0
hsweb-boost/pom.xml

@@ -32,6 +32,7 @@
     <modules>
         <module>hsweb-boost-validator</module>
         <module>hsweb-boost-aop</module>
+        <module>hsweb-boost-compiler</module>
     </modules>